Improve test tooling (#18259)

## Summary

Unifies test mocking tooling across Jest and Storybook, replaces
handcrafted mock data with auto-generated server-fetched data, and
restructures the mock data generation script for maintainability.

### Mock data generation

- Split `generate-mock-data.ts` into three focused modules under
`scripts/mock-data/`:
  - `utils.ts` — shared authentication, GraphQL client, and file writer
  - `generate-metadata.ts` — fetches object metadata from `/metadata`
- `generate-record-data.ts` — fetches record data from `/graphql` using
metadata-driven dynamic queries
- The orchestrator (`generate-mock-data.ts`) authenticates once and
passes the token to both generators
- Company records are now fetched from the actual server (limited to 10
records) instead of being handcrafted
- Generated files are organized under `generated/metadata/objects/` and
`generated/data/companies/`

### Unified test utilities

- Consolidated Jest and MSW mocking into shared utilities that compose
production code (`prefillRecord`, `getRecordNodeFromRecord`,
`getRecordConnectionFromRecords`) with mock metadata
- Renamed `generateEmptyJestRecordNode` → `generateMockRecordNode` and
moved to `testing/utils/`
- Extracted `generateMockRecordConnection` into its own file
- Removed `sanitizeInputForPrefill` workaround (no longer needed with
correctly shaped generated data)
This commit is contained in:
Charles Bochet
2026-02-26 14:31:54 +01:00
committed by GitHub
parent 2f0103faa5
commit 8a7a19f312
33 changed files with 23068 additions and 21543 deletions
+2 -1
View File
@@ -1,2 +1,3 @@
src/generated
src/generated-metadata
src/generated-metadata
src/testing/mock-data/generated
@@ -1,6 +1,10 @@
import { setProjectAnnotations } from '@storybook/react-vite';
import * as projectAnnotations from './preview';
// Pre-warm the dynamic import used by WorkflowStepDecorator so the
// module is cached before any test runs (avoids flaky timeouts in CI).
import('~/testing/utils/generatedMockObjectMetadataItems');
// This is an important step to apply the right configuration when testing your stories.
// More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations
setProjectAnnotations([projectAnnotations]);
+1 -1
View File
@@ -259,7 +259,7 @@
"executor": "nx:run-commands",
"options": {
"cwd": "{projectRoot}",
"command": "dotenv npx tsx scripts/generate-mock-data.ts"
"command": "dotenv npx vite-node scripts/generate-mock-data.ts"
}
},
"chromatic": {
@@ -1,281 +1,15 @@
/* eslint-disable no-console */
import * as fs from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'url';
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
const SERVER_BASE_URL =
process.env.REACT_APP_SERVER_BASE_URL ?? 'http://localhost:3000';
const AUTH_EMAIL = 'tim@apple.dev';
const AUTH_PASSWORD = 'tim@apple.dev';
const WORKSPACE_SUBDOMAIN = 'apple';
const serverUrl = new URL(SERVER_BASE_URL);
const WORKSPACE_ORIGIN = `${serverUrl.protocol}//${WORKSPACE_SUBDOMAIN}.${serverUrl.host}`;
const currentDir = path.dirname(fileURLToPath(import.meta.url));
const OUTPUT_DIR = path.resolve(
currentDir,
'../src/testing/mock-data/generated',
);
const graphqlRequest = async (
endpoint: string,
query: string,
token?: string,
): Promise<unknown> => {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
Origin: WORKSPACE_ORIGIN,
};
if (token !== undefined) {
headers['Authorization'] = `Bearer ${token}`;
}
const response = await fetch(`${SERVER_BASE_URL}${endpoint}`, {
method: 'POST',
headers,
body: JSON.stringify({ query }),
});
const json = (await response.json()) as {
data?: unknown;
errors?: { message: string }[];
};
if (
json.errors !== undefined &&
json.errors !== null &&
json.errors.length > 0
) {
const errorDetails = json.errors.map((error) => error.message).join(', ');
throw new Error(`GraphQL error on ${endpoint}: ${errorDetails}`);
}
return json.data;
};
const authenticate = async (): Promise<string> => {
console.log(
`Authenticating as ${AUTH_EMAIL} on workspace ${WORKSPACE_SUBDOMAIN}...`,
);
const loginData = (await graphqlRequest(
'/metadata',
`mutation GetLoginTokenFromCredentials {
getLoginTokenFromCredentials(
email: "${AUTH_EMAIL}",
password: "${AUTH_PASSWORD}",
origin: "${WORKSPACE_ORIGIN}"
) {
loginToken { token }
}
}`,
)) as {
getLoginTokenFromCredentials: { loginToken: { token: string } };
};
const loginToken = loginData.getLoginTokenFromCredentials.loginToken.token;
const authData = (await graphqlRequest(
'/metadata',
`mutation GetAuthTokensFromLoginToken {
getAuthTokensFromLoginToken(
loginToken: "${loginToken}",
origin: "${WORKSPACE_ORIGIN}"
) {
tokens {
accessOrWorkspaceAgnosticToken { token }
}
}
}`,
)) as {
getAuthTokensFromLoginToken: {
tokens: { accessOrWorkspaceAgnosticToken: { token: string } };
};
};
const accessToken =
authData.getAuthTokensFromLoginToken.tokens.accessOrWorkspaceAgnosticToken
.token;
console.log('Authenticated successfully.');
return accessToken;
};
// Apollo Client automatically adds __typename to every object level;
// raw fetch does not, so we include it explicitly here.
const METADATA_QUERY = `
query ObjectMetadataItems {
objects(paging: { first: 1000 }) {
__typename
edges {
__typename
node {
__typename
id
universalIdentifier
nameSingular
namePlural
labelSingular
labelPlural
description
icon
isCustom
isRemote
isActive
isSystem
isUIReadOnly
createdAt
updatedAt
labelIdentifierFieldMetadataId
imageIdentifierFieldMetadataId
applicationId
shortcut
isLabelSyncedWithName
isSearchable
duplicateCriteria
indexMetadataList {
__typename
id
createdAt
updatedAt
name
indexWhereClause
indexType
isUnique
isCustom
indexFieldMetadataList {
__typename
id
fieldMetadataId
createdAt
updatedAt
order
}
}
fieldsList {
__typename
id
universalIdentifier
type
name
label
description
icon
isCustom
isActive
isSystem
isUIReadOnly
isNullable
isUnique
createdAt
updatedAt
defaultValue
options
settings
isLabelSyncedWithName
morphId
applicationId
relation {
__typename
type
sourceObjectMetadata {
__typename
id
nameSingular
namePlural
}
targetObjectMetadata {
__typename
id
nameSingular
namePlural
}
sourceFieldMetadata {
__typename
id
name
}
targetFieldMetadata {
__typename
id
name
}
}
morphRelations {
__typename
type
sourceObjectMetadata {
__typename
id
nameSingular
namePlural
}
targetObjectMetadata {
__typename
id
nameSingular
namePlural
}
sourceFieldMetadata {
__typename
id
name
}
targetFieldMetadata {
__typename
id
name
}
}
}
}
}
pageInfo {
__typename
hasNextPage
hasPreviousPage
startCursor
endCursor
}
}
}
`;
import { generateMetadata } from './mock-data/generate-metadata.js';
import { generateRecordData } from './mock-data/generate-record-data.js';
import { authenticate } from './mock-data/utils.js';
const main = async () => {
console.log(`Server: ${SERVER_BASE_URL}`);
console.log(`Output: ${OUTPUT_DIR}`);
console.log('');
const token = await authenticate();
console.log('Fetching object metadata from /metadata ...');
const metadata = await graphqlRequest('/metadata', METADATA_QUERY, token);
const metadata = await generateMetadata(token);
await generateRecordData(token, metadata);
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
const filePath = path.join(OUTPUT_DIR, 'mock-metadata-query-result.ts');
const content = [
'/* eslint-disable */',
'// @ts-nocheck',
"import { ObjectMetadataItemsQuery } from '~/generated-metadata/graphql';",
'',
'// This file was automatically generated by scripts/generate-mock-data.ts',
'// Do not edit this file manually.',
'',
'// prettier-ignore',
'export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =',
JSON.stringify(metadata, null, 2) + ';',
'',
].join('\n');
fs.writeFileSync(filePath, content, 'utf-8');
console.log(`Written: ${filePath}`);
console.log('Done!');
console.log('All mock data generated!');
};
main().catch((error) => {
@@ -0,0 +1,160 @@
/* eslint-disable no-console */
import { graphqlRequest, writeGeneratedFile } from './utils.js';
// Apollo Client automatically adds __typename to every object level;
// raw fetch does not, so we include it explicitly here.
const METADATA_QUERY = `
query ObjectMetadataItems {
objects(paging: { first: 1000 }) {
__typename
edges {
__typename
node {
__typename
id
universalIdentifier
nameSingular
namePlural
labelSingular
labelPlural
description
icon
isCustom
isRemote
isActive
isSystem
isUIReadOnly
createdAt
updatedAt
labelIdentifierFieldMetadataId
imageIdentifierFieldMetadataId
applicationId
shortcut
isLabelSyncedWithName
isSearchable
duplicateCriteria
indexMetadataList {
__typename
id
createdAt
updatedAt
name
indexWhereClause
indexType
isUnique
isCustom
indexFieldMetadataList {
__typename
id
fieldMetadataId
createdAt
updatedAt
order
}
}
fieldsList {
__typename
id
universalIdentifier
type
name
label
description
icon
isCustom
isActive
isSystem
isUIReadOnly
isNullable
isUnique
createdAt
updatedAt
defaultValue
options
settings
isLabelSyncedWithName
morphId
applicationId
relation {
__typename
type
sourceObjectMetadata {
__typename
id
nameSingular
namePlural
}
targetObjectMetadata {
__typename
id
nameSingular
namePlural
}
sourceFieldMetadata {
__typename
id
name
}
targetFieldMetadata {
__typename
id
name
}
}
morphRelations {
__typename
type
sourceObjectMetadata {
__typename
id
nameSingular
namePlural
}
targetObjectMetadata {
__typename
id
nameSingular
namePlural
}
sourceFieldMetadata {
__typename
id
name
}
targetFieldMetadata {
__typename
id
name
}
}
}
}
}
pageInfo {
__typename
hasNextPage
hasPreviousPage
startCursor
endCursor
}
}
}
`;
export const generateMetadata = async (token: string) => {
console.log('Fetching object metadata from /metadata ...');
const metadata = await graphqlRequest('/metadata', METADATA_QUERY, token);
writeGeneratedFile(
'metadata/objects/mock-objects-metadata.ts',
'mockedStandardObjectMetadataQueryResult',
'ObjectMetadataItemsQuery',
"import { ObjectMetadataItemsQuery } from '~/generated-metadata/graphql';",
metadata,
);
return metadata as {
objects: { edges: { node: Record<string, unknown> }[] };
};
};
@@ -0,0 +1,85 @@
/* eslint-disable no-console */
import { print } from 'graphql';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { generateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromObject';
import { generateFindManyRecordsQuery } from '@/object-record/utils/generateFindManyRecordsQuery';
import { graphqlRequest, writeGeneratedFile } from './utils.js';
const RECORDS_LIMIT = 10;
// Production query builders omit __typename on connection/edge wrappers
// since Apollo Client injects them automatically. Raw fetch needs them explicit.
const addTypenamesToSelections = (query: string): string =>
query.replace(/\{(?!\s*__typename\b)/g, '{\n__typename');
const toObjectMetadataItems = (rawMetadata: {
objects: { edges: { node: Record<string, unknown> }[] };
}): ObjectMetadataItem[] =>
rawMetadata.objects.edges.map((edge) => {
const { fieldsList, indexMetadataList, ...rest } = edge.node;
return {
...rest,
fields: fieldsList,
readableFields: fieldsList,
updatableFields: fieldsList,
indexMetadatas: ((indexMetadataList as unknown[]) ?? []).map(
(index: any) => ({
...index,
indexFieldMetadatas: index.indexFieldMetadataList ?? [],
}),
),
} as unknown as ObjectMetadataItem;
});
export const generateRecordData = async (
token: string,
rawMetadata: { objects: { edges: { node: Record<string, unknown> }[] } },
) => {
const objectMetadataItems = toObjectMetadataItems(rawMetadata);
const companyObject = objectMetadataItems.find(
(item) => item.nameSingular === 'company',
);
if (!companyObject) {
throw new Error('Company object metadata not found');
}
const recordGqlFields = generateDepthRecordGqlFieldsFromObject({
objectMetadataItems,
objectMetadataItem: companyObject,
depth: 1,
});
const queryDocument = generateFindManyRecordsQuery({
objectMetadataItem: companyObject,
objectMetadataItems,
recordGqlFields,
objectPermissionsByObjectMetadataId: {},
});
const query = addTypenamesToSelections(print(queryDocument));
console.log(
`Fetching ${companyObject.namePlural} (limit: ${RECORDS_LIMIT}) from /graphql ...`,
);
const data = (await graphqlRequest('/graphql', query, token, {
limit: RECORDS_LIMIT,
})) as Record<string, { edges: { node: Record<string, unknown> }[] }>;
const records = data[companyObject.namePlural].edges.map((edge) => edge.node);
console.log(` Got ${records.length} ${companyObject.namePlural}.`);
writeGeneratedFile(
'data/companies/mock-companies-data.ts',
'mockedCompanyRecords',
'Record<string, unknown>[]',
'',
records,
);
};
@@ -0,0 +1,134 @@
/* eslint-disable no-console */
import * as fs from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'url';
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
const SERVER_BASE_URL =
process.env.REACT_APP_SERVER_BASE_URL ?? 'http://localhost:3000';
const AUTH_EMAIL = 'jane.austen@apple.dev';
const AUTH_PASSWORD = 'tim@apple.dev';
const WORKSPACE_SUBDOMAIN = 'apple';
const serverUrl = new URL(SERVER_BASE_URL);
export const WORKSPACE_ORIGIN = `${serverUrl.protocol}//${WORKSPACE_SUBDOMAIN}.${serverUrl.host}`;
const currentDir = path.dirname(fileURLToPath(import.meta.url));
export const GENERATED_DIR = path.resolve(
currentDir,
'../../src/testing/mock-data/generated',
);
export const graphqlRequest = async (
endpoint: string,
query: string,
token?: string,
variables?: Record<string, unknown>,
): Promise<unknown> => {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
Origin: WORKSPACE_ORIGIN,
};
if (token !== undefined) {
headers['Authorization'] = `Bearer ${token}`;
}
const response = await fetch(`${SERVER_BASE_URL}${endpoint}`, {
method: 'POST',
headers,
body: JSON.stringify({ query, variables }),
});
const json = (await response.json()) as {
data?: unknown;
errors?: { message: string }[];
};
if (
json.errors !== undefined &&
json.errors !== null &&
json.errors.length > 0
) {
const errorDetails = json.errors.map((error) => error.message).join(', ');
throw new Error(`GraphQL error on ${endpoint}: ${errorDetails}`);
}
return json.data;
};
export const authenticate = async (): Promise<string> => {
console.log(
`Authenticating as ${AUTH_EMAIL} on workspace ${WORKSPACE_SUBDOMAIN}...`,
);
const loginData = (await graphqlRequest(
'/metadata',
`mutation GetLoginTokenFromCredentials {
getLoginTokenFromCredentials(
email: "${AUTH_EMAIL}",
password: "${AUTH_PASSWORD}",
origin: "${WORKSPACE_ORIGIN}"
) {
loginToken { token }
}
}`,
)) as {
getLoginTokenFromCredentials: { loginToken: { token: string } };
};
const loginToken = loginData.getLoginTokenFromCredentials.loginToken.token;
const authData = (await graphqlRequest(
'/metadata',
`mutation GetAuthTokensFromLoginToken {
getAuthTokensFromLoginToken(
loginToken: "${loginToken}",
origin: "${WORKSPACE_ORIGIN}"
) {
tokens {
accessOrWorkspaceAgnosticToken { token }
}
}
}`,
)) as {
getAuthTokensFromLoginToken: {
tokens: { accessOrWorkspaceAgnosticToken: { token: string } };
};
};
const accessToken =
authData.getAuthTokensFromLoginToken.tokens.accessOrWorkspaceAgnosticToken
.token;
console.log('Authenticated successfully.');
return accessToken;
};
export const writeGeneratedFile = (
relativePath: string,
exportName: string,
typeName: string,
typeImport: string,
data: unknown,
) => {
const filePath = path.join(GENERATED_DIR, relativePath);
fs.mkdirSync(path.dirname(filePath), { recursive: true });
const content = [
'/* eslint-disable */',
'// @ts-nocheck',
typeImport,
'',
'// This file was automatically generated — do not edit manually.',
'',
'// prettier-ignore',
`export const ${exportName}: ${typeName} =`,
JSON.stringify(data, null, 2) + ';',
'',
].join('\n');
fs.writeFileSync(filePath, content, 'utf-8');
console.log(`Written: ${filePath}`);
};
@@ -11,7 +11,7 @@ import {
QUERY_MAX_RECORDS,
} from 'twenty-shared/constants';
import { MessageParticipantRole } from 'twenty-shared/types';
import { generateEmptyJestRecordNode } from '~/testing/jest/generateEmptyJestRecordNode';
import { generateMockRecordNode } from '~/testing/utils/generateMockRecordNode';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
@@ -84,7 +84,7 @@ const mocks = [
messages: {
edges: [
{
node: generateEmptyJestRecordNode({
node: generateMockRecordNode({
objectNameSingular: 'message',
input: {
id: '1',
@@ -95,7 +95,7 @@ const mocks = [
cursor: '1',
},
{
node: generateEmptyJestRecordNode({
node: generateMockRecordNode({
objectNameSingular: 'message',
input: {
id: '2',
@@ -135,7 +135,7 @@ const mocks = [
messageParticipants: {
edges: [
{
node: generateEmptyJestRecordNode({
node: generateMockRecordNode({
objectNameSingular: 'messageParticipant',
input: {
id: 'messageParticipant-1',
@@ -146,7 +146,7 @@ const mocks = [
cursor: '1',
},
{
node: generateEmptyJestRecordNode({
node: generateMockRecordNode({
objectNameSingular: 'messageParticipant',
input: {
id: 'messageParticipant-2',
@@ -1,5 +1,5 @@
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queries';
import { mockedStandardObjectMetadataQueryResult } from '~/testing/mock-data/generated/mock-metadata-query-result';
import { mockedStandardObjectMetadataQueryResult } from '~/testing/mock-data/generated/metadata/objects/mock-objects-metadata';
export const query = FIND_MANY_OBJECT_METADATA_ITEMS;
@@ -1,13 +1,16 @@
import { isAppEffectRedirectEnabledState } from '@/app/states/isAppEffectRedirectEnabledState';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { useCallback } from 'react';
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { useStore } from 'jotai';
export const useLoadMockedObjectMetadataItems = () => {
const store = useStore();
const loadMockedObjectMetadataItems = useCallback(async () => {
const { generatedMockObjectMetadataItems } = await import(
'~/testing/utils/generatedMockObjectMetadataItems'
);
if (
!isDeeplyEqual(
store.get(objectMetadataItemsState.atom),
@@ -1,12 +1,15 @@
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql';
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
import { type RecordGqlFields } from '@/object-record/graphql/record-gql-fields/types/RecordGqlFields';
import { isNonCompositeField } from '@/object-record/object-filter-dropdown/utils/isNonCompositeField';
import { type ObjectPermissions } from 'twenty-shared/types';
import {
FieldMetadataType,
type ObjectPermissions,
RelationType,
} from 'twenty-shared/types';
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
type MapFieldMetadataToGraphQLQueryArgs = {
@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`generateActivityTargetGqlFields snapshot tests should match snapshot for Note with loadRelations="activity" 1`] = `
{
@@ -5,7 +5,7 @@ import {
variables,
} from '@/object-record/hooks/__mocks__/useFindOneRecord';
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { generateEmptyJestRecordNode } from '~/testing/jest/generateEmptyJestRecordNode';
import { generateMockRecordNode } from '~/testing/utils/generateMockRecordNode';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
const mocks = [
@@ -16,7 +16,7 @@ const mocks = [
},
result: jest.fn(() => ({
data: {
person: generateEmptyJestRecordNode({
person: generateMockRecordNode({
objectNameSingular: 'person',
input: { id: '6205681e-7c11-40b4-9e32-f523dbe54590' },
withDepthOneRelation: true,
@@ -12,7 +12,7 @@ import {
type RecordUpdateHookParams,
} from '@/object-record/record-field/ui/contexts/FieldContext';
import { useToggleEditOnlyInput } from '@/object-record/record-field/ui/hooks/useToggleEditOnlyInput';
import { generateEmptyJestRecordNode } from '~/testing/jest/generateEmptyJestRecordNode';
import { generateMockRecordNode } from '~/testing/utils/generateMockRecordNode';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
@@ -39,7 +39,7 @@ const mocks: MockedResponse[] = [
result: jest.fn(() => ({
data: {
updateCompany: {
...generateEmptyJestRecordNode({
...generateMockRecordNode({
objectNameSingular: CoreObjectNameSingular.Company,
input: { id: recordId },
withDepthOneRelation: true,
@@ -27,8 +27,23 @@ import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSe
import { FieldMetadataType } from 'twenty-shared/types';
import { RelationManyToOneFieldInput } from '@/object-record/record-field/ui/meta-types/input/components/RelationManyToOneFieldInput';
import { getCompaniesMock } from '~/testing/mock-data/companies';
import { getMockFieldMetadataItemOrThrow } from '~/testing/utils/getMockFieldMetadataItemOrThrow';
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
import { getFieldInputEventContextProviderWithJestMocks } from './utils/getFieldInputEventContextProviderWithJestMocks';
const personMetadata = getMockObjectMetadataItemOrThrow('person');
const companyMetadata = getMockObjectMetadataItemOrThrow('company');
const companyFieldOnPerson = getMockFieldMetadataItemOrThrow({
objectMetadataItem: personMetadata,
fieldName: 'company',
});
const peopleFieldOnCompany = getMockFieldMetadataItemOrThrow({
objectMetadataItem: companyMetadata,
fieldName: 'people',
});
const companiesMock = getCompaniesMock();
const RelationWorkspaceSetterEffect = () => {
const setRecordFieldInputLayoutDirectionLoading = useSetAtomComponentState(
recordFieldInputLayoutDirectionLoadingComponentState,
@@ -75,7 +90,7 @@ const RelationManyToOneFieldInputWithContext = ({
<FieldContext.Provider
value={{
fieldDefinition: {
fieldMetadataId: 'f6be42ac-ccb8-4df0-8c22-a9627d655c76',
fieldMetadataId: companyFieldOnPerson.id,
label: 'Company',
type: FieldMetadataType.RELATION,
iconName: 'IconLink',
@@ -84,9 +99,9 @@ const RelationManyToOneFieldInputWithContext = ({
relationObjectMetadataNamePlural: 'companies',
relationObjectMetadataNameSingular:
CoreObjectNameSingular.Company,
relationObjectMetadataId: '69e0aa5d-a9c5-486b-a99d-fe191195a19d',
relationObjectMetadataId: companyMetadata.id,
objectMetadataNameSingular: 'person',
relationFieldMetadataId: '94902a74-b8ca-4a56-8573-7469f0b664f6',
relationFieldMetadataId: peopleFieldOnCompany.id,
},
},
recordId: recordId,
@@ -154,7 +169,7 @@ export const Submit: Story = {
expect(handleSubmitMocked).toHaveBeenCalledTimes(0);
const item = await canvas.findByText('Linkedin', undefined, {
const item = await canvas.findByText(companiesMock[0].name, undefined, {
timeout: 3000,
});
@@ -171,7 +186,9 @@ export const Cancel: Story = {
const canvas = within(canvasElement);
expect(handleCancelMocked).toHaveBeenCalledTimes(0);
await canvas.findByText('Linkedin', undefined, { timeout: 3000 });
await canvas.findByText(companiesMock[0].name, undefined, {
timeout: 3000,
});
const emptyDiv = canvas.getByTestId('data-field-input-click-outside-div');
@@ -24,6 +24,19 @@ import { recordStoreFamilySelector } from '@/object-record/record-store/states/s
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { FieldMetadataType } from 'twenty-shared/types';
import { RelationType } from '~/generated-metadata/graphql';
import { getMockFieldMetadataItemOrThrow } from '~/testing/utils/getMockFieldMetadataItemOrThrow';
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
const personMetadata = getMockObjectMetadataItemOrThrow('person');
const companyMetadata = getMockObjectMetadataItemOrThrow('company');
const companyFieldOnPerson = getMockFieldMetadataItemOrThrow({
objectMetadataItem: personMetadata,
fieldName: 'company',
});
const peopleFieldOnCompany = getMockFieldMetadataItemOrThrow({
objectMetadataItem: companyMetadata,
fieldName: 'people',
});
const RelationWorkspaceSetterEffect = () => {
useEffect(() => {
@@ -39,7 +52,7 @@ const RelationOneToManyFieldInputWithContext = () => {
const fieldDefinition = useMemo(
() => ({
fieldMetadataId: '94902a74-b8ca-4a56-8573-7469f0b664f6',
fieldMetadataId: peopleFieldOnCompany.id,
label: 'People',
type: FieldMetadataType.RELATION,
iconName: 'IconLink',
@@ -49,7 +62,7 @@ const RelationOneToManyFieldInputWithContext = () => {
relationObjectMetadataNamePlural: 'people',
relationObjectMetadataNameSingular: CoreObjectNameSingular.Person,
objectMetadataNameSingular: 'company',
relationFieldMetadataId: 'f6be42ac-ccb8-4df0-8c22-a9627d655c76',
relationFieldMetadataId: companyFieldOnPerson.id,
},
}),
[],
@@ -61,7 +61,7 @@ describe('computeViewRecordGqlOperationFilter', () => {
expect(result).toEqual({
name: {
ilike: '%Linkedin%',
ilike: `%${companiesMock[0].name}%`,
},
});
});
@@ -116,7 +116,7 @@ describe('computeViewRecordGqlOperationFilter', () => {
and: [
{
name: {
ilike: '%Linkedin%',
ilike: `%${companiesMock[0].name}%`,
},
},
{
@@ -16,8 +16,8 @@ import { useRecordIndexContextOrThrow } from '@/object-record/record-index/conte
import { hasUserSelectedAllRowsComponentState } from '@/object-record/record-table/record-table-row/states/hasUserSelectedAllRowsFamilyState';
import { selectedRowIdsComponentSelector } from '@/object-record/record-table/states/selectors/selectedRowIdsComponentSelector';
import { unselectedRowIdsComponentSelector } from '@/object-record/record-table/states/selectors/unselectedRowIdsComponentSelector';
import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
import { useAtomComponentSelectorCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorCallbackState';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { atom, useStore } from 'jotai';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
@@ -42,17 +42,17 @@ export const RecordIndexFiltersToContextStoreEffect = () => {
recordIndexId,
);
const hasUserSelectedAllRowsAtom = useAtomComponentStateCallbackState(
const hasUserSelectedAllRows = useAtomComponentStateValue(
hasUserSelectedAllRowsComponentState,
recordIndexId,
);
const selectedRowIdsAtom = useAtomComponentSelectorCallbackState(
const selectedRowIds = useAtomComponentSelectorValue(
selectedRowIdsComponentSelector,
recordIndexId,
);
const unselectedRowIdsAtom = useAtomComponentSelectorCallbackState(
const unselectedRowIds = useAtomComponentSelectorValue(
unselectedRowIdsComponentSelector,
recordIndexId,
);
@@ -88,17 +88,14 @@ export const RecordIndexFiltersToContextStoreEffect = () => {
anyFieldFilterValue: string;
},
) => {
const hasUserSelectedAllRows = get(hasUserSelectedAllRowsAtom);
let newRule: ContextStoreTargetedRecordsRule;
if (hasUserSelectedAllRows) {
const unselectedRowIds = get(unselectedRowIdsAtom);
newRule = {
mode: 'exclusion',
excludedRecordIds: unselectedRowIds,
};
} else {
const selectedRowIds = get(selectedRowIdsAtom);
newRule = {
mode: 'selection',
selectedRecordIds: selectedRowIds,
@@ -132,9 +129,9 @@ export const RecordIndexFiltersToContextStoreEffect = () => {
},
),
[
hasUserSelectedAllRowsAtom,
selectedRowIdsAtom,
unselectedRowIdsAtom,
hasUserSelectedAllRows,
selectedRowIds,
unselectedRowIds,
contextStoreTargetedRecordsRuleAtom,
contextStoreFiltersAtom,
contextStoreFilterGroupsAtom,
@@ -321,6 +321,10 @@ export const useMultipleRecordPickerPerformSearch = () => {
},
}));
if (operationSignatures.length === 0) {
return;
}
performCombinedFindManyRecords({ operationSignatures }).then(
({ result }) => {
Object.values(result)
@@ -10,10 +10,13 @@ import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorato
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
import { RecordTableDecorator } from '~/testing/decorators/RecordTableDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
import { getCompaniesMock } from '~/testing/mock-data/companies';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { mockedViewsData } from '~/testing/mock-data/views';
import { sleep } from '~/utils/sleep';
const companiesMock = getCompaniesMock();
const meta: Meta = {
title: 'Modules/ObjectRecord/RecordTable/RecordTable',
component: RecordTableWithWrappers,
@@ -70,7 +73,7 @@ export const ScrolledLeft: Story = {
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await canvas.findAllByText('Linkedin', {}, { timeout: 3000 });
await canvas.findAllByText(companiesMock[0].name, {}, { timeout: 3000 });
const scrollWrapper = canvasElement.ownerDocument.body.querySelector(
'.scroll-wrapper-x-enabled',
@@ -88,7 +91,7 @@ export const ScrolledLeft: Story = {
},
});
await canvas.findByText('Facebook');
await canvas.findByText(companiesMock[1].name);
},
};
@@ -100,7 +103,7 @@ export const ScrolledBottom: Story = {
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await canvas.findAllByText('Linkedin', {}, { timeout: 3000 });
await canvas.findAllByText(companiesMock[0].name, {}, { timeout: 3000 });
const scrollWrapper = canvasElement.ownerDocument.body.querySelector(
'.scroll-wrapper-y-enabled',
@@ -118,6 +121,6 @@ export const ScrolledBottom: Story = {
},
});
await canvas.findByText('Facebook');
await canvas.findByText(companiesMock[1].name);
},
};
@@ -4,9 +4,9 @@ import { type ColumnDefinition } from '@/object-record/record-table/types/Column
import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns';
import { findByProperty } from 'twenty-shared/utils';
import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql';
import { getMockCompanyObjectMetadataItem } from '~/testing/mock-data/companies';
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
const COMPANY_MOCK_OBJECT = getMockCompanyObjectMetadataItem();
const COMPANY_MOCK_OBJECT = getMockObjectMetadataItemOrThrow('company');
export const SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS = (
[
@@ -12,7 +12,13 @@ import { PageDragDropProvider } from '@/navigation/components/PageDragDropProvid
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
import { OBJECT_SETTINGS_WIDTH } from '@/settings/data-model/constants/ObjectSettings';
import { SignInAppNavigationDrawerMock } from '@/sign-in-background-mock/components/SignInAppNavigationDrawerMock';
import { SignInBackgroundMockPage } from '@/sign-in-background-mock/components/SignInBackgroundMockPage';
import { lazy, Suspense } from 'react';
const SignInBackgroundMockPage = lazy(() =>
import('@/sign-in-background-mock/components/SignInBackgroundMockPage').then(
(module) => ({ default: module.SignInBackgroundMockPage }),
),
);
import { useShowFullscreen } from '@/ui/layout/fullscreen/hooks/useShowFullscreen';
import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal';
import { NAVIGATION_DRAWER_CONSTRAINTS } from '@/ui/layout/resizable-panel/constants/NavigationDrawerConstraints';
@@ -106,7 +112,9 @@ export const DefaultLayout = () => {
{showAuthModal ? (
<>
<StyledMainContainer>
<SignInBackgroundMockPage />
<Suspense fallback={null}>
<SignInBackgroundMockPage />
</Suspense>
</StyledMainContainer>
<AnimatePresence mode="wait">
<LayoutGroup>
@@ -1,5 +1,11 @@
import { SignInBackgroundMockPage } from '@/sign-in-background-mock/components/SignInBackgroundMockPage';
import { Trans, useLingui } from '@lingui/react/macro';
import { lazy, Suspense } from 'react';
const SignInBackgroundMockPage = lazy(() =>
import('@/sign-in-background-mock/components/SignInBackgroundMockPage').then(
(module) => ({ default: module.SignInBackgroundMockPage }),
),
);
import { AppPath } from 'twenty-shared/types';
import { RootStackingContextZIndices } from '@/ui/layout/constants/RootStackingContextZIndices';
@@ -61,7 +67,9 @@ export const NotFound = () => {
</StyledButtonContainer>
</AnimatedPlaceholderErrorContainer>
</StyledBackDrop>
<SignInBackgroundMockPage />
<Suspense fallback={null}>
<SignInBackgroundMockPage />
</Suspense>
</>
);
};
@@ -10,69 +10,75 @@ import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
import { useStepsOutputSchema } from '@/workflow/workflow-variables/hooks/useStepsOutputSchema';
import { type Decorator } from '@storybook/react-vite';
import { useStore } from 'jotai';
import { useCallback, useEffect, useState } from 'react';
import { useAtomValue, useStore } from 'jotai';
import { useEffect, useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
import {
getWorkflowMock,
getWorkflowNodeIdMock,
mockedWorkflow,
mockedWorkflowNodeId,
mockedWorkflowVersion,
} from '~/testing/mock-data/workflow';
export const WorkflowStepDecorator: Decorator = (Story) => {
const workflowVisualizerComponentInstanceId = 'workflow-visualizer-test-id';
const workflowVersion = getWorkflowMock().versions.edges[0]
.node as WorkflowVersion;
const workflowVersion = mockedWorkflowVersion as WorkflowVersion;
const { populateStepsOutputSchema } = useStepsOutputSchema();
const { loadMockedObjectMetadataItems } = useLoadMockedObjectMetadataItems();
const [ready, setReady] = useState(false);
const store = useStore();
const handleMount = useCallback(async () => {
await loadMockedObjectMetadataItems();
useEffect(() => {
const setup = async () => {
await loadMockedObjectMetadataItems();
store.set(
workflowVisualizerWorkflowIdComponentState.atomFamily({
instanceId: workflowVisualizerComponentInstanceId,
}),
getWorkflowMock().id,
);
store.set(
workflowVisualizerWorkflowVersionIdComponentState.atomFamily({
instanceId: workflowVisualizerComponentInstanceId,
}),
workflowVersion.id,
);
store.set(
workflowVisualizerWorkflowRunIdComponentState.atomFamily({
instanceId: workflowVisualizerComponentInstanceId,
}),
'123',
);
store.set(
workflowSelectedNodeComponentState.atomFamily({
instanceId: workflowVisualizerComponentInstanceId,
}),
getWorkflowNodeIdMock(),
);
store.set(
flowComponentState.atomFamily({
instanceId: workflowVisualizerComponentInstanceId,
}),
{
workflowVersionId: workflowVersion.id,
trigger: workflowVersion.trigger,
steps: workflowVersion.steps,
},
);
store.set(
commandMenuWorkflowIdComponentState.atomFamily({
instanceId: workflowVisualizerComponentInstanceId,
}),
getWorkflowMock().id,
);
populateStepsOutputSchema(workflowVersion);
setReady(true);
store.set(
workflowVisualizerWorkflowIdComponentState.atomFamily({
instanceId: workflowVisualizerComponentInstanceId,
}),
mockedWorkflow.id,
);
store.set(
workflowVisualizerWorkflowVersionIdComponentState.atomFamily({
instanceId: workflowVisualizerComponentInstanceId,
}),
workflowVersion.id,
);
store.set(
workflowVisualizerWorkflowRunIdComponentState.atomFamily({
instanceId: workflowVisualizerComponentInstanceId,
}),
'123',
);
store.set(
workflowSelectedNodeComponentState.atomFamily({
instanceId: workflowVisualizerComponentInstanceId,
}),
mockedWorkflowNodeId,
);
store.set(
flowComponentState.atomFamily({
instanceId: workflowVisualizerComponentInstanceId,
}),
{
workflowVersionId: workflowVersion.id,
trigger: workflowVersion.trigger,
steps: workflowVersion.steps,
},
);
store.set(
commandMenuWorkflowIdComponentState.atomFamily({
instanceId: workflowVisualizerComponentInstanceId,
}),
mockedWorkflow.id,
);
populateStepsOutputSchema(workflowVersion);
setReady(true);
};
setup();
}, [
loadMockedObjectMetadataItems,
populateStepsOutputSchema,
@@ -80,9 +86,11 @@ export const WorkflowStepDecorator: Decorator = (Story) => {
store,
]);
useEffect(() => {
handleMount();
}, [handleMount]);
const workflowVersionId = useAtomValue(
workflowVisualizerWorkflowVersionIdComponentState.atomFamily({
instanceId: workflowVisualizerComponentInstanceId,
}),
);
return (
<CommandMenuPageComponentInstanceContext.Provider
@@ -95,7 +103,7 @@ export const WorkflowStepDecorator: Decorator = (Story) => {
instanceId: workflowVisualizerComponentInstanceId,
}}
>
{ready && <Story />}
{ready && isDefined(workflowVersionId) && <Story />}
</WorkflowVisualizerComponentInstanceContext.Provider>
</CommandMenuPageComponentInstanceContext.Provider>
);
+59 -131
View File
@@ -26,7 +26,7 @@ import { LIST_PLANS } from '@/billing/graphql/queries/listPlans';
import { GET_ROLES } from '@/settings/roles/graphql/queries/getRolesQuery';
import { isDefined } from 'twenty-shared/utils';
import { mockBillingPlans } from '~/testing/mock-data/billing-plans';
import { mockedStandardObjectMetadataQueryResult } from '~/testing/mock-data/generated/mock-metadata-query-result';
import { mockedStandardObjectMetadataQueryResult } from '~/testing/mock-data/generated/metadata/objects/mock-objects-metadata';
import { getRolesMock } from '~/testing/mock-data/roles';
import { mockedTasks } from '~/testing/mock-data/tasks';
import {
@@ -35,12 +35,31 @@ import {
workflowQueryResult,
} from '~/testing/mock-data/workflow';
import { oneSucceededWorkflowRunQueryResult } from '~/testing/mock-data/workflow-run';
import { getConnectionTypename } from '@/object-record/cache/utils/getConnectionTypename';
import { getEdgeTypename } from '@/object-record/cache/utils/getEdgeTypename';
import { getEmptyPageInfo } from '@/object-record/cache/utils/getEmptyPageInfo';
import { mockedViewFieldsData } from './mock-data/view-fields';
const peopleMock = getPeopleRecordConnectionMock();
const companiesMock = getCompaniesRecordConnectionMock();
const duplicateCompanyMock = getCompanyDuplicateMock();
// Wraps raw server-fetched records (which already have correct field shapes)
// into a GraphQL connection response structure.
const wrapRecordsAsConnection = (
objectNameSingular: string,
records: Record<string, unknown>[],
) => ({
__typename: getConnectionTypename(objectNameSingular),
edges: records.map((node) => ({
__typename: getEdgeTypename(objectNameSingular),
node,
cursor: '',
})),
pageInfo: getEmptyPageInfo(),
totalCount: records.length,
});
const getRootFieldNamesFromQuery = (query: string) => {
try {
const document = parse(query);
@@ -201,62 +220,48 @@ export const graphqlMocks = {
});
}),
graphql.query('Search', () => {
const personSearchEdges = peopleMock
.slice(0, 2)
.map((person: Record<string, unknown>, index: number) => ({
node: {
__typename: 'SearchRecordDTO',
recordId: person.id,
objectNameSingular: 'person',
objectLabelSingular: 'Person',
label:
`${(person.name as Record<string, string>)?.firstName ?? ''} ${(person.name as Record<string, string>)?.lastName ?? ''}`.trim(),
imageUrl: '',
tsRankCD: 0.2,
tsRank: 0.12158542,
},
cursor: `cursor-${index + 1}`,
}));
const companySearchEdges = companiesMock
.slice(0, 2)
.map((company: Record<string, unknown>, index: number) => ({
node: {
__typename: 'SearchRecordDTO',
recordId: company.id,
objectNameSingular: 'company',
objectLabelSingular: 'Company',
label: company.name,
imageUrl: '',
tsRankCD: 0.2,
tsRank: 0.12158542,
},
cursor: `cursor-${personSearchEdges.length + index + 1}`,
}));
const allEdges = [...personSearchEdges, ...companySearchEdges];
return HttpResponse.json({
data: {
search: {
edges: [
{
node: {
__typename: 'SearchRecordDTO',
recordId: '20202020-2d40-4e49-8df4-9c6a049191de',
objectNameSingular: 'person',
label: 'Louis Duss',
imageUrl: '',
tsRankCD: 0.2,
tsRank: 0.12158542,
},
cursor: 'cursor-1',
},
{
node: {
__typename: 'SearchRecordDTO',
recordId: '20202020-3ec3-4fe3-8997-b76aa0bfa408',
objectNameSingular: 'company',
label: 'Linkedin',
imageUrl: 'https://twenty-icons.com/linkedin.com',
tsRankCD: 0.2,
tsRank: 0.12158542,
},
cursor: 'cursor-2',
},
{
node: {
__typename: 'SearchRecordDTO',
recordId: '20202020-3f74-492d-a101-2a70f50a1645',
objectNameSingular: 'company',
label: 'Libeo',
imageUrl: 'https://twenty-icons.com/libeo.io',
tsRankCD: 0.2,
tsRank: 0.12158542,
},
cursor: 'cursor-3',
},
{
node: {
__typename: 'SearchRecordDTO',
recordId: '20202020-ac73-4797-824e-87a1f5aea9e0',
objectNameSingular: 'person',
label: 'Sylvie Palmer',
imageUrl: '',
tsRankCD: 0.1,
tsRank: 0.06079271,
},
cursor: 'cursor-4',
},
],
edges: allEdges,
pageInfo: {
hasNextPage: true,
endCursor: 'cursor-4',
endCursor: allEdges[allEdges.length - 1]?.cursor ?? null,
},
},
},
@@ -390,45 +395,7 @@ export const graphqlMocks = {
return HttpResponse.json({
data: {
companies: {
edges: mockedData.map((company) => ({
node: {
...company,
favorites: {
edges: [],
__typename: 'FavoriteConnection',
},
attachments: {
edges: [],
__typename: 'AttachmentConnection',
},
people: {
edges: [],
__typename: 'PersonConnection',
},
opportunities: {
edges: [],
__typename: 'OpportunityConnection',
},
taskTargets: {
edges: [],
__typename: 'TaskTargetConnection',
},
noteTargets: {
edges: [],
__typename: 'NoteTargetConnection',
},
},
cursor: null,
})),
pageInfo: {
hasNextPage: false,
hasPreviousPage: false,
startCursor: null,
endCursor: null,
},
totalCount: mockedData.length,
},
companies: wrapRecordsAsConnection('company', mockedData),
},
});
}),
@@ -436,46 +403,7 @@ export const graphqlMocks = {
return HttpResponse.json({
data: {
companyDuplicates: [
{
edges: [
{
node: {
...duplicateCompanyMock,
favorites: {
edges: [],
__typename: 'FavoriteConnection',
},
attachments: {
edges: [],
__typename: 'AttachmentConnection',
},
people: {
edges: [],
__typename: 'PersonConnection',
},
opportunities: {
edges: [],
__typename: 'OpportunityConnection',
},
taskTargets: {
edges: [],
__typename: 'TaskTargetConnection',
},
noteTargets: {
edges: [],
__typename: 'NoteTargetConnection',
},
},
cursor: null,
},
],
pageInfo: {
hasNextPage: false,
hasPreviousPage: false,
startCursor: null,
endCursor: null,
},
},
wrapRecordsAsConnection('company', [duplicateCompanyMock]),
],
},
});
@@ -1,784 +1,21 @@
import { type Company } from '@/companies/types/Company';
import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { isDefined } from 'twenty-shared/utils';
import { mockedCompanyRecords } from '~/testing/mock-data/generated/data/companies/mock-companies-data';
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
export const companiesQueryResult = {
companies: {
__typename: 'CompanyConnection',
totalCount: 13,
pageInfo: {
__typename: 'PageInfo',
hasNextPage: false,
startCursor:
'WzEsICIyMDIwMjAyMC0zZWMzLTRmZTMtODk5Ny1iNzZhYTBiZmE0MDgiXQ==',
endCursor: 'WzEzLCAiMjAyMDIwMjAtMTQ1NS00YzU3LWFmYWYtZGQ1ZGMwODYzNjFkIl0=',
},
edges: [
{
__typename: 'CompanyEdge',
cursor: 'WzEsICIyMDIwMjAyMC0zZWMzLTRmZTMtODk5Ny1iNzZhYTBiZmE0MDgiXQ==',
node: {
__typename: 'Company',
id: '20202020-3ec3-4fe3-8997-b76aa0bfa408',
employees: 100,
createdAt: '2025-01-05T09:00:20.412Z',
updatedAt: '2025-01-05T10:15:30.412Z',
deletedAt: null,
name: 'Linkedin',
accountOwnerId: null,
accountOwner: null,
domainName: {
__typename: 'Links',
primaryLinkLabel: 'https://linkedin.com',
primaryLinkUrl: '',
secondaryLinks: [],
},
address: {
__typename: 'Address',
addressStreet1: '',
addressStreet2: '',
addressCity: '',
addressState: '',
addressCountry: '',
addressPostcode: '',
addressLat: null,
addressLng: null,
},
position: 1,
idealCustomerProfile: true,
linkedinLink: {
__typename: 'Links',
primaryLinkLabel: '',
primaryLinkUrl: '',
secondaryLinks: [],
},
xLink: {
__typename: 'Links',
primaryLinkLabel: '',
primaryLinkUrl: '',
secondaryLinks: [],
},
annualRecurringRevenue: {
__typename: 'Currency',
amountMicros: null,
currencyCode: '',
},
previousEmployees: {
__typename: 'Person',
id: '20202020-2d40-4e49-8df4-9c6a049191de',
email: 'louis.duss@google.com',
position: 14,
testJson: null,
testRating: null,
companyId: '20202020-c21e-4ec2-873b-de4264d89025',
avatarUrl: '',
updatedAt: '2025-01-05T11:36:42.400Z',
testMultiSelect: null,
testBoolean: true,
testSelect: 'OPTION_1',
testDateOnly: null,
bestCompanyId: null,
testUuid: null,
phone: '+33788901234',
createdAt: '2025-01-05T09:00:20.412Z',
city: 'Seattle',
testPhone: '',
jobTitle: 'CTO',
testCurrency: {
__typename: 'Currency',
amountMicros: null,
currencyCode: 'USD',
},
xLink: {
__typename: 'Links',
primaryLinkLabel: '',
primaryLinkUrl: 'twitter.com',
},
testLinks: {
__typename: 'Links',
primaryLinkUrl: '',
primaryLinkLabel: '',
secondaryLinks: [],
},
name: {
__typename: 'FullName',
firstName: 'Louis',
lastName: 'Duss',
},
linkedinLink: {
__typename: 'Links',
primaryLinkLabel: '',
primaryLinkUrl: 'linkedin.com',
},
testAddress: {
__typename: 'Address',
addressStreet1: '',
addressStreet2: '',
addressCity: '',
addressState: '',
addressCountry: '',
addressPostcode: '',
addressLat: null,
addressLng: null,
},
testLink: {
__typename: 'Links',
primaryLinkLabel: '',
primaryLinkUrl: '',
},
},
},
},
{
__typename: 'CompanyEdge',
cursor: 'WzIsICIyMDIwMjAyMC01ZDgxLTQ2ZDYtYmY4My1mN2ZkMzNlYTYxMDIiXQ==',
node: {
__typename: 'Company',
id: '20202020-5d81-46d6-bf83-f7fd33ea6102',
employees: null,
createdAt: '2025-01-06T08:30:15.412Z',
updatedAt: '2025-01-06T14:45:22.412Z',
deletedAt: null,
name: 'Facebook',
idealCustomerProfile: false,
accountOwner: null,
accountOwnerId: null,
domainName: {
__typename: 'Links',
primaryLinkUrl: 'https://facebook.com',
primaryLinkLabel: '',
secondaryLinks: [],
},
address: {
__typename: 'Address',
addressStreet1: '',
addressStreet2: '',
addressCity: '',
addressState: '',
addressCountry: '',
addressPostcode: '',
addressLat: null,
addressLng: null,
},
previousEmployees: null,
annualRecurringRevenue: {
__typename: 'Currency',
amountMicros: null,
currencyCode: '',
},
position: 2,
xLink: {
__typename: 'Links',
primaryLinkLabel: '',
primaryLinkUrl: '',
secondaryLinks: [],
},
linkedinLink: {
__typename: 'Links',
primaryLinkLabel: '',
primaryLinkUrl: '',
secondaryLinks: [],
},
},
},
{
__typename: 'CompanyEdge',
cursor: 'WzMsICIyMDIwMjAyMC0wNzEzLTQwYTUtODIxNi04MjgwMjQwMWQzM2UiXQ==',
node: {
__typename: 'Company',
id: '20202020-0713-40a5-8216-82802401d33e',
employees: null,
createdAt: '2025-01-07T10:15:30.412Z',
updatedAt: '2025-01-07T16:20:45.412Z',
deletedAt: null,
name: 'Anthropic',
idealCustomerProfile: false,
accountOwner: null,
accountOwnerId: null,
domainName: {
__typename: 'Links',
primaryLinkUrl: 'https://anthropic.com',
primaryLinkLabel: '',
secondaryLinks: [],
},
address: {
__typename: 'Address',
addressStreet1: '',
addressStreet2: '',
addressCity: '',
addressState: '',
addressCountry: '',
addressPostcode: '',
addressLat: null,
addressLng: null,
},
previousEmployees: null,
annualRecurringRevenue: {
__typename: 'Currency',
amountMicros: null,
currencyCode: '',
},
position: 3,
xLink: {
__typename: 'Links',
primaryLinkLabel: '',
primaryLinkUrl: '',
secondaryLinks: [],
},
linkedinLink: {
__typename: 'Links',
primaryLinkLabel: '',
primaryLinkUrl: '',
secondaryLinks: [],
},
},
},
{
__typename: 'CompanyEdge',
cursor: 'WzQsICIyMDIwMjAyMC1lZDg5LTQxM2EtYjMxYS05NjI5ODZlNjdiYjQiXQ==',
node: {
__typename: 'Company',
id: '20202020-ed89-413a-b31a-962986e67bb4',
employees: null,
createdAt: '2025-01-08T11:45:10.412Z',
updatedAt: '2025-01-08T17:30:25.412Z',
deletedAt: null,
name: 'Microsoft',
idealCustomerProfile: true,
accountOwner: null,
accountOwnerId: null,
domainName: {
__typename: 'Links',
primaryLinkUrl: 'https://microsoft.com',
primaryLinkLabel: '',
secondaryLinks: [],
},
address: {
__typename: 'Address',
addressStreet1: '',
addressStreet2: '',
addressCity: '',
addressState: '',
addressCountry: '',
addressPostcode: '',
addressLat: null,
addressLng: null,
},
previousEmployees: null,
annualRecurringRevenue: {
__typename: 'Currency',
amountMicros: null,
currencyCode: '',
},
position: 4,
xLink: {
__typename: 'Links',
primaryLinkLabel: '',
primaryLinkUrl: '',
secondaryLinks: [],
},
linkedinLink: {
__typename: 'Links',
primaryLinkLabel: '',
primaryLinkUrl: '',
secondaryLinks: [],
},
},
},
{
__typename: 'CompanyEdge',
cursor: 'WzUsICIyMDIwMjAyMC0xNzFlLTRiY2MtOWNmNy00MzQ0OGQ2ZmIyNzgiXQ==',
node: {
__typename: 'Company',
id: '20202020-171e-4bcc-9cf7-43448d6fb278',
employees: null,
createdAt: '2025-01-09T09:20:45.412Z',
updatedAt: '2025-01-09T15:10:30.412Z',
deletedAt: null,
name: 'Airbnb',
idealCustomerProfile: true,
accountOwner: null,
accountOwnerId: null,
domainName: {
__typename: 'Links',
primaryLinkUrl: 'https://airbnb.com',
primaryLinkLabel: '',
secondaryLinks: [],
},
address: {
__typename: 'Address',
addressStreet1: '',
addressStreet2: '',
addressCity: '',
addressState: '',
addressCountry: '',
addressPostcode: '',
addressLat: null,
addressLng: null,
},
previousEmployees: null,
annualRecurringRevenue: {
__typename: 'Currency',
amountMicros: null,
currencyCode: '',
},
position: 5,
xLink: {
__typename: 'Links',
primaryLinkLabel: '',
primaryLinkUrl: '',
secondaryLinks: [],
},
linkedinLink: {
__typename: 'Links',
primaryLinkLabel: '',
primaryLinkUrl: '',
secondaryLinks: [],
},
},
},
{
__typename: 'CompanyEdge',
cursor: 'WzYsICIyMDIwMjAyMC1jMjFlLTRlYzItODczYi1kZTQyNjRkODkwMjUiXQ==',
node: {
__typename: 'Company',
id: '20202020-c21e-4ec2-873b-de4264d89025',
employees: null,
createdAt: '2025-01-10T13:30:20.412Z',
updatedAt: '2025-01-10T18:45:35.412Z',
deletedAt: null,
name: 'Google',
idealCustomerProfile: false,
accountOwner: null,
accountOwnerId: null,
domainName: {
__typename: 'Links',
primaryLinkUrl: 'https://google.com',
primaryLinkLabel: '',
secondaryLinks: [],
},
address: {
__typename: 'Address',
addressStreet1: '',
addressStreet2: '',
addressCity: '',
addressState: '',
addressCountry: '',
addressPostcode: '',
addressLat: null,
addressLng: null,
},
previousEmployees: null,
annualRecurringRevenue: {
__typename: 'Currency',
amountMicros: null,
currencyCode: '',
},
position: 6,
xLink: {
__typename: 'Links',
primaryLinkLabel: '',
primaryLinkUrl: '',
secondaryLinks: [],
},
linkedinLink: {
__typename: 'Links',
primaryLinkLabel: '',
primaryLinkUrl: '',
secondaryLinks: [],
},
},
},
{
__typename: 'CompanyEdge',
cursor: 'WzcsICIyMDIwMjAyMC03MDdlLTQ0ZGMtYTFkMi0zMDAzMGJmMWE5NDQiXQ==',
node: {
__typename: 'Company',
id: '20202020-707e-44dc-a1d2-30030bf1a944',
employees: null,
createdAt: '2025-01-11T07:15:50.412Z',
updatedAt: '2025-01-11T12:25:15.412Z',
deletedAt: null,
name: 'Netflix',
idealCustomerProfile: true,
accountOwner: null,
accountOwnerId: null,
domainName: {
__typename: 'Links',
primaryLinkUrl: 'https://netflix.com',
primaryLinkLabel: '',
secondaryLinks: [],
},
address: {
__typename: 'Address',
addressStreet1: '',
addressStreet2: '',
addressCity: '',
addressState: '',
addressCountry: '',
addressPostcode: '',
addressLat: null,
addressLng: null,
},
previousEmployees: null,
annualRecurringRevenue: {
__typename: 'Currency',
amountMicros: null,
currencyCode: '',
},
position: 7,
xLink: {
__typename: 'Links',
primaryLinkLabel: '',
primaryLinkUrl: '',
secondaryLinks: [],
},
linkedinLink: {
__typename: 'Links',
primaryLinkLabel: '',
primaryLinkUrl: '',
secondaryLinks: [],
},
},
},
{
__typename: 'CompanyEdge',
cursor: 'WzgsICIyMDIwMjAyMC0zZjc0LTQ5MmQtYTEwMS0yYTcwZjUwYTE2NDUiXQ==',
node: {
__typename: 'Company',
id: '20202020-3f74-492d-a101-2a70f50a1645',
employees: null,
createdAt: '2025-01-12T14:40:25.412Z',
updatedAt: '2025-01-12T19:55:40.412Z',
deletedAt: null,
name: 'Libeo',
idealCustomerProfile: false,
accountOwner: null,
accountOwnerId: null,
domainName: {
__typename: 'Links',
primaryLinkUrl: 'https://libeo.io',
primaryLinkLabel: '',
secondaryLinks: [],
},
address: {
__typename: 'Address',
addressStreet1: '',
addressStreet2: '',
addressCity: '',
addressState: '',
addressCountry: '',
addressPostcode: '',
addressLat: null,
addressLng: null,
},
previousEmployees: null,
annualRecurringRevenue: {
__typename: 'Currency',
amountMicros: null,
currencyCode: '',
},
position: 8,
xLink: {
__typename: 'Links',
primaryLinkLabel: '',
primaryLinkUrl: '',
secondaryLinks: [],
},
linkedinLink: {
__typename: 'Links',
primaryLinkLabel: '',
primaryLinkUrl: '',
secondaryLinks: [],
},
},
},
{
__typename: 'CompanyEdge',
cursor: 'WzksICIyMDIwMjAyMC1jZmJmLTQxNTYtYTc5MC1lMzk4NTRkY2Q0ZWIiXQ==',
node: {
__typename: 'Company',
id: '20202020-cfbf-4156-a790-e39854dcd4eb',
employees: null,
createdAt: '2025-01-13T08:25:35.412Z',
updatedAt: '2025-01-13T13:40:50.412Z',
deletedAt: null,
name: 'Claap',
idealCustomerProfile: false,
accountOwner: null,
accountOwnerId: null,
domainName: {
__typename: 'Links',
primaryLinkUrl: 'https://claap.com',
primaryLinkLabel: '',
secondaryLinks: [],
},
address: {
__typename: 'Address',
addressStreet1: '',
addressStreet2: '',
addressCity: '',
addressState: '',
addressCountry: '',
addressPostcode: '',
addressLat: null,
addressLng: null,
},
previousEmployees: null,
annualRecurringRevenue: {
__typename: 'Currency',
amountMicros: null,
currencyCode: '',
},
position: 9,
xLink: {
__typename: 'Links',
primaryLinkLabel: '',
primaryLinkUrl: '',
secondaryLinks: [],
},
linkedinLink: {
__typename: 'Links',
primaryLinkLabel: '',
primaryLinkUrl: '',
secondaryLinks: [],
},
},
},
{
__typename: 'CompanyEdge',
cursor: 'WzEwLCAiMjAyMDIwMjAtZjg2Yi00MTlmLWI3OTQtMDIzMTlhYmU4NjM3Il0=',
node: {
__typename: 'Company',
id: '20202020-f86b-419f-b794-02319abe8637',
employees: null,
createdAt: '2025-01-14T16:10:15.412Z',
updatedAt: '2025-01-14T21:30:25.412Z',
deletedAt: null,
name: 'Hasura',
idealCustomerProfile: false,
accountOwner: null,
accountOwnerId: null,
domainName: {
__typename: 'Links',
primaryLinkUrl: 'https://hasura.io',
primaryLinkLabel: '',
secondaryLinks: [],
},
address: {
__typename: 'Address',
addressStreet1: '',
addressStreet2: '',
addressCity: '',
addressState: '',
addressCountry: '',
addressPostcode: '',
addressLat: null,
addressLng: null,
},
previousEmployees: null,
annualRecurringRevenue: {
__typename: 'Currency',
amountMicros: null,
currencyCode: '',
},
position: 10,
xLink: {
__typename: 'Links',
primaryLinkLabel: '',
primaryLinkUrl: '',
secondaryLinks: [],
},
linkedinLink: {
__typename: 'Links',
primaryLinkLabel: '',
primaryLinkUrl: '',
secondaryLinks: [],
},
},
},
{
__typename: 'CompanyEdge',
cursor: 'WzExLCAiMjAyMDIwMjAtNTUxOC00NTUzLTk0MzMtNDJkOGViODI4MzRiIl0=',
node: {
__typename: 'Company',
id: '20202020-5518-4553-9433-42d8eb82834b',
employees: null,
createdAt: '2025-01-15T12:50:40.412Z',
updatedAt: '2025-01-15T17:15:55.412Z',
deletedAt: null,
name: 'Wework',
idealCustomerProfile: false,
accountOwner: null,
accountOwnerId: null,
domainName: {
__typename: 'Links',
primaryLinkUrl: 'https://wework.com',
primaryLinkLabel: '',
secondaryLinks: [],
},
address: {
__typename: 'Address',
addressStreet1: '',
addressStreet2: '',
addressCity: '',
addressState: '',
addressCountry: '',
addressPostcode: '',
addressLat: null,
addressLng: null,
},
previousEmployees: null,
annualRecurringRevenue: {
__typename: 'Currency',
amountMicros: null,
currencyCode: '',
},
position: 11,
xLink: {
__typename: 'Links',
primaryLinkLabel: '',
primaryLinkUrl: '',
secondaryLinks: [],
},
linkedinLink: {
__typename: 'Links',
primaryLinkLabel: '',
primaryLinkUrl: '',
secondaryLinks: [],
},
},
},
{
__typename: 'CompanyEdge',
cursor: 'WzEyLCAiMjAyMDIwMjAtZjc5ZS00MGRkLWJkMDYtYzM2ZTZhYmI0Njc4Il0=',
node: {
__typename: 'Company',
id: '20202020-f79e-40dd-bd06-c36e6abb4678',
employees: null,
createdAt: '2025-01-16T10:35:20.412Z',
updatedAt: '2025-01-16T15:50:35.412Z',
deletedAt: null,
name: 'Samsung',
idealCustomerProfile: false,
accountOwner: null,
accountOwnerId: null,
domainName: {
__typename: 'Links',
primaryLinkUrl: 'https://samsung.com',
primaryLinkLabel: '',
secondaryLinks: [],
},
address: {
__typename: 'Address',
addressStreet1: '',
addressStreet2: '',
addressCity: '',
addressState: '',
addressCountry: '',
addressPostcode: '',
addressLat: null,
addressLng: null,
},
previousEmployees: null,
annualRecurringRevenue: {
__typename: 'Currency',
amountMicros: null,
currencyCode: '',
},
position: 12,
xLink: {
__typename: 'Links',
primaryLinkLabel: '',
primaryLinkUrl: '',
secondaryLinks: [],
},
linkedinLink: {
__typename: 'Links',
primaryLinkLabel: '',
primaryLinkUrl: '',
secondaryLinks: [],
},
},
},
{
__typename: 'CompanyEdge',
cursor: 'WzEzLCAiMjAyMDIwMjAtMTQ1NS00YzU3LWFmYWYtZGQ1ZGMwODYzNjFkIl0=',
node: {
__typename: 'Company',
id: '20202020-1455-4c57-afaf-dd5dc086361d',
employees: null,
createdAt: '2025-01-17T11:20:10.412Z',
updatedAt: '2025-01-17T16:45:25.412Z',
deletedAt: null,
name: 'Algolia',
idealCustomerProfile: false,
accountOwner: null,
accountOwnerId: null,
domainName: {
__typename: 'Links',
primaryLinkUrl: 'https://algolia.com',
primaryLinkLabel: '',
secondaryLinks: [],
},
address: {
__typename: 'Address',
addressStreet1: '',
addressStreet2: '',
addressCity: '',
addressState: '',
addressCountry: '',
addressPostcode: '',
addressLat: null,
addressLng: null,
},
previousEmployees: null,
annualRecurringRevenue: {
__typename: 'Currency',
amountMicros: null,
currencyCode: '',
},
position: 13,
xLink: {
__typename: 'Links',
primaryLinkLabel: '',
primaryLinkUrl: '',
secondaryLinks: [],
},
linkedinLink: {
__typename: 'Links',
primaryLinkLabel: '',
primaryLinkUrl: '',
secondaryLinks: [],
},
},
},
],
},
};
const allMockedCompanyRecords = getRecordsFromRecordConnection({
recordConnection: companiesQueryResult.companies,
}) as ObjectRecord[];
const allMockedCompanyRecords = mockedCompanyRecords as ObjectRecord[];
export const getCompaniesMock = () => {
return [...allMockedCompanyRecords] as Company[];
};
export const getCompaniesRecordConnectionMock = () => {
const companiesMock = companiesQueryResult.companies.edges.map(
(edge) => edge.node,
);
return companiesMock;
return [...allMockedCompanyRecords];
};
export const getMockCompanyObjectMetadataItem = () => {
const companyObjectMetadataItem = getMockObjectMetadataItemOrThrow('company');
return companyObjectMetadataItem;
return getMockObjectMetadataItemOrThrow('company');
};
export const getCompanyDuplicateMock = () => {
@@ -811,26 +48,3 @@ export const findMockCompanyRecord = ({
return company;
};
export const getEmptyCompanyMock = () => {
return {
id: '9231e6ee-4cc2-4c7b-8c55-dff16f4d968a',
name: '',
domainName: {
__typename: 'Links',
primaryLinkUrl: '',
primaryLinkLabel: '',
secondaryLinks: [],
},
address: {},
accountOwner: null,
createdAt: null,
updatedAt: null,
employees: null,
idealCustomerProfile: null,
linkedinLink: null,
xLink: null,
_activityCount: null,
__typename: 'Company',
};
};
@@ -1,4 +1,4 @@
import { companiesQueryResult } from '~/testing/mock-data/companies';
import { getCompaniesMock } from '~/testing/mock-data/companies';
import { mockedWorkspaceMemberData } from '~/testing/mock-data/users';
import { mockedViewsData } from '~/testing/mock-data/views';
@@ -18,8 +18,8 @@ export const mockedFavoritesData = [
workflowVersion: null,
forWorkspaceMemberId: mockedWorkspaceMemberData.id,
forWorkspaceMember: mockedWorkspaceMemberData,
companyId: companiesQueryResult.companies.edges[0].node.id,
company: companiesQueryResult.companies.edges[0].node,
companyId: getCompaniesMock()[0].id,
company: getCompaniesMock()[0],
viewId: null,
view: null,
taskId: null,
@@ -1,29 +1,10 @@
import { type RecordGqlConnection } from '@/object-record/graphql/types/RecordGqlConnection';
export const getWorkflowMock = () => {
return workflowQueryResult.workflows.edges[0].node;
};
export const getWorkflowVersionsMock = () => {
return {
...getWorkflowMock().versions,
__typename: 'WorkflowVersionConnection',
totalCount: 1,
pageInfo: {
__typename: 'PageInfo',
hasNextPage: false,
hasPreviousPage: false,
startCursor:
'eyJjcmVhdGVkQXQiOiIyMDI0LTA5LTE5VDEwOjEwOjA0LjcyNVoiLCJpZCI6ImY2MTg4NDNhLTI2YmUtNGE1NC1hNjBmLWY0Y2U4OGE1OTRmMCJ9',
endCursor:
'eyJjcmVhdGVkQXQiOiIyMDI0LTA5LTE5VDEwOjEwOjA0LjcyNVoiLCJpZCI6ImY2MTg4NDNhLTI2YmUtNGE1NC1hNjBmLWY0Y2U4OGE1OTRmMCJ9',
},
};
};
export const getWorkflowNodeIdMock = () => {
return getWorkflowMock().versions.edges[0].node.steps[0].id;
};
// Keep function wrappers as aliases for backward compatibility — they just
// return the pre-computed constants so callers can be migrated gradually.
export const getWorkflowMock = () => mockedWorkflow;
export const getWorkflowVersionsMock = () => mockedWorkflowVersions;
export const getWorkflowNodeIdMock = () => mockedWorkflowNodeId;
export const MOCKED_STEP_ID = '04d5f3bf-9714-400d-ba27-644006a5fb1b';
@@ -1552,3 +1533,24 @@ export const workflowQueryResult = {
],
},
} satisfies { workflows: RecordGqlConnection };
export const mockedWorkflow = workflowQueryResult.workflows.edges[0].node;
export const mockedWorkflowVersion = mockedWorkflow.versions.edges[0].node;
export const mockedWorkflowVersions = {
...mockedWorkflow.versions,
__typename: 'WorkflowVersionConnection' as const,
totalCount: 1,
pageInfo: {
__typename: 'PageInfo' as const,
hasNextPage: false,
hasPreviousPage: false,
startCursor:
'eyJjcmVhdGVkQXQiOiIyMDI0LTA5LTE5VDEwOjEwOjA0LjcyNVoiLCJpZCI6ImY2MTg4NDNhLTI2YmUtNGE1NC1hNjBmLWY0Y2U4OGE1OTRmMCJ9',
endCursor:
'eyJjcmVhdGVkQXQiOiIyMDI0LTA5LTE5VDEwOjEwOjA0LjcyNVoiLCJpZCI6ImY2MTg4NDNhLTI2YmUtNGE1NC1hNjBmLWY0Y2U4OGE1OTRmMCJ9',
},
};
export const mockedWorkflowNodeId = mockedWorkflowVersion.steps[0].id;
@@ -0,0 +1,28 @@
import { getRecordConnectionFromRecords } from '@/object-record/cache/utils/getRecordConnectionFromRecords';
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
import { generateMockRecord } from '~/testing/utils/generateMockRecordNode';
export const generateMockRecordConnection = ({
objectNameSingular,
records,
computeReferences = false,
}: {
objectNameSingular: string;
records: Record<string, unknown>[];
computeReferences?: boolean;
}) => {
const objectMetadataItem =
getMockObjectMetadataItemOrThrow(objectNameSingular);
const prefilledRecords = records.map((recordInput) =>
generateMockRecord({ objectNameSingular, input: recordInput }),
);
return getRecordConnectionFromRecords({
objectMetadataItems: generatedMockObjectMetadataItems,
objectMetadataItem,
records: prefilledRecords,
computeReferences,
});
};
@@ -1,37 +1,44 @@
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
import { generateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromObject';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { prefillRecord } from '@/object-record/utils/prefillRecord';
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
type GenerateEmptyJestRecordNodeArgs = {
objectNameSingular: string;
input: Record<string, unknown>;
withDepthOneRelation?: boolean;
};
export const generateEmptyJestRecordNode = ({
export const generateMockRecord = ({
objectNameSingular,
input,
withDepthOneRelation = false,
}: GenerateEmptyJestRecordNodeArgs) => {
}: {
objectNameSingular: string;
input: Record<string, unknown>;
}): ObjectRecord => {
const objectMetadataItem =
getMockObjectMetadataItemOrThrow(objectNameSingular);
if (!objectMetadataItem) {
throw new Error(
`ObjectMetadataItem not found for objectNameSingular: ${objectNameSingular} while generating empty Jest record node`,
);
}
return prefillRecord({ objectMetadataItem, input });
};
const prefilledRecord = prefillRecord({
objectMetadataItem,
input,
});
export const generateMockRecordNode = ({
objectNameSingular,
input,
withDepthOneRelation = false,
computeReferences,
}: {
objectNameSingular: string;
input: Record<string, unknown>;
withDepthOneRelation?: boolean;
computeReferences?: boolean;
}) => {
const objectMetadataItem =
getMockObjectMetadataItemOrThrow(objectNameSingular);
const record = generateMockRecord({ objectNameSingular, input });
return getRecordNodeFromRecord({
record: prefilledRecord,
record,
objectMetadataItem,
objectMetadataItems: generatedMockObjectMetadataItems,
computeReferences,
recordGqlFields: withDepthOneRelation
? generateDepthRecordGqlFieldsFromObject({
objectMetadataItem,
@@ -2,7 +2,7 @@ import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataIte
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { objectMetadataItemSchema } from '@/object-metadata/validation-schemas/objectMetadataItemSchema';
import { mockedStandardObjectMetadataQueryResult } from '~/testing/mock-data/generated/mock-metadata-query-result';
import { mockedStandardObjectMetadataQueryResult } from '~/testing/mock-data/generated/metadata/objects/mock-objects-metadata';
export const generatedMockObjectMetadataItems: ObjectMetadataItem[] =
mockedStandardObjectMetadataQueryResult.objects.edges.map((edge) => {