Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b46ad88
adds renderHook override to testing-lib
TheSonOfThomp Jan 9, 2024
488ad00
update renderHook imports in basic packages
TheSonOfThomp Jan 9, 2024
5292c24
update usage in toast
TheSonOfThomp Jan 9, 2024
3ce1187
export `act` from @lg-tl
TheSonOfThomp Jan 9, 2024
7700735
update usage in `hooks`
TheSonOfThomp Jan 9, 2024
9e6e2a9
Updates return value of `useToast.updateToast`
TheSonOfThomp Jan 9, 2024
021551d
remove `act` warnings
TheSonOfThomp Jan 9, 2024
085cad2
update types in RTLOverrides
TheSonOfThomp Jan 9, 2024
232d059
Creates `waitForState `
TheSonOfThomp Jan 10, 2024
f25d2e8
updates hooks & toast tests
TheSonOfThomp Jan 10, 2024
f921f8e
adds changesets
TheSonOfThomp Jan 10, 2024
9b6023b
updates dependencies
TheSonOfThomp Jan 10, 2024
f11ddfe
updates externalDependencies
TheSonOfThomp Jan 10, 2024
ee41597
update testing-lib build script
TheSonOfThomp Jan 10, 2024
304e49d
Merge branch 'adam/React17-renderHook' into adam/date-pixcker-x-react-17
TheSonOfThomp Jan 10, 2024
a66de99
updates renderHook imports
TheSonOfThomp Jan 10, 2024
558023f
fixes how isControlled is set
TheSonOfThomp Jan 10, 2024
55c3283
adds comment for @ts-expect-error
TheSonOfThomp Jan 10, 2024
f7fdbfd
moves globToRegex
TheSonOfThomp Jan 10, 2024
030772e
adds docs to Exists
TheSonOfThomp Jan 10, 2024
ea3617b
Merge branch 'adam/React17-renderHook' into adam/date-picker-x-react-17
TheSonOfThomp Jan 10, 2024
44f32b6
Update config.ts
TheSonOfThomp Jan 10, 2024
629f085
Merge branch 'adam/date-picker-lg-3152' into adam/date-picker-x-react-17
TheSonOfThomp Jan 10, 2024
9578ad8
Update hooks.spec.tsx
TheSonOfThomp Jan 11, 2024
0869425
Merge branch 'adam/React17-renderHook' into adam/date-picker-x-react-17
TheSonOfThomp Jan 11, 2024
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
6 changes: 6 additions & 0 deletions .changeset/bright-walls-glow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@lg-tools/validate': patch
---

- Adds `'@leafygreen-ui/testing-lib'` to list of external dependencies.
- Updates handling of external dependencies with glob patterns
5 changes: 5 additions & 0 deletions .changeset/fast-bananas-watch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@leafygreen-ui/toast': patch
---

Updates `updateToast` function to consistently return the new toast object
8 changes: 8 additions & 0 deletions .changeset/healthy-needles-learn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@leafygreen-ui/leafygreen-provider': patch
'@leafygreen-ui/hooks': patch
'@leafygreen-ui/toast': patch
'@leafygreen-ui/a11y': patch
---

Updates test to import `renderHook` from `@leafygreen-ui/testing-lib`
5 changes: 5 additions & 0 deletions .changeset/khaki-lies-carry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@leafygreen-ui/testing-lib': minor
---

Exports `waitForState`, a wrapper around `act` that returns the result of the state update callback
5 changes: 5 additions & 0 deletions .changeset/strange-scissors-prove.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@leafygreen-ui/testing-lib': minor
---

Exports overrides for `renderHook` and `act` that will work in both a React 17 and React 18 test environment
3 changes: 2 additions & 1 deletion packages/a11y/src/A11y.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React from 'react';
import { render } from '@testing-library/react';
import { renderHook } from '@testing-library/react-hooks';
import { axe } from 'jest-axe';

import { renderHook } from '@leafygreen-ui/testing-lib';

