Skip to content

Commit

Permalink
Merge pull request #1635 from apollographql/fm-usability
Browse files Browse the repository at this point in the history
FragmentMatcher warning
  • Loading branch information
helfer authored Apr 30, 2017
2 parents cf2ed55 + e104aec commit e85c39e
Show file tree
Hide file tree
Showing 12 changed files with 331 additions and 212 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Change log

### vNEXT

- Feature: Print a warning when heuristically matching fragments on interface/union [PR #1635](https://github.com/apollographql/apollo-client/pull/1635)

### 1.1.1
- Fix: Remove ability to set default fetchPolicy, which broke polling queries [PR #1630](https://github.com/apollographql/apollo-client/pull/1630)
Expand Down
13 changes: 12 additions & 1 deletion src/data/fragmentMatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import {
isTest,
} from '../util/environment';

import {
warnOnce,
} from '../util/warnOnce';

export interface FragmentMatcherInterface {
match(idValue: IdValue, typeCondition: string, context: ReadStoreContext): boolean;
}
Expand Down Expand Up @@ -122,6 +126,8 @@ export class HeuristicFragmentMatcher implements FragmentMatcherInterface {
console.warn(`You're using fragments in your queries, but don't have the addTypename:
true option set in Apollo Client. Please turn on that option so that we can accurately
match fragments.`);
console.warn(`DEPRECATION WARNING: using fragments without __typename is unsupported behavior ` +
`and will be removed in future versions of Apollo client. You should fix this and set addTypename to true now.`);

/* istanbul ignore if */
if (!isTest()) {
Expand All @@ -142,7 +148,12 @@ export class HeuristicFragmentMatcher implements FragmentMatcherInterface {
// 1. A fragment on a non-matching concrete type or interface or union
// 2. A fragment on a matching interface or union
// If it's 1, we don't want to return anything, if it's 2 we want to match. We can't tell the
// difference, so for now, we just do our best to resolve the fragment but turn on partial data
// difference, so we warn the user, but still try to match it (backcompat).
warnOnce(`You are using the simple (heuristic) fragment matcher, but your queries contain union or interface types.
Apollo Client will not be able to able to accurately map fragments.` +
`To make this error go away, use the IntrospectionFragmentMatcher as described in the docs: ` +
`http://dev.apollodata.com/react/initialization.html#fragment-matcher`, 'error');

context.returnPartialData = true;
return true;
}
Expand Down
16 changes: 9 additions & 7 deletions src/data/writeToStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,15 @@ export function writeSelectionSetToStore({
const resultFieldKey: string = resultKeyNameFromField(selection);
const value: any = result[resultFieldKey];

if (value !== undefined) {
writeFieldToStore({
dataId,
value,
field: selection,
context,
});
if (included) {
if (typeof value !== 'undefined') {
writeFieldToStore({
dataId,
value,
field: selection,
context,
});
}
}
} else if (isInlineFragment(selection)) {
if (included) {
Expand Down
15 changes: 15 additions & 0 deletions src/util/warnOnce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

const haveWarned = Object.create({});

export function warnOnce(msg: string, type = 'warn') {
if (!haveWarned[msg]) {
haveWarned[msg] = true;
switch (type) {
case 'error':
console.error(msg);
break;
default:
console.warn(msg);
}
}
}
8 changes: 7 additions & 1 deletion test/ApolloClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -504,13 +504,14 @@ describe('ApolloClient', () => {
});

client.writeFragment({
data: { e: 4, h: { id: 'bar', i: 7 } },
data: { __typename: 'Foo', e: 4, h: { id: 'bar', i: 7 } },
id: 'foo',
fragment: gql`fragment fragmentFoo on Foo { e h { i } }`,
});

assert.deepEqual(client.store.getState().apollo.data, {
'foo': {
__typename: 'Foo',
e: 4,
h: {
type: 'id',
Expand All @@ -531,6 +532,7 @@ describe('ApolloClient', () => {

assert.deepEqual(client.store.getState().apollo.data, {
'foo': {
__typename: 'Foo',
e: 4,
f: 5,
g: 6,
Expand All @@ -555,6 +557,7 @@ describe('ApolloClient', () => {

assert.deepEqual(client.store.getState().apollo.data, {
'foo': {
__typename: 'Foo',
e: 4,
f: 5,
g: 6,
Expand All @@ -579,6 +582,7 @@ describe('ApolloClient', () => {

assert.deepEqual(client.store.getState().apollo.data, {
'foo': {
__typename: 'Foo',
e: 4,
f: 5,
g: 6,
Expand All @@ -604,6 +608,7 @@ describe('ApolloClient', () => {

assert.deepEqual(client.store.getState().apollo.data, {
'foo': {
__typename: 'Foo',
e: 4,
f: 5,
g: 6,
Expand All @@ -629,6 +634,7 @@ describe('ApolloClient', () => {

assert.deepEqual(client.store.getState().apollo.data, {
'foo': {
__typename: 'Foo',
e: 4,
f: 5,
g: 6,
Expand Down
133 changes: 71 additions & 62 deletions test/diffAgainstStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import {
import { writeQueryToStore } from '../src/data/writeToStore';

import gql from 'graphql-tag';
import {
withError,
} from './util/wrap';

import {
HeuristicFragmentMatcher,
Expand Down Expand Up @@ -87,76 +90,82 @@ describe('diffing queries against the store', () => {
assert.deepEqual(store['1'], result.people_one);
});

it('does not swallow errors other than field errors', () => {
const firstQuery = gql`
query {
person {
powers
}
}`;
const firstResult = {
person: {
powers: 'the force',
},
};
const store = writeQueryToStore({
result: firstResult,
query: firstQuery,
});
const unionQuery = gql`
query {
...notARealFragment
}`;
assert.throws(() => {
diffQueryAgainstStore({
store,
query: unionQuery,
it('does not swallow errors other than field errors', (done) => {
withError( () => {
const firstQuery = gql`
query {
person {
powers
}
}`;
const firstResult = {
person: {
powers: 'the force',
},
};
const store = writeQueryToStore({
result: firstResult,
query: firstQuery,
});
}, /No fragment/);
const unionQuery = gql`
query {
...notARealFragment
}`;
assert.throws(() => {
diffQueryAgainstStore({
store,
query: unionQuery,
});
}, /No fragment/);
done();
}, /IntrospectionFragmentMatcher/);
});

it('does not error on a correct query with union typed fragments', () => {
const firstQuery = gql`
query {
person {
__typename
firstName
lastName
}
}`;
const firstResult = {
person: {
__typename: 'Author',
firstName: 'John',
lastName: 'Smith',
},
};
const store = writeQueryToStore({
result: firstResult,
query: firstQuery,
});
const unionQuery = gql`
query {
person {
__typename
... on Author {
it('does not error on a correct query with union typed fragments', (done) => {
withError(() => {
const firstQuery = gql`
query {
person {
__typename
firstName
lastName
}
... on Jedi {
powers
}`;
const firstResult = {
person: {
__typename: 'Author',
firstName: 'John',
lastName: 'Smith',
},
};
const store = writeQueryToStore({
result: firstResult,
query: firstQuery,
});
const unionQuery = gql`
query {
person {
__typename
... on Author {
firstName
lastName
}
... on Jedi {
powers
}
}
}
}`;
const { isMissing } = diffQueryAgainstStore({
store,
query: unionQuery,
returnPartialData: false,
fragmentMatcherFunction,
});
}`;
const { isMissing } = diffQueryAgainstStore({
store,
query: unionQuery,
returnPartialData: false,
fragmentMatcherFunction,
});

assert.isTrue(isMissing);
assert.isTrue(isMissing);
done();
}, /IntrospectionFragmentMatcher/);
});

it('does not error on a query with fields missing from all but one named fragment', () => {
Expand Down
10 changes: 5 additions & 5 deletions test/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ describe('ReduxDataProxy', () => {
},
},
'foo': {
__typename: 'Type2',
__typename: 'Foo',
e: 4,
f: 5,
g: 6,
Expand All @@ -214,7 +214,7 @@ describe('ReduxDataProxy', () => {
},
},
'bar': {
__typename: 'Type3',
__typename: 'Bar',
i: 7,
j: 8,
k: 9,
Expand Down Expand Up @@ -264,7 +264,7 @@ describe('ReduxDataProxy', () => {
apollo: {
data: {
'foo': {
__typename: 'Type1',
__typename: 'Foo',
'field({"literal":true,"value":42})': 1,
'field({"literal":false,"value":42})': 2,
},
Expand Down Expand Up @@ -294,7 +294,7 @@ describe('ReduxDataProxy', () => {
initialState: {
apollo: {
data: {
'bar': { __typename: 'Type1', a: 1, b: 2, c: 3 },
'bar': { __typename: 'Bar', a: 1, b: 2, c: 3 },
},
},
},
Expand All @@ -303,7 +303,7 @@ describe('ReduxDataProxy', () => {
initialState: {
apollo: {
data: {
'foo': { __typename: 'Type1', a: 1, b: 2, c: 3 },
'foo': { __typename: 'Foo', a: 1, b: 2, c: 3 },
},
},
},
Expand Down
Loading

0 comments on commit e85c39e

Please sign in to comment.