Fix Jotai state leak in frontend tests causing unbounded memory growth (#18249)
## Summary The global `jotaiStore` singleton was shared across all tests without reset, leaking state between test suites and causing unbounded heap growth within each Jest worker. - `getJestMetadataAndApolloMocksWrapper` now creates a **fresh Jotai store per wrapper** (equivalent of RecoilRoot isolation), so each test gets clean state - Added `workerIdleMemoryLimit: '512MB'` to recycle workers that still accumulate memory - Set `maxWorkers: 3` for CI ## Performance Measured on 48 test suites (command-menu + views + object-record hooks), single worker: | Metric | Before | After | |---|---|---| | Peak heap | **1519 MB** | **~530 MB** (-65%) | | Final heap | **1519 MB** | **437 MB** (-71%) | | Growth pattern | Monotonic (never freed) | Bounded sawtooth (recycled at 512MB) |
This commit is contained in:
@@ -86,7 +86,8 @@ const jestConfig = {
|
||||
'display/icon/index.ts',
|
||||
],
|
||||
coverageDirectory: './coverage',
|
||||
maxWorkers: '50%',
|
||||
maxWorkers: 3,
|
||||
workerIdleMemoryLimit: '512MB',
|
||||
errorOnDeprecated: true,
|
||||
};
|
||||
|
||||
|
||||
+7
-16
@@ -4,10 +4,7 @@ import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/
|
||||
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
||||
import { type RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import {
|
||||
jotaiStore,
|
||||
resetJotaiStore,
|
||||
} from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { resetJotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups } from '@/views/hooks/useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups';
|
||||
import { coreViewsState } from '@/views/states/coreViewState';
|
||||
import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations';
|
||||
@@ -42,10 +39,6 @@ describe('useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups', () => {
|
||||
resetJotaiStore();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jotaiStore.set(coreViewsState.atom, []);
|
||||
});
|
||||
|
||||
const mockViewFilterGroup: ViewFilterGroup = {
|
||||
__typename: 'ViewFilterGroup',
|
||||
id: 'filter-group-1',
|
||||
@@ -79,8 +72,6 @@ describe('useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups', () => {
|
||||
} satisfies CoreViewWithRelations;
|
||||
|
||||
it('should apply view filter groups from current view', () => {
|
||||
jotaiStore.set(coreViewsState.atom, [mockCoreView]);
|
||||
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const { applyCurrentViewFilterGroupsToCurrentRecordFilterGroups } =
|
||||
@@ -103,6 +94,9 @@ describe('useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups', () => {
|
||||
contextStoreCurrentViewId: mockView.id,
|
||||
contextStoreCurrentObjectMetadataNameSingular:
|
||||
mockObjectMetadataItemNameSingular,
|
||||
onInitializeJotaiStore: (store) => {
|
||||
store.set(coreViewsState.atom, [mockCoreView]);
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
@@ -130,8 +124,6 @@ describe('useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups', () => {
|
||||
});
|
||||
|
||||
it('should not apply view filter groups when current view is not found', () => {
|
||||
jotaiStore.set(coreViewsState.atom, []);
|
||||
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const { applyCurrentViewFilterGroupsToCurrentRecordFilterGroups } =
|
||||
@@ -173,10 +165,6 @@ describe('useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups', () => {
|
||||
});
|
||||
|
||||
it('should handle view with empty view filter groups', () => {
|
||||
jotaiStore.set(coreViewsState.atom, [
|
||||
{ ...mockCoreView, viewFilterGroups: [] },
|
||||
]);
|
||||
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const { applyCurrentViewFilterGroupsToCurrentRecordFilterGroups } =
|
||||
@@ -199,6 +187,9 @@ describe('useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups', () => {
|
||||
contextStoreCurrentObjectMetadataNameSingular:
|
||||
mockObjectMetadataItemNameSingular,
|
||||
onInitializeJotaiStore: (store) => {
|
||||
store.set(coreViewsState.atom, [
|
||||
{ ...mockCoreView, viewFilterGroups: [] },
|
||||
]);
|
||||
store.set(
|
||||
contextStoreCurrentViewIdComponentState.atomFamily({
|
||||
instanceId: 'instanceId',
|
||||
|
||||
+7
-14
@@ -4,10 +4,7 @@ import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import {
|
||||
jotaiStore,
|
||||
resetJotaiStore,
|
||||
} from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { resetJotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { coreViewsState } from '@/views/states/coreViewState';
|
||||
import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations';
|
||||
import { type View } from '@/views/types/View';
|
||||
@@ -44,10 +41,6 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => {
|
||||
resetJotaiStore();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jotaiStore.set(coreViewsState.atom, []);
|
||||
});
|
||||
|
||||
const allCompaniesView = mockedViewsData[0];
|
||||
const allCompaniesCoreView = mockedCoreViewsData[0];
|
||||
|
||||
@@ -90,8 +83,6 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => {
|
||||
} satisfies CoreViewWithRelations;
|
||||
|
||||
it('should apply filters from current view', () => {
|
||||
jotaiStore.set(coreViewsState.atom, [mockCoreView]);
|
||||
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const { applyCurrentViewFiltersToCurrentRecordFilters } =
|
||||
@@ -114,6 +105,9 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => {
|
||||
contextStoreCurrentObjectMetadataNameSingular:
|
||||
mockObjectMetadataItemNameSingular,
|
||||
contextStoreCurrentViewId: mockView.id,
|
||||
onInitializeJotaiStore: (store) => {
|
||||
store.set(coreViewsState.atom, [mockCoreView]);
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
@@ -139,8 +133,6 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => {
|
||||
});
|
||||
|
||||
it('should not apply filters when current view is not found', () => {
|
||||
jotaiStore.set(coreViewsState.atom, []);
|
||||
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const { applyCurrentViewFiltersToCurrentRecordFilters } =
|
||||
@@ -182,8 +174,6 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => {
|
||||
});
|
||||
|
||||
it('should handle view with empty filters', () => {
|
||||
jotaiStore.set(coreViewsState.atom, [{ ...mockCoreView, viewFilters: [] }]);
|
||||
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const { applyCurrentViewFiltersToCurrentRecordFilters } =
|
||||
@@ -206,6 +196,9 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => {
|
||||
contextStoreCurrentObjectMetadataNameSingular:
|
||||
mockObjectMetadataItemNameSingular,
|
||||
onInitializeJotaiStore: (store) => {
|
||||
store.set(coreViewsState.atom, [
|
||||
{ ...mockCoreView, viewFilters: [] },
|
||||
]);
|
||||
store.set(
|
||||
contextStoreCurrentViewIdComponentState.atomFamily({
|
||||
instanceId: 'instanceId',
|
||||
|
||||
+4
-14
@@ -6,10 +6,6 @@ import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/use
|
||||
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
|
||||
import {
|
||||
jotaiStore,
|
||||
resetJotaiStore,
|
||||
} from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { coreViewsState } from '@/views/states/coreViewState';
|
||||
import { type CoreViewSortEssential } from '@/views/types/CoreViewSortEssential';
|
||||
import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations';
|
||||
@@ -37,10 +33,6 @@ describe('useApplyCurrentViewSortsToCurrentRecordSorts', () => {
|
||||
);
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
resetJotaiStore();
|
||||
});
|
||||
|
||||
const mockFieldMetadataItem = mockObjectMetadataItem.fields.find(
|
||||
(field) => field.name === 'name',
|
||||
);
|
||||
@@ -77,8 +69,6 @@ describe('useApplyCurrentViewSortsToCurrentRecordSorts', () => {
|
||||
} satisfies CoreViewWithRelations;
|
||||
|
||||
it('should apply sorts from current view', () => {
|
||||
jotaiStore.set(coreViewsState.atom, [mockCoreView]);
|
||||
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const { applyCurrentViewSortsToCurrentRecordSorts } =
|
||||
@@ -101,6 +91,9 @@ describe('useApplyCurrentViewSortsToCurrentRecordSorts', () => {
|
||||
contextStoreCurrentObjectMetadataNameSingular:
|
||||
mockObjectMetadataItemNameSingular,
|
||||
contextStoreCurrentViewId: mockView.id,
|
||||
onInitializeJotaiStore: (store) => {
|
||||
store.set(coreViewsState.atom, [mockCoreView]);
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
@@ -119,8 +112,6 @@ describe('useApplyCurrentViewSortsToCurrentRecordSorts', () => {
|
||||
});
|
||||
|
||||
it('should not apply sorts when current view is not found', () => {
|
||||
jotaiStore.set(coreViewsState.atom, []);
|
||||
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const { applyCurrentViewSortsToCurrentRecordSorts } =
|
||||
@@ -167,8 +158,6 @@ describe('useApplyCurrentViewSortsToCurrentRecordSorts', () => {
|
||||
viewSorts: [],
|
||||
};
|
||||
|
||||
jotaiStore.set(coreViewsState.atom, [viewWithNoSorts]);
|
||||
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const { applyCurrentViewSortsToCurrentRecordSorts } =
|
||||
@@ -191,6 +180,7 @@ describe('useApplyCurrentViewSortsToCurrentRecordSorts', () => {
|
||||
contextStoreCurrentObjectMetadataNameSingular:
|
||||
mockObjectMetadataItemNameSingular,
|
||||
onInitializeJotaiStore: (store) => {
|
||||
store.set(coreViewsState.atom, [viewWithNoSorts]);
|
||||
store.set(
|
||||
contextStoreCurrentViewIdComponentState.atomFamily({
|
||||
instanceId: 'instanceId',
|
||||
|
||||
@@ -7,9 +7,10 @@ import { ContextStoreComponentInstanceContext } from '@/context-store/states/con
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper';
|
||||
import { SnackBarComponentInstanceContext } from '@/ui/feedback/snack-bar-manager/contexts/SnackBarComponentInstanceContext';
|
||||
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { resetJotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||
import { type InMemoryCache } from '@apollo/client';
|
||||
import type { Store } from 'jotai/vanilla/store';
|
||||
import { JestContextStoreSetter } from '~/testing/jest/JestContextStoreSetter';
|
||||
import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter';
|
||||
|
||||
@@ -23,13 +24,15 @@ export const getJestMetadataAndApolloMocksWrapper = ({
|
||||
apolloMocks?:
|
||||
| readonly MockedResponse<Record<string, any>, Record<string, any>>[]
|
||||
| undefined;
|
||||
onInitializeJotaiStore?: (store: typeof jotaiStore) => void;
|
||||
onInitializeJotaiStore?: (store: Store) => void;
|
||||
objectMetadataItems?: ObjectMetadataItem[];
|
||||
}) => {
|
||||
onInitializeJotaiStore?.(jotaiStore);
|
||||
const store = resetJotaiStore();
|
||||
|
||||
onInitializeJotaiStore?.(store);
|
||||
|
||||
return ({ children }: { children: ReactNode }) => (
|
||||
<JotaiProvider store={jotaiStore}>
|
||||
<JotaiProvider store={store}>
|
||||
<SnackBarComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'snack-bar-manager' }}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user