Skip to content

Commit

Permalink
Return an extendable render function when customizing options
Browse files Browse the repository at this point in the history
  • Loading branch information
lemonmade committed May 14, 2024
1 parent c3fd858 commit 0e01748
Show file tree
Hide file tree
Showing 6 changed files with 247 additions and 43 deletions.
6 changes: 6 additions & 0 deletions .changeset/good-mails-help.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@quilted/preact-testing': patch
'@quilted/react-testing': patch
---

Return an extendable render function when customizing `options`
38 changes: 22 additions & 16 deletions packages/preact-testing/source/environment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,12 @@ export function createEnvironment(): Environment {
const allRendered = new Set<Root<any, any, any>>();
const render = createRender({});

return {render, createRender, rendered: allRendered, destroyAll};
return {
render,
createRender: createRender as any,
rendered: allRendered,
destroyAll,
};

type ResolveRoot = (node: Node<unknown>) => Node<unknown> | null;
type Render = (element: VNode<unknown>) => VNode<unknown>;
Expand Down Expand Up @@ -657,7 +662,8 @@ export function createEnvironment(): Environment {
Actions extends PlainObject = EmptyObject,
Async extends boolean = false,
>(
createRenderOptions: CustomRenderOptions<
createRenderOptions: CustomRenderExtendOptions<
{},
RenderOptions,
Context,
Context,
Expand All @@ -667,11 +673,13 @@ export function createEnvironment(): Environment {
>,
): CustomRender<RenderOptions, Context, Actions, Async> {
const {
options: customizeOptions,
render = defaultRender,
context: createContext = defaultContext,
actions: createActions = defaultActions,
afterRender,
} = createRenderOptions as CustomRenderOptions<
} = createRenderOptions as CustomRenderExtendOptions<
PlainObject,
PlainObject,
PlainObject,
PlainObject,
Expand All @@ -684,22 +692,27 @@ export function createEnvironment(): Environment {
element: VNode<Props>,
options: RenderOptions = {} as any,
) {
const context: any = createContext(options, {});
const resolvedOptions = customizeOptions
? {...options, ...customizeOptions(options)}
: options;
const context: any = createContext(resolvedOptions, {});
const actions: any = {};

const root = createRoot<Props, Context, Actions>(element, {
context,
actions,
render: (element) => render(element, context, options),
render: (element) => render(element, context, resolvedOptions),
resolveRoot: (root) => root.find(element.type),
});

Object.assign(actions, createActions(root as any, options, {}));
Object.assign(actions, createActions(root as any, resolvedOptions, {}));

root.mount();

if (afterRender) {
const afterRenderResult = root.act(() => afterRender(root, options));
const afterRenderResult = root.act(() =>
afterRender(root, resolvedOptions),
);

return isPromise(afterRenderResult)
? afterRenderResult.then(() => root)
Expand Down Expand Up @@ -776,7 +789,8 @@ export function createEnvironment(): Environment {
render: additionalRender = defaultRender,
afterRender: additionalAfterRender = noop,
}: CustomRenderExtendOptions<any, any, any, any, any, any, any>) => {
const extendedRender = createRender<any, any, any, any>({
return createRender<any, any, any, any>({
options: customizeOptions,
context(options, initialContext) {
const baseContext: any = {
...initialContext,
Expand Down Expand Up @@ -818,14 +832,6 @@ export function createEnvironment(): Environment {
return isPromise(result) ? result.then(finalResult) : finalResult();
},
});

return customizeOptions
? (element: any, options = {}) =>
extendedRender(element, {
...options,
...customizeOptions(options),
})
: extendedRender;
},
});

Expand Down
99 changes: 95 additions & 4 deletions packages/preact-testing/source/tests/e2e.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// @vitest-environment jsdom

import {describe, it, expect} from 'vitest';
import {describe, it, expect, vi} from 'vitest';

import {h, Component, Fragment} from 'preact';
import {useState} from 'preact/hooks';
import {h, Component, Fragment, createContext} from 'preact';
import {useContext, useState} from 'preact/hooks';

import {render} from '../index.ts';
import {render, createRender, type CustomRenderOptions} from '../index.ts';

describe('e2e', () => {
it('throws if an Preact component throws during render', () => {
Expand Down Expand Up @@ -73,4 +73,95 @@ describe('e2e', () => {
),
).not.toThrowError();
});

describe('options', () => {
type ColorScheme = 'dark' | 'light';

const ColorSchemeContext = createContext<ColorScheme>('light');

interface Options {
colorScheme?: ColorScheme;
}

function useColorScheme() {
return useContext(ColorSchemeContext);
}

function ColorThemedComponent() {
return <p>Color scheme: {useColorScheme()}</p>;
}

it('gets access to options when rendering', () => {
const renderSpy = vi.fn((element, _, {colorScheme = 'light'}) => {
return (
<ColorSchemeContext.Provider value={colorScheme}>
{element}
</ColorSchemeContext.Provider>
);
}) satisfies CustomRenderOptions<Options>['render'];

const renderWithColorScheme = createRender<Options>({
render: renderSpy,
});

const renderedWithDefaultTheme = renderWithColorScheme(
<ColorThemedComponent />,
);

const renderedWithDarkTheme = renderWithColorScheme(
<ColorThemedComponent />,
{colorScheme: 'dark'},
);

expect(renderSpy).toHaveBeenNthCalledWith(
1,
expect.anything(),
expect.anything(),
expect.not.objectContaining({colorScheme: expect.any(String)}),
);

expect(renderSpy).toHaveBeenNthCalledWith(
2,
expect.anything(),
expect.anything(),
{colorScheme: 'dark'},
);

expect(renderedWithDefaultTheme.text).toBe('Color scheme: light');
expect(renderedWithDarkTheme.text).toBe('Color scheme: dark');
});

it('can override options in an extended render function', () => {
const renderWithColorScheme = createRender<
Options & {highContrast?: boolean}
>({
render(element, _, {colorScheme = 'light'}) {
return (
<ColorSchemeContext.Provider value={colorScheme}>
{element}
</ColorSchemeContext.Provider>
);
},
});

const optionsSpy = vi.fn(() => ({colorScheme: 'dark' as ColorScheme}));

const renderWithDarkColorScheme = renderWithColorScheme.extend<Options>({
options: optionsSpy,
});

const renderedWithDarkTheme = renderWithDarkColorScheme(
<ColorThemedComponent />,
{highContrast: true},
);

expect(optionsSpy).toHaveBeenCalledWith({highContrast: true});

expect(renderedWithDarkTheme.text).toBe('Color scheme: dark');

expect(renderWithDarkColorScheme.hook(() => useColorScheme()).value).toBe(
'dark',
);
});
});
});
38 changes: 22 additions & 16 deletions packages/react-testing/source/environment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,12 @@ export function createEnvironment<
const allRendered = new Set<Root<any, any, any>>();
const render = createRender({});

return {render, createRender, rendered: allRendered, destroyAll};
return {
render,
createRender: createRender as any,
rendered: allRendered,
destroyAll,
};

type ResolveRoot = (node: Node<unknown>) => Node<unknown> | null;
type Render = (element: ReactElement<unknown>) => ReactElement<unknown>;
Expand Down Expand Up @@ -638,7 +643,8 @@ export function createEnvironment<
Actions extends PlainObject = EmptyObject,
Async extends boolean = false,
>(
createRenderOptions: CustomRenderOptions<
createRenderOptions: CustomRenderExtendOptions<
{},
RenderOptions,
Context,
Context,
Expand All @@ -649,11 +655,13 @@ export function createEnvironment<
>,
): CustomRender<RenderOptions, Context, Actions, Extensions, Async> {
const {
options: customizeOptions,
render = defaultRender,
context: createContext = defaultContext,
actions: createActions = defaultActions,
afterRender,
} = createRenderOptions as CustomRenderOptions<
} = createRenderOptions as CustomRenderExtendOptions<
PlainObject,
PlainObject,
PlainObject,
PlainObject,
Expand All @@ -667,22 +675,27 @@ export function createEnvironment<
element: ReactElement<Props>,
options: RenderOptions = {} as any,
) {
const context: any = createContext(options, {});
const resolvedOptions = customizeOptions
? {...options, ...customizeOptions(options)}
: options;
const context: any = createContext(resolvedOptions, {});
const actions: any = {};

const root = createRoot<unknown, Context, Actions>(element, {
context,
actions,
render: (element) => render(element, context, options),
render: (element) => render(element, context, resolvedOptions),
resolveRoot: (root) => root.find(element.type),
});

Object.assign(actions, createActions(root as any, options, {}));
Object.assign(actions, createActions(root as any, resolvedOptions, {}));

root.mount();

if (afterRender) {
const afterRenderResult = root.act(() => afterRender(root, options));
const afterRenderResult = root.act(() =>
afterRender(root, resolvedOptions),
);

return isPromise(afterRenderResult)
? afterRenderResult.then(() => root)
Expand Down Expand Up @@ -759,7 +772,8 @@ export function createEnvironment<
render: additionalRender = defaultRender,
afterRender: additionalAfterRender = noop,
}: CustomRenderExtendOptions<any, any, any, any, any, any, any>) => {
const extendedRender = createRender<any, any, any, any>({
return createRender<any, any, any, any>({
options: customizeOptions,
context(options, initialContext) {
const baseContext: any = {
...initialContext,
Expand Down Expand Up @@ -801,14 +815,6 @@ export function createEnvironment<
return isPromise(result) ? result.then(finalResult) : finalResult();
},
});

return customizeOptions
? (element: any, options = {}) =>
extendedRender(element, {
...options,
...customizeOptions(options),
})
: extendedRender;
},
});

Expand Down

0 comments on commit 0e01748

Please sign in to comment.