[TEST] Covering useDeleteOne relations optimistic cache behavior (#10238)

## Introduction
Added coverage on the `useDeleteOneRecord` hooks, especially its
optimistic behavior feature.
Introduced a new testing tool `InMemoryTestingCacheInstance` that has
builtin very basic expectors in order to avoid future duplication when
covering others record hooks `update, create, destroy` etc etc

## Notes
Added few comments in this PR regarding some builtin functions I've
created around companies and people mocked object model and that I think
could be cool to spread and centralize within a dedicated "class
template"

Also put in light that unless I'm mistaken some tests are running on
`RecordNode` and not `RecordObject`

Took few directions on my own that as I always I would suggestion nor
remarks on them !

Let me know

## Misc
- Should we refactor `useDeleteOneRecord` tests to follow `eachTesting`
pattern ? => I feel like this is inappropriate as this hooks is already
high level, the only plus value would be less tests code despite
readability IMO
This commit is contained in:
Paul Rastoin
2025-03-03 10:22:26 +01:00
committed by GitHub
parent c6e5238d71
commit 2e4c596644
30 changed files with 2989 additions and 289 deletions

View File

@ -0,0 +1,113 @@
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
import { computeDepthOneRecordGqlFieldsFromRecord } from '@/object-record/graphql/utils/computeDepthOneRecordGqlFieldsFromRecord';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { InMemoryCache, NormalizedCacheObject } from '@apollo/client';
import { expect } from '@storybook/jest';
import { isDefined } from 'twenty-shared';
type ObjectMetadataItemAndRecordId = {
recordId: string;
objectMetadataItem: ObjectMetadataItem;
};
type RecordsWithObjectMetadataItem = {
records: ObjectRecord[];
objectMetadataItem: ObjectMetadataItem;
}[];
type GetMockCachedRecord = {
recordId: string;
objectMetadataItem: ObjectMetadataItem;
matchObject?: Record<string, unknown>;
snapshotPropertyMatchers?: {
deletedAt?: any;
updatedAt?: any;
createdAt?: any;
};
};
type InMemoryTestingCacheInstanceArgs = {
objectMetadataItems: ObjectMetadataItem[];
initialRecordsInCache?: RecordsWithObjectMetadataItem;
};
export class InMemoryTestingCacheInstance {
private _cache: InMemoryCache;
private objectMetadataItems: ObjectMetadataItem[];
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: computeDepthOneRecordGqlFieldsFromRecord({
objectMetadataItem,
record,
}),
}),
),
);
};
public assertCachedRecordIsNull = ({
objectMetadataItem,
recordId,
}: ObjectMetadataItemAndRecordId) => {
const cachedRecord = getRecordFromCache({
cache: this._cache,
objectMetadataItem,
objectMetadataItems: this.objectMetadataItems,
recordId,
});
expect(cachedRecord).toBeNull();
};
public assertCachedRecordMatchSnapshot = ({
objectMetadataItem,
recordId,
matchObject,
snapshotPropertyMatchers,
}: GetMockCachedRecord) => {
const cachedRecord = getRecordFromCache({
cache: this._cache,
objectMetadataItem,
objectMetadataItems: this.objectMetadataItems,
recordId,
});
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;
}
}