Skip to content

Commit

Permalink
feat: support react 18 (#120)
Browse files Browse the repository at this point in the history
  • Loading branch information
jleon15 authored Oct 20, 2022
1 parent eb89682 commit d66c41b
Show file tree
Hide file tree
Showing 6 changed files with 1,542 additions and 1,539 deletions.
15 changes: 7 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,29 +47,28 @@
"@semantic-release/github": "^8.0.4",
"@semantic-release/npm": "^9.0.1",
"@semantic-release/release-notes-generator": "^10.0.3",
"@testing-library/react": "^12.0.0",
"@testing-library/react-hooks": "^7.0.2",
"@testing-library/react": "^13.4.0",
"@types/chance": "^1.1.3",
"@types/jest": "^27.4.0",
"@types/react": "^17.0.38",
"@types/react-dom": "^17.0.9",
"@types/react": "^18.0.21",
"@types/react-dom": "^18.0.6",
"chance": "^1.1.8",
"concurrently": "^7.2.1",
"eslint": "^7.32.0",
"husky": "^5.1.1",
"jest": "^27.4.7",
"prettier": "^2.6.2",
"pretty-quick": "^3.1.3",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"rimraf": "^3.0.2",
"semantic-release": "^19.0.3",
"ts-jest": "^27.0.7",
"typescript": "^4.7.2"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0",
"react-dom": "^16.8.0 || ^17.0.0"
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
},
"resolutions": {
"tmpl": "^1.0.5",
Expand Down
67 changes: 35 additions & 32 deletions test/core/useBasisTheory.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';
import type { BasisTheoryInitOptions } from '@basis-theory/basis-theory-js/types/sdk';
import { renderHook } from '@testing-library/react-hooks';
import { renderHook, waitFor } from '@testing-library/react';
import { Chance } from 'chance';
import { useBasisTheory } from '../../src';
import { BasisTheoryProvider } from '../../src/core/BasisTheoryProvider';
Expand All @@ -11,7 +11,7 @@ jest.mock('../../src/core/BasisTheoryReact');
describe('useBasisTheory', () => {
const chance = new Chance();

let key: string;
let key: string | undefined;
let options: BasisTheoryInitOptions;

beforeEach(() => {
Expand All @@ -23,15 +23,17 @@ describe('useBasisTheory', () => {
});

test('should not initialize if key is falsy', async () => {
const { result, rerender, waitForNextUpdate } = renderHook(() =>
useBasisTheory('')
);
const { result, rerender } = renderHook(() => useBasisTheory(''));
const initialValue = result.current;

expect(result.current.bt).toBeUndefined();

rerender();
// expect rejection, because value never changes
// eslint-disable-next-line jest/require-to-throw-message
await expect(() => waitForNextUpdate()).rejects.toThrow();

await waitFor(() => {
expect(result.current).toStrictEqual(initialValue);
});

expect(result.current.bt).toBeUndefined();
expect(BasisTheoryReact).toHaveBeenCalledTimes(0);
});
Expand All @@ -55,26 +57,21 @@ describe('useBasisTheory', () => {
.spyOn(BasisTheoryReact.prototype, 'init')
.mockRejectedValueOnce(new Error(errorMessage));

const { waitForNextUpdate, result } = renderHook(() =>
useBasisTheory(key, options)
);
const { result } = renderHook(() => useBasisTheory(key, options));

expect(result.current.error).toBeUndefined();

await waitForNextUpdate();

expect(result.current.error).toStrictEqual(new Error(errorMessage));
await waitFor(() =>
expect(result.current.error).toStrictEqual(new Error(errorMessage))
);
});

test('should pass parameters to BasisTheory init', async () => {
const init = jest.spyOn(BasisTheoryReact.prototype, 'init');

const { waitForNextUpdate } = renderHook(() =>
useBasisTheory(key, options)
);
renderHook(() => useBasisTheory(key, options));

await waitForNextUpdate();
expect(init).toHaveBeenCalledWith(key, options);
await waitFor(() => expect(init).toHaveBeenCalledWith(key, options));
});

test('should not update instance if props change', async () => {
Expand All @@ -84,22 +81,23 @@ describe('useBasisTheory', () => {
bt.init = init;
jest.mocked(BasisTheoryReact).mockReturnValue(bt);

const { waitForNextUpdate, rerender } = renderHook(() =>
useBasisTheory(key, options)
);
const { result, rerender } = renderHook(() => useBasisTheory(key, options));

await waitForNextUpdate();
const initialValue = result.current;

key = chance.string();
await waitFor(() => {
expect(result.current).not.toBe(initialValue);
});

key = chance.string();
rerender();

expect(init).toHaveBeenCalledTimes(1);
});

describe('Context', () => {
let btFromContext: BasisTheoryReact;
let wrapper: React.FC<{ apiKey?: string }>;
let wrapper: React.FC<{ apiKey?: string; children?: React.ReactNode }>;

beforeEach(() => {
btFromContext = {
Expand All @@ -117,6 +115,10 @@ describe('useBasisTheory', () => {
expect(result.current.bt).toStrictEqual(btFromContext);
});

interface RenderProps {
apiKey?: string;
}

test('should favor new instance created from props over Context', async () => {
const bt = {
[chance.string()]: chance.string(),
Expand All @@ -125,8 +127,8 @@ describe('useBasisTheory', () => {
.spyOn(BasisTheoryReact.prototype, 'init')
.mockResolvedValue(bt);

const { result, rerender, waitForNextUpdate } = renderHook(
({ apiKey }) => useBasisTheory(apiKey),
const { result, rerender } = renderHook(
({ apiKey }: RenderProps) => useBasisTheory(apiKey),
{
wrapper,
initialProps: {},
Expand All @@ -139,12 +141,13 @@ describe('useBasisTheory', () => {

// passes apiKey to hook
rerender({ apiKey: key });
await waitForNextUpdate();

// should get back initialized instance
expect(result.current.bt).toStrictEqual(bt);
expect(init).toHaveBeenCalledTimes(1);
expect(init).toHaveBeenLastCalledWith(key, undefined);
await waitFor(() => {
// should get back initialized instance
expect(result.current.bt).toStrictEqual(bt);
expect(init).toHaveBeenCalledTimes(1);
expect(init).toHaveBeenLastCalledWith(key, undefined);
});

// goes back passing undefined apiKey to hook
rerender({ apiKey: undefined });
Expand Down
2 changes: 1 addition & 1 deletion test/core/useBasisTheoryValue.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { renderHook } from '@testing-library/react-hooks';
import { renderHook } from '@testing-library/react';
import type { BasisTheoryReact } from '../../src';
import { useBasisTheory } from '../../src/core/useBasisTheory';
import { useBasisTheoryValue } from '../../src/elements/useBasisTheoryValue';
Expand Down
39 changes: 28 additions & 11 deletions test/elements/useElement.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type,@typescript-eslint/explicit-member-accessibility */
import React from 'react';
import type {
BasisTheoryElements,
ElementType,
} from '@basis-theory/basis-theory-js/types/elements';
import { renderHook } from '@testing-library/react-hooks';
import { renderHook, waitFor } from '@testing-library/react';
import { Chance } from 'chance';
import { useBasisTheoryValue } from '../../src/elements/useBasisTheoryValue';
import { useElement } from '../../src/elements/useElement';
Expand All @@ -17,7 +19,22 @@ describe('useElement', () => {
let mount: jest.Mock;
let update: jest.Mock;

let asyncError: unknown;

class ErrorBoundary extends React.Component<{
children: React.ReactElement;
}> {
componentDidCatch(error: unknown) {
asyncError = error;
}

render() {
return !asyncError && this.props.children;
}
}

beforeEach(() => {
asyncError = undefined;
mount = jest.fn().mockResolvedValue(undefined);
update = jest.fn().mockResolvedValue(undefined);
bt = {
Expand Down Expand Up @@ -122,13 +139,11 @@ describe('useElement', () => {

mount.mockRejectedValue(errorMessage);

const { result, waitForNextUpdate } = renderHook(() =>
useElement(id, type, mockRef, {})
);

await waitForNextUpdate();
renderHook(() => useElement(id, type, mockRef, {}), {
wrapper: ErrorBoundary,
});

expect(result.error).toStrictEqual(errorMessage);
await waitFor(() => expect(asyncError).toStrictEqual(errorMessage));
});

test('should update options', () => {
Expand Down Expand Up @@ -217,25 +232,27 @@ describe('useElement', () => {

update.mockRejectedValue(errorMessage);

const { result, rerender, waitForNextUpdate } = renderHook(
const { rerender } = renderHook(
({ options }) => useElement(id, type, mockRef, options),
{
initialProps: {
options: {},
},
wrapper: ErrorBoundary,
}
);

// no error yet
expect(result.error).toBeUndefined();
await waitFor(() => expect(asyncError).toBeUndefined());

// trigger error by updating (triggered options update)
rerender({
options: {
[chance.string()]: chance.string(),
},
});
await waitForNextUpdate();
expect(result.error).toStrictEqual(errorMessage);

await waitFor(() => expect(asyncError).toStrictEqual(errorMessage));
});
});
/* eslint-enable @typescript-eslint/explicit-function-return-type,@typescript-eslint/explicit-member-accessibility */
2 changes: 1 addition & 1 deletion test/elements/useListener.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type {
BaseElement,
EventType,
} from '@basis-theory/basis-theory-js/types/elements';
import { renderHook } from '@testing-library/react-hooks';
import { renderHook } from '@testing-library/react';
import { Chance } from 'chance';
import { useListener } from '../../src/elements/useListener';

Expand Down
Loading

0 comments on commit d66c41b

Please sign in to comment.