From da9276bd039e5cff7147a3757095e6969478c748 Mon Sep 17 00:00:00 2001 From: sand4rt Date: Mon, 10 Oct 2022 21:49:52 +0200 Subject: [PATCH] feat(ct): vue2 rerender complete (#17929) Partial fix for https://github.com/microsoft/playwright/issues/15919 --- packages/playwright-ct-vue/index.d.ts | 8 +-- packages/playwright-ct-vue2/index.d.ts | 9 ++- .../playwright-ct-vue2/registerSource.mjs | 61 ++++++++++++++----- .../ct-vue2-cli/src/components/Counter.vue | 10 +-- .../ct-vue2-cli/src/notation-jsx.spec.tsx | 36 +++++++++-- .../ct-vue2-cli/src/notation-vue.spec.ts | 52 +++++++++++++--- 6 files changed, 137 insertions(+), 39 deletions(-) diff --git a/packages/playwright-ct-vue/index.d.ts b/packages/playwright-ct-vue/index.d.ts index c24dfcdfec..b67fec8912 100644 --- a/packages/playwright-ct-vue/index.d.ts +++ b/packages/playwright-ct-vue/index.d.ts @@ -42,10 +42,10 @@ type JsonObject = { [Key in string]?: JsonValue }; type Slot = string | string[]; export interface MountOptions> { - props?: Props, + props?: Props; slots?: Record & { default?: Slot }; - on?: Record, - hooksConfig?: JsonObject, + on?: Record; + hooksConfig?: JsonObject; } interface MountResult> extends Locator { @@ -55,7 +55,7 @@ interface MountResult> extends Locator { interface MountResultJsx extends Locator { unmount(): Promise; - rerender(props: JSX.Element): Promise + rerender(component: JSX.Element): Promise } export interface ComponentFixtures { diff --git a/packages/playwright-ct-vue2/index.d.ts b/packages/playwright-ct-vue2/index.d.ts index 96a86ecb3d..b67fec8912 100644 --- a/packages/playwright-ct-vue2/index.d.ts +++ b/packages/playwright-ct-vue2/index.d.ts @@ -50,11 +50,16 @@ export interface MountOptions> { interface MountResult> extends Locator { unmount(): Promise; - rerender(options: { props: Props }): Promise + rerender(options: Omit, 'hooksConfig'>): Promise +} + +interface MountResultJsx extends Locator { + unmount(): Promise; + rerender(component: JSX.Element): Promise } export interface ComponentFixtures { - mount(component: JSX.Element): Promise; + mount(component: JSX.Element): Promise; mount(component: any, options?: MountOptions): Promise; mount(component: any, options: MountOptions & { props: Props }): Promise>; } diff --git a/packages/playwright-ct-vue2/registerSource.mjs b/packages/playwright-ct-vue2/registerSource.mjs index 70a8946399..585258cb1a 100644 --- a/packages/playwright-ct-vue2/registerSource.mjs +++ b/packages/playwright-ct-vue2/registerSource.mjs @@ -37,18 +37,16 @@ export function register(components) { /** * @param {Component | string} child * @param {import('vue').CreateElement} h - * @returns {import('vue').VNode | string} */ -function renderChild(child, h) { - return typeof child === 'string' ? child : render(child, h); +function createChild(child, h) { + return typeof child === 'string' ? child : createWrapper(child, h); } /** * @param {Component} component * @param {import('vue').CreateElement} h - * @returns {import('vue').VNode} */ -function render(component, h) { +function createComponent(component, h) { /** * @type {import('vue').Component | string | undefined} */ @@ -87,9 +85,9 @@ function render(component, h) { if (typeof child !== 'string' && child.type === 'template' && child.kind === 'jsx') { const slotProperty = Object.keys(child.props).find(k => k.startsWith('v-slot:')); const slot = slotProperty ? slotProperty.substring('v-slot:'.length) : 'default'; - nodeData.scopedSlots[slot] = () => child.children.map(c => renderChild(c, h)); + nodeData.scopedSlots[slot] = () => child.children.map(c => createChild(c, h)); } else { - children.push(renderChild(child, h)); + children.push(createChild(child, h)); } } @@ -110,7 +108,7 @@ function render(component, h) { // Vue test util syntax. const options = component.options || {}; for (const [key, value] of Object.entries(options.slots || {})) { - const list = (Array.isArray(value) ? value : [value]).map(v => renderChild(v, h)); + const list = (Array.isArray(value) ? value : [value]).map(v => createChild(v, h)); if (key === 'default') children.push(...list); else @@ -130,18 +128,33 @@ function render(component, h) { lastArg = children; } - const wrapper = h(componentFunc, nodeData, lastArg); + return { Component: componentFunc, nodeData, slots: lastArg }; +} + +/** + * @param {Component} component + * @param {import('vue').CreateElement} h + * @returns {import('vue').VNode} + */ +function createWrapper(component, h) { + const { Component, nodeData, slots } = createComponent(component, h); + const wrapper = h(Component, nodeData, slots); return wrapper; } const instanceKey = Symbol('instanceKey'); +const wrapperKey = Symbol('wrapperKey'); window.playwrightMount = async (component, rootElement, hooksConfig) => { for (const hook of /** @type {any} */(window).__pw_hooks_before_mount || []) await hook({ hooksConfig }); const instance = new Vue({ - render: h => render(component, h), + render: h => { + const wrapper = createWrapper(component, h); + /** @type {any} */ (rootElement)[wrapperKey] = wrapper; + return wrapper; + }, }).$mount(); rootElement.appendChild(instance.$el); /** @type {any} */ (rootElement)[instanceKey] = instance; @@ -159,10 +172,30 @@ window.playwrightUnmount = async rootElement => { }; window.playwrightRerender = async (element, options) => { - const component = /** @type {any} */(element)[instanceKey]; - if (!component) + const wrapper = /** @type {any} */(element)[wrapperKey]; + if (!wrapper) throw new Error('Component was not mounted'); - for (const [key, value] of Object.entries(/** @type {any} */(options).props || /** @type {any} */(options).options.props)) - component.$children[0][key] = value; + const component = wrapper.componentInstance; + const { nodeData, slots } = createComponent(options, component.$createElement); + + + for (const [name] of Object.entries(component.$listeners || {})) { + component.$off(name); + delete component.$listeners[name]; + } + + for (const [name, value] of Object.entries(nodeData.on || {})) { + component.$on(name, value); + component.$listeners[name] = value; + } + + Object.assign(component.$scopedSlots, nodeData.scopedSlots); + component.$slots.default = slots; + + for (const [key, value] of Object.entries(nodeData.props || {})) + component[key] = value; + + if (!Object.keys(nodeData.props || {}).length) + component.$forceUpdate(); }; diff --git a/tests/components/ct-vue2-cli/src/components/Counter.vue b/tests/components/ct-vue2-cli/src/components/Counter.vue index 9f4251ae9f..9998a6cb85 100644 --- a/tests/components/ct-vue2-cli/src/components/Counter.vue +++ b/tests/components/ct-vue2-cli/src/components/Counter.vue @@ -1,8 +1,10 @@