Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: hooks config #6

Merged
merged 2 commits into from
Dec 30, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion playwright/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
</head>
<body>
<div id="root"></div>
<script type="module" src="/playwright/index.ts"></script>
<script type="module" src="./index.tsx"></script>
</body>
</html>
Empty file removed playwright/index.ts
Empty file.
15 changes: 15 additions & 0 deletions playwright/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { beforeMount, afterMount } from '../src/hooks.mjs';
import { ThemeContext } from '../tests/components/theme';

export type HooksConfig = {
theme: 'dark' | 'light';
}

beforeMount<HooksConfig>(async ({ hooksConfig, App }) => {
if (hooksConfig?.theme === 'dark')
return <ThemeContext.Provider value="dark"><App /></ThemeContext.Provider>;
});

afterMount<HooksConfig>(async () => {
console.log(`After mount`);
});
15 changes: 13 additions & 2 deletions src/hooks.mts
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
import { JSX } from 'preact/jsx-runtime';

const __pw_hooks_before_mount: any[] = [];
const __pw_hooks_after_mount: any[] = [];

window.__pw_hooks_before_mount = __pw_hooks_before_mount;
window.__pw_hooks_after_mount = __pw_hooks_after_mount;

export const beforeMount = (callback: any) => {
type JsonPrimitive = string | number | boolean | null;
type JsonValue = JsonPrimitive | JsonObject | JsonArray;
type JsonArray = JsonValue[];
type JsonObject = { [Key in string]?: JsonValue };

export const beforeMount = <HooksConfig extends JsonObject>(
callback: (params: { hooksConfig: HooksConfig; App: () => JSX.Element }) => Promise<void | JSX.Element>
) => {
__pw_hooks_before_mount.push(callback);
};

export const afterMount = (callback: any) => {
export const afterMount = <HooksConfig extends JsonObject>(
callback: (params: { hooksConfig: HooksConfig }) => Promise<void>
) => {
__pw_hooks_after_mount.push(callback);
};
25 changes: 24 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import {
// @ts-ignore
_addRunnerPlugin,
PlaywrightTestConfig as BasePlaywrightTestConfig,
Locator,
} from "@playwright/test";
// @ts-ignore
import { fixtures } from "@playwright/test/lib/mount";
import path from "path";
import { JSX } from 'preact/jsx-runtime';
// @ts-ignore
import type { InlineConfig } from "vite";

Expand All @@ -30,6 +32,27 @@ _addRunnerPlugin(() => {
});
});

const test = baseTest.extend(fixtures);
type JsonPrimitive = string | number | boolean | null;
type JsonValue = JsonPrimitive | JsonObject | JsonArray;
type JsonArray = JsonValue[];
type JsonObject = { [Key in string]?: JsonValue };
sand4rt marked this conversation as resolved.
Show resolved Hide resolved

export interface MountOptions<HooksConfig extends JsonObject> {
hooksConfig?: HooksConfig;
}

interface MountResult extends Locator {
unmount(): Promise<void>;
update(component: JSX.Element): Promise<void>;
}

interface ComponentFixtures {
mount<HooksConfig extends JsonObject>(
component: JSX.Element,
options?: MountOptions<HooksConfig>
): Promise<MountResult>;
}

const test = baseTest.extend<ComponentFixtures>(fixtures);

export { test, expect, devices };
21 changes: 11 additions & 10 deletions src/setup.mts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ComponentType, h, render, VNode } from "preact";
import { ComponentType, h, JSX, render, VNode } from "preact";

