refactor: make all components svelte5 compliant and update tests

This commit is contained in:
Marcin Wiśniowski 2025-02-15 14:38:24 +01:00
parent 3e0a19230d
commit 53d6d57854
7 changed files with 63 additions and 31 deletions

View file

@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import type { Snippet } from "svelte"
import type { import type {
SvelteComponent as V4Component, SvelteComponent as V4Component,
Component as V5Component, Component as V5Component,
@ -21,7 +22,7 @@ import type {
} from 'svelte/types/runtime'; } from 'svelte/types/runtime';
import type { TestType, Locator } from '@playwright/experimental-ct-core'; import type { TestType, Locator } from '@playwright/experimental-ct-core';
type ComponentSlot = string | string[]; type ComponentSlot = Snippet | string;
type ComponentSlots = Record<string, ComponentSlot> & { default?: ComponentSlot }; type ComponentSlots = Record<string, ComponentSlot> & { default?: ComponentSlot };
type ComponentEvents = Record<string, Function>; type ComponentEvents = Record<string, Function>;

View file

@ -19,10 +19,10 @@
// This file is injected into the registry as text, no dependencies are allowed. // This file is injected into the registry as text, no dependencies are allowed.
import { asClassComponent } from 'svelte/legacy'; import { asClassComponent } from 'svelte/legacy';
import { createRawSnippet } from "svelte";
/** @typedef {import('../playwright-ct-core/types/component').Component} Component */ /** @typedef {import('../playwright-ct-core/types/component').Component} Component */
/** @typedef {import('../playwright-ct-core/types/component').ObjectComponent} ObjectComponent */ /** @typedef {import('../playwright-ct-core/types/component').ObjectComponent} ObjectComponent */
/** @typedef {any} FrameworkComponent */
/** @typedef {import('svelte').SvelteComponent} SvelteComponent */ /** @typedef {import('svelte').SvelteComponent} SvelteComponent */
/** @typedef {import('svelte').ComponentType} ComponentType */ /** @typedef {import('svelte').ComponentType} ComponentType */
@ -38,6 +38,30 @@ function isObjectComponent(component) {
); );
} }
/** @type {( component: ObjectComponent ) => Record<string, any>} */
function extractParams(component) {
let {props, slots, on} = component;
slots = Object.fromEntries(
Object.entries(slots ?? {}).map(([key, snippet]) => {
if(typeof snippet === "string") {
console.log("ugraded", key);
return [key, createRawSnippet(() => ({render: () => snippet}))];
}
return [key, snippet]
})
);
on = Object.fromEntries(
Object.entries(on ?? {}).map(([key, fn]) => {
return [`on${key}`, fn]
})
);
return {props, slots, on};
}
const __pwSvelteComponentKey = Symbol('svelteComponent'); const __pwSvelteComponentKey = Symbol('svelteComponent');
window.playwrightMount = async (component, rootElement, hooksConfig) => { window.playwrightMount = async (component, rootElement, hooksConfig) => {
@ -45,17 +69,20 @@ window.playwrightMount = async (component, rootElement, hooksConfig) => {
throw new Error('JSX mount notation is not supported'); throw new Error('JSX mount notation is not supported');
const objectComponent = component; const objectComponent = component;
component.type = component.type;
/** @type {ComponentType} */
const componentCtor = asClassComponent(component.type); const componentCtor = asClassComponent(component.type);
class App extends componentCtor { class App extends componentCtor {
constructor(options = {}) { constructor(options = {}) {
if (!isObjectComponent(component))
throw new Error('JSX mount notation is not supported');
let {props, slots, on} = extractParams(component);
super({ super({
target: rootElement, target: rootElement,
props: { props: {
...objectComponent.props, ...props,
$$scope: {} ...slots,
...on,
}, },
...options ...options
}); });
@ -63,9 +90,7 @@ window.playwrightMount = async (component, rootElement, hooksConfig) => {
} }
/** @type {SvelteComponent | undefined} */ /** @type {SvelteComponent | undefined} */
let svelteComponent; let svelteComponent;
for (const hook of window.__pw_hooks_before_mount || []) { for (const hook of window.__pw_hooks_before_mount || []) {
svelteComponent = await hook({ hooksConfig, App }); svelteComponent = await hook({ hooksConfig, App });
} }
@ -76,9 +101,6 @@ window.playwrightMount = async (component, rootElement, hooksConfig) => {
rootElement[__pwSvelteComponentKey] = svelteComponent; rootElement[__pwSvelteComponentKey] = svelteComponent;
for (const [key, listener] of Object.entries(objectComponent.on || {}))
svelteComponent.$on(key, event => listener(event.detail));
for (const hook of window.__pw_hooks_after_mount || []) for (const hook of window.__pw_hooks_after_mount || [])
await hook({ hooksConfig, svelteComponent }); await hook({ hooksConfig, svelteComponent });
}; };
@ -101,8 +123,11 @@ window.playwrightUpdate = async (rootElement, component) => {
); );
if (!svelteComponent) throw new Error('Component was not mounted'); if (!svelteComponent) throw new Error('Component was not mounted');
for (const [key, listener] of Object.entries(component.on || {})) let {props, slots, on} = extractParams(component);
svelteComponent.$on(key, event => listener(event.detail));
if (component.props) svelteComponent.$set(component.props); svelteComponent.$set({
...props,
...slots,
...on,
});
}; };

View file

@ -1,7 +1,5 @@
<script> <script>
import { createEventDispatcher } from "svelte"; const { title, onsubmit } = $props();
export let title;
const dispatch = createEventDispatcher();
</script> </script>
<button on:click={() => dispatch('submit', 'hello')}>{title}</button> <button onclick={() => onsubmit('hello')}>{title}</button>

View file

@ -1,14 +1,12 @@
<script> <script>
import { update, remountCount } from '../store' import { update, remountCount } from '../store'
import { createEventDispatcher } from "svelte"; const { count, onsubmit, main, children } = $props()
export let count;
const dispatch = createEventDispatcher();
update(); update();
</script> </script>
<button on:click={() => dispatch('submit', 'hello')}> <button onclick={() => onsubmit?.('hello')}>
<span data-testid="props">{count}</span> <span data-testid="props">{count}</span>
<span data-testid="remount-count">{remountCount}</span> <span data-testid="remount-count">{remountCount}</span>
<slot name="main" /> {@render main?.()}
<slot /> {@render children?.()}
</button> </button>

View file

@ -1,7 +1,11 @@
<script>
const {children} = $props()
</script>
<div> <div>
<h1>Welcome!</h1> <h1>Welcome!</h1>
<main> <main>
<slot /> {@render children?.()}
</main> </main>
<footer> <footer>
Thanks for visiting. Thanks for visiting.

View file

@ -1,11 +1,17 @@
<script>
const {header, main, footer} = $props()
console.log({header, main, footer});
</script>
<div> <div>
<header> <header>
<slot name="header" /> {@render header?.()}
</header> </header>
<main> <main>
<slot name="main" /> {@render main?.()}
</main> </main>
<footer> <footer>
<slot name="footer" /> {@render footer?.()}
</footer> </footer>
</div> </div>

View file

@ -5,7 +5,7 @@ import NamedSlots from '@/components/NamedSlots.svelte';
test('render a default slot', async ({ mount }) => { test('render a default slot', async ({ mount }) => {
const component = await mount(DefaultSlot, { const component = await mount(DefaultSlot, {
slots: { slots: {
default: 'Main Content', children: 'Main Content',
}, },
}); });
await expect(component).toContainText('Main Content'); await expect(component).toContainText('Main Content');