Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FragmentMatcher warning #1635

Merged
merged 5 commits into from
Apr 30, 2017
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
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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this condition is never true. Can you confirm @helfer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mdebbar What makes you think that it's never true?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nvm.. I got confused with the nesting, I thought it was:

if (included) {
  ...
} 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