Refactor tests command menu (#1702)
* Fix tests * Refactor tests command menu * Improve tests * Fix optimistic render breaking tests
This commit is contained in:
@ -1,185 +1,62 @@
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { useEffect } from 'react';
|
||||
import { expect } from '@storybook/jest';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { fireEvent, userEvent, within } from '@storybook/testing-library';
|
||||
import { userEvent, within } from '@storybook/testing-library';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { CommandType } from '@/command-menu/types/Command';
|
||||
import { IconCheckbox, IconNotes } from '@/ui/icon';
|
||||
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { sleep } from '~/testing/sleep';
|
||||
|
||||
import { WrapperCommandMenu } from './WrapperCommandMenu';
|
||||
import { CommandMenu } from '../CommandMenu';
|
||||
|
||||
enum CommandType {
|
||||
Navigate = 'Navigate',
|
||||
Create = 'Create',
|
||||
}
|
||||
|
||||
const meta: Meta<typeof WrapperCommandMenu> = {
|
||||
title: 'Modules/CommandMenu/WrapperCommandMenu',
|
||||
component: () => (
|
||||
<WrapperCommandMenu
|
||||
companies={[
|
||||
{
|
||||
__typename: 'Company',
|
||||
accountOwner: null,
|
||||
address: '',
|
||||
createdAt: '2023-09-19T08:35:37.174Z',
|
||||
domainName: 'facebook.com',
|
||||
employees: null,
|
||||
linkedinUrl: null,
|
||||
xUrl: null,
|
||||
annualRecurringRevenue: null,
|
||||
idealCustomerProfile: false,
|
||||
id: 'twenty-118995f3-5d81-46d6-bf83-f7fd33ea6102',
|
||||
name: 'Facebook',
|
||||
_activityCount: 0,
|
||||
},
|
||||
{
|
||||
__typename: 'Company',
|
||||
accountOwner: null,
|
||||
address: '',
|
||||
createdAt: '2023-09-19T08:35:37.188Z',
|
||||
domainName: 'airbnb.com',
|
||||
employees: null,
|
||||
linkedinUrl: null,
|
||||
xUrl: null,
|
||||
annualRecurringRevenue: null,
|
||||
idealCustomerProfile: false,
|
||||
id: 'twenty-89bb825c-171e-4bcc-9cf7-43448d6fb278',
|
||||
name: 'Airbnb',
|
||||
_activityCount: 0,
|
||||
},
|
||||
{
|
||||
__typename: 'Company',
|
||||
accountOwner: null,
|
||||
address: '',
|
||||
createdAt: '2023-09-19T08:35:37.206Z',
|
||||
domainName: 'claap.io',
|
||||
employees: null,
|
||||
linkedinUrl: null,
|
||||
xUrl: null,
|
||||
annualRecurringRevenue: null,
|
||||
idealCustomerProfile: false,
|
||||
id: 'twenty-9d162de6-cfbf-4156-a790-e39854dcd4eb',
|
||||
name: 'Claap',
|
||||
_activityCount: 0,
|
||||
},
|
||||
]}
|
||||
activities={[
|
||||
{
|
||||
__typename: 'Activity',
|
||||
id: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfb400',
|
||||
title: 'Performance update',
|
||||
body: '[{"id":"555df0c3-ab88-4c62-abae-c9b557c37c5b","type":"paragraph","props":{"textColor":"default","backgroundColor":"default","textAlignment":"left"},"content":[{"type":"text","text":"In the North American region, we have observed a strong growth rate of 18% in sales. Europe followed suit with a significant 14% increase, while Asia-Pacific sustained its performance with a steady 10% rise. Special kudos to the North American team for the excellent work done in penetrating new markets and establishing stronger footholds in the existing ones.","styles":{}}],"children":[]},{"id":"13530934-b3ce-4332-9238-3760aa4acb3e","type":"paragraph","props":{"textColor":"default","backgroundColor":"default","textAlignment":"left"},"content":[],"children":[]}]',
|
||||
},
|
||||
{
|
||||
__typename: 'Activity',
|
||||
id: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfc408',
|
||||
title: 'Buyout Proposal',
|
||||
body: '[{"id":"333df0c3-ab88-4c62-abae-c9b557c37c5b","type":"paragraph","props":{"textColor":"default","backgroundColor":"default","textAlignment":"left"},"content":[{"type":"text","text":"We are considering the potential acquisition of [Company], a leading company in [Industry/Specific Technology]. This company has demonstrated remarkable success and pioneering advancements in their field, paralleling our own commitment to progress. By integrating their expertise with our own, we believe that we can amplify our growth, broaden our offerings, and fortify our position at the forefront of technology. This prospective partnership could help to ensure our continued leadership in the industry and allow us to deliver even more innovative solutions for our customers.","styles":{}}],"children":[]},{"id":"13530934-b3ce-4332-9238-3760aa4acb3e","type":"paragraph","props":{"textColor":"default","backgroundColor":"default","textAlignment":"left"},"content":[],"children":[]}]',
|
||||
},
|
||||
]}
|
||||
values={[
|
||||
{
|
||||
to: '/people',
|
||||
label: 'Go to People',
|
||||
type: CommandType.Navigate,
|
||||
shortcuts: ['G', 'P'],
|
||||
},
|
||||
{
|
||||
to: '/companies',
|
||||
label: 'Go to Companies',
|
||||
type: CommandType.Navigate,
|
||||
shortcuts: ['G', 'C'],
|
||||
},
|
||||
{
|
||||
to: '/opportunities',
|
||||
label: 'Go to Opportunities',
|
||||
type: CommandType.Navigate,
|
||||
shortcuts: ['G', 'O'],
|
||||
},
|
||||
{
|
||||
to: '/settings/profile',
|
||||
label: 'Go to Settings',
|
||||
type: CommandType.Navigate,
|
||||
shortcuts: ['G', 'S'],
|
||||
},
|
||||
{
|
||||
to: '/tasks',
|
||||
label: 'Go to Tasks',
|
||||
type: CommandType.Navigate,
|
||||
shortcuts: ['G', 'T'],
|
||||
},
|
||||
{
|
||||
to: '',
|
||||
label: 'Create Task',
|
||||
type: CommandType.Create,
|
||||
},
|
||||
]}
|
||||
people={[
|
||||
{
|
||||
__typename: 'Person',
|
||||
id: 'twenty-0aa00beb-ac73-4797-824e-87a1f5aea9e0',
|
||||
phone: '+33780123456',
|
||||
email: 'sylvie.palmer@linkedin.com',
|
||||
city: 'Los Angeles',
|
||||
firstName: 'Sylvie',
|
||||
lastName: 'Palmer',
|
||||
displayName: 'Sylvie Palmer',
|
||||
avatarUrl: null,
|
||||
createdAt: '2023-09-19T08:35:37.240Z',
|
||||
},
|
||||
{
|
||||
__typename: 'Person',
|
||||
id: 'twenty-1d151852-490f-4466-8391-733cfd66a0c8',
|
||||
phone: '+33782345678',
|
||||
email: 'isabella.scott@microsoft.com',
|
||||
city: 'New York',
|
||||
firstName: 'Isabella',
|
||||
lastName: 'Scott',
|
||||
displayName: 'Isabella Scott',
|
||||
avatarUrl: null,
|
||||
createdAt: '2023-09-19T08:35:37.257Z',
|
||||
},
|
||||
{
|
||||
__typename: 'Person',
|
||||
id: 'twenty-240da2ec-2d40-4e49-8df4-9c6a049190df',
|
||||
phone: '+33788901234',
|
||||
email: 'bertrand.voulzy@google.com',
|
||||
city: 'Seattle',
|
||||
firstName: 'Bertrand',
|
||||
lastName: 'Voulzy',
|
||||
displayName: 'Bertrand Voulzy',
|
||||
avatarUrl: null,
|
||||
createdAt: '2023-09-19T08:35:37.291Z',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
),
|
||||
const meta: Meta<typeof CommandMenu> = {
|
||||
title: 'Modules/CommandMenu/CommandMenu',
|
||||
component: CommandMenu,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<MemoryRouter>
|
||||
<Story />
|
||||
</MemoryRouter>
|
||||
),
|
||||
ComponentDecorator,
|
||||
ComponentWithRouterDecorator,
|
||||
(Story) => {
|
||||
const { addToCommandMenu, setToIntitialCommandMenu, openCommandMenu } =
|
||||
useCommandMenu();
|
||||
|
||||
useEffect(() => {
|
||||
setToIntitialCommandMenu();
|
||||
addToCommandMenu([
|
||||
{
|
||||
to: '',
|
||||
label: 'Create Task',
|
||||
type: CommandType.Create,
|
||||
Icon: IconCheckbox,
|
||||
onCommandClick: () => console.log('create task click'),
|
||||
},
|
||||
{
|
||||
to: '',
|
||||
label: 'Create Note',
|
||||
type: CommandType.Create,
|
||||
Icon: IconNotes,
|
||||
onCommandClick: () => console.log('create note click'),
|
||||
},
|
||||
]);
|
||||
openCommandMenu();
|
||||
}, [addToCommandMenu, setToIntitialCommandMenu, openCommandMenu]);
|
||||
|
||||
return <Story />;
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof WrapperCommandMenu>;
|
||||
type Story = StoryObj<typeof CommandMenu>;
|
||||
|
||||
export const DefaultWithoutSearch: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
fireEvent.keyDown(canvasElement, {
|
||||
key: 'k',
|
||||
code: 'KeyK',
|
||||
metaKey: true,
|
||||
});
|
||||
await sleep(50);
|
||||
play: async () => {
|
||||
const canvas = within(document.body);
|
||||
const searchInput = await canvas.findByPlaceholderText('Search');
|
||||
await sleep(10);
|
||||
await userEvent.type(searchInput, '');
|
||||
|
||||
expect(await canvas.findByText('Create Task')).toBeInTheDocument();
|
||||
expect(await canvas.findByText('Go to People')).toBeInTheDocument();
|
||||
expect(await canvas.findByText('Go to Companies')).toBeInTheDocument();
|
||||
@ -190,66 +67,42 @@ export const DefaultWithoutSearch: Story = {
|
||||
};
|
||||
|
||||
export const MatchingPersonCompanyActivityCreateNavigate: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
fireEvent.keyDown(canvasElement, {
|
||||
key: 'k',
|
||||
code: 'KeyK',
|
||||
metaKey: true,
|
||||
});
|
||||
await sleep(50);
|
||||
play: async () => {
|
||||
const canvas = within(document.body);
|
||||
const searchInput = await canvas.findByPlaceholderText('Search');
|
||||
await sleep(10);
|
||||
await userEvent.type(searchInput, 'a');
|
||||
expect(await canvas.findByText('Isabella Scott')).toBeInTheDocument();
|
||||
await userEvent.type(searchInput, 'n');
|
||||
expect(await canvas.findByText('Alexandre Prot')).toBeInTheDocument();
|
||||
expect(await canvas.findByText('Airbnb')).toBeInTheDocument();
|
||||
expect(await canvas.findByText('Buyout Proposal')).toBeInTheDocument();
|
||||
expect(await canvas.findByText('Create Task')).toBeInTheDocument();
|
||||
expect(await canvas.findByText('Go to Tasks')).toBeInTheDocument();
|
||||
expect(await canvas.findByText('My very first note')).toBeInTheDocument();
|
||||
expect(await canvas.findByText('Create Note')).toBeInTheDocument();
|
||||
expect(await canvas.findByText('Go to Companies')).toBeInTheDocument();
|
||||
},
|
||||
};
|
||||
|
||||
export const OnlyMatchingCreateAndNavigate: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
fireEvent.keyDown(canvasElement, {
|
||||
key: 'k',
|
||||
code: 'KeyK',
|
||||
metaKey: true,
|
||||
});
|
||||
await sleep(50);
|
||||
play: async () => {
|
||||
const canvas = within(document.body);
|
||||
const searchInput = await canvas.findByPlaceholderText('Search');
|
||||
await sleep(10);
|
||||
await userEvent.type(searchInput, 'tas');
|
||||
await userEvent.type(searchInput, 'ta');
|
||||
expect(await canvas.findByText('Create Task')).toBeInTheDocument();
|
||||
expect(await canvas.findByText('Go to Tasks')).toBeInTheDocument();
|
||||
},
|
||||
};
|
||||
|
||||
export const AtleastMatchingOnePerson: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
fireEvent.keyDown(canvasElement, {
|
||||
key: 'k',
|
||||
code: 'KeyK',
|
||||
metaKey: true,
|
||||
});
|
||||
await sleep(50);
|
||||
play: async () => {
|
||||
const canvas = within(document.body);
|
||||
const searchInput = await canvas.findByPlaceholderText('Search');
|
||||
await sleep(10);
|
||||
await userEvent.type(searchInput, 'sy');
|
||||
expect(await canvas.findByText('Sylvie Palmer')).toBeInTheDocument();
|
||||
await userEvent.type(searchInput, 'alex');
|
||||
expect(await canvas.findByText('Alexandre Prot')).toBeInTheDocument();
|
||||
},
|
||||
};
|
||||
|
||||
export const NotMatchingAnything: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
fireEvent.keyDown(canvasElement, {
|
||||
key: 'k',
|
||||
code: 'KeyK',
|
||||
metaKey: true,
|
||||
});
|
||||
await sleep(50);
|
||||
play: async () => {
|
||||
const canvas = within(document.body);
|
||||
const searchInput = await canvas.findByPlaceholderText('Search');
|
||||
await sleep(10);
|
||||
|
||||
@ -1,247 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
|
||||
import { IconNotes } from '@/ui/icon';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
import { Avatar } from '@/users/components/Avatar';
|
||||
import { getLogoUrlFromDomainName } from '~/utils';
|
||||
|
||||
import { useCommandMenu } from '../../hooks/useCommandMenu';
|
||||
import { isCommandMenuOpenedState } from '../../states/isCommandMenuOpenedState';
|
||||
import { Command, CommandType } from '../../types/Command';
|
||||
import { CommandMenuItem } from '../CommandMenuItem';
|
||||
import {
|
||||
StyledDialog,
|
||||
StyledEmpty,
|
||||
StyledGroup,
|
||||
StyledInput,
|
||||
StyledList,
|
||||
} from '../CommandMenuStyles';
|
||||
|
||||
export const WrapperCommandMenu = ({
|
||||
values,
|
||||
people,
|
||||
companies,
|
||||
activities,
|
||||
}: {
|
||||
values: Command[];
|
||||
people: Array<{
|
||||
__typename?: 'Person';
|
||||
id: string;
|
||||
phone?: string | null;
|
||||
email?: string | null;
|
||||
city?: string | null;
|
||||
firstName?: string | null;
|
||||
lastName?: string | null;
|
||||
displayName: string;
|
||||
avatarUrl?: string | null;
|
||||
createdAt: string;
|
||||
}>;
|
||||
companies: Array<{
|
||||
__typename?: 'Company';
|
||||
address: string;
|
||||
createdAt: string;
|
||||
domainName: string;
|
||||
employees?: number | null;
|
||||
linkedinUrl?: string | null;
|
||||
xUrl?: string | null;
|
||||
annualRecurringRevenue?: number | null;
|
||||
idealCustomerProfile: boolean;
|
||||
id: string;
|
||||
name: string;
|
||||
_activityCount: number;
|
||||
accountOwner?: {
|
||||
__typename?: 'User';
|
||||
id: string;
|
||||
email: string;
|
||||
displayName: string;
|
||||
avatarUrl?: string | null;
|
||||
} | null;
|
||||
}>;
|
||||
activities: Array<{
|
||||
__typename?: 'Activity';
|
||||
id: string;
|
||||
title?: string | null;
|
||||
body?: string | null;
|
||||
}>;
|
||||
}) => {
|
||||
const { openCommandMenu, closeCommandMenu } = useCommandMenu();
|
||||
const openActivityRightDrawer = useOpenActivityRightDrawer();
|
||||
const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
|
||||
const [search, setSearch] = useState('');
|
||||
const commandMenuCommands = values;
|
||||
|
||||
const peopleData = people.filter((i) =>
|
||||
search.length > 0
|
||||
? (i.firstName
|
||||
? i.firstName?.toLowerCase().includes(search.toLowerCase())
|
||||
: false) ||
|
||||
(i.lastName
|
||||
? i.lastName?.toLowerCase().includes(search.toLowerCase())
|
||||
: false)
|
||||
: false,
|
||||
);
|
||||
|
||||
const companyData = companies.filter((i) =>
|
||||
search.length > 0
|
||||
? i.name
|
||||
? i.name?.toLowerCase().includes(search.toLowerCase())
|
||||
: false
|
||||
: false,
|
||||
);
|
||||
|
||||
const activityData = activities.filter((i) =>
|
||||
search.length > 0
|
||||
? (i.title
|
||||
? i.title?.toLowerCase().includes(search.toLowerCase())
|
||||
: false) ||
|
||||
(i.body ? i.body?.toLowerCase().includes(search.toLowerCase()) : false)
|
||||
: false,
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
'ctrl+k,meta+k',
|
||||
() => {
|
||||
openCommandMenu();
|
||||
},
|
||||
AppHotkeyScope.CommandMenu,
|
||||
[openCommandMenu],
|
||||
);
|
||||
|
||||
const checkInShortcuts = (cmd: Command, search: string) => {
|
||||
if (cmd.shortcuts && cmd.shortcuts.length > 0) {
|
||||
return cmd.shortcuts
|
||||
.join('')
|
||||
.toLowerCase()
|
||||
.includes(search.toLowerCase());
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const checkInLabels = (cmd: Command, search: string) => {
|
||||
if (cmd.label) {
|
||||
return cmd.label.toLowerCase().includes(search.toLowerCase());
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const matchingNavigateCommand = commandMenuCommands.filter(
|
||||
(cmd) =>
|
||||
(search.length > 0
|
||||
? checkInShortcuts(cmd, search) || checkInLabels(cmd, search)
|
||||
: true) && cmd.type === CommandType.Navigate,
|
||||
);
|
||||
|
||||
const matchingCreateCommand = commandMenuCommands.filter(
|
||||
(cmd) =>
|
||||
(search.length > 0
|
||||
? checkInShortcuts(cmd, search) || checkInLabels(cmd, search)
|
||||
: true) && cmd.type === CommandType.Create,
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledDialog
|
||||
open={isCommandMenuOpened}
|
||||
onOpenChange={(opened) => {
|
||||
if (!opened) {
|
||||
closeCommandMenu();
|
||||
}
|
||||
}}
|
||||
shouldFilter={false}
|
||||
label="Global Command Menu"
|
||||
>
|
||||
<StyledInput
|
||||
value={search}
|
||||
placeholder="Search"
|
||||
onValueChange={setSearch}
|
||||
/>
|
||||
<StyledList>
|
||||
{matchingCreateCommand.length < 1 &&
|
||||
matchingNavigateCommand.length < 1 &&
|
||||
peopleData.length < 1 &&
|
||||
companyData.length < 1 &&
|
||||
activityData.length < 1 && (
|
||||
<StyledEmpty>No results found.</StyledEmpty>
|
||||
)}
|
||||
{matchingCreateCommand.length > 0 && (
|
||||
<StyledGroup heading="Create">
|
||||
{matchingCreateCommand.map((cmd) => (
|
||||
<CommandMenuItem
|
||||
to={cmd.to}
|
||||
key={cmd.label}
|
||||
Icon={cmd.Icon}
|
||||
label={cmd.label}
|
||||
onClick={cmd.onCommandClick}
|
||||
shortcuts={cmd.shortcuts || []}
|
||||
/>
|
||||
))}
|
||||
</StyledGroup>
|
||||
)}
|
||||
{matchingNavigateCommand.length > 0 && (
|
||||
<StyledGroup heading="Navigate">
|
||||
{matchingNavigateCommand.map((cmd) => (
|
||||
<CommandMenuItem
|
||||
to={cmd.to}
|
||||
key={cmd.label}
|
||||
label={cmd.label}
|
||||
onClick={cmd.onCommandClick}
|
||||
shortcuts={cmd.shortcuts || []}
|
||||
/>
|
||||
))}
|
||||
</StyledGroup>
|
||||
)}
|
||||
{peopleData.length > 0 && (
|
||||
<StyledGroup heading="People">
|
||||
{peopleData.map((person) => (
|
||||
<CommandMenuItem
|
||||
key={person.id}
|
||||
to={`person/${person.id}`}
|
||||
label={person.displayName}
|
||||
Icon={() => (
|
||||
<Avatar
|
||||
type="rounded"
|
||||
avatarUrl={null}
|
||||
colorId={person.id}
|
||||
placeholder={person.displayName}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</StyledGroup>
|
||||
)}
|
||||
{companyData.length > 0 && (
|
||||
<StyledGroup heading="Companies">
|
||||
{companyData.map((company) => (
|
||||
<CommandMenuItem
|
||||
key={company.id}
|
||||
label={company.name}
|
||||
to={`companies/${company.id}`}
|
||||
Icon={() => (
|
||||
<Avatar
|
||||
colorId={company.id}
|
||||
placeholder={company.name}
|
||||
avatarUrl={getLogoUrlFromDomainName(company.domainName)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</StyledGroup>
|
||||
)}
|
||||
{activityData.length > 0 && (
|
||||
<StyledGroup heading="Notes">
|
||||
{activityData.map((activity) => (
|
||||
<CommandMenuItem
|
||||
Icon={IconNotes}
|
||||
key={activity.id}
|
||||
label={activity.title ?? ''}
|
||||
onClick={() => openActivityRightDrawer(activity.id)}
|
||||
/>
|
||||
))}
|
||||
</StyledGroup>
|
||||
)}
|
||||
</StyledList>
|
||||
</StyledDialog>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user