Add tests on companies page (#155)

This commit is contained in:
Charles Bochet 2023-05-29 22:08:01 +02:00 committed by GitHub
parent 2f50cdc07e
commit 30d2337462
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 459 additions and 108 deletions

View File

@ -38,8 +38,11 @@ jobs:
run: cd front && npm run lint
- name: Build Storybook
run: cd front && npm run build-storybook --quiet
- name: Serve Storybook and run tests
- name: Serve Storybook and run storybook tests
run: |
cd front && npx concurrently -k -s first -n "SB,TEST" -c "magenta,blue" \
"npx http-server storybook-static --silent --port 6006" \
"npm run coverage"
"npm run coverage"
- name: run jest tests
run: |
cd front && npm run test

View File

@ -33,7 +33,7 @@
"storybook": "storybook dev -p 6006 -s ../public",
"test-storybook": "test-storybook",
"build-storybook": "storybook build -s public",
"coverage": "test-storybook --coverage && npx nyc report --reporter=lcov -t coverage/storybook --report-dir coverage/storybook --check-coverage --lines 50",
"coverage": "test-storybook --coverage && npx nyc report --reporter=lcov -t coverage/storybook --report-dir coverage/storybook --check-coverage",
"graphql:generate": "REACT_APP_GRAPHQL_ADMIN_SECRET=$REACT_APP_GRAPHQL_ADMIN_SECRET graphql-codegen --config codegen.js"
},
"eslintConfig": {
@ -122,5 +122,9 @@
},
"msw": {
"workerDirectory": "public"
},
"nyc": {
"lines": 60,
"statements": 60
}
}

View File

@ -60,7 +60,7 @@ function SortAndFilterBar<SortField, TData extends FilterableFieldsType>({
key={sort.key}
labelValue={sort.label}
id={sort.key}
icon={sort.order === 'asc' ? <FaArrowDown /> : <FaArrowUp />}
icon={sort.order === 'desc' ? <FaArrowDown /> : <FaArrowUp />}
onRemove={() => onRemoveSort(sort.key)}
/>
);

View File

@ -24,7 +24,7 @@ export type GraphqlMutationUser = {
id: string;
email?: string;
displayName?: string;
workspaceMember_id?: string;
workspaceMemberId?: string;
__typename: string;
};
@ -42,6 +42,6 @@ export const mapToGqlUser = (user: User): GraphqlMutationUser => ({
id: user.id,
email: user.email,
displayName: user.displayName,
workspaceMember_id: user.workspaceMember?.id,
workspaceMemberId: user.workspaceMember?.id,
__typename: 'users',
});

View File

@ -0,0 +1,74 @@
import { expect } from '@storybook/jest';
import type { Meta } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library';
import Companies from '../Companies';
import { Story } from './Companies.stories';
import { mocks, render } from './shared';
const meta: Meta<typeof Companies> = {
title: 'Pages/Companies',
component: Companies,
};
export default meta;
export const FilterByName: Story = {
render,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const filterButton = canvas.getByText('Filter');
await userEvent.click(filterButton);
const nameFilterButton = canvas.getByText('Name', { selector: 'li' });
await userEvent.click(nameFilterButton);
const nameInput = canvas.getByPlaceholderText('Name');
await userEvent.type(nameInput, 'Air', {
delay: 200,
});
expect(await canvas.findByText('Airbnb')).toBeInTheDocument();
expect(await canvas.findByText('Aircall')).toBeInTheDocument();
await expect(canvas.queryAllByText('Qonto')).toStrictEqual([]);
expect(await canvas.findByText('Name:')).toBeInTheDocument();
expect(await canvas.findByText('Contains Air')).toBeInTheDocument();
},
parameters: {
msw: mocks,
},
};
export const FilterByAccountOwner: Story = {
render,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const filterButton = canvas.getByText('Filter');
await userEvent.click(filterButton);
const accountOwnerFilterButton = canvas.getByText('Account Owner', {
selector: 'li',
});
await userEvent.click(accountOwnerFilterButton);
const accountOwnerNameInput = canvas.getByPlaceholderText('Account Owner');
await userEvent.type(accountOwnerNameInput, 'Char', {
delay: 200,
});
const charlesChip = canvas.getByText('Charles Test', { selector: 'li' });
await userEvent.click(charlesChip);
expect(await canvas.findByText('Airbnb')).toBeInTheDocument();
await expect(canvas.queryAllByText('Qonto')).toStrictEqual([]);
expect(await canvas.findByText('Account Owner:')).toBeInTheDocument();
expect(await canvas.findByText('Is Charles Test')).toBeInTheDocument();
},
parameters: {
msw: mocks,
},
};

View File

@ -0,0 +1,11 @@
{ /* Companies.mdx */ }
import { Canvas, Meta } from '@storybook/blocks';
import * as Companies from './Companies.stories';
<Meta of={Companies} />
# Companies View
<Canvas of={Companies.Default} />

View File

@ -0,0 +1,45 @@
import { expect } from '@storybook/jest';
import type { Meta } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library';
import Companies from '../Companies';
import { Story } from './Companies.stories';
import { mocks, render } from './shared';
const meta: Meta<typeof Companies> = {
title: 'Pages/Companies',
component: Companies,
};
export default meta;
export const SortByName: Story = {
render,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const sortButton = canvas.getByText('Sort');
await userEvent.click(sortButton);
const nameSortButton = canvas.getByText('Name', { selector: 'li' });
await userEvent.click(nameSortButton);
expect(await canvas.getByTestId('remove-icon-name')).toBeInTheDocument();
expect(await canvas.findByText('Airbnb')).toBeInTheDocument();
expect(
(await canvas.findAllByRole('checkbox')).map((item) => {
return item.getAttribute('id');
})[1],
).toStrictEqual('company-selected-89bb825c-171e-4bcc-9cf7-43448d6fb278');
const cancelButton = canvas.getByText('Cancel');
await userEvent.click(cancelButton);
await expect(canvas.queryAllByTestId('remove-icon-name')).toStrictEqual([]);
},
parameters: {
msw: mocks,
},
};

View File

@ -0,0 +1,21 @@
import type { Meta, StoryObj } from '@storybook/react';
import Companies from '../Companies';
import { render, mocks } from './shared';
const meta: Meta<typeof Companies> = {
title: 'Pages/Companies',
component: Companies,
};
export default meta;
export type Story = StoryObj<typeof Companies>;
export const Default: Story = {
render,
parameters: {
msw: mocks,
},
};

View File

@ -0,0 +1,61 @@
import { graphql } from 'msw';
import { RecoilRoot } from 'recoil';
import { ThemeProvider } from '@emotion/react';
import { MemoryRouter } from 'react-router-dom';
import { ApolloProvider } from '@apollo/client';
import { filterAndSortData } from '../../../testing/mock-data';
import { mockedCompaniesData } from '../../../testing/mock-data/companies';
import { GraphqlQueryCompany } from '../../../interfaces/entities/company.interface';
import { lightTheme } from '../../../layout/styles/themes';
import { FullHeightStorybookLayout } from '../../../testing/FullHeightStorybookLayout';
import { mockedClient } from '../../../testing/mockedClient';
import Companies from '../Companies';
import { GraphqlQueryUser } from '../../../interfaces/entities/user.interface';
import { mockedUsersData } from '../../../testing/mock-data/users';
export const mocks = [
graphql.query('GetCompanies', (req, res, ctx) => {
const returnedMockedData = filterAndSortData<GraphqlQueryCompany>(
mockedCompaniesData,
req.variables.where,
req.variables.orderBy,
req.variables.limit,
);
return res(
ctx.data({
companies: returnedMockedData,
}),
);
}),
graphql.query('SearchUserQuery', (req, res, ctx) => {
const returnedMockedData = filterAndSortData<GraphqlQueryUser>(
mockedUsersData,
req.variables.where,
req.variables.orderBy,
req.variables.limit,
);
return res(
ctx.data({
searchResults: returnedMockedData,
}),
);
}),
];
export function render() {
return (
<RecoilRoot>
<ApolloProvider client={mockedClient}>
<ThemeProvider theme={lightTheme}>
<MemoryRouter>
<FullHeightStorybookLayout>
<Companies />
</FullHeightStorybookLayout>
</MemoryRouter>
</ThemeProvider>
</ApolloProvider>
</RecoilRoot>
);
}

View File

@ -14,7 +14,7 @@ import { QueryMode } from '../../generated/graphql';
export const nameFilter = {
key: 'name',
label: 'Company',
label: 'Name',
icon: <TbBuilding size={16} />,
type: 'text',
operands: [

View File

@ -0,0 +1,71 @@
import { expect } from '@storybook/jest';
import type { Meta } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library';
import People from '../People';
import { Story } from './People.stories';
import { mocks, render } from './shared';
const meta: Meta<typeof People> = {
title: 'Pages/People',
component: People,
};
export default meta;
export const FilterByEmail: Story = {
render,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const filterButton = canvas.getByText('Filter');
await userEvent.click(filterButton);
const emailFilterButton = canvas.getByText('Email', { selector: 'li' });
await userEvent.click(emailFilterButton);
const emailInput = canvas.getByPlaceholderText('Email');
await userEvent.type(emailInput, 'al', {
delay: 200,
});
expect(await canvas.findByText('Alexandre Prot')).toBeInTheDocument();
await expect(canvas.queryAllByText('John Doe')).toStrictEqual([]);
expect(await canvas.findByText('Email:')).toBeInTheDocument();
expect(await canvas.findByText('Contains al')).toBeInTheDocument();
},
parameters: {
msw: mocks,
},
};
export const FilterByCompanyName: Story = {
render,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const filterButton = canvas.getByText('Filter');
await userEvent.click(filterButton);
const companyFilterButton = canvas.getByText('Company', { selector: 'li' });
await userEvent.click(companyFilterButton);
const companyNameInput = canvas.getByPlaceholderText('Company');
await userEvent.type(companyNameInput, 'Qon', {
delay: 200,
});
const qontoChip = canvas.getByText('Qonto', { selector: 'li' });
await userEvent.click(qontoChip);
expect(await canvas.findByText('Alexandre Prot')).toBeInTheDocument();
await expect(canvas.queryAllByText('John Doe')).toStrictEqual([]);
expect(await canvas.findByText('Company:')).toBeInTheDocument();
expect(await canvas.findByText('Is Qonto')).toBeInTheDocument();
},
parameters: {
msw: mocks,
},
};

View File

@ -0,0 +1,47 @@
import { expect } from '@storybook/jest';
import type { Meta } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library';
import People from '../People';
import { Story } from './People.stories';
import { mocks, render } from './shared';
const meta: Meta<typeof People> = {
title: 'Pages/People',
component: People,
};
export default meta;
export const SortByEmail: Story = {
render,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const sortButton = canvas.getByText('Sort');
await userEvent.click(sortButton);
const emailSortButton = canvas.getByText('Email', { selector: 'li' });
await userEvent.click(emailSortButton);
expect(await canvas.getByTestId('remove-icon-email')).toBeInTheDocument();
expect(await canvas.findByText('Alexandre Prot')).toBeInTheDocument();
expect(
(await canvas.findAllByRole('checkbox')).map((item) => {
return item.getAttribute('id');
})[1],
).toStrictEqual('person-selected-7dfbc3f7-6e5e-4128-957e-8d86808cdf6b');
const cancelButton = canvas.getByText('Cancel');
await userEvent.click(cancelButton);
await expect(canvas.queryAllByTestId('remove-icon-email')).toStrictEqual(
[],
);
},
parameters: {
msw: mocks,
},
};

View File