type JsxComponent = {
kind: "jsx";
Expand All @@ -22,20 +22,17 @@ type ObjectComponent = {

type PwVNode = JsxComponent | ObjectComponent;

type HookConfig = any;
type Mounter = (config: HookConfig) => Promise<void>;

declare global {
interface Window {
playwrightMount(
playwrightMount<HooksConfig = any>(
vnode: PwVNode,
scratch: HTMLElement,
config: HookConfig
config: HooksConfig
): Promise<void>;
playwrightUnmount(scratch: HTMLElement): Promise<void>;
playwrightUpdate(scratch: HTMLElement, vnode: PwVNode): Promise<void>;
__pw_hooks_before_mount: Mounter[];
__pw_hooks_after_mount: Mounter[];
__pw_hooks_before_mount: (<HooksConfig = any>(params: { hooksConfig: HooksConfig; App: () => any }) => Promise<void | JSX.Element>)[];
__pw_hooks_after_mount: (<HooksConfig = any>(params: { hooksConfig: HooksConfig; }) => Promise<void>)[];
}
}

Expand All @@ -58,11 +55,15 @@ function normalizeNode(node: PwVNode | string): string | VNode {
}

window.playwrightMount = async (vnode, scratch, hooksConfig) => {
let App = () => normalizeNode(vnode);
for (const hook of window.__pw_hooks_before_mount || []) {
await hook({ hooksConfig });
const wrapper = await hook({ App, hooksConfig });
if (wrapper) {
App = () => wrapper;
}
}

render(normalizeNode(vnode), scratch);
render(App(), scratch);

for (const hook of window.__pw_hooks_after_mount || []) {
await hook({ hooksConfig });
Expand Down
3 changes: 0 additions & 3 deletions tests/App.tsx

This file was deleted.

7 changes: 7 additions & 0 deletions tests/components/Theme.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { createContext } from 'preact';

export const ThemeContext = createContext('light');

export function Theme() {
return <ThemeContext.Consumer>{theme => theme}</ThemeContext.Consumer>;
}
29 changes: 20 additions & 9 deletions tests/preact.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { DefaultChildren } from './components/DefaultChildren';
import { MultiRoot } from './components/MultiRoot';
import { Counter } from './components/Counter';
import { EmptyFragment } from './components/EmptyFragment';
import { Theme } from './components/theme';
import type { HooksConfig } from '../playwright';

test('render props', async ({ mount }) => {
const component = await mount(<Button title="Submit" />);
Expand Down Expand Up @@ -62,24 +64,24 @@ test('execute callback when the button is clicked', async ({ mount }) => {
test('render a default child', async ({ mount }) => {
const component = await mount(<DefaultChildren>
Main Content
</DefaultChildren>)
await expect(component).toContainText('Main Content')
</DefaultChildren>);
await expect(component).toContainText('Main Content');
});

test('render a component as slot', async ({ mount }) => {
const component = await mount(<DefaultChildren>
<Button title="Submit" />
</DefaultChildren>)
await expect(component).toContainText('Submit')
</DefaultChildren>);
await expect(component).toContainText('Submit');
});

test('render multiple children', async ({ mount }) => {
const component = await mount(<DefaultChildren>
<div id="one">One</div>
<div id="two">Two</div>
</DefaultChildren>)
await expect(component.locator('#one')).toContainText('One')
await expect(component.locator('#two')).toContainText('Two')
</DefaultChildren>);
await expect(component.locator('#one')).toContainText('One');
await expect(component.locator('#two')).toContainText('Two');
});

test('execute callback when a child node is clicked', async ({ mount }) => {
Expand All @@ -92,8 +94,8 @@ test('execute callback when a child node is clicked', async ({ mount }) => {
});

test('unmount', async ({ page, mount }) => {
const component = await mount(<Button title="Submit" />)
await expect(page.locator('#root')).toContainText('Submit')
const component = await mount(<Button title="Submit" />);
await expect(page.locator('#root')).toContainText('Submit');
await component.unmount();
await expect(page.locator('#root')).not.toContainText('Submit');
});
Expand All @@ -113,3 +115,12 @@ test('get textContent of the empty fragment', async ({ mount }) => {
expect(await component.textContent()).toBe('');
await expect(component).toHaveText('');
});

test('hooks configuration', async ({ mount }) => {
const component = await mount<HooksConfig>(<Theme />, {
hooksConfig: {
theme: 'dark'
}
});
await expect(component).toHaveText('dark');
});