Feat/single entity select relation picker (#345)

* - Implemented recoil scoped state
- Implemented SingleEntitySelect
- Implemented keyboard shortcut up/down select

* Added useRecoilScopedValue

* Fix storybook

* Fix storybook

* Fix storybook

* Fix storybook

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Lucas Bordeau
2023-06-21 22:29:07 +02:00
committed by GitHub
parent 8a330b9746
commit e679f45615
23 changed files with 653 additions and 180 deletions

View File

@ -1,10 +1,15 @@
import { getOperationName } from '@apollo/client/utilities';
import { expect } from '@storybook/jest';
import type { Meta } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library';
import { graphql } from 'msw';
import { UPDATE_PERSON } from '@/people/services';
import { SEARCH_COMPANY_QUERY } from '@/search/services/search';
import { Company } from '~/generated/graphql';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { fetchOneFromData } from '~/testing/mock-data';
import { mockedCompaniesData } from '~/testing/mock-data/companies';
import { mockedPeopleData } from '~/testing/mock-data/people';
import { getRenderWrapperForPage } from '~/testing/renderWrappers';
import { sleep } from '~/testing/sleep';
@ -95,59 +100,121 @@ export const CheckCheckboxes: Story = {
},
};
const editRelationMocks = (
initiallySelectedCompanyName: string,
searchCompanyNames: Array<string>,
updateSelectedCompany: Pick<Company, 'name' | 'domainName'>,
) => [
...graphqlMocks.filter((graphqlMock) => {
if (
typeof graphqlMock.info.operationName === 'string' &&
[
getOperationName(UPDATE_PERSON),
getOperationName(SEARCH_COMPANY_QUERY),
].includes(graphqlMock.info.operationName)
) {
return false;
}
return true;
}),
...[
graphql.mutation(getOperationName(UPDATE_PERSON) ?? '', (req, res, ctx) => {
return res(
ctx.data({
updateOnePerson: {
...fetchOneFromData(mockedPeopleData, req.variables.id),
...{
company: {
id: req.variables.companyId,
name: updateSelectedCompany.name,
domainName: updateSelectedCompany.domainName,
__typename: 'Company',
},
},
},
}),
);
}),
graphql.query(
getOperationName(SEARCH_COMPANY_QUERY) ?? '',
(req, res, ctx) => {
if (!req.variables.where?.AND) {
// Selected company case
const searchResults = mockedCompaniesData.filter((company) =>
[initiallySelectedCompanyName].includes(company.name),
);
return res(
ctx.data({
searchResults: searchResults,
}),
);
}
if (
req.variables.where?.AND?.some(
(where: { id?: { in: Array<string> } }) => where.id?.in,
)
) {
// Selected company case
const searchResults = mockedCompaniesData.filter((company) =>
[initiallySelectedCompanyName].includes(company.name),
);
return res(
ctx.data({
searchResults: searchResults,
}),
);
} else {
// Search case
const searchResults = mockedCompaniesData.filter((company) =>
searchCompanyNames.includes(company.name),
);
return res(
ctx.data({
searchResults: searchResults,
}),
);
}
},
),
],
];
export const EditRelation: Story = {
render: getRenderWrapperForPage(<People />, '/people'),
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const secondRowCompanyCell = await canvas.findByText(
const firstRowCompanyCell = await canvas.findByText(
mockedPeopleData[1].company.name,
);
await userEvent.click(secondRowCompanyCell);
await userEvent.click(firstRowCompanyCell);
const relationInput = await canvas.findByPlaceholderText('Company');
const relationInput = await canvas.findByPlaceholderText('Search');
await userEvent.type(relationInput, 'Air', {
delay: 200,
});
const airbnbChip = await canvas.findByText('Airbnb', {
selector: 'div > span',
selector: 'div',
});
await userEvent.click(airbnbChip);
const newSecondRowCompanyCell = await canvas.findByText('Airbnb');
const otherCell = await canvas.findByText('Janice Dane');
await userEvent.click(otherCell);
await userEvent.click(newSecondRowCompanyCell);
await canvas.findByText('Airbnb');
},
parameters: {
actions: {},
msw: [
...graphqlMocks.filter((graphqlMock) => {
return graphqlMock.info.operationName !== 'UpdatePeople';
}),
...[
graphql.mutation('UpdatePeople', (req, res, ctx) => {
return res(
ctx.data({
updateOnePerson: {
...fetchOneFromData(mockedPeopleData, req.variables.id),
...{
company: {
id: req.variables.companyId,
name: 'Airbnb',
domainName: 'airbnb.com',
__typename: 'Company',
},
},
},
}),
);
}),
],
],
msw: editRelationMocks('Qonto', ['Airbnb', 'Aircall'], {
name: 'Airbnb',
domainName: 'airbnb.com',
}),
},
};
@ -157,51 +224,32 @@ export const SelectRelationWithKeys: Story = {
const canvas = within(canvasElement);
const thirdRowCompanyCell = await canvas.findByText(
mockedPeopleData[2].company.name,
mockedPeopleData[0].company.name,
);
await userEvent.click(thirdRowCompanyCell);
const relationInput = await canvas.findByPlaceholderText('Company');
const relationInput = await canvas.findByPlaceholderText('Search');
await userEvent.type(relationInput, 'Air', {
delay: 200,
});
await userEvent.type(relationInput, '{arrowdown}');
await userEvent.type(relationInput, '{arrowdown}');
await userEvent.type(relationInput, '{arrowup}');
await userEvent.type(relationInput, '{arrowdown}');
await userEvent.type(relationInput, '{arrowdown}');
await userEvent.type(relationInput, '{enter}');
sleep(25);
const newThirdRowCompanyCell = await canvas.findByText('Aircall');
await userEvent.click(newThirdRowCompanyCell);
const allAirbns = await canvas.findAllByText('Aircall');
expect(allAirbns.length).toBe(1);
},
parameters: {
actions: {},
msw: [
...graphqlMocks.filter((graphqlMock) => {
return graphqlMock.info.operationName !== 'UpdatePeople';
}),
...[
graphql.mutation('UpdatePeople', (req, res, ctx) => {
return res(
ctx.data({
updateOnePerson: {
...fetchOneFromData(mockedPeopleData, req.variables.id),
...{
company: {
id: req.variables.companyId,
name: 'Aircall',
domainName: 'aircall.io',
__typename: 'Company',
},
},
},
}),
);
}),
],
],
msw: editRelationMocks('Qonto', ['Airbnb', 'Aircall'], {
name: 'Aircall',
domainName: 'aircall.io',
}),
},
};

View File

@ -7,6 +7,7 @@ import { EditableDate } from '@/ui/components/editable-cell/types/EditableDate';
import { EditablePhone } from '@/ui/components/editable-cell/types/EditablePhone';
import { EditableText } from '@/ui/components/editable-cell/types/EditableText';
import { ColumnHead } from '@/ui/components/table/ColumnHead';
import { RecoilScope } from '@/ui/hooks/RecoilScope';
import {
IconBuildingSkyscraper,
IconCalendarEvent,
@ -79,7 +80,11 @@ export const usePeopleColumns = () => {
viewIcon={<IconBuildingSkyscraper size={16} />}
/>
),
cell: (props) => <PeopleCompanyCell people={props.row.original} />,
cell: (props) => (
<RecoilScope>
<PeopleCompanyCell people={props.row.original} />
</RecoilScope>
),
size: 150,
}),
columnHelper.accessor('phone', {