@ -1,113 +1,21 @@
import { expect } from '@storybook/jest';
import type { Meta, StoryObj } from '@storybook/react';
import { RecoilRoot } from 'recoil';
import { ThemeProvider } from '@emotion/react';
import { MemoryRouter } from 'react-router-dom';
import { graphql } from 'msw';
import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client';
import { userEvent, within } from '@storybook/testing-library';
import People from '../People';
import { lightTheme } from '../../../layout/styles/themes';
import { FullHeightStorybookLayout } from '../../../testing/FullHeightStorybookLayout';
import { filterAndSortData } from '../../../testing/mock-data';
import { GraphqlQueryPerson } from '../../../interfaces/entities/person.interface';
import { mockedPeopleData } from '../../../testing/mock-data/people';
import { GraphqlQueryCompany } from '../../../interfaces/entities/company.interface';
import { mockCompaniesData } from '../../../testing/mock-data/companies';
import { render, mocks } from './shared';
const meta: Meta<typeof People> = {
title: 'Pages/People',
component: People,
};
const mockedClient = new ApolloClient({
uri: process.env.REACT_APP_API_URL,
cache: new InMemoryCache(),
defaultOptions: {
watchQuery: {
fetchPolicy: 'no-cache',
errorPolicy: 'all',
},
query: {
fetchPolicy: 'no-cache',
errorPolicy: 'all',
},
},
});
export default meta;
type Story = StoryObj<typeof People>;
const render = () => (
<RecoilRoot>
<ApolloProvider client={mockedClient}>
<ThemeProvider theme={lightTheme}>
<MemoryRouter>
<FullHeightStorybookLayout>
<People />
</FullHeightStorybookLayout>
</MemoryRouter>
</ThemeProvider>
</ApolloProvider>
</RecoilRoot>
);
const defaultMocks = [
graphql.query('GetPeople', (req, res, ctx) => {
const returnedMockedData = filterAndSortData<GraphqlQueryPerson>(
mockedPeopleData,
req.variables.where,
req.variables.orderBy,
req.variables.limit,
);
return res(
ctx.data({
people: returnedMockedData,
}),
);
}),
graphql.query('SearchQuery', (req, res, ctx) => {
const returnedMockedData = filterAndSortData<GraphqlQueryCompany>(
mockCompaniesData,
req.variables.where,
req.variables.orderBy,
req.variables.limit,
);
return res(
ctx.data({
searchResults: returnedMockedData,
}),
);
}),
];
export type Story = StoryObj<typeof People>;
export const Default: Story = {
render,
parameters: {
msw: defaultMocks,
},
};
export const FilterByEmail: Story = {
render,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const filterButton = canvas.getByText('Filter');
await userEvent.click(filterButton);
const emailFilterButton = canvas.getByText('Email', { selector: 'li' });
await userEvent.click(emailFilterButton);
const emailInput = canvas.getByPlaceholderText('Email');
await userEvent.type(emailInput, 'al', {
delay: 200,
});
await expect(canvas.queryAllByText('John')).toStrictEqual([]);
},
parameters: {
msw: defaultMocks,
msw: mocks,
},
};

View File

@ -0,0 +1,61 @@
import { graphql } from 'msw';
import { RecoilRoot } from 'recoil';
import { ThemeProvider } from '@emotion/react';
import { MemoryRouter } from 'react-router-dom';
import { ApolloProvider } from '@apollo/client';
import { filterAndSortData } from '../../../testing/mock-data';
import { mockedPeopleData } from '../../../testing/mock-data/people';
import { mockedCompaniesData } from '../../../testing/mock-data/companies';
import { GraphqlQueryCompany } from '../../../interfaces/entities/company.interface';
import { GraphqlQueryPerson } from '../../../interfaces/entities/person.interface';
import { lightTheme } from '../../../layout/styles/themes';
import { FullHeightStorybookLayout } from '../../../testing/FullHeightStorybookLayout';
import { mockedClient } from '../../../testing/mockedClient';
import People from '../People';
export const mocks = [
graphql.query('GetPeople', (req, res, ctx) => {
const returnedMockedData = filterAndSortData<GraphqlQueryPerson>(
mockedPeopleData,
req.variables.where,
req.variables.orderBy,
req.variables.limit,
);
return res(
ctx.data({
people: returnedMockedData,
}),
);
}),
graphql.query('SearchCompanyQuery', (req, res, ctx) => {
const returnedMockedData = filterAndSortData<GraphqlQueryCompany>(
mockedCompaniesData,
req.variables.where,
req.variables.orderBy,
req.variables.limit,
);
return res(
ctx.data({
searchResults: returnedMockedData,
}),
);
}),
];
export function render() {
return (
<RecoilRoot>
<ApolloProvider client={mockedClient}>
<ThemeProvider theme={lightTheme}>
<MemoryRouter>
<FullHeightStorybookLayout>
<People />
</FullHeightStorybookLayout>
</MemoryRouter>
</ThemeProvider>
</ApolloProvider>
</RecoilRoot>
);
}

