feat(ct): vue3 html as slot (#18007)
This commit is contained in:
parent
49901c8ed7
commit
e76adafc5b
|
|
@ -18,6 +18,8 @@
|
||||||
// This file is injected into the registry as text, no dependencies are allowed.
|
// This file is injected into the registry as text, no dependencies are allowed.
|
||||||
|
|
||||||
import { createApp, setDevtoolsHook, h } from 'vue';
|
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('@playwright/test/types/component').Component} Component */
|
||||||
/** @typedef {import('vue').Component} FrameworkComponent */
|
/** @typedef {import('vue').Component} FrameworkComponent */
|
||||||
|
|
@ -43,6 +45,49 @@ function createChild(child) {
|
||||||
return typeof child === 'string' ? child : createWrapper(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
|
* @param {Component} component
|
||||||
*/
|
*/
|
||||||
|
|
@ -109,9 +154,9 @@ function createComponent(component) {
|
||||||
// Vue test util syntax.
|
// Vue test util syntax.
|
||||||
for (const [key, value] of Object.entries(component.options?.slots || {})) {
|
for (const [key, value] of Object.entries(component.options?.slots || {})) {
|
||||||
if (key === 'default')
|
if (key === 'default')
|
||||||
children.push(value);
|
children.push(slotToFunction(value));
|
||||||
else
|
else
|
||||||
slots[key] = value;
|
slots[key] = slotToFunction(value);
|
||||||
}
|
}
|
||||||
props = component.options?.props || {};
|
props = component.options?.props || {};
|
||||||
for (const [key, value] of Object.entries(component.options?.on || {}))
|
for (const [key, value] of Object.entries(component.options?.on || {}))
|
||||||
|
|
|
||||||
|
|
@ -71,18 +71,18 @@ test('emit an submit event when the button is clicked', async ({ mount }) => {
|
||||||
|
|
||||||
test('render a default slot', async ({ mount }) => {
|
test('render a default slot', async ({ mount }) => {
|
||||||
const component = await mount(<DefaultSlot>
|
const component = await mount(<DefaultSlot>
|
||||||
Main Content
|
<strong>Main Content</strong>
|
||||||
</DefaultSlot>)
|
</DefaultSlot>)
|
||||||
await expect(component).toContainText('Main Content')
|
await expect(component.getByRole('strong')).toContainText('Main Content')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('render a component with multiple slots', async ({ mount }) => {
|
test('render a component with multiple slots', async ({ mount }) => {
|
||||||
const component = await mount(<DefaultSlot>
|
const component = await mount(<DefaultSlot>
|
||||||
<div id="one">One</div>
|
<div data-testid="one">One</div>
|
||||||
<div id="two">Two</div>
|
<div data-testid="two">Two</div>
|
||||||
</DefaultSlot>)
|
</DefaultSlot>)
|
||||||
await expect(component.locator('#one')).toContainText('One')
|
await expect(component.getByTestId('one')).toContainText('One')
|
||||||
await expect(component.locator('#two')).toContainText('Two')
|
await expect(component.getByTestId('two')).toContainText('Two')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('render a component with a named slot', async ({ mount }) => {
|
test('render a component with a named slot', async ({ mount }) => {
|
||||||
|
|
|
||||||
|
|
@ -81,20 +81,23 @@ test('emit an submit event when the button is clicked', async ({ mount }) => {
|
||||||
test('render a default slot', async ({ mount }) => {
|
test('render a default slot', async ({ mount }) => {
|
||||||
const component = await mount(DefaultSlot, {
|
const component = await mount(DefaultSlot, {
|
||||||
slots: {
|
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 }) => {
|
test('render a component with multiple slots', async ({ mount }) => {
|
||||||
const component = await mount(DefaultSlot, {
|
const component = await mount(DefaultSlot, {
|
||||||
slots: {
|
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.getByTestId('one')).toContainText('One')
|
||||||
await expect(component).toContainText('two')
|
await expect(component.getByTestId('two')).toContainText('Two')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('render a component with a named slot', async ({ mount }) => {
|
test('render a component with a named slot', async ({ mount }) => {
|
||||||
|
|
|
||||||
|
|
@ -82,20 +82,23 @@ test('emit an submit event when the button is clicked', async ({ mount }) => {
|
||||||
test('render a default slot', async ({ mount }) => {
|
test('render a default slot', async ({ mount }) => {
|
||||||
const component = await mount(DefaultSlot, {
|
const component = await mount(DefaultSlot, {
|
||||||
slots: {
|
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 }) => {
|
test('render a component with multiple slots', async ({ mount }) => {
|
||||||
const component = await mount(DefaultSlot, {
|
const component = await mount(DefaultSlot, {
|
||||||
slots: {
|
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.getByTestId('one')).toContainText('One')
|
||||||
await expect(component).toContainText('two')
|
await expect(component.getByTestId('two')).toContainText('Two')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('render a component with a named slot', async ({ mount }) => {
|
test('render a component with a named slot', async ({ mount }) => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue