diff --git a/jest.config.cjs b/jest.config.cjs index 5db721668..c24b30c56 100644 --- a/jest.config.cjs +++ b/jest.config.cjs @@ -20,10 +20,6 @@ module.exports = { setupFilesAfterEnv: ['tests/setupTests.ts'], snapshotSerializers: ['@emotion/jest/serializer'], testEnvironment: 'jsdom', - testPathIgnorePatterns: [ - '/node_modules/', - '/__snapshots__', - '/dist*', - ], - timers: 'modern', + testPathIgnorePatterns: ['/node_modules/', '/__snapshots__', '/dist*'], + timers: 'real', }; diff --git a/notes.txt b/notes.txt new file mode 100644 index 000000000..69c498aed --- /dev/null +++ b/notes.txt @@ -0,0 +1,12 @@ +p1 +red FF5E4D --> wasabi DEFAAB +nude F2D6C7 --> cool 2 E6E3ED +racing green 254233 --> warm base 212121 +Green CAD1B4 --> Warm 1 F7F2EE +Burnt Orange AC6A27 --> E0D9CF + +p2 +Secondary 1 A6A1E2 --> 939BF0 (still secondary, etc.) +Secondary 2 B8B4E8 --> B8BCFF +Secondary 3 CAC7EE --> DCDEFF +Secondary 4 F7F6FD --> F5F5FF \ No newline at end of file diff --git a/package.json b/package.json index 9dcfe075a..11da2c2f6 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,7 @@ "@testing-library/dom": "^8.0.0", "@testing-library/jest-dom": "^5.11.6", "@testing-library/react": "^12.0.0", - "@testing-library/user-event": "^13.0.7", + "@testing-library/user-event": "^14.0.3", "@types/jest": "^27.0.0", "@types/lodash.round": "^4.0.6", "@types/lodash.throttle": "^4.1.6", @@ -150,7 +150,6 @@ "lint-staged": "^12.1.2", "prettier": "2.6.1", "react-is": "^17.0.1", - "regenerator-runtime": "^0.13.7", "rimraf": "^3.0.2", "rollup": "^2.26.11", "tslib": "^2.3.0", diff --git a/src/shared-components/accordion/test.tsx b/src/shared-components/accordion/test.tsx index 9379de76d..33f3b5ca1 100644 --- a/src/shared-components/accordion/test.tsx +++ b/src/shared-components/accordion/test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render, userEvent } from 'src/tests/testingLibraryHelpers'; +import { render } from 'src/tests/testingLibraryHelpers'; import { Accordion } from './index'; @@ -11,10 +11,6 @@ const testAccordionProps = { }; describe('', () => { - /** - * TODO: Fix Emotion 11 CI snapshot serializer order issue - */ - // eslint-disable-next-line jest/no-disabled-tests describe('UI snapshots', () => { it('renders regular accordion', () => { const { container } = render(); @@ -39,14 +35,14 @@ describe('', () => { }); }); - it('invokes onClick when title is clicked', () => { + it('invokes onClick when title is clicked', async () => { const spy = jest.fn(); - const { getByRole } = render( + const { getByRole, user } = render( , ); - userEvent.click(getByRole('button')); + await user.click(getByRole('button')); expect(spy).toHaveBeenCalled(); }); diff --git a/src/shared-components/alert/test.tsx b/src/shared-components/alert/test.tsx index 11feb836b..14d4fc9cb 100644 --- a/src/shared-components/alert/test.tsx +++ b/src/shared-components/alert/test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { act, render, userEvent } from 'src/tests/testingLibraryHelpers'; +import { act, render } from 'src/tests/testingLibraryHelpers'; import { assert } from 'src/utils/assert'; import { Alert } from './index'; @@ -50,19 +50,20 @@ describe('Alert UI snapshots', () => { expect(container.firstElementChild).toMatchSnapshot(); }); - it('alert onExit is triggered on click', () => { + it('alert onExit is triggered on click', async () => { jest.useFakeTimers(); const spy = jest.fn(); - const { container } = render( + const { container, user } = render( } onExit={spy} duration="sticky" />, + { userEventOptions: { delay: null } }, ); assert(container.firstElementChild); - userEvent.click(container.firstElementChild); + await user.click(container.firstElementChild); act(() => { jest.runAllTimers(); @@ -71,20 +72,21 @@ describe('Alert UI snapshots', () => { expect(spy).toHaveBeenCalled(); }); - it('alert with custom CTA', () => { + it('alert with custom CTA', async () => { jest.useFakeTimers(); const spy = jest.fn(); - const { container } = render( + const { container, user } = render( } type="error" ctaContent="Update Payment Method" onExit={spy} />, + { userEventOptions: { delay: null } }, ); assert(container.firstElementChild); - userEvent.click(container.firstElementChild); + await user.click(container.firstElementChild); act(() => { jest.runAllTimers(); diff --git a/src/shared-components/banner/test.tsx b/src/shared-components/banner/test.tsx index 028562ba2..50b5cbe6e 100644 --- a/src/shared-components/banner/test.tsx +++ b/src/shared-components/banner/test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render, userEvent } from 'src/tests/testingLibraryHelpers'; +import { render } from 'src/tests/testingLibraryHelpers'; import { Banner } from './index'; @@ -26,13 +26,13 @@ describe('Banner UI snapshots', () => { expect(container.firstElementChild).toMatchSnapshot(); }); - it('banner with click handler', () => { + it('banner with click handler', async () => { const spy = jest.fn(); - const { getByRole } = render( + const { getByRole, user } = render( , ); - userEvent.click(getByRole('button')); + await user.click(getByRole('button')); expect(spy).toHaveBeenCalled(); }); }); diff --git a/src/shared-components/button/components/button/test.tsx b/src/shared-components/button/components/button/test.tsx index 7ba231db7..81d434ae3 100644 --- a/src/shared-components/button/components/button/test.tsx +++ b/src/shared-components/button/components/button/test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render, userEvent } from 'src/tests/testingLibraryHelpers'; +import { render } from 'src/tests/testingLibraryHelpers'; import { assert } from 'src/utils/assert'; import { CameraIcon } from '../../../../icons'; @@ -7,10 +7,6 @@ import { CameraIcon } from '../../../../icons'; import { Button } from './index'; describe('); + const { container, user } = render( + , + ); assert(container.firstElementChild); - userEvent.click(container.firstElementChild); + await user.click(container.firstElementChild); expect(spy).toHaveBeenCalled(); }); - it('should not be invoked if disabled', () => { + it('should not be invoked if disabled', async () => { const spy = jest.fn(); - const { container } = render( + const { container, user } = render( , ); assert(container.firstElementChild); - userEvent.click(container.firstElementChild); + await user.click(container.firstElementChild); expect(spy).not.toHaveBeenCalled(); }); - it('should not be invoked if loading', () => { + it('should not be invoked if loading', async () => { const spy = jest.fn(); - const { container } = render( + const { container, user } = render( , ); assert(container.firstElementChild); - userEvent.click(container.firstElementChild); + await user.click(container.firstElementChild); expect(spy).not.toHaveBeenCalled(); }); diff --git a/src/shared-components/button/components/linkButton/test.tsx b/src/shared-components/button/components/linkButton/test.tsx index 8eeaae971..da1bd8f22 100644 --- a/src/shared-components/button/components/linkButton/test.tsx +++ b/src/shared-components/button/components/linkButton/test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render, userEvent } from 'src/tests/testingLibraryHelpers'; +import { render } from 'src/tests/testingLibraryHelpers'; import { assert } from 'src/utils/assert'; import { LinkButton } from './index'; @@ -31,28 +31,30 @@ describe('', () => { }); describe('onClick callback', () => { - it('should be invoked onClick', () => { + it('should be invoked onClick', async () => { const spy = jest.fn(); - const { container } = render(text); + const { container, user } = render( + text, + ); assert(container.firstElementChild); - userEvent.click(container.firstElementChild); + await user.click(container.firstElementChild); expect(spy).toHaveBeenCalled(); }); - it('should not be invoked if disabled', () => { + it('should not be invoked if disabled', async () => { const spy = jest.fn(); - const { container } = render( + const { container, user } = render( text , ); assert(container.firstElementChild); - userEvent.click(container.firstElementChild); + await user.click(container.firstElementChild); expect(spy).not.toHaveBeenCalled(); }); diff --git a/src/shared-components/button/components/roundButton/test.tsx b/src/shared-components/button/components/roundButton/test.tsx index 8b548c461..ea312d7b3 100644 --- a/src/shared-components/button/components/roundButton/test.tsx +++ b/src/shared-components/button/components/roundButton/test.tsx @@ -1,15 +1,11 @@ import React from 'react'; -import { render, userEvent } from 'src/tests/testingLibraryHelpers'; +import { render } from 'src/tests/testingLibraryHelpers'; import { CameraIcon } from '../../../../icons'; import { RoundButton } from './index'; describe('', () => { - /** - * TODO: Fix Emotion 11 CI snapshot serializer order issue - */ - // eslint-disable-next-line jest/no-disabled-tests describe('UI snapshots', () => { it('renders with props', () => { const { container } = render( @@ -23,39 +19,39 @@ describe('', () => { }); describe('onClick callback', () => { - it('should be invoked onClick', () => { + it('should be invoked onClick', async () => { const spy = jest.fn(); - const { getByRole } = render( + const { getByRole, user } = render( }> Button Text , ); - userEvent.click(getByRole('button')); + await user.click(getByRole('button')); expect(spy).toHaveBeenCalled(); }); - it('should not be invoked if disabled', () => { + it('should not be invoked if disabled', async () => { const spy = jest.fn(); - const { getByRole } = render( + const { getByRole, user } = render( }> Button Text , ); - userEvent.click(getByRole('button')); + await user.click(getByRole('button')); expect(spy).not.toHaveBeenCalled(); }); - it('should not be invoked if loading', () => { + it('should not be invoked if loading', async () => { const spy = jest.fn(); - const { getByRole } = render( + const { getByRole, user } = render( }> Button Text , ); - userEvent.click(getByRole('button')); + await user.click(getByRole('button')); expect(spy).not.toHaveBeenCalled(); }); }); diff --git a/src/shared-components/button/components/textButton/test.tsx b/src/shared-components/button/components/textButton/test.tsx index 6ade45b86..b7866a43c 100644 --- a/src/shared-components/button/components/textButton/test.tsx +++ b/src/shared-components/button/components/textButton/test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render, userEvent } from 'src/tests/testingLibraryHelpers'; +import { render } from 'src/tests/testingLibraryHelpers'; import { TextButton } from './index'; @@ -20,26 +20,26 @@ describe('', () => { }); describe('onClick callback', () => { - it('should invoke onClick', () => { + it('should invoke onClick', async () => { const spy = jest.fn(); - const { getByRole } = render( + const { getByRole, user } = render( Button Text, ); - userEvent.click(getByRole('button')); + await user.click(getByRole('button')); expect(spy).toHaveBeenCalled(); }); - it('should not be clickable if disabled', () => { + it('should not be clickable if disabled', async () => { const spy = jest.fn(); - const { getByRole } = render( + const { getByRole, user } = render( Button Text , ); - userEvent.click(getByRole('button')); + await user.click(getByRole('button')); expect(spy).not.toHaveBeenCalled(); }); diff --git a/src/shared-components/carousel/test.tsx b/src/shared-components/carousel/test.tsx index d170d3351..b2b6041c5 100644 --- a/src/shared-components/carousel/test.tsx +++ b/src/shared-components/carousel/test.tsx @@ -11,10 +11,6 @@ const cards = [ ]; describe('', () => { - /** - * TODO: Fix Emotion 11 CI snapshot serializer order issue - */ - // eslint-disable-next-line jest/no-disabled-tests describe('UI snapshots', () => { it('renders with props', () => { const { container } = render( diff --git a/src/shared-components/dialogModal/test.tsx b/src/shared-components/dialogModal/test.tsx index 0541c681d..adf40567e 100644 --- a/src/shared-components/dialogModal/test.tsx +++ b/src/shared-components/dialogModal/test.tsx @@ -9,32 +9,28 @@ const bodyString = 'Dialog Modal Children Content'; const modalBody = {bodyString}; describe('', () => { - /** - * TODO: Fix Emotion 11 CI snapshot serializer order issue - */ - // eslint-disable-next-line jest/no-disabled-tests describe('UI snapshots', () => { - it('renders dialog modal with custom color', async () => { - const { container, findByText } = render( + it('renders dialog modal with custom color', () => { + const { container, getByText } = render(
{modalBody}
, { withPortalContainer: true }, ); - await findByText(bodyString); + getByText(bodyString); expect(container).toMatchSnapshot(); }); }); - it('render children content correctly', async () => { - const { getAllByText, findByText } = render( + it('render children content correctly', () => { + const { getAllByText, getByText } = render( {modalBody}, { withPortalContainer: true }, ); - const body = await findByText(bodyString); + const body = getByText(bodyString); expect(getAllByText(modalTitle).length).toBeGreaterThan(0); expect(body).toBeInTheDocument(); diff --git a/src/shared-components/dropdown/test.tsx b/src/shared-components/dropdown/test.tsx index 22999fdc9..547e60922 100644 --- a/src/shared-components/dropdown/test.tsx +++ b/src/shared-components/dropdown/test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { fireEvent, render, userEvent } from 'src/tests/testingLibraryHelpers'; +import { fireEvent, render } from 'src/tests/testingLibraryHelpers'; import { DesktopDropdown } from './desktopDropdown'; import { MobileDropdown } from './mobileDropdown'; @@ -79,9 +79,9 @@ describe('', () => { }); describe('onDropdownContainerFocus callback', () => { - it('should be invoked on focus', () => { + it('should be invoked on focus', async () => { const spy = jest.fn(); - const { getByRole } = render( + const { getByRole, user } = render( ', () => { />, ); - userEvent.click(getByRole('combobox')); + await user.click(getByRole('combobox')); expect(spy).toHaveBeenCalled(); }); }); @@ -119,9 +119,9 @@ describe('', () => { }); describe('onSelectClick callback', () => { - it(`${ON_CLICK_TEST}`, () => { + it(`${ON_CLICK_TEST}`, async () => { const spy = jest.fn(); - const { getByRole } = render( + const { getByRole, user } = render( ', () => { />, ); - userEvent.click(getByRole('button')); + await user.click(getByRole('button')); expect(spy).toHaveBeenCalled(); }); }); describe('onOptionClick callback', () => { - it(`${ON_CLICK_TEST}`, () => { + it(`${ON_CLICK_TEST}`, async () => { const spy = jest.fn(); - const { getAllByRole } = render( + const { getAllByRole, user } = render( ', () => { const listItems = getAllByRole('menuitemradio'); // Arbitrarily select last item - userEvent.click(listItems[listItems.length - 1]); + await user.click(listItems[listItems.length - 1]); expect(spy).toHaveBeenCalled(); }); }); describe('onDropdownContainerFocus callback', () => { - it('should be invoked on focus', () => { + it('should be invoked on focus', async () => { const spy = jest.fn(); - render( + const { user } = render( ', () => { />, ); - userEvent.tab(); + await user.tab(); expect(spy).toHaveBeenCalled(); }); }); diff --git a/src/shared-components/field/test.tsx b/src/shared-components/field/test.tsx index e5be0bf58..afa798de6 100644 --- a/src/shared-components/field/test.tsx +++ b/src/shared-components/field/test.tsx @@ -4,10 +4,6 @@ import { render } from 'src/tests/testingLibraryHelpers'; import { Field } from './index'; describe('', () => { - /** - * TODO: Fix Emotion 11 CI snapshot serializer order issue - */ - // eslint-disable-next-line jest/no-disabled-tests describe('UI snapshots', () => { it('renders with default props', () => { const { container } = render( diff --git a/src/shared-components/optionButton/test.tsx b/src/shared-components/optionButton/test.tsx index 6fe1e966b..6e78c3dd2 100644 --- a/src/shared-components/optionButton/test.tsx +++ b/src/shared-components/optionButton/test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render, userEvent } from 'src/tests/testingLibraryHelpers'; +import { render } from 'src/tests/testingLibraryHelpers'; import { assert } from 'src/utils/assert'; import { AcneGlyph } from '../../icons'; @@ -38,9 +38,9 @@ describe('', () => { }); describe('onClick callback', () => { - it('is invoked when clicked', () => { + it('is invoked when clicked', async () => { const spy = jest.fn(); - const { container } = render( + const { container, user } = render( ', () => { assert(container.firstElementChild); - userEvent.click(container.firstElementChild); + await user.click(container.firstElementChild); expect(spy).toHaveBeenCalled(); }); }); diff --git a/src/shared-components/segmentedControl/test.tsx b/src/shared-components/segmentedControl/test.tsx index 7279a0fb3..4826470dc 100644 --- a/src/shared-components/segmentedControl/test.tsx +++ b/src/shared-components/segmentedControl/test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render, userEvent } from 'src/tests/testingLibraryHelpers'; +import { render } from 'src/tests/testingLibraryHelpers'; import { SegmentedControl } from './index'; @@ -20,16 +20,16 @@ describe('', () => { expect(container.firstElementChild).toMatchSnapshot(); }); - it('calls onClick when button is clicked', () => { + it('calls onClick when button is clicked', async () => { const spy = jest.fn(); - const { getByRole } = render( + const { getByRole, user } = render( , ); const button = getByRole('button', { name: 'Tab 3' }); - userEvent.click(button); + await user.click(button); expect(spy).toHaveBeenCalled(); }); }); diff --git a/src/shared-components/selectorButton/test.tsx b/src/shared-components/selectorButton/test.tsx index 61aee9704..107426bd5 100644 --- a/src/shared-components/selectorButton/test.tsx +++ b/src/shared-components/selectorButton/test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render, userEvent } from 'src/tests/testingLibraryHelpers'; +import { render } from 'src/tests/testingLibraryHelpers'; import { assert } from 'src/utils/assert'; import { AcneOneGlyph } from '../../icons'; @@ -160,40 +160,26 @@ describe('', () => { }); describe('onClick callback', () => { - it('is invoked on click', () => { + it('is invoked on click', async () => { const spy = jest.fn(); - const { container } = render( + const { container, user } = render( , ); assert(container.firstElementChild); - userEvent.click(container.firstElementChild); + await user.click(container.firstElementChild); expect(spy).toHaveBeenCalled(); }); - it('does nothing when no onClick is set', () => { - const { container } = render(); - - // Just check that no exception is thrown - expect(() => { - assert(container.firstElementChild); - userEvent.click(container.firstElementChild); - }).not.toThrow(); - expect(() => { - assert(container.firstElementChild); - userEvent.type(container.firstElementChild, '{enter}'); - }).not.toThrow(); - }); - - it('is invoked when enter is pressed', () => { + it('is invoked when enter is pressed', async () => { const spy = jest.fn(); - const { container } = render( + const { container, user } = render( , ); assert(container.firstElementChild); - userEvent.type(container.firstElementChild, '{enter}'); + await user.type(container.firstElementChild, '{enter}'); expect(spy).toHaveBeenCalled(); }); diff --git a/src/shared-components/tabs/test.tsx b/src/shared-components/tabs/test.tsx index 54683cb44..947a02014 100644 --- a/src/shared-components/tabs/test.tsx +++ b/src/shared-components/tabs/test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render, userEvent } from 'src/tests/testingLibraryHelpers'; +import { render } from 'src/tests/testingLibraryHelpers'; import { Tabs } from './index'; @@ -37,9 +37,9 @@ describe('', () => { }); describe('on click function', () => { - it('returns correct params', () => { + it('returns correct params', async () => { const spy = jest.fn(); - const { getByRole } = render( + const { getByRole, user } = render( ', () => { />, ); - userEvent.click(getByRole('button', { name: 'Tab 1' })); + await user.click(getByRole('button', { name: 'Tab 1' })); expect(spy).toHaveBeenCalledWith({ id: 1, text: 'Tab 1' }); }); diff --git a/src/shared-components/toggle/test.tsx b/src/shared-components/toggle/test.tsx index 98edc2f58..d36d3ed21 100644 --- a/src/shared-components/toggle/test.tsx +++ b/src/shared-components/toggle/test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render, userEvent } from 'src/tests/testingLibraryHelpers'; +import { render } from 'src/tests/testingLibraryHelpers'; import { Toggle } from './index'; @@ -36,11 +36,13 @@ describe('', () => { }); describe('when checkbox is clicked', () => { - it('fires onChange function with correct argument when function exists', () => { + it('fires onChange function with correct argument when function exists', async () => { const spy = jest.fn(); - const { getByRole } = render(); + const { getByRole, user } = render( + , + ); - userEvent.click(getByRole('checkbox')); + await user.click(getByRole('checkbox')); expect(spy).toHaveBeenCalledWith(false); }); }); diff --git a/src/tests/testingLibraryHelpers.tsx b/src/tests/testingLibraryHelpers.tsx index 7e426aa95..f19c3a1d4 100644 --- a/src/tests/testingLibraryHelpers.tsx +++ b/src/tests/testingLibraryHelpers.tsx @@ -1,14 +1,22 @@ import React from 'react'; import { ThemeProvider } from '@emotion/react'; import * as ReactTestingLibrary from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { primaryTheme, REACT_PORTAL_SECTION_ID, ThemeType } from '../constants'; +type UserEventSetup = typeof userEvent.setup; + interface RenderOptions extends ReactTestingLibrary.RenderOptions { theme?: ThemeType; + userEventOptions?: Parameters[number]; withPortalContainer?: boolean; } +interface RenderReturn extends ReactTestingLibrary.RenderResult { + user: ReturnType; +} + /** * Many of our pages rely on components that make use of portals. * @@ -30,24 +38,27 @@ const usePortalContainer = () => { const customRender = ( Component: React.ReactElement, options: RenderOptions = {}, -): ReactTestingLibrary.RenderResult => { +): RenderReturn => { const { theme = primaryTheme, container, withPortalContainer = false, + userEventOptions, ...rest } = options; - return ReactTestingLibrary.render(Component, { - container: withPortalContainer ? usePortalContainer() : container, - wrapper: ({ children }) => ( - {children} - ), - ...rest, - }); + return { + ...ReactTestingLibrary.render(Component, { + container: withPortalContainer ? usePortalContainer() : container, + wrapper: ({ children }) => ( + {children} + ), + ...rest, + }), + user: userEvent.setup(userEventOptions), + }; }; export * from '@testing-library/react'; -export { default as userEvent } from '@testing-library/user-event'; export { customRender as render }; diff --git a/tests/setupTests.ts b/tests/setupTests.ts index a20d3f3c1..7b0828bfa 100644 --- a/tests/setupTests.ts +++ b/tests/setupTests.ts @@ -1,2 +1 @@ import '@testing-library/jest-dom'; -import 'regenerator-runtime/runtime'; // Allows async in tests diff --git a/yarn.lock b/yarn.lock index c0b4ee08e..f1768942a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2883,12 +2883,10 @@ "@testing-library/dom" "^8.0.0" "@types/react-dom" "*" -"@testing-library/user-event@^13.0.7": - version "13.5.0" - resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-13.5.0.tgz#69d77007f1e124d55314a2b73fd204b333b13295" - integrity sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg== - dependencies: - "@babel/runtime" "^7.12.5" +"@testing-library/user-event@^14.0.3": + version "14.0.3" + resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.0.3.tgz#463667596122c13d997f70b73426947ab71de962" + integrity sha512-zIgBG5CxfXbMsm4wBS6iQC3TBNMZk16O25i4shS9MM+eSG7PZHrsBF6LFIesUkepkZ3QKKgstB2/Nola6nvy4A== "@tootallnate/once@1": version "1.1.2"