Hone logo
Hone
Problems

Vue Test Renderer for Component Rendering and Assertion

This challenge focuses on building a simplified Vue test renderer. You will create a function that takes a Vue component definition and its props, mounts it in a virtual environment, and returns a "test instance" that allows you to inspect and assert on its rendered output. This is a fundamental tool for unit testing Vue components, enabling you to verify their behavior without a full browser DOM.

Problem Description

You are tasked with creating a vueTestRenderer function. This function should simulate the rendering of a Vue component and provide an interface to access its rendered output and perform basic assertions.

What needs to be achieved:

  1. Component Mounting: The vueTestRenderer should accept a Vue component definition (as an Options API object or a setup function) and an optional object of props.
  2. Virtual Rendering: It should "render" this component into a virtual representation. For this challenge, we'll abstract away the actual DOM manipulation and focus on generating a structured output representing the component's content.
  3. Test Instance Return: The function should return a "test instance" object. This instance will contain methods to interact with and inspect the rendered component.

Key Requirements:

  • The vueTestRenderer function should be written in TypeScript.
  • It should support rendering basic Vue components, including those using the Options API and Composition API.
  • The test instance should expose a way to access the rendered output (e.g., a html property or a getHtml() method).
  • The test instance should allow for simple assertions, such as checking for the presence of specific text or attributes.

Expected Behavior:

When vueTestRenderer is called with a component and props, it should return an object that accurately reflects what the component would render. You should be able to inspect this rendered output.

Edge Cases to Consider:

  • Components with no props.
  • Components with deeply nested structures.
  • Components that conditionally render elements.
  • Components with dynamic content.

Examples

Example 1:

import { defineComponent, ref } from 'vue';

// Component Definition (Options API)
const MyButtonOptions = {
  props: {
    label: String,
    disabled: Boolean,
  },
  template: '<button :disabled="disabled">{{ label }}</button>',
};

// Mock vueTestRenderer function
const vueTestRenderer = (component: any, props: Record<string, any> = {}) => {
  // ... implementation ...
  // For demonstration, let's assume it returns a simplified structure
  const renderedHtml = `<button ${props.disabled ? 'disabled' : ''}>${props.label || 'Default Label'}</button>`;
  return {
    html: renderedHtml,
    // For this example, we'll just return the raw html
  };
};

const result = vueTestRenderer(MyButtonOptions, { label: 'Click Me', disabled: false });

console.log(result.html);
// Expected Output: '<button >Click Me</button>'

Example 2:

import { defineComponent, ref } from 'vue';

// Component Definition (Composition API)
const GreetingComponent = defineComponent({
  props: {
    name: String,
  },
  setup(props) {
    const message = ref(`Hello, ${props.name}!`);
    return { message };
  },
  template: '<div>{{ message }}</div>',
});

// Mock vueTestRenderer function (same as above for simplicity)
const vueTestRenderer = (component: any, props: Record<string, any> = {}) => {
    // ... implementation ...
    // Simplified rendering for Composition API
    let renderedHtml = '';
    if (component.setup) {
        const setupContext = { props };
        const vm = component.setup(props);
        // Very basic template compilation simulation
        if (component.template) {
            renderedHtml = component.template.replace(/{{(.*?)}}/g, (match, key) => {
                const cleanKey = key.trim();
                return vm[cleanKey] !== undefined ? vm[cleanKey] : '';
            });
        }
    } else {
        // Options API handling (simplified)
        renderedHtml = component.template.replace(/{{(.*?)}}/g, (match, key) => {
            const cleanKey = key.trim();
            return props[cleanKey] !== undefined ? props[cleanKey] : '';
        });
    }
    return {
        html: renderedHtml,
    };
};


const result = vueTestRenderer(GreetingComponent, { name: 'Alice' });

console.log(result.html);
// Expected Output: '<div>Hello, Alice!</div>'

Example 3:

import { defineComponent, ref } from 'vue';

const ConditionalMessage = defineComponent({
  props: {
    showMessage: Boolean,
  },
  setup(props) {
    return {
      message: 'This is a secret message.',
      showMessage: props.showMessage,
    };
  },
  template: '<div><p v-if="showMessage">{{ message }}</p></div>',
});

// Mock vueTestRenderer function (same as above)
const vueTestRenderer = (component: any, props: Record<string, any> = {}) => {
    // ... implementation ...
    let renderedHtml = '';
    if (component.setup) {
        const setupContext = { props };
        const vm = component.setup(props);
        if (component.template) {
            renderedHtml = component.template;
            // Basic v-if simulation
            if (vm.showMessage === false && renderedHtml.includes('v-if="showMessage"')) {
                renderedHtml = renderedHtml.replace(/<p[^>]*>.*?<\/p>/s, '');
            }
            renderedHtml = renderedHtml.replace(/{{(.*?)}}/g, (match, key) => {
                const cleanKey = key.trim();
                return vm[cleanKey] !== undefined ? vm[cleanKey] : '';
            });
        }
    }
    return {
        html: renderedHtml,
    };
};


const result1 = vueTestRenderer(ConditionalMessage, { showMessage: true });
console.log(result1.html);
// Expected Output: '<div><p >This is a secret message.</p></div>'

const result2 = vueTestRenderer(ConditionalMessage, { showMessage: false });
console.log(result2.html);
// Expected Output: '<div></div>'

Constraints

  • Your vueTestRenderer function should accept a Vue component definition (either a component options object or a defineComponent call result) and an optional props object.
  • The props object will be a Record<string, any>.
  • The output html property from the test instance should be a string representing the rendered HTML.
  • You do not need to implement complex Vue features like directives (v-model, v-for), lifecycle hooks, or reactivity beyond basic prop handling and simple template interpolations. Focus on the core rendering and inspection.
  • The solution should be efficient enough for typical unit testing scenarios.

Notes

This challenge is a simplified implementation of how libraries like @vue/test-utils work. The goal is to understand the core concepts of rendering components in a testing environment and creating an interface for assertions.

Think about how Vue handles props and templates. You'll likely need to simulate the process of merging props with default values and interpolating dynamic content within templates. For the Composition API, consider how setup returns values that are then used in the template.

For assertions, consider adding a findText(selector: string) method to the test instance that returns a boolean indicating if the text is present, or perhaps a method to get all text content. For this challenge, focusing on a simple html property is sufficient.

Loading editor...
typescript