Skip to content

Commit

Permalink
Implement request state service
Browse files Browse the repository at this point in the history
  • Loading branch information
igorT committed Aug 1, 2019
1 parent ef87a71 commit 143e1b2
Show file tree
Hide file tree
Showing 22 changed files with 1,329 additions and 168 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"@types/ember-qunit": "^3.4.6",
"@types/ember-test-helpers": "~1.0.5",
"@types/ember-testing-helpers": "~0.0.3",
"@types/ember__debug": "^3.0.3",
"@types/ember__debug": "3.0.4",
"@types/ember__test-helpers": "~0.7.8",
"@types/qunit": "^2.5.3",
"@types/rsvp": "^4.0.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ module('async belongs-to rendering tests', function(hooks) {
hooks.beforeEach(function() {
let { owner } = this;
owner.register('model:person', Person);
owner.register('model:pet', Pet);
owner.register('adapter:application', TestAdapter);
owner.register(
'serializer:application',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ class TestRecordData {
removeFromInverseRelationships(isNew: boolean) {}

_initRecordCreateOptions(options) {}
isNew() {
return false;
}
isDeleted() {
return false;
}
}

let CustomStore = Store.extend({
Expand Down Expand Up @@ -194,6 +200,7 @@ module('integration/record-data - Custom RecordData Implementations', function(h
let calledUnloadRecord = 0;
let calledRollbackAttributes = 0;
let calledDidCommit = 0;
let isNew = false;

class LifecycleRecordData extends TestRecordData {
pushData(data, calculateChange?: boolean) {
Expand All @@ -202,6 +209,7 @@ module('integration/record-data - Custom RecordData Implementations', function(h

clientDidCreate() {
calledClientDidCreate++;
isNew = true;
}

willCommit() {
Expand All @@ -222,6 +230,11 @@ module('integration/record-data - Custom RecordData Implementations', function(h

didCommit(data) {
calledDidCommit++;
isNew = false;
}

isNew() {
return isNew;
}
}

Expand Down
230 changes: 230 additions & 0 deletions packages/-ember-data/tests/integration/request-state-service-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
import { setupTest } from 'ember-qunit';
import Model from 'ember-data/model';
import Store from 'ember-data/store';
import { module, test } from 'qunit';
import { identifierCacheFor } from '@ember-data/store/-private';
import EmberObject from '@ember/object';
import { attr } from '@ember-data/model';
import { REQUEST_SERVICE } from '@ember-data/canary-features';
import { RequestStateEnum } from '@ember-data/store/-private/ts-interfaces/fetch-manager';

class Person extends Model {
// TODO fix the typing for naked attrs
@attr('string', {})
name;

@attr('string', {})
lastName;
}

if (REQUEST_SERVICE) {
module('integration/request-state-service - Request State Service', function(hooks) {
setupTest(hooks);

let store: Store;

hooks.beforeEach(function() {
let { owner } = this;
owner.register('model:person', Person);
store = owner.lookup('service:store');
});

test('getPendingRequest and getLastRequest return correct inflight and fulfilled requests', async function(assert) {
assert.expect(10);

let normalizedHash = {
data: {
type: 'person',
id: '1',
lid: '',
attributes: {
name: 'Scumbag Dale',
},
relationships: {},
},
included: [],
};

let { owner } = this;

let TestAdapter = EmberObject.extend({
findRecord() {
const personHash = {
type: 'person',
id: '1',
name: 'Scumbag Dale',
};

return Promise.resolve(personHash);
},
deleteRecord() {
return Promise.resolve();
},

updateRecord() {
return Promise.resolve();
},

createRecord() {
return Promise.resolve();
},
});

owner.register('adapter:application', TestAdapter);

store = owner.lookup('service:store');

let promise = store.findRecord('person', '1');
let requestService = store.getRequestStateService();

// Relying on sequential lids until identifiers land
let identifier = identifierCacheFor(store).getOrCreateRecordIdentifier({ type: 'person', id: '1' });
normalizedHash.data.lid = identifier.lid;
let request = requestService.getPendingRequestsForRecord(identifier)[0];

assert.equal(request.state, 'pending', 'request is pending');
assert.equal(request.type, 'query', 'request is a query');
let requestOp = {
op: 'findRecord',
recordIdentifier: identifier,
options: {},
};
assert.deepEqual(request.request.data[0], requestOp, 'request op is correct');

let person = await promise;
let lastRequest = requestService.getLastRequestForRecord(identifier);
let requestStateResult = {
type: 'query' as const,
state: 'fulfilled' as RequestStateEnum,
request: { data: [requestOp] },
response: { data: normalizedHash },
};
assert.deepEqual(lastRequest, requestStateResult, 'request is correct after fulfilling');
assert.deepEqual(
requestService.getPendingRequestsForRecord(identifier).length,
0,
'no pending requests remaining'
);

let savingPromise = person.save();
let savingRequest = requestService.getPendingRequestsForRecord(identifier)[0];

assert.equal(savingRequest.state, 'pending', 'request is pending');
assert.equal(savingRequest.type, 'mutation', 'request is a mutation');
let savingRequestOp = {
op: 'saveRecord',
recordIdentifier: identifier,
options: {},
};
assert.deepEqual(savingRequest.request.data[0], savingRequestOp, 'request op is correct');

await savingPromise;
let lastSavingRequest = requestService.getLastRequestForRecord(identifier);
let savingRequestStateResult = {
type: 'mutation' as const,
state: 'fulfilled' as RequestStateEnum,
request: { data: [savingRequestOp] },
response: { data: undefined },
};
assert.deepEqual(lastSavingRequest, savingRequestStateResult, 'request is correct after fulfilling');
assert.deepEqual(
requestService.getPendingRequestsForRecord(identifier).length,
0,
'no pending requests remaining'
);
});

test('can subscribe to events for an identifier', async function(assert) {
assert.expect(9);

const personHash = {
type: 'person',
id: '1',
name: 'Scumbag Dale',
};

let normalizedHash = {
data: {
type: 'person',
id: '1',
attributes: {
name: 'Scumbag Dale',
},
relationships: {},
},
included: [],
};

let { owner } = this;

let TestAdapter = EmberObject.extend({
findRecord() {
return Promise.resolve(personHash);
},
deleteRecord() {
return Promise.resolve();
},

updateRecord() {
return Promise.resolve();
},

createRecord() {
return Promise.resolve();
},
});

owner.register('adapter:application', TestAdapter, { singleton: false });

store = owner.lookup('service:store');

let requestService = store.getRequestStateService();
// Relying on sequential lids until identifiers land
let identifier = identifierCacheFor(store).getOrCreateRecordIdentifier({ type: 'person', id: '1' });
let count = 0;
let requestOp = {
op: 'findRecord',
recordIdentifier: identifier,
options: {},
};
let savingRequestOp = {
op: 'saveRecord',
recordIdentifier: identifier,
options: {},
};

let unsubToken = requestService.subscribeForRecord(identifier, request => {
if (count === 0) {
assert.equal(request.state, 'pending', 'request is pending');
assert.equal(request.type, 'query', 'request is a query');
assert.deepEqual(request.request.data[0], requestOp, 'request op is correct');
} else if (count === 1) {
let requestStateResult = {
type: 'query' as const,
state: 'fulfilled' as RequestStateEnum,
request: { data: [requestOp] },
response: { data: normalizedHash },
};
assert.deepEqual(request, requestStateResult, 'request is correct after fulfilling');
} else if (count === 2) {
assert.equal(request.state, 'pending', 'request is pending');
assert.equal(request.type, 'mutation', 'request is a mutation');
assert.deepEqual(request.request.data[0], savingRequestOp, 'request op is correct');
} else if (count === 3) {
let savingRequestStateResult = {
type: 'mutation' as const,
state: 'fulfilled' as RequestStateEnum,
request: { data: [savingRequestOp] },
response: { data: undefined },
};
assert.deepEqual(request, savingRequestStateResult, 'request is correct after fulfilling');
}
count++;
});

let person = await store.findRecord('person', '1');
await person.save();
assert.equal(count, 4, 'callback called four times');
});
});
}
4 changes: 2 additions & 2 deletions packages/-ember-data/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@
"dummy/*": ["tests/dummy/app/*", "app/*"],
"ember-data": ["addon"],
"ember-data/*": ["addon/*"],
"ember-data/test-support": ["addon-test-support"],
"ember-data/test-support/*": ["addon-test-support/*"],
"@ember-data/store": ["../store/addon"],
"@ember-data/store/*": ["../store/addon/*"],
"ember-data/test-support": ["addon-test-support"],
"ember-data/test-support/*": ["addon-test-support/*"],
"@ember-data/adapter/error": ["../adapter/addon/error"],
"@ember-data/canary-features": ["../canary-features/addon"],
"*": ["../store/types/*"]
Expand Down
11 changes: 1 addition & 10 deletions packages/adapter/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
"paths": {
"dummy/tests/*": ["tests/*"],
"dummy/*": ["tests/dummy/app/*", "app/*"],
"@ember-data/adapter": ["addon"],
"@ember-data/adapter/*": ["addon/*"],
"@ember-data/adapter/test-support": ["addon-test-support"],
"@ember-data/adapter/test-support/*": ["addon-test-support/*"],
"ember-data": ["../-ember-data/addon"],
Expand All @@ -28,13 +26,6 @@
"*": ["types/*"]
}
},
"include": [
"app/**/*",
"addon/**/*",
"tests/**/*",
"types/**/*",
"test-support/**/*",
"addon-test-support/**/*"
],
"include": ["app/**/*", "addon/**/*", "tests/**/*", "types/**/*", "test-support/**/*", "addon-test-support/**/*"],
"exclude": ["node_modules"]
}
1 change: 1 addition & 0 deletions packages/canary-features/addon/default-features.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ export default {
RECORD_DATA_ERRORS: null,
RECORD_DATA_STATE: null,
IDENTIFIERS: null,
REQUEST_SERVICE: null,
};
1 change: 1 addition & 0 deletions packages/canary-features/addon/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ export const FEATURES = assign({}, DEFAULT_FEATURES, ENV.FEATURES);
export const SAMPLE_FEATURE_FLAG = featureValue(FEATURES.SAMPLE_FEATURE_FLAG);
export const RECORD_DATA_ERRORS = featureValue(FEATURES.RECORD_DATA_ERRORS);
export const RECORD_DATA_STATE = featureValue(FEATURES.RECORD_DATA_STATE);
export const REQUEST_SERVICE = featureValue(FEATURES.REQUEST_SERVICE);
export const IDENTIFIERS = featureValue(FEATURES.IDENTIFIERS);
Loading

0 comments on commit 143e1b2

Please sign in to comment.