Skip to content

Commit

Permalink
add a failing test case for multiple model calls
Browse files Browse the repository at this point in the history
As noted[1] by @viniciussbs, there's a flaw in this implementation. When
`model()` is called twice on a route due to a query params change, all
watchQuery subscriptions are cancelled, including the brand new one with
the updated query params.

Really we should only be cancelling the old queries that existed prior
to the model reload.

[1]: #20 (comment)
  • Loading branch information
bgentry committed Sep 3, 2017
1 parent f972828 commit 9ffe7b5
Show file tree
Hide file tree
Showing 10 changed files with 231 additions and 8 deletions.
74 changes: 68 additions & 6 deletions tests/acceptance/query-and-unsubscribe-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,27 @@ moduleForAcceptance('Acceptance | main', {
},
});

const mockHuman = {
id: '1000',
name: 'Luke Skywalker',
__typename: 'Human',
};

const mockDroid = {
id: '1001',
name: 'BB8',
__typename: 'Droid',
};

test('visiting /luke', function(assert) {
let done = assert.async();
let human = mockHuman;

let mockHuman = {
id: '1000',
name: 'Luke Skywalker',
};
addResolveFunctionsToSchema(this.pretender.schema, {
Query: {
human(obj, args) {
assert.deepEqual(args, { id: '1000' });
return mockHuman;
return human;
},
},
});
Expand All @@ -37,7 +46,7 @@ test('visiting /luke', function(assert) {

// try updating the mock, refetching the result (w/ queryOnce), and ensuring
// that there are no errors:
mockHuman.name = 'Luke Skywalker II';
human.name = 'Luke Skywalker II';
click('.refetch-button');

andThen(() => {
Expand All @@ -62,3 +71,56 @@ test('visiting /luke', function(assert) {
});
});
});

test('visiting /characters', function(assert) {
let done = assert.async();

let firstQuery = true;

addResolveFunctionsToSchema(this.pretender.schema, {
Query: {
characters(obj, args) {
if (firstQuery) {
firstQuery = false;
assert.deepEqual(args, { kind: 'human' });
return [mockHuman];
}

assert.deepEqual(args, { kind: 'droid' });
return [mockDroid];
},
},
});

let apollo = application.__container__.lookup('service:apollo');
let getQueries = () => apollo.client.queryManager.queryStore.getStore();

visit('/characters?kind=human');

andThen(function() {
assert.equal(currentURL(), '/characters?kind=human');
assert.equal(find('.model-name').text(), 'Luke Skywalker');

andThen(() => {
// Because we used watchQuery() there should be an ongoing query in the
// apollo query manager:
let queries = getQueries();
assert.equal(Object.keys(queries).length, 1, 'there is an active watchQuery');

// Change the query param to re-fetch model, which will trigger
// resetController() when it's done:
visit('/characters?kind=droid');

andThen(function() {
assert.equal(currentURL(), '/characters?kind=droid');
assert.equal(find('.model-name').text(), 'BB8');
// Since we changed the query kind from 'human' to 'droid', a new
// watchQuery should have been fetched referencing 'droid'. It should
// still be active, while the 'human' query should not.
let queries = getQueries();
assert.equal(Object.keys(queries).length, 1, 'there is an active watchQuery');
done();
});
});
});
})
6 changes: 6 additions & 0 deletions tests/dummy/app/controllers/characters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Ember from 'ember';

export default Ember.Controller.extend({
queryParams: ['kind'],
kind: null
});
12 changes: 12 additions & 0 deletions tests/dummy/app/gql/queries/characters.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
query characters($kind: String!) {
characters(kind: $kind) {
... on Droid {
id
name
}
... on Human {
id
name
}
}
}
1 change: 1 addition & 0 deletions tests/dummy/app/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const Router = Ember.Router.extend({
});

Router.map(function() {
this.route('characters');
this.route('luke');
this.route('new-review');
});
Expand Down
19 changes: 19 additions & 0 deletions tests/dummy/app/routes/characters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Ember from 'ember';
import RouteQueryManager from 'ember-apollo-client/mixins/route-query-manager';
import query from 'dummy/gql/queries/characters';

const { inject: { service } } = Ember;

export default Ember.Route.extend(RouteQueryManager, {
apollo: service(),

queryParams: {
kind: {
refreshModel: true
}
},

model(variables) {
return this.apollo.watchQuery({ query, variables }, 'characters');
}
});
17 changes: 17 additions & 0 deletions tests/dummy/app/services/apollo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import ApolloService from "ember-apollo-client/services/apollo";
import Ember from "ember";
import { IntrospectionFragmentMatcher } from "apollo-client";
import TypeIntrospectionQuery from "dummy/utils/graphql-type-query";

const { computed, merge } = Ember;

export default ApolloService.extend({
clientOptions: computed(function() {
let opts = this._super(...arguments);
return merge(opts, {
fragmentMatcher: new IntrospectionFragmentMatcher({
introspectionQueryResultData: TypeIntrospectionQuery,
}),
});
}),
});
5 changes: 5 additions & 0 deletions tests/dummy/app/templates/characters.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<ul>
{{#each model as |item|}}
<li class='model-name'>{{item.name}}</li>
{{/each}}
</ul>
76 changes: 76 additions & 0 deletions tests/dummy/app/utils/graphql-type-query.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
export default {
__schema: {
types: [
{
kind: "UNION",
name: "SearchResult",
possibleTypes: [
{
name: "Droid",
},
{
name: "Human",
},
{
name: "Starship",
},
],
},
{
kind: "OBJECT",
name: "Droid",
possibleTypes: null,
},
{
kind: "OBJECT",
name: "Human",
possibleTypes: null,
},
{
kind: "OBJECT",
name: "Starship",
possibleTypes: null,
},
{
kind: "OBJECT",
name: "__Schema",
possibleTypes: null,
},
{
kind: "OBJECT",
name: "__Type",
possibleTypes: null,
},
{
kind: "ENUM",
name: "__TypeKind",
possibleTypes: null,
},
{
kind: "OBJECT",
name: "__Field",
possibleTypes: null,
},
{
kind: "OBJECT",
name: "__InputValue",
possibleTypes: null,
},
{
kind: "OBJECT",
name: "__EnumValue",
possibleTypes: null,
},
{
kind: "OBJECT",
name: "__Directive",
possibleTypes: null,
},
{
kind: "ENUM",
name: "__DirectiveLocation",
possibleTypes: null,
},
],
},
};
2 changes: 2 additions & 0 deletions tests/fixtures/test-schema.graphql.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ type Query {
search(text: String): [SearchResult]
characters(kind: String): [SearchResult]
droid(id: ID!): Droid
human(id: ID!): Human
Expand Down
27 changes: 25 additions & 2 deletions tests/helpers/start-pretender.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
import Ember from 'ember';
import Pretender from 'pretender';
import { graphql } from 'graphql';
import { addMockFunctionsToSchema, makeExecutableSchema } from 'graphql-tools';
import {
addMockFunctionsToSchema,
addResolveFunctionsToSchema,
makeExecutableSchema,
} from 'graphql-tools';
import schemaString from '../fixtures/test-schema.graphql';

const { RSVP } = Ember;

const interfaceResolveType = {
__resolveType(data) {
// Explicitly resolve the type of any interface type based on the
// special attribute __typename, which should be provided in mocks of
// these types. Otherwise fallback to the data.typename.name, which
// comes from built-in mocks.
return data.__typename || data.typename.name;
},
};

export default function startPretender() {
let resolvers = {
// This is where you would declare custom resolvers. For example, assume we
Expand All @@ -23,6 +37,14 @@ export default function startPretender() {
// }
// },
};
let typeResolvers = {
// This is where you can declare custom type resolvers, such as those
// necessary to infer the specific object type of a union type.

// We use this interface type in test mocks, need to teach Apollo Client how
// to resolve its type:
SearchResult: interfaceResolveType,
};
let mocks = {
// This is where you tell graphql-tools how to generate a mock for a custom
// scalar type:
Expand All @@ -33,7 +55,8 @@ export default function startPretender() {
};

let schema = makeExecutableSchema({ typeDefs: schemaString, resolvers });
addMockFunctionsToSchema({ schema, mocks });
addResolveFunctionsToSchema(schema, typeResolvers);
addMockFunctionsToSchema({ schema, mocks, preserveResolvers: true });

let pretender = new Pretender(function() {
this.unhandledRequest = function(verb, path) {
Expand Down

0 comments on commit 9ffe7b5

Please sign in to comment.