{"ScriptPreparationCode":"// Complex nested state structure (5 levels deep)\r\nconst createComplexState = () =\u003E ({\r\n user: {\r\n profile: {\r\n personal: {\r\n details: {\r\n info: {\r\n name: \u0027John Doe\u0027,\r\n age: 30,\r\n preferences: [\u0027reading\u0027, \u0027coding\u0027, \u0027music\u0027]\r\n }\r\n }\r\n }\r\n }\r\n },\r\n app: {\r\n ui: {\r\n theme: \u0027dark\u0027,\r\n layout: \u0027grid\u0027\r\n }\r\n },\r\n data: {\r\n items: [\r\n { id: 1, name: \u0027Item 1\u0027, meta: { category: \u0027A\u0027, tags: [\u0027tag1\u0027] } },\r\n { id: 2, name: \u0027Item 2\u0027, meta: { category: \u0027B\u0027, tags: [\u0027tag2\u0027] } }\r\n ]\r\n }\r\n});\r\n\r\n// Simple state structure (1-2 levels)\r\nconst createSimpleState = () =\u003E ({\r\n counter: 0,\r\n flag: true,\r\n items: [\u0027a\u0027, \u0027b\u0027, \u0027c\u0027]\r\n});\r\n\r\n// Store original references for comparison\r\nvar originalComplexState = createComplexState();\r\nvar originalSimpleState = createSimpleState();\r\n\r\n// States for manual mutation testing\r\nvar mutableComplexState = createComplexState();\r\nvar mutableSimpleState = createSimpleState();","TestCases":[{"Name":"mutation \u002B equal","Code":"// Create realistic Onyx state (30 keys, 5 levels, 300 items)\r\nconst createRealisticState = () =\u003E ({\r\n user: {\r\n accountID: 12345,\r\n profile: {\r\n personal: {\r\n details: {\r\n info: {\r\n name: \u0027John Doe\u0027,\r\n age: 30,\r\n email: \u0027john@example.com\u0027\r\n }\r\n }\r\n }\r\n }\r\n },\r\n reports: Array.from({ length: 300 }, (_, i) =\u003E ({\r\n reportID: \u0060r${i}\u0060,\r\n name: \u0060Report ${i}\u0060,\r\n participants: [1, 2, 3],\r\n lastMessage: {\r\n text: \u0027Hello\u0027,\r\n timestamp: Date.now()\r\n }\r\n })),\r\n transactions: Array.from({ length: 300 }, (_, i) =\u003E ({\r\n transactionID: \u0060t${i}\u0060,\r\n amount: Math.random() * 1000,\r\n currency: \u0027USD\u0027\r\n })),\r\n policy: {\r\n settings: {\r\n approval: {\r\n rules: {\r\n level1: { amount: 100 },\r\n level2: { amount: 500 }\r\n }\r\n }\r\n }\r\n }\r\n});\r\n\r\n// ShallowEqual implementation (like Onyx uses)\r\nfunction shallowEqual(a, b) {\r\n if (a === b) return true;\r\n if (!a || !b) return false;\r\n if (typeof a !== \u0027object\u0027 || typeof b !== \u0027object\u0027) return a === b;\r\n \r\n const keysA = Object.keys(a);\r\n const keysB = Object.keys(b);\r\n \r\n if (keysA.length !== keysB.length) return false;\r\n \r\n for (let i = 0; i \u003C keysA.length; i\u002B\u002B) {\r\n if (a[keysA[i]] !== b[keysA[i]]) return false;\r\n }\r\n \r\n return true;\r\n}\r\n\r\n// Store before state\r\nconst beforeState = createRealisticState();\r\nconst prevValue = beforeState;\r\n\r\n// Mutate (simulate merge/set)\r\nconst afterState = createRealisticState();\r\nafterState.user.profile.personal.details.info.age = 31;\r\nafterState.reports[0].name = \u0027Modified Report\u0027;\r\nafterState.policy.settings.approval.rules.level1.amount = 200;\r\n\r\n// Check for changes (what useOnyx does)\r\nconst hasChanged = !shallowEqual(prevValue, afterState);","IsDeferred":false},{"Name":"Immerjs \u002B reference check","Code":"const createRealisticState = () =\u003E ({\r\n user: {\r\n accountID: 12345,\r\n profile: {\r\n personal: {\r\n details: {\r\n info: {\r\n name: \u0027John Doe\u0027,\r\n age: 30,\r\n email: \u0027john@example.com\u0027\r\n }\r\n }\r\n }\r\n }\r\n },\r\n reports: Array.from({ length: 300 }, (_, i) =\u003E ({\r\n reportID: \u0060r${i}\u0060,\r\n name: \u0060Report ${i}\u0060,\r\n participants: [1, 2, 3],\r\n lastMessage: {\r\n text: \u0027Hello\u0027,\r\n timestamp: Date.now()\r\n }\r\n })),\r\n transactions: Array.from({ length: 300 }, (_, i) =\u003E ({\r\n transactionID: \u0060t${i}\u0060,\r\n amount: Math.random() * 1000,\r\n currency: \u0027USD\u0027\r\n })),\r\n policy: {\r\n settings: {\r\n approval: {\r\n rules: {\r\n level1: { amount: 100 },\r\n level2: { amount: 500 }\r\n }\r\n }\r\n }\r\n }\r\n});\r\n\r\nconst originalState = createRealisticState();\r\n\r\n// Create new immutable state with Immer\r\nconst newState = immer.produce(originalState, draft =\u003E {\r\n draft.user.profile.personal.details.info.age = 31;\r\n draft.reports[0].name = \u0027Modified Report\u0027;\r\n draft.policy.settings.approval.rules.level1.amount = 200;\r\n});\r\n\r\n// Fast reference equality check\r\nconst hasChanged = originalState !== newState;\r\nconst userChanged = originalState.user !== newState.user;\r\nconst reportsChanged = originalState.reports !== newState.reports;\r\n\r\n// This is what useOnyx would do - just check if reference changed\r\nconst shouldUpdate = hasChanged;","IsDeferred":false},{"Name":"manual immutable (no lib)","Code":"const createRealisticState = () =\u003E ({\r\n user: {\r\n accountID: 12345,\r\n profile: {\r\n personal: {\r\n details: {\r\n info: {\r\n name: \u0027John Doe\u0027,\r\n age: 30,\r\n email: \u0027john@example.com\u0027\r\n }\r\n }\r\n }\r\n }\r\n },\r\n reports: Array.from({ length: 300 }, (_, i) =\u003E ({\r\n reportID: \u0060r${i}\u0060,\r\n name: \u0060Report ${i}\u0060,\r\n participants: [1, 2, 3],\r\n lastMessage: {\r\n text: \u0027Hello\u0027,\r\n timestamp: Date.now()\r\n }\r\n })),\r\n transactions: Array.from({ length: 300 }, (_, i) =\u003E ({\r\n transactionID: \u0060t${i}\u0060,\r\n amount: Math.random() * 1000,\r\n currency: \u0027USD\u0027\r\n })),\r\n policy: {\r\n settings: {\r\n approval: {\r\n rules: {\r\n level1: { amount: 100 },\r\n level2: { amount: 500 }\r\n }\r\n }\r\n }\r\n }\r\n});\r\n\r\nconst originalState = createRealisticState();\r\n\r\n// Manual immutable updates - only recreate changed paths\r\nconst newState = {\r\n ...originalState,\r\n user: {\r\n ...originalState.user,\r\n profile: {\r\n ...originalState.user.profile,\r\n personal: {\r\n ...originalState.user.profile.personal,\r\n details: {\r\n ...originalState.user.profile.personal.details,\r\n info: {\r\n ...originalState.user.profile.personal.details.info,\r\n age: 31\r\n }\r\n }\r\n }\r\n }\r\n },\r\n reports: [\r\n { ...originalState.reports[0], name: \u0027Modified Report\u0027 },\r\n ...originalState.reports.slice(1)\r\n ],\r\n policy: {\r\n ...originalState.policy,\r\n settings: {\r\n ...originalState.policy.settings,\r\n approval: {\r\n ...originalState.policy.settings.approval,\r\n rules: {\r\n ...originalState.policy.settings.approval.rules,\r\n level1: {\r\n ...originalState.policy.settings.approval.rules.level1,\r\n amount: 200\r\n }\r\n }\r\n }\r\n }\r\n }\r\n};\r\n\r\n// Fast reference equality check\r\nconst hasChanged = originalState !== newState;","IsDeferred":false},{"Name":"Proxy","Code":"const createRealisticState = () =\u003E ({\r\n user: {\r\n accountID: 12345,\r\n profile: {\r\n personal: {\r\n details: {\r\n info: {\r\n name: \u0027John Doe\u0027,\r\n age: 30,\r\n email: \u0027john@example.com\u0027\r\n }\r\n }\r\n }\r\n }\r\n },\r\n reports: Array.from({ length: 300 }, (_, i) =\u003E ({\r\n reportID: \u0060r${i}\u0060,\r\n name: \u0060Report ${i}\u0060,\r\n participants: [1, 2, 3],\r\n lastMessage: {\r\n text: \u0027Hello\u0027,\r\n timestamp: Date.now()\r\n }\r\n })),\r\n transactions: Array.from({ length: 300 }, (_, i) =\u003E ({\r\n transactionID: \u0060t${i}\u0060,\r\n amount: Math.random() * 1000,\r\n currency: \u0027USD\u0027\r\n })),\r\n policy: {\r\n settings: {\r\n approval: {\r\n rules: {\r\n level1: { amount: 100 },\r\n level2: { amount: 500 }\r\n }\r\n }\r\n }\r\n }\r\n});\r\n\r\n// Create Proxy with change tracking\r\nfunction createChangeTrackingProxy(obj, pathPrefix = \u0027\u0027) {\r\n let isDirty = false;\r\n const metadata = { isDirty: () =\u003E isDirty };\r\n \r\n function makeProxy(target, path) {\r\n return new Proxy(target, {\r\n set(obj, prop, value) {\r\n if (obj[prop] !== value) {\r\n isDirty = true;\r\n }\r\n obj[prop] = value;\r\n return true;\r\n },\r\n get(obj, prop) {\r\n if (prop === \u0027__metadata__\u0027) return metadata;\r\n \r\n const value = obj[prop];\r\n if (value \u0026\u0026 typeof value === \u0027object\u0027 \u0026\u0026 !Array.isArray(value)) {\r\n return makeProxy(value, \u0060${path}.${prop}\u0060);\r\n }\r\n return value;\r\n }\r\n });\r\n }\r\n \r\n return makeProxy(obj, pathPrefix);\r\n}\r\n\r\nconst state = createChangeTrackingProxy(createRealisticState());\r\n\r\n// Mutate through proxy\r\nstate.user.profile.personal.details.info.age = 31;\r\nstate.reports[0].name = \u0027Modified Report\u0027;\r\nstate.policy.settings.approval.rules.level1.amount = 200;\r\n\r\n// Check if dirty\r\nconst hasChanged = state.__metadata__.isDirty();","IsDeferred":false},{"Name":"deepClone \u002B deepEqual","Code":"const fastEquals = window[\u0027fast-equals\u0027];\r\n\r\nconst createRealisticState = () =\u003E ({\r\n user: {\r\n accountID: 12345,\r\n profile: {\r\n personal: {\r\n details: {\r\n info: {\r\n name: \u0027John Doe\u0027,\r\n age: 30,\r\n email: \u0027john@example.com\u0027\r\n }\r\n }\r\n }\r\n }\r\n },\r\n reports: Array.from({ length: 300 }, (_, i) =\u003E ({\r\n reportID: \u0060r${i}\u0060,\r\n name: \u0060Report ${i}\u0060,\r\n participants: [1, 2, 3],\r\n lastMessage: {\r\n text: \u0027Hello\u0027,\r\n timestamp: Date.now()\r\n }\r\n })),\r\n transactions: Array.from({ length: 300 }, (_, i) =\u003E ({\r\n transactionID: \u0060t${i}\u0060,\r\n amount: Math.random() * 1000,\r\n currency: \u0027USD\u0027\r\n })),\r\n policy: {\r\n settings: {\r\n approval: {\r\n rules: {\r\n level1: { amount: 100 },\r\n level2: { amount: 500 }\r\n }\r\n }\r\n }\r\n }\r\n});\r\n\r\n// Store deep clone of before state\r\nconst beforeState = JSON.parse(JSON.stringify(createRealisticState()));\r\n\r\n// Mutate\r\nconst afterState = createRealisticState();\r\nafterState.user.profile.personal.details.info.age = 31;\r\nafterState.reports[0].name = \u0027Modified Report\u0027;\r\nafterState.policy.settings.approval.rules.level1.amount = 200;\r\n\r\n// Deep equality check\r\nconst hasChanged = !fastEquals.deepEqual(beforeState, afterState);","IsDeferred":false},{"Name":"Onyx fast merge","Code":"// Onyx merge utilities\r\nconst ONYX_INTERNALS__REPLACE_OBJECT_MARK = \u0027ONYX_INTERNALS__REPLACE_OBJECT_MARK\u0027;\r\n\r\nfunction isMergeableObject(value) {\r\n const isNonNullObject = value != null ? typeof value === \u0027object\u0027 : false;\r\n return isNonNullObject \u0026\u0026 !(value instanceof RegExp) \u0026\u0026 !(value instanceof Date) \u0026\u0026 !Array.isArray(value);\r\n}\r\n\r\nfunction mergeObject(target, source, options, metadata, basePath) {\r\n const destination = {};\r\n const targetObject = isMergeableObject(target) ? target : undefined;\r\n\r\n if (targetObject) {\r\n Object.keys(targetObject).forEach((key) =\u003E {\r\n const targetProperty = targetObject?.[key];\r\n const sourceProperty = source?.[key];\r\n const shouldOmitNullishProperty = options.shouldRemoveNestedNulls \u0026\u0026 (targetProperty === null || sourceProperty === null);\r\n\r\n if (targetProperty === undefined || shouldOmitNullishProperty) {\r\n return;\r\n }\r\n destination[key] = targetProperty;\r\n });\r\n }\r\n\r\n Object.keys(source).forEach((key) =\u003E {\r\n let targetProperty = targetObject?.[key];\r\n const sourceProperty = source?.[key];\r\n const shouldOmitNullishProperty = options.shouldRemoveNestedNulls \u0026\u0026 sourceProperty === null;\r\n\r\n if (sourceProperty === undefined || shouldOmitNullishProperty) {\r\n return;\r\n }\r\n\r\n if (!isMergeableObject(sourceProperty)) {\r\n destination[key] = sourceProperty;\r\n return;\r\n }\r\n\r\n if (options.objectRemovalMode === \u0027mark\u0027 \u0026\u0026 targetProperty === null) {\r\n targetProperty = {[ONYX_INTERNALS__REPLACE_OBJECT_MARK]: true};\r\n metadata.replaceNullPatches.push([[...basePath, key], {...sourceProperty}]);\r\n }\r\n\r\n if (options.objectRemovalMode === \u0027replace\u0027 \u0026\u0026 sourceProperty[ONYX_INTERNALS__REPLACE_OBJECT_MARK]) {\r\n const sourcePropertyWithoutMark = {...sourceProperty};\r\n delete sourcePropertyWithoutMark.ONYX_INTERNALS__REPLACE_OBJECT_MARK;\r\n destination[key] = sourcePropertyWithoutMark;\r\n return;\r\n }\r\n\r\n destination[key] = fastMerge(targetProperty, sourceProperty, options, metadata, [...basePath, key]).result;\r\n });\r\n\r\n return destination;\r\n}\r\n\r\nfunction fastMerge(target, source, options, metadata, basePath = []) {\r\n if (!metadata) {\r\n metadata = { replaceNullPatches: [] };\r\n }\r\n\r\n if (Array.isArray(source) || source === null || source === undefined) {\r\n return {result: source, replaceNullPatches: metadata.replaceNullPatches};\r\n }\r\n\r\n const optionsWithDefaults = {\r\n shouldRemoveNestedNulls: options?.shouldRemoveNestedNulls ?? false,\r\n objectRemovalMode: options?.objectRemovalMode ?? \u0027none\u0027,\r\n };\r\n\r\n const mergedValue = mergeObject(target, source, optionsWithDefaults, metadata, basePath);\r\n return {result: mergedValue, replaceNullPatches: metadata.replaceNullPatches};\r\n}\r\n\r\nfunction shallowEqual(a, b) {\r\n if (a === b) return true;\r\n if (!a || !b) return false;\r\n if (typeof a !== \u0027object\u0027 || typeof b !== \u0027object\u0027) return a === b;\r\n \r\n const keysA = Object.keys(a);\r\n const keysB = Object.keys(b);\r\n \r\n if (keysA.length !== keysB.length) return false;\r\n \r\n for (let i = 0; i \u003C keysA.length; i\u002B\u002B) {\r\n if (a[keysA[i]] !== b[keysA[i]]) return false;\r\n }\r\n \r\n return true;\r\n}\r\n\r\nconst createRealisticState = () =\u003E ({\r\n user: {\r\n accountID: 12345,\r\n profile: {\r\n personal: {\r\n details: {\r\n info: {\r\n name: \u0027John Doe\u0027,\r\n age: 30,\r\n email: \u0027john@example.com\u0027\r\n }\r\n }\r\n }\r\n }\r\n },\r\n reports: Array.from({ length: 300 }, (_, i) =\u003E ({\r\n reportID: \u0060r${i}\u0060,\r\n name: \u0060Report ${i}\u0060,\r\n participants: [1, 2, 3],\r\n lastMessage: {\r\n text: \u0027Hello\u0027,\r\n timestamp: Date.now()\r\n }\r\n })),\r\n transactions: Array.from({ length: 300 }, (_, i) =\u003E ({\r\n transactionID: \u0060t${i}\u0060,\r\n amount: Math.random() * 1000,\r\n currency: \u0027USD\u0027\r\n })),\r\n policy: {\r\n settings: {\r\n approval: {\r\n rules: {\r\n level1: { amount: 100 },\r\n level2: { amount: 500 }\r\n }\r\n }\r\n }\r\n }\r\n});\r\n\r\n// Simulate what Onyx does: merge updates into existing state\r\nconst originalState = createRealisticState();\r\n\r\n// Updates to merge (like what comes from Onyx.merge)\r\nconst updates = {\r\n user: {\r\n profile: {\r\n personal: {\r\n details: {\r\n info: {\r\n age: 31\r\n }\r\n }\r\n }\r\n }\r\n },\r\n reports: [\r\n { reportID: \u0027r0\u0027, name: \u0027Modified Report\u0027, participants: [1, 2, 3], lastMessage: { text: \u0027Hello\u0027, timestamp: Date.now() } }\r\n ],\r\n policy: {\r\n settings: {\r\n approval: {\r\n rules: {\r\n level1: { amount: 200 }\r\n }\r\n }\r\n }\r\n }\r\n};\r\n\r\n// Merge using Onyx fastMerge\r\nconst mergeResult = fastMerge(originalState, updates);\r\nconst newState = mergeResult.result;\r\n\r\n// Check for changes using shallowEqual (what useOnyx does)\r\nconst hasChanged = !shallowEqual(originalState, newState);\r\nconst userChanged = !shallowEqual(originalState.user, newState.user);\r\nconst reportsChanged = !shallowEqual(originalState.reports, newState.reports);","IsDeferred":false}]}