View File

@ -40,7 +40,7 @@ export const EMPTY_QUERY = gql`
`;
export const SEARCH_COMPANY_QUERY = gql`
query SearchQuery($where: CompanyWhereInput, $limit: Int) {
query SearchCompanyQuery($where: CompanyWhereInput, $limit: Int) {
searchResults: companies(where: $where, take: $limit) {
id
name

View File

@ -1,6 +1,6 @@
import { GraphqlQueryCompany } from '../../interfaces/entities/company.interface';
export const mockCompaniesData: Array<GraphqlQueryCompany> = [
export const mockedCompaniesData: Array<GraphqlQueryCompany> = [
{
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
domainName: 'airbnb.com',
@ -8,7 +8,12 @@ export const mockCompaniesData: Array<GraphqlQueryCompany> = [
createdAt: '2023-04-26T10:08:54.724515+00:00',
address: '17 rue de clignancourt',
employees: 12,
accountOwner: null,
accountOwner: {
email: 'charles@test.com',
displayName: 'Charles Test',
id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6b',
__typename: 'users',
},
__typename: 'companies',
},
{

View File

@ -23,7 +23,10 @@ function filterData<DataT>(
if (filterElement.is) {
const nestedKey = Object.keys(filterElement.is)[0] as string;
if (typeof item[key as keyof typeof item] === 'object') {
if (
item[key as keyof typeof item] &&
typeof item[key as keyof typeof item] === 'object'
) {
const nestedItem = item[key as keyof typeof item];
return (
nestedItem[nestedKey as keyof typeof nestedItem] ===
@ -71,6 +74,7 @@ export function filterAndSortData<DataT>(
limit: number,
): Array<DataT> {
let filteredData = filterData<DataT>(data, where);
console.log(filteredData);
if (orderBy) {
const firstOrderBy = orderBy[0];
@ -84,8 +88,12 @@ export function filterAndSortData<DataT>(
return 0;
}
const sortDirection =
firstOrderBy[key as unknown as keyof typeof firstOrderBy];
if (typeof itemAValue === 'string' && typeof itemBValue === 'string') {
return itemBValue.localeCompare(itemAValue);
return sortDirection === 'desc'
? itemBValue.localeCompare(itemAValue)
: -itemBValue.localeCompare(itemAValue);
}
return 0;
});

View File

@ -0,0 +1,16 @@
import { GraphqlQueryUser } from '../../interfaces/entities/user.interface';
export const mockedUsersData: Array<GraphqlQueryUser> = [
{
id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6b',
__typename: 'User',
email: 'charles@test.com',
displayName: 'Charles Test',
},
{
id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6c',
__typename: 'User',
email: 'felix@test.com',
displayName: 'Felix Test',
},
];

View File

@ -0,0 +1,16 @@
import { ApolloClient, InMemoryCache } from '@apollo/client';
export const mockedClient = new ApolloClient({
uri: process.env.REACT_APP_API_URL,
cache: new InMemoryCache(),
defaultOptions: {
watchQuery: {
fetchPolicy: 'no-cache',
errorPolicy: 'all',
},
query: {
fetchPolicy: 'no-cache',
errorPolicy: 'all',
},
},
});