Skip to content

Commit

Permalink
feat: SB-802, SB-803 Migrate documentsList mutations,
Browse files Browse the repository at this point in the history
SB-802 migrate documentsListCreateMutation,
SB-803 migrate documentsDeleteMutation

* SB-802 unskip and fix tests,
remove relay from tests

* SB-802 create getDocumentDeleteMock,
fix storybook document and documents stories,
update test file to use getDocumentDeleteMock

* SB-802 use fragment in documentsListCreateMutation

* SB-802 remove deleteMutation mocks from stories,
update getDocumentDeleteMock

* SB-802 fix redundant var

* SB-802 update pnpm lock file


Approved-by: Michał Kleszcz
  • Loading branch information
voytek98 committed Feb 15, 2023
1 parent 1dcbd6a commit def1470
Show file tree
Hide file tree
Showing 10 changed files with 957 additions and 2,030 deletions.
4 changes: 2 additions & 2 deletions packages/webapp/.storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ const config: StorybookViteConfig = {
'../src/routes/crudDemoItem/editCrudDemoItem/editCrudDemoItem.stories.tsx',
'../src/routes/demoItem/demoItem.stories.tsx',
// '../src/routes/demoItems/demoItems.stories.tsx',
// '../src/routes/documents/document/document.stories.tsx',
'../src/routes/documents/document/document.stories.tsx',
'../src/routes/documents/document/skeleton/skeleton.stories.tsx',
// '../src/routes/documents/documents.stories.tsx',
'../src/routes/documents/documents.stories.tsx',
'../src/routes/finances/cancelSubscription/cancelSubscription.stories.tsx',
'../src/routes/finances/editPaymentMethod/editPaymentMethodForm/editPaymentMethodForm.stories.tsx',
'../src/routes/finances/editSubscription/editSubscription.stories.tsx',
Expand Down
25 changes: 22 additions & 3 deletions packages/webapp/src/mocks/factories/document.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { times } from 'ramda';
import { MockPayloadGenerator, RelayMockEnvironment } from 'relay-test-utils';

import { DocumentDemoItemType } from '../../shared/services/graphqlApi';
import { connectionFromArray, makeId, composeMockedListQueryResult } from '../../tests/utils/fixtures';
import DocumentsListQuery from '../../routes/documents/__generated__/documentsListQuery.graphql';
import { documentsListQuery } from '../../routes/documents/documents.graphql';
import { documentsListDeleteMutation, documentsListQuery } from '../../routes/documents/documents.graphql';
import { DocumentDemoItemType } from '../../shared/services/graphqlApi';
import { DocumentsDeleteMutationMutation } from '../../shared/services/graphqlApi/__generated/gql/graphql';
import {
composeMockedListQueryResult,
composeMockedQueryResult,
connectionFromArray,
makeId,
} from '../../tests/utils/fixtures';
import { createDeepFactory } from './factoryCreators';

export const documentFactory = createDeepFactory<Partial<DocumentDemoItemType>>(() => ({
Expand All @@ -28,3 +34,16 @@ export const fillDocumentsListQuery = (env?: RelayMockEnvironment, data = times(

return composeMockedListQueryResult(documentsListQuery, 'allDocumentDemoItems', 'DocumentDemoItemType', { data });
};

export const fillDocumentDeleteQuery = (id: string, data: DocumentsDeleteMutationMutation) => {
const deleteMutationMock = composeMockedQueryResult(documentsListDeleteMutation, {
variables: {
input: {
id,
},
},
data,
});

return deleteMutationMock;
};
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
import { fireEvent, screen } from '@testing-library/react';
import { times } from 'ramda';
import { act, fireEvent, screen, waitFor } from '@testing-library/react';
import { createMockEnvironment, MockPayloadGenerator } from 'relay-test-utils';

import { Documents } from '../documents.component';
import { documentFactory, fillDocumentsListQuery } from '../../../mocks/factories';
import { documentFactory, fillDocumentDeleteQuery, fillDocumentsListQuery } from '../../../mocks/factories';
import { DocumentsDeleteMutationMutation } from '../../../shared/services/graphqlApi/__generated/gql/graphql';
import { render } from '../../../tests/utils/rendering';

const generateRelayEnvironmentDocuments = (documents: any) => {
return createMockEnvironment();
};
import { Documents } from '../documents.component';

describe('Documents: Component', () => {
const Component = () => <Documents />;

it('should render list of documents', async () => {
const env = createMockEnvironment();
const documentsLength = 3;
const generatedDocs = times(() => documentFactory(), documentsLength);

const mockRequest = fillDocumentsListQuery(env, generatedDocs);
const mockRequest = fillDocumentsListQuery(undefined, generatedDocs);
render(<Component />, { apolloMocks: (defaultMocks) => defaultMocks.concat(mockRequest) });

expect(await screen.findAllByRole('link')).toHaveLength(documentsLength);
Expand All @@ -33,52 +28,44 @@ describe('Documents: Component', () => {
expect(await screen.findByText('No documents')).toBeInTheDocument();
});

// TODO: documentsListCreateMutation have to be migrated to apollo
it.skip('should add new item to the list', async () => {
it('should add new item to the list', async () => {
const generatedDoc = documentFactory();
const relayEnvironment = generateRelayEnvironmentDocuments([generatedDoc]);

const mockRequest = fillDocumentsListQuery(relayEnvironment, [generatedDoc]);
const mockRequest = fillDocumentsListQuery(undefined, [generatedDoc]);

render(<Component />, { apolloMocks: (defaultMocks) => defaultMocks.concat(mockRequest) });

const file = new File(['content'], 'file.png', { type: 'image/png' });
const file = new File(['content'], `${generatedDoc.file?.name}`, { type: 'image/png' });
fireEvent.change(await screen.findByTestId('file-input'), {
target: { files: [file] },
});

await waitFor(() => {
relayEnvironment.mock.resolveMostRecentOperation((operation) =>
MockPayloadGenerator.generate(operation, {
DocumentDemoItemType: (context, generateId) => ({
...documentFactory({ file: { name: file.name } }),
id: `${generateId()}`,
}),
})
);
});

expect(screen.getByText(file.name)).toBeInTheDocument();
expect(screen.getAllByRole('listitem')).toHaveLength(2);
expect(await screen.findByText(file.name)).toBeInTheDocument();
expect(screen.getAllByRole('listitem')).toHaveLength(1);
});

// TODO: documentsDeleteMutation have to be migrated to apollo
it.skip('should remove new item from the list', async () => {
const document = documentFactory();
const relayEnvironment = generateRelayEnvironmentDocuments([document]);
const mockRequest = fillDocumentsListQuery([document]);
render(<Component />, { relayEnvironment, apolloMocks: (defaultMocks) => defaultMocks.concat(mockRequest) });

fireEvent.click(await screen.findByRole('button', { name: /delete/i }));

act(() => {
relayEnvironment.mock.resolveMostRecentOperation((operation) =>
MockPayloadGenerator.generate(operation, {
DeleteDocumentDemoItemMutationPayload: () => ({ deletedIds: [document.id] }),
})
);
it('should remove new item from the list', async () => {
const generatedDoc = documentFactory();
const id = generatedDoc.id as string;
const mutationData: DocumentsDeleteMutationMutation = {
deleteDocumentDemoItem: {
deletedIds: [id],
__typename: 'DeleteDocumentDemoItemMutationPayload',
},
};

const deleteMutationMock = fillDocumentDeleteQuery(id, mutationData);
deleteMutationMock.newData = jest.fn(() => ({
data: mutationData,
}));

const mockRequest = fillDocumentsListQuery(undefined, [generatedDoc]);

render(<Component />, {
apolloMocks: (defaultMocks) => defaultMocks.concat(mockRequest, deleteMutationMock),
});
fireEvent.click(await screen.findByRole('button', { name: /delete/i }));

expect(screen.queryByRole('listitem')).not.toBeInTheDocument();
expect(deleteMutationMock.newData).toHaveBeenCalled();
});
});
38 changes: 6 additions & 32 deletions packages/webapp/src/routes/documents/document/document.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import { Story } from '@storybook/react';
import graphql from 'babel-plugin-relay/macro';
import { useLazyLoadQuery } from 'react-relay';
import { MockPayloadGenerator } from 'relay-test-utils';
import styled from 'styled-components';

import { documentFactory } from '../../../mocks/factories';
import { withRelay } from '../../../shared/utils/storybook';
import { connectionFromArray } from '../../../tests/utils/fixtures';
import { documentListItemStoryQuery } from './__generated__/documentListItemStoryQuery.graphql';
import { withProviders } from '../../../shared/utils/storybook';
import { Document, DocumentProps } from './document.component';

const Container = styled.div`
Expand All @@ -16,38 +11,17 @@ const Container = styled.div`
`;

const Template: Story<DocumentProps> = (args: DocumentProps) => {
const data = useLazyLoadQuery<documentListItemStoryQuery>(
graphql`
query documentListItemStoryQuery @relay_test_operation {
allDocumentDemoItems(first: 1) {
edges {
node {
...documentListItem
}
}
}
}
`,
{}
);
const generatedDoc = documentFactory();
const { file, createdAt } = generatedDoc;
const id = generatedDoc.id as string;

const item = data.allDocumentDemoItems?.edges[0]?.node;

return <Container>{item && <Document {...args} item={item} />}</Container>;
return <Container>{generatedDoc && <Document {...args} item={{ id, file, createdAt }} />}</Container>;
};

export default {
title: 'Routes/Documents/Document',
component: Document,
decorators: [
withRelay((env) => {
env.mock.queueOperationResolver((operation) =>
MockPayloadGenerator.generate(operation, {
DocumentDemoItemConnection: () => connectionFromArray([documentFactory()]),
})
);
}),
],
decorators: [withProviders({})],
argTypes: {
item: {
control: {
Expand Down
11 changes: 6 additions & 5 deletions packages/webapp/src/routes/documents/documents.component.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { FormattedMessage } from 'react-intl';
import { useQuery } from '@apollo/client';
import { isEmpty } from 'ramda';
import { Dropzone } from '../../shared/components/forms/dropzone';
import { FormattedMessage } from 'react-intl';

import { EmptyState } from '../../shared/components/emptyState';
import { Dropzone } from '../../shared/components/forms/dropzone';
import { useMappedConnection } from '../../shared/hooks/useMappedConnection';
import { DocumentsListQueryQuery } from '../../shared/services/graphqlApi/__generated/gql/graphql';
import { Container, Header, List } from './documents.styles';
import { Document, DocumentSkeleton } from './document';
import { MAX_FILE_SIZE, MAX_FILES } from './documents.constants';
import { useHandleDrop } from './documents.hooks';
import { MAX_FILES, MAX_FILE_SIZE } from './documents.constants';
import { documentsListQuery } from './documents.graphql';
import { useHandleDrop } from './documents.hooks';
import { Container, Header, List } from './documents.styles';

type ListContentProps = {
data: DocumentsListQueryQuery;
Expand Down
21 changes: 21 additions & 0 deletions packages/webapp/src/routes/documents/documents.graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,24 @@ export const documentsListQuery = gql(/* GraphQL */ `
}
}
`);

export const documentsListCreateMutation = gql(/* GraphQL */ `
mutation documentsListCreateMutation($input: CreateDocumentDemoItemMutationInput!) {
createDocumentDemoItem(input: $input) {
documentDemoItemEdge {
node {
createdAt
...documentListItem
}
}
}
}
`);

export const documentsListDeleteMutation = gql(/* GraphQL */ `
mutation documentsDeleteMutation($input: DeleteDocumentDemoItemMutationInput!) {
deleteDocumentDemoItem(input: $input) {
deletedIds
}
}
`);
79 changes: 44 additions & 35 deletions packages/webapp/src/routes/documents/documents.hooks.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,67 @@
import graphql from 'babel-plugin-relay/macro';
import { ConnectionHandler } from 'relay-runtime';
import { usePromiseMutation } from '../../shared/services/graphqlApi/usePromiseMutation';
import { documentsListCreateMutation } from './__generated__/documentsListCreateMutation.graphql';
import { useMutation } from '@apollo/client';

import {
documentListItemFragment,
documentsListCreateMutation,
documentsListDeleteMutation,
} from './documents.graphql';

export const useHandleDrop = () => {
const [commitDropMutation] = usePromiseMutation<documentsListCreateMutation>(
graphql`
mutation documentsListCreateMutation($input: CreateDocumentDemoItemMutationInput!, $connections: [ID!]!) {
createDocumentDemoItem(input: $input) {
documentDemoItemEdge @appendEdge(connections: $connections) {
node {
createdAt
file {
name
url
}
}
}
}
}
`
);
const [commitMutation] = useMutation(documentsListCreateMutation, {
update(cache, { data }) {
cache.modify({
fields: {
allDocumentDemoItems(existingConnection = { edges: [] }) {
const node = data?.createDocumentDemoItem?.documentDemoItemEdge?.node;
if (!node) return existingConnection;

const normalizedId = cache.identify(node);

const isAlreadyInStore = existingConnection.edges.some(({ node }) => node.__ref === normalizedId);
if (isAlreadyInStore) return existingConnection;

const newFile = {
node: cache.writeFragment({
data: node,
fragment: documentListItemFragment,
}),
};

return { ...existingConnection, edges: [...existingConnection.edges, newFile] };
},
},
});
},
});

return async (files: File[]) => {
for (const file of files) {
await commitDropMutation({
await commitMutation({
variables: {
input: {},
connections: [ConnectionHandler.getConnectionID('root', 'documentsList_allDocumentDemoItems')],
},
uploadables: {
file,
input: {
file,
},
},
});
}
};
};

export const useHandleDelete = () => {
const [commitDeleteMutation] = usePromiseMutation(graphql`
mutation documentsDeleteMutation($input: DeleteDocumentDemoItemMutationInput!, $connections: [ID!]!) {
deleteDocumentDemoItem(input: $input) {
deletedIds @deleteEdge(connections: $connections)
}
}
`);
const [commitDeleteMutation] = useMutation(documentsListDeleteMutation, {
update(cache, { data }) {
const deletedId = data?.deleteDocumentDemoItem?.deletedIds?.[0];
const normalizedId = cache.identify({ id: deletedId, __typename: 'DocumentDemoItemType' });
cache.evict({ id: normalizedId });
},
});

return async (id: string) => {
await commitDeleteMutation({
variables: {
input: {
id,
},
connections: [ConnectionHandler.getConnectionID('root', 'documentsList_allDocumentDemoItems')],
},
});
};
Expand Down
Loading

0 comments on commit def1470

Please sign in to comment.