feat(ct): allow unmounting components (#15974)

This commit is contained in:
Pavel Feldman 2022-07-27 15:12:36 -07:00 committed by GitHub
parent 607910f6aa
commit 2eff208e54
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 113 additions and 14 deletions

8
package-lock.json generated
View File

@ -5770,7 +5770,6 @@
"version": "3.49.0",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.49.0.tgz",
"integrity": "sha512-+lmjic1pApJWDfPCpUUTc1m8azDqYCG1JN9YEngrx/hUyIcFJo6VZhj0A1Ai0wqoHcEIuQy+e9tk+4uDgdtsFA==",
"peer": true,
"engines": {
"node": ">= 8"
}
@ -6490,6 +6489,9 @@
"@sveltejs/vite-plugin-svelte": "^1.0.1",
"vite": "^3.0.0"
},
"devDependencies": {
"svelte": "^3.49.0"
},
"engines": {
"node": ">=14"
}
@ -7485,6 +7487,7 @@
"requires": {
"@playwright/test": "1.25.0-next",
"@sveltejs/vite-plugin-svelte": "^1.0.1",
"svelte": "*",
"vite": "^3.0.0"
},
"dependencies": {
@ -10959,8 +10962,7 @@
"svelte": {
"version": "3.49.0",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.49.0.tgz",
"integrity": "sha512-+lmjic1pApJWDfPCpUUTc1m8azDqYCG1JN9YEngrx/hUyIcFJo6VZhj0A1Ai0wqoHcEIuQy+e9tk+4uDgdtsFA==",
"peer": true
"integrity": "sha512-+lmjic1pApJWDfPCpUUTc1m8azDqYCG1JN9YEngrx/hUyIcFJo6VZhj0A1Ai0wqoHcEIuQy+e9tk+4uDgdtsFA=="
},
"svelte-hmr": {
"version": "0.14.12",

View File

@ -36,6 +36,7 @@ export type PlaywrightTestConfig = Omit<BasePlaywrightTestConfig, 'use'> & {
export interface ComponentFixtures {
mount(component: JSX.Element, options?: { hooksConfig?: any }): Promise<Locator>;
unmount(component: Locator): Promise<void>;
}
export const test: TestType<

View File

@ -77,3 +77,8 @@ window.playwrightMount = async (component, rootElement, hooksConfig) => {
for (const hook of /** @type {any} */(window).__pw_hooks_after_mount || [])
await hook({ hooksConfig });
};
window.playwrightUnmount = async (element, rootElement) => {
if (!ReactDOM.unmountComponentAtNode(rootElement))
throw new Error('Component was not mounted');
};

View File

@ -14,5 +14,7 @@
* limitations under the License.
*/
import type { SvelteComponent } from "svelte";
export declare function beforeMount(callback: (params: { hooksConfig: any }) => Promise<void>): void;
export declare function afterMount(callback: (params: { hooksConfig: any }) => Promise<void>): void;
export declare function afterMount(callback: (params: { hooksConfig: any, svelteComponent: SvelteComponent }) => Promise<void>): void;

View File

@ -47,6 +47,7 @@ interface ComponentFixtures {
on?: { [key: string]: Function },
hooksConfig?: any,
}): Promise<Locator>;
unmount(component: Locator): Promise<void>;
}
export const test: TestType<

View File

@ -26,8 +26,11 @@
}
},
"dependencies": {
"@sveltejs/vite-plugin-svelte": "^1.0.1",
"@playwright/test": "1.25.0-next",
"@sveltejs/vite-plugin-svelte": "^1.0.1",
"vite": "^3.0.0"
},
"devDependencies": {
"svelte": "^3.49.0"
}
}

View File

@ -20,6 +20,7 @@
/** @typedef {import('../playwright-test/types/component').Component} Component */
/** @typedef {any} FrameworkComponent */
/** @typedef {import('svelte').SvelteComponent} SvelteComponent */
/** @type {Map<string, FrameworkComponent>} */
const registry = new Map();
@ -54,14 +55,24 @@ window.playwrightMount = async (component, rootElement, hooksConfig) => {
for (const hook of /** @type {any} */(window).__pw_hooks_before_mount || [])
await hook({ hooksConfig });
const wrapper = new componentCtor({
const svelteComponent = /** @type {SvelteComponent} */ (new componentCtor({
target: rootElement,
props: component.options?.props,
});
}));
rootElement[svelteComponentKey] = svelteComponent;
for (const hook of /** @type {any} */(window).__pw_hooks_after_mount || [])
await hook({ hooksConfig });
await hook({ hooksConfig, svelteComponent });
for (const [key, listener] of Object.entries(component.options?.on || {}))
wrapper.$on(key, event => listener(event.detail));
svelteComponent.$on(key, event => listener(event.detail));
};
window.playwrightUnmount = async (element, rootElement) => {
const svelteComponent = /** @type {SvelteComponent} */ (rootElement[svelteComponentKey]);
if (!svelteComponent)
throw new Error('Component was not mounted');
svelteComponent.$destroy();
};
const svelteComponentKey = Symbol('svelteComponent');

View File

@ -48,6 +48,7 @@ export interface ComponentFixtures {
on?: { [key: string]: Function },
hooksConfig?: any,
}): Promise<Locator>;
unmount(locator: Locator): Promise<void>;
}
export const test: TestType<

View File

@ -164,6 +164,16 @@ window.playwrightMount = async (component, rootElement, hooksConfig) => {
for (const hook of /** @type {any} */(window).__pw_hooks_before_mount || [])
await hook({ app, hooksConfig });
const instance = app.mount(rootElement);
instance.$el[appKey] = app;
for (const hook of /** @type {any} */(window).__pw_hooks_after_mount || [])
await hook({ app, hooksConfig, instance });
};
window.playwrightUnmount = async element => {
const app = /** @type {import('vue').App} */ (element[appKey]);
if (!app)
throw new Error('Component was not mounted');
app.unmount();
};
const appKey = Symbol('appKey');

View File

@ -48,6 +48,7 @@ export interface ComponentFixtures {
on?: { [key: string]: Function },
hooksConfig?: any,
}): Promise<Locator>;
unmount(locator: Locator): Promise<void>;
}
export const test: TestType<

View File

@ -135,8 +135,6 @@ function render(component, h) {
}
window.playwrightMount = async (component, rootElement, hooksConfig) => {
const config = hooksConfig || /** @type {any} */(component).options?.hooksConfig;
for (const hook of /** @type {any} */(window).__pw_hooks_before_mount || [])
await hook({ hooksConfig });
@ -144,7 +142,18 @@ window.playwrightMount = async (component, rootElement, hooksConfig) => {
render: h => render(component, h),
}).$mount();
rootElement.appendChild(instance.$el);
/** @type {any} */ (instance.$el)[instanceKey] = instance;
for (const hook of /** @type {any} */(window).__pw_hooks_after_mount || [])
await hook({ hooksConfig, instance });
};
window.playwrightUnmount = async element => {
const component = /** @type {any} */(element)[instanceKey];
if (!component)
throw new Error('Component was not mounted');
component.$destroy();
element.remove();
};
const instanceKey = Symbol('instanceKey');

View File

@ -20,7 +20,10 @@ import type { Component, JsxComponent, ObjectComponentOptions } from '../types/c
let boundCallbacksForMount: Function[] = [];
export const fixtures: Fixtures<
PlaywrightTestArgs & PlaywrightTestOptions & { mount: (component: any, options: any) => Promise<Locator> },
PlaywrightTestArgs & PlaywrightTestOptions & {
mount: (component: any, options: any) => Promise<Locator>;
unmount: (locator: Locator) => Promise<void>;
},
PlaywrightWorkerArgs & PlaywrightWorkerOptions & { _ctWorker: { context: BrowserContext | undefined, hash: string } },
{ _contextFactory: (options?: BrowserContextOptions) => Promise<BrowserContext>, _contextReuseEnabled: boolean }> = {
@ -49,6 +52,15 @@ export const fixtures: Fixtures<
});
boundCallbacksForMount = [];
},
unmount: async ({}, use) => {
await use(async (locator: Locator) => {
await locator.evaluate(async element => {
const rootElement = document.getElementById('root')!;
await window.playwrightUnmount(element, rootElement);
});
});
},
};
async function innerMount(page: Page, jsxOrType: JsxComponent | string, options: ObjectComponentOptions = {}): Promise<string> {

View File

@ -39,5 +39,6 @@ export type Component = JsxComponent | ObjectComponent;
declare global {
interface Window {
playwrightMount(component: Component, rootElement: Element, hooksConfig: any): Promise<void>;
playwrightUnmount(element: Element, rootElement: Element): Promise<void>;
}
}

View File

@ -18,3 +18,10 @@ test('should configure app', async ({ page, mount }) => {
});
expect(messages).toEqual(['Before mount: {\"route\":\"A\"}', 'After mount']);
});
test('should unmount', async ({ page, mount, unmount }) => {
const component = await mount(<App></App>);
await expect(page.locator('#root')).toContainText('Hello Vite + React!');
await unmount(component);
await expect(page.locator('#root')).not.toContainText('Hello Vite + React!');
});

View File

@ -12,7 +12,7 @@
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^1.0.1",
"@tsconfig/svelte": "^2.0.1",
"svelte": "^3.44.0",
"svelte": "^3.49.0",
"svelte-check": "^2.2.7",
"svelte-preprocess": "^4.9.8",
"tslib": "^2.3.1",
@ -23,4 +23,4 @@
"@playwright/experimental-ct-svelte": "^1.22.2",
"@playwright/test": "^1.22.2"
}
}
}

View File

@ -47,3 +47,14 @@ test('should configure app', async ({ page, mount }) => {
});
expect(messages).toEqual(['Before mount: {\"route\":\"A\"}', 'After mount']);
});
test('should unmount', async ({ page, mount, unmount }) => {
const component = await mount(Counter, {
props: {
suffix: 'my suffix',
},
});
await expect(page.locator('#root')).toContainText('my suffix')
await unmount(component);
await expect(page.locator('#root')).not.toContainText('my suffix');
});

View File

@ -78,3 +78,14 @@ test('should run hooks', async ({ page, mount }) => {
})
expect(messages).toEqual(['Before mount: {\"route\":\"A\"}, app: true', 'After mount el: HTMLButtonElement'])
})
test('should unmount', async ({ page, mount, unmount }) => {
const component = await mount(Button, {
props: {
title: 'Submit'
}
})
await expect(page.locator('#root')).toContainText('Submit')
await unmount(component);
await expect(page.locator('#root')).not.toContainText('Submit');
});

View File

@ -72,3 +72,14 @@ test('should run hooks', async ({ page, mount }) => {
})
expect(messages).toEqual(['Before mount: {\"route\":\"A\"}', 'After mount el: HTMLButtonElement'])
})
test('should unmount', async ({ page, mount, unmount }) => {
const component = await mount(Button, {
props: {
title: 'Submit'
}
})
await expect(page.locator('#root')).toContainText('Submit')
await unmount(component);
await expect(page.locator('#root')).not.toContainText('Submit');
});