diff --git a/packages/playwright-ct-svelte/index.d.ts b/packages/playwright-ct-svelte/index.d.ts index 16f0f9f05a..f7cb89e919 100644 --- a/packages/playwright-ct-svelte/index.d.ts +++ b/packages/playwright-ct-svelte/index.d.ts @@ -42,22 +42,23 @@ type JsonObject = { [Key in string]?: JsonValue }; type Slot = string | string[]; -export interface MountOptions { +export interface MountOptions { props?: ComponentProps; slots?: Record & { default?: Slot }; on?: Record; hooksConfig?: JsonObject; } -interface MountResult extends Locator { +interface MountResult extends Locator { unmount(): Promise; + rerender(options: Omit, 'hooksConfig'|'slots'>): Promise } interface ComponentFixtures { mount( component: new (...args: any[]) => Component, options?: MountOptions - ): Promise; + ): Promise>; } export const test: TestType< diff --git a/packages/playwright-ct-svelte/registerSource.mjs b/packages/playwright-ct-svelte/registerSource.mjs index 8e7b2b1d8e..6f7c7be600 100644 --- a/packages/playwright-ct-svelte/registerSource.mjs +++ b/packages/playwright-ct-svelte/registerSource.mjs @@ -66,6 +66,8 @@ function createSlots(slots) { return svelteSlots; } +const svelteComponentKey = Symbol('svelteComponent'); + window.playwrightMount = async (component, rootElement, hooksConfig) => { let componentCtor = registry.get(component.type); if (!componentCtor) { @@ -98,11 +100,11 @@ window.playwrightMount = async (component, rootElement, hooksConfig) => { })); rootElement[svelteComponentKey] = svelteComponent; - for (const hook of /** @type {any} */(window).__pw_hooks_after_mount || []) - await hook({ hooksConfig, svelteComponent }); - for (const [key, listener] of Object.entries(component.options?.on || {})) svelteComponent.$on(key, event => listener(event.detail)); + + for (const hook of /** @type {any} */(window).__pw_hooks_after_mount || []) + await hook({ hooksConfig, svelteComponent }); }; window.playwrightUnmount = async rootElement => { @@ -112,4 +114,14 @@ window.playwrightUnmount = async rootElement => { svelteComponent.$destroy(); }; -const svelteComponentKey = Symbol('svelteComponent'); +window.playwrightRerender = async (rootElement, component) => { + const svelteComponent = /** @type {SvelteComponent} */ (rootElement[svelteComponentKey]); + if (!svelteComponent) + throw new Error('Component was not mounted'); + + for (const [key, listener] of Object.entries(component.options?.on || {})) + svelteComponent.$on(key, event => listener(event.detail)); + + if (component.options?.props) + svelteComponent.$set(component.options.props); +}; diff --git a/tests/components/ct-svelte-vite/src/components/Counter.svelte b/tests/components/ct-svelte-vite/src/components/Counter.svelte new file mode 100644 index 0000000000..2b8a064b46 --- /dev/null +++ b/tests/components/ct-svelte-vite/src/components/Counter.svelte @@ -0,0 +1,14 @@ + + +
dispatch('submit', 'hello')}> +
{count}
+
{remountCount}
+ + +
diff --git a/tests/components/ct-svelte-vite/src/store/index.ts b/tests/components/ct-svelte-vite/src/store/index.ts new file mode 100644 index 0000000000..43d7ee31fc --- /dev/null +++ b/tests/components/ct-svelte-vite/src/store/index.ts @@ -0,0 +1,4 @@ +export let remountCount = 0; +export function update() { + remountCount++; +} diff --git a/tests/components/ct-svelte-vite/src/tests.spec.ts b/tests/components/ct-svelte-vite/src/tests.spec.ts index c10344a8b6..415241dba3 100644 --- a/tests/components/ct-svelte-vite/src/tests.spec.ts +++ b/tests/components/ct-svelte-vite/src/tests.spec.ts @@ -16,6 +16,7 @@ import { test, expect } from '@playwright/experimental-ct-svelte'; import Button from './components/Button.svelte'; +import Counter from './components/Counter.svelte'; import DefaultSlot from './components/DefaultSlot.svelte'; import NamedSlots from './components/NamedSlots.svelte'; import MultiRoot from './components/MultiRoot.svelte'; @@ -32,6 +33,36 @@ test('render props', async ({ mount }) => { await expect(component).toContainText('Submit') }) +test('renderer updates props without remounting', async ({ mount }) => { + const component = await mount(Counter, { + props: { count: 9001 } + }) + await expect(component.locator('#props')).toContainText('9001') + + await component.rerender({ + props: { count: 1337 } + }) + await expect(component).not.toContainText('9001') + await expect(component.locator('#props')).toContainText('1337') + + await expect(component.locator('#remount-count')).toContainText('1') +}) + +test('renderer updates event listeners without remounting', async ({ mount }) => { + const component = await mount(Counter) + + const messages: string[] = [] + await component.rerender({ + on: { + submit: (data: string) => messages.push(data) + } + }) + await component.click(); + expect(messages).toEqual(['hello']) + + await expect(component.locator('#remount-count')).toContainText('1') +}) + test('emit an submit event when the button is clicked', async ({ mount }) => { const messages = [] const component = await mount(Button, { diff --git a/tests/components/ct-svelte/src/components/Counter.svelte b/tests/components/ct-svelte/src/components/Counter.svelte new file mode 100644 index 0000000000..2b8a064b46 --- /dev/null +++ b/tests/components/ct-svelte/src/components/Counter.svelte @@ -0,0 +1,14 @@ + + +
dispatch('submit', 'hello')}> +
{count}
+
{remountCount}
+ + +
diff --git a/tests/components/ct-svelte/src/store/index.ts b/tests/components/ct-svelte/src/store/index.ts new file mode 100644 index 0000000000..43d7ee31fc --- /dev/null +++ b/tests/components/ct-svelte/src/store/index.ts @@ -0,0 +1,4 @@ +export let remountCount = 0; +export function update() { + remountCount++; +} diff --git a/tests/components/ct-svelte/src/tests.spec.ts b/tests/components/ct-svelte/src/tests.spec.ts index 60fcc94c3b..d1c3d007b9 100644 --- a/tests/components/ct-svelte/src/tests.spec.ts +++ b/tests/components/ct-svelte/src/tests.spec.ts @@ -16,6 +16,7 @@ import { test, expect } from '@playwright/experimental-ct-svelte'; import Button from './components/Button.svelte'; +import Counter from './components/Counter.svelte'; import Component from './components/Component.svelte'; import DefaultSlot from './components/DefaultSlot.svelte'; import NamedSlots from './components/NamedSlots.svelte' @@ -33,6 +34,36 @@ test('render props', async ({ mount }) => { await expect(component).toContainText('Submit') }) +test('renderer updates props without remounting', async ({ mount }) => { + const component = await mount(Counter, { + props: { count: 9001 } + }) + await expect(component.locator('#props')).toContainText('9001') + + await component.rerender({ + props: { count: 1337 } + }) + await expect(component).not.toContainText('9001') + await expect(component.locator('#props')).toContainText('1337') + + await expect(component.locator('#remount-count')).toContainText('1') +}) + +test('renderer updates event listeners without remounting', async ({ mount }) => { + const component = await mount(Counter) + + const messages: string[] = [] + await component.rerender({ + on: { + submit: (data: string) => messages.push(data) + } + }) + await component.click(); + expect(messages).toEqual(['hello']) + + await expect(component.locator('#remount-count')).toContainText('1') +}) + test('emit an submit event when the button is clicked', async ({ mount }) => { const messages = [] const component = await mount(Button, {