From 78d713d336d398764da707b808cc642cee0f33cf Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Tue, 24 Feb 2026 13:09:31 +0200 Subject: [PATCH] test(execution): add additional tests from original PR #4458 Adds non-null sibling-error propagation tests from PR #4458 (originally from v16 work) that were not ported to next. --- src/execution/__tests__/nonnull-test.ts | 183 ++++++++++++++++++++++++ 1 file changed, 183 insertions(+) diff --git a/src/execution/__tests__/nonnull-test.ts b/src/execution/__tests__/nonnull-test.ts index 8cba9643e3..5c3efad424 100644 --- a/src/execution/__tests__/nonnull-test.ts +++ b/src/execution/__tests__/nonnull-test.ts @@ -2,8 +2,11 @@ import { expect } from 'chai'; import { describe, it } from 'mocha'; import { expectJSON } from '../../__testUtils__/expectJSON.js'; +import { resolveOnNextTick } from '../../__testUtils__/resolveOnNextTick.js'; +import { invariant } from '../../jsutils/invariant.js'; import type { PromiseOrValue } from '../../jsutils/PromiseOrValue.js'; +import { promiseWithResolvers } from '../../jsutils/promiseWithResolvers.js'; import { parse } from '../../language/parser.js'; @@ -526,6 +529,186 @@ describe('Execute: handles non-nullable types', () => { }); }); + describe('Handles multiple errors for a single response position', () => { + it('nullable and non-nullable root fields throw nested errors', async () => { + const query = ` + { + promiseNonNullNest { + syncNonNull + } + promiseNest { + syncNonNull + } + } + `; + const result = await executeQuery(query, throwingData); + + expectJSON(result).toDeepEqual({ + data: null, + errors: [ + { + message: syncNonNullError.message, + path: ['promiseNest', 'syncNonNull'], + locations: [{ line: 7, column: 13 }], + }, + { + message: syncNonNullError.message, + path: ['promiseNonNullNest', 'syncNonNull'], + locations: [{ line: 4, column: 13 }], + }, + ], + }); + }); + + it('a nullable root field throws a slower nested error after a non-nullable root field throws a nested error', async () => { + const query = ` + { + promiseNonNullNest { + syncNonNull + } + promiseNest { + promiseNonNull + } + } + `; + const result = await executeQuery(query, throwingData); + + expectJSON(result).toDeepEqual({ + data: null, + errors: [ + { + message: syncNonNullError.message, + path: ['promiseNonNullNest', 'syncNonNull'], + locations: [{ line: 4, column: 13 }], + }, + ], + }); + + // allow time for slower error to reject + invariant(result.errors !== undefined); + const initialErrors = [...result.errors]; + for (let i = 0; i < 5; i++) { + // eslint-disable-next-line no-await-in-loop + await resolveOnNextTick(); + } + expectJSON(initialErrors).toDeepEqual(result.errors); + }); + + it('nullable and non-nullable nested fields throw nested errors', async () => { + const query = ` + { + syncNest { + promiseNonNullNest { + syncNonNull + } + promiseNest { + syncNonNull + } + } + } + `; + const result = await executeQuery(query, throwingData); + + expectJSON(result).toDeepEqual({ + data: { syncNest: null }, + errors: [ + { + message: syncNonNullError.message, + path: ['syncNest', 'promiseNest', 'syncNonNull'], + locations: [{ line: 8, column: 15 }], + }, + { + message: syncNonNullError.message, + path: ['syncNest', 'promiseNonNullNest', 'syncNonNull'], + locations: [{ line: 5, column: 15 }], + }, + ], + }); + }); + + it('a nullable nested field throws a slower nested error after a non-nullable nested field throws a nested error', async () => { + const query = ` + { + syncNest { + promiseNonNullNest { + syncNonNull + } + promiseNest { + promiseNest { + promiseNest { + promiseNonNull + } + } + } + } + } + `; + const result = await executeQuery(query, throwingData); + + expectJSON(result).toDeepEqual({ + data: { syncNest: null }, + errors: [ + { + message: syncNonNullError.message, + path: ['syncNest', 'promiseNonNullNest', 'syncNonNull'], + locations: [{ line: 5, column: 15 }], + }, + ], + }); + + invariant(result.errors !== undefined); + const initialErrors = [...result.errors]; + for (let i = 0; i < 20; i++) { + // eslint-disable-next-line no-await-in-loop + await resolveOnNextTick(); + } + expectJSON(initialErrors).toDeepEqual(result.errors); + }); + + it('suppresses a later error after a parent has been nulled', async () => { + const query = ` + { + syncNest { + syncNonNull + promise + } + } + `; + + const nonNullDeferred = promiseWithResolvers(); + const promiseDeferred = promiseWithResolvers(); + + const resultPromise = executeQuery(query, { + syncNest: { + syncNonNull: () => nonNullDeferred.promise, + promise: () => promiseDeferred.promise, + }, + }); + + nonNullDeferred.reject(syncNonNullError); + + // Give the first error a chance to null out the parent position. + await resolveOnNextTick(); + await resolveOnNextTick(); + await resolveOnNextTick(); + + promiseDeferred.reject(promiseError); + + const result = await resultPromise; + + expectJSON(result).toDeepEqual({ + data: { syncNest: null }, + errors: [ + { + message: syncNonNullError.message, + path: ['syncNest', 'syncNonNull'], + locations: [{ line: 4, column: 13 }], + }, + ], + }); + }); + }); + describe('Handles non-null argument', () => { const schemaWithNonNullArg = new GraphQLSchema({ query: new GraphQLObjectType({