Skip to content

Commit

Permalink
Fix storybook tests (twentyhq#5487)
Browse files Browse the repository at this point in the history
Fixes twentyhq#5486

---------

Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com>
Co-authored-by: v1b3m <vibenjamin6@gmail.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
  • Loading branch information
4 people authored May 21, 2024
1 parent e47101e commit 36b467d
Show file tree
Hide file tree
Showing 16 changed files with 709 additions and 101 deletions.
8 changes: 4 additions & 4 deletions packages/twenty-front/nyc.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ const globalCoverage = {
};

const modulesCoverage = {
branches: 45,
statements: 70,
lines: 70,
functions: 65,
branches: 25,
statements: 50,
lines: 50,
functions: 40,
include: ['src/modules/**/*'],
exclude: ['src/**/*.ts'],
};
Expand Down
6 changes: 5 additions & 1 deletion packages/twenty-front/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { ClientConfigProvider } from '@/client-config/components/ClientConfigPro
import { ClientConfigProviderEffect } from '@/client-config/components/ClientConfigProviderEffect';
import { billingState } from '@/client-config/states/billingState';
import { PromiseRejectionEffect } from '@/error-handler/components/PromiseRejectionEffect';
import indexAppPath from '@/navigation/utils/indexAppPath';
import { ApolloMetadataClientProvider } from '@/object-metadata/components/ApolloMetadataClientProvider';
import { ObjectMetadataItemsProvider } from '@/object-metadata/components/ObjectMetadataItemsProvider';
import { PrefetchDataProvider } from '@/prefetch/components/PrefetchDataProvider';
Expand Down Expand Up @@ -138,7 +139,10 @@ const createRouter = (isBillingEnabled?: boolean) =>
path={AppPath.PlanRequiredSuccess}
element={<PaymentSuccess />}
/>
<Route path={AppPath.Index} element={<DefaultHomePage />} />
<Route
path={indexAppPath.getIndexAppPath()}
element={<DefaultHomePage />}
/>
<Route path={AppPath.TasksPage} element={<Tasks />} />
<Route path={AppPath.Impersonate} element={<ImpersonateEffect />} />
<Route path={AppPath.RecordIndexPage} element={<RecordIndexPage />} />
Expand Down
72 changes: 32 additions & 40 deletions packages/twenty-front/src/__stories__/App.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,58 +1,39 @@
import { HelmetProvider } from 'react-helmet-async';
import { getOperationName } from '@apollo/client/utilities';
import { jest } from '@storybook/jest';
import { Meta, StoryObj } from '@storybook/react';
import { graphql, HttpResponse } from 'msw';
import { RecoilRoot } from 'recoil';
import { IconsProvider } from 'twenty-ui';

import { ClientConfigProvider } from '@/client-config/components/ClientConfigProvider';
import { ClientConfigProviderEffect } from '@/client-config/components/ClientConfigProviderEffect';
import { ObjectMetadataItemsProvider } from '@/object-metadata/components/ObjectMetadataItemsProvider';
import { SnackBarProvider } from '@/ui/feedback/snack-bar-manager/components/SnackBarProvider';
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
import indexAppPath from '@/navigation/utils/indexAppPath';
import { AppPath } from '@/types/AppPath';
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
import { AppThemeProvider } from '@/ui/theme/components/AppThemeProvider';
import { UserProvider } from '@/users/components/UserProvider';
import { UserProviderEffect } from '@/users/components/UserProviderEffect';
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
import { App } from '~/App';
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
import { FullHeightStorybookLayout } from '~/testing/FullHeightStorybookLayout';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { mockedUsersData } from '~/testing/mock-data/users';

const meta: Meta<typeof App> = {
title: 'App/App',
component: App,
decorators: [
MemoryRouterDecorator,
(Story) => (
<>
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
<ClientConfigProviderEffect />
<ClientConfigProvider>
<UserProviderEffect />
<UserProvider>
<FullHeightStorybookLayout>
<ObjectMetadataItemsProvider>
<IconsProvider>
<HelmetProvider>
<SnackBarProvider>
<AppThemeProvider>
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
<ObjectMetadataItemsProvider>
<Story />
</ObjectMetadataItemsProvider>
</SnackBarProviderScope>
</AppThemeProvider>
</SnackBarProvider>
</HelmetProvider>
</IconsProvider>
</ObjectMetadataItemsProvider>
</FullHeightStorybookLayout>
</UserProvider>
</ClientConfigProvider>
</SnackBarProviderScope>
</>
),
(Story) => {
return (
<RecoilRoot>
<AppErrorBoundary>
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
<IconsProvider>
<HelmetProvider>
<Story />
</HelmetProvider>
</IconsProvider>
</SnackBarProviderScope>
</AppErrorBoundary>
</RecoilRoot>
);
},
],
parameters: {
msw: graphqlMocks,
Expand All @@ -62,9 +43,20 @@ const meta: Meta<typeof App> = {
export default meta;
export type Story = StoryObj<typeof App>;

export const Default: Story = {};
export const Default: Story = {
play: async () => {
jest
.spyOn(indexAppPath, 'getIndexAppPath')
.mockReturnValue('iframe.html' as AppPath);
},
};

export const DarkMode: Story = {
play: async () => {
jest
.spyOn(indexAppPath, 'getIndexAppPath')
.mockReturnValue('iframe.html' as AppPath);
},
parameters: {
msw: {
handlers: [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,60 @@
import { getOperationName } from '@apollo/client/utilities';
import { Meta, StoryObj } from '@storybook/react';
import { graphql, HttpResponse } from 'msw';
import { ComponentDecorator } from 'twenty-ui';

import { Calendar } from '@/activities/calendar/components/Calendar';
import { getTimelineCalendarEventsFromCompanyId } from '@/activities/calendar/queries/getTimelineCalendarEventsFromCompanyId';
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { mockedTimelineCalendarEvents } from '~/testing/mock-data/timeline-calendar-events';

const meta: Meta<typeof Calendar> = {
title: 'Modules/Activities/Calendar/Calendar',
component: Calendar,
decorators: [ComponentDecorator, SnackBarDecorator],
decorators: [
ComponentDecorator,
ObjectMetadataItemsDecorator,
SnackBarDecorator,
],
parameters: {
container: { width: 728 },
msw: graphqlMocks,
msw: {
handlers: [
...graphqlMocks.handlers,
graphql.query(
getOperationName(getTimelineCalendarEventsFromCompanyId) ?? '',
({ variables }) => {
if (variables.page > 1) {
return HttpResponse.json({
data: {
getTimelineCalendarEventsFromCompanyId: {
__typename: 'TimelineCalendarEventsWithTotal',
totalNumberOfCalendarEvents: 3,
timelineCalendarEvents: [],
},
},
});
}
return HttpResponse.json({
data: {
getTimelineCalendarEventsFromCompanyId: {
__typename: 'TimelineCalendarEventsWithTotal',
totalNumberOfCalendarEvents: 3,
timelineCalendarEvents: mockedTimelineCalendarEvents,
},
},
});
},
),
],
},
},
args: {
targetableObject: {
id: '1',
targetObjectNameSingular: 'Person',
targetObjectNameSingular: 'Company',
},
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableE
import { ObjectFilterDropdownScope } from '@/object-record/object-filter-dropdown/scopes/ObjectFilterDropdownScope';
import { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/ComponentWithRecoilScopeDecorator';
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { mockedTasks } from '~/testing/mock-data/activities';
Expand All @@ -21,10 +22,10 @@ const meta: Meta<typeof TaskGroups> = {
),
ComponentWithRouterDecorator,
ComponentWithRecoilScopeDecorator,
ObjectMetadataItemsDecorator,
SnackBarDecorator,
],
parameters: {
msw: graphqlMocks,
customRecoilScopeContext: TasksRecoilScopeContext,
},
};
Expand All @@ -43,4 +44,7 @@ export const WithTasks: Story = {
},
] as ActivityTargetableObject[],
},
parameters: {
msw: graphqlMocks,
},
};
12 changes: 12 additions & 0 deletions packages/twenty-front/src/modules/navigation/utils/indexAppPath.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { AppPath } from '@/types/AppPath';

const getIndexAppPath = () => {
return AppPath.Index;
};

// This file is using the default export pattern to be compatible
// with the way it is imported in the tests.
// Otherwise we cannot mock it: https://github.com/jestjs/jest/issues/12145 as we are using ES native modules
// TBH: I am not a big fan of this pattern, alternatively we could set a global variable or a recoilState
// to store the value
export default { getIndexAppPath };
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export type FieldMetadataItem = Omit<
})
| null;
defaultValue?: any;
options?: FieldMetadataItemOption[];
options?: FieldMetadataItemOption[] | null;
relationDefinition?: {
relationId: RelationDefinition['relationId'];
direction: RelationDefinitionType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const fieldMetadataItemSchema = z.object({
value: z.string().trim().min(1),
}),
)
.nullable()
.optional(),
relationDefinition: z
.object({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export const SettingsDataModelFieldPreview = ({
objectMetadataNameSingular: objectMetadataItem.nameSingular,
relationObjectMetadataNameSingular:
relationObjectMetadataItem?.nameSingular,
options: fieldMetadataItem.options,
options: fieldMetadataItem.options ?? [],
},
defaultValue: fieldMetadataItem.defaultValue,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const meta: Meta<PageDecoratorArgs> = {
RelationPickerDecorator,
],
args: {
routePath: 'toto-not-found',
routePath: '/toto-not-found',
},
parameters: {
msw: graphqlMocks,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,64 @@
import { MemoryRouter } from 'react-router-dom';
import {
createMemoryRouter,
createRoutesFromElements,
Outlet,
Route,
RouterProvider,
} from 'react-router-dom';
import { Decorator } from '@storybook/react';

import {
computeLocation,
isRouteParams,
} from '~/testing/decorators/PageDecorator';

import { ComponentStorybookLayout } from '../ComponentStorybookLayout';

export const ComponentWithRouterDecorator: Decorator = (Story) => (
interface StrictArgs {
[name: string]: unknown;
}

const Providers = () => (
<ComponentStorybookLayout>
<MemoryRouter>
<Story />
</MemoryRouter>
<Outlet />
</ComponentStorybookLayout>
);

const createRouter = ({
Story,
args,
initialEntries,
initialIndex,
}: {
Story: () => JSX.Element;
args: StrictArgs;
initialEntries?: {
pathname: string;
}[];
initialIndex?: number;
}) =>
createMemoryRouter(
createRoutesFromElements(
<Route element={<Providers />}>
<Route path={(args.routePath as string) ?? '*'} element={<Story />} />
</Route>,
),
{ initialEntries, initialIndex },
);

export const ComponentWithRouterDecorator: Decorator = (Story, { args }) => {
return (
<RouterProvider
router={createRouter({
Story,
args,
initialEntries:
args.routePath &&
typeof args.routePath === 'string' &&
(args.routeParams === undefined || isRouteParams(args.routeParams))
? [computeLocation(args.routePath, args.routeParams)]
: [{ pathname: '/' }],
})}
/>
);
};
Loading

0 comments on commit 36b467d

Please sign in to comment.