From b24a7cf3b9280ebf67e08b7d1fd9bd1bb39fc495 Mon Sep 17 00:00:00 2001 From: shaharyarsheikh Date: Mon, 25 Apr 2022 13:22:42 +0500 Subject: [PATCH 01/12] build successful on react 18 --- package.json | 8 ++++---- src/Provider.tsx | 1 + src/utils.tsx | 2 +- yarn.lock | 53 ++++++++++++++++++++++++++++++++---------------- 4 files changed, 41 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index 7b214211..7f7fafc0 100644 --- a/package.json +++ b/package.json @@ -46,8 +46,8 @@ "@types/hoist-non-react-statics": "^3.3.1", "@types/jest": "^26.0.14", "@types/prop-types": "^15.5.6", - "@types/react": "^16.7.18", - "@types/react-dom": "^16.9.5", + "@types/react": "^18.0.6", + "@types/react-dom": "^18.0.2", "@typescript-eslint/eslint-plugin": "^2.23.0", "@typescript-eslint/parser": "^2.23.0", "enzyme": "^3.8.0", @@ -59,8 +59,8 @@ "eslint-plugin-react-hooks": "^4.1.0", "jest": "^26.5.2", "prettier": "1.19.1", - "react": "^16.8.0", - "react-dom": "^16.8.0", + "react": "^18.0.0", + "react-dom": "^18.0.0", "rollup": "^2.32.1", "rollup-plugin-typescript2": "^0.28.0", "rollup-plugin-uglify": "^6.0.4", diff --git a/src/Provider.tsx b/src/Provider.tsx index 49c68cd8..542970a9 100644 --- a/src/Provider.tsx +++ b/src/Provider.tsx @@ -30,6 +30,7 @@ interface OptimizelyProviderProps { user?: Promise | UserInfo; userId?: string; userAttributes?: UserAttributes; + children?: React.ReactNode } interface OptimizelyProviderState { diff --git a/src/utils.tsx b/src/utils.tsx index 16dd56a0..529a8cf2 100644 --- a/src/utils.tsx +++ b/src/utils.tsx @@ -94,7 +94,7 @@ export function hoistStaticsAndForwardRefs>( // From the React docs: // https://reactjs.org/docs/higher-order-components.html#static-methods-must-be-copied-over // https://reactjs.org/docs/forwarding-refs.html#forwarding-refs-in-higher-order-components - const forwardRef: React.RefForwardingComponent = (props, ref) => ; + const forwardRef: React.ForwardRefRenderFunction = (props, ref) => ; forwardRef.displayName = `${displayName}(${Source.displayName || Source.name})`; return hoistNonReactStatics< React.ForwardRefExoticComponent & React.RefAttributes>, diff --git a/yarn.lock b/yarn.lock index 4dd6b2b6..2be01e3d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -712,14 +712,14 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== -"@types/react-dom@^16.9.5": - version "16.9.8" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.8.tgz#fe4c1e11dfc67155733dfa6aa65108b4971cb423" - integrity sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA== +"@types/react-dom@^18.0.2": + version "18.0.2" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.2.tgz#2d6b46557aa30257e87e67a6d952146d15979d79" + integrity sha512-UxeS+Wtj5bvLRREz9tIgsK4ntCuLDo0EcAcACgw3E+9wE8ePDr9uQpq53MfcyxyIS55xJ+0B6mDS8c4qkkHLBg== dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^16.7.18": +"@types/react@*": version "16.9.51" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.51.tgz#f8aa51ffa9996f1387f63686696d9b59713d2b60" integrity sha512-lQa12IyO+DMlnSZ3+AGHRUiUcpK47aakMMoBG8f7HGxJT8Yfe+WE128HIXaHOHVPReAW0oDS3KAI0JI2DDe1PQ== @@ -727,6 +727,15 @@ "@types/prop-types" "*" csstype "^3.0.2" +"@types/react@^18.0.6": + version "18.0.6" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.6.tgz#30206c3830af6ce8639b91ace5868bc2d3d1d96c" + integrity sha512-bPqwzJRzKtfI0mVYr5R+1o9BOE8UEXefwc1LwcBtfnaAn6OoqMhLa/91VA8aeWfDPJt1kHvYKI8RHcQybZLHHA== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + "@types/resolve@1.17.1": version "1.17.1" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" @@ -734,6 +743,11 @@ dependencies: "@types/node" "*" +"@types/scheduler@*": + version "0.16.2" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" + integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== + "@types/stack-utils@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff" @@ -3999,15 +4013,13 @@ randexp@0.4.6: discontinuous-range "1.0.0" ret "~0.1.10" -react-dom@^16.8.0: - version "16.13.1" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.1.tgz#c1bd37331a0486c078ee54c4740720993b2e0e7f" - integrity sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag== +react-dom@^18.0.0: + version "18.0.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.0.0.tgz#26b88534f8f1dbb80853e1eabe752f24100d8023" + integrity sha512-XqX7uzmFo0pUceWFCt7Gff6IyIMzFUn7QMZrbrQfGxtaxXZIcGQzoNpRLE3fQLnS4XzLLPMZX2T9TRcSrasicw== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - scheduler "^0.19.1" + scheduler "^0.21.0" react-is@^16.12.0, react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.6: version "16.13.1" @@ -4024,14 +4036,12 @@ react-test-renderer@^16.0.0-0: react-is "^16.8.6" scheduler "^0.19.1" -react@^16.8.0: - version "16.13.1" - resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" - integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w== +react@^18.0.0: + version "18.0.0" + resolved "https://registry.yarnpkg.com/react/-/react-18.0.0.tgz#b468736d1f4a5891f38585ba8e8fb29f91c3cb96" + integrity sha512-x+VL6wbT4JRVPm7EGxXhZ8w8LTROaxPXOqhlGyVSrv0sB1jkyFGgXxJ8LVoPRLvPR6/CIZGFmfzqUa2NYeMr2A== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" read-pkg-up@^7.0.1: version "7.0.1" @@ -4320,6 +4330,13 @@ scheduler@^0.19.1: loose-envify "^1.1.0" object-assign "^4.1.1" +scheduler@^0.21.0: + version "0.21.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.21.0.tgz#6fd2532ff5a6d877b6edb12f00d8ab7e8f308820" + integrity sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ== + dependencies: + loose-envify "^1.1.0" + "semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.7.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" From c90471bcb0311cd2917973aac18a97ed5982f597 Mon Sep 17 00:00:00 2001 From: shaharyarsheikh Date: Fri, 6 May 2022 14:54:49 +0500 Subject: [PATCH 02/12] experiment.spec updated to RTL --- package.json | 2 + src/Experiment.spec.tsx | 499 ++++++++++++++++++++++++++++++++-------- yarn.lock | 190 ++++++++++++++- 3 files changed, 590 insertions(+), 101 deletions(-) diff --git a/package.json b/package.json index 7f7fafc0..a376db1f 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,8 @@ "@rollup/plugin-commonjs": "^16.0.0", "@rollup/plugin-node-resolve": "^10.0.0", "@rollup/plugin-replace": "^2.3.4", + "@testing-library/jest-dom": "^5.16.4", + "@testing-library/react": "^13.1.1", "@types/enzyme": "^3.1.15", "@types/enzyme-adapter-react-16": "^1.0.3", "@types/hoist-non-react-statics": "^3.3.1", diff --git a/src/Experiment.spec.tsx b/src/Experiment.spec.tsx index b98c8fbc..111ecf4a 100644 --- a/src/Experiment.spec.tsx +++ b/src/Experiment.spec.tsx @@ -15,14 +15,16 @@ */ /// import * as React from 'react'; -import * as Enzyme from 'enzyme'; +// import * as Enzyme from 'enzyme'; import { act } from 'react-dom/test-utils'; import Adapter from 'enzyme-adapter-react-16'; -Enzyme.configure({ adapter: new Adapter() }); +// Enzyme.configure({ adapter: new Adapter() }); import { OptimizelyExperiment } from './Experiment'; -import { mount } from 'enzyme'; +// import { mount } from 'enzyme'; +import { render, screen, waitFor } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; import { OptimizelyProvider } from './Provider'; import { ReactSDKClient } from './client'; import { OptimizelyVariation } from './Variation'; @@ -44,11 +46,11 @@ describe('', () => { optimizelyMock = ({ onReady: jest.fn().mockImplementation(config => onReadyPromise), - activate: jest.fn().mockImplementation(experimentKey => variationKey), - onUserUpdate: jest.fn().mockImplementation(handler => () => { }), + activate: jest.fn().mockImplementation((experimentKey, s) => variationKey), + onUserUpdate: jest.fn().mockImplementation(handler => () => {}), notificationCenter: { - addNotificationListener: jest.fn().mockImplementation((type, handler) => { }), - removeNotificationListener: jest.fn().mockImplementation(id => { }), + addNotificationListener: jest.fn().mockImplementation((type, handler) => {}), + removeNotificationListener: jest.fn().mockImplementation(id => {}), }, user: { id: 'testuser', @@ -57,244 +59,495 @@ describe('', () => { isReady: jest.fn().mockImplementation(() => isReady), getIsReadyPromiseFulfilled: () => true, getIsUsingSdkKey: () => true, - onForcedVariationsUpdate: jest.fn().mockReturnValue(() => { }), + onForcedVariationsUpdate: jest.fn().mockReturnValue(() => {}), } as unknown) as ReactSDKClient; }); it('does not throw an error when not rendered in the context of an OptimizelyProvider', () => { expect(() => { - // @ts-ignore - mount({variation => variation}); + // mount({variation => variation}); + render( + // + + {(variation: string) => {variation}} + + // + ); }).toBeDefined(); }); describe('when isServerSide prop is false', () => { it('should wait client is ready then render result of activate', async () => { - const component = mount( + // const component = mount( + // + // {variation => variation} + // + // ); + + // expect(optimizelyMock.onReady).toHaveBeenCalledWith({ timeout: 100 }); + // // while it's waiting for onReady() + // expect(component.text()).toBe(''); + + // // Simulate client becoming ready: onReady resolving, firing config update notification + // resolver.resolve({ success: true }); + + // await optimizelyMock.onReady(); + + // component.update(); + + // expect(optimizelyMock.activate).toHaveBeenCalledWith('experiment1', undefined, undefined); + // expect(component.text()).toBe(variationKey); + + const { container, rerender } = render( - {variation => variation} + + {(variation: string) => {variation}} + ); expect(optimizelyMock.onReady).toHaveBeenCalledWith({ timeout: 100 }); + // while it's waiting for onReady() - expect(component.text()).toBe(''); + expect(container.innerHTML).toBe(''); // Simulate client becoming ready: onReady resolving, firing config update notification resolver.resolve({ success: true }); await optimizelyMock.onReady(); - component.update(); + rerender( + + + {(variation: string) => {variation}} + + + ); + + await waitFor(() => expect(screen.getByTestId('variation-key')).toHaveTextContent('variationResult')); expect(optimizelyMock.activate).toHaveBeenCalledWith('experiment1', undefined, undefined); - expect(component.text()).toBe(variationKey); }); it('should allow timeout to be overridden', async () => { - const component = mount( + // const component = mount( + // + // + // {(variation: string) => variation} + // + // + // ); + + // expect(optimizelyMock.onReady).toHaveBeenCalledWith({ timeout: 200 }); + // // while it's waiting for onReady() + // expect(component.text()).toBe(''); + + // // Simulate client becoming ready: onReady resolving, firing config update notification + // resolver.resolve({ success: true }); + + // await optimizelyMock.onReady(); + + // expect(optimizelyMock.activate).toHaveBeenCalledWith('experiment1', undefined, undefined); + + const { container } = render( - {variation => variation} + {(variation: string) => {variation}} ); expect(optimizelyMock.onReady).toHaveBeenCalledWith({ timeout: 200 }); + // while it's waiting for onReady() - expect(component.text()).toBe(''); + expect(container.innerHTML).toBe(''); - // Simulate client becoming ready: onReady resolving, firing config update notification + // // Simulate client becoming ready resolver.resolve({ success: true }); - await optimizelyMock.onReady(); expect(optimizelyMock.activate).toHaveBeenCalledWith('experiment1', undefined, undefined); }); it(`should use the Experiment prop's timeout when there is no timeout passed to `, async () => { - const component = mount( + // const component = mount( + // + // + // {(variation: string) => variation} + // + // + // ); + + // expect(optimizelyMock.onReady).toHaveBeenCalledWith({ timeout: 200 }); + // // while it's waiting for onReady() + // expect(component.text()).toBe(''); + + // // Simulate client becoming ready + // resolver.resolve({ success: true }); + + // await optimizelyMock.onReady(); + + // expect(optimizelyMock.activate).toHaveBeenCalledWith('experiment1', undefined, undefined); + + const { container } = render( - {variation => variation} + {(variation: string) => {variation}} ); expect(optimizelyMock.onReady).toHaveBeenCalledWith({ timeout: 200 }); + // while it's waiting for onReady() - expect(component.text()).toBe(''); + expect(container.innerHTML).toBe(''); - // Simulate client becoming ready + // // Simulate client becoming ready resolver.resolve({ success: true }); - await optimizelyMock.onReady(); expect(optimizelyMock.activate).toHaveBeenCalledWith('experiment1', undefined, undefined); }); it('should render using when the variationKey matches', async () => { - const component = mount( + // const component = mount( + // + // + // other variation + // correct variation + // default variation + // + // + // ); + + // // while it's waiting for onReady() + // expect(component.text()).toBe(''); + + // // Simulate client becoming ready + // resolver.resolve({ success: true }); + + // await optimizelyMock.onReady(); + + // component.update(); + + // expect(component.text()).toBe('correct variation'); + + const { container } = render( - other variation - correct variation - default variation + + other variation + + + correct variation + + + default variation + ); - // while it's waiting for onReady() - expect(component.text()).toBe(''); + expect(container.innerHTML).toBe(''); - // Simulate client becoming ready + // // Simulate client becoming ready resolver.resolve({ success: true }); await optimizelyMock.onReady(); - component.update(); - - expect(component.text()).toBe('correct variation'); + await waitFor(() => expect(screen.getByTestId('variation-key')).toHaveTextContent('correct variation')); }); it('should render using ', async () => { - const component = mount( + // const component = mount( + // + // + // other variation + // default variation + // + // + // ); + + // // while it's waiting for onReady() + // expect(component.text()).toBe(''); + // resolver.resolve({ success: true }); + + // await optimizelyMock.onReady(); + + // component.update(); + + // expect(component.text()).toBe('default variation'); + + const { container } = render( - other variation - default variation + + other variation + + + + default variation + ); // while it's waiting for onReady() - expect(component.text()).toBe(''); + expect(container.innerHTML).toBe(''); + + // Simulate client becoming ready resolver.resolve({ success: true }); await optimizelyMock.onReady(); - component.update(); - - expect(component.text()).toBe('default variation'); + await waitFor(() => expect(screen.getByTestId('variation-key')).toHaveTextContent('default variation')); }); it('should render an empty string when no default or matching variation is provided', async () => { - const component = mount( + // const component = mount( + // + // + // other variation + // other variation 2 + // + // + // ); + + // // while it's waiting for onReady() + // expect(component.text()).toBe(''); + // resolver.resolve({ success: true }); + + // await optimizelyMock.onReady(); + + // expect(component.text()).toBe(''); + + const { container } = render( - other variation - other variation 2 + + other variation + + + other variation2 + ); // while it's waiting for onReady() - expect(component.text()).toBe(''); + expect(container.innerHTML).toBe(''); + + // Simulate client becoming ready resolver.resolve({ success: true }); await optimizelyMock.onReady(); - expect(component.text()).toBe(''); + expect(container.innerHTML).toBe(''); }); it('should pass the override props through', async () => { - const component = mount( + // const component = mount( + // + // + // {(variation: string) => variation} + // + // + // ); + + // expect(optimizelyMock.onReady).toHaveBeenCalledWith({ timeout: 100 }); + // // while it's waiting for onReady() + // expect(component.text()).toBe(''); + + // // Simulate client becoming ready + // resolver.resolve({ success: true }); + + // await optimizelyMock.onReady(); + + // component.update(); + + // expect(optimizelyMock.activate).toHaveBeenCalledWith('experiment1', 'james123', { betaUser: true }); + + // expect(component.text()).toBe('variationResult'); + + const { container } = render( - {variation => variation} + {(variation: string) => {variation}} ); expect(optimizelyMock.onReady).toHaveBeenCalledWith({ timeout: 100 }); + // while it's waiting for onReady() - expect(component.text()).toBe(''); + expect(container.innerHTML).toBe(''); // Simulate client becoming ready resolver.resolve({ success: true }); await optimizelyMock.onReady(); - component.update(); - expect(optimizelyMock.activate).toHaveBeenCalledWith('experiment1', 'james123', { betaUser: true }); - expect(component.text()).toBe('variationResult'); + await waitFor(() => expect(screen.getByTestId('variation-key')).toHaveTextContent('variationResult')); }); it('should pass the values for clientReady and didTimeout', async () => { - const component = mount( + // const component = mount( + // + // + // {(variation : string, clientReady: boolean, didTimeout : boolean) => `${variation}|${clientReady}|${didTimeout}`} + // + // + // ); + + // // while it's waiting for onReady() + // expect(component.text()).toBe(''); + + // // Simulate client becoming ready + // resolver.resolve({ success: true }); + + // await optimizelyMock.onReady(); + + // component.update(); + + // expect(optimizelyMock.activate).toHaveBeenCalledWith('experiment1', undefined, undefined); + // expect(component.text()).toBe('variationResult|true|false'); + + const { container } = render( - {(variation, clientReady, didTimeout) => `${variation}|${clientReady}|${didTimeout}`} + {(variation: string, clientReady: boolean, didTimeout: boolean) => ( + {`${variation}|${clientReady}|${didTimeout}`} + )} ); // while it's waiting for onReady() - expect(component.text()).toBe(''); + expect(container.innerHTML).toBe(''); // Simulate client becoming ready resolver.resolve({ success: true }); await optimizelyMock.onReady(); - component.update(); - expect(optimizelyMock.activate).toHaveBeenCalledWith('experiment1', undefined, undefined); - expect(component.text()).toBe('variationResult|true|false'); + await waitFor(() => expect(screen.getByTestId('variation-key')).toHaveTextContent('variationResult|true|false')); }); describe('when the onReady() promise return { success: false }', () => { it('should still render', async () => { - const component = mount( + // const component = mount( + // + // + // other variation + // other variation 2 + // + // + // ); + + // // while it's waiting for onReady() + // expect(component.text()).toBe(''); + // resolver.resolve({ success: false, reason: 'fail' }); + + // await optimizelyMock.onReady(); + + // expect(component.text()).toBe(''); + + const { container } = render( - other variation - other variation 2 + + other variation + + + other variation2 + ); // while it's waiting for onReady() - expect(component.text()).toBe(''); - resolver.resolve({ success: false, reason: 'fail' }); + expect(container.innerHTML).toBe(''); + resolver.resolve({ success: false, reason: 'fail' }); await optimizelyMock.onReady(); - expect(component.text()).toBe(''); + expect(container.innerHTML).toBe(''); }); }); }); describe('when autoUpdate prop is true', () => { it('should re-render when the OPTIMIZELY_CONFIG_UDPATE notification fires', async () => { - const component = mount( + // const component = mount( + // + // + // {(variation: string) => variation} + // + // + // ); + + // expect(optimizelyMock.onReady).toHaveBeenCalledWith({ timeout: 100 }); + // // while it's waiting for onReady() + // expect(component.text()).toBe(''); + + // // Simulate client becoming ready + // resolver.resolve({ success: true }); + // isReady = true; + // await act(async () => await optimizelyMock.onReady()); + + // component.update(); + + // expect(optimizelyMock.activate).toHaveBeenCalledWith('experiment1', undefined, undefined); + + // expect(component.text()).toBe('variationResult'); + + // // capture the OPTIMIZELY_CONFIG_UPDATE function + // // change the return value of activate + // const mockActivate = optimizelyMock.activate as jest.Mock; + // mockActivate.mockImplementationOnce(() => 'newVariation'); + + // const updateFn = (optimizelyMock.notificationCenter.addNotificationListener as jest.Mock).mock.calls[0][1]; + // updateFn(); + // expect(optimizelyMock.activate).toBeCalledTimes(2); + + // component.update(); + + // expect(optimizelyMock.activate).toHaveBeenCalledWith('experiment1', undefined, undefined); + // expect(component.text()).toBe('newVariation'); + + const { container } = render( - {variation => variation} + {(variation: string, clientReady: boolean, didTimeout: boolean) => ( + {variation} + )} ); - expect(optimizelyMock.onReady).toHaveBeenCalledWith({ timeout: 100 }); + // while it's waiting for onReady() - expect(component.text()).toBe(''); + expect(container.innerHTML).toBe(''); // Simulate client becoming ready resolver.resolve({ success: true }); isReady = true; await act(async () => await optimizelyMock.onReady()); - component.update(); - expect(optimizelyMock.activate).toHaveBeenCalledWith('experiment1', undefined, undefined); - expect(component.text()).toBe('variationResult'); + await waitFor(() => expect(screen.getByTestId('variation-key')).toHaveTextContent('variationResult')); // capture the OPTIMIZELY_CONFIG_UPDATE function // change the return value of activate @@ -303,37 +556,60 @@ describe('', () => { const updateFn = (optimizelyMock.notificationCenter.addNotificationListener as jest.Mock).mock.calls[0][1]; updateFn(); - expect(optimizelyMock.activate).toBeCalledTimes(2); - - component.update(); expect(optimizelyMock.activate).toHaveBeenCalledWith('experiment1', undefined, undefined); - expect(component.text()).toBe('newVariation'); + await waitFor(() => expect(screen.getByTestId('variation-key')).toHaveTextContent('newVariation')); + expect(optimizelyMock.activate).toBeCalledTimes(2); }); it('should re-render when the user changes', async () => { - const component = mount( + // const component = mount( + // + // + // {(variation: string) => variation} + // + // + // ); + // expect(optimizelyMock.onReady).toHaveBeenCalledWith({ timeout: 100 }); + // // while it's waiting for onReady() + // expect(component.text()).toBe(''); + // // Simulate client becoming ready + // resolver.resolve({ success: true }); + // isReady = true; + // await act(async () => await optimizelyMock.onReady()); + // component.update(); + // expect(optimizelyMock.activate).toHaveBeenCalledWith('experiment1', undefined, undefined); + // expect(component.text()).toBe('variationResult'); + // // capture the onUserUpdate function + // const updateFn = (optimizelyMock.onUserUpdate as jest.Mock).mock.calls[0][0]; + // const mockActivate = optimizelyMock.activate as jest.Mock; + // mockActivate.mockImplementationOnce(() => 'newVariation'); + // updateFn(); + // component.update(); + // expect(optimizelyMock.activate).toBeCalledTimes(2); + // expect(optimizelyMock.activate).toHaveBeenCalledWith('experiment1', undefined, undefined); + // expect(component.text()).toBe('newVariation'); + + const { container } = render( - {variation => variation} + {(variation: string, clientReady: boolean, didTimeout: boolean) => ( + {variation} + )} ); - expect(optimizelyMock.onReady).toHaveBeenCalledWith({ timeout: 100 }); - // while it's waiting for onReady() - expect(component.text()).toBe(''); + // while it's waiting for onReady() + expect(container.innerHTML).toBe(''); // Simulate client becoming ready resolver.resolve({ success: true }); isReady = true; await act(async () => await optimizelyMock.onReady()); - - component.update(); - expect(optimizelyMock.activate).toHaveBeenCalledWith('experiment1', undefined, undefined); - expect(component.text()).toBe('variationResult'); + await waitFor(() => expect(screen.getByTestId('variation-key')).toHaveTextContent('variationResult')); // capture the onUserUpdate function const updateFn = (optimizelyMock.onUserUpdate as jest.Mock).mock.calls[0][0]; @@ -341,38 +617,63 @@ describe('', () => { mockActivate.mockImplementationOnce(() => 'newVariation'); updateFn(); - component.update(); - - expect(optimizelyMock.activate).toBeCalledTimes(2); - expect(optimizelyMock.activate).toHaveBeenCalledWith('experiment1', undefined, undefined); - expect(component.text()).toBe('newVariation'); + await waitFor(() => expect(screen.getByTestId('variation-key')).toHaveTextContent('newVariation')); + expect(optimizelyMock.activate).toBeCalledTimes(2); }); }); describe('when the isServerSide prop is true', () => { it('should immediately render the result of the experiment without waiting', async () => { - const component = mount( + // const component = mount( + // + // {(variation: string) => variation} + // + // ); + + // expect(component.text()).toBe(variationKey); + + render( - {variation => variation} + + {(variation: string, clientReady: boolean, didTimeout: boolean) => ( + {variation} + )} + ); - expect(component.text()).toBe(variationKey); + await waitFor(() => expect(screen.getByTestId('variation-key')).toHaveTextContent(variationKey)); }); it('should render using when the variationKey matches', async () => { - const component = mount( + // const component = mount( + // + // + // other variation + // correct variation + // default variation + // + // + // ); + + // expect(component.text()).toBe('correct variation'); + render( - other variation - correct variation - default variation + + other variation + + + correct variation + + + default variation + ); - - expect(component.text()).toBe('correct variation'); + await waitFor(() => expect(screen.getByTestId('variation-key')).toHaveTextContent('correct variation')); }); }); }); diff --git a/yarn.lock b/yarn.lock index 2be01e3d..dd26e3b4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -225,6 +225,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" +"@babel/runtime@^7.12.5", "@babel/runtime@^7.9.2": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.9.tgz#d19fbf802d01a8cb6cf053a64e472d42c434ba72" + integrity sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.10.4", "@babel/template@^7.3.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" @@ -567,6 +574,49 @@ dependencies: "@sinonjs/commons" "^1.7.0" +"@testing-library/dom@^8.5.0": + version "8.13.0" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.13.0.tgz#bc00bdd64c7d8b40841e27a70211399ad3af46f5" + integrity sha512-9VHgfIatKNXQNaZTtLnalIy0jNZzY35a4S3oi08YAt9Hv1VsfZ/DfA45lM8D/UhtHBGJ4/lGwp0PZkVndRkoOQ== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.12.5" + "@types/aria-query" "^4.2.0" + aria-query "^5.0.0" + chalk "^4.1.0" + dom-accessibility-api "^0.5.9" + lz-string "^1.4.4" + pretty-format "^27.0.2" + +"@testing-library/jest-dom@^5.16.4": + version "5.16.4" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.4.tgz#938302d7b8b483963a3ae821f1c0808f872245cd" + integrity sha512-Gy+IoFutbMQcky0k+bqqumXZ1cTGswLsFqmNLzNdSKkU9KGV2u9oXhukCbbJ9/LRPKiqwxEE8VpV/+YZlfkPUA== + dependencies: + "@babel/runtime" "^7.9.2" + "@types/testing-library__jest-dom" "^5.9.1" + aria-query "^5.0.0" + chalk "^3.0.0" + css "^3.0.0" + css.escape "^1.5.1" + dom-accessibility-api "^0.5.6" + lodash "^4.17.15" + redent "^3.0.0" + +"@testing-library/react@^13.1.1": + version "13.1.1" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-13.1.1.tgz#6c1635e25acca8ca5be8ee3b19ad1391681c5846" + integrity sha512-8mirlAa0OKaUvnqnZF6MdAh2tReYA2KtWVw1PKvaF5EcCZqgK5pl8iF+3uW90JdG5Ua2c2c2E2wtLdaug3dsVg== + dependencies: + "@babel/runtime" "^7.12.5" + "@testing-library/dom" "^8.5.0" + "@types/react-dom" "^18.0.0" + +"@types/aria-query@^4.2.0": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc" + integrity sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig== + "@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7": version "7.1.10" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.10.tgz#ca58fc195dd9734e77e57c6f2df565623636ab40" @@ -679,6 +729,14 @@ dependencies: "@types/istanbul-lib-report" "*" +"@types/jest@*": + version "27.4.1" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.4.1.tgz#185cbe2926eaaf9662d340cc02e548ce9e11ab6d" + integrity sha512-23iPJADSmicDVrWk+HT58LMJtzLAnB2AgIzplQuq/bSrGaxCrlvRFjGbXmamnnk/mAmCdLStiGqggu28ocUyiw== + dependencies: + jest-matcher-utils "^27.0.0" + pretty-format "^27.0.0" + "@types/jest@26.x", "@types/jest@^26.0.14": version "26.0.14" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.14.tgz#078695f8f65cb55c5a98450d65083b2b73e5a3f3" @@ -712,7 +770,7 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== -"@types/react-dom@^18.0.2": +"@types/react-dom@^18.0.0", "@types/react-dom@^18.0.2": version "18.0.2" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.2.tgz#2d6b46557aa30257e87e67a6d952146d15979d79" integrity sha512-UxeS+Wtj5bvLRREz9tIgsK4ntCuLDo0EcAcACgw3E+9wE8ePDr9uQpq53MfcyxyIS55xJ+0B6mDS8c4qkkHLBg== @@ -753,6 +811,13 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff" integrity sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw== +"@types/testing-library__jest-dom@^5.9.1": + version "5.14.3" + resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.3.tgz#ee6c7ffe9f8595882ee7bda8af33ae7b8789ef17" + integrity sha512-oKZe+Mf4ioWlMuzVBaXQ9WDnEm1+umLx0InILg+yvZVBBDmzV5KfZyLrCvadtWcx8+916jLmHafcmqqffl+iIw== + dependencies: + "@types/jest" "*" + "@types/yargs-parser@*": version "15.0.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d" @@ -878,6 +943,11 @@ ansi-regex@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -892,6 +962,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + anymatch@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" @@ -915,6 +990,11 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +aria-query@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.0.0.tgz#210c21aaf469613ee8c9a62c7f86525e058db52c" + integrity sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg== + arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" @@ -1412,6 +1492,20 @@ css-what@2.1: resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== +css.escape@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" + integrity sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s= + +css@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/css/-/css-3.0.0.tgz#4447a4d58fdd03367c516ca9f64ae365cee4aa5d" + integrity sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ== + dependencies: + inherits "^2.0.4" + source-map "^0.6.1" + source-map-resolve "^0.6.0" + cssom@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" @@ -1545,6 +1639,11 @@ diff-sequences@^26.5.0: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.5.0.tgz#ef766cf09d43ed40406611f11c6d8d9dd8b2fefd" integrity sha512-ZXx86srb/iYy6jG71k++wBN9P9J05UNQ5hQHQd9MtMPvcqXPx/vKU69jfHV637D00Q2gSgPk2D+jSx3l1lDW/Q== +diff-sequences@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" + integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== + discontinuous-range@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a" @@ -1564,6 +1663,11 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +dom-accessibility-api@^0.5.6, dom-accessibility-api@^0.5.9: + version "0.5.14" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz#56082f71b1dc7aac69d83c4285eef39c15d93f56" + integrity sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg== + dom-serializer@0: version "0.2.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" @@ -2472,6 +2576,11 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -2480,7 +2589,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.3: +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -2877,6 +2986,16 @@ jest-diff@^26.5.2: jest-get-type "^26.3.0" pretty-format "^26.5.2" +jest-diff@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def" + integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw== + dependencies: + chalk "^4.0.0" + diff-sequences "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + jest-docblock@^26.0.0: version "26.0.0" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-26.0.0.tgz#3e2fa20899fc928cb13bd0ff68bd3711a36889b5" @@ -2930,6 +3049,11 @@ jest-get-type@^26.3.0: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== +jest-get-type@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" + integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== + jest-haste-map@^26.5.2: version "26.5.2" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.5.2.tgz#a15008abfc502c18aa56e4919ed8c96304ceb23d" @@ -2993,6 +3117,16 @@ jest-matcher-utils@^26.5.2: jest-get-type "^26.3.0" pretty-format "^26.5.2" +jest-matcher-utils@^27.0.0: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab" + integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw== + dependencies: + chalk "^4.0.0" + jest-diff "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + jest-message-util@^26.5.2: version "26.5.2" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.5.2.tgz#6c4c4c46dcfbabb47cd1ba2f6351559729bc11bb" @@ -3407,6 +3541,11 @@ loose-envify@^1.1.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +lz-string@^1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" + integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY= + magic-string@^0.25.7: version "0.25.7" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" @@ -3499,6 +3638,11 @@ mimic-response@^2.0.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== +min-indent@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" + integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== + minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" @@ -3939,6 +4083,15 @@ pretty-format@^26.5.2: ansi-styles "^4.0.0" react-is "^16.12.0" +pretty-format@^27.0.0, pretty-format@^27.0.2, pretty-format@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== + dependencies: + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^17.0.1" + progress@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" @@ -4026,6 +4179,11 @@ react-is@^16.12.0, react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1, react- resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-is@^17.0.1: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + react-test-renderer@^16.0.0-0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.13.1.tgz#de25ea358d9012606de51e012d9742e7f0deabc1" @@ -4071,11 +4229,24 @@ readable-stream@^3.1.1: string_decoder "^1.1.1" util-deprecate "^1.0.1" +redent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" + integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== + dependencies: + indent-string "^4.0.0" + strip-indent "^3.0.0" + reflect.ownkeys@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460" integrity sha1-dJrO7H8/34tj+SegSAnpDFwLNGA= +regenerator-runtime@^0.13.4: + version "0.13.9" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== + regex-not@^1.0.0, regex-not@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" @@ -4474,6 +4645,14 @@ source-map-resolve@^0.5.0: source-map-url "^0.4.0" urix "^0.1.0" +source-map-resolve@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.6.0.tgz#3d9df87e236b53f16d01e58150fc7711138e5ed2" + integrity sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + source-map-support@^0.5.6: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" @@ -4678,6 +4857,13 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== +strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" + strip-json-comments@^3.0.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" From 50de0123ada3a4b3c8116602e43eec21849f9c83 Mon Sep 17 00:00:00 2001 From: shaharyarsheikh Date: Tue, 10 May 2022 10:39:16 +0500 Subject: [PATCH 03/12] experiment file's extra code removed --- src/Experiment.spec.tsx | 278 +--------------------------------------- 1 file changed, 3 insertions(+), 275 deletions(-) diff --git a/src/Experiment.spec.tsx b/src/Experiment.spec.tsx index 111ecf4a..d8aa3d19 100644 --- a/src/Experiment.spec.tsx +++ b/src/Experiment.spec.tsx @@ -15,16 +15,11 @@ */ /// import * as React from 'react'; -// import * as Enzyme from 'enzyme'; import { act } from 'react-dom/test-utils'; -import Adapter from 'enzyme-adapter-react-16'; -// Enzyme.configure({ adapter: new Adapter() }); - -import { OptimizelyExperiment } from './Experiment'; - -// import { mount } from 'enzyme'; import { render, screen, waitFor } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; + +import { OptimizelyExperiment } from './Experiment'; import { OptimizelyProvider } from './Provider'; import { ReactSDKClient } from './client'; import { OptimizelyVariation } from './Variation'; @@ -65,39 +60,16 @@ describe('', () => { it('does not throw an error when not rendered in the context of an OptimizelyProvider', () => { expect(() => { - // mount({variation => variation}); render( - // {(variation: string) => {variation}} - // ); }).toBeDefined(); }); describe('when isServerSide prop is false', () => { it('should wait client is ready then render result of activate', async () => { - // const component = mount( - // - // {variation => variation} - // - // ); - - // expect(optimizelyMock.onReady).toHaveBeenCalledWith({ timeout: 100 }); - // // while it's waiting for onReady() - // expect(component.text()).toBe(''); - - // // Simulate client becoming ready: onReady resolving, firing config update notification - // resolver.resolve({ success: true }); - - // await optimizelyMock.onReady(); - - // component.update(); - - // expect(optimizelyMock.activate).toHaveBeenCalledWith('experiment1', undefined, undefined); - // expect(component.text()).toBe(variationKey); - const { container, rerender } = render( @@ -130,25 +102,6 @@ describe('', () => { }); it('should allow timeout to be overridden', async () => { - // const component = mount( - // - // - // {(variation: string) => variation} - // - // - // ); - - // expect(optimizelyMock.onReady).toHaveBeenCalledWith({ timeout: 200 }); - // // while it's waiting for onReady() - // expect(component.text()).toBe(''); - - // // Simulate client becoming ready: onReady resolving, firing config update notification - // resolver.resolve({ success: true }); - - // await optimizelyMock.onReady(); - - // expect(optimizelyMock.activate).toHaveBeenCalledWith('experiment1', undefined, undefined); - const { container } = render( @@ -170,25 +123,6 @@ describe('', () => { }); it(`should use the Experiment prop's timeout when there is no timeout passed to `, async () => { - // const component = mount( - // - // - // {(variation: string) => variation} - // - // - // ); - - // expect(optimizelyMock.onReady).toHaveBeenCalledWith({ timeout: 200 }); - // // while it's waiting for onReady() - // expect(component.text()).toBe(''); - - // // Simulate client becoming ready - // resolver.resolve({ success: true }); - - // await optimizelyMock.onReady(); - - // expect(optimizelyMock.activate).toHaveBeenCalledWith('experiment1', undefined, undefined); - const { container } = render( @@ -210,28 +144,6 @@ describe('', () => { }); it('should render using when the variationKey matches', async () => { - // const component = mount( - // - // - // other variation - // correct variation - // default variation - // - // - // ); - - // // while it's waiting for onReady() - // expect(component.text()).toBe(''); - - // // Simulate client becoming ready - // resolver.resolve({ success: true }); - - // await optimizelyMock.onReady(); - - // component.update(); - - // expect(component.text()).toBe('correct variation'); - const { container } = render( @@ -250,7 +162,7 @@ describe('', () => { // while it's waiting for onReady() expect(container.innerHTML).toBe(''); - // // Simulate client becoming ready + // Simulate client becoming ready resolver.resolve({ success: true }); await optimizelyMock.onReady(); @@ -259,25 +171,6 @@ describe('', () => { }); it('should render using ', async () => { - // const component = mount( - // - // - // other variation - // default variation - // - // - // ); - - // // while it's waiting for onReady() - // expect(component.text()).toBe(''); - // resolver.resolve({ success: true }); - - // await optimizelyMock.onReady(); - - // component.update(); - - // expect(component.text()).toBe('default variation'); - const { container } = render( @@ -304,23 +197,6 @@ describe('', () => { }); it('should render an empty string when no default or matching variation is provided', async () => { - // const component = mount( - // - // - // other variation - // other variation 2 - // - // - // ); - - // // while it's waiting for onReady() - // expect(component.text()).toBe(''); - // resolver.resolve({ success: true }); - - // await optimizelyMock.onReady(); - - // expect(component.text()).toBe(''); - const { container } = render( @@ -346,33 +222,6 @@ describe('', () => { }); it('should pass the override props through', async () => { - // const component = mount( - // - // - // {(variation: string) => variation} - // - // - // ); - - // expect(optimizelyMock.onReady).toHaveBeenCalledWith({ timeout: 100 }); - // // while it's waiting for onReady() - // expect(component.text()).toBe(''); - - // // Simulate client becoming ready - // resolver.resolve({ success: true }); - - // await optimizelyMock.onReady(); - - // component.update(); - - // expect(optimizelyMock.activate).toHaveBeenCalledWith('experiment1', 'james123', { betaUser: true }); - - // expect(component.text()).toBe('variationResult'); - const { container } = render( ', () => { }); it('should pass the values for clientReady and didTimeout', async () => { - // const component = mount( - // - // - // {(variation : string, clientReady: boolean, didTimeout : boolean) => `${variation}|${clientReady}|${didTimeout}`} - // - // - // ); - - // // while it's waiting for onReady() - // expect(component.text()).toBe(''); - - // // Simulate client becoming ready - // resolver.resolve({ success: true }); - - // await optimizelyMock.onReady(); - - // component.update(); - - // expect(optimizelyMock.activate).toHaveBeenCalledWith('experiment1', undefined, undefined); - // expect(component.text()).toBe('variationResult|true|false'); - const { container } = render( @@ -446,23 +274,6 @@ describe('', () => { describe('when the onReady() promise return { success: false }', () => { it('should still render', async () => { - // const component = mount( - // - // - // other variation - // other variation 2 - // - // - // ); - - // // while it's waiting for onReady() - // expect(component.text()).toBe(''); - // resolver.resolve({ success: false, reason: 'fail' }); - - // await optimizelyMock.onReady(); - - // expect(component.text()).toBe(''); - const { container } = render( @@ -489,43 +300,6 @@ describe('', () => { describe('when autoUpdate prop is true', () => { it('should re-render when the OPTIMIZELY_CONFIG_UDPATE notification fires', async () => { - // const component = mount( - // - // - // {(variation: string) => variation} - // - // - // ); - - // expect(optimizelyMock.onReady).toHaveBeenCalledWith({ timeout: 100 }); - // // while it's waiting for onReady() - // expect(component.text()).toBe(''); - - // // Simulate client becoming ready - // resolver.resolve({ success: true }); - // isReady = true; - // await act(async () => await optimizelyMock.onReady()); - - // component.update(); - - // expect(optimizelyMock.activate).toHaveBeenCalledWith('experiment1', undefined, undefined); - - // expect(component.text()).toBe('variationResult'); - - // // capture the OPTIMIZELY_CONFIG_UPDATE function - // // change the return value of activate - // const mockActivate = optimizelyMock.activate as jest.Mock; - // mockActivate.mockImplementationOnce(() => 'newVariation'); - - // const updateFn = (optimizelyMock.notificationCenter.addNotificationListener as jest.Mock).mock.calls[0][1]; - // updateFn(); - // expect(optimizelyMock.activate).toBeCalledTimes(2); - - // component.update(); - - // expect(optimizelyMock.activate).toHaveBeenCalledWith('experiment1', undefined, undefined); - // expect(component.text()).toBe('newVariation'); - const { container } = render( @@ -563,33 +337,6 @@ describe('', () => { }); it('should re-render when the user changes', async () => { - // const component = mount( - // - // - // {(variation: string) => variation} - // - // - // ); - // expect(optimizelyMock.onReady).toHaveBeenCalledWith({ timeout: 100 }); - // // while it's waiting for onReady() - // expect(component.text()).toBe(''); - // // Simulate client becoming ready - // resolver.resolve({ success: true }); - // isReady = true; - // await act(async () => await optimizelyMock.onReady()); - // component.update(); - // expect(optimizelyMock.activate).toHaveBeenCalledWith('experiment1', undefined, undefined); - // expect(component.text()).toBe('variationResult'); - // // capture the onUserUpdate function - // const updateFn = (optimizelyMock.onUserUpdate as jest.Mock).mock.calls[0][0]; - // const mockActivate = optimizelyMock.activate as jest.Mock; - // mockActivate.mockImplementationOnce(() => 'newVariation'); - // updateFn(); - // component.update(); - // expect(optimizelyMock.activate).toBeCalledTimes(2); - // expect(optimizelyMock.activate).toHaveBeenCalledWith('experiment1', undefined, undefined); - // expect(component.text()).toBe('newVariation'); - const { container } = render( @@ -625,14 +372,6 @@ describe('', () => { describe('when the isServerSide prop is true', () => { it('should immediately render the result of the experiment without waiting', async () => { - // const component = mount( - // - // {(variation: string) => variation} - // - // ); - - // expect(component.text()).toBe(variationKey); - render( @@ -647,17 +386,6 @@ describe('', () => { }); it('should render using when the variationKey matches', async () => { - // const component = mount( - // - // - // other variation - // correct variation - // default variation - // - // - // ); - - // expect(component.text()).toBe('correct variation'); render( From 6fbd47d881b92d2ef692a54dafc7f8967df0558e Mon Sep 17 00:00:00 2001 From: shaharyarsheikh Date: Tue, 10 May 2022 10:46:22 +0500 Subject: [PATCH 04/12] experiment file's extra code removed --- src/Experiment.spec.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Experiment.spec.tsx b/src/Experiment.spec.tsx index d8aa3d19..1ce940f6 100644 --- a/src/Experiment.spec.tsx +++ b/src/Experiment.spec.tsx @@ -41,7 +41,7 @@ describe('', () => { optimizelyMock = ({ onReady: jest.fn().mockImplementation(config => onReadyPromise), - activate: jest.fn().mockImplementation((experimentKey, s) => variationKey), + activate: jest.fn().mockImplementation(experimentKey => variationKey), onUserUpdate: jest.fn().mockImplementation(handler => () => {}), notificationCenter: { addNotificationListener: jest.fn().mockImplementation((type, handler) => {}), @@ -115,7 +115,7 @@ describe('', () => { // while it's waiting for onReady() expect(container.innerHTML).toBe(''); - // // Simulate client becoming ready + // Simulate client becoming ready; onReady resolving, firing config update notification resolver.resolve({ success: true }); await optimizelyMock.onReady(); @@ -136,7 +136,7 @@ describe('', () => { // while it's waiting for onReady() expect(container.innerHTML).toBe(''); - // // Simulate client becoming ready + // Simulate client becoming ready resolver.resolve({ success: true }); await optimizelyMock.onReady(); From e52107628d09ce5644da5c4d138a47cb6675947c Mon Sep 17 00:00:00 2001 From: shaharyarsheikh Date: Wed, 11 May 2022 14:03:38 +0500 Subject: [PATCH 05/12] features.spec updated --- src/Feature.spec.tsx | 139 ++++++++++++++++++++----------------------- 1 file changed, 65 insertions(+), 74 deletions(-) diff --git a/src/Feature.spec.tsx b/src/Feature.spec.tsx index e189791a..bf667ade 100644 --- a/src/Feature.spec.tsx +++ b/src/Feature.spec.tsx @@ -14,14 +14,12 @@ * limitations under the License. */ import * as React from 'react'; -import * as Enzyme from 'enzyme'; import { act } from 'react-dom/test-utils'; -import Adapter from 'enzyme-adapter-react-16'; -Enzyme.configure({ adapter: new Adapter() }); +import { render, screen, waitFor } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; -import { mount } from 'enzyme'; import { OptimizelyProvider } from './Provider'; -import { ReactSDKClient } from './client'; +import { ReactSDKClient, VariableValuesObject } from './client'; import { OptimizelyFeature } from './Feature'; describe('', () => { @@ -46,10 +44,10 @@ describe('', () => { onReady: jest.fn().mockImplementation(config => onReadyPromise), getFeatureVariables: jest.fn().mockImplementation(() => featureVariables), isFeatureEnabled: jest.fn().mockImplementation(() => isEnabledMock), - onUserUpdate: jest.fn().mockImplementation(handler => () => { }), + onUserUpdate: jest.fn().mockImplementation(handler => () => {}), notificationCenter: { - addNotificationListener: jest.fn().mockImplementation((type, handler) => { }), - removeNotificationListener: jest.fn().mockImplementation(id => { }), + addNotificationListener: jest.fn().mockImplementation((type, handler) => {}), + removeNotificationListener: jest.fn().mockImplementation(id => {}), }, user: { id: 'testuser', @@ -70,10 +68,12 @@ describe('', () => { describe('when the isServerSide prop is false', () => { it('should wait until onReady() is resolved then render result of isFeatureEnabled and getFeatureVariables', async () => { - const component = mount( + const { container } = render( - {(isEnabled, variables) => `${isEnabled ? 'true' : 'false'}|${variables.foo}`} + {(isEnabled: boolean, variables: VariableValuesObject) => ( + {`${isEnabled ? 'true' : 'false'}|${variables.foo}`} + )} ); @@ -81,25 +81,25 @@ describe('', () => { expect(optimizelyMock.onReady).toHaveBeenCalledWith({ timeout: undefined }); // while it's waiting for onReady() - expect(component.text()).toBe(''); + expect(container.innerHTML).toBe(''); // Simulate client becoming ready resolver.resolve({ success: true }); await optimizelyMock.onReady(); - component.update(); - expect(optimizelyMock.isFeatureEnabled).toHaveBeenCalledWith('feature1', undefined, undefined); expect(optimizelyMock.getFeatureVariables).toHaveBeenCalledWith('feature1', undefined, undefined); - expect(component.text()).toBe('true|bar'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('true|bar')); }); it('should respect the timeout provided in ', async () => { - const component = mount( + const { container } = render( - {(isEnabled, variables) => `${isEnabled ? 'true' : 'false'}|${variables.foo}`} + {(isEnabled: boolean, variables: VariableValuesObject) => ( + {`${isEnabled ? 'true' : 'false'}|${variables.foo}`} + )} ); @@ -107,51 +107,51 @@ describe('', () => { expect(optimizelyMock.onReady).toHaveBeenCalledWith({ timeout: 200 }); // while it's waiting for onReady() - expect(component.text()).toBe(''); + expect(container.innerHTML).toBe(''); // Simulate client becoming ready resolver.resolve({ success: true }); await optimizelyMock.onReady(); - component.update(); - expect(optimizelyMock.isFeatureEnabled).toHaveBeenCalledWith('feature1', undefined, undefined); expect(optimizelyMock.getFeatureVariables).toHaveBeenCalledWith('feature1', undefined, undefined); - expect(component.text()).toBe('true|bar'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('true|bar')); }); it('should pass the values for clientReady and didTimeout', async () => { - const component = mount( + const { container } = render( - - {(isEnabled, variables, clientReady, didTimeout) => - `${isEnabled ? 'true' : 'false'}|${variables.foo}|${clientReady}|${didTimeout}` - } + + {(isEnabled: boolean, variables: VariableValuesObject, clientReady: boolean, didTimeout: boolean) => ( + {`${isEnabled ? 'true' : 'false'}|${ + variables.foo + }|${clientReady}|${didTimeout}`} + )} ); // while it's waiting for onReady() - expect(component.text()).toBe(''); + expect(container.innerHTML).toBe(''); // Simulate client becoming ready resolver.resolve({ success: true }); await optimizelyMock.onReady(); - component.update(); - expect(optimizelyMock.isFeatureEnabled).toHaveBeenCalledWith('feature1', undefined, undefined); expect(optimizelyMock.getFeatureVariables).toHaveBeenCalledWith('feature1', undefined, undefined); - expect(component.text()).toBe('true|bar|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('true|bar|true|false')); }); it('should respect a locally passed timeout prop', async () => { - const component = mount( + const { container } = render( - {(isEnabled, variables) => `${isEnabled ? 'true' : 'false'}|${variables.foo}`} + {(isEnabled: boolean, variables: VariableValuesObject) => ( + {`${isEnabled ? 'true' : 'false'}|${variables.foo}`} + )} ); @@ -159,50 +159,48 @@ describe('', () => { expect(optimizelyMock.onReady).toHaveBeenCalledWith({ timeout: 100 }); // while it's waiting for onReady() - expect(component.text()).toBe(''); + expect(container.innerHTML).toBe(''); // Simulate client becoming ready resolver.resolve({ success: true }); await optimizelyMock.onReady(); - component.update(); - expect(optimizelyMock.isFeatureEnabled).toHaveBeenCalledWith('feature1', undefined, undefined); expect(optimizelyMock.getFeatureVariables).toHaveBeenCalledWith('feature1', undefined, undefined); - expect(component.text()).toBe('true|bar'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('true|bar')); }); it('should pass the override props through', async () => { - const component = mount( - + const { container } = render( + - {(isEnabled, variables) => `${isEnabled ? 'true' : 'false'}|${variables.foo}`} + {(isEnabled: boolean, variables: VariableValuesObject) => ( + {`${isEnabled ? 'true' : 'false'}|${variables.foo}`} + )} ); // while it's waiting for onReady() - expect(component.text()).toBe(''); - + expect(container.innerHTML).toBe(''); // Simulate client becoming ready resolver.resolve({ success: true }); - await optimizelyMock.onReady(); - component.update(); - expect(optimizelyMock.isFeatureEnabled).toHaveBeenCalledWith('feature1', 'james123', { betaUser: true }); expect(optimizelyMock.getFeatureVariables).toHaveBeenCalledWith('feature1', 'james123', { betaUser: true }); - expect(component.text()).toBe('true|bar'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('true|bar')); }); describe(`when the "autoUpdate" prop is true`, () => { it('should update when the OPTIMIZELY_CONFIG_UPDATE handler is called', async () => { - const component = mount( + const { container } = render( - {(isEnabled, variables) => `${isEnabled ? 'true' : 'false'}|${variables.foo}`} + {(isEnabled: boolean, variables: VariableValuesObject) => ( + {`${isEnabled ? 'true' : 'false'}|${variables.foo}`} + )} ); @@ -210,7 +208,7 @@ describe('', () => { expect(optimizelyMock.onReady).toHaveBeenCalledWith({ timeout: 200 }); // while it's waiting for onReady() - expect(component.text()).toBe(''); + expect(container.innerHTML).toBe(''); // Simulate client becoming ready resolver.resolve({ success: true }); @@ -218,11 +216,9 @@ describe('', () => { isReady = true; await act(async () => await optimizelyMock.onReady()); - component.update(); - expect(optimizelyMock.isFeatureEnabled).toHaveBeenCalledWith('feature1', undefined, undefined); expect(optimizelyMock.getFeatureVariables).toHaveBeenCalledWith('feature1', undefined, undefined); - expect(component.text()).toBe('true|bar'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('true|bar')); // change the return value of activate const mockIFE = optimizelyMock.isFeatureEnabled as jest.Mock; @@ -234,20 +230,18 @@ describe('', () => { const updateFn = (optimizelyMock.notificationCenter.addNotificationListener as jest.Mock).mock.calls[0][1]; act(updateFn); - - component.update(); - expect(optimizelyMock.isFeatureEnabled).toHaveBeenCalledTimes(2); expect(optimizelyMock.getFeatureVariables).toHaveBeenCalledTimes(2); - - expect(component.text()).toBe('false|baz'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|baz')); }); it('should update when the user changes', async () => { - const component = mount( + const { container } = render( - {(isEnabled, variables) => `${isEnabled ? 'true' : 'false'}|${variables.foo}`} + {(isEnabled: boolean, variables: VariableValuesObject) => ( + {`${isEnabled ? 'true' : 'false'}|${variables.foo}`} + )} ); @@ -255,7 +249,7 @@ describe('', () => { expect(optimizelyMock.onReady).toHaveBeenCalledWith({ timeout: 200 }); // while it's waiting for onReady() - expect(component.text()).toBe(''); + expect(container.innerHTML).toBe(''); // Simulate client becoming ready resolver.resolve({ success: true }); @@ -263,11 +257,9 @@ describe('', () => { isReady = true; await act(async () => await optimizelyMock.onReady()); - component.update(); - expect(optimizelyMock.isFeatureEnabled).toHaveBeenCalledWith('feature1', undefined, undefined); expect(optimizelyMock.getFeatureVariables).toHaveBeenCalledWith('feature1', undefined, undefined); - expect(component.text()).toBe('true|bar'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('true|bar')); const updateFn = (optimizelyMock.onUserUpdate as jest.Mock).mock.calls[0][0]; const mockIFE = optimizelyMock.isFeatureEnabled as jest.Mock; @@ -279,21 +271,21 @@ describe('', () => { act(updateFn); - component.update(); - expect(optimizelyMock.isFeatureEnabled).toHaveBeenCalledTimes(2); expect(optimizelyMock.getFeatureVariables).toHaveBeenCalledTimes(2); - expect(component.text()).toBe('false|baz'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|baz')); }); }); describe('when the onReady() promise returns { success: false }', () => { it('should still render', async () => { - const component = mount( + const { container } = render( - {(isEnabled, variables) => `${isEnabled ? 'true' : 'false'}|${variables.foo}`} + {(isEnabled: boolean, variables: VariableValuesObject) => ( + {`${isEnabled ? 'true' : 'false'}|${variables.foo}`} + )} ); @@ -301,32 +293,31 @@ describe('', () => { expect(optimizelyMock.onReady).toHaveBeenCalledWith({ timeout: 200 }); // while it's waiting for onReady() - expect(component.text()).toBe(''); + expect(container.innerHTML).toBe(''); resolver.resolve({ success: false, reason: 'fail', dataReadyPromise: Promise.resolve() }); // Simulate config update notification firing after datafile fetched await optimizelyMock.onReady().then(res => res.dataReadyPromise); - component.update(); - expect(optimizelyMock.isFeatureEnabled).toHaveBeenCalledWith('feature1', undefined, undefined); expect(optimizelyMock.getFeatureVariables).toHaveBeenCalledWith('feature1', undefined, undefined); - expect(component.text()).toBe('true|bar'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('true|bar')); }); }); }); describe('when the isServerSide prop is true', () => { it('should immediately render the result of isFeatureEnabled and getFeatureVariables', async () => { - const component = mount( + const { container } = render( - {(isEnabled, variables) => `${isEnabled ? 'true' : 'false'}|${variables.foo}`} + {(isEnabled: boolean, variables: VariableValuesObject) => ( + {`${isEnabled ? 'true' : 'false'}|${variables.foo}`} + )} ); - - expect(component.text()).toBe('true|bar'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('true|bar')); }); }); }); From a67840a42341b71cf94b07a26cc7690589f99945 Mon Sep 17 00:00:00 2001 From: shaharyarsheikh Date: Fri, 13 May 2022 16:06:00 +0500 Subject: [PATCH 06/12] hooks file added --- src/hooks.spec.tsx | 466 +++++++++++++++++++++------------------------ 1 file changed, 217 insertions(+), 249 deletions(-) diff --git a/src/hooks.spec.tsx b/src/hooks.spec.tsx index ce226282..722769d7 100644 --- a/src/hooks.spec.tsx +++ b/src/hooks.spec.tsx @@ -13,18 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import * as Enzyme from 'enzyme'; -import Adapter from 'enzyme-adapter-react-16'; import * as React from 'react'; import { act } from 'react-dom/test-utils'; +import { render, screen, waitFor } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; import { OptimizelyProvider } from './Provider'; import { OnReadyResult, ReactSDKClient, VariableValuesObject } from './client'; import { useExperiment, useFeature, useDecision } from './hooks'; import { OptimizelyDecision } from './utils'; -Enzyme.configure({ adapter: new Adapter() }); - const defaultDecision: OptimizelyDecision = { enabled: false, variables: {}, @@ -37,18 +35,24 @@ const defaultDecision: OptimizelyDecision = { const MyFeatureComponent = ({ options = {}, overrides = {} }: any) => { const [isEnabled, variables, clientReady, didTimeout] = useFeature('feature1', { ...options }, { ...overrides }); - return <>{`${isEnabled ? 'true' : 'false'}|${JSON.stringify(variables)}|${clientReady}|${didTimeout}`}; + return ( + {`${isEnabled ? 'true' : 'false'}|${JSON.stringify( + variables + )}|${clientReady}|${didTimeout}`} + ); }; const MyExperimentComponent = ({ options = {}, overrides = {} }: any) => { const [variation, clientReady, didTimeout] = useExperiment('experiment1', { ...options }, { ...overrides }); - return <>{`${variation}|${clientReady}|${didTimeout}`}; + return {`${variation}|${clientReady}|${didTimeout}`}; }; const MyDecideComponent = ({ options = {}, overrides = {} }: any) => { const [decision, clientReady, didTimeout] = useDecision('feature1', { ...options }, { ...overrides }); return ( - <>{`${decision.enabled ? 'true' : 'false'}|${JSON.stringify(decision.variables)}|${clientReady}|${didTimeout}`} + {`${decision.enabled ? 'true' : 'false'}|${JSON.stringify( + decision.variables + )}|${clientReady}|${didTimeout}`} ); }; @@ -77,7 +81,7 @@ describe('hooks', () => { beforeEach(() => { getOnReadyPromise = ({ timeout = 0 }: any): Promise => new Promise(resolve => { - setTimeout(function () { + setTimeout(function() { resolve( Object.assign( { @@ -109,13 +113,13 @@ describe('hooks', () => { isFeatureEnabled: isFeatureEnabledMock, onUserUpdate: jest.fn().mockImplementation(handler => { userUpdateCallbacks.push(handler); - return () => { }; + return () => {}; }), notificationCenter: { addNotificationListener: jest.fn().mockImplementation((type, handler) => { notificationListenerCallbacks.push(handler); }), - removeNotificationListener: jest.fn().mockImplementation(id => { }), + removeNotificationListener: jest.fn().mockImplementation(id => {}), }, user: { id: 'testuser', @@ -126,7 +130,7 @@ describe('hooks', () => { getIsUsingSdkKey: () => true, onForcedVariationsUpdate: jest.fn().mockImplementation(handler => { forcedVariationUpdateCallbacks.push(handler); - return () => { }; + return () => {}; }), getForcedVariations: jest.fn().mockReturnValue({}), decide: decideMock, @@ -148,8 +152,8 @@ describe('hooks', () => { UseDecisionLoggingComponent = ({ options = {}, overrides = {} }: any) => { const [decision] = useDecision('feature1', { ...options }, { ...overrides }); - mockLog(decision.enabled); - return
{decision.enabled}
; + decision && mockLog(decision.enabled); + return
{decision && decision.enabled}
; }; }); @@ -163,51 +167,48 @@ describe('hooks', () => { describe('useExperiment', () => { it('should return a variation when activate returns a variation', async () => { activateMock.mockReturnValue('12345'); - const component = Enzyme.mount( + + const { container } = render( ); await optimizelyMock.onReady(); - component.update(); - expect(component.text()).toBe('12345|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('12345|true|false')); }); it('should return null when activate returns null', async () => { activateMock.mockReturnValue(null); featureVariables = {}; - const component = Enzyme.mount( + const { container } = render( ); await optimizelyMock.onReady(); - component.update(); - expect(component.text()).toBe('null|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('null|true|false')); }); it('should respect the timeout option passed', async () => { activateMock.mockReturnValue(null); readySuccess = false; - const component = Enzyme.mount( + render( ); - expect(component.text()).toBe('null|false|false'); // initial render + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('null|false|false')); await optimizelyMock.onReady(); - component.update(); - expect(component.text()).toBe('null|false|true'); // when didTimeout + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('null|false|true')); // when didTimeout // Simulate datafile fetch completing after timeout has already passed // Activate now returns a variation activateMock.mockReturnValue('12345'); // Wait for completion of dataReadyPromise await optimizelyMock.onReady().then(res => res.dataReadyPromise); - component.update(); - expect(component.text()).toBe('12345|true|true'); // when clientReady + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('12345|true|true')); // when clientReady }); it('should gracefully handle the client promise rejecting after timeout', async () => { @@ -217,20 +218,22 @@ describe('hooks', () => { new Promise((res, rej) => { setTimeout(() => rej('some error with user'), mockDelay); }); - const component = Enzyme.mount( + + render( ); - expect(component.text()).toBe('null|false|false'); // initial render + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('null|false|false')); // initial render + await new Promise(r => setTimeout(r, mockDelay * 3)); - component.update(); - expect(component.text()).toBe('null|false|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('null|false|false')); }); it('should re-render when the user attributes change using autoUpdate', async () => { activateMock.mockReturnValue(null); - const component = Enzyme.mount( + + render( @@ -239,50 +242,48 @@ describe('hooks', () => { // TODO - Wrap this with async act() once we upgrade to React 16.9 // See https://github.com/facebook/react/issues/15379 await optimizelyMock.onReady(); - component.update(); - expect(component.text()).toBe('null|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('null|true|false')); activateMock.mockReturnValue('12345'); // Simulate the user object changing - act(() => { + await act(async () => { userUpdateCallbacks.forEach(fn => fn()); }); - component.update(); - expect(component.text()).toBe('12345|true|false'); + // component.update(); + // await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('12345|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('12345|true|false')); }); it('should not re-render when the user attributes change without autoUpdate', async () => { activateMock.mockReturnValue(null); - const component = Enzyme.mount( + render( ); - // TODO - Wrap this with async act() once we upgrade to React 16.9 - // See https://github.com/facebook/react/issues/15379 + // // TODO - Wrap this with async act() once we upgrade to React 16.9 + // // See https://github.com/facebook/react/issues/15379 await optimizelyMock.onReady(); - component.update(); - expect(component.text()).toBe('null|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('null|true|false')); activateMock.mockReturnValue('12345'); // Simulate the user object changing - act(() => { + await act(async () => { userUpdateCallbacks.forEach(fn => fn()); }); - component.update(); - expect(component.text()).toBe('null|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('null|true|false')); }); it('should return the variation immediately on the first call when the client is already ready', async () => { readySuccess = true; activateMock.mockReturnValue('12345'); - const component = Enzyme.mount( + + render( ); - component.update(); expect(mockLog).toHaveBeenCalledTimes(1); expect(mockLog).toHaveBeenCalledWith('12345'); }); @@ -299,13 +300,11 @@ describe('hooks', () => { getOnReadyPromise = (): Promise => readyPromise; activateMock.mockReturnValue(null); - const component = Enzyme.mount( + render( ); - component.update(); - expect(mockLog).toHaveBeenCalledTimes(1); expect(mockLog).toHaveBeenCalledWith(null); @@ -318,131 +317,130 @@ describe('hooks', () => { const dataReadyPromise = Promise.resolve(); resolveReadyPromise!({ success: true, dataReadyPromise }); await dataReadyPromise; - component.update(); - - expect(mockLog).toHaveBeenCalledTimes(1); - expect(mockLog).toHaveBeenCalledWith('12345'); + await waitFor(() => expect(mockLog).toHaveBeenCalledTimes(1)); + await waitFor(() => expect(mockLog).toHaveBeenCalledWith('12345')); }); it('should re-render after updating the override user ID argument', async () => { activateMock.mockReturnValue(null); - const component = Enzyme.mount( + + const { rerender } = render( ); - component.update(); - expect(component.text()).toBe('null|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('null|true|false')); activateMock.mockReturnValue('12345'); - component.setProps({ - children: , - }); - component.update(); - expect(component.text()).toBe('12345|true|false'); + + rerender( + + + + ); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('12345|true|false')); }); it('should re-render after updating the override user attributes argument', async () => { activateMock.mockReturnValue(null); - const component = Enzyme.mount( + + const { rerender } = render( ); - component.update(); - expect(component.text()).toBe('null|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('null|true|false')); activateMock.mockReturnValue('12345'); - component.setProps({ - children: ( + + rerender( + - ), - }); - component.update(); - expect(component.text()).toBe('12345|true|false'); + + ); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('12345|true|false')); activateMock.mockReturnValue('67890'); - component.setProps({ - children: ( + + rerender( + - ), - }); - component.update(); - expect(component.text()).toBe('67890|true|false'); + + ); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('67890|true|false')); }); it('should not recompute the decision when passed the same override attributes', async () => { activateMock.mockReturnValue(null); - const component = Enzyme.mount( + + const { rerender } = render( + />{' '} ); expect(activateMock).toHaveBeenCalledTimes(1); activateMock.mockReset(); - component.setProps({ - children: ( + + rerender( + - ), - }); - component.update(); + />{' '} + + ); expect(activateMock).not.toHaveBeenCalled(); }); it('should re-render after setForcedVariation is called on the client', async () => { activateMock.mockReturnValue(null); - const component = Enzyme.mount( + + render( - + {' '} ); - - component.update(); - expect(component.text()).toBe('null|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('null|true|false')); activateMock.mockReturnValue('12345'); forcedVariationUpdateCallbacks[0](); - component.update(); - expect(component.text()).toBe('12345|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('12345|true|false')); }); }); describe('useFeature', () => { it('should render true when the feature is enabled', async () => { isFeatureEnabledMock.mockReturnValue(true); - const component = Enzyme.mount( + + render( ); await optimizelyMock.onReady(); - component.update(); - expect(component.text()).toBe('true|{"foo":"bar"}|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('true|{"foo":"bar"}|true|false')); }); it('should render false when the feature is disabled', async () => { isFeatureEnabledMock.mockReturnValue(false); featureVariables = {}; - const component = Enzyme.mount( + + render( ); await optimizelyMock.onReady(); - component.update(); - expect(component.text()).toBe('false|{}|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{}|true|false')); }); it('should respect the timeout option passed', async () => { @@ -450,16 +448,16 @@ describe('hooks', () => { featureVariables = {}; readySuccess = false; - const component = Enzyme.mount( + render( ); - expect(component.text()).toBe('false|{}|false|false'); // initial render + + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{}|false|false')); // initial render await optimizelyMock.onReady(); - component.update(); - expect(component.text()).toBe('false|{}|false|true'); // when didTimeout + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{}|false|true')); // when didTimeout // Simulate datafile fetch completing after timeout has already passed // isFeatureEnabled now returns true, getFeatureVariables returns variable values @@ -467,16 +465,13 @@ describe('hooks', () => { featureVariables = mockFeatureVariables; // Wait for completion of dataReadyPromise await optimizelyMock.onReady().then(res => res.dataReadyPromise); - component.update(); // Simulate datafile fetch completing after timeout has already passed // Activate now returns a variation activateMock.mockReturnValue('12345'); // Wait for completion of dataReadyPromise await optimizelyMock.onReady().then(res => res.dataReadyPromise); - component.update(); - - expect(component.text()).toBe('true|{"foo":"bar"}|true|true'); // when clientReady + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('true|{"foo":"bar"}|true|true')); // when clientReady }); it('should gracefully handle the client promise rejecting after timeout', async () => { @@ -486,21 +481,23 @@ describe('hooks', () => { new Promise((res, rej) => { setTimeout(() => rej('some error with user'), mockDelay); }); - const component = Enzyme.mount( + + render( ); - expect(component.text()).toBe('false|{}|false|false'); // initial render + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{}|false|false')); // initial render + await new Promise(r => setTimeout(r, mockDelay * 3)); - component.update(); - expect(component.text()).toBe('false|{}|false|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{}|false|false')); }); it('should re-render when the user attributes change using autoUpdate', async () => { isFeatureEnabledMock.mockReturnValue(false); featureVariables = {}; - const component = Enzyme.mount( + + render( @@ -509,23 +506,22 @@ describe('hooks', () => { // TODO - Wrap this with async act() once we upgrade to React 16.9 // See https://github.com/facebook/react/issues/15379 await optimizelyMock.onReady(); - component.update(); - expect(component.text()).toBe('false|{}|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{}|true|false')); isFeatureEnabledMock.mockReturnValue(true); featureVariables = mockFeatureVariables; // Simulate the user object changing - act(() => { + await act(async () => { userUpdateCallbacks.forEach(fn => fn()); }); - component.update(); - expect(component.text()).toBe('true|{"foo":"bar"}|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('true|{"foo":"bar"}|true|false')); }); it('should not re-render when the user attributes change without autoUpdate', async () => { isFeatureEnabledMock.mockReturnValue(false); featureVariables = {}; - const component = Enzyme.mount( + + render( @@ -534,8 +530,7 @@ describe('hooks', () => { // TODO - Wrap this with async act() once we upgrade to React 16.9 // See https://github.com/facebook/react/issues/15379 await optimizelyMock.onReady(); - component.update(); - expect(component.text()).toBe('false|{}|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{}|true|false')); isFeatureEnabledMock.mockReturnValue(true); featureVariables = mockFeatureVariables; @@ -543,19 +538,18 @@ describe('hooks', () => { act(() => { userUpdateCallbacks.forEach(fn => fn()); }); - component.update(); - expect(component.text()).toBe('false|{}|true|false'); + // component.update(); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{}|true|false')); }); it('should return the variation immediately on the first call when the client is already ready', async () => { readySuccess = true; isFeatureEnabledMock.mockReturnValue(false); - const component = Enzyme.mount( + render( ); - component.update(); expect(mockLog).toHaveBeenCalledTimes(1); expect(mockLog).toHaveBeenCalledWith(false); }); @@ -572,12 +566,11 @@ describe('hooks', () => { getOnReadyPromise = (): Promise => readyPromise; isFeatureEnabledMock.mockReturnValue(false); - const component = Enzyme.mount( + render( ); - component.update(); expect(mockLog).toHaveBeenCalledTimes(1); expect(mockLog).toHaveBeenCalledWith(false); @@ -591,68 +584,66 @@ describe('hooks', () => { const dataReadyPromise = Promise.resolve(); resolveReadyPromise!({ success: true, dataReadyPromise }); await dataReadyPromise; - component.update(); - - expect(mockLog).toHaveBeenCalledTimes(1); - expect(mockLog).toHaveBeenCalledWith(true); + await waitFor(() => expect(mockLog).toHaveBeenCalledTimes(1)); + await waitFor(() => expect(mockLog).toHaveBeenCalledWith(true)); }); it('should re-render after updating the override user ID argument', async () => { isFeatureEnabledMock.mockReturnValue(false); - const component = Enzyme.mount( + const { rerender } = render( ); - component.update(); - expect(component.text()).toBe('false|{"foo":"bar"}|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{"foo":"bar"}|true|false')); isFeatureEnabledMock.mockReturnValue(true); - component.setProps({ - children: , - }); - component.update(); - expect(component.text()).toBe('true|{"foo":"bar"}|true|false'); + + rerender( + + + + ); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('true|{"foo":"bar"}|true|false')); }); it('should re-render after updating the override user attributes argument', async () => { isFeatureEnabledMock.mockReturnValue(false); - const component = Enzyme.mount( + const { rerender } = render( ); - component.update(); - expect(component.text()).toBe('false|{"foo":"bar"}|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{"foo":"bar"}|true|false')); isFeatureEnabledMock.mockReturnValue(true); - component.setProps({ - children: ( + + rerender( + - ), - }); - component.update(); - expect(component.text()).toBe('true|{"foo":"bar"}|true|false'); + + ); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('true|{"foo":"bar"}|true|false')); isFeatureEnabledMock.mockReturnValue(false); featureVariables = { myvar: 3 }; - component.setProps({ - children: ( + + rerender( + - ), - }); - component.update(); - expect(component.text()).toBe('false|{"myvar":3}|true|false'); + />{' '} + + ); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{"myvar":3}|true|false')); }); it('should not recompute the decision when passed the same override attributes', async () => { isFeatureEnabledMock.mockReturnValue(false); - const component = Enzyme.mount( + const { rerender } = render( { ); expect(isFeatureEnabledMock).toHaveBeenCalledTimes(1); isFeatureEnabledMock.mockReset(); - component.setProps({ - children: ( + + rerender( + - ), - }); - component.update(); + + ); expect(isFeatureEnabledMock).not.toHaveBeenCalled(); }); }); @@ -682,14 +673,14 @@ describe('hooks', () => { enabled: true, variables: { foo: 'bar' }, }); - const component = Enzyme.mount( + render( ); await optimizelyMock.onReady(); - component.update(); - expect(component.text()).toBe('true|{"foo":"bar"}|true|false'); + // component.update(); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('true|{"foo":"bar"}|true|false')); }); it('should render false when the flag is disabled', async () => { @@ -698,30 +689,29 @@ describe('hooks', () => { enabled: false, variables: { foo: 'bar' }, }); - const component = Enzyme.mount( + render( ); await optimizelyMock.onReady(); - component.update(); - expect(component.text()).toBe('false|{"foo":"bar"}|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{"foo":"bar"}|true|false')); }); it('should respect the timeout option passed', async () => { decideMock.mockReturnValue({ ...defaultDecision }); readySuccess = false; - const component = Enzyme.mount( + render( ); - expect(component.text()).toBe('false|{}|false|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{}|false|false')); await optimizelyMock.onReady(); - component.update(); - expect(component.text()).toBe('false|{}|false|true'); + // component.update(); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{}|false|true')); // Simulate datafile fetch completing after timeout has already passed // flag is now true and decision contains variables @@ -732,14 +722,12 @@ describe('hooks', () => { }); await optimizelyMock.onReady().then(res => res.dataReadyPromise); - component.update(); // Simulate datafile fetch completing after timeout has already passed // Wait for completion of dataReadyPromise await optimizelyMock.onReady().then(res => res.dataReadyPromise); - component.update(); - expect(component.text()).toBe('true|{"foo":"bar"}|true|true'); // when clientReady + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('true|{"foo":"bar"}|true|true')); // when clientReady }); it('should gracefully handle the client promise rejecting after timeout', async () => { @@ -749,20 +737,19 @@ describe('hooks', () => { new Promise((res, rej) => { setTimeout(() => rej('some error with user'), mockDelay); }); - const component = Enzyme.mount( + render( ); - expect(component.text()).toBe('false|{}|false|false'); // initial render + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{}|false|false')); // initial render await new Promise(r => setTimeout(r, mockDelay * 3)); - component.update(); - expect(component.text()).toBe('false|{}|false|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{}|false|false')); }); it('should re-render when the user attributes change using autoUpdate', async () => { decideMock.mockReturnValue({ ...defaultDecision }); - const component = Enzyme.mount( + render( @@ -771,8 +758,7 @@ describe('hooks', () => { // TODO - Wrap this with async act() once we upgrade to React 16.9 // See https://github.com/facebook/react/issues/15379 await optimizelyMock.onReady(); - component.update(); - expect(component.text()).toBe('false|{}|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{}|true|false')); decideMock.mockReturnValue({ ...defaultDecision, @@ -780,16 +766,15 @@ describe('hooks', () => { variables: { foo: 'bar' }, }); // Simulate the user object changing - act(() => { + await act(async () => { userUpdateCallbacks.forEach(fn => fn()); }); - component.update(); - expect(component.text()).toBe('true|{"foo":"bar"}|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('true|{"foo":"bar"}|true|false')); }); it('should not re-render when the user attributes change without autoUpdate', async () => { decideMock.mockReturnValue({ ...defaultDecision }); - const component = Enzyme.mount( + render( @@ -798,8 +783,7 @@ describe('hooks', () => { // TODO - Wrap this with async act() once we upgrade to React 16.9 // See https://github.com/facebook/react/issues/15379 await optimizelyMock.onReady(); - component.update(); - expect(component.text()).toBe('false|{}|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{}|true|false')); decideMock.mockReturnValue({ ...defaultDecision, @@ -807,22 +791,21 @@ describe('hooks', () => { variables: { foo: 'bar' }, }); // Simulate the user object changing - act(() => { + await act(async () => { userUpdateCallbacks.forEach(fn => fn()); }); - component.update(); - expect(component.text()).toBe('false|{}|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{}|true|false')); }); it('should return the decision immediately on the first call when the client is already ready', async () => { readySuccess = true; decideMock.mockReturnValue({ ...defaultDecision }); - const component = Enzyme.mount( + render( ); - component.update(); + // component.update(); expect(mockLog).toHaveBeenCalledTimes(1); expect(mockLog).toHaveBeenCalledWith(false); }); @@ -839,13 +822,11 @@ describe('hooks', () => { getOnReadyPromise = (): Promise => readyPromise; decideMock.mockReturnValue({ ...defaultDecision }); - const component = Enzyme.mount( + render( ); - component.update(); - expect(mockLog).toHaveBeenCalledTimes(1); expect(mockLog).toHaveBeenCalledWith(false); @@ -858,67 +839,63 @@ describe('hooks', () => { const dataReadyPromise = Promise.resolve(); resolveReadyPromise!({ success: true, dataReadyPromise }); await dataReadyPromise; - component.update(); - - expect(mockLog).toHaveBeenCalledTimes(1); - expect(mockLog).toHaveBeenCalledWith(true); + await waitFor(() => expect(mockLog).toHaveBeenCalledTimes(1)); + await waitFor(() => expect(mockLog).toHaveBeenCalledWith(true)); }); it('should re-render after updating the override user ID argument', async () => { decideMock.mockReturnValue({ ...defaultDecision }); - const component = Enzyme.mount( + const { rerender } = render( ); - component.update(); - expect(component.text()).toBe('false|{}|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{}|true|false')); decideMock.mockReturnValue({ ...defaultDecision, enabled: true }); - component.setProps({ - children: , - }); - component.update(); - expect(component.text()).toBe('true|{}|true|false'); + rerender( + + + + ); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('true|{}|true|false')); }); it('should re-render after updating the override user attributes argument', async () => { decideMock.mockReturnValue({ ...defaultDecision }); - const component = Enzyme.mount( + const { rerender } = render( ); - component.update(); - expect(component.text()).toBe('false|{}|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{}|true|false')); decideMock.mockReturnValue({ ...defaultDecision, enabled: true }); - component.setProps({ - children: ( + rerender( + - ), - }); - component.update(); - expect(component.text()).toBe('true|{}|true|false'); + + ); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('true|{}|true|false')); decideMock.mockReturnValue({ ...defaultDecision, enabled: false, variables: { myvar: 3 } }); - component.setProps({ - children: ( + rerender( + - ), - }); - component.update(); - expect(component.text()).toBe('false|{"myvar":3}|true|false'); + + ); + + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{"myvar":3}|true|false')); }); it('should not recompute the decision when passed the same override attributes', async () => { decideMock.mockReturnValue({ ...defaultDecision }); - const component = Enzyme.mount( + const { rerender } = render( { ); expect(decideMock).toHaveBeenCalledTimes(1); decideMock.mockReset(); - component.setProps({ - children: ( + + rerender( + - ), - }); - component.update(); + + ); + expect(decideMock).not.toHaveBeenCalled(); }); it('should not recompute the decision when autoupdate is not passed and setting setForcedDecision', async () => { decideMock.mockReturnValue({ ...defaultDecision, flagKey: 'exp1' }); - const component = Enzyme.mount( + render( ); - component.update(); - expect(component.text()).toBe('false|{}|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{}|true|false')); optimizelyMock.setForcedDecision( { flagKey: 'exp1', @@ -958,20 +935,18 @@ describe('hooks', () => { { variationKey: 'var2' } ); - component.update(); - expect(component.text()).toBe('false|{}|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{}|true|false')); }); it('should not recompute the decision when autoupdate is false and setting setForcedDecision', async () => { decideMock.mockReturnValue({ ...defaultDecision, flagKey: 'exp1' }); - const component = Enzyme.mount( + render( ); - component.update(); - expect(component.text()).toBe('false|{}|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{}|true|false')); optimizelyMock.setForcedDecision( { flagKey: 'exp1', @@ -980,20 +955,18 @@ describe('hooks', () => { { variationKey: 'var2' } ); - component.update(); - expect(component.text()).toBe('false|{}|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{}|true|false')); }); it('should recompute the decision when autoupdate is true and setting setForcedDecision', async () => { decideMock.mockReturnValue({ ...defaultDecision, flagKey: 'exp1' }); - const component = Enzyme.mount( + render( ); - component.update(); - expect(component.text()).toBe('false|{}|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{}|true|false')); optimizelyMock.setForcedDecision( { flagKey: 'exp1', @@ -1004,13 +977,12 @@ describe('hooks', () => { decideMock.mockReturnValue({ ...defaultDecision, variables: { foo: 'bar' } }); await optimizelyMock.onReady(); - component.update(); - expect(component.text()).toBe('false|{"foo":"bar"}|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{"foo":"bar"}|true|false')); }); it('should not recompute the decision if autoupdate is true but overrideUserId is passed and setting setForcedDecision', async () => { decideMock.mockReturnValue({ ...defaultDecision, flagKey: 'exp1' }); - const component = Enzyme.mount( + render( { ); - component.update(); - expect(component.text()).toBe('false|{}|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{}|true|false')); optimizelyMock.setForcedDecision( { flagKey: 'exp1', @@ -1032,13 +1003,12 @@ describe('hooks', () => { ); await optimizelyMock.onReady(); - component.update(); - expect(component.text()).toBe('false|{}|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{}|true|false')); }); it('should not recompute the decision if autoupdate is true but overrideAttributes are passed and setting setForcedDecision', async () => { decideMock.mockReturnValue({ ...defaultDecision, flagKey: 'exp1' }); - const component = Enzyme.mount( + render( { ); - component.update(); - expect(component.text()).toBe('false|{}|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{}|true|false')); optimizelyMock.setForcedDecision( { flagKey: 'exp1', @@ -1062,8 +1031,7 @@ describe('hooks', () => { ); await optimizelyMock.onReady(); - component.update(); - expect(component.text()).toBe('false|{}|true|false'); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('false|{}|true|false')); }); }); }); From 1db77f198fa96e07cf7f65fa7603cf932314603b Mon Sep 17 00:00:00 2001 From: shaharyarsheikh Date: Mon, 16 May 2022 15:23:43 +0500 Subject: [PATCH 07/12] withOptimizely spec updated --- src/withOptimizely.spec.tsx | 58 ++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/src/withOptimizely.spec.tsx b/src/withOptimizely.spec.tsx index 75713ef2..3fa83939 100644 --- a/src/withOptimizely.spec.tsx +++ b/src/withOptimizely.spec.tsx @@ -16,11 +16,9 @@ /// import * as React from 'react'; -import * as Enzyme from 'enzyme'; -import Adapter from 'enzyme-adapter-react-16'; -Enzyme.configure({ adapter: new Adapter() }); +import { render, screen, waitFor } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; -import { mount } from 'enzyme'; import { OptimizelyProvider } from './Provider'; import { withOptimizely } from './withOptimizely'; import { ReactSDKClient } from './client'; @@ -37,7 +35,12 @@ class InnerComponent extends React.Component { } render() { - return
test
; + return ( +
+ {JSON.stringify({ ...this.props })} + test +
+ ); } } @@ -57,7 +60,7 @@ describe('withOptimizely', () => { foo: 'bar', }; const userId = 'jordan'; - const component = mount( + render( @@ -71,7 +74,7 @@ describe('withOptimizely', () => { describe('when only userId prop is provided', () => { it('should call setUser with the correct user id / attributes', () => { const userId = 'jordan'; - const component = mount( + render( @@ -88,7 +91,7 @@ describe('withOptimizely', () => { describe(`when the user prop is passed only with "id"`, () => { it('should call setUser with the correct user id / attributes', () => { const userId = 'jordan'; - const component = mount( + render( @@ -106,7 +109,7 @@ describe('withOptimizely', () => { it('should call setUser with the correct user id / attributes', () => { const userId = 'jordan'; const attributes = { foo: 'bar' }; - const component = mount( + render( @@ -124,7 +127,7 @@ describe('withOptimizely', () => { it('should respect the user object prop', () => { const userId = 'jordan'; const attributes = { foo: 'bar' }; - const component = mount( + render( { }); it('should inject optimizely and optimizelyReadyTimeout from ', async () => { - const component = mount( + render( ); - const innerComponent = component.find(InnerComponent); - expect(innerComponent.props()).toEqual({ - optimizely: optimizelyClient, - isServerSide: false, - optimizelyReadyTimeout: 200, - }); + await waitFor(() => + expect(screen.getByTestId('props-of-component')).toHaveTextContent( + '{"optimizelyReadyTimeout":200,"optimizely":{},"isServerSide":false}' + ) + ); expect(optimizelyClient.setUser).not.toHaveBeenCalled(); }); it('should inject the isServerSide prop', async () => { - const component = mount( + render( ); - - const innerComponent = component.find(InnerComponent); - expect(innerComponent.props()).toEqual({ - optimizely: optimizelyClient, - isServerSide: true, - optimizelyReadyTimeout: 200, - }); + await waitFor(() => + expect(screen.getByTestId('props-of-component')).toHaveTextContent( + '{"optimizelyReadyTimeout":200,"optimizely":{},"isServerSide":true}' + ) + ); }); it('should forward refs', () => { interface FancyInputProps extends TestProps { defaultValue: string; } - const FancyInput: React.RefForwardingComponent = (props, ref) => ( - + const FancyInput: React.ForwardRefRenderFunction = (props, ref) => ( + ); const ForwardingFancyInput = React.forwardRef(FancyInput); const OptimizelyInput = withOptimizely(ForwardingFancyInput); @@ -191,7 +191,7 @@ describe('withOptimizely', () => { setUser: jest.fn(), } as unknown) as ReactSDKClient; - const component = mount( + render( { ); expect(inputRef.current).toBeInstanceOf(HTMLInputElement); expect(typeof inputRef.current!.focus).toBe('function'); - const inputNode: HTMLInputElement = component.find('input').getDOMNode(); + const inputNode: HTMLInputElement = screen.getByTestId('input-element'); expect(inputRef.current!).toBe(inputNode); }); From 8501e85844ed84164a70cdce8326c9716aee6f6e Mon Sep 17 00:00:00 2001 From: shaharyarsheikh Date: Tue, 17 May 2022 12:53:07 +0500 Subject: [PATCH 08/12] removed enzyme from package.json --- package.json | 4 - yarn.lock | 434 ++------------------------------------------------- 2 files changed, 11 insertions(+), 427 deletions(-) diff --git a/package.json b/package.json index a376db1f..7c04e108 100644 --- a/package.json +++ b/package.json @@ -43,8 +43,6 @@ "@rollup/plugin-replace": "^2.3.4", "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.1.1", - "@types/enzyme": "^3.1.15", - "@types/enzyme-adapter-react-16": "^1.0.3", "@types/hoist-non-react-statics": "^3.3.1", "@types/jest": "^26.0.14", "@types/prop-types": "^15.5.6", @@ -52,8 +50,6 @@ "@types/react-dom": "^18.0.2", "@typescript-eslint/eslint-plugin": "^2.23.0", "@typescript-eslint/parser": "^2.23.0", - "enzyme": "^3.8.0", - "enzyme-adapter-react-16": "^1.7.1", "eslint": "^6.8.0", "eslint-config-prettier": "^6.10.0", "eslint-plugin-prettier": "^3.1.2", diff --git a/yarn.lock b/yarn.lock index dd26e3b4..d1469d9c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -650,28 +650,6 @@ dependencies: "@babel/types" "^7.3.0" -"@types/cheerio@*": - version "0.22.22" - resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.22.tgz#ae71cf4ca59b8bbaf34c99af7a5d6c8894988f5f" - integrity sha512-05DYX4zU96IBfZFY+t3Mh88nlwSMtmmzSYaQkKN48T495VV1dkHSah6qYyDTN5ngaS0i0VonH37m+RuzSM0YiA== - dependencies: - "@types/node" "*" - -"@types/enzyme-adapter-react-16@^1.0.3": - version "1.0.6" - resolved "https://registry.yarnpkg.com/@types/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.0.6.tgz#8aca7ae2fd6c7137d869b6616e696d21bb8b0cec" - integrity sha512-VonDkZ15jzqDWL8mPFIQnnLtjwebuL9YnDkqeCDYnB4IVgwUm0mwKkqhrxLL6mb05xm7qqa3IE95m8CZE9imCg== - dependencies: - "@types/enzyme" "*" - -"@types/enzyme@*", "@types/enzyme@^3.1.15": - version "3.10.7" - resolved "https://registry.yarnpkg.com/@types/enzyme/-/enzyme-3.10.7.tgz#ebdf3b972d293095e09af479e36c772025285e3a" - integrity sha512-J+0wduPGAkzOvW7sr6hshGv1gBI3WXLRTczkRKzVPxLP3xAkYxZmvvagSBPw8Z452fZ8TGUxCmAXcb44yLQksw== - dependencies: - "@types/cheerio" "*" - "@types/react" "*" - "@types/eslint-visitor-keys@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" @@ -901,21 +879,6 @@ acorn@^7.1.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -airbnb-prop-types@^2.16.0: - version "2.16.0" - resolved "https://registry.yarnpkg.com/airbnb-prop-types/-/airbnb-prop-types-2.16.0.tgz#b96274cefa1abb14f623f804173ee97c13971dc2" - integrity sha512-7WHOFolP/6cS96PhKNrslCLMYAI8yB1Pp6u6XmxozQOiZbsI5ycglZr5cHhBFfuRcQQjzCMith5ZPZdYiJCxUg== - dependencies: - array.prototype.find "^2.1.1" - function.prototype.name "^1.1.2" - is-regex "^1.1.0" - object-is "^1.1.2" - object.assign "^4.1.0" - object.entries "^1.1.2" - prop-types "^15.7.2" - prop-types-exact "^1.2.0" - react-is "^16.13.1" - ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3: version "6.12.5" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.5.tgz#19b0e8bae8f476e5ba666300387775fb1a00a4da" @@ -1010,11 +973,6 @@ arr-union@^3.1.0: resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= -array-filter@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83" - integrity sha1-uveeYubvTCpMC4MSMtr/7CUfnYM= - array-includes@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348" @@ -1029,22 +987,6 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= -array.prototype.find@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.1.1.tgz#3baca26108ca7affb08db06bf0be6cb3115a969c" - integrity sha512-mi+MYNJYLTx2eNYy+Yh6raoQacCsNeeMUaspFPh9Y141lFSsWxxB8V9mM2ye+eqiRs917J6/pJ4M9ZPzenWckA== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.4" - -array.prototype.flat@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz#0de82b426b0318dbfdb940089e38b043d37f6c7b" - integrity sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - array.prototype.flatmap@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.3.tgz#1c13f84a178566042dd63de4414440db9222e443" @@ -1181,11 +1123,6 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -boolbase@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" - integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -1323,18 +1260,6 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== -cheerio@^1.0.0-rc.3: - version "1.0.0-rc.3" - resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.3.tgz#094636d425b2e9c0f4eb91a46c05630c9a1a8bf6" - integrity sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA== - dependencies: - css-select "~1.2.0" - dom-serializer "~0.1.1" - entities "~1.1.1" - htmlparser2 "^3.9.1" - lodash "^4.15.0" - parse5 "^3.0.1" - ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" @@ -1420,11 +1345,6 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -commander@^2.19.0: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" @@ -1477,21 +1397,6 @@ cross-spawn@^7.0.0: shebang-command "^2.0.0" which "^2.0.1" -css-select@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" - integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= - dependencies: - boolbase "~1.0.0" - css-what "2.1" - domutils "1.5.1" - nth-check "~1.0.1" - -css-what@2.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" - integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== - css.escape@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" @@ -1644,11 +1549,6 @@ diff-sequences@^27.5.1: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== -discontinuous-range@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a" - integrity sha1-44Mx8IRLukm5qctxx3FYWqsbxlo= - doctrine@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" @@ -1668,32 +1568,6 @@ dom-accessibility-api@^0.5.6, dom-accessibility-api@^0.5.9: resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz#56082f71b1dc7aac69d83c4285eef39c15d93f56" integrity sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg== -dom-serializer@0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" - integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== - dependencies: - domelementtype "^2.0.1" - entities "^2.0.0" - -dom-serializer@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0" - integrity sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA== - dependencies: - domelementtype "^1.3.0" - entities "^1.1.1" - -domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" - integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== - -domelementtype@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.2.tgz#f3b6e549201e46f588b59463dd77187131fe6971" - integrity sha512-wFwTwCVebUrMgGeAwRL/NhZtHAUyT9n9yg4IMDwf10+6iCMxSkVq9MGCVEH+QZWo1nNidy8kNvwmv4zWHDTqvA== - domexception@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" @@ -1701,29 +1575,6 @@ domexception@^2.0.1: dependencies: webidl-conversions "^5.0.0" -domhandler@^2.3.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" - integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== - dependencies: - domelementtype "1" - -domutils@1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" - integrity sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8= - dependencies: - dom-serializer "0" - domelementtype "1" - -domutils@^1.5.1: - version "1.7.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" - integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== - dependencies: - dom-serializer "0" - domelementtype "1" - ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" @@ -1754,79 +1605,6 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" -entities@^1.1.1, entities@~1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" - integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== - -entities@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f" - integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ== - -enzyme-adapter-react-16@^1.7.1: - version "1.15.5" - resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.5.tgz#7a6f0093d3edd2f7025b36e7fbf290695473ee04" - integrity sha512-33yUJGT1nHFQlbVI5qdo5Pfqvu/h4qPwi1o0a6ZZsjpiqq92a3HjynDhwd1IeED+Su60HDWV8mxJqkTnLYdGkw== - dependencies: - enzyme-adapter-utils "^1.13.1" - enzyme-shallow-equal "^1.0.4" - has "^1.0.3" - object.assign "^4.1.0" - object.values "^1.1.1" - prop-types "^15.7.2" - react-is "^16.13.1" - react-test-renderer "^16.0.0-0" - semver "^5.7.0" - -enzyme-adapter-utils@^1.13.1: - version "1.13.1" - resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.13.1.tgz#59c1b734b0927543e3d8dc477299ec957feb312d" - integrity sha512-5A9MXXgmh/Tkvee3bL/9RCAAgleHqFnsurTYCbymecO4ohvtNO5zqIhHxV370t7nJAwaCfkgtffarKpC0GPt0g== - dependencies: - airbnb-prop-types "^2.16.0" - function.prototype.name "^1.1.2" - object.assign "^4.1.0" - object.fromentries "^2.0.2" - prop-types "^15.7.2" - semver "^5.7.1" - -enzyme-shallow-equal@^1.0.1, enzyme-shallow-equal@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.4.tgz#b9256cb25a5f430f9bfe073a84808c1d74fced2e" - integrity sha512-MttIwB8kKxypwHvRynuC3ahyNc+cFbR8mjVIltnmzQ0uKGqmsfO4bfBuLxb0beLNPhjblUEYvEbsg+VSygvF1Q== - dependencies: - has "^1.0.3" - object-is "^1.1.2" - -enzyme@^3.8.0: - version "3.11.0" - resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.11.0.tgz#71d680c580fe9349f6f5ac6c775bc3e6b7a79c28" - integrity sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw== - dependencies: - array.prototype.flat "^1.2.3" - cheerio "^1.0.0-rc.3" - enzyme-shallow-equal "^1.0.1" - function.prototype.name "^1.1.2" - has "^1.0.3" - html-element-map "^1.2.0" - is-boolean-object "^1.0.1" - is-callable "^1.1.5" - is-number-object "^1.0.4" - is-regex "^1.0.5" - is-string "^1.0.5" - is-subset "^0.1.1" - lodash.escape "^4.0.1" - lodash.isequal "^4.5.0" - object-inspect "^1.7.0" - object-is "^1.0.2" - object.assign "^4.1.0" - object.entries "^1.1.1" - object.values "^1.1.1" - raf "^3.4.1" - rst-selector-parser "^2.2.3" - string.prototype.trim "^1.2.1" - error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -1834,7 +1612,7 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.4, es-abstract@^1.17.5: +es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.5: version "1.17.7" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c" integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g== @@ -1851,7 +1629,7 @@ es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.4, es-abstrac string.prototype.trimend "^1.0.1" string.prototype.trimstart "^1.0.1" -es-abstract@^1.18.0-next.0, es-abstract@^1.18.0-next.1: +es-abstract@^1.18.0-next.0: version "1.18.0-next.1" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68" integrity sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA== @@ -2314,25 +2092,11 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -function.prototype.name@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.2.tgz#5cdf79d7c05db401591dfde83e3b70c5123e9a45" - integrity sha512-C8A+LlHBJjB2AdcRPorc5JvJ5VUoWlXdEHLOJdCI7kjHEtGTpHQUiqMvCIKUwIsGwZX2jZJy761AXsn356bJQg== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - functions-have-names "^1.2.0" - functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= -functions-have-names@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.1.tgz#a981ac397fa0c9964551402cdc5533d7a4d52f91" - integrity sha512-j48B/ZI7VKs3sgeI2cZp7WXWmZXu7Iq5pl5/vptV5N2mq+DGFuS/ulaDjtaoLpYzuD6u8UgrUKHfgo7fDTSiBA== - gensync@^1.0.0-beta.1: version "1.0.0-beta.1" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" @@ -2498,13 +2262,6 @@ hosted-git-info@^2.1.4: resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== -html-element-map@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/html-element-map/-/html-element-map-1.2.0.tgz#dfbb09efe882806af63d990cf6db37993f099f22" - integrity sha512-0uXq8HsuG1v2TmQ8QkIhzbrqeskE4kn52Q18QJ9iAA/SnHoEKXWiUxHQtclRsCFWEUD2So34X+0+pZZu862nnw== - dependencies: - array-filter "^1.0.0" - html-encoding-sniffer@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" @@ -2517,18 +2274,6 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== -htmlparser2@^3.9.1: - version "3.10.1" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" - integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== - dependencies: - domelementtype "^1.3.1" - domhandler "^2.3.0" - domutils "^1.5.1" - entities "^1.1.1" - inherits "^2.0.1" - readable-stream "^3.1.1" - http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -2589,7 +2334,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4: +inherits@2, inherits@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -2646,17 +2391,12 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= -is-boolean-object@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.1.tgz#10edc0900dd127697a92f6f9807c7617d68ac48e" - integrity sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ== - is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-callable@^1.1.4, is-callable@^1.1.5, is-callable@^1.2.2: +is-callable@^1.1.4, is-callable@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== @@ -2759,11 +2499,6 @@ is-negative-zero@^2.0.0: resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461" integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE= -is-number-object@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" - integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw== - is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" @@ -2795,7 +2530,7 @@ is-reference@^1.2.1: dependencies: "@types/estree" "*" -is-regex@^1.0.5, is-regex@^1.1.0, is-regex@^1.1.1: +is-regex@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== @@ -2817,11 +2552,6 @@ is-string@^1.0.5: resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== -is-subset@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" - integrity sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY= - is-symbol@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" @@ -3504,21 +3234,6 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" -lodash.escape@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98" - integrity sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg= - -lodash.flattendeep@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" - integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= - -lodash.isequal@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" - integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= - lodash.memoize@4.x: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -3529,7 +3244,7 @@ lodash.sortby@^4.7.0: resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= -lodash@^4.15.0, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19: +lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== @@ -3675,11 +3390,6 @@ mkdirp@^0.5.1: dependencies: minimist "^1.2.5" -moo@^0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/moo/-/moo-0.5.1.tgz#7aae7f384b9b09f620b6abf6f74ebbcd1b65dbc4" - integrity sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w== - ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -3722,17 +3432,6 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -nearley@^2.7.10: - version "2.19.7" - resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.19.7.tgz#eafbe3e2d8ccfe70adaa5c026ab1f9709c116218" - integrity sha512-Y+KNwhBPcSJKeyQCFjn8B/MIe+DDlhaaDgjVldhy5xtFewIbiQgcbZV8k2gCVwkI1ZsKCnjIYZbR+0Fim5QYgg== - dependencies: - commander "^2.19.0" - moo "^0.5.0" - railroad-diagrams "^1.0.0" - randexp "0.4.6" - semver "^5.4.1" - nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -3796,13 +3495,6 @@ npm-run-path@^4.0.0: dependencies: path-key "^3.0.0" -nth-check@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" - integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== - dependencies: - boolbase "~1.0.0" - nwsapi@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" @@ -3827,19 +3519,11 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-inspect@^1.7.0, object-inspect@^1.8.0: +object-inspect@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== -object-is@^1.0.2, object-is@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.3.tgz#2e3b9e65560137455ee3bd62aec4d90a2ea1cc81" - integrity sha512-teyqLvFWzLkq5B9ki8FVWA902UER2qkxmdA4nLf+wjOLAWgxzCWZNCxpDq9MvE8MmhWNr+I8w3BN49Vx36Y6Xg== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.18.0-next.1" - object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -3862,7 +3546,7 @@ object.assign@^4.1.0, object.assign@^4.1.1: has-symbols "^1.0.1" object-keys "^1.1.1" -object.entries@^1.1.1, object.entries@^1.1.2: +object.entries@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.2.tgz#bc73f00acb6b6bb16c203434b10f9a7e797d3add" integrity sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA== @@ -3980,13 +3664,6 @@ parse5@5.1.1: resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== -parse5@^3.0.1: - version "3.0.3" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c" - integrity sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA== - dependencies: - "@types/node" "*" - pascalcase@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" @@ -4105,15 +3782,6 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.4" -prop-types-exact@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/prop-types-exact/-/prop-types-exact-1.2.0.tgz#825d6be46094663848237e3925a98c6e944e9869" - integrity sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA== - dependencies: - has "^1.0.3" - object.assign "^4.1.0" - reflect.ownkeys "^0.2.0" - prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" @@ -4146,26 +3814,6 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== -raf@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" - integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== - dependencies: - performance-now "^2.1.0" - -railroad-diagrams@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e" - integrity sha1-635iZ1SN3t+4mcG5Dlc3RVnN234= - -randexp@0.4.6: - version "0.4.6" - resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3" - integrity sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ== - dependencies: - discontinuous-range "1.0.0" - ret "~0.1.10" - react-dom@^18.0.0: version "18.0.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.0.0.tgz#26b88534f8f1dbb80853e1eabe752f24100d8023" @@ -4174,7 +3822,7 @@ react-dom@^18.0.0: loose-envify "^1.1.0" scheduler "^0.21.0" -react-is@^16.12.0, react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.6: +react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -4184,16 +3832,6 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-test-renderer@^16.0.0-0: - version "16.13.1" - resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.13.1.tgz#de25ea358d9012606de51e012d9742e7f0deabc1" - integrity sha512-Sn2VRyOK2YJJldOqoh8Tn/lWQ+ZiKhyZTPtaO0Q6yNj+QDbmRkVFap6pZPy3YQk8DScRDfyqm/KxKYP9gCMRiQ== - dependencies: - object-assign "^4.1.1" - prop-types "^15.6.2" - react-is "^16.8.6" - scheduler "^0.19.1" - react@^18.0.0: version "18.0.0" resolved "https://registry.yarnpkg.com/react/-/react-18.0.0.tgz#b468736d1f4a5891f38585ba8e8fb29f91c3cb96" @@ -4220,15 +3858,6 @@ read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" -readable-stream@^3.1.1: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - redent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" @@ -4237,11 +3866,6 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" -reflect.ownkeys@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460" - integrity sha1-dJrO7H8/34tj+SegSAnpDFwLNGA= - regenerator-runtime@^0.13.4: version "0.13.9" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" @@ -4424,14 +4048,6 @@ rollup@^2.32.1: optionalDependencies: fsevents "~2.1.2" -rst-selector-parser@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91" - integrity sha1-gbIw6i/MYGbInjRy3nlChdmwPZE= - dependencies: - lodash.flattendeep "^4.4.0" - nearley "^2.7.10" - rsvp@^4.8.4: version "4.8.5" resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" @@ -4449,7 +4065,7 @@ rxjs@^6.6.0: dependencies: tslib "^1.9.0" -safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: +safe-buffer@^5.0.1, safe-buffer@^5.1.2: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -4493,14 +4109,6 @@ saxes@^5.0.0: dependencies: xmlchars "^2.2.0" -scheduler@^0.19.1: - version "0.19.1" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196" - integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - scheduler@^0.21.0: version "0.21.0" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.21.0.tgz#6fd2532ff5a6d877b6edb12f00d8ab7e8f308820" @@ -4508,7 +4116,7 @@ scheduler@^0.21.0: dependencies: loose-envify "^1.1.0" -"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.7.0, semver@^5.7.1: +"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -4797,14 +4405,6 @@ string.prototype.matchall@^4.0.2: regexp.prototype.flags "^1.3.0" side-channel "^1.0.2" -string.prototype.trim@^1.2.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.2.tgz#f538d0bacd98fc4297f0bef645226d5aaebf59f3" - integrity sha512-b5yrbl3BXIjHau9Prk7U0RRYcUYdN4wGSVaqoBQS50CCE3KBuYU0TYRNPFCP7aVoNMX87HKThdMRVIP3giclKg== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.18.0-next.0" - string.prototype.trimend@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" @@ -4821,13 +4421,6 @@ string.prototype.trimstart@^1.0.1: define-properties "^1.1.3" es-abstract "^1.17.5" -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - strip-ansi@^5.1.0, strip-ansi@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" @@ -5148,11 +4741,6 @@ use@^3.1.0: resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== -util-deprecate@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - "utility-types@^2.1.0 || ^3.0.0": version "3.10.0" resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.10.0.tgz#ea4148f9a741015f05ed74fd615e1d20e6bed82b" From f5cd8301caff95218f1a1c5482e8e5991d5531bf Mon Sep 17 00:00:00 2001 From: zashraf1985 Date: Tue, 17 May 2022 09:46:59 -0700 Subject: [PATCH 09/12] updated node version to be used by tests --- .github/workflows/react.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/react.yml b/.github/workflows/react.yml index dce56565..5522cf2c 100644 --- a/.github/workflows/react.yml +++ b/.github/workflows/react.yml @@ -13,10 +13,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Set up Node 10 + - name: Set up Node 16 uses: actions/setup-node@v3 with: - node-version: 10 + node-version: 16 - run: yarn install - run: yarn test From 41ef17c229be2a0dcbe6457f9ce2bb741d8e018d Mon Sep 17 00:00:00 2001 From: zashraf1985 Date: Tue, 17 May 2022 09:58:57 -0700 Subject: [PATCH 10/12] trying to fix ci --- .github/workflows/integration_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index ac978f97..cd09b3f3 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -49,4 +49,4 @@ jobs: HOME: 'home/runner' run: | echo "$GITHUB_CONTEXT" - home/runner/travisci-tools/trigger-script-with-status-update.sh + home/runner/travisci-tools/trigger-script-with-status-update.sh main From 88a51347b8bfaa3884a7793f76d0b4859a43ef06 Mon Sep 17 00:00:00 2001 From: shaharyarsheikh Date: Wed, 18 May 2022 10:51:25 +0500 Subject: [PATCH 11/12] PR suggested changes --- .github/workflows/integration_test.yml | 2 +- src/hooks.spec.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index b7966f1c..e05b9061 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -45,5 +45,5 @@ jobs: TRAVIS_COM_TOKEN: ${{ secrets.TRAVIS_COM_TOKEN }} EVENT_MESSAGE: ${{ github.event.message }} HOME: 'home/runner' - run: + run: | home/runner/travisci-tools/trigger-script-with-status-update.sh main diff --git a/src/hooks.spec.tsx b/src/hooks.spec.tsx index 722769d7..7a1f5803 100644 --- a/src/hooks.spec.tsx +++ b/src/hooks.spec.tsx @@ -198,7 +198,7 @@ describe('hooks', () => { ); - await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('null|false|false')); + await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('null|false|false')); // initial render await optimizelyMock.onReady(); await waitFor(() => expect(screen.getByTestId('result')).toHaveTextContent('null|false|true')); // when didTimeout From 581a1c276f2ff196f06322174ec41ad02485eb6e Mon Sep 17 00:00:00 2001 From: zashraf1985 Date: Mon, 23 May 2022 18:39:17 -0700 Subject: [PATCH 12/12] Updated CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c1eaf4a..b774b009 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Bug fixes - addresses issues [#152](https://github.com/optimizely/react-sdk/issues/152) and [#134](https://github.com/optimizely/react-sdk/issues/134): Gracefully returns pessimistic default values when hooks fail instead of throwing an error. - fixed issue [#156](https://github.com/optimizely/react-sdk/issues/156) - Added children prop to make the SDK compatible with React 18([#158](https://github.com/optimizely/react-sdk/pull/158)). +- Updates React SDK to use React 18 and fixed related typescript issues ([#159](https://github.com/optimizely/react-sdk/pull/159)). +- Replaces `enzyme` with `react testing library` to make unit tests work with React 18 ([#159](https://github.com/optimizely/react-sdk/pull/159)). ## [2.8.1] - March 7, 2022