From e76adafc5bfae9b08febc6596fa0fde6c74aab80 Mon Sep 17 00:00:00 2001 From: sand4rt Date: Tue, 18 Oct 2022 22:06:36 +0200 Subject: [PATCH] feat(ct): vue3 html as slot (#18007) --- packages/playwright-ct-vue/registerSource.mjs | 49 ++++++++++++++++++- .../ct-vue-vite/src/notation-jsx.spec.tsx | 12 ++--- .../ct-vue-vite/src/notation-vue.spec.js | 13 +++-- .../ct-vue-vite/src/notation-vue.spec.ts | 13 +++-- 4 files changed, 69 insertions(+), 18 deletions(-) diff --git a/packages/playwright-ct-vue/registerSource.mjs b/packages/playwright-ct-vue/registerSource.mjs index 29194e9a56..2dfb41defd 100644 --- a/packages/playwright-ct-vue/registerSource.mjs +++ b/packages/playwright-ct-vue/registerSource.mjs @@ -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}`; + + const { code } = compile(`${template}`, { + 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 || {})) diff --git a/tests/components/ct-vue-vite/src/notation-jsx.spec.tsx b/tests/components/ct-vue-vite/src/notation-jsx.spec.tsx index 3786fa0b62..63569e5849 100644 --- a/tests/components/ct-vue-vite/src/notation-jsx.spec.tsx +++ b/tests/components/ct-vue-vite/src/notation-jsx.spec.tsx @@ -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( - Main Content + Main Content ) - 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( -
One
-
Two
+
One
+
Two
) - 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 }) => { diff --git a/tests/components/ct-vue-vite/src/notation-vue.spec.js b/tests/components/ct-vue-vite/src/notation-vue.spec.js index c39df92e1a..316635c478 100644 --- a/tests/components/ct-vue-vite/src/notation-vue.spec.js +++ b/tests/components/ct-vue-vite/src/notation-vue.spec.js @@ -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: 'Main Content' } }) - 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: [ + '
One
', + '
Two
' + ] } }) - 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 }) => { diff --git a/tests/components/ct-vue-vite/src/notation-vue.spec.ts b/tests/components/ct-vue-vite/src/notation-vue.spec.ts index dbc7a0d4e9..abf3f5e0e4 100644 --- a/tests/components/ct-vue-vite/src/notation-vue.spec.ts +++ b/tests/components/ct-vue-vite/src/notation-vue.spec.ts @@ -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: 'Main Content' } }) - 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: [ + '
One
', + '
Two
' + ] } }) - 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 }) => {