From 130f79c929687a30090b582d8413e379fe8dab40 Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Tue, 21 Nov 2023 15:49:40 +0200 Subject: [PATCH] add checks for actual deferral --- src/execution/__tests__/defer-test.ts | 175 ++++++++++++++++++++++++-- 1 file changed, 167 insertions(+), 8 deletions(-) diff --git a/src/execution/__tests__/defer-test.ts b/src/execution/__tests__/defer-test.ts index 261db67df98..52a929e5b20 100644 --- a/src/execution/__tests__/defer-test.ts +++ b/src/execution/__tests__/defer-test.ts @@ -154,6 +154,61 @@ async function complete(document: DocumentNode, rootValue: unknown = { hero }) { return result; } +function getCountingHero() { + let stopped = false; + let count = 0; + const counts = new Map(); + function increment() { + if (stopped) { + return; + } + // eslint-disable-next-line @typescript-eslint/no-floating-promises + Promise.resolve().then(() => { + count++; + increment(); + }); + } + increment(); + const countingHero = { + stop: () => { + stopped = true; + }, + counts, + hero: () => { + counts.set('hero', count); + return { + id: () => { + counts.set('id', count); + return hero.id; + }, + name: () => { + counts.set('name', count); + return hero.name; + }, + nestedObject: () => { + counts.set('nestedObject', count); + return { + deeperObject: () => { + counts.set('deeperObject', count); + return { + foo: () => { + counts.set('foo', count); + return 'foo'; + }, + bar: () => { + counts.set('bar', count); + return 'bar'; + }, + }; + }, + }; + }, + }; + }, + }; + return countingHero; +} + describe('Execute: defer directive', () => { it('Can defer fragments containing scalar types', async () => { const document = parse(` @@ -167,7 +222,8 @@ describe('Execute: defer directive', () => { name } `); - const result = await complete(document); + const countingHero = getCountingHero(); + const result = await complete(document, { hero: countingHero.hero }); expectJSON(result).toDeepEqual([ { @@ -192,6 +248,11 @@ describe('Execute: defer directive', () => { hasNext: false, }, ]); + + countingHero.stop(); + expect(countingHero.counts.get('hero')).to.equal(0); + expect(countingHero.counts.get('id')).to.equal(0); + expect(countingHero.counts.get('name')).to.equal(1); }); it('Can disable defer using if argument', async () => { const document = parse(` @@ -485,7 +546,8 @@ describe('Execute: defer directive', () => { } } `); - const result = await complete(document); + const countingHero = getCountingHero(); + const result = await complete(document, { hero: countingHero.hero }); expectJSON(result).toDeepEqual([ { data: { @@ -516,6 +578,11 @@ describe('Execute: defer directive', () => { hasNext: false, }, ]); + + countingHero.stop(); + expect(countingHero.counts.get('hero')).to.equal(0); + expect(countingHero.counts.get('id')).to.equal(1); + expect(countingHero.counts.get('name')).to.equal(1); }); it('Separately emits defer fragments with different labels with varying subfields', async () => { @@ -533,7 +600,8 @@ describe('Execute: defer directive', () => { } } `); - const result = await complete(document); + const countingHero = getCountingHero(); + const result = await complete(document, { hero: countingHero.hero }); expectJSON(result).toDeepEqual([ { data: {}, @@ -564,6 +632,11 @@ describe('Execute: defer directive', () => { hasNext: false, }, ]); + + countingHero.stop(); + expect(countingHero.counts.get('hero')).to.equal(1); + expect(countingHero.counts.get('id')).to.equal(1); + expect(countingHero.counts.get('name')).to.equal(1); }); it('Separately emits defer fragments with different labels with varying subfields that return promises', async () => { @@ -634,7 +707,8 @@ describe('Execute: defer directive', () => { } } `); - const result = await complete(document); + const countingHero = getCountingHero(); + const result = await complete(document, { hero: countingHero.hero }); expectJSON(result).toDeepEqual([ { data: { @@ -666,6 +740,11 @@ describe('Execute: defer directive', () => { hasNext: false, }, ]); + + countingHero.stop(); + expect(countingHero.counts.get('hero')).to.equal(0); + expect(countingHero.counts.get('id')).to.equal(1); + expect(countingHero.counts.get('name')).to.equal(1); }); it('Separately emits nested defer fragments with varying subfields of same priorities but different level of defers', async () => { @@ -681,7 +760,8 @@ describe('Execute: defer directive', () => { } } `); - const result = await complete(document); + const countingHero = getCountingHero(); + const result = await complete(document, { hero: countingHero.hero }); expectJSON(result).toDeepEqual([ { data: {}, @@ -716,6 +796,11 @@ describe('Execute: defer directive', () => { hasNext: false, }, ]); + + countingHero.stop(); + expect(countingHero.counts.get('hero')).to.equal(1); + expect(countingHero.counts.get('name')).to.equal(1); + expect(countingHero.counts.get('id')).to.equal(2); }); it('Can deduplicate multiple defers on the same object', async () => { @@ -854,9 +939,8 @@ describe('Execute: defer directive', () => { } } `); - const result = await complete(document, { - hero: { nestedObject: { deeperObject: { foo: 'foo', bar: 'bar' } } }, - }); + const countingHero = getCountingHero(); + const result = await complete(document, { hero: countingHero.hero }); expectJSON(result).toDeepEqual([ { data: { @@ -893,6 +977,81 @@ describe('Execute: defer directive', () => { hasNext: false, }, ]); + + countingHero.stop(); + expect(countingHero.counts.get('hero')).to.equal(0); + expect(countingHero.counts.get('nestedObject')).to.equal(1); + expect(countingHero.counts.get('deeperObject')).to.equal(1); + expect(countingHero.counts.get('foo')).to.equal(1); + expect(countingHero.counts.get('bar')).to.equal(2); + }); + + it('Deduplicates subfields present in a parent defer payload', async () => { + const document = parse(` + query { + hero { + ... @defer { + nestedObject { + deeperObject { + foo + } + ... @defer { + deeperObject { + foo + bar + } + } + } + } + } + } + `); + const countingHero = getCountingHero(); + const result = await complete(document, { hero: countingHero.hero }); + expectJSON(result).toDeepEqual([ + { + data: { + hero: {}, + }, + pending: [{ id: '0', path: ['hero'] }], + hasNext: true, + }, + { + pending: [{ id: '1', path: ['hero', 'nestedObject'] }], + incremental: [ + { + data: { + nestedObject: { + deeperObject: { foo: 'foo' }, + }, + }, + id: '0', + }, + ], + completed: [{ id: '0' }], + hasNext: true, + }, + { + incremental: [ + { + data: { + bar: 'bar', + }, + id: '1', + subPath: ['deeperObject'], + }, + ], + completed: [{ id: '1' }], + hasNext: false, + }, + ]); + + countingHero.stop(); + expect(countingHero.counts.get('hero')).to.equal(0); + expect(countingHero.counts.get('nestedObject')).to.equal(1); + expect(countingHero.counts.get('deeperObject')).to.equal(1); + expect(countingHero.counts.get('foo')).to.equal(1); + expect(countingHero.counts.get('bar')).to.equal(2); }); it('Deduplicates fields with deferred fragments at multiple levels', async () => {