diff --git a/.changeset/spotty-maps-kick.md b/.changeset/spotty-maps-kick.md new file mode 100644 index 0000000000..e6eb76dcaa --- /dev/null +++ b/.changeset/spotty-maps-kick.md @@ -0,0 +1,5 @@ +--- +'@urql/exchange-graphcache': patch +--- + +Improve warning and error console output in development by cleaning up the GraphQL trace stack. diff --git a/exchanges/graphcache/src/helpers/help.ts b/exchanges/graphcache/src/helpers/help.ts index e6bb146e24..39a6004079 100644 --- a/exchanges/graphcache/src/helpers/help.ts +++ b/exchanges/graphcache/src/helpers/help.ts @@ -32,6 +32,8 @@ const cache = new Set(); export const currentDebugStack: string[] = []; +export const popDebugNode = () => currentDebugStack.pop(); + export const pushDebugNode = (typename: void | string, node: DebugNode) => { let identifier = ''; if (node.kind === Kind.INLINE_FRAGMENT) { diff --git a/exchanges/graphcache/src/operations/query.test.ts b/exchanges/graphcache/src/operations/query.test.ts index cc83f38a11..a7adb78b20 100644 --- a/exchanges/graphcache/src/operations/query.test.ts +++ b/exchanges/graphcache/src/operations/query.test.ts @@ -4,7 +4,7 @@ import { write } from './write'; import { query } from './query'; const TODO_QUERY = gql` - query todos { + query Todos { todos { id text @@ -22,17 +22,12 @@ const TODO_QUERY = gql` describe('Query', () => { let schema, store, alteredRoot; - const spy: { console?: any } = {}; beforeAll(() => { schema = require('../test-utils/simple_schema.json'); alteredRoot = require('../test-utils/altered_root_schema.json'); }); - afterEach(() => { - spy.console.mockRestore(); - }); - beforeEach(() => { store = new Store({ schema }); write( @@ -46,7 +41,8 @@ describe('Query', () => { ], } ); - spy.console = jest.spyOn(console, 'warn'); + + jest.resetAllMocks(); }); it('test partial results', () => { @@ -75,7 +71,7 @@ describe('Query', () => { it('should warn once for invalid fields on an entity', () => { const INVALID_TODO_QUERY = gql` - query { + query InvalidTodo { todos { id text @@ -83,27 +79,47 @@ describe('Query', () => { } } `; + query(store, { query: INVALID_TODO_QUERY }); expect(console.warn).toHaveBeenCalledTimes(1); + expect((console.warn as any).mock.calls[0][0]).toMatch( + /Caused At: "InvalidTodo" query/ + ); + query(store, { query: INVALID_TODO_QUERY }); expect(console.warn).toHaveBeenCalledTimes(1); + expect((console.warn as any).mock.calls[0][0]).toMatch(/incomplete/); }); - it('should warn once for invalid sub-entities on an entity', () => { + it('should warn once for invalid sub-entities on an entity at the right stack', () => { const INVALID_TODO_QUERY = gql` - query { + query InvalidTodo { todos { + ...ValidTodo + ...InvalidFields + } + } + + fragment ValidTodo on Todo { + id + text + } + + fragment InvalidFields on Todo { + id + writer { id - text - writer { - id - } } } `; + query(store, { query: INVALID_TODO_QUERY }); expect(console.warn).toHaveBeenCalledTimes(1); + expect((console.warn as any).mock.calls[0][0]).toMatch( + /Caused At: "InvalidTodo" query, "InvalidFields" Fragment/ + ); + query(store, { query: INVALID_TODO_QUERY }); expect(console.warn).toHaveBeenCalledTimes(1); expect((console.warn as any).mock.calls[0][0]).toMatch(/writer/); diff --git a/exchanges/graphcache/src/operations/query.ts b/exchanges/graphcache/src/operations/query.ts index 7807b1a9c4..331fb66b89 100644 --- a/exchanges/graphcache/src/operations/query.ts +++ b/exchanges/graphcache/src/operations/query.ts @@ -34,7 +34,7 @@ import { } from '../store'; import * as InMemoryData from '../store/data'; -import { warn, pushDebugNode } from '../helpers/help'; +import { warn, pushDebugNode, popDebugNode } from '../helpers/help'; import { Context, @@ -94,6 +94,10 @@ export const read = ( ? readRoot(ctx, rootKey, rootSelect, data) : readSelection(ctx, rootKey, rootSelect, data); + if (process.env.NODE_ENV !== 'production') { + popDebugNode(); + } + return { dependencies: getCurrentDependencies(), partial: data === undefined ? false : ctx.partial, @@ -209,9 +213,15 @@ export const readFragment = ( entityKey ); - return ( - readSelection(ctx, entityKey, getSelectionSet(fragment), {} as Data) || null - ); + const result = + readSelection(ctx, entityKey, getSelectionSet(fragment), {} as Data) || + null; + + if (process.env.NODE_ENV !== 'production') { + popDebugNode(); + } + + return result; }; const readSelection = ( diff --git a/exchanges/graphcache/src/operations/shared.ts b/exchanges/graphcache/src/operations/shared.ts index da7c8f4771..54a4970793 100644 --- a/exchanges/graphcache/src/operations/shared.ts +++ b/exchanges/graphcache/src/operations/shared.ts @@ -7,7 +7,7 @@ import { SelectionSet, isFieldNode, } from '../ast'; -import { warn, pushDebugNode } from '../helpers/help'; +import { warn, pushDebugNode, popDebugNode } from '../helpers/help'; import { hasField } from '../store/data'; import { Store, keyOfField } from '../store'; @@ -118,6 +118,9 @@ export class SelectionIterator { if (index >= select.length) { this.indexStack.pop(); this.selectionStack.pop(); + if (process.env.NODE_ENV !== 'production') { + popDebugNode(); + } continue; } else { const node = select[index]; diff --git a/exchanges/graphcache/src/operations/write.ts b/exchanges/graphcache/src/operations/write.ts index 912647fcf6..8296fab2bf 100644 --- a/exchanges/graphcache/src/operations/write.ts +++ b/exchanges/graphcache/src/operations/write.ts @@ -13,7 +13,7 @@ import { getFieldAlias, } from '../ast'; -import { invariant, warn, pushDebugNode } from '../helpers/help'; +import { invariant, warn, pushDebugNode, popDebugNode } from '../helpers/help'; import { NullArray, Variables, Data, Link, OperationRequest } from '../types'; @@ -75,6 +75,11 @@ export const startWrite = ( } writeSelection(ctx, operationName, getSelectionSet(operation), data); + + if (process.env.NODE_ENV !== 'production') { + popDebugNode(); + } + return result; }; @@ -113,6 +118,11 @@ export const writeOptimistic = ( ); writeSelection(ctx, operationName, getSelectionSet(operation), result.data!); + + if (process.env.NODE_ENV !== 'production') { + popDebugNode(); + } + clearDataState(); return result; }; @@ -160,6 +170,10 @@ export const writeFragment = ( ); writeSelection(ctx, entityKey, getSelectionSet(fragment), writeData); + + if (process.env.NODE_ENV !== 'production') { + popDebugNode(); + } }; const writeSelection = ( @@ -221,6 +235,7 @@ const writeSelection = ( if (ctx.optimistic && isRoot) { const resolver = ctx.store.optimisticMutations[fieldName]; + if (!resolver) continue; // We have to update the context to reflect up-to-date ResolveInfo updateContext(ctx, typename, typename, fieldKey, fieldName);