feat(ct): vue3 html as slot (#18007)

This commit is contained in:
sand4rt 2022-10-18 22:06:36 +02:00 committed by GitHub
parent 49901c8ed7
commit e76adafc5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 69 additions and 18 deletions

View File

@ -18,6 +18,8 @@
// This file is injected into the registry as text, no dependencies are allowed.
import { createApp, setDevtoolsHook, h } from 'vue';
import { compile } from '@vue/compiler-dom';
import * as Vue from 'vue';
/** @typedef {import('@playwright/test/types/component').Component} Component */
/** @typedef {import('vue').Component} FrameworkComponent */
@ -43,6 +45,49 @@ function createChild(child) {
return typeof child === 'string' ? child : createWrapper(child);
}
/**
* Copied from: https://github.com/vuejs/test-utils/blob/main/src/utils/compileSlots.ts
* Vue does not provide an easy way to compile template in "slot" mode
* Since we do not want to rely on compiler internals and specify
* transforms manually we create fake component invocation with the slot we
* need and pick slots param from render function later. Fake component will
* never be instantiated but it requires to be a component so compile
* properly generate invocation. Since we do not want to monkey-patch
* `resolveComponent` function we are just using one of built-in components.
*
* @param {string} html
*/
function createSlot(html) {
let template = html.trim();
const hasWrappingTemplate = template && template.startsWith('<template');
// allow content without `template` tag, for easier testing
if (!hasWrappingTemplate)
template = `<template #default="params">${template}</template>`;
const { code } = compile(`<transition>${template}</transition>`, {
mode: 'function',
prefixIdentifiers: false
});
const createRenderFunction = new Function('Vue', code);
const renderFn = createRenderFunction(Vue);
return (ctx = {}) => {
const result = renderFn(ctx);
const slotName = Object.keys(result.children)[0];
return result.children[slotName](ctx);
};
}
function slotToFunction(slot) {
if (typeof slot === 'string')
return createSlot(slot)();
if (Array.isArray(slot))
return slot.map(slot => createSlot(slot)());
throw Error(`Invalid slot received.`);
}
/**
* @param {Component} component
*/
@ -109,9 +154,9 @@ function createComponent(component) {
// Vue test util syntax.
for (const [key, value] of Object.entries(component.options?.slots || {})) {
if (key === 'default')
children.push(value);
children.push(slotToFunction(value));
else
slots[key] = value;
slots[key] = slotToFunction(value);
}
props = component.options?.props || {};
for (const [key, value] of Object.entries(component.options?.on || {}))

View File

@ -71,18 +71,18 @@ test('emit an submit event when the button is clicked', async ({ mount }) => {
test('render a default slot', async ({ mount }) => {
const component = await mount(<DefaultSlot>
Main Content
<strong>Main Content</strong>
</DefaultSlot>)
await expect(component).toContainText('Main Content')
await expect(component.getByRole('strong')).toContainText('Main Content')
})
test('render a component with multiple slots', async ({ mount }) => {
const component = await mount(<DefaultSlot>
<div id="one">One</div>
<div id="two">Two</div>
<div data-testid="one">One</div>
<div data-testid="two">Two</div>
</DefaultSlot>)
await expect(component.locator('#one')).toContainText('One')
await expect(component.locator('#two')).toContainText('Two')
await expect(component.getByTestId('one')).toContainText('One')
await expect(component.getByTestId('two')).toContainText('Two')
})
test('render a component with a named slot', async ({ mount }) => {

View File

@ -81,20 +81,23 @@ test('emit an submit event when the button is clicked', async ({ mount }) => {
test('render a default slot', async ({ mount }) => {
const component = await mount(DefaultSlot, {
slots: {
default: 'Main Content'
default: '<strong>Main Content</strong>'
}
})
await expect(component).toContainText('Main Content')
await expect(component.getByRole('strong')).toContainText('Main Content')
})
test('render a component with multiple slots', async ({ mount }) => {
const component = await mount(DefaultSlot, {
slots: {
default: ['one', 'two']
default: [
'<div data-testid="one">One</div>',
'<div data-testid="two">Two</div>'
]
}
})
await expect(component).toContainText('one')
await expect(component).toContainText('two')
await expect(component.getByTestId('one')).toContainText('One')
await expect(component.getByTestId('two')).toContainText('Two')
})
test('render a component with a named slot', async ({ mount }) => {

View File

@ -82,20 +82,23 @@ test('emit an submit event when the button is clicked', async ({ mount }) => {
test('render a default slot', async ({ mount }) => {
const component = await mount(DefaultSlot, {
slots: {
default: 'Main Content'
default: '<strong>Main Content</strong>'
}
})
await expect(component).toContainText('Main Content')
await expect(component.getByRole('strong')).toContainText('Main Content')
})
test('render a component with multiple slots', async ({ mount }) => {
const component = await mount(DefaultSlot, {
slots: {
default: ['one', 'two']
default: [
'<div data-testid="one">One</div>',
'<div data-testid="two">Two</div>'
]
}
})
await expect(component).toContainText('one')
await expect(component).toContainText('two')
await expect(component.getByTestId('one')).toContainText('One')
await expect(component.getByTestId('two')).toContainText('Two')
})
test('render a component with a named slot', async ({ mount }) => {