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

fix(ct-react): do not reset mount hooks upon update #29072

Merged
merged 1 commit into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
43 changes: 25 additions & 18 deletions packages/playwright-ct-react/registerSource.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import __pwReact from 'react';
import { createRoot as __pwCreateRoot } from 'react-dom/client';
/** @typedef {import('../playwright-ct-core/types/component').JsxComponent} JsxComponent */

/** @type {Map<Element, import('react-dom/client').Root>} */
/** @type {Map<Element, { root: import('react-dom/client').Root, setRenderer: (renderer: any) => void }>} */
const __pwRootRegistry = new Map();

/**
Expand All @@ -48,43 +48,50 @@ function __pwRender(value) {
window.playwrightMount = async (component, rootElement, hooksConfig) => {
if (!isJsxComponent(component))
throw new Error('Object mount notation is not supported');

let App = () => __pwRender(component);
for (const hook of window.__pw_hooks_before_mount || []) {
const wrapper = await hook({ App, hooksConfig });
if (wrapper)
App = () => wrapper;
}

if (__pwRootRegistry.has(rootElement)) {
throw new Error(
'Attempting to mount a component into an container that already has a React root'
);
}

const root = __pwCreateRoot(rootElement);
__pwRootRegistry.set(rootElement, root);
root.render(App());
const entry = { root, setRenderer: () => undefined };
__pwRootRegistry.set(rootElement, entry);

const App = () => {
/** @type {any} */
const [renderer, setRenderer] = __pwReact.useState(() => __pwRender(component));
entry.setRenderer = setRenderer;
return renderer;
};
let AppWrapper = App;
for (const hook of window.__pw_hooks_before_mount || []) {
const wrapper = await hook({ App: AppWrapper, hooksConfig });
if (wrapper)
AppWrapper = () => wrapper;
}

root.render(__pwReact.createElement(AppWrapper));

for (const hook of window.__pw_hooks_after_mount || [])
await hook({ hooksConfig });
};

window.playwrightUnmount = async rootElement => {
const root = __pwRootRegistry.get(rootElement);
if (root === undefined)
const entry = __pwRootRegistry.get(rootElement);
if (!entry)
throw new Error('Component was not mounted');

root.unmount();
entry.root.unmount();
__pwRootRegistry.delete(rootElement);
};

window.playwrightUpdate = async (rootElement, component) => {
if (!isJsxComponent(component))
throw new Error('Object mount notation is not supported');

const root = __pwRootRegistry.get(rootElement);
if (root === undefined)
const entry = __pwRootRegistry.get(rootElement);
if (!entry)
throw new Error('Component was not mounted');

root.render(__pwRender(component));
entry.setRenderer(() => __pwRender(component));
};
30 changes: 25 additions & 5 deletions packages/playwright-ct-react17/registerSource.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import __pwReact from 'react';
import __pwReactDOM from 'react-dom';
/** @typedef {import('../playwright-ct-core/types/component').JsxComponent} JsxComponent */

/** @type {Map<Element, { setRenderer: (renderer: any) => void }>} */
const __pwRootRegistry = new Map();

/**
* @param {any} component
* @returns {component is JsxComponent}
Expand All @@ -45,15 +48,29 @@ function __pwRender(value) {
window.playwrightMount = async (component, rootElement, hooksConfig) => {
if (!isJsxComponent(component))
throw new Error('Object mount notation is not supported');
if (__pwRootRegistry.has(rootElement)) {
throw new Error(
'Attempting to mount a component into an container that already has a React root'
);
}

let App = () => __pwRender(component);
const entry = { setRenderer: () => undefined };
__pwRootRegistry.set(rootElement, entry);

const App = () => {
/** @type {any} */
const [renderer, setRenderer] = __pwReact.useState(() => __pwRender(component));
entry.setRenderer = setRenderer;
return renderer;
};
let AppWrapper = App;
for (const hook of window.__pw_hooks_before_mount || []) {
const wrapper = await hook({ App, hooksConfig });
const wrapper = await hook({ App: AppWrapper, hooksConfig });
if (wrapper)
App = () => wrapper;
AppWrapper = () => wrapper;
}

__pwReactDOM.render(App(), rootElement);
__pwReactDOM.render(__pwReact.createElement(AppWrapper), rootElement);

for (const hook of window.__pw_hooks_after_mount || [])
await hook({ hooksConfig });
Expand All @@ -68,5 +85,8 @@ window.playwrightUpdate = async (rootElement, component) => {
if (!isJsxComponent(component))
throw new Error('Object mount notation is not supported');

__pwReactDOM.render(__pwRender(component), rootElement);
const entry = __pwRootRegistry.get(rootElement);
if (!entry)
throw new Error('Component was not mounted');
entry.setRenderer(() => __pwRender(component));
};
3 changes: 2 additions & 1 deletion tests/components/ct-react-vite/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import logo from './assets/logo.svg';
import LoginPage from './pages/LoginPage';
import DashboardPage from './pages/DashboardPage';

export default function App() {
export default function App({ title }: { title?: string }) {
return <>
<header>
<img src={logo} alt="logo" width={125} height={125} />
{title && <h1>{title}</h1>}
<Link to="/">Login</Link>
<Link to="/dashboard">Dashboard</Link>
</header>
Expand Down
12 changes: 12 additions & 0 deletions tests/components/ct-react-vite/tests/react-router.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,15 @@ test('navigate to a page by clicking a link', async ({ page, mount }) => {
await expect(component.getByRole('main')).toHaveText('Dashboard');
await expect(page).toHaveURL('/dashboard');
});

test('update should not reset mount hooks', async ({ page, mount }) => {
const component = await mount<HooksConfig>(<App title='before'/>, {
hooksConfig: { routing: true },
});
await expect(component.getByRole('heading')).toHaveText('before');
await expect(component.getByRole('main')).toHaveText('Login');

await component.update(<App title='after'/>);
await expect(component.getByRole('heading')).toHaveText('after');
await expect(component.getByRole('main')).toHaveText('Login');
});
3 changes: 2 additions & 1 deletion tests/components/ct-react17/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import logo from './assets/logo.svg';
import LoginPage from './pages/LoginPage';
import DashboardPage from './pages/DashboardPage';

export default function App() {
export default function App({ title }: { title?: string }) {
return <>
<header>
<img src={logo} alt="logo" width={125} height={125} />
{title && <h1>{title}</h1>}
<Link to="/">Login</Link>
<Link to="/dashboard">Dashboard</Link>
</header>
Expand Down
12 changes: 12 additions & 0 deletions tests/components/ct-react17/tests/react-router.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,15 @@ test('navigate to a page by clicking a link', async ({ page, mount }) => {
await expect(component.getByRole('main')).toHaveText('Dashboard');
await expect(page).toHaveURL('/dashboard');
});

test('update should not reset mount hooks', async ({ page, mount }) => {
const component = await mount<HooksConfig>(<App title='before'/>, {
hooksConfig: { routing: true },
});
await expect(component.getByRole('heading')).toHaveText('before');
await expect(component.getByRole('main')).toHaveText('Login');

await component.update(<App title='after'/>);
await expect(component.getByRole('heading')).toHaveText('after');
await expect(component.getByRole('main')).toHaveText('Login');
});
Loading