Files
twenty/packages/twenty-front/src/testing/cache/inMemoryTestingCacheInstance.ts
T
Charles Bochet d5a7dec117 refactor: rename ObjectMetadataItem to EnrichedObjectMetadataItem and clean up metadata flows (#18830)
## Summary

- Renames `ObjectMetadataItem` to `EnrichedObjectMetadataItem` across
the entire frontend (~440 files) to clarify that this type includes
derived fields (`readableFields`, `updatableFields`, nested `fields[]`,
`indexMetadatas[]`) computed at read time from the metadata store
- Creates `splitObjectMetadataGqlResponse` that goes directly from a
GraphQL `ObjectMetadataItemsQuery` response to flat store items
(combining the old
`mapPaginatedObjectMetadataItemsToObjectMetadataItems` +
`splitObjectMetadataItemWithRelated` two-step flow into one call)
- Removes `ObjectMetadataItemWithRelated` type and all "WithRelated"
naming
- Renames `generatedMockObjectMetadataItems` to
`generateTestEnrichedObjectMetadataItemsMock` to make it clear this is
test-only enriched data
- Deletes `useLoadMockedObjectMetadataItems` hook (consolidated into
`useLoadMockedMinimalMetadata`)
- Ensures nothing destined for the metadata store computes
`readableFields`/`updatableFields` (preventing the localStorage bloat
from #18809)

## Type hierarchy (before → after)

**Before:**
```
ObjectMetadataItemsQuery → mapPaginated → ObjectMetadataItemWithRelated → enrich → ObjectMetadataItem
                                        → split → FlatObjectMetadataItem (store)
```

**After:**
```
ObjectMetadataItemsQuery → splitObjectMetadataGqlResponse → FlatObjectMetadataItem (store)
                         → mapPaginated + enrich (tests only) → EnrichedObjectMetadataItem
```

## Test plan

- [x] `npx nx typecheck twenty-front` passes
- [x] `npx nx test twenty-front` passes (767 suites, 4505 tests)
- [x] `npx nx lint twenty-front` passes
- [ ] CI checks pass


Made with [Cursor](https://cursor.com)
2026-03-22 15:53:47 +01:00

119 lines
3.6 KiB
TypeScript

import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
import { generateDepthRecordGqlFieldsFromRecord } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromRecord';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { InMemoryCache, type NormalizedCacheObject } from '@apollo/client';
import { isDefined } from 'twenty-shared/utils';
type ObjectMetadataItemAndRecordId = {
recordId: string;
objectMetadataItem: EnrichedObjectMetadataItem;
};
type RecordsWithObjectMetadataItem = {
records: ObjectRecord[];
objectMetadataItem: EnrichedObjectMetadataItem;
}[];
type GetMockCachedRecord = {
recordId: string;
objectMetadataItem: EnrichedObjectMetadataItem;
matchObject?: Record<string, unknown>;
snapshotPropertyMatchers?: {
deletedAt?: any;
updatedAt?: any;
createdAt?: any;
};
};
type InMemoryTestingCacheInstanceArgs = {
objectMetadataItems: EnrichedObjectMetadataItem[];
initialRecordsInCache?: RecordsWithObjectMetadataItem;
};
export class InMemoryTestingCacheInstance {
private _cache: InMemoryCache;
private objectMetadataItems: EnrichedObjectMetadataItem[];
private initialStateExtract: NormalizedCacheObject;
constructor({
objectMetadataItems,
initialRecordsInCache = [],
}: InMemoryTestingCacheInstanceArgs) {
this.objectMetadataItems = objectMetadataItems;
this._cache = new InMemoryCache();
this.populateRecordsInCache(initialRecordsInCache);
this.initialStateExtract = this._cache.extract();
}
public populateRecordsInCache = (
recordsWithObjectMetadataItem: RecordsWithObjectMetadataItem,
) => {
recordsWithObjectMetadataItem.forEach(({ objectMetadataItem, records }) =>
records.forEach((record) =>
updateRecordFromCache({
cache: this._cache,
objectMetadataItem,
objectMetadataItems: this.objectMetadataItems,
record,
recordGqlFields: generateDepthRecordGqlFieldsFromRecord({
objectMetadataItems: this.objectMetadataItems,
depth: 1,
objectMetadataItem,
record,
}),
objectPermissionsByObjectMetadataId: {},
}),
),
);
};
public assertCachedRecordIsNull = ({
objectMetadataItem,
recordId,
}: ObjectMetadataItemAndRecordId) => {
const cachedRecord = getRecordFromCache({
cache: this._cache,
objectMetadataItem,
objectMetadataItems: this.objectMetadataItems,
recordId,
objectPermissionsByObjectMetadataId: {},
});
expect(cachedRecord).toBeNull();
};
public assertCachedRecordMatchSnapshot = ({
objectMetadataItem,
recordId,
matchObject,
snapshotPropertyMatchers,
}: GetMockCachedRecord) => {
const cachedRecord = getRecordFromCache({
cache: this._cache,
objectMetadataItem,
objectMetadataItems: this.objectMetadataItems,
recordId,
objectPermissionsByObjectMetadataId: {},
});
expect(cachedRecord).not.toBeNull();
if (cachedRecord === null) {
throw new Error('Should never occurs, cachedRecord is null');
}
if (isDefined(matchObject)) {
expect(cachedRecord).toMatchObject(matchObject);
}
expect(cachedRecord).toMatchSnapshot(snapshotPropertyMatchers ?? {});
};
public restoreCacheToInitialState = async () => {
return this._cache.restore(this.initialStateExtract);
};
public get cache() {
return this._cache;
}
}