From 44c8bfabce4ba61488cfdea98f23b4e610f52ebe Mon Sep 17 00:00:00 2001 From: Carlo Palinckx Date: Sun, 19 Aug 2018 21:12:54 +0200 Subject: [PATCH 1/6] added test to readQuery --- .../src/__tests__/cache.ts | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/packages/apollo-cache-inmemory/src/__tests__/cache.ts b/packages/apollo-cache-inmemory/src/__tests__/cache.ts index 8d4f7fe802a..3f54b2246f8 100644 --- a/packages/apollo-cache-inmemory/src/__tests__/cache.ts +++ b/packages/apollo-cache-inmemory/src/__tests__/cache.ts @@ -200,6 +200,7 @@ describe('Cache', () => { ), ).toEqual({ a: 1, b: 2 }); }); + it('will read some data from the store with null variables', () => { const proxy = createCache({ initialState: { @@ -229,6 +230,40 @@ describe('Cache', () => { ), ).toEqual({ a: 1 }); }); + + it('should not mutate arguments passed in', () => { + const proxy = createCache({ + initialState: { + apollo: { + data: { + ROOT_QUERY: { + 'field({"literal":true,"value":42})': 1, + 'field({"literal":false,"value":42})': 2, + }, + }, + }, + }, + }); + + const args = { + query: gql` + query($literal: Boolean, $value: Int) { + a: field(literal: true, value: 42) + b: field(literal: $literal, value: $value) + } + `, + variables: { + literal: false, + value: 42, + }, + }; + + const preQueryCopy = { ...args }; + + expect(stripSymbols(proxy.readQuery(args))).toEqual({ a: 1, b: 2 }); + + expect(preQueryCopy).toEqual(args); + }); }); describe('readFragment', () => { From e2922c2a0e5993b8e54604ba2bf5502ec1ac2271 Mon Sep 17 00:00:00 2001 From: Carlo Palinckx Date: Mon, 8 Oct 2018 17:15:18 +0200 Subject: [PATCH 2/6] deepcloned pre query copy --- packages/apollo-cache-inmemory/src/__tests__/cache.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/apollo-cache-inmemory/src/__tests__/cache.ts b/packages/apollo-cache-inmemory/src/__tests__/cache.ts index 3f54b2246f8..41b20a6a662 100644 --- a/packages/apollo-cache-inmemory/src/__tests__/cache.ts +++ b/packages/apollo-cache-inmemory/src/__tests__/cache.ts @@ -1,6 +1,7 @@ import { ApolloCache } from 'apollo-cache'; import gql, { disableFragmentWarnings } from 'graphql-tag'; import { stripSymbols } from 'apollo-utilities'; +import _ from 'lodash'; import { InMemoryCache, ApolloReducerConfig, NormalizedCache } from '..'; @@ -258,7 +259,7 @@ describe('Cache', () => { }, }; - const preQueryCopy = { ...args }; + const preQueryCopy = _.cloneDeep(args); expect(stripSymbols(proxy.readQuery(args))).toEqual({ a: 1, b: 2 }); From f562d10622225005a5e8a46521dc93f9e6db4697 Mon Sep 17 00:00:00 2001 From: Hugh Willson Date: Sat, 27 Oct 2018 07:06:47 -0400 Subject: [PATCH 3/6] Slight code adjustments --- .../apollo-cache-inmemory/src/__tests__/cache.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/apollo-cache-inmemory/src/__tests__/cache.ts b/packages/apollo-cache-inmemory/src/__tests__/cache.ts index 41b20a6a662..12f3dc3f925 100644 --- a/packages/apollo-cache-inmemory/src/__tests__/cache.ts +++ b/packages/apollo-cache-inmemory/src/__tests__/cache.ts @@ -1,7 +1,7 @@ import { ApolloCache } from 'apollo-cache'; import gql, { disableFragmentWarnings } from 'graphql-tag'; import { stripSymbols } from 'apollo-utilities'; -import _ from 'lodash'; +import { cloneDeep } from 'lodash'; import { InMemoryCache, ApolloReducerConfig, NormalizedCache } from '..'; @@ -246,7 +246,7 @@ describe('Cache', () => { }, }); - const args = { + const options = { query: gql` query($literal: Boolean, $value: Int) { a: field(literal: true, value: 42) @@ -259,11 +259,9 @@ describe('Cache', () => { }, }; - const preQueryCopy = _.cloneDeep(args); - - expect(stripSymbols(proxy.readQuery(args))).toEqual({ a: 1, b: 2 }); - - expect(preQueryCopy).toEqual(args); + const preQueryCopy = cloneDeep(options); + expect(stripSymbols(proxy.readQuery(options))).toEqual({ a: 1, b: 2 }); + expect(preQueryCopy).toEqual(options); }); }); From 1bac2420fd10b053de9545ba2dc5a3680e1ade71 Mon Sep 17 00:00:00 2001 From: Hugh Willson Date: Sat, 27 Oct 2018 07:07:16 -0400 Subject: [PATCH 4/6] Convert CRLF's to LF's --- packages/apollo-cache/src/__tests__/cache.ts | 298 +++++++++---------- 1 file changed, 149 insertions(+), 149 deletions(-) diff --git a/packages/apollo-cache/src/__tests__/cache.ts b/packages/apollo-cache/src/__tests__/cache.ts index 34ecbaeb1c8..6f10d7c57d8 100644 --- a/packages/apollo-cache/src/__tests__/cache.ts +++ b/packages/apollo-cache/src/__tests__/cache.ts @@ -1,149 +1,149 @@ -import gql from 'graphql-tag'; - -import { ApolloCache as Cache } from '../cache'; - -class TestCache extends Cache {} - -describe('abstract cache', () => { - describe('transformDocument', () => { - it('returns the document', () => { - const test = new TestCache(); - expect(test.transformDocument('a')).toBe('a'); - }); - }); - - describe('transformForLink', () => { - it('returns the document', () => { - const test = new TestCache(); - expect(test.transformForLink('a')).toBe('a'); - }); - }); - - describe('readQuery', () => { - it('runs the read method', () => { - const test = new TestCache(); - test.read = jest.fn(); - - test.readQuery({}); - expect(test.read).toBeCalled(); - }); - - it('defaults optimistic to false', () => { - const test = new TestCache(); - test.read = ({ optimistic }) => optimistic; - - expect(test.readQuery({})).toBe(false); - expect(test.readQuery({}, true)).toBe(true); - }); - }); - - describe('readFragment', () => { - it('runs the read method', () => { - const test = new TestCache(); - test.read = jest.fn(); - const fragment = { - id: 'frag', - fragment: gql` - fragment a on b { - name - } - `, - }; - - test.readFragment(fragment); - expect(test.read).toBeCalled(); - }); - - it('defaults optimistic to false', () => { - const test = new TestCache(); - test.read = ({ optimistic }) => optimistic; - const fragment = { - id: 'frag', - fragment: gql` - fragment a on b { - name - } - `, - }; - - expect(test.readFragment(fragment)).toBe(false); - expect(test.readFragment(fragment, true)).toBe(true); - }); - }); - - describe('writeQuery', () => { - it('runs the write method', () => { - const test = new TestCache(); - test.write = jest.fn(); - - test.writeQuery({}); - expect(test.write).toBeCalled(); - }); - }); - - describe('writeFragment', () => { - it('runs the write method', () => { - const test = new TestCache(); - test.write = jest.fn(); - const fragment = { - id: 'frag', - fragment: gql` - fragment a on b { - name - } - `, - }; - - test.writeFragment(fragment); - expect(test.write).toBeCalled(); - }); - }); - - describe('writeData', () => { - it('either writes a fragment or a query', () => { - const test = new TestCache(); - test.read = jest.fn(); - test.writeFragment = jest.fn(); - test.writeQuery = jest.fn(); - - test.writeData({}); - expect(test.writeQuery).toBeCalled(); - - test.writeData({ id: 1 }); - expect(test.read).toBeCalled(); - expect(test.writeFragment).toBeCalled(); - - // Edge case for falsey id - test.writeData({ id: 0 }); - expect(test.read).toHaveBeenCalledTimes(2); - expect(test.writeFragment).toHaveBeenCalledTimes(2); - }); - - it('suppresses read errors', () => { - const test = new TestCache(); - test.read = () => { - throw new Error(); - }; - test.writeFragment = jest.fn(); - - expect(() => test.writeData({ id: 1 })).not.toThrow(); - expect(test.writeFragment).toBeCalled(); - }); - - it('reads __typename from typenameResult or defaults to __ClientData', () => { - const test = new TestCache(); - test.read = () => ({ __typename: 'a' }); - let res; - test.writeFragment = obj => - (res = obj.fragment.definitions[0].typeCondition.name.value); - - test.writeData({ id: 1 }); - expect(res).toBe('a'); - - test.read = () => ({}); - - test.writeData({ id: 1 }); - expect(res).toBe('__ClientData'); - }); - }); -}); +import gql from 'graphql-tag'; + +import { ApolloCache as Cache } from '../cache'; + +class TestCache extends Cache {} + +describe('abstract cache', () => { + describe('transformDocument', () => { + it('returns the document', () => { + const test = new TestCache(); + expect(test.transformDocument('a')).toBe('a'); + }); + }); + + describe('transformForLink', () => { + it('returns the document', () => { + const test = new TestCache(); + expect(test.transformForLink('a')).toBe('a'); + }); + }); + + describe('readQuery', () => { + it('runs the read method', () => { + const test = new TestCache(); + test.read = jest.fn(); + + test.readQuery({}); + expect(test.read).toBeCalled(); + }); + + it('defaults optimistic to false', () => { + const test = new TestCache(); + test.read = ({ optimistic }) => optimistic; + + expect(test.readQuery({})).toBe(false); + expect(test.readQuery({}, true)).toBe(true); + }); + }); + + describe('readFragment', () => { + it('runs the read method', () => { + const test = new TestCache(); + test.read = jest.fn(); + const fragment = { + id: 'frag', + fragment: gql` + fragment a on b { + name + } + `, + }; + + test.readFragment(fragment); + expect(test.read).toBeCalled(); + }); + + it('defaults optimistic to false', () => { + const test = new TestCache(); + test.read = ({ optimistic }) => optimistic; + const fragment = { + id: 'frag', + fragment: gql` + fragment a on b { + name + } + `, + }; + + expect(test.readFragment(fragment)).toBe(false); + expect(test.readFragment(fragment, true)).toBe(true); + }); + }); + + describe('writeQuery', () => { + it('runs the write method', () => { + const test = new TestCache(); + test.write = jest.fn(); + + test.writeQuery({}); + expect(test.write).toBeCalled(); + }); + }); + + describe('writeFragment', () => { + it('runs the write method', () => { + const test = new TestCache(); + test.write = jest.fn(); + const fragment = { + id: 'frag', + fragment: gql` + fragment a on b { + name + } + `, + }; + + test.writeFragment(fragment); + expect(test.write).toBeCalled(); + }); + }); + + describe('writeData', () => { + it('either writes a fragment or a query', () => { + const test = new TestCache(); + test.read = jest.fn(); + test.writeFragment = jest.fn(); + test.writeQuery = jest.fn(); + + test.writeData({}); + expect(test.writeQuery).toBeCalled(); + + test.writeData({ id: 1 }); + expect(test.read).toBeCalled(); + expect(test.writeFragment).toBeCalled(); + + // Edge case for falsey id + test.writeData({ id: 0 }); + expect(test.read).toHaveBeenCalledTimes(2); + expect(test.writeFragment).toHaveBeenCalledTimes(2); + }); + + it('suppresses read errors', () => { + const test = new TestCache(); + test.read = () => { + throw new Error(); + }; + test.writeFragment = jest.fn(); + + expect(() => test.writeData({ id: 1 })).not.toThrow(); + expect(test.writeFragment).toBeCalled(); + }); + + it('reads __typename from typenameResult or defaults to __ClientData', () => { + const test = new TestCache(); + test.read = () => ({ __typename: 'a' }); + let res; + test.writeFragment = obj => + (res = obj.fragment.definitions[0].typeCondition.name.value); + + test.writeData({ id: 1 }); + expect(res).toBe('a'); + + test.read = () => ({}); + + test.writeData({ id: 1 }); + expect(res).toBe('__ClientData'); + }); + }); +}); From cdf9f350d4bb281da4315bb7d7b044281244d83d Mon Sep 17 00:00:00 2001 From: Hugh Willson Date: Sat, 27 Oct 2018 07:09:10 -0400 Subject: [PATCH 5/6] Changelog update --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88413a3853d..a88e8642e23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ - Optimistic tests cleanup.
[@joshribakoff](https://github.com/joshribakoff) in [#3834](https://github.com/apollographql/apollo-client/pull/3834) +### Apollo Cache (vNext) + +- Add `readQuery` test to make sure options aren't mutated.
+ [@CarloPalinckx](https://github.com/CarloPalinckx) in [#3838](https://github.com/apollographql/apollo-client/pull/3838) + ## Apollo Client (2.4.4) ### Apollo Utilities (1.0.24) From 7bf18209298a92fb965acfc7bf6d4fc6e5cafdce Mon Sep 17 00:00:00 2001 From: Hugh Willson Date: Sat, 27 Oct 2018 07:17:53 -0400 Subject: [PATCH 6/6] Convert CRLF's to LF's --- .../src/__tests__/cache.ts | 2358 ++++++++--------- 1 file changed, 1179 insertions(+), 1179 deletions(-) diff --git a/packages/apollo-cache-inmemory/src/__tests__/cache.ts b/packages/apollo-cache-inmemory/src/__tests__/cache.ts index 12f3dc3f925..0cd4f2cc631 100644 --- a/packages/apollo-cache-inmemory/src/__tests__/cache.ts +++ b/packages/apollo-cache-inmemory/src/__tests__/cache.ts @@ -1,1179 +1,1179 @@ -import { ApolloCache } from 'apollo-cache'; -import gql, { disableFragmentWarnings } from 'graphql-tag'; -import { stripSymbols } from 'apollo-utilities'; -import { cloneDeep } from 'lodash'; - -import { InMemoryCache, ApolloReducerConfig, NormalizedCache } from '..'; - -disableFragmentWarnings(); - -describe('Cache', () => { - function createCache({ - initialState, - config, - }: { - initialState?: any; - config?: ApolloReducerConfig; - } = {}): ApolloCache { - return new InMemoryCache( - config || { addTypename: false }, - // XXX this is the old format. The tests need to be updated but since it is mapped down - ).restore(initialState ? initialState.apollo.data : {}); - } - - describe('readQuery', () => { - it('will read some data from the store', () => { - const proxy = createCache({ - initialState: { - apollo: { - data: { - ROOT_QUERY: { - a: 1, - b: 2, - c: 3, - }, - }, - }, - }, - }); - expect( - stripSymbols( - proxy.readQuery({ - query: gql` - { - a - } - `, - }), - ), - ).toEqual({ a: 1 }); - expect( - stripSymbols( - proxy.readQuery({ - query: gql` - { - b - c - } - `, - }), - ), - ).toEqual({ b: 2, c: 3 }); - expect( - stripSymbols( - proxy.readQuery({ - query: gql` - { - a - b - c - } - `, - }), - ), - ).toEqual({ a: 1, b: 2, c: 3 }); - }); - - it('will read some deeply nested data from the store', () => { - const proxy = createCache({ - initialState: { - apollo: { - data: { - ROOT_QUERY: { - a: 1, - b: 2, - c: 3, - d: { - type: 'id', - id: 'foo', - generated: false, - }, - }, - foo: { - e: 4, - f: 5, - g: 6, - h: { - type: 'id', - id: 'bar', - generated: false, - }, - }, - bar: { - i: 7, - j: 8, - k: 9, - }, - }, - }, - }, - }); - - expect( - stripSymbols( - proxy.readQuery({ - query: gql` - { - a - d { - e - } - } - `, - }), - ), - ).toEqual({ a: 1, d: { e: 4 } }); - expect( - stripSymbols( - proxy.readQuery({ - query: gql` - { - a - d { - e - h { - i - } - } - } - `, - }), - ), - ).toEqual({ a: 1, d: { e: 4, h: { i: 7 } } }); - expect( - stripSymbols( - proxy.readQuery({ - query: gql` - { - a - b - c - d { - e - f - g - h { - i - j - k - } - } - } - `, - }), - ), - ).toEqual({ - a: 1, - b: 2, - c: 3, - d: { e: 4, f: 5, g: 6, h: { i: 7, j: 8, k: 9 } }, - }); - }); - - it('will read some data from the store with variables', () => { - const proxy = createCache({ - initialState: { - apollo: { - data: { - ROOT_QUERY: { - 'field({"literal":true,"value":42})': 1, - 'field({"literal":false,"value":42})': 2, - }, - }, - }, - }, - }); - - expect( - stripSymbols( - proxy.readQuery({ - query: gql` - query($literal: Boolean, $value: Int) { - a: field(literal: true, value: 42) - b: field(literal: $literal, value: $value) - } - `, - variables: { - literal: false, - value: 42, - }, - }), - ), - ).toEqual({ a: 1, b: 2 }); - }); - - it('will read some data from the store with null variables', () => { - const proxy = createCache({ - initialState: { - apollo: { - data: { - ROOT_QUERY: { - 'field({"literal":false,"value":null})': 1, - }, - }, - }, - }, - }); - - expect( - stripSymbols( - proxy.readQuery({ - query: gql` - query($literal: Boolean, $value: Int) { - a: field(literal: $literal, value: $value) - } - `, - variables: { - literal: false, - value: null, - }, - }), - ), - ).toEqual({ a: 1 }); - }); - - it('should not mutate arguments passed in', () => { - const proxy = createCache({ - initialState: { - apollo: { - data: { - ROOT_QUERY: { - 'field({"literal":true,"value":42})': 1, - 'field({"literal":false,"value":42})': 2, - }, - }, - }, - }, - }); - - const options = { - query: gql` - query($literal: Boolean, $value: Int) { - a: field(literal: true, value: 42) - b: field(literal: $literal, value: $value) - } - `, - variables: { - literal: false, - value: 42, - }, - }; - - const preQueryCopy = cloneDeep(options); - expect(stripSymbols(proxy.readQuery(options))).toEqual({ a: 1, b: 2 }); - expect(preQueryCopy).toEqual(options); - }); - }); - - describe('readFragment', () => { - it('will throw an error when there is no fragment', () => { - const proxy = createCache(); - - expect(() => { - proxy.readFragment({ - id: 'x', - fragment: gql` - query { - a - b - c - } - `, - }); - }).toThrowError( - 'Found a query operation. No operations are allowed when using a fragment as a query. Only fragments are allowed.', - ); - expect(() => { - proxy.readFragment({ - id: 'x', - fragment: gql` - schema { - query: Query - } - `, - }); - }).toThrowError( - 'Found 0 fragments. `fragmentName` must be provided when there is not exactly 1 fragment.', - ); - }); - - it('will throw an error when there is more than one fragment but no fragment name', () => { - const proxy = createCache(); - - expect(() => { - proxy.readFragment({ - id: 'x', - fragment: gql` - fragment a on A { - a - } - - fragment b on B { - b - } - `, - }); - }).toThrowError( - 'Found 2 fragments. `fragmentName` must be provided when there is not exactly 1 fragment.', - ); - expect(() => { - proxy.readFragment({ - id: 'x', - fragment: gql` - fragment a on A { - a - } - - fragment b on B { - b - } - - fragment c on C { - c - } - `, - }); - }).toThrowError( - 'Found 3 fragments. `fragmentName` must be provided when there is not exactly 1 fragment.', - ); - }); - - it('will read some deeply nested data from the store at any id', () => { - const proxy = createCache({ - initialState: { - apollo: { - data: { - ROOT_QUERY: { - __typename: 'Type1', - a: 1, - b: 2, - c: 3, - d: { - type: 'id', - id: 'foo', - generated: false, - }, - }, - foo: { - __typename: 'Foo', - e: 4, - f: 5, - g: 6, - h: { - type: 'id', - id: 'bar', - generated: false, - }, - }, - bar: { - __typename: 'Bar', - i: 7, - j: 8, - k: 9, - }, - }, - }, - }, - }); - - expect( - stripSymbols( - proxy.readFragment({ - id: 'foo', - fragment: gql` - fragment fragmentFoo on Foo { - e - h { - i - } - } - `, - }), - ), - ).toEqual({ e: 4, h: { i: 7 } }); - expect( - stripSymbols( - proxy.readFragment({ - id: 'foo', - fragment: gql` - fragment fragmentFoo on Foo { - e - f - g - h { - i - j - k - } - } - `, - }), - ), - ).toEqual({ e: 4, f: 5, g: 6, h: { i: 7, j: 8, k: 9 } }); - expect( - stripSymbols( - proxy.readFragment({ - id: 'bar', - fragment: gql` - fragment fragmentBar on Bar { - i - } - `, - }), - ), - ).toEqual({ i: 7 }); - expect( - stripSymbols( - proxy.readFragment({ - id: 'bar', - fragment: gql` - fragment fragmentBar on Bar { - i - j - k - } - `, - }), - ), - ).toEqual({ i: 7, j: 8, k: 9 }); - expect( - stripSymbols( - proxy.readFragment({ - id: 'foo', - fragment: gql` - fragment fragmentFoo on Foo { - e - f - g - h { - i - j - k - } - } - - fragment fragmentBar on Bar { - i - j - k - } - `, - fragmentName: 'fragmentFoo', - }), - ), - ).toEqual({ e: 4, f: 5, g: 6, h: { i: 7, j: 8, k: 9 } }); - expect( - stripSymbols( - proxy.readFragment({ - id: 'bar', - fragment: gql` - fragment fragmentFoo on Foo { - e - f - g - h { - i - j - k - } - } - - fragment fragmentBar on Bar { - i - j - k - } - `, - fragmentName: 'fragmentBar', - }), - ), - ).toEqual({ i: 7, j: 8, k: 9 }); - }); - - it('will read some data from the store with variables', () => { - const proxy = createCache({ - initialState: { - apollo: { - data: { - foo: { - __typename: 'Foo', - 'field({"literal":true,"value":42})': 1, - 'field({"literal":false,"value":42})': 2, - }, - }, - }, - }, - }); - - expect( - stripSymbols( - proxy.readFragment({ - id: 'foo', - fragment: gql` - fragment foo on Foo { - a: field(literal: true, value: 42) - b: field(literal: $literal, value: $value) - } - `, - variables: { - literal: false, - value: 42, - }, - }), - ), - ).toEqual({ a: 1, b: 2 }); - }); - - it('will return null when an id that can’t be found is provided', () => { - const client1 = createCache(); - const client2 = createCache({ - initialState: { - apollo: { - data: { - bar: { __typename: 'Bar', a: 1, b: 2, c: 3 }, - }, - }, - }, - }); - const client3 = createCache({ - initialState: { - apollo: { - data: { - foo: { __typename: 'Foo', a: 1, b: 2, c: 3 }, - }, - }, - }, - }); - - expect( - stripSymbols( - client1.readFragment({ - id: 'foo', - fragment: gql` - fragment fooFragment on Foo { - a - b - c - } - `, - }), - ), - ).toEqual(null); - expect( - stripSymbols( - client2.readFragment({ - id: 'foo', - fragment: gql` - fragment fooFragment on Foo { - a - b - c - } - `, - }), - ), - ).toEqual(null); - expect( - stripSymbols( - client3.readFragment({ - id: 'foo', - fragment: gql` - fragment fooFragment on Foo { - a - b - c - } - `, - }), - ), - ).toEqual({ a: 1, b: 2, c: 3 }); - }); - }); - - describe('writeQuery', () => { - it('will write some data to the store', () => { - const proxy = createCache(); - - proxy.writeQuery({ - data: { a: 1 }, - query: gql` - { - a - } - `, - }); - - expect((proxy as InMemoryCache).extract()).toEqual({ - ROOT_QUERY: { - a: 1, - }, - }); - - proxy.writeQuery({ - data: { b: 2, c: 3 }, - query: gql` - { - b - c - } - `, - }); - - expect((proxy as InMemoryCache).extract()).toEqual({ - ROOT_QUERY: { - a: 1, - b: 2, - c: 3, - }, - }); - - proxy.writeQuery({ - data: { a: 4, b: 5, c: 6 }, - query: gql` - { - a - b - c - } - `, - }); - - expect((proxy as InMemoryCache).extract()).toEqual({ - ROOT_QUERY: { - a: 4, - b: 5, - c: 6, - }, - }); - }); - - it('will write some deeply nested data to the store', () => { - const proxy = createCache(); - - proxy.writeQuery({ - data: { a: 1, d: { e: 4 } }, - query: gql` - { - a - d { - e - } - } - `, - }); - - expect((proxy as InMemoryCache).extract()).toEqual({ - ROOT_QUERY: { - a: 1, - d: { - type: 'id', - id: '$ROOT_QUERY.d', - generated: true, - }, - }, - '$ROOT_QUERY.d': { - e: 4, - }, - }); - - proxy.writeQuery({ - data: { a: 1, d: { h: { i: 7 } } }, - query: gql` - { - a - d { - h { - i - } - } - } - `, - }); - - expect((proxy as InMemoryCache).extract()).toEqual({ - ROOT_QUERY: { - a: 1, - d: { - type: 'id', - id: '$ROOT_QUERY.d', - generated: true, - }, - }, - '$ROOT_QUERY.d': { - e: 4, - h: { - type: 'id', - id: '$ROOT_QUERY.d.h', - generated: true, - }, - }, - '$ROOT_QUERY.d.h': { - i: 7, - }, - }); - - proxy.writeQuery({ - data: { - a: 1, - b: 2, - c: 3, - d: { e: 4, f: 5, g: 6, h: { i: 7, j: 8, k: 9 } }, - }, - query: gql` - { - a - b - c - d { - e - f - g - h { - i - j - k - } - } - } - `, - }); - - expect((proxy as InMemoryCache).extract()).toEqual({ - ROOT_QUERY: { - a: 1, - b: 2, - c: 3, - d: { - type: 'id', - id: '$ROOT_QUERY.d', - generated: true, - }, - }, - '$ROOT_QUERY.d': { - e: 4, - f: 5, - g: 6, - h: { - type: 'id', - id: '$ROOT_QUERY.d.h', - generated: true, - }, - }, - '$ROOT_QUERY.d.h': { - i: 7, - j: 8, - k: 9, - }, - }); - }); - - it('will write some data to the store with variables', () => { - const proxy = createCache(); - - proxy.writeQuery({ - data: { - a: 1, - b: 2, - }, - query: gql` - query($literal: Boolean, $value: Int) { - a: field(literal: true, value: 42) - b: field(literal: $literal, value: $value) - } - `, - variables: { - literal: false, - value: 42, - }, - }); - - expect((proxy as InMemoryCache).extract()).toEqual({ - ROOT_QUERY: { - 'field({"literal":true,"value":42})': 1, - 'field({"literal":false,"value":42})': 2, - }, - }); - }); - it('will write some data to the store with variables where some are null', () => { - const proxy = createCache(); - - proxy.writeQuery({ - data: { - a: 1, - b: 2, - }, - query: gql` - query($literal: Boolean, $value: Int) { - a: field(literal: true, value: 42) - b: field(literal: $literal, value: $value) - } - `, - variables: { - literal: false, - value: null, - }, - }); - - expect((proxy as InMemoryCache).extract()).toEqual({ - ROOT_QUERY: { - 'field({"literal":true,"value":42})': 1, - 'field({"literal":false,"value":null})': 2, - }, - }); - }); - }); - - describe('writeFragment', () => { - it('will throw an error when there is no fragment', () => { - const proxy = createCache(); - - expect(() => { - proxy.writeFragment({ - data: {}, - id: 'x', - fragment: gql` - query { - a - b - c - } - `, - }); - }).toThrowError( - 'Found a query operation. No operations are allowed when using a fragment as a query. Only fragments are allowed.', - ); - expect(() => { - proxy.writeFragment({ - data: {}, - id: 'x', - fragment: gql` - schema { - query: Query - } - `, - }); - }).toThrowError( - 'Found 0 fragments. `fragmentName` must be provided when there is not exactly 1 fragment.', - ); - }); - - it('will throw an error when there is more than one fragment but no fragment name', () => { - const proxy = createCache(); - - expect(() => { - proxy.writeFragment({ - data: {}, - id: 'x', - fragment: gql` - fragment a on A { - a - } - - fragment b on B { - b - } - `, - }); - }).toThrowError( - 'Found 2 fragments. `fragmentName` must be provided when there is not exactly 1 fragment.', - ); - expect(() => { - proxy.writeFragment({ - data: {}, - id: 'x', - fragment: gql` - fragment a on A { - a - } - - fragment b on B { - b - } - - fragment c on C { - c - } - `, - }); - }).toThrowError( - 'Found 3 fragments. `fragmentName` must be provided when there is not exactly 1 fragment.', - ); - }); - - it('will write some deeply nested data into the store at any id', () => { - const proxy = createCache({ - config: { dataIdFromObject: (o: any) => o.id, addTypename: false }, - }); - - proxy.writeFragment({ - data: { __typename: 'Foo', e: 4, h: { id: 'bar', i: 7 } }, - id: 'foo', - fragment: gql` - fragment fragmentFoo on Foo { - e - h { - i - } - } - `, - }); - - expect((proxy as InMemoryCache).extract()).toMatchSnapshot(); - proxy.writeFragment({ - data: { __typename: 'Foo', f: 5, g: 6, h: { id: 'bar', j: 8, k: 9 } }, - id: 'foo', - fragment: gql` - fragment fragmentFoo on Foo { - f - g - h { - j - k - } - } - `, - }); - - expect((proxy as InMemoryCache).extract()).toMatchSnapshot(); - - proxy.writeFragment({ - data: { i: 10, __typename: 'Bar' }, - id: 'bar', - fragment: gql` - fragment fragmentBar on Bar { - i - } - `, - }); - - expect((proxy as InMemoryCache).extract()).toMatchSnapshot(); - - proxy.writeFragment({ - data: { j: 11, k: 12, __typename: 'Bar' }, - id: 'bar', - fragment: gql` - fragment fragmentBar on Bar { - j - k - } - `, - }); - - expect((proxy as InMemoryCache).extract()).toMatchSnapshot(); - - proxy.writeFragment({ - data: { - __typename: 'Foo', - e: 4, - f: 5, - g: 6, - h: { __typename: 'Bar', id: 'bar', i: 7, j: 8, k: 9 }, - }, - id: 'foo', - fragment: gql` - fragment fooFragment on Foo { - e - f - g - h { - i - j - k - } - } - - fragment barFragment on Bar { - i - j - k - } - `, - fragmentName: 'fooFragment', - }); - - expect((proxy as InMemoryCache).extract()).toMatchSnapshot(); - - proxy.writeFragment({ - data: { __typename: 'Bar', i: 10, j: 11, k: 12 }, - id: 'bar', - fragment: gql` - fragment fooFragment on Foo { - e - f - g - h { - i - j - k - } - } - - fragment barFragment on Bar { - i - j - k - } - `, - fragmentName: 'barFragment', - }); - - expect((proxy as InMemoryCache).extract()).toMatchSnapshot(); - }); - it('writes data that can be read back', () => { - const proxy = createCache({ - config: { addTypename: true }, - }); - const readWriteFragment = gql` - fragment aFragment on query { - getSomething { - id - } - } - `; - const data = { - __typename: 'query', - getSomething: { id: '123', __typename: 'Something' }, - }; - proxy.writeFragment({ - data, - id: 'query', - fragment: readWriteFragment, - }); - - const result = proxy.readFragment({ - fragment: readWriteFragment, - id: 'query', - }); - expect(stripSymbols(result)).toEqual(data); - }); - - it('will write some data to the store with variables', () => { - const proxy = createCache({ - config: { addTypename: true }, - }); - - proxy.writeFragment({ - data: { - a: 1, - b: 2, - __typename: 'Foo', - }, - id: 'foo', - fragment: gql` - fragment foo on Foo { - a: field(literal: true, value: 42) - b: field(literal: $literal, value: $value) - } - `, - variables: { - literal: false, - value: 42, - }, - }); - - expect((proxy as InMemoryCache).extract()).toEqual({ - foo: { - __typename: 'Foo', - 'field({"literal":true,"value":42})': 1, - 'field({"literal":false,"value":42})': 2, - }, - }); - }); - }); - - describe('performTransaction', () => { - it('will not broadcast mid-transaction', () => { - const cache = createCache(); - - let numBroadcasts = 0; - - const query = gql` - { - a - } - `; - - cache.watch({ - query, - optimistic: false, - callback: () => { - numBroadcasts++; - }, - }); - - expect(numBroadcasts).toEqual(0); - - cache.performTransaction(proxy => { - proxy.writeQuery({ - data: { a: 1 }, - query, - }); - - expect(numBroadcasts).toEqual(0); - - proxy.writeQuery({ - data: { a: 4, b: 5, c: 6 }, - query: gql` - { - a - b - c - } - `, - }); - - expect(numBroadcasts).toEqual(0); - }); - - expect(numBroadcasts).toEqual(1); - }); - }); - - describe('performOptimisticTransaction', () => { - it('will only broadcast once', () => { - const cache = createCache(); - - let numBroadcasts = 0; - - const query = gql` - { - a - } - `; - - cache.watch({ - query, - optimistic: true, - callback: () => { - numBroadcasts++; - }, - }); - - expect(numBroadcasts).toEqual(0); - - cache.recordOptimisticTransaction(proxy => { - proxy.writeQuery({ - data: { a: 1 }, - query, - }); - - expect(numBroadcasts).toEqual(0); - - proxy.writeQuery({ - data: { a: 4, b: 5, c: 6 }, - query: gql` - { - a - b - c - } - `, - }); - - expect(numBroadcasts).toEqual(0); - }, 1); - - expect(numBroadcasts).toEqual(1); - }); - }); -}); +import { ApolloCache } from 'apollo-cache'; +import gql, { disableFragmentWarnings } from 'graphql-tag'; +import { stripSymbols } from 'apollo-utilities'; +import { cloneDeep } from 'lodash'; + +import { InMemoryCache, ApolloReducerConfig, NormalizedCache } from '..'; + +disableFragmentWarnings(); + +describe('Cache', () => { + function createCache({ + initialState, + config, + }: { + initialState?: any; + config?: ApolloReducerConfig; + } = {}): ApolloCache { + return new InMemoryCache( + config || { addTypename: false }, + // XXX this is the old format. The tests need to be updated but since it is mapped down + ).restore(initialState ? initialState.apollo.data : {}); + } + + describe('readQuery', () => { + it('will read some data from the store', () => { + const proxy = createCache({ + initialState: { + apollo: { + data: { + ROOT_QUERY: { + a: 1, + b: 2, + c: 3, + }, + }, + }, + }, + }); + expect( + stripSymbols( + proxy.readQuery({ + query: gql` + { + a + } + `, + }), + ), + ).toEqual({ a: 1 }); + expect( + stripSymbols( + proxy.readQuery({ + query: gql` + { + b + c + } + `, + }), + ), + ).toEqual({ b: 2, c: 3 }); + expect( + stripSymbols( + proxy.readQuery({ + query: gql` + { + a + b + c + } + `, + }), + ), + ).toEqual({ a: 1, b: 2, c: 3 }); + }); + + it('will read some deeply nested data from the store', () => { + const proxy = createCache({ + initialState: { + apollo: { + data: { + ROOT_QUERY: { + a: 1, + b: 2, + c: 3, + d: { + type: 'id', + id: 'foo', + generated: false, + }, + }, + foo: { + e: 4, + f: 5, + g: 6, + h: { + type: 'id', + id: 'bar', + generated: false, + }, + }, + bar: { + i: 7, + j: 8, + k: 9, + }, + }, + }, + }, + }); + + expect( + stripSymbols( + proxy.readQuery({ + query: gql` + { + a + d { + e + } + } + `, + }), + ), + ).toEqual({ a: 1, d: { e: 4 } }); + expect( + stripSymbols( + proxy.readQuery({ + query: gql` + { + a + d { + e + h { + i + } + } + } + `, + }), + ), + ).toEqual({ a: 1, d: { e: 4, h: { i: 7 } } }); + expect( + stripSymbols( + proxy.readQuery({ + query: gql` + { + a + b + c + d { + e + f + g + h { + i + j + k + } + } + } + `, + }), + ), + ).toEqual({ + a: 1, + b: 2, + c: 3, + d: { e: 4, f: 5, g: 6, h: { i: 7, j: 8, k: 9 } }, + }); + }); + + it('will read some data from the store with variables', () => { + const proxy = createCache({ + initialState: { + apollo: { + data: { + ROOT_QUERY: { + 'field({"literal":true,"value":42})': 1, + 'field({"literal":false,"value":42})': 2, + }, + }, + }, + }, + }); + + expect( + stripSymbols( + proxy.readQuery({ + query: gql` + query($literal: Boolean, $value: Int) { + a: field(literal: true, value: 42) + b: field(literal: $literal, value: $value) + } + `, + variables: { + literal: false, + value: 42, + }, + }), + ), + ).toEqual({ a: 1, b: 2 }); + }); + + it('will read some data from the store with null variables', () => { + const proxy = createCache({ + initialState: { + apollo: { + data: { + ROOT_QUERY: { + 'field({"literal":false,"value":null})': 1, + }, + }, + }, + }, + }); + + expect( + stripSymbols( + proxy.readQuery({ + query: gql` + query($literal: Boolean, $value: Int) { + a: field(literal: $literal, value: $value) + } + `, + variables: { + literal: false, + value: null, + }, + }), + ), + ).toEqual({ a: 1 }); + }); + + it('should not mutate arguments passed in', () => { + const proxy = createCache({ + initialState: { + apollo: { + data: { + ROOT_QUERY: { + 'field({"literal":true,"value":42})': 1, + 'field({"literal":false,"value":42})': 2, + }, + }, + }, + }, + }); + + const options = { + query: gql` + query($literal: Boolean, $value: Int) { + a: field(literal: true, value: 42) + b: field(literal: $literal, value: $value) + } + `, + variables: { + literal: false, + value: 42, + }, + }; + + const preQueryCopy = cloneDeep(options); + expect(stripSymbols(proxy.readQuery(options))).toEqual({ a: 1, b: 2 }); + expect(preQueryCopy).toEqual(options); + }); + }); + + describe('readFragment', () => { + it('will throw an error when there is no fragment', () => { + const proxy = createCache(); + + expect(() => { + proxy.readFragment({ + id: 'x', + fragment: gql` + query { + a + b + c + } + `, + }); + }).toThrowError( + 'Found a query operation. No operations are allowed when using a fragment as a query. Only fragments are allowed.', + ); + expect(() => { + proxy.readFragment({ + id: 'x', + fragment: gql` + schema { + query: Query + } + `, + }); + }).toThrowError( + 'Found 0 fragments. `fragmentName` must be provided when there is not exactly 1 fragment.', + ); + }); + + it('will throw an error when there is more than one fragment but no fragment name', () => { + const proxy = createCache(); + + expect(() => { + proxy.readFragment({ + id: 'x', + fragment: gql` + fragment a on A { + a + } + + fragment b on B { + b + } + `, + }); + }).toThrowError( + 'Found 2 fragments. `fragmentName` must be provided when there is not exactly 1 fragment.', + ); + expect(() => { + proxy.readFragment({ + id: 'x', + fragment: gql` + fragment a on A { + a + } + + fragment b on B { + b + } + + fragment c on C { + c + } + `, + }); + }).toThrowError( + 'Found 3 fragments. `fragmentName` must be provided when there is not exactly 1 fragment.', + ); + }); + + it('will read some deeply nested data from the store at any id', () => { + const proxy = createCache({ + initialState: { + apollo: { + data: { + ROOT_QUERY: { + __typename: 'Type1', + a: 1, + b: 2, + c: 3, + d: { + type: 'id', + id: 'foo', + generated: false, + }, + }, + foo: { + __typename: 'Foo', + e: 4, + f: 5, + g: 6, + h: { + type: 'id', + id: 'bar', + generated: false, + }, + }, + bar: { + __typename: 'Bar', + i: 7, + j: 8, + k: 9, + }, + }, + }, + }, + }); + + expect( + stripSymbols( + proxy.readFragment({ + id: 'foo', + fragment: gql` + fragment fragmentFoo on Foo { + e + h { + i + } + } + `, + }), + ), + ).toEqual({ e: 4, h: { i: 7 } }); + expect( + stripSymbols( + proxy.readFragment({ + id: 'foo', + fragment: gql` + fragment fragmentFoo on Foo { + e + f + g + h { + i + j + k + } + } + `, + }), + ), + ).toEqual({ e: 4, f: 5, g: 6, h: { i: 7, j: 8, k: 9 } }); + expect( + stripSymbols( + proxy.readFragment({ + id: 'bar', + fragment: gql` + fragment fragmentBar on Bar { + i + } + `, + }), + ), + ).toEqual({ i: 7 }); + expect( + stripSymbols( + proxy.readFragment({ + id: 'bar', + fragment: gql` + fragment fragmentBar on Bar { + i + j + k + } + `, + }), + ), + ).toEqual({ i: 7, j: 8, k: 9 }); + expect( + stripSymbols( + proxy.readFragment({ + id: 'foo', + fragment: gql` + fragment fragmentFoo on Foo { + e + f + g + h { + i + j + k + } + } + + fragment fragmentBar on Bar { + i + j + k + } + `, + fragmentName: 'fragmentFoo', + }), + ), + ).toEqual({ e: 4, f: 5, g: 6, h: { i: 7, j: 8, k: 9 } }); + expect( + stripSymbols( + proxy.readFragment({ + id: 'bar', + fragment: gql` + fragment fragmentFoo on Foo { + e + f + g + h { + i + j + k + } + } + + fragment fragmentBar on Bar { + i + j + k + } + `, + fragmentName: 'fragmentBar', + }), + ), + ).toEqual({ i: 7, j: 8, k: 9 }); + }); + + it('will read some data from the store with variables', () => { + const proxy = createCache({ + initialState: { + apollo: { + data: { + foo: { + __typename: 'Foo', + 'field({"literal":true,"value":42})': 1, + 'field({"literal":false,"value":42})': 2, + }, + }, + }, + }, + }); + + expect( + stripSymbols( + proxy.readFragment({ + id: 'foo', + fragment: gql` + fragment foo on Foo { + a: field(literal: true, value: 42) + b: field(literal: $literal, value: $value) + } + `, + variables: { + literal: false, + value: 42, + }, + }), + ), + ).toEqual({ a: 1, b: 2 }); + }); + + it('will return null when an id that can’t be found is provided', () => { + const client1 = createCache(); + const client2 = createCache({ + initialState: { + apollo: { + data: { + bar: { __typename: 'Bar', a: 1, b: 2, c: 3 }, + }, + }, + }, + }); + const client3 = createCache({ + initialState: { + apollo: { + data: { + foo: { __typename: 'Foo', a: 1, b: 2, c: 3 }, + }, + }, + }, + }); + + expect( + stripSymbols( + client1.readFragment({ + id: 'foo', + fragment: gql` + fragment fooFragment on Foo { + a + b + c + } + `, + }), + ), + ).toEqual(null); + expect( + stripSymbols( + client2.readFragment({ + id: 'foo', + fragment: gql` + fragment fooFragment on Foo { + a + b + c + } + `, + }), + ), + ).toEqual(null); + expect( + stripSymbols( + client3.readFragment({ + id: 'foo', + fragment: gql` + fragment fooFragment on Foo { + a + b + c + } + `, + }), + ), + ).toEqual({ a: 1, b: 2, c: 3 }); + }); + }); + + describe('writeQuery', () => { + it('will write some data to the store', () => { + const proxy = createCache(); + + proxy.writeQuery({ + data: { a: 1 }, + query: gql` + { + a + } + `, + }); + + expect((proxy as InMemoryCache).extract()).toEqual({ + ROOT_QUERY: { + a: 1, + }, + }); + + proxy.writeQuery({ + data: { b: 2, c: 3 }, + query: gql` + { + b + c + } + `, + }); + + expect((proxy as InMemoryCache).extract()).toEqual({ + ROOT_QUERY: { + a: 1, + b: 2, + c: 3, + }, + }); + + proxy.writeQuery({ + data: { a: 4, b: 5, c: 6 }, + query: gql` + { + a + b + c + } + `, + }); + + expect((proxy as InMemoryCache).extract()).toEqual({ + ROOT_QUERY: { + a: 4, + b: 5, + c: 6, + }, + }); + }); + + it('will write some deeply nested data to the store', () => { + const proxy = createCache(); + + proxy.writeQuery({ + data: { a: 1, d: { e: 4 } }, + query: gql` + { + a + d { + e + } + } + `, + }); + + expect((proxy as InMemoryCache).extract()).toEqual({ + ROOT_QUERY: { + a: 1, + d: { + type: 'id', + id: '$ROOT_QUERY.d', + generated: true, + }, + }, + '$ROOT_QUERY.d': { + e: 4, + }, + }); + + proxy.writeQuery({ + data: { a: 1, d: { h: { i: 7 } } }, + query: gql` + { + a + d { + h { + i + } + } + } + `, + }); + + expect((proxy as InMemoryCache).extract()).toEqual({ + ROOT_QUERY: { + a: 1, + d: { + type: 'id', + id: '$ROOT_QUERY.d', + generated: true, + }, + }, + '$ROOT_QUERY.d': { + e: 4, + h: { + type: 'id', + id: '$ROOT_QUERY.d.h', + generated: true, + }, + }, + '$ROOT_QUERY.d.h': { + i: 7, + }, + }); + + proxy.writeQuery({ + data: { + a: 1, + b: 2, + c: 3, + d: { e: 4, f: 5, g: 6, h: { i: 7, j: 8, k: 9 } }, + }, + query: gql` + { + a + b + c + d { + e + f + g + h { + i + j + k + } + } + } + `, + }); + + expect((proxy as InMemoryCache).extract()).toEqual({ + ROOT_QUERY: { + a: 1, + b: 2, + c: 3, + d: { + type: 'id', + id: '$ROOT_QUERY.d', + generated: true, + }, + }, + '$ROOT_QUERY.d': { + e: 4, + f: 5, + g: 6, + h: { + type: 'id', + id: '$ROOT_QUERY.d.h', + generated: true, + }, + }, + '$ROOT_QUERY.d.h': { + i: 7, + j: 8, + k: 9, + }, + }); + }); + + it('will write some data to the store with variables', () => { + const proxy = createCache(); + + proxy.writeQuery({ + data: { + a: 1, + b: 2, + }, + query: gql` + query($literal: Boolean, $value: Int) { + a: field(literal: true, value: 42) + b: field(literal: $literal, value: $value) + } + `, + variables: { + literal: false, + value: 42, + }, + }); + + expect((proxy as InMemoryCache).extract()).toEqual({ + ROOT_QUERY: { + 'field({"literal":true,"value":42})': 1, + 'field({"literal":false,"value":42})': 2, + }, + }); + }); + it('will write some data to the store with variables where some are null', () => { + const proxy = createCache(); + + proxy.writeQuery({ + data: { + a: 1, + b: 2, + }, + query: gql` + query($literal: Boolean, $value: Int) { + a: field(literal: true, value: 42) + b: field(literal: $literal, value: $value) + } + `, + variables: { + literal: false, + value: null, + }, + }); + + expect((proxy as InMemoryCache).extract()).toEqual({ + ROOT_QUERY: { + 'field({"literal":true,"value":42})': 1, + 'field({"literal":false,"value":null})': 2, + }, + }); + }); + }); + + describe('writeFragment', () => { + it('will throw an error when there is no fragment', () => { + const proxy = createCache(); + + expect(() => { + proxy.writeFragment({ + data: {}, + id: 'x', + fragment: gql` + query { + a + b + c + } + `, + }); + }).toThrowError( + 'Found a query operation. No operations are allowed when using a fragment as a query. Only fragments are allowed.', + ); + expect(() => { + proxy.writeFragment({ + data: {}, + id: 'x', + fragment: gql` + schema { + query: Query + } + `, + }); + }).toThrowError( + 'Found 0 fragments. `fragmentName` must be provided when there is not exactly 1 fragment.', + ); + }); + + it('will throw an error when there is more than one fragment but no fragment name', () => { + const proxy = createCache(); + + expect(() => { + proxy.writeFragment({ + data: {}, + id: 'x', + fragment: gql` + fragment a on A { + a + } + + fragment b on B { + b + } + `, + }); + }).toThrowError( + 'Found 2 fragments. `fragmentName` must be provided when there is not exactly 1 fragment.', + ); + expect(() => { + proxy.writeFragment({ + data: {}, + id: 'x', + fragment: gql` + fragment a on A { + a + } + + fragment b on B { + b + } + + fragment c on C { + c + } + `, + }); + }).toThrowError( + 'Found 3 fragments. `fragmentName` must be provided when there is not exactly 1 fragment.', + ); + }); + + it('will write some deeply nested data into the store at any id', () => { + const proxy = createCache({ + config: { dataIdFromObject: (o: any) => o.id, addTypename: false }, + }); + + proxy.writeFragment({ + data: { __typename: 'Foo', e: 4, h: { id: 'bar', i: 7 } }, + id: 'foo', + fragment: gql` + fragment fragmentFoo on Foo { + e + h { + i + } + } + `, + }); + + expect((proxy as InMemoryCache).extract()).toMatchSnapshot(); + proxy.writeFragment({ + data: { __typename: 'Foo', f: 5, g: 6, h: { id: 'bar', j: 8, k: 9 } }, + id: 'foo', + fragment: gql` + fragment fragmentFoo on Foo { + f + g + h { + j + k + } + } + `, + }); + + expect((proxy as InMemoryCache).extract()).toMatchSnapshot(); + + proxy.writeFragment({ + data: { i: 10, __typename: 'Bar' }, + id: 'bar', + fragment: gql` + fragment fragmentBar on Bar { + i + } + `, + }); + + expect((proxy as InMemoryCache).extract()).toMatchSnapshot(); + + proxy.writeFragment({ + data: { j: 11, k: 12, __typename: 'Bar' }, + id: 'bar', + fragment: gql` + fragment fragmentBar on Bar { + j + k + } + `, + }); + + expect((proxy as InMemoryCache).extract()).toMatchSnapshot(); + + proxy.writeFragment({ + data: { + __typename: 'Foo', + e: 4, + f: 5, + g: 6, + h: { __typename: 'Bar', id: 'bar', i: 7, j: 8, k: 9 }, + }, + id: 'foo', + fragment: gql` + fragment fooFragment on Foo { + e + f + g + h { + i + j + k + } + } + + fragment barFragment on Bar { + i + j + k + } + `, + fragmentName: 'fooFragment', + }); + + expect((proxy as InMemoryCache).extract()).toMatchSnapshot(); + + proxy.writeFragment({ + data: { __typename: 'Bar', i: 10, j: 11, k: 12 }, + id: 'bar', + fragment: gql` + fragment fooFragment on Foo { + e + f + g + h { + i + j + k + } + } + + fragment barFragment on Bar { + i + j + k + } + `, + fragmentName: 'barFragment', + }); + + expect((proxy as InMemoryCache).extract()).toMatchSnapshot(); + }); + it('writes data that can be read back', () => { + const proxy = createCache({ + config: { addTypename: true }, + }); + const readWriteFragment = gql` + fragment aFragment on query { + getSomething { + id + } + } + `; + const data = { + __typename: 'query', + getSomething: { id: '123', __typename: 'Something' }, + }; + proxy.writeFragment({ + data, + id: 'query', + fragment: readWriteFragment, + }); + + const result = proxy.readFragment({ + fragment: readWriteFragment, + id: 'query', + }); + expect(stripSymbols(result)).toEqual(data); + }); + + it('will write some data to the store with variables', () => { + const proxy = createCache({ + config: { addTypename: true }, + }); + + proxy.writeFragment({ + data: { + a: 1, + b: 2, + __typename: 'Foo', + }, + id: 'foo', + fragment: gql` + fragment foo on Foo { + a: field(literal: true, value: 42) + b: field(literal: $literal, value: $value) + } + `, + variables: { + literal: false, + value: 42, + }, + }); + + expect((proxy as InMemoryCache).extract()).toEqual({ + foo: { + __typename: 'Foo', + 'field({"literal":true,"value":42})': 1, + 'field({"literal":false,"value":42})': 2, + }, + }); + }); + }); + + describe('performTransaction', () => { + it('will not broadcast mid-transaction', () => { + const cache = createCache(); + + let numBroadcasts = 0; + + const query = gql` + { + a + } + `; + + cache.watch({ + query, + optimistic: false, + callback: () => { + numBroadcasts++; + }, + }); + + expect(numBroadcasts).toEqual(0); + + cache.performTransaction(proxy => { + proxy.writeQuery({ + data: { a: 1 }, + query, + }); + + expect(numBroadcasts).toEqual(0); + + proxy.writeQuery({ + data: { a: 4, b: 5, c: 6 }, + query: gql` + { + a + b + c + } + `, + }); + + expect(numBroadcasts).toEqual(0); + }); + + expect(numBroadcasts).toEqual(1); + }); + }); + + describe('performOptimisticTransaction', () => { + it('will only broadcast once', () => { + const cache = createCache(); + + let numBroadcasts = 0; + + const query = gql` + { + a + } + `; + + cache.watch({ + query, + optimistic: true, + callback: () => { + numBroadcasts++; + }, + }); + + expect(numBroadcasts).toEqual(0); + + cache.recordOptimisticTransaction(proxy => { + proxy.writeQuery({ + data: { a: 1 }, + query, + }); + + expect(numBroadcasts).toEqual(0); + + proxy.writeQuery({ + data: { a: 4, b: 5, c: 6 }, + query: gql` + { + a + b + c + } + `, + }); + + expect(numBroadcasts).toEqual(0); + }, 1); + + expect(numBroadcasts).toEqual(1); + }); + }); +});