mod-arch-shared
Version:
Shared library for modular architecture micro-frontend projects
126 lines • 4.76 kB
JavaScript
import { renderHook as renderHookRTL, waitFor, } from '@testing-library/react';
/**
* Wrapper on top of RTL `renderHook` returning a result that implements the `RenderHookResultExt` interface.
*
* `renderHook` provides full control over the rendering of your hook including the ability to wrap the test component.
* This is usually used to add context providers from `React.createContext` for the hook to access with `useContext`.
* `initialProps` and props subsequently set by `rerender` will be provided to the wrapper.
*
* ```
* const renderResult = renderHook(({ who }: { who: string }) => useSayHello(who), { initialProps: { who: 'world' }});
* expect(renderResult).hookToBe('Hello world!');
* renderResult.rerender({ who: 'there' });
* expect(renderResult).hookToBe('Hello there!');
* ```
*/
export const renderHook = (render, options) => {
let updateCount = 0;
let prevResult;
let currentResult;
const renderResult = renderHookRTL((props) => {
updateCount++;
prevResult = currentResult;
currentResult = render(props);
return currentResult;
}, options);
const renderResultExt = {
...renderResult,
getPreviousResult: () => (updateCount > 1 ? prevResult : renderResult.result.current),
getUpdateCount: () => updateCount,
waitForNextUpdate: async (currentOptions) => {
const expected = updateCount;
try {
await waitFor(() => expect(updateCount).toBeGreaterThan(expected), currentOptions);
}
catch {
throw new Error('waitForNextUpdate timed out');
}
},
};
return renderResultExt;
};
/**
* Lightweight API for testing a single hook.
*
* Prefer this method of testing over `renderHook` for simplicity.
*
* ```
* const renderResult = testHook(useSayHello)('world');
* expectHook(renderResult).toBe('Hello world!');
* renderResult.rerender('there');
* expectHook(renderResult).toBe('Hello there!');
* ```
*/
export const testHook = (hook) =>
// not ideal to nest functions in terms of API but cannot find a better way to infer P from hook and not initialParams
(...initialParams) => {
const renderResult = renderHook(({ $params }) => hook(...$params), {
initialProps: {
$params: initialParams,
},
});
return {
...renderResult,
rerender: (...params) => renderResult.rerender({ $params: params }),
};
};
/**
* A helper function for asserting the return value of hooks based on `useFetchState`.
*
* eg.
* ```
* expectHook(renderResult).isStrictEqual(standardUseFetchState('test value', true))
* ```
* is equivalent to:
* ```
* expectHook(renderResult).isStrictEqual(['test value', true, undefined, expect.any(Function)])
* ```
*/
export const standardUseFetchState = (data, loaded = false, error) => [data, loaded, error, expect.any(Function)];
// create a new asymmetric matcher that matches everything
const everything = () => {
const r = expect.anything();
r.asymmetricMatch = () => true;
return r;
};
/**
* Extracts a subset of values from the source that can be used to compare equality.
*
* Recursively traverses the `booleanTarget`. For every property or array index equal to `true`,
* adds the value of the source to the result wrapped in custom matcher `expect.isIdentityEqual`.
* If the entry is `false` or `undefined`, add an everything matcher to the result.
*/
export const createComparativeValue = (source, booleanTarget) => createComparativeValueRecursive(source, booleanTarget);
const createComparativeValueRecursive = (source,
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
booleanTarget) => {
if (typeof booleanTarget === 'boolean') {
return booleanTarget ? expect.isIdentityEqual(source) : everything();
}
if (Array.isArray(booleanTarget)) {
if (Array.isArray(source)) {
const r = new Array(source.length).fill(everything());
booleanTarget.forEach((b, i) => {
if (b != null) {
r[i] = createComparativeValueRecursive(source[i], b);
}
});
return r;
}
return undefined;
}
if (source == null ||
typeof source === 'string' ||
typeof source === 'number' ||
typeof source === 'function') {
return source;
}
const obj = {};
const btObj = booleanTarget;
Object.keys(btObj).forEach((key) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
obj[key] = createComparativeValueRecursive(source[key], btObj[key]);
});
return expect.objectContaining(obj);
};
//# sourceMappingURL=hooks.js.map