From 3a71c65e569f5bb43b2e0781ae83db6c524254f2 Mon Sep 17 00:00:00 2001 From: vrazuvaev Date: Mon, 13 Jul 2026 13:22:18 +0200 Subject: [PATCH] test(apollo-forest-run): cover null list-difference invariant Add an intentionally failing update-layer regression for the ring0 CompositeNull plus CompositeListDifference signature. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d1f3dcb1-ea24-47fc-a789-5723d1bf5a5f --- .../src/forest/__tests__/updateObject.test.ts | 56 ++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/packages/apollo-forest-run/src/forest/__tests__/updateObject.test.ts b/packages/apollo-forest-run/src/forest/__tests__/updateObject.test.ts index 5f8d28170..aeda98cfc 100644 --- a/packages/apollo-forest-run/src/forest/__tests__/updateObject.test.ts +++ b/packages/apollo-forest-run/src/forest/__tests__/updateObject.test.ts @@ -1,10 +1,11 @@ -import { replaceValue } from "../updateObject"; +import { replaceValue, updateObject } from "../updateObject"; import { UpdateTreeContext } from "../types"; import { ForestRun } from "../../ForestRun"; import { gql, createTestOperation } from "../../__tests__/helpers/descriptor"; import { generateChunk } from "../../__tests__/helpers/values"; import { resolveFieldValue } from "../../values/resolve"; import { ObjectChunk } from "../../values/types"; +import * as Difference from "../../diff/difference"; describe("replaceValue invariant", () => { it("reports the operation when a scalar is replaced with an object", () => { @@ -132,3 +133,56 @@ describe("incompatible object difference invariant", () => { ); }); }); + +describe("divergent composite chunks", () => { + it("keeps a null field when a composite list difference reaches it", () => { + const base = generateChunk(` + query Ring0CompositeList { + emailsInfo @mock(value: null) { + address + } + } + `).value; + const listDifference = Difference.createCompositeListDifference(); + listDifference.layout = []; + const difference = Difference.createObjectDifference(); + Difference.addFieldDifference(difference, "emailsInfo", listDifference); + Difference.addDirtyField(difference, "emailsInfo"); + + const warn = jest.fn(); + const context = { + operation: base.operation, + drafts: new Map(), + missingFields: new Map(), + changes: new Map(), + findParent: () => undefined, + env: { + objectKey: () => false, + logger: { + debug: jest.fn(), + log: jest.fn(), + warn, + error: jest.fn(), + }, + }, + } as unknown as UpdateTreeContext; + + let result: ReturnType; + expect(() => { + result = updateObject(context, base, difference); + }).not.toThrow(); + + expect(result).toBeUndefined(); + expect(base.data.emailsInfo).toBeNull(); + expect(warn).toHaveBeenCalledWith( + 'Skipping update for "query Ring0CompositeList" at path emailsInfo: ' + + "field is an explicit null (CompositeNull) in this chunk, but the incoming " + + "CompositeListDifference expects a composite list; the node has divergent " + + "chunks for this field (null in one, list in another). Leaving the null value " + + "unchanged. This usually means the same node was written with conflicting " + + "values in a single operation (e.g. aliased selections or a repeated node id) " + + "or via a partial/errored write; ensure such selections resolve this field " + + "consistently.", + ); + }); +});