From 4b9c9d3a1824898fb91c48b914dfdf86e3abf290 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Musia=C5=82?= Date: Wed, 21 Oct 2026 13:41:28 +0200 Subject: [PATCH 1/2] Deep compare arrays on merge and return old array if unchanged --- lib/OnyxCache.ts | 2 + lib/utils.ts | 46 +++++++- tests/perf-test/Onyx.perf-test.ts | 30 ++++++ tests/perf-test/useOnyx.perf-test.tsx | 27 ++++- tests/perf-test/utils.perf-test.ts | 48 +++++++++ tests/unit/onyxCacheTest.tsx | 31 ++++++ tests/unit/onyxTest.ts | 37 +++++++ tests/unit/utilsTest.ts | 133 ++++++++++++++++++++++++ tests/utils/collections/objectArrays.ts | 24 +++++ 9 files changed, 373 insertions(+), 5 deletions(-) create mode 100644 tests/utils/collections/objectArrays.ts diff --git a/lib/OnyxCache.ts b/lib/OnyxCache.ts index 5cd0dc6a4..3003b5f1f 100644 --- a/lib/OnyxCache.ts +++ b/lib/OnyxCache.ts @@ -24,6 +24,8 @@ const FROZEN_EMPTY_COLLECTION: Readonly; @@ -22,6 +23,12 @@ type FastMergeOptions = { * If set to "replace", we will completely replace the marked objects with the new value instead of merging them. */ objectRemovalMode?: 'mark' | 'replace' | 'none'; + + /** + * If true, a source array deep-equal to the target array keeps the target reference, so unchanged merges return the target. + * Arrays inside an object are only compared when nothing else in it changed. Costs a deep comparison per array. + */ + shouldKeepEqualArrays?: boolean; }; type FastMergeMetadata = { @@ -56,12 +63,15 @@ function fastMerge(target: TValue, source: TValue, options?: FastMergeOp // otherwise "mergeObject" will throw an error, // because it expects an object as "source" if (!isMergeableObject(source)) { - return {result: source, replaceNullPatches: metadata.replaceNullPatches}; + // Arrays are replaced, not merged, but a deep-equal source array keeps the target reference. + const result = options?.shouldKeepEqualArrays && areDeepEqualArrays(target, source) ? target : source; + return {result, replaceNullPatches: metadata.replaceNullPatches}; } const optionsWithDefaults: FastMergeOptions = { shouldRemoveNestedNulls: options?.shouldRemoveNestedNulls ?? false, objectRemovalMode: options?.objectRemovalMode ?? 'none', + shouldKeepEqualArrays: options?.shouldKeepEqualArrays ?? false, }; const mergedValue = mergeObject(target, source as Record, optionsWithDefaults, metadata, basePath) as TValue; @@ -93,6 +103,10 @@ function mergeObject>( // If nothing changed, we return the original target reference for reference stability. let hasChanged = !targetObject; + // Keys whose array was replaced. Compared at the end, and only if nothing else changed, + // since any other change gives the object a new reference anyway. + let replacedArrayKeys: string[] | undefined; + // First we want to copy over all keys from the target into the destination object, // in case "target" is a mergable object. // If "shouldRemoveNestedNulls" is true, we want to remove null values from the merged object @@ -130,8 +144,14 @@ function mergeObject>( // If the source value is not a mergable object, we need to set the key directly. if (!isMergeableObject(sourceProperty)) { - if (destination[key] !== sourceProperty) { - hasChanged = true; + const destinationProperty = destination[key]; + if (destinationProperty !== sourceProperty) { + if (options.shouldKeepEqualArrays && !hasChanged && Array.isArray(destinationProperty) && Array.isArray(sourceProperty)) { + replacedArrayKeys ??= []; + replacedArrayKeys.push(key); + } else { + hasChanged = true; + } } destination[key] = sourceProperty; continue; @@ -167,7 +187,20 @@ function mergeObject>( destination[key] = merged; } - return hasChanged ? (destination as TObject) : (targetObject as TObject); + if (hasChanged) { + return destination as TObject; + } + + // Only arrays were replaced. If they are all deep-equal, the merge is a no-op. + if (replacedArrayKeys) { + for (const key of replacedArrayKeys) { + if (!deepEqual(targetObject?.[key], destination[key])) { + return destination as TObject; + } + } + } + + return targetObject as TObject; } /** Checks whether the given object is an object and not null/undefined. */ @@ -196,6 +229,11 @@ function isMergeableObject>(value: unkno return isNonNullObject && !(value instanceof RegExp) && !(value instanceof Date) && !Array.isArray(value); } +/** Checks whether both values are arrays with deep-equal contents. */ +function areDeepEqualArrays(a: unknown, b: unknown): boolean { + return Array.isArray(a) && Array.isArray(b) && deepEqual(a, b); +} + /** * Reports whether a value needs cleaning (nested null/undefined, or the replace-object mark) before it's * safe to store by reference. Read-only, non-allocating. diff --git a/tests/perf-test/Onyx.perf-test.ts b/tests/perf-test/Onyx.perf-test.ts index 221a043e6..dbae9028d 100644 --- a/tests/perf-test/Onyx.perf-test.ts +++ b/tests/perf-test/Onyx.perf-test.ts @@ -2,6 +2,7 @@ import {measureAsyncFunction} from 'reassure'; import type {OnyxKey, OnyxUpdate} from '../../lib'; import Onyx from '../../lib'; import createRandomReportAction, {getRandomReportActions} from '../utils/collections/reportActions'; +import {createObjectArrayCollection} from '../utils/collections/objectArrays'; import type GenericCollection from '../utils/GenericCollection'; import OnyxUtils from '../../lib/OnyxUtils'; import createDeferredTask from '../../lib/createDeferredTask'; @@ -82,6 +83,35 @@ describe('Onyx', () => { afterEach: clearOnyxAfterEachMeasure, }); }); + + test('one call with 1.5k deep-equal arrays and a collection subscriber that recomputes changed members', async () => { + const arraysCollectionKey = ONYXKEYS.COLLECTION.TEST_KEY_2; + const arrays = createObjectArrayCollection(arraysCollectionKey, 1500); + const sameArrays = createObjectArrayCollection(arraysCollectionKey, 1500); + + // Stand-in for a subscriber that recomputes every member whose reference changed. + let previousCollection: Record | undefined; + const connection = Onyx.connect({ + key: arraysCollectionKey, + callback: (collection) => { + for (const key of Object.keys(collection ?? {})) { + if (collection[key] !== previousCollection?.[key]) { + JSON.stringify(collection[key]); + } + } + previousCollection = collection; + }, + }); + + await measureAsyncFunction(() => Onyx.mergeCollection(arraysCollectionKey, sameArrays), { + beforeEach: async () => { + await Onyx.mergeCollection(arraysCollectionKey, arrays); + }, + afterEach: clearOnyxAfterEachMeasure, + }); + + Onyx.disconnect(connection); + }); }); describe('setCollection', () => { diff --git a/tests/perf-test/useOnyx.perf-test.tsx b/tests/perf-test/useOnyx.perf-test.tsx index c09f4ba56..95b58a05c 100644 --- a/tests/perf-test/useOnyx.perf-test.tsx +++ b/tests/perf-test/useOnyx.perf-test.tsx @@ -1,10 +1,11 @@ -import {screen} from '@testing-library/react-native'; +import {act, screen} from '@testing-library/react-native'; import React from 'react'; import {Text, View} from 'react-native'; import {measureRenders} from 'reassure'; import type {FetchStatus, OnyxEntry, OnyxKey, OnyxValue, ResultMetadata, UseOnyxOptions} from '../../lib'; import Onyx, {useOnyx} from '../../lib'; import type {UseOnyxSelector} from '../../lib/useOnyx'; +import {createObjectArrayCollection} from '../utils/collections/objectArrays'; const ONYXKEYS = { TEST_KEY: 'test', @@ -394,6 +395,30 @@ describe('useOnyx', () => { ); }); + /** + * Expected renders: 1. + * + * Merging deep-equal members (arrays included) keeps the cached references, so a subscriber without a selector doesn't re-render. + */ + test('merging deep-equal members with arrays does not re-render', async () => { + const key = ONYXKEYS.COLLECTION.TEST_KEY; + await measureRenders(, { + beforeEach: async () => { + await Onyx.mergeCollection(key, createObjectArrayCollection(key, 3)); + }, + scenario: async () => { + await screen.findByText(dataMatcher(key, createObjectArrayCollection(key, 3))); + + await act(async () => { + await Onyx.mergeCollection(key, createObjectArrayCollection(key, 3)); + }); + + await screen.findByText(dataMatcher(key, createObjectArrayCollection(key, 3))); + }, + afterEach: clearOnyxAfterEachMeasure, + }); + }); + /** * Expected renders: 1. * diff --git a/tests/perf-test/utils.perf-test.ts b/tests/perf-test/utils.perf-test.ts index fe9df4b32..ae54b23b8 100644 --- a/tests/perf-test/utils.perf-test.ts +++ b/tests/perf-test/utils.perf-test.ts @@ -2,6 +2,7 @@ import {measureFunction} from 'reassure'; import Onyx from '../../lib'; import utils from '../../lib/utils'; import createRandomReportAction, {getRandomReportActions} from '../utils/collections/reportActions'; +import {createObjectArray, createObjectArrayCollection} from '../utils/collections/objectArrays'; const ONYXKEYS = { COLLECTION: { @@ -31,6 +32,53 @@ describe('utils', () => { }); }); + describe('fastMerge collection of arrays', () => { + const COLLECTION_SIZE = 1500; + + const createCollection = (createMember?: typeof createObjectArray) => createObjectArrayCollection(collectionKey, COLLECTION_SIZE, createMember); + + // Merges every member separately, like OnyxCache.merge() does. + const mergeCollection = (target: Record, source: Record) => { + for (const key of Object.keys(source)) { + utils.fastMerge(target[key], source[key], {shouldRemoveNestedNulls: true, objectRemovalMode: 'replace', shouldKeepEqualArrays: true}); + } + }; + + const target = createCollection(); + + test('all members deep-equal', async () => { + const source = createCollection(); + await measureFunction(() => mergeCollection(target, source)); + }); + + test('one member different', async () => { + const source = createCollection((index) => createObjectArray(index, undefined, index === 0 ? 0 : undefined)); + await measureFunction(() => mergeCollection(target, source)); + }); + + test('all members different in the first element', async () => { + const source = createCollection((index) => createObjectArray(index, undefined, 0)); + await measureFunction(() => mergeCollection(target, source)); + }); + + test('all members different in the last element', async () => { + const source = createCollection((index) => createObjectArray(index, undefined, index % 3)); + await measureFunction(() => mergeCollection(target, source)); + }); + + test('all members long and different only in the first element', async () => { + const longTarget = createCollection((index) => createObjectArray(index, 50)); + const source = createCollection((index) => createObjectArray(index, 50, 0)); + await measureFunction(() => mergeCollection(longTarget, source)); + }); + + test('all members long and different only in the last element', async () => { + const longTarget = createCollection((index) => createObjectArray(index, 50)); + const source = createCollection((index) => createObjectArray(index, 50, 49)); + await measureFunction(() => mergeCollection(longTarget, source)); + }); + }); + describe('formatActionName', () => { test('one call', async () => { await measureFunction(() => utils.formatActionName(Onyx.METHOD.SET, ONYXKEYS.COLLECTION.TEST_KEY)); diff --git a/tests/unit/onyxCacheTest.tsx b/tests/unit/onyxCacheTest.tsx index 43c11ef16..5d6e78344 100644 --- a/tests/unit/onyxCacheTest.tsx +++ b/tests/unit/onyxCacheTest.tsx @@ -970,6 +970,37 @@ describe('Onyx', () => { expect(before).toBe(after); }); + it('should keep the cached array reference and not mark collection dirty when merged array is deep-equal', async () => { + await initOnyx(); + const memberKey = `${ONYX_KEYS.COLLECTION.MOCK_COLLECTION}1`; + await Onyx.set(memberKey, [{name: 'item', data: {value: 1}}]); + + const cachedValue = cache.get(memberKey); + const before = cache.getCollectionData(ONYX_KEYS.COLLECTION.MOCK_COLLECTION); + + // Merge a new, deep-equal array + cache.merge({[memberKey]: [{name: 'item', data: {value: 1}}]}); + + expect(cache.get(memberKey)).toBe(cachedValue); + // A rebuild would reuse the snapshot too, so check the dirty set directly. + expect((cache as unknown as {dirtyCollections: Set}).dirtyCollections.has(ONYX_KEYS.COLLECTION.MOCK_COLLECTION)).toBe(false); + expect(cache.getCollectionData(ONYX_KEYS.COLLECTION.MOCK_COLLECTION)).toBe(before); + }); + + it('should replace the cached array when merged array differs', async () => { + await initOnyx(); + const memberKey = `${ONYX_KEYS.COLLECTION.MOCK_COLLECTION}1`; + await Onyx.set(memberKey, [{name: 'item', data: {value: 1}}]); + + const before = cache.getCollectionData(ONYX_KEYS.COLLECTION.MOCK_COLLECTION); + const newValue = [{name: 'item', data: {value: 2}}]; + + cache.merge({[memberKey]: newValue}); + + expect(cache.get(memberKey)).toBe(newValue); + expect(cache.getCollectionData(ONYX_KEYS.COLLECTION.MOCK_COLLECTION)).not.toBe(before); + }); + it('should mark collection dirty when a member value changes', async () => { await initOnyx(); await Onyx.set(`${ONYX_KEYS.COLLECTION.MOCK_COLLECTION}1`, {id: 1}); diff --git a/tests/unit/onyxTest.ts b/tests/unit/onyxTest.ts index 9d7ed3567..5b0527da9 100644 --- a/tests/unit/onyxTest.ts +++ b/tests/unit/onyxTest.ts @@ -1542,6 +1542,43 @@ describe('Onyx', () => { }); }); + it('should not call collection subscribers when every merged collection member is deep-equal and contains arrays', async () => { + const cat = `${ONYX_KEYS.COLLECTION.ANIMALS}cat`; + const dog = `${ONYX_KEYS.COLLECTION.ANIMALS}dog`; + const createCollection = (): GenericCollection => ({ + [cat]: [{name: 'Fluffy', tags: ['small']}], + [dog]: {name: 'Rex', tags: [{id: 1}, {id: 2}]}, + }); + + await Onyx.mergeCollection(ONYX_KEYS.COLLECTION.ANIMALS, createCollection()); + + const collectionCallback = jest.fn(); + const catCallback = jest.fn(); + const connections = [Onyx.connect({key: ONYX_KEYS.COLLECTION.ANIMALS, callback: collectionCallback}), Onyx.connect({key: cat, callback: catCallback})]; + await waitForPromisesToResolve(); + expect(collectionCallback).toHaveBeenCalledTimes(1); + expect(catCallback).toHaveBeenCalledTimes(1); + + const cachedCat = cache.get(cat); + const cachedDog = cache.get(dog); + + // When the same collection is merged again with new, deep-equal values + await Onyx.mergeCollection(ONYX_KEYS.COLLECTION.ANIMALS, createCollection()); + + // And then merged through Onyx.update, which batches members of the same collection + await Onyx.update(Object.entries(createCollection()).map(([key, value]) => ({onyxMethod: Onyx.METHOD.MERGE, key, value})) as Array>); + + // Then the cached values keep their references and no subscriber is called again + expect(cache.get(cat)).toBe(cachedCat); + expect(cache.get(dog)).toBe(cachedDog); + expect(collectionCallback).toHaveBeenCalledTimes(1); + expect(catCallback).toHaveBeenCalledTimes(1); + + for (const id of connections) { + Onyx.disconnect(id); + } + }); + it('should update Snapshot when its data changed', async () => { const cat = `${ONYX_KEYS.COLLECTION.ANIMALS}cat`; const snapshot1 = `${ONYX_KEYS.COLLECTION.SNAPSHOT}1`; diff --git a/tests/unit/utilsTest.ts b/tests/unit/utilsTest.ts index a45b39a80..e15f99ed2 100644 --- a/tests/unit/utilsTest.ts +++ b/tests/unit/utilsTest.ts @@ -52,6 +52,8 @@ const testMergeChanges: DeepObject[] = [ }, ]; +const KEEP_EQUAL_ARRAYS = {shouldKeepEqualArrays: true}; + describe('utils', () => { describe('fastMerge', () => { describe('primitives', () => { @@ -299,6 +301,137 @@ describe('utils', () => { expect(result.result.a.b).not.toBe(target.a.b); expect(result.result.a.b).toEqual({c: 99, d: 2}); }); + + it('should return the source array when "shouldKeepEqualArrays" is not set', () => { + const source = [1, 2]; + expect(utils.fastMerge([1, 2], source).result).toBe(source); + + const nestedSource = {a: [1]}; + const result = utils.fastMerge({a: [1]}, nestedSource); + expect(result.result.a).toBe(nestedSource.a); + }); + + it('should return the target array when the source array is deep-equal', () => { + const target = [1, 2]; + const result = utils.fastMerge(target, [1, 2], KEEP_EQUAL_ARRAYS); + expect(result.result).toBe(target); + }); + + it('should return the source array when the source array differs', () => { + const target = [1, 2]; + const source = [1, 3]; + const result = utils.fastMerge(target, source, KEEP_EQUAL_ARRAYS); + expect(result.result).toBe(source); + }); + + it('should return the target array when merging an empty array into an empty array', () => { + const target: unknown[] = []; + const result = utils.fastMerge(target, [], KEEP_EQUAL_ARRAYS); + expect(result.result).toBe(target); + }); + + it('should return the target object when a nested array is deep-equal', () => { + const nestedArray = [{x: 1}]; + const target = {a: nestedArray, b: 1}; + const source = {a: [{x: 1}]}; + const result = utils.fastMerge(target, source, KEEP_EQUAL_ARRAYS); + expect(result.result).toBe(target); + expect(result.result.a).toBe(nestedArray); + }); + + it.each([ + ['before', {b: 2, a: [1]}], + ['after', {a: [1], b: 2}], + ])('should not compare a nested array when a sibling %s it changes', (_, source) => { + const target = {a: [1], b: 1}; + const result = utils.fastMerge(target, source, KEEP_EQUAL_ARRAYS); + expect(result.result).toEqual({a: [1], b: 2}); + expect(result.result.a).toBe(source.a); + }); + + it('should not compare a nested array when a target key is removed', () => { + const target = {a: [1], b: 1}; + const source = {a: [1], b: null}; + const result = utils.fastMerge(target, source, {...KEEP_EQUAL_ARRAYS, shouldRemoveNestedNulls: true}); + expect(result.result).toEqual({a: [1]}); + expect(result.result.a).toBe(source.a); + }); + + it('should preserve a nested object with deep-equal arrays when a sibling of that object changes', () => { + const nestedObject = {a: [1]}; + const target = {x: nestedObject, b: 1}; + const source = {x: {a: [1]}, b: 2}; + const result = utils.fastMerge(target, source, KEEP_EQUAL_ARRAYS); + expect(result.result).not.toBe(target); + expect(result.result.x).toBe(nestedObject); + }); + + it('should return a new object when one of several nested arrays differs', () => { + const target = {a: [1], b: [2], c: 'c'}; + const source = {a: [1], b: [3]}; + const result = utils.fastMerge(target, source, KEEP_EQUAL_ARRAYS); + expect(result.result).not.toBe(target); + expect(result.result).toEqual({a: [1], b: [3], c: 'c'}); + }); + + it('should return the target object when several nested arrays are deep-equal', () => { + const target = {a: [1], b: [{c: 2}], d: 'd'}; + const result = utils.fastMerge(target, {a: [1], b: [{c: 2}], d: 'd'}, KEEP_EQUAL_ARRAYS); + expect(result.result).toBe(target); + }); + + it('should return the source array when a nested array differs', () => { + const target = {a: [{x: 1}], b: 1}; + const source = {a: [{x: 2}]}; + const result = utils.fastMerge(target, source, KEEP_EQUAL_ARRAYS); + expect(result.result).not.toBe(target); + expect(result.result.a).toBe(source.a); + }); + + it('should treat arrays of objects with a different key order as deep-equal', () => { + const target = [{a: 1, b: 2}]; + const result = utils.fastMerge(target, [{b: 2, a: 1}], KEEP_EQUAL_ARRAYS); + expect(result.result).toBe(target); + }); + + it('should compare null elements inside arrays as they are', () => { + const target = [null, {a: null}]; + expect(utils.fastMerge(target, [null, {a: null}], {...KEEP_EQUAL_ARRAYS, shouldRemoveNestedNulls: true}).result).toBe(target); + + const source = [null, {a: 1}]; + expect(utils.fastMerge(target, source, {...KEEP_EQUAL_ARRAYS, shouldRemoveNestedNulls: true}).result).toBe(source); + }); + + it.each([ + ['an extra key in the source', [{a: 1}], [{a: 1, b: 2}]], + ['a missing key in the source', [{a: 1, b: 2}], [{a: 1}]], + ['an undefined value instead of a missing key', [{a: 1}], [{a: 1, b: undefined}]], + ['a different key with the same key count', [{a: 1}], [{b: 1}]], + ['a different nested array length', [{a: [1, 2]}], [{a: [1]}]], + ['an array instead of an object', [{0: 1}], [[1]]], + ['a different primitive type', [1], ['1']], + ['a different Date', [new Date(1)], [new Date(2)]], + ['a different Map', [new Map([['a', 1]])], [new Map([['a', 2]])]], + ])('should return the source array when the arrays differ by %s', (_, target, source) => { + expect(utils.fastMerge(target, source, KEEP_EQUAL_ARRAYS).result).toBe(source); + }); + + it.each([ + ['nested arrays and objects', [{a: [1, {b: [2, 3]}], c: {d: 'e'}}], [{a: [1, {b: [2, 3]}], c: {d: 'e'}}]], + ['objects without a prototype', [Object.assign(Object.create(null) as object, {a: 1})], [{a: 1}]], + ['Dates', [new Date(1)], [new Date(1)]], + ['Maps', [new Map([['a', {b: 1}]])], [new Map([['a', {b: 1}]])]], + ['booleans, strings and nulls', [true, 'a', null], [true, 'a', null]], + ])('should return the target array when the arrays contain deep-equal %s', (_, target, source) => { + expect(utils.fastMerge(target, source, KEEP_EQUAL_ARRAYS).result).toBe(target); + }); + + it('should replace an array with an object and an object with an array', () => { + const arraySource = [1]; + const objectSource = {0: 1}; + expect(utils.fastMerge({0: 1}, arraySource).result).toBe(arraySource); + expect(utils.fastMerge([1], objectSource).result).toEqual({0: 1}); + }); }); }); diff --git a/tests/utils/collections/objectArrays.ts b/tests/utils/collections/objectArrays.ts new file mode 100644 index 000000000..266cf9010 --- /dev/null +++ b/tests/utils/collections/objectArrays.ts @@ -0,0 +1,24 @@ +type ObjectArrayItemMock = { + name: string; + type: string; + isEnabled: boolean; + data: {value: number; label: string}; +}; + +/** Creates an array of small objects (1 to 3 by default). "changedIndex" gives that item a different value. */ +function createObjectArray(index: number, length = (index % 3) + 1, changedIndex?: number): ObjectArrayItemMock[] { + return Array.from({length}, (_, itemIndex) => ({ + name: `item${itemIndex}`, + type: 'item', + isEnabled: true, + data: {value: itemIndex === changedIndex ? 1 : 0, label: `label${index}`}, + })); +} + +/** Creates "size" members keyed by "collectionKey" + index. */ +function createObjectArrayCollection(collectionKey: string, size: number, createMember: (index: number) => ObjectArrayItemMock[] = createObjectArray): Record { + return Object.fromEntries(Array.from({length: size}, (_, index) => [`${collectionKey}${index}`, createMember(index)])); +} + +export {createObjectArray, createObjectArrayCollection}; +export type {ObjectArrayItemMock}; From 71c972c47d50785f6480f4b9e64309667a8ffa45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Musia=C5=82?= Date: Mon, 26 Oct 2026 10:04:20 +0100 Subject: [PATCH 2/2] Update test --- tests/perf-test/Onyx.perf-test.ts | 2 +- tests/unit/onyxTest.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/perf-test/Onyx.perf-test.ts b/tests/perf-test/Onyx.perf-test.ts index dbae9028d..fb1b21c3b 100644 --- a/tests/perf-test/Onyx.perf-test.ts +++ b/tests/perf-test/Onyx.perf-test.ts @@ -110,7 +110,7 @@ describe('Onyx', () => { afterEach: clearOnyxAfterEachMeasure, }); - Onyx.disconnect(connection); + connection.unsubscribe(); }); }); diff --git a/tests/unit/onyxTest.ts b/tests/unit/onyxTest.ts index 5b0527da9..6b07e1c0f 100644 --- a/tests/unit/onyxTest.ts +++ b/tests/unit/onyxTest.ts @@ -1575,7 +1575,7 @@ describe('Onyx', () => { expect(catCallback).toHaveBeenCalledTimes(1); for (const id of connections) { - Onyx.disconnect(id); + id.unsubscribe(); } });