feat(ct): allow unmounting components (#15974)
This commit is contained in:
		
							parent
							
								
									607910f6aa
								
							
						
					
					
						commit
						2eff208e54
					
				|  | @ -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", | ||||
|  |  | |||
|  | @ -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< | ||||
|  |  | |||
|  | @ -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'); | ||||
| }; | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -47,6 +47,7 @@ interface ComponentFixtures { | |||
|     on?: { [key: string]: Function }, | ||||
|     hooksConfig?: any, | ||||
|   }): Promise<Locator>; | ||||
|   unmount(component: Locator): Promise<void>; | ||||
| } | ||||
| 
 | ||||
| export const test: TestType< | ||||
|  |  | |||
|  | @ -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" | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -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'); | ||||
|  |  | |||
|  | @ -48,6 +48,7 @@ export interface ComponentFixtures { | |||
|     on?: { [key: string]: Function }, | ||||
|     hooksConfig?: any, | ||||
|   }): Promise<Locator>; | ||||
|   unmount(locator: Locator): Promise<void>; | ||||
| } | ||||
| 
 | ||||
| export const test: TestType< | ||||
|  |  | |||
|  | @ -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'); | ||||
|  |  | |||
|  | @ -48,6 +48,7 @@ export interface ComponentFixtures { | |||
|     on?: { [key: string]: Function }, | ||||
|     hooksConfig?: any, | ||||
|   }): Promise<Locator>; | ||||
|   unmount(locator: Locator): Promise<void>; | ||||
| } | ||||
| 
 | ||||
| export const test: TestType< | ||||
|  |  | |||
|  | @ -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'); | ||||
|  |  | |||
|  | @ -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> { | ||||
|  |  | |||
|  | @ -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>; | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -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!'); | ||||
| }); | ||||
|  |  | |||
|  | @ -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" | ||||
|   } | ||||
| } | ||||
| } | ||||
|  |  | |||
|  | @ -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'); | ||||
| }); | ||||
|  |  | |||
|  | @ -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'); | ||||
| }); | ||||
|  |  | |||
|  | @ -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'); | ||||
| }); | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue