From 356b4e514e190d590fd144e78e21f831bb43ac4f Mon Sep 17 00:00:00 2001 From: Sergey Melnikov Date: Fri, 7 Oct 2022 16:20:16 -0400 Subject: [PATCH 1/3] [react-testing] Disable flakly test for React 17 --- .changeset/five-birds-thank.md | 2 + packages/react-testing/src/tests/e2e.test.tsx | 45 ++++++++++--------- 2 files changed, 26 insertions(+), 21 deletions(-) create mode 100644 .changeset/five-birds-thank.md diff --git a/.changeset/five-birds-thank.md b/.changeset/five-birds-thank.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/five-birds-thank.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/react-testing/src/tests/e2e.test.tsx b/packages/react-testing/src/tests/e2e.test.tsx index 50abd66cce..25a67c06a9 100644 --- a/packages/react-testing/src/tests/e2e.test.tsx +++ b/packages/react-testing/src/tests/e2e.test.tsx @@ -173,31 +173,34 @@ describe('@shopify/react-testing', () => { ); } - it('releases any stale promises when component is destroyed', async () => { - const wrapper = mount(); - wrapper.act(() => new Promise(() => {})); - wrapper.act(() => new Promise(() => {})); - await wrapper.destroy(); + // eslint-disable-next-line no-process-env + if (process.env.REACT_VERSION !== '17') { + it('releases any stale promises when component is destroyed', async () => { + const wrapper = mount(); + wrapper.act(() => new Promise(() => {})); + wrapper.act(() => new Promise(() => {})); + await wrapper.destroy(); - // React 17 will fail without this await - await new Promise((resolve) => setTimeout(resolve, 0)); + // React 17 will fail without this await + await new Promise((resolve) => setTimeout(resolve, 0)); - function EffectChangeComponent({children}: {children?: ReactNode}) { - const [counter, setCounter] = useState(0); - useEffect(() => setCounter(100), []); + function EffectChangeComponent({children}: {children?: ReactNode}) { + const [counter, setCounter] = useState(0); + useEffect(() => setCounter(100), []); - return ( - // eslint-disable-next-line @shopify/jsx-prefer-fragment-wrappers -
- {counter} - {children} -
- ); - } - const newWrapper = mount(); + return ( + // eslint-disable-next-line @shopify/jsx-prefer-fragment-wrappers +
+ {counter} + {children} +
+ ); + } + const newWrapper = mount(); - expect(newWrapper.find(Message)!.html()).toBe('100'); - }); + expect(newWrapper.find(Message)!.html()).toBe('100'); + }); + } it('updates element tree when state is changed', () => { const wrapper = mount(); From 8520cd101bbb53f6b98e09340bee5f9ae5b06ccb Mon Sep 17 00:00:00 2001 From: Ryan Wilson-Perkin Date: Fri, 14 Oct 2022 10:59:41 -0400 Subject: [PATCH 2/3] Use it.skip on React 17 for this test --- .eslintrc.js | 4 ++++ packages/react-testing/src/tests/e2e.test.tsx | 11 +++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 8154ba1747..3786b2060f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -16,6 +16,10 @@ module.exports = { 'node/no-extraneous-require': 'off', 'import/no-cycle': 'off', 'jest/require-tothrow-message': 'off', + 'jest/no-standalone-expect': [ + 'error', + {additionalTestBlockFunctions: ['itIf']}, + ], 'callback-return': 'off', 'func-style': 'off', 'react/display-name': 'off', diff --git a/packages/react-testing/src/tests/e2e.test.tsx b/packages/react-testing/src/tests/e2e.test.tsx index 25a67c06a9..38e46fe94b 100644 --- a/packages/react-testing/src/tests/e2e.test.tsx +++ b/packages/react-testing/src/tests/e2e.test.tsx @@ -15,6 +15,8 @@ import {createPortal} from 'react-dom'; import {mount, createMount} from '../mount'; +const itIf = (condition) => (condition ? it : it.skip); + describe('@shopify/react-testing', () => { it('does not time out with large trees', () => { function RecurseMyself({times}: {times: number}) { @@ -174,8 +176,9 @@ describe('@shopify/react-testing', () => { } // eslint-disable-next-line no-process-env - if (process.env.REACT_VERSION !== '17') { - it('releases any stale promises when component is destroyed', async () => { + itIf(process.env.REACT_VERSION !== '17')( + 'releases any stale promises when component is destroyed', + async () => { const wrapper = mount(); wrapper.act(() => new Promise(() => {})); wrapper.act(() => new Promise(() => {})); @@ -199,8 +202,8 @@ describe('@shopify/react-testing', () => { const newWrapper = mount(); expect(newWrapper.find(Message)!.html()).toBe('100'); - }); - } + }, + ); it('updates element tree when state is changed', () => { const wrapper = mount(); From 0f3a23a553446cfb97d895ac101dccc4699622c2 Mon Sep 17 00:00:00 2001 From: Ben Scott Date: Thu, 6 Oct 2022 17:49:59 -0700 Subject: [PATCH 3/3] graphql-testing: Add test for fetchMore behavior --- .../graphql-testing/src/tests/e2e.test.tsx | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/packages/graphql-testing/src/tests/e2e.test.tsx b/packages/graphql-testing/src/tests/e2e.test.tsx index 6cb0ee7a6d..23baba92e2 100644 --- a/packages/graphql-testing/src/tests/e2e.test.tsx +++ b/packages/graphql-testing/src/tests/e2e.test.tsx @@ -24,6 +24,26 @@ const petQuery: DocumentNode<{pet?: {name: string} | null}, {id: string}> = gql` } `; +const petsQuery: DocumentNode< + {pets: {edges: {cursor: string; node: {name: string}}[]}}, + {first: number; after?: string | null} +> = gql` + query Pets($first: Number!, $after: String) { + pets(first: $first, after: $after) { + edges { + cursor + node { + ...CatInfo + } + } + } + } + + fragment CatInfo on Cat { + name + } +`; + function MyComponent({id = '1'} = {}) { const {data, loading, error, refetch} = useQuery(petQuery, {variables: {id}}); @@ -60,6 +80,55 @@ function MyComponent({id = '1'} = {}) { ); } +function MyComponentWithFetchMore({itemsPerPage = 1} = {}) { + const {data, loading, fetchMore} = useQuery(petsQuery, { + variables: {first: itemsPerPage}, + }); + + const loadingMarkup = loading ?

Loading

: null; + const petsMarkup = data ? ( + <> + LoadedNames:{' '} + {data.pets.edges.map((petEdge) => petEdge.node.name).join('&')} +
+ LoadedCount: {data.pets.edges.length} + + ) : null; + + const handleFetchMoreButtonClick = async () => { + if (!data || data.pets.edges.length === 0) { + return; + } + const edges = data.pets.edges; + const cursor = edges[edges.length - 1].cursor; + + await fetchMore({ + variables: {first: itemsPerPage, after: cursor}, + updateQuery({pets: {edges: oldEdges}}, {fetchMoreResult}) { + const pets = fetchMoreResult && fetchMoreResult.pets; + + return { + ...fetchMoreResult, + pets: { + ...pets, + edges: [...oldEdges, ...(pets?.edges ?? [])], + }, + }; + }, + }); + }; + + return ( + <> + {loadingMarkup} + {petsMarkup} + + + ); +} + describe('graphql-testing', () => { it('does not resolve immediately', () => { const graphQL = createGraphQL({ @@ -227,4 +296,78 @@ describe('graphql-testing', () => { expect(myComponent).toContainReactText(newName); }); + + it('handles fetchMore', async () => { + const graphQL = createGraphQL({ + Pets: ({ + variables: {first = 10, after}, + }: { + variables: {first?: number; after: string}; + }) => { + const fullData = [ + {cursor: 'a', node: {__typename: 'Cat', name: 'Garfield'}}, + {cursor: 'b', node: {__typename: 'Cat', name: 'Nermal'}}, + {cursor: 'c', node: {__typename: 'Cat', name: 'Arlene'}}, + {cursor: 'd', node: {__typename: 'Cat', name: 'Not Odie'}}, + ].map((item) => ({__typename: 'Edge', ...item})); + + // eslint-disable-next-line jest/no-if + const startPosition = after + ? fullData.findIndex((item) => item.cursor === after) + 1 + : 0; + + return { + pets: { + __typename: 'Pets', + edges: fullData.slice(startPosition, startPosition + first), + }, + }; + }, + }); + + const myComponent = mount( + + + , + ); + + graphQL.wrap((resolve) => myComponent.act(resolve)); + await graphQL.resolveAll(); + + expect(graphQL).toHavePerformedGraphQLOperation(petsQuery, { + first: 1, + }); + + // Start with just one item loaded + expect(myComponent).toContainReactText('LoadedNames: Garfield'); + expect(myComponent).toContainReactText('LoadedCount: 1'); + + // Trigger a fetchMore, and see that LoadedNames/Count updates with + // an additional item + const request = myComponent.find('button').trigger('onClick'); + await graphQL.resolveAll(); + await request; + + expect(graphQL).toHavePerformedGraphQLOperation(petsQuery, { + first: 1, + after: 'a', + }); + expect(myComponent).toContainReactText('LoadedNames: Garfield&Nermal'); + expect(myComponent).toContainReactText('LoadedCount: 2'); + + // Trigger another fetchMore, and see that LoadedNames/Count updates with + // an additional item + const request2 = myComponent.find('button').trigger('onClick'); + await graphQL.resolveAll(); + await request2; + + expect(graphQL).toHavePerformedGraphQLOperation(petsQuery, { + first: 1, + after: 'b', + }); + expect(myComponent).toContainReactText( + 'LoadedNames: Garfield&Nermal&Arlene', + ); + expect(myComponent).toContainReactText('LoadedCount: 3'); + }); });