mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge pull request #1056 from atmire/stale-issue
Fix issue where stale RemoteData is emitted first
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
|
import { cold } from 'jasmine-marbles';
|
||||||
import { Observable, of as observableOf } from 'rxjs';
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
import { TestScheduler } from 'rxjs/testing';
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
import { getMockRequestService } from '../../shared/mocks/request.service.mock';
|
import { getMockRequestService } from '../../shared/mocks/request.service.mock';
|
||||||
@@ -13,16 +13,15 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
|
|||||||
import { ComColDataService } from './comcol-data.service';
|
import { ComColDataService } from './comcol-data.service';
|
||||||
import { CommunityDataService } from './community-data.service';
|
import { CommunityDataService } from './community-data.service';
|
||||||
import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
|
import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
|
||||||
import { FindListOptions, GetRequest } from './request.models';
|
import { FindListOptions } from './request.models';
|
||||||
import { RequestEntry } from './request.reducer';
|
|
||||||
import { RequestService } from './request.service';
|
import { RequestService } from './request.service';
|
||||||
import {
|
import {
|
||||||
createFailedRemoteDataObject$,
|
createFailedRemoteDataObject$,
|
||||||
createNoContentRemoteDataObject$,
|
createSuccessfulRemoteDataObject$,
|
||||||
createSuccessfulRemoteDataObject$
|
createFailedRemoteDataObject,
|
||||||
|
createSuccessfulRemoteDataObject
|
||||||
} from '../../shared/remote-data.utils';
|
} from '../../shared/remote-data.utils';
|
||||||
import { BitstreamDataService } from './bitstream-data.service';
|
import { BitstreamDataService } from './bitstream-data.service';
|
||||||
import { take } from 'rxjs/operators';
|
|
||||||
|
|
||||||
const LINK_NAME = 'test';
|
const LINK_NAME = 'test';
|
||||||
|
|
||||||
@@ -50,8 +49,8 @@ class TestService extends ComColDataService<any> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tslint:disable:no-shadowed-variable
|
||||||
describe('ComColDataService', () => {
|
describe('ComColDataService', () => {
|
||||||
let scheduler: TestScheduler;
|
|
||||||
let service: TestService;
|
let service: TestService;
|
||||||
let requestService: RequestService;
|
let requestService: RequestService;
|
||||||
let cds: CommunityDataService;
|
let cds: CommunityDataService;
|
||||||
@@ -59,6 +58,8 @@ describe('ComColDataService', () => {
|
|||||||
let halService: any = {};
|
let halService: any = {};
|
||||||
let bitstreamDataService: BitstreamDataService;
|
let bitstreamDataService: BitstreamDataService;
|
||||||
let rdbService: RemoteDataBuildService;
|
let rdbService: RemoteDataBuildService;
|
||||||
|
let testScheduler: TestScheduler;
|
||||||
|
let topEndpoint: string;
|
||||||
|
|
||||||
const store = {} as Store<CoreState>;
|
const store = {} as Store<CoreState>;
|
||||||
const notificationsService = {} as NotificationsService;
|
const notificationsService = {} as NotificationsService;
|
||||||
@@ -69,17 +70,9 @@ describe('ComColDataService', () => {
|
|||||||
const options = Object.assign(new FindListOptions(), {
|
const options = Object.assign(new FindListOptions(), {
|
||||||
scopeID: scopeID
|
scopeID: scopeID
|
||||||
});
|
});
|
||||||
const getRequestEntry$ = (successful: boolean) => {
|
|
||||||
return observableOf({
|
|
||||||
response: { isSuccessful: successful } as any
|
|
||||||
} as RequestEntry);
|
|
||||||
};
|
|
||||||
|
|
||||||
const communitiesEndpoint = 'https://rest.api/core/communities';
|
const communitiesEndpoint = 'https://rest.api/core/communities';
|
||||||
const communityEndpoint = `${communitiesEndpoint}/${scopeID}`;
|
const communityEndpoint = `${communitiesEndpoint}/${scopeID}`;
|
||||||
const scopedEndpoint = `${communityEndpoint}/${LINK_NAME}`;
|
const scopedEndpoint = `${communityEndpoint}/${LINK_NAME}`;
|
||||||
const serviceEndpoint = `https://rest.api/core/${LINK_NAME}`;
|
|
||||||
const authHeader = 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJlaWQiOiJhNjA4NmIzNC0zOTE4LTQ1YjctOGRkZC05MzI5YTcwMmEyNmEiLCJzZyI6W10sImV4cCI6MTUzNDk0MDcyNX0.RV5GAtiX6cpwBN77P_v16iG9ipeyiO7faNYSNMzq_sQ';
|
|
||||||
|
|
||||||
const mockHalService = {
|
const mockHalService = {
|
||||||
getEndpoint: (linkPath) => observableOf(communitiesEndpoint)
|
getEndpoint: (linkPath) => observableOf(communitiesEndpoint)
|
||||||
@@ -98,8 +91,8 @@ describe('ComColDataService', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function initMockCommunityDataService(): CommunityDataService {
|
function initMockCommunityDataService(): CommunityDataService {
|
||||||
return jasmine.createSpyObj('responseCache', {
|
return jasmine.createSpyObj('cds', {
|
||||||
getEndpoint: hot('--a-', { a: communitiesEndpoint }),
|
getEndpoint: cold('--a-', { a: communitiesEndpoint }),
|
||||||
getIDHref: communityEndpoint
|
getIDHref: communityEndpoint
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -134,7 +127,15 @@ describe('ComColDataService', () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const initTestScheduler = (): TestScheduler => {
|
||||||
|
return new TestScheduler((actual, expected) => {
|
||||||
|
expect(actual).toEqual(expected);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
topEndpoint = 'https://rest.api/core/communities/search/top';
|
||||||
|
testScheduler = initTestScheduler();
|
||||||
cds = initMockCommunityDataService();
|
cds = initMockCommunityDataService();
|
||||||
requestService = getMockRequestService();
|
requestService = getMockRequestService();
|
||||||
objectCache = initMockObjectCacheService();
|
objectCache = initMockObjectCacheService();
|
||||||
@@ -145,113 +146,94 @@ describe('ComColDataService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('getBrowseEndpoint', () => {
|
describe('getBrowseEndpoint', () => {
|
||||||
beforeEach(() => {
|
it(`should call createAndSendGetRequest with the scope Community's self link`, () => {
|
||||||
scheduler = getTestScheduler();
|
testScheduler.run(({ cold, flush, expectObservable }) => {
|
||||||
});
|
(cds.getEndpoint as jasmine.Spy).and.returnValue(cold('a', { a: communitiesEndpoint }));
|
||||||
|
(rdbService.buildSingle as jasmine.Spy).and.returnValue(cold('a', { a: createFailedRemoteDataObject() }));
|
||||||
it('should send a new FindByIDRequest for the scope Community', () => {
|
spyOn(service as any, 'createAndSendGetRequest');
|
||||||
cds = initMockCommunityDataService();
|
service.getBrowseEndpoint(options);
|
||||||
requestService = getMockRequestService(getRequestEntry$(true));
|
flush();
|
||||||
objectCache = initMockObjectCacheService();
|
expectObservable((service as any).createAndSendGetRequest.calls.argsFor(0)[0]).toBe('(a|)', { a: communityEndpoint });
|
||||||
service = initTestService();
|
expect((service as any).createAndSendGetRequest.calls.argsFor(0)[1]).toBeTrue();
|
||||||
|
});
|
||||||
const expected = new GetRequest(requestService.generateRequestId(), communityEndpoint);
|
|
||||||
|
|
||||||
scheduler.schedule(() => service.getBrowseEndpoint(options).subscribe());
|
|
||||||
scheduler.flush();
|
|
||||||
|
|
||||||
expect(requestService.send).toHaveBeenCalledWith(expected, true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('if the scope Community can\'t be found', () => {
|
describe('if the scope Community can\'t be found', () => {
|
||||||
it('should throw an error', () => {
|
it('should throw an error', () => {
|
||||||
const result = service.getBrowseEndpoint(options).pipe(take(1));
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
const expected = cold('--#-', undefined, new Error(`The Community with scope ${scopeID} couldn't be retrieved`));
|
// spies re-defined here to use the "cold" function from rxjs's TestScheduler
|
||||||
|
// rather than the one imported from jasmine-marbles.
|
||||||
expect(result).toBeObservable(expected);
|
// Mixing the two seems to lead to unpredictable results
|
||||||
});
|
(cds.getEndpoint as jasmine.Spy).and.returnValue(cold('a', { a: communitiesEndpoint }));
|
||||||
});
|
(rdbService.buildSingle as jasmine.Spy).and.returnValue(cold('a', { a: createFailedRemoteDataObject() }));
|
||||||
|
const expectedError = new Error(`The Community with scope ${scopeID} couldn't be retrieved`);
|
||||||
describe('cache refresh', () => {
|
expectObservable(service.getBrowseEndpoint(options)).toBe('#', undefined, expectedError);
|
||||||
let communityWithoutParentHref;
|
|
||||||
let data;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
spyOn(halService, 'getEndpoint').and.returnValue(observableOf('https://rest.api/core/communities/search/top'));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('cache refreshed top level community', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
(rdbService.buildSingle as jasmine.Spy).and.returnValue(createNoContentRemoteDataObject$());
|
|
||||||
data = {
|
|
||||||
dso: Object.assign(new Community(), {
|
|
||||||
metadata: [{
|
|
||||||
key: 'dc.title',
|
|
||||||
value: 'top level community'
|
|
||||||
}]
|
|
||||||
}),
|
|
||||||
_links: {
|
|
||||||
parentCommunity: {
|
|
||||||
href: 'topLevel/parentCommunity'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
communityWithoutParentHref = {
|
|
||||||
dso: Object.assign(new Community(), {
|
|
||||||
metadata: [{
|
|
||||||
key: 'dc.title',
|
|
||||||
value: 'top level community'
|
|
||||||
}]
|
|
||||||
}),
|
|
||||||
_links: {}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
it('top level community cache refreshed', () => {
|
|
||||||
scheduler.schedule(() => (service as any).refreshCache(data));
|
|
||||||
scheduler.flush();
|
|
||||||
expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith('https://rest.api/core/communities/search/top');
|
|
||||||
});
|
|
||||||
it('top level community without parent link, cache not refreshed', () => {
|
|
||||||
scheduler.schedule(() => (service as any).refreshCache(communityWithoutParentHref));
|
|
||||||
scheduler.flush();
|
|
||||||
expect(requestService.setStaleByHrefSubstring).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('cache refreshed child community', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
const parentCommunity = Object.assign(new Community(), {
|
|
||||||
uuid: 'a20da287-e174-466a-9926-f66as300d399',
|
|
||||||
id: 'a20da287-e174-466a-9926-f66as300d399',
|
|
||||||
metadata: [{
|
|
||||||
key: 'dc.title',
|
|
||||||
value: 'parent community'
|
|
||||||
}],
|
|
||||||
_links: {}
|
|
||||||
});
|
|
||||||
(rdbService.buildSingle as jasmine.Spy).and.returnValue(createSuccessfulRemoteDataObject$(parentCommunity));
|
|
||||||
data = {
|
|
||||||
dso: Object.assign(new Community(), {
|
|
||||||
metadata: [{
|
|
||||||
key: 'dc.title',
|
|
||||||
value: 'child community'
|
|
||||||
}]
|
|
||||||
}),
|
|
||||||
_links: {
|
|
||||||
parentCommunity: {
|
|
||||||
href: 'child/parentCommunity'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
it('child level community cache refreshed', () => {
|
|
||||||
scheduler.schedule(() => (service as any).refreshCache(data));
|
|
||||||
scheduler.flush();
|
|
||||||
expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith('a20da287-e174-466a-9926-f66as300d399');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('cache refresh', () => {
|
||||||
|
let communityWithoutParentHref;
|
||||||
|
let communityWithParentHref;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
communityWithParentHref = {
|
||||||
|
_links: {
|
||||||
|
parentCommunity: {
|
||||||
|
href: 'topLevel/parentCommunity'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as Community;
|
||||||
|
communityWithoutParentHref = {
|
||||||
|
_links: {}
|
||||||
|
} as Community;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('cache refreshed top level community', () => {
|
||||||
|
it(`should refresh the top level community cache when the dso has a parent link that can't be resolved`, () => {
|
||||||
|
testScheduler.run(({ flush, cold }) => {
|
||||||
|
spyOn(halService, 'getEndpoint').and.returnValue(cold('a', { a: topEndpoint }));
|
||||||
|
spyOn(service, 'findByHref').and.returnValue(cold('a', { a: createSuccessfulRemoteDataObject({}) }));
|
||||||
|
service.refreshCache(communityWithParentHref);
|
||||||
|
flush();
|
||||||
|
expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith(topEndpoint);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it(`shouldn't do anything when the dso doesn't have a parent link`, () => {
|
||||||
|
testScheduler.run(({ flush, cold }) => {
|
||||||
|
spyOn(halService, 'getEndpoint').and.returnValue(cold('a', { a: topEndpoint }));
|
||||||
|
spyOn(service, 'findByHref').and.returnValue(cold('a', { a: createSuccessfulRemoteDataObject({}) }));
|
||||||
|
service.refreshCache(communityWithoutParentHref);
|
||||||
|
flush();
|
||||||
|
expect(requestService.setStaleByHrefSubstring).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('cache refreshed child community', () => {
|
||||||
|
let parentCommunity: Community;
|
||||||
|
beforeEach(() => {
|
||||||
|
parentCommunity = Object.assign(new Community(), {
|
||||||
|
uuid: 'a20da287-e174-466a-9926-f66as300d399',
|
||||||
|
id: 'a20da287-e174-466a-9926-f66as300d399',
|
||||||
|
metadata: [{
|
||||||
|
key: 'dc.title',
|
||||||
|
value: 'parent community'
|
||||||
|
}],
|
||||||
|
_links: {}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should refresh a specific cached community when the parent link can be resolved', () => {
|
||||||
|
testScheduler.run(({ flush, cold }) => {
|
||||||
|
spyOn(halService, 'getEndpoint').and.returnValue(cold('a', { a: topEndpoint }));
|
||||||
|
spyOn(service, 'findByHref').and.returnValue(cold('a', { a: createSuccessfulRemoteDataObject(parentCommunity) }));
|
||||||
|
service.refreshCache(communityWithParentHref);
|
||||||
|
flush();
|
||||||
|
expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith('a20da287-e174-466a-9926-f66as300d399');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -20,6 +20,9 @@ import { getMockRequestService } from '../../shared/mocks/request.service.mock';
|
|||||||
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub';
|
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub';
|
||||||
import { RequestParam } from '../cache/models/request-param.model';
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
import { getMockRemoteDataBuildService } from '../../shared/mocks/remote-data-build.service.mock';
|
import { getMockRemoteDataBuildService } from '../../shared/mocks/remote-data-build.service.mock';
|
||||||
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
import { RemoteData } from './remote-data';
|
||||||
|
import { RequestEntryState } from './request.reducer';
|
||||||
|
|
||||||
const endpoint = 'https://rest.api/core';
|
const endpoint = 'https://rest.api/core';
|
||||||
|
|
||||||
@@ -63,6 +66,10 @@ describe('DataService', () => {
|
|||||||
let comparator;
|
let comparator;
|
||||||
let objectCache;
|
let objectCache;
|
||||||
let store;
|
let store;
|
||||||
|
let selfLink;
|
||||||
|
let linksToFollow;
|
||||||
|
let testScheduler;
|
||||||
|
let remoteDataMocks;
|
||||||
|
|
||||||
function initTestService(): TestService {
|
function initTestService(): TestService {
|
||||||
requestService = getMockRequestService();
|
requestService = getMockRequestService();
|
||||||
@@ -81,6 +88,34 @@ describe('DataService', () => {
|
|||||||
}
|
}
|
||||||
} as any;
|
} as any;
|
||||||
store = {} as Store<CoreState>;
|
store = {} as Store<CoreState>;
|
||||||
|
selfLink = 'https://rest.api/endpoint/1698f1d3-be98-4c51-9fd8-6bfedcbd59b7';
|
||||||
|
linksToFollow = [
|
||||||
|
followLink('a'),
|
||||||
|
followLink('b')
|
||||||
|
];
|
||||||
|
|
||||||
|
testScheduler = new TestScheduler((actual, expected) => {
|
||||||
|
// asserting the two objects are equal
|
||||||
|
// e.g. using chai.
|
||||||
|
expect(actual).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
const timeStamp = new Date().getTime();
|
||||||
|
const msToLive = 15 * 60 * 1000;
|
||||||
|
const payload = { foo: 'bar' };
|
||||||
|
const statusCodeSuccess = 200;
|
||||||
|
const statusCodeError = 404;
|
||||||
|
const errorMessage = 'not found';
|
||||||
|
remoteDataMocks = {
|
||||||
|
RequestPending: new RemoteData(undefined, msToLive, timeStamp, RequestEntryState.RequestPending, undefined, undefined, undefined),
|
||||||
|
ResponsePending: new RemoteData(undefined, msToLive, timeStamp, RequestEntryState.ResponsePending, undefined, undefined, undefined),
|
||||||
|
Success: new RemoteData(timeStamp, msToLive, timeStamp, RequestEntryState.Success, undefined, payload, statusCodeSuccess),
|
||||||
|
SuccessStale: new RemoteData(timeStamp, msToLive, timeStamp, RequestEntryState.SuccessStale, undefined, payload, statusCodeSuccess),
|
||||||
|
Error: new RemoteData(timeStamp, msToLive, timeStamp, RequestEntryState.Error, errorMessage, undefined, statusCodeError),
|
||||||
|
ErrorStale: new RemoteData(timeStamp, msToLive, timeStamp, RequestEntryState.ErrorStale, errorMessage, undefined, statusCodeError),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
return new TestService(
|
return new TestService(
|
||||||
requestService,
|
requestService,
|
||||||
rdbService,
|
rdbService,
|
||||||
@@ -307,14 +342,12 @@ describe('DataService', () => {
|
|||||||
|
|
||||||
describe('update', () => {
|
describe('update', () => {
|
||||||
let operations;
|
let operations;
|
||||||
let selfLink;
|
|
||||||
let dso;
|
let dso;
|
||||||
let dso2;
|
let dso2;
|
||||||
const name1 = 'random string';
|
const name1 = 'random string';
|
||||||
const name2 = 'another random string';
|
const name2 = 'another random string';
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
operations = [{ op: 'replace', path: '/0/value', value: name2 } as Operation];
|
operations = [{ op: 'replace', path: '/0/value', value: name2 } as Operation];
|
||||||
selfLink = 'https://rest.api/endpoint/1698f1d3-be98-4c51-9fd8-6bfedcbd59b7';
|
|
||||||
|
|
||||||
dso = Object.assign(new DSpaceObject(), {
|
dso = Object.assign(new DSpaceObject(), {
|
||||||
_links: { self: { href: selfLink } },
|
_links: { self: { href: selfLink } },
|
||||||
@@ -340,5 +373,452 @@ describe('DataService', () => {
|
|||||||
expect(objectCache.addPatch).not.toHaveBeenCalled();
|
expect(objectCache.addPatch).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe(`reRequestStaleRemoteData`, () => {
|
||||||
|
let callback: jasmine.Spy<jasmine.Func>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
callback = jasmine.createSpy();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe(`when shouldReRequest is false`, () => {
|
||||||
|
it(`shouldn't do anything`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable, flush }) => {
|
||||||
|
const expected = 'a-b-c-d-e-f';
|
||||||
|
const values = {
|
||||||
|
a: remoteDataMocks.RequestPending,
|
||||||
|
b: remoteDataMocks.ResponsePending,
|
||||||
|
c: remoteDataMocks.Success,
|
||||||
|
d: remoteDataMocks.SuccessStale,
|
||||||
|
e: remoteDataMocks.Error,
|
||||||
|
f: remoteDataMocks.ErrorStale,
|
||||||
|
};
|
||||||
|
|
||||||
|
expectObservable((service as any).reRequestStaleRemoteData(false, callback)(cold(expected, values))).toBe(expected, values);
|
||||||
|
// since the callback happens in a tap(), flush to ensure it has been executed
|
||||||
|
flush();
|
||||||
|
expect(callback).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(`when shouldReRequest is true`, () => {
|
||||||
|
it(`should call the callback for stale RemoteData objects, but still pass the source observable unmodified`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable, flush }) => {
|
||||||
|
const expected = 'a-b';
|
||||||
|
const values = {
|
||||||
|
a: remoteDataMocks.SuccessStale,
|
||||||
|
b: remoteDataMocks.ErrorStale,
|
||||||
|
};
|
||||||
|
|
||||||
|
expectObservable((service as any).reRequestStaleRemoteData(true, callback)(cold(expected, values))).toBe(expected, values);
|
||||||
|
// since the callback happens in a tap(), flush to ensure it has been executed
|
||||||
|
flush();
|
||||||
|
expect(callback).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should only call the callback for stale RemoteData objects if something is subscribed to it`, (done) => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
const expected = 'a';
|
||||||
|
const values = {
|
||||||
|
a: remoteDataMocks.SuccessStale,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result$ = (service as any).reRequestStaleRemoteData(true, callback)(cold(expected, values));
|
||||||
|
expectObservable(result$).toBe(expected, values);
|
||||||
|
expect(callback).not.toHaveBeenCalled();
|
||||||
|
result$.subscribe(() => {
|
||||||
|
expect(callback).toHaveBeenCalled();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`shouldn't do anything for RemoteData objects that aren't stale`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable, flush }) => {
|
||||||
|
const expected = 'a-b-c-d';
|
||||||
|
const values = {
|
||||||
|
a: remoteDataMocks.RequestPending,
|
||||||
|
b: remoteDataMocks.ResponsePending,
|
||||||
|
c: remoteDataMocks.Success,
|
||||||
|
d: remoteDataMocks.Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
expectObservable((service as any).reRequestStaleRemoteData(true, callback)(cold(expected, values))).toBe(expected, values);
|
||||||
|
// since the callback happens in a tap(), flush to ensure it has been executed
|
||||||
|
flush();
|
||||||
|
expect(callback).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(`findByHref`, () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(service as any, 'createAndSendGetRequest').and.callFake((href$) => { href$.subscribe().unsubscribe(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should call buildHrefFromFindOptions with href and linksToFollow`, () => {
|
||||||
|
testScheduler.run(({ cold }) => {
|
||||||
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink);
|
||||||
|
spyOn(rdbService, 'buildSingle').and.returnValue(cold('a', { a: remoteDataMocks.Success }));
|
||||||
|
spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataMocks.Success }));
|
||||||
|
|
||||||
|
service.findByHref(selfLink, true, true, ...linksToFollow);
|
||||||
|
expect(service.buildHrefFromFindOptions).toHaveBeenCalledWith(selfLink, {}, [], ...linksToFollow);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should call createAndSendGetRequest with the result from buildHrefFromFindOptions and useCachedVersionIfAvailable`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue('bingo!');
|
||||||
|
spyOn(rdbService, 'buildSingle').and.returnValue(cold('a', { a: remoteDataMocks.Success }));
|
||||||
|
spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataMocks.Success }));
|
||||||
|
|
||||||
|
service.findByHref(selfLink, true, true, ...linksToFollow);
|
||||||
|
expect((service as any).createAndSendGetRequest).toHaveBeenCalledWith(jasmine.anything(), true);
|
||||||
|
expectObservable(rdbService.buildSingle.calls.argsFor(0)[0]).toBe('(a|)', { a: 'bingo!' });
|
||||||
|
|
||||||
|
service.findByHref(selfLink, false, true, ...linksToFollow);
|
||||||
|
expect((service as any).createAndSendGetRequest).toHaveBeenCalledWith(jasmine.anything(), false);
|
||||||
|
expectObservable(rdbService.buildSingle.calls.argsFor(1)[0]).toBe('(a|)', { a: 'bingo!' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should call rdbService.buildSingle with the result from buildHrefFromFindOptions and linksToFollow`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue('bingo!');
|
||||||
|
spyOn(rdbService, 'buildSingle').and.returnValue(cold('a', { a: remoteDataMocks.Success }));
|
||||||
|
spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataMocks.Success }));
|
||||||
|
|
||||||
|
service.findByHref(selfLink, true, true, ...linksToFollow);
|
||||||
|
expect(rdbService.buildSingle).toHaveBeenCalledWith(jasmine.anything() as any, ...linksToFollow);
|
||||||
|
expectObservable(rdbService.buildSingle.calls.argsFor(0)[0]).toBe('(a|)', { a: 'bingo!' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should return a the output from reRequestStaleRemoteData`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink);
|
||||||
|
spyOn(rdbService, 'buildSingle').and.returnValue(cold('a', { a: remoteDataMocks.Success }));
|
||||||
|
spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: 'bingo!' }));
|
||||||
|
const expected = 'a';
|
||||||
|
const values = {
|
||||||
|
a: 'bingo!',
|
||||||
|
};
|
||||||
|
|
||||||
|
expectObservable(service.findByHref(selfLink, true, true, ...linksToFollow)).toBe(expected, values);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should call reRequestStaleRemoteData with reRequestOnStale and the exact same findByHref call as a callback`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink);
|
||||||
|
spyOn(rdbService, 'buildSingle').and.returnValue(cold('a', { a: remoteDataMocks.SuccessStale }));
|
||||||
|
spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataMocks.SuccessStale }));
|
||||||
|
|
||||||
|
service.findByHref(selfLink, true, true, ...linksToFollow);
|
||||||
|
expect((service as any).reRequestStaleRemoteData.calls.argsFor(0)[0]).toBeTrue();
|
||||||
|
spyOn(service, 'findByHref').and.returnValue(cold('a', { a: remoteDataMocks.SuccessStale }));
|
||||||
|
// prove that the spy we just added hasn't been called yet
|
||||||
|
expect(service.findByHref).not.toHaveBeenCalled();
|
||||||
|
// call the callback passed to reRequestStaleRemoteData
|
||||||
|
(service as any).reRequestStaleRemoteData.calls.argsFor(0)[1]();
|
||||||
|
// verify that findByHref _has_ been called now, with the same params as the original call
|
||||||
|
expect(service.findByHref).toHaveBeenCalledWith(jasmine.anything(), true, true, ...linksToFollow);
|
||||||
|
// ... except for selflink, which will have been turned in to an observable.
|
||||||
|
expectObservable((service.findByHref as jasmine.Spy).calls.argsFor(0)[0]).toBe('(a|)', { a: selfLink });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(`when useCachedVersionIfAvailable is true`, () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink);
|
||||||
|
spyOn(service as any, 'reRequestStaleRemoteData').and.callFake(() => (source) => source);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should emit a cached completed RemoteData immediately, and keep emitting if it gets rerequested`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(rdbService, 'buildSingle').and.returnValue(cold('a-b-c-d-e', {
|
||||||
|
a: remoteDataMocks.Success,
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
}));
|
||||||
|
const expected = 'a-b-c-d-e';
|
||||||
|
const values = {
|
||||||
|
a: remoteDataMocks.Success,
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
};
|
||||||
|
|
||||||
|
expectObservable(service.findByHref(selfLink, true, true, ...linksToFollow)).toBe(expected, values);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should not emit a cached stale RemoteData, but only start emitting after the state first changes to RequestPending`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(rdbService, 'buildSingle').and.returnValue(cold('a-b-c-d-e', {
|
||||||
|
a: remoteDataMocks.SuccessStale,
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
}));
|
||||||
|
const expected = '--b-c-d-e';
|
||||||
|
const values = {
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
};
|
||||||
|
|
||||||
|
expectObservable(service.findByHref(selfLink, true, true, ...linksToFollow)).toBe(expected, values);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(`when useCachedVersionIfAvailable is false`, () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink);
|
||||||
|
spyOn(service as any, 'reRequestStaleRemoteData').and.callFake(() => (source) => source);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it(`should not emit a cached completed RemoteData, but only start emitting after the state first changes to RequestPending`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(rdbService, 'buildSingle').and.returnValue(cold('a-b-c-d-e', {
|
||||||
|
a: remoteDataMocks.Success,
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
}));
|
||||||
|
const expected = '--b-c-d-e';
|
||||||
|
const values = {
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
};
|
||||||
|
|
||||||
|
expectObservable(service.findByHref(selfLink, false, true, ...linksToFollow)).toBe(expected, values);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should not emit a cached stale RemoteData, but only start emitting after the state first changes to RequestPending`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(rdbService, 'buildSingle').and.returnValue(cold('a-b-c-d-e', {
|
||||||
|
a: remoteDataMocks.SuccessStale,
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
}));
|
||||||
|
const expected = '--b-c-d-e';
|
||||||
|
const values = {
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
};
|
||||||
|
|
||||||
|
expectObservable(service.findByHref(selfLink, false, true, ...linksToFollow)).toBe(expected, values);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(`findAllByHref`, () => {
|
||||||
|
let findListOptions;
|
||||||
|
beforeEach(() => {
|
||||||
|
findListOptions = { currentPage: 5 };
|
||||||
|
spyOn(service as any, 'createAndSendGetRequest').and.callFake((href$) => { href$.subscribe().unsubscribe(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should call buildHrefFromFindOptions with href and linksToFollow`, () => {
|
||||||
|
testScheduler.run(({ cold }) => {
|
||||||
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink);
|
||||||
|
spyOn(rdbService, 'buildList').and.returnValue(cold('a', { a: remoteDataMocks.Success }));
|
||||||
|
spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataMocks.Success }));
|
||||||
|
|
||||||
|
service.findAllByHref(selfLink, findListOptions, true, true, ...linksToFollow);
|
||||||
|
expect(service.buildHrefFromFindOptions).toHaveBeenCalledWith(selfLink, findListOptions, [], ...linksToFollow);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should call createAndSendGetRequest with the result from buildHrefFromFindOptions and useCachedVersionIfAvailable`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue('bingo!');
|
||||||
|
spyOn(rdbService, 'buildList').and.returnValue(cold('a', { a: remoteDataMocks.Success }));
|
||||||
|
spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataMocks.Success }));
|
||||||
|
|
||||||
|
service.findAllByHref(selfLink, findListOptions, true, true, ...linksToFollow);
|
||||||
|
expect((service as any).createAndSendGetRequest).toHaveBeenCalledWith(jasmine.anything(), true);
|
||||||
|
expectObservable(rdbService.buildList.calls.argsFor(0)[0]).toBe('(a|)', { a: 'bingo!' });
|
||||||
|
|
||||||
|
service.findAllByHref(selfLink, findListOptions, false, true, ...linksToFollow);
|
||||||
|
expect((service as any).createAndSendGetRequest).toHaveBeenCalledWith(jasmine.anything(), false);
|
||||||
|
expectObservable(rdbService.buildList.calls.argsFor(1)[0]).toBe('(a|)', { a: 'bingo!' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should call rdbService.buildList with the result from buildHrefFromFindOptions and linksToFollow`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue('bingo!');
|
||||||
|
spyOn(rdbService, 'buildList').and.returnValue(cold('a', { a: remoteDataMocks.Success }));
|
||||||
|
spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataMocks.Success }));
|
||||||
|
|
||||||
|
service.findAllByHref(selfLink, findListOptions, true, true, ...linksToFollow);
|
||||||
|
expect(rdbService.buildList).toHaveBeenCalledWith(jasmine.anything() as any, ...linksToFollow);
|
||||||
|
expectObservable(rdbService.buildList.calls.argsFor(0)[0]).toBe('(a|)', { a: 'bingo!' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should call reRequestStaleRemoteData with reRequestOnStale and the exact same findAllByHref call as a callback`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue('bingo!');
|
||||||
|
spyOn(rdbService, 'buildList').and.returnValue(cold('a', { a: remoteDataMocks.SuccessStale }));
|
||||||
|
spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataMocks.SuccessStale }));
|
||||||
|
|
||||||
|
service.findAllByHref(selfLink, findListOptions, true, true, ...linksToFollow);
|
||||||
|
expect((service as any).reRequestStaleRemoteData.calls.argsFor(0)[0]).toBeTrue();
|
||||||
|
spyOn(service, 'findAllByHref').and.returnValue(cold('a', { a: remoteDataMocks.SuccessStale }));
|
||||||
|
// prove that the spy we just added hasn't been called yet
|
||||||
|
expect(service.findAllByHref).not.toHaveBeenCalled();
|
||||||
|
// call the callback passed to reRequestStaleRemoteData
|
||||||
|
(service as any).reRequestStaleRemoteData.calls.argsFor(0)[1]();
|
||||||
|
// verify that findAllByHref _has_ been called now, with the same params as the original call
|
||||||
|
expect(service.findAllByHref).toHaveBeenCalledWith(jasmine.anything(), findListOptions, true, true, ...linksToFollow);
|
||||||
|
// ... except for selflink, which will have been turned in to an observable.
|
||||||
|
expectObservable((service.findAllByHref as jasmine.Spy).calls.argsFor(0)[0]).toBe('(a|)', { a: selfLink });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should return a the output from reRequestStaleRemoteData`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink);
|
||||||
|
spyOn(rdbService, 'buildList').and.returnValue(cold('a', { a: remoteDataMocks.Success }));
|
||||||
|
spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: 'bingo!' }));
|
||||||
|
const expected = 'a';
|
||||||
|
const values = {
|
||||||
|
a: 'bingo!',
|
||||||
|
};
|
||||||
|
|
||||||
|
expectObservable(service.findAllByHref(selfLink, findListOptions, true, true, ...linksToFollow)).toBe(expected, values);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(`when useCachedVersionIfAvailable is true`, () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink);
|
||||||
|
spyOn(service as any, 'reRequestStaleRemoteData').and.callFake(() => (source) => source);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should emit a cached completed RemoteData immediately, and keep emitting if it gets rerequested`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(rdbService, 'buildList').and.returnValue(cold('a-b-c-d-e', {
|
||||||
|
a: remoteDataMocks.Success,
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
}));
|
||||||
|
const expected = 'a-b-c-d-e';
|
||||||
|
const values = {
|
||||||
|
a: remoteDataMocks.Success,
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
};
|
||||||
|
|
||||||
|
expectObservable(service.findAllByHref(selfLink, findListOptions, true, true, ...linksToFollow)).toBe(expected, values);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should not emit a cached stale RemoteData, but only start emitting after the state first changes to RequestPending`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(rdbService, 'buildList').and.returnValue(cold('a-b-c-d-e', {
|
||||||
|
a: remoteDataMocks.SuccessStale,
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
}));
|
||||||
|
const expected = '--b-c-d-e';
|
||||||
|
const values = {
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
};
|
||||||
|
|
||||||
|
expectObservable(service.findAllByHref(selfLink, findListOptions, true, true, ...linksToFollow)).toBe(expected, values);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(`when useCachedVersionIfAvailable is false`, () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink);
|
||||||
|
spyOn(service as any, 'reRequestStaleRemoteData').and.callFake(() => (source) => source);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it(`should not emit a cached completed RemoteData, but only start emitting after the state first changes to RequestPending`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(rdbService, 'buildList').and.returnValue(cold('a-b-c-d-e', {
|
||||||
|
a: remoteDataMocks.Success,
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
}));
|
||||||
|
const expected = '--b-c-d-e';
|
||||||
|
const values = {
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
};
|
||||||
|
|
||||||
|
expectObservable(service.findAllByHref(selfLink, findListOptions, false, true, ...linksToFollow)).toBe(expected, values);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should not emit a cached stale RemoteData, but only start emitting after the state first changes to RequestPending`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(rdbService, 'buildList').and.returnValue(cold('a-b-c-d-e', {
|
||||||
|
a: remoteDataMocks.SuccessStale,
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
}));
|
||||||
|
const expected = '--b-c-d-e';
|
||||||
|
const values = {
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
};
|
||||||
|
|
||||||
|
expectObservable(service.findAllByHref(selfLink, findListOptions, false, true, ...linksToFollow)).toBe(expected, values);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
/* tslint:enable:max-classes-per-file */
|
/* tslint:enable:max-classes-per-file */
|
||||||
|
@@ -12,6 +12,7 @@ import {
|
|||||||
takeWhile,
|
takeWhile,
|
||||||
switchMap,
|
switchMap,
|
||||||
tap,
|
tap,
|
||||||
|
skipWhile,
|
||||||
} from 'rxjs/operators';
|
} from 'rxjs/operators';
|
||||||
import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
|
import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
|
||||||
import { NotificationOptions } from '../../shared/notifications/models/notification-options.model';
|
import { NotificationOptions } from '../../shared/notifications/models/notification-options.model';
|
||||||
@@ -45,29 +46,6 @@ import { UpdateDataService } from './update-data.service';
|
|||||||
import { GenericConstructor } from '../shared/generic-constructor';
|
import { GenericConstructor } from '../shared/generic-constructor';
|
||||||
import { NoContent } from '../shared/NoContent.model';
|
import { NoContent } from '../shared/NoContent.model';
|
||||||
|
|
||||||
/**
|
|
||||||
* An operator that will call the given function if the incoming RemoteData is stale and
|
|
||||||
* shouldReRequest is true
|
|
||||||
*
|
|
||||||
* @param shouldReRequest Whether or not to call the re-request function if the RemoteData is stale
|
|
||||||
* @param requestFn The function to call if the RemoteData is stale and shouldReRequest is
|
|
||||||
* true
|
|
||||||
*/
|
|
||||||
export const reRequestStaleRemoteData = <T>(shouldReRequest: boolean, requestFn: () => Observable<RemoteData<T>>) =>
|
|
||||||
(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> => {
|
|
||||||
if (shouldReRequest === true) {
|
|
||||||
return source.pipe(
|
|
||||||
tap((remoteData: RemoteData<T>) => {
|
|
||||||
if (hasValue(remoteData) && remoteData.isStale) {
|
|
||||||
requestFn();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return source;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export abstract class DataService<T extends CacheableObject> implements UpdateDataService<T> {
|
export abstract class DataService<T extends CacheableObject> implements UpdateDataService<T> {
|
||||||
protected abstract requestService: RequestService;
|
protected abstract requestService: RequestService;
|
||||||
protected abstract rdbService: RemoteDataBuildService;
|
protected abstract rdbService: RemoteDataBuildService;
|
||||||
@@ -332,6 +310,30 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
|
|||||||
return this.findByHref(href$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
return this.findByHref(href$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An operator that will call the given function if the incoming RemoteData is stale and
|
||||||
|
* shouldReRequest is true
|
||||||
|
*
|
||||||
|
* @param shouldReRequest Whether or not to call the re-request function if the RemoteData is stale
|
||||||
|
* @param requestFn The function to call if the RemoteData is stale and shouldReRequest is
|
||||||
|
* true
|
||||||
|
*/
|
||||||
|
protected reRequestStaleRemoteData<O>(shouldReRequest: boolean, requestFn: () => Observable<RemoteData<O>>) {
|
||||||
|
return (source: Observable<RemoteData<O>>): Observable<RemoteData<O>> => {
|
||||||
|
if (shouldReRequest === true) {
|
||||||
|
return source.pipe(
|
||||||
|
tap((remoteData: RemoteData<O>) => {
|
||||||
|
if (hasValue(remoteData) && remoteData.isStale) {
|
||||||
|
requestFn();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an observable of {@link RemoteData} of an object, based on an href, with a list of
|
* Returns an observable of {@link RemoteData} of an object, based on an href, with a list of
|
||||||
* {@link FollowLinkConfig}, to automatically resolve {@link HALLink}s of the object
|
* {@link FollowLinkConfig}, to automatically resolve {@link HALLink}s of the object
|
||||||
@@ -358,7 +360,12 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
|
|||||||
this.createAndSendGetRequest(requestHref$, useCachedVersionIfAvailable);
|
this.createAndSendGetRequest(requestHref$, useCachedVersionIfAvailable);
|
||||||
|
|
||||||
return this.rdbService.buildSingle<T>(requestHref$, ...linksToFollow).pipe(
|
return this.rdbService.buildSingle<T>(requestHref$, ...linksToFollow).pipe(
|
||||||
reRequestStaleRemoteData(reRequestOnStale, () =>
|
// This skip ensures that if a stale object is present in the cache when you do a
|
||||||
|
// call it isn't immediately returned, but we wait until the remote data for the new request
|
||||||
|
// is created. If useCachedVersionIfAvailable is false it also ensures you don't get a
|
||||||
|
// cached completed object
|
||||||
|
skipWhile((rd: RemoteData<T>) => useCachedVersionIfAvailable ? rd.isStale : rd.hasCompleted),
|
||||||
|
this.reRequestStaleRemoteData(reRequestOnStale, () =>
|
||||||
this.findByHref(href$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow))
|
this.findByHref(href$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -390,7 +397,12 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
|
|||||||
this.createAndSendGetRequest(requestHref$, useCachedVersionIfAvailable);
|
this.createAndSendGetRequest(requestHref$, useCachedVersionIfAvailable);
|
||||||
|
|
||||||
return this.rdbService.buildList<T>(requestHref$, ...linksToFollow).pipe(
|
return this.rdbService.buildList<T>(requestHref$, ...linksToFollow).pipe(
|
||||||
reRequestStaleRemoteData(reRequestOnStale, () =>
|
// This skip ensures that if a stale object is present in the cache when you do a
|
||||||
|
// call it isn't immediately returned, but we wait until the remote data for the new request
|
||||||
|
// is created. If useCachedVersionIfAvailable is false it also ensures you don't get a
|
||||||
|
// cached completed object
|
||||||
|
skipWhile((rd: RemoteData<PaginatedList<T>>) => useCachedVersionIfAvailable ? rd.isStale : rd.hasCompleted),
|
||||||
|
this.reRequestStaleRemoteData(reRequestOnStale, () =>
|
||||||
this.findAllByHref(href$, findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow))
|
this.findAllByHref(href$, findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -110,7 +110,6 @@ export abstract class TasksService<T extends CacheableObject> extends DataServic
|
|||||||
find((href: string) => hasValue(href)),
|
find((href: string) => hasValue(href)),
|
||||||
mergeMap((href) => this.findByHref(href, false, true).pipe(
|
mergeMap((href) => this.findByHref(href, false, true).pipe(
|
||||||
getAllCompletedRemoteData(),
|
getAllCompletedRemoteData(),
|
||||||
filter((rd: RemoteData<T>) => !rd.isSuccessStale),
|
|
||||||
tap(() => this.requestService.setStaleByHrefSubstring(href)))
|
tap(() => this.requestService.setStaleByHrefSubstring(href)))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user