Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/violet-cougars-greet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'graphiql': patch
---

Fixes issue where with IncrementalDelivery directives objects wouldn't deep-merge.
228 changes: 162 additions & 66 deletions packages/graphiql/cypress/integration/incrementalDelivery.spec.ts
Original file line number Diff line number Diff line change
@@ -1,74 +1,170 @@
const testStreamQuery = /* GraphQL */ `
query StreamQuery($delay: Int) {
streamable(delay: $delay) @stream(initialCount: 2) {
text
}
}
`;
describe('IncrementalDelivery support via fetcher', () => {
describe('When operation contains @stream', () => {
const testStreamQuery = /* GraphQL */ `
query StreamQuery($delay: Int) {
streamable(delay: $delay) @stream(initialCount: 2) {
text
}
}
`;

const mockStreamSuccess = {
data: {
streamable: [
{
text: 'Hi',
},
{
text: '你好',
},
{
text: 'Hola',
},
{
text: 'أهلاً',
},
{
text: 'Bonjour',
},
{
text: 'سلام',
},
{
text: '안녕',
},
{
text: 'Ciao',
},
{
text: 'हेलो',
},
{
text: 'Здорово',
const mockStreamSuccess = {
data: {
streamable: [
{
text: 'Hi',
},
{
text: '你好',
},
{
text: 'Hola',
},
{
text: 'أهلاً',
},
{
text: 'Bonjour',
},
{
text: 'سلام',
},
{
text: '안녕',
},
{
text: 'Ciao',
},
{
text: 'हेलो',
},
{
text: 'Здорово',
},
],
},
],
},
hasNext: false,
};
hasNext: false,
};

const testDeferQuery = /* GraphQL */ `
query DeferQuery($delay: Int) {
streamable(delay: $delay) @stream(initialCount: 2) {
text
}
}
`;
it('Expects slower streams to resolve in several increments, and the payloads to patch properly', () => {
const delay = 100;
const timeout = mockStreamSuccess.data.streamable.length * (delay * 1.5);

describe('IncrementalDelivery support via fetcher', () => {
it('Expects slower streams to resolve in several increments, and the payloads to patch properly', () => {
const delay = 100;
const timeout = mockStreamSuccess.data.streamable.length * (delay * 1.5);
cy.visit(`/?query=${testStreamQuery}`);
cy.assertQueryResult(
{ query: testStreamQuery, variables: { delay } },
mockStreamSuccess,
timeout,
);
});

cy.visit(`/?query=${testStreamQuery}`);
cy.assertQueryResult(
{ query: testStreamQuery, variables: { delay } },
mockStreamSuccess,
timeout,
);
it('Expects a quick stream to resolve in a single increment', () => {
cy.visit(`/?query=${testStreamQuery}`);
cy.assertQueryResult(
{ query: testStreamQuery, variables: { delay: 0 } },
mockStreamSuccess,
);
});
});
it('Expects a quick stream to resolve in a single increment', () => {
cy.visit(`/?query=${testStreamQuery}`);
cy.assertQueryResult(
{ query: testStreamQuery, variables: { delay: 0 } },
mockStreamSuccess,
);

describe('When operating with @defer', () => {
it('Excepts to see a slow response but path properly', () => {
const delay = 1000;
const timeout = delay * 1.5;

const testQuery = /* GraphQL */ `
query DeferQuery($delay: Int) {
deferrable {
normalString
... @defer {
deferredString(delay: $delay)
}
}
}
`;

cy.visit(`/?query=${testQuery}`);
cy.assertQueryResult(
{ query: testQuery, variables: { delay } },
{
data: {
deferrable: {
normalString: 'Nice',
deferredString:
'Oops, this took 1 seconds longer than I thought it would!',
},
},
hasNext: false,
},
timeout,
);
});

it('Expects to merge types when members arrive at different times', () => {
/*
This tests that;
1. user ({name}) => { name }
2. user ({age}) => { name, age }
3. user.friends.0 ({name}) => { name, age, friends: [{name}] } <- can sometimes happen before 4, due the the promise race
4. user.friends.0 ({age}) => { name, age, friends: [{name, age}] }

This shows us that we can deep merge defers, deep merge streams, and also deep merge defers inside streams
*/

const delay = 1000;
const timeout = 4 /* friends */ * (delay * 1.5);

const testQuery = /* GraphQL */ `
query DeferQuery($delay: Int) {
person {
name
... @defer {
age(delay: $delay)
}
friends @stream(initialCount: 0) {
... @defer {
name
}
... @defer {
age(delay: $delay)
}
}
}
}
`;

cy.visit(`/?query=${testQuery}`);
cy.assertQueryResult(
{ query: testQuery, variables: { delay } },
{
data: {
person: {
name: 'Mark',
friends: [
{
name: 'James',
age: 1000,
},
{
name: 'Mary',
age: 1000,
},
{
name: 'John',
age: 1000,
},
{
name: 'Patrica',
age: 1000,
},
],
age: 1000,
},
},
hasNext: false,
},
timeout,
);
});
});
});
5 changes: 2 additions & 3 deletions packages/graphiql/cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@ declare namespace Cypress {
variablesString?: string;
};
type MockResult =
| {
data: any;
}
| { data: any }
| { data: any; hasNext?: boolean }
| { error: any[] };
interface Chainable<Subject = any> {
/**
Expand Down
4 changes: 2 additions & 2 deletions packages/graphiql/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@
"codemirror-graphql": "^1.0.0",
"copy-to-clipboard": "^3.2.0",
"entities": "^2.0.0",
"dset": "^3.1.0",
"graphql-language-service": "^3.1.2",
"markdown-it": "^10.0.0",
"@graphiql/toolkit": "^0.1.0",
"dset": "^3.0.0"
"@graphiql/toolkit": "^0.1.0"
},
"peerDependencies": {
"graphql": "^14.0.0 || ^15.0.0",
Expand Down
5 changes: 3 additions & 2 deletions packages/graphiql/src/components/GraphiQL.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import {
} from 'graphql';
import copyToClipboard from 'copy-to-clipboard';
import { getFragmentDependenciesForAST } from 'graphql-language-service-utils';
import { dset } from 'dset';

import { ExecuteButton } from './ExecuteButton';
import { ImagePreview } from './ImagePreview';
Expand All @@ -53,6 +52,7 @@ import {
introspectionQueryName,
introspectionQuerySansSubscriptions,
} from '../utility/introspectionQueries';
import { dset } from 'dset/merge';

import type {
Fetcher,
Expand Down Expand Up @@ -1113,7 +1113,8 @@ export class GraphiQL extends React.Component<GraphiQLProps, GraphiQLState> {
`Expected part to contain a data property, but got ${part}`,
);
}
dset(payload.data, part.path, part.data);

dset(payload.data, path, data);
} else if (data) {
// If there is no path, we don't know what to do with the payload,
// so we just set it.
Expand Down
35 changes: 35 additions & 0 deletions packages/graphiql/test/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,37 @@ const DeferrableObject = new GraphQLObjectType({
},
});

const Person = new GraphQLObjectType({
name: 'Person',
fields: () => ({
name: {
type: GraphQLString,
resolve: obj => obj.name,
},
age: {
args: {
delay: delayArgument(600),
},
type: GraphQLInt,
resolve: async function lazilyReturnValue(_value, args) {
const seconds = args.delay / 1000;
await sleep(args.delay);
return Math.ceil(args.delay);
},
},
friends: {
type: new GraphQLList(Person),
async *resolve(_value, args) {
const names = ['James', 'Mary', 'John', 'Patrica']; // Top 4 names https://www.ssa.gov/oact/babynames/decades/century.html
for (const name of names) {
await sleep(100);
yield { name };
}
},
},
}),
});

const sleep = async timeout => new Promise(res => setTimeout(res, timeout));

const TestType = new GraphQLObjectType({
Expand Down Expand Up @@ -206,6 +237,10 @@ const TestType = new GraphQLObjectType({
}
},
},
person: {
type: Person,
resolve: () => ({ name: 'Mark' }),
},
longDescriptionType: {
type: TestType,
description:
Expand Down
14 changes: 7 additions & 7 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9031,10 +9031,10 @@ dotenv@^6.2.0:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-6.2.0.tgz#941c0410535d942c8becf28d3f357dbd9d476064"
integrity sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==

dset@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/dset/-/dset-3.0.0.tgz#b49ef4a6a092c2c5328618eca2ccf6885fafb431"
integrity sha512-pp0B9VgLwMem6bfSDJujcXa41swmXkhWICL1jwC7WbD/NaxXPCXO0Z1sOrVshIQaD4D/pi5lDS7NCt6qIytWaA==
dset@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/dset/-/dset-3.1.0.tgz#23feb6df93816ea452566308b1374d6e869b0d7b"
integrity sha512-7xTQ5DzyE59Nn+7ZgXDXjKAGSGmXZHqttMVVz1r4QNfmGpyj+cm2YtI3II0c/+4zS4a9yq2mBhgdeq2QnpcYlw==

duplexer2@~0.1.4:
version "0.1.4"
Expand Down Expand Up @@ -10990,13 +10990,13 @@ grapheme-splitter@^1.0.4:
integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==

"graphiql@file:packages/graphiql":
version "1.4.0-rc.1"
version "1.4.0"
dependencies:
"@graphiql/toolkit" "^0.1.0"
codemirror "^5.54.0"
codemirror-graphql "^0.15.2"
codemirror-graphql "^1.0.0"
copy-to-clipboard "^3.2.0"
dset "^3.0.0"
dset "^3.1.0"
entities "^2.0.0"
graphql-language-service "^3.1.2"
markdown-it "^10.0.0"
Expand Down