import { AriaLabelProps, AriaLabelPropsWithLabel } from './AriaLabelProps';
import {
prefersReducedMotion,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React, { PropsWithChildren } from 'react';
import { act, waitFor } from '@testing-library/react';
import { renderHook } from '@testing-library/react-hooks';

import { Month, newUTC } from '@leafygreen-ui/date-utils';
import { consoleOnce } from '@leafygreen-ui/lib';
import { renderHook } from '@leafygreen-ui/testing-lib';

import { MAX_DATE, MIN_DATE } from '../constants';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import React from 'react';
import { ChangeEventHandler } from 'react';
import { render } from '@testing-library/react';
import { renderHook, RenderHookResult } from '@testing-library/react-hooks';
import { RenderHookResult } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import { renderHook } from '@leafygreen-ui/testing-lib';

import { useControlledValue } from './useControlledValue';

const errorSpy = jest.spyOn(console, 'error');

const renderUseControlledValueHook = <T extends any>(
...[valueProp, callback, initial]: Parameters<typeof useControlledValue<T>>
): RenderHookResult<T, ReturnType<typeof useControlledValue<T>>> => {
): RenderHookResult<ReturnType<typeof useControlledValue<T>>, T> => {
const result = renderHook(v => useControlledValue(v, callback, initial), {
initialProps: valueProp,
});
Expand Down Expand Up @@ -109,7 +111,7 @@ describe('packages/hooks/useControlledValue', () => {
test('setting value to undefined should keep the component controlled', () => {
const { rerender, result } = renderUseControlledValueHook('apple');
expect(result.current.isControlled).toBe(true);
rerender(undefined);
rerender();
expect(result.current.isControlled).toBe(true);
});

Expand Down Expand Up @@ -144,8 +146,10 @@ describe('packages/hooks/useControlledValue', () => {
});

test('setValue updates the value', () => {
const { result } = renderUseControlledValueHook<string>(undefined);
const { result, rerender } =
renderUseControlledValueHook<string>(undefined);
result.current.setValue('banana');
rerender();
expect(result.current.value).toBe('banana');
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ export const useControlledValue = <T extends any>(
// If the value prop changes from undefined to something defined,
// then isControlled is set to true,
// and will remain true for the life of the component
const isControlled: boolean = useMemo(() => {
return isControlled || !isUndefined(valueProp);
}, [valueProp]);
const [isControlled, setControlled] = useState(!isUndefined(valueProp));
useEffect(() => {
setControlled(isControlled || !isUndefined(valueProp));
}, [isControlled, valueProp]);

const wasControlled = usePrevious(isControlled);

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { renderHook } from '@testing-library/react';

import { DateType, Month, newUTC } from '@leafygreen-ui/date-utils';
import { renderHook } from '@leafygreen-ui/testing-lib';

import { useDateSegments } from './useDateSegments';
import { OnUpdateCallback } from './useDateSegments.types';
Expand Down
35 changes: 20 additions & 15 deletions packages/hooks/src/hooks.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { act, renderHook } from '@testing-library/react-hooks';
import { waitFor } from '@testing-library/react';

import { act, renderHook } from '@leafygreen-ui/testing-lib';

import {
useEventListener,
Expand Down Expand Up @@ -94,7 +96,7 @@ describe('packages/hooks', () => {
describe.skip('useMutationObserver', () => {}); //eslint-disable-line jest/no-disabled-tests

test('useViewportSize responds to updates in window size', async () => {
const { result, waitForNextUpdate } = renderHook(() => useViewportSize());
const { result, rerender } = renderHook(() => useViewportSize());

const mutableWindow: { -readonly [K in keyof Window]: Window[K] } = window;
const initialHeight = 360;
Expand All @@ -104,10 +106,11 @@ describe('packages/hooks', () => {
mutableWindow.innerWidth = initialWidth;

window.dispatchEvent(new Event('resize'));
await act(waitForNextUpdate);

expect(result?.current?.height).toBe(initialHeight);
expect(result?.current?.width).toBe(initialWidth);
rerender();
await waitFor(() => {
expect(result?.current?.height).toBe(initialHeight);
expect(result?.current?.width).toBe(initialWidth);
});

const updateHeight = 768;
const updateWidth = 1024;
Expand All @@ -116,10 +119,10 @@ describe('packages/hooks', () => {
mutableWindow.innerWidth = updateWidth;

window.dispatchEvent(new Event('resize'));
await act(waitForNextUpdate);

expect(result?.current?.height).toBe(updateHeight);
expect(result?.current?.width).toBe(updateWidth);
await waitFor(() => {
expect(result?.current?.height).toBe(updateHeight);
expect(result?.current?.width).toBe(updateWidth);
});
});

describe('usePoller', () => {
Expand Down Expand Up @@ -249,26 +252,28 @@ describe('packages/hooks', () => {
expect(pollHandler).toHaveBeenCalledTimes(0);
});

test('when document is not visible', () => {
test('when document is not visible', async () => {
const pollHandler = jest.fn();

renderHook(() => usePoller(pollHandler));
const { rerender } = renderHook(() => usePoller(pollHandler));

expect(pollHandler).toHaveBeenCalledTimes(1);

mutableDocument.visibilityState = 'hidden';
act(() => {
document.dispatchEvent(new Event('visibilitychange'));
});

jest.advanceTimersByTime(30e3);

expect(pollHandler).toHaveBeenCalledTimes(1);

mutableDocument.visibilityState = 'visible';
rerender(pollHandler);

act(() => {
document.dispatchEvent(new Event('visibilitychange'));
});
jest.advanceTimersByTime(30e3);

// immediate triggers the pollHandler
expect(pollHandler).toHaveBeenCalledTimes(2);
Expand Down Expand Up @@ -308,11 +313,11 @@ describe('packages/hooks', () => {
rerender(2020);
expect(result.current).toEqual(42);

rerender();
rerender(123);
expect(result.current).toEqual(2020);

rerender();
expect(result.current).toEqual(2020);
expect(result.current).toEqual(123);
});
});

Expand Down
29 changes: 18 additions & 11 deletions packages/hooks/src/useDynamicRefs/useDynamicRefs.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { renderHook } from '@testing-library/react-hooks';

// import { renderHook } from '@testing-library/react-hooks';
import { consoleOnce } from '@leafygreen-ui/lib';
import { renderHook } from '@leafygreen-ui/testing-lib';

import { DynamicRefGetter, useDynamicRefs } from '.';

Expand All @@ -11,11 +11,13 @@ describe('packages/hooks/useDynamicRefs', () => {
});

test('returns identical getter when rerendered ', () => {
const props = { prefix: 'A' };
const { result, rerender } = renderHook(v => useDynamicRefs(v), {
initialProps: { prefix: 'A' },
initialProps: props,
});
rerender();
expect(result.all[0]).toBe(result.all[1]);
const initialValue = result.current;
rerender(props);
expect(result.current).toStrictEqual(initialValue);
});

test('returns unique getters when called with the same prefix', () => {
Expand All @@ -33,11 +35,14 @@ describe('packages/hooks/useDynamicRefs', () => {

test('returns unique getters when re-rendered with a different prefix', () => {
// This is an edge-case, but this is the behavior we want if it happens
const props = { prefix: 'A' };
const { result, rerender } = renderHook(v => useDynamicRefs(v), {
initialProps: { prefix: 'A' },
initialProps: props,
});
rerender({ prefix: 'B' });
expect(result.all[0]).not.toBe(result.all[1]);
const initialValue = result.current;
const newProps = { prefix: 'B' };
rerender(newProps);
expect(result.current).not.toBe(initialValue);
});

describe('ref getter function', () => {
Expand Down Expand Up @@ -66,18 +71,20 @@ describe('packages/hooks/useDynamicRefs', () => {
});

test('returns identical refs when called with the same key', () => {
const { result } = renderHook(() => useDynamicRefs({ prefix: 'A' }));
const props = { prefix: 'A' };
const { result } = renderHook(() => useDynamicRefs(props));
const ref1 = result.current('key');
const ref2 = result.current('key');
expect(ref1).toBe(ref2);
});

test('returns identical refs when rerendered', () => {
const props = { prefix: 'A' };
const { result, rerender } = renderHook(v => useDynamicRefs(v), {
initialProps: { prefix: 'A' },
initialProps: props,
});
const ref1 = result.current('key');
rerender();
rerender(props);
const ref2 = result.current('key');
expect(ref1).toBe(ref2);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { PropsWithChildren } from 'react';
import { act, fireEvent, render, waitFor } from '@testing-library/react';
import { renderHook } from '@testing-library/react-hooks';

import { renderHook } from '@leafygreen-ui/testing-lib';

import { PopoverProvider, type PopoverState, usePopoverContext } from '.';

Expand Down
13 changes: 9 additions & 4 deletions packages/testing-lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,18 @@
"@testing-library/user-event": "13.5.0",
"lodash": "^4.17.21"
},
"devDependencies": {
"@lg-tools/build": "0.3.1"
},
"peerDependencies": {
"@lg-tools/test": "0.3.2"
"@testing-library/react": "^12.0.0 || ^13.1.0 || ^14.0.0"
},
"optionalDependencies": {
"@testing-library/react-hooks": ">=3.7.0"
},
"scripts": {
"build": "lg build-package",
"tsc": "lg build-ts",
"docs": "lg build-tsdoc"
"build": "lg-internal-build-package",
"tsc": "tsc --build tsconfig.json"
},
"license": "Apache-2.0",
"publishConfig": {
Expand Down
36 changes: 36 additions & 0 deletions packages/testing-lib/src/RTLOverrides.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as RTL from '@testing-library/react';

/**
* Utility type that returns `X.Y` if it exists, otherwise defaults to fallback type `Z`, or `any`
*/
export type Exists<
X,
Y extends keyof X | string,
Z = unknown,
> = Y extends keyof X ? X[Y] : Z;

/**
* Re-exports `renderHook` from `"@testing-library/react"` if it exists,
* or from `"@testing-library/react-hooks"`
*
* (used when running in a React 17 test environment)
*/
export const renderHook: Exists<typeof RTL, 'renderHook'> =
(RTL as any).renderHook ??
(() => {
const RHTL = require('@testing-library/react-hooks');
return RHTL.renderHook;
})();

/**
* Re-exports `act` from `"@testing-library/react"` if it exists,
* or from `"@testing-library/react-hooks"`
*
* (used when running in a React 17 test environment)
*/
export const act: Exists<typeof RTL, 'act'> =
RTL.act ??
(() => {
const RHTL = require('@testing-library/react-hooks');
return RHTL.act;
})();
3 changes: 3 additions & 0 deletions packages/testing-lib/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import * as Context from './context';
import * as jest from './jest';
import * as JestDOM from './jest-dom';
export { act, renderHook } from './RTLOverrides';
export { waitForState } from './waitForState';

export { Context, jest, JestDOM };

export { eventContainingTargetValue } from './eventContainingTargetValue';
Expand Down
19 changes: 19 additions & 0 deletions packages/testing-lib/src/waitForState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { act } from './RTLOverrides';

/**
* Wrapper around `act`.
*
* Awaits an `act` call,
* and returns the value of the state update callback
*/
export const waitForState = async <T extends any>(
callback: () => T,
): Promise<T> => {
let val: T;
await act(() => {
val = callback();
});

// @ts-expect-error - val is returned before TS sees it as being defined
return val;
};
1 change: 0 additions & 1 deletion packages/testing-lib/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
"rootDir": "src",
"baseUrl": ".",
"paths": {
"@leafygreen-ui/icon/dist/*": ["../icon/src/generated/*"],
"@leafygreen-ui/*": ["../*/src"]
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { act } from 'react-dom/test-utils';
import { cleanup } from '@testing-library/react';
import { renderHook } from '@testing-library/react-hooks';

import { renderHook } from '@leafygreen-ui/testing-lib';

import { Variant } from '../../Toast.types';
import { ToastId, ToastStack } from '../ToastContext.types';
Expand Down
Loading