diff --git a/package.json b/package.json index 05ad3c01..306da2cb 100644 --- a/package.json +++ b/package.json @@ -151,7 +151,6 @@ "tslib": "1.6.0", "typescript": "<2.10.0", "uglify-js": "^3.1.9", - "jest-localstorage-mock": "^2.4.0", "zone.js": "^0.8.12" }, "dependencies": { diff --git a/src/common.spec.ts b/src/common.spec.ts index 4a43ffe8..0e03db2b 100644 --- a/src/common.spec.ts +++ b/src/common.spec.ts @@ -1,6 +1,5 @@ import { isLive, isCollection, isResource } from './common'; import { ICacheable } from './interfaces/cacheable'; -import { Resource } from './resource'; import { DocumentCollection } from './document-collection'; import { DocumentResource } from './document-resource'; diff --git a/src/common.ts b/src/common.ts index a7f56f30..c7e26832 100644 --- a/src/common.ts +++ b/src/common.ts @@ -8,11 +8,11 @@ export function isLive(cacheable: ICacheable, ttl: number = null): boolean { } export function isCollection(document: DocumentResource | DocumentCollection): document is DocumentCollection { - return !('id' in document.data); + return document !== null && 'data' in document && document.data !== null && !('id' in document.data); } export function isResource(document: DocumentResource | DocumentCollection): document is DocumentResource { - return 'id' in document.data; + return document !== null && 'data' in document && document.data !== null && 'id' in document.data; } // NOTE: Checks that the service passed to the method is registered (method needs to have service's type or a resource as first arg) diff --git a/src/document-collection.spec.ts b/src/document-collection.spec.ts index e03b6530..1ec62091 100644 --- a/src/document-collection.spec.ts +++ b/src/document-collection.spec.ts @@ -1,7 +1,5 @@ import { DocumentCollection } from './document-collection'; import { Resource } from './resource'; -import { IDataCollection } from './interfaces/data-collection'; -import { Converter } from './services/converter'; describe('document-collection', () => { let collection = new DocumentCollection(); diff --git a/src/document-collection.ts b/src/document-collection.ts index 7c9d7ca2..5a29b944 100644 --- a/src/document-collection.ts +++ b/src/document-collection.ts @@ -1,3 +1,4 @@ +import { IResourcesByType } from './interfaces'; import { Resource } from './resource'; import { Page } from './services/page'; import { Document } from './document'; @@ -25,8 +26,8 @@ export class DocumentCollection extends Document return null; } - public fill(data_collection: IDataCollection): void { - let included_resources = Converter.buildIncluded(data_collection); + public fill(data_collection: IDataCollection, included_resources?: IResourcesByType): void { + included_resources = included_resources || Converter.buildIncluded(data_collection); // sometimes get Cannot set property 'number' of undefined (page) if (this.page && data_collection.meta) { @@ -42,7 +43,7 @@ export class DocumentCollection extends Document this.builded = data_collection.data && data_collection.data.length === 0; for (let dataresource of data_collection.data) { let res = this.find(dataresource.id) || Converter.getService(dataresource.type).getOrCreateResource(dataresource.id); - res.fill({ data: dataresource } /* , included_resources */); // @todo check with included resources? + res.fill({ data: dataresource }, included_resources); new_ids[dataresource.id] = dataresource.id; this.data.push(res); if (Object.keys(res.attributes).length > 0) { diff --git a/src/document-resource.spec.ts b/src/document-resource.spec.ts index b36bdf15..daf55f06 100644 --- a/src/document-resource.spec.ts +++ b/src/document-resource.spec.ts @@ -1,26 +1,54 @@ import { DocumentResource } from './document-resource'; -import { Resource } from './resource'; import { Page } from './services/page'; -import { Document } from './document'; -import { IDataObject } from './interfaces/data-object'; +import { Resource } from './resource'; +import { Converter } from './services/converter'; +import { IRelationships } from './interfaces/relationship'; +import { DocumentCollection } from './document-collection'; +import { Service } from './service'; + +export class MockResource extends Resource { + public attributes = { + name: '', + description: '' + }; + public type = 'resource'; + + public relationships: IRelationships = { + resource: new DocumentResource(), + collection: new DocumentCollection() + }; +} + +class MockResourcesService extends Service { + public type = 'resource'; + public resource = MockResource; +} describe('document resource', () => { - let document_resource = new DocumentResource(); + let document_resource; + + beforeEach(() => { + document_resource = new DocumentResource(); + }); + it('should be created', () => { expect(document_resource.builded).toBe(false); expect(document_resource.content).toBe('id'); }); it('data property should have a new resource instance', () => { - let Resource_spy = spyOn(Resource, 'constructor'); - let resource = new Resource(); + const resource = new Resource(); expect(document_resource.data).toEqual(resource); }); it('page property should have a new page instance', () => { - let page = new Page(); + const page = new Page(); expect(document_resource.page).toEqual(page); }); - it('fill mehotd should call Reource class fill mehtod with the passed IDataObject parameter and fill meta property', () => { - let Resource_fill_spy = spyOn(document_resource.data, 'fill'); + it('fill method should call Resource class fill method with the passed IDataObject parameter and fill meta property', () => { + const mockResourceService = new MockResourcesService(); + const resource = new MockResource(); + spyOn(mockResourceService, 'getOrCreateResource').and.returnValue(resource); + spyOn(Converter, 'getService').and.returnValue(mockResourceService); + const Resource_fill_spy = spyOn(resource, 'fill'); document_resource.fill({ data: { type: 'data', @@ -31,9 +59,13 @@ describe('document resource', () => { expect(Resource_fill_spy).toHaveBeenCalled(); expect(document_resource.meta).toEqual({ meta: 'meta' }); }); - it('if passed IDataObject has no meta property, fill mehotd should should assign an empty Object', () => { + it('if passed IDataObject has no meta property, fill method should should assign an empty Object', () => { document_resource.meta = null; - let Resource_fill_spy = spyOn(document_resource.data, 'fill'); + const mockResourceService = new MockResourcesService(); + const resource = new MockResource(); + spyOn(mockResourceService, 'getOrCreateResource').and.returnValue(resource); + spyOn(Converter, 'getService').and.returnValue(mockResourceService); + const Resource_fill_spy = spyOn(resource, 'fill'); document_resource.fill({ data: { type: 'data', diff --git a/src/document-resource.ts b/src/document-resource.ts index 026d9a22..f6762eb9 100644 --- a/src/document-resource.ts +++ b/src/document-resource.ts @@ -2,6 +2,7 @@ import { Resource } from './resource'; import { Page } from './services/page'; import { Document } from './document'; import { IDataObject } from './interfaces/data-object'; +import { Converter } from './services/converter'; export class DocumentResource extends Document { public data: R = new Resource(); // @todo? @@ -11,6 +12,10 @@ export class DocumentResource extends Document { public page = new Page(); public fill(data_resource: IDataObject): void { + if ((!this.data.type && !this.data.id) && (data_resource.data.type && data_resource.data.id)) { + this.data = Converter.getService(data_resource.data.type).getOrCreateResource(data_resource.data.id); + } + this.data.fill(data_resource); this.meta = data_resource.meta || {}; } diff --git a/src/resource.spec.ts b/src/resource.spec.ts index 3282c3a4..c2ffbf46 100644 --- a/src/resource.spec.ts +++ b/src/resource.spec.ts @@ -1,12 +1,10 @@ import { DocumentCollection } from './document-collection'; -import { IDataObject } from 'src/interfaces/data-object'; -import { Base } from 'src/services/base'; -import { IParamsResource } from 'src/interfaces/params-resource'; +import { IDataObject } from './interfaces/data-object'; +import { IParamsResource } from './interfaces'; import { DocumentResource } from './document-resource'; import { Core } from './core'; import { PathBuilder } from './services/path-builder'; import { Resource } from './resource'; -import { IDataCollection } from './interfaces/data-collection'; import { of } from 'rxjs'; describe('resource', () => { diff --git a/src/resource.ts b/src/resource.ts index 3eb77105..18647884 100644 --- a/src/resource.ts +++ b/src/resource.ts @@ -1,5 +1,6 @@ import { Core } from './core'; -import { IResourcesByType } from './interfaces/resources-by-type'; +import { IDataResource } from './interfaces/data-resource'; +import { IResourcesByType } from './interfaces'; import { Service } from './service'; import { Base } from './services/base'; import { PathBuilder } from './services/path-builder'; @@ -67,10 +68,19 @@ export class Resource implements ICacheable { relationships[relation_alias].data.push(reational_object); // no se agregó aún a included && se ha pedido incluir con el parms.include - let temporal_id = resource.type + '_' + resource.id; + let temporal_id = `${resource.type}_${resource.id}`; if (included_ids.indexOf(temporal_id) === -1 && params.include.indexOf(relation_alias) !== -1) { - included_ids.push(temporal_id); - included.push(resource.toObject({}).data); + const { + data: data, + included: included_resources + }: { data: IDataResource; included?: Array } = resource.toObject(params); + + included_ids = [ + ...included_ids, + temporal_id, + ...(included_resources || []).map((result: IDataResource) => `${result.type}_${result.id}`) + ]; + included = [...included, data, ...(included_resources || [])]; } } } else { @@ -102,10 +112,19 @@ export class Resource implements ICacheable { } // no se agregó aún a included && se ha pedido incluir con el parms.include - let temporal_id = relationship_data.type + '_' + relationship_data.id; + let temporal_id = `${relationship_data.type}_${relationship_data.id}`; if (included_ids.indexOf(temporal_id) === -1 && params.include.indexOf(relation_alias) !== -1) { - included_ids.push(temporal_id); - included.push(relationship_data.toObject({}).data); + const { + data: data, + included: included_resources + }: { data: IDataResource; included?: Array } = relationship_data.toObject(params); + + included_ids = [ + ...included_ids, + temporal_id, + ...(included_resources || []).map((result: IDataResource) => `${result.type}_${result.id}`) + ]; + included = [...included, data, ...(included_resources || [])]; } } } diff --git a/src/service.ts b/src/service.ts index e83afb23..75cbc2f7 100644 --- a/src/service.ts +++ b/src/service.ts @@ -144,7 +144,7 @@ export class Service { } public getOrCreateResource(id: string): R { - let service = Converter.getService(this.type); + let service = this.getService(); if (service.cachememory && id in service.cachememory.resources) { return service.cachememory.resources[id]; } else { diff --git a/src/services/converter.ts b/src/services/converter.ts index 76b151a6..6c143f5c 100644 --- a/src/services/converter.ts +++ b/src/services/converter.ts @@ -2,23 +2,28 @@ import { Core } from '../core'; import { Resource } from '../resource'; import { Service } from '../service'; -import { IResourcesByType, IObjectsById } from '../interfaces'; +import { IObjectsById, IResourcesByType } from '../interfaces'; import { IDataObject } from '../interfaces/data-object'; import { IDataCollection } from '../interfaces/data-collection'; import { IDataResource } from '../interfaces/data-resource'; import { isDevMode } from '@angular/core'; +import { DocumentResource } from '../document-resource'; +import { DocumentCollection } from '../document-collection'; export class Converter { /* Convert json arrays (like included) to an indexed Resources array by [type][id] */ public static json_array2resources_array_by_type(json_array: Array): IResourcesByType { - let all_resources: IObjectsById = {}; - let resources_by_type: IResourcesByType = {}; + const all_resources = Converter.json_array2resources_array(json_array, true); - Converter.json_array2resources_array(json_array, all_resources); - for (const key in all_resources) { - let resource = all_resources[key]; + return Converter.resources_by_type(Object.values(all_resources)); + } + + protected static resources_by_type(resources: Array): IResourcesByType { + let resources_by_type: IResourcesByType = {}; + for (const key in resources) { + let resource = resources[key]; if (!(resource.type in resources_by_type)) { resources_by_type[resource.type] = {}; @@ -29,10 +34,33 @@ export class Converter { return resources_by_type; } - public static json2resource(json_resource: IDataResource, instance_relationships): Resource { + public static json2resource(json_resource: IDataResource, instance_relationships: boolean): Resource { let resource_service = Converter.getService(json_resource.type); if (resource_service) { - return Converter.procreate(json_resource); + const resource = Converter.procreate(json_resource); + if (instance_relationships) { + for (const relationshipName in resource.relationships) { + if (Array.isArray(resource.relationships[relationshipName].data)) { + const resourceData = (resource.relationships[relationshipName].data as Array) + .map(singleResourceData => Converter.procreate(singleResourceData)); + const documentCollection = new DocumentCollection(); + documentCollection.fill({data: resourceData}); + resource.relationships[relationshipName] = documentCollection; + } else { + const resourceData = resource.relationships[relationshipName].data as IDataResource; + if (!resourceData || !resourceData.id || !resourceData.type) { + continue; + } + + const relationshipResource = Converter.procreate(resourceData); + const documentResource = new DocumentResource(); + documentResource.fill({data: relationshipResource}); + resource.relationships[relationshipName] = documentResource; + } + } + } + + return resource; } else { if (isDevMode()) { console.warn( @@ -76,8 +104,8 @@ export class Converter { resource = Converter.getService(data.type).getOrCreateResource(data.id); } - resource.attributes = data.attributes || {}; - resource.relationships = <{ [key: string]: any }>data.relationships; + resource.attributes = { ...(resource.attributes || {}), ...data.attributes }; + resource.relationships = ({ ...(resource.relationships || {}), ...data.relationships }); resource.is_new = false; return resource; @@ -86,10 +114,17 @@ export class Converter { /* Convert json arrays (like included) to an Resources arrays without [keys] */ - private static json_array2resources_array(json_array: Array, destination_array: IObjectsById = {}): void { + private static json_array2resources_array( + json_array: Array, + instance_relationships: boolean = false + ): IObjectsById { + let destination_array: IObjectsById = {}; + for (let data of json_array) { - let resource = Converter.json2resource(data, false); + let resource = Converter.json2resource(data, instance_relationships); destination_array[resource.type + '_' + resource.id] = resource; } + + return destination_array; } } diff --git a/src/services/resource-relationships-converter.spec.ts b/src/services/resource-relationships-converter.spec.ts index 052f6f7b..f83e68e9 100644 --- a/src/services/resource-relationships-converter.spec.ts +++ b/src/services/resource-relationships-converter.spec.ts @@ -1,12 +1,11 @@ import { ResourceRelationshipsConverter } from './resource-relationships-converter'; -import { DocumentCollection } from 'src/document-collection'; +import { DocumentCollection } from '../document-collection'; import { CacheStore } from '../services/cachestore'; import { CacheMemory } from '../services/cachememory'; import { Converter } from './converter'; import { Service } from '../service'; import { DocumentResource } from '../document-resource'; import { Resource } from '../resource'; -import { IResourcesByType } from '../interfaces'; import { IRelationships } from '../interfaces/relationship'; function clone(obj) { @@ -71,11 +70,12 @@ describe('ResourceRelationshipsConverter', () => { it('should set builded to true when a hasOne relationsihp is builded', () => { spyOn(CacheStore.prototype, 'setResource'); + spyOn(Converter, 'getService').and.callFake(getService); resource_relationships_converter.buildRelationships(); expect((resource_relationships_converter as any).relationships_dest.resource.builded).toBeTruthy(); }); - it(`buildRelationships method should add hasMany and hasOne relationships to relationships_dest as appropiapte + it(`buildRelationships method should add hasMany and hasOne relationships to relationships_dest as appropriate using relationships_from data`, () => { // set up spy spyOn(Converter, 'getService').and.callFake(getService); diff --git a/src/services/resource-relationships-converter.ts b/src/services/resource-relationships-converter.ts index faeb30cc..95a8a005 100644 --- a/src/services/resource-relationships-converter.ts +++ b/src/services/resource-relationships-converter.ts @@ -70,7 +70,7 @@ export class ResourceRelationshipsConverter { return; } - (this.relationships_dest[relation_alias]).fill(relation_from_value); + (this.relationships_dest[relation_alias]).fill(relation_from_value, this.included_resources); } private __buildRelationshipHasOne(relation_data_from: IDataObject, relation_alias: string): void { @@ -81,7 +81,8 @@ export class ResourceRelationshipsConverter { return; } - if (relation_data_from.data.id !== (this.relationships_dest[relation_alias].data).id) { + if (!this.relationships_dest[relation_alias].data + || relation_data_from.data.id !== (this.relationships_dest[relation_alias].data).id) { this.relationships_dest[relation_alias].data = new Resource(); } diff --git a/src/test/get-resource-with-parameters.spec.ts b/src/test/get-resource-with-parameters.spec.ts index e074d17b..d913f9e5 100644 --- a/src/test/get-resource-with-parameters.spec.ts +++ b/src/test/get-resource-with-parameters.spec.ts @@ -1,17 +1,15 @@ // WARNING: this test is not isolated -import { HttpClient, HttpHandler, HttpRequest, HttpEvent, HttpResponse, HttpHeaders } from '@angular/common/http'; -import { DocumentCollection } from 'src/document-collection'; +import { HttpClient, HttpHandler, HttpRequest, HttpEvent, HttpResponse } from '@angular/common/http'; +import { DocumentCollection } from '../document-collection'; import { DocumentResource } from '../document-resource'; import { Resource } from '../resource'; import { Http as JsonapiHttpImported } from '../sources/http.service'; import { JsonapiConfig } from '../jsonapi-config'; import { StoreService as JsonapiStore } from '../sources/store.service'; import { Core } from '../core'; -import { Observable, BehaviorSubject, of as observableOf } from 'rxjs'; -import { delay } from 'rxjs/operators'; +import { Observable, BehaviorSubject } from 'rxjs'; import { Service } from '../service'; -import * as localForage from 'localforage'; class TestResource extends Resource { public type = 'test_resources'; @@ -65,12 +63,14 @@ class HttpHandlerMock implements HttpHandler { describe('core methods', () => { let core: Core; - it('should create core service instance', () => { + beforeEach(() => { + // TODO: fix library error: cleacCache and clearCacheMemory are not droping localForage allstore instance correctly while testing core = new Core( new JsonapiConfig(), new JsonapiStore(), new JsonapiHttpImported(new HttpClient(new HttpHandlerMock()), new JsonapiConfig()) ); + Core.injectedServices.JsonapiStoreService.clearCache(); expect(core).toBeTruthy(); }); it(`service's get method should return a stream with the requested resource including the requested attributes (fields)`, async () => { @@ -99,13 +99,6 @@ describe('core methods', () => { }); it(`when requesting a resource with optional attributes, the incoming attributes should be merged with cached ones`, async () => { - // TODO: fix library error: cleacCache and clearCacheMemory are not droping localForage allstore instance correctly while testing - core = new Core( - new JsonapiConfig(), - new JsonapiStore(), - new JsonapiHttpImported(new HttpClient(new HttpHandlerMock()), new JsonapiConfig()) - ); - Core.injectedServices.JsonapiStoreService.clearCache(); let test_service = new TestService(); let http_request_spy = spyOn(HttpClient.prototype, 'request').and.callThrough(); diff --git a/src/test/get-resource.spec.ts b/src/test/get-resource.spec.ts index 225b7f3a..1b08e679 100644 --- a/src/test/get-resource.spec.ts +++ b/src/test/get-resource.spec.ts @@ -1,16 +1,16 @@ // WARNING: this test is not isolated -import { HttpClient, HttpHandler, HttpRequest, HttpEvent, HttpResponse, HttpHeaders } from '@angular/common/http'; -import { DocumentCollection } from 'src/document-collection'; +import { HttpClient, HttpEvent, HttpHandler, HttpHeaders, HttpRequest, HttpResponse } from '@angular/common/http'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { delay } from 'rxjs/operators'; +import { Core } from '../core'; +import { DocumentCollection } from '../document-collection'; import { DocumentResource } from '../document-resource'; +import { JsonapiConfig } from '../jsonapi-config'; import { Resource } from '../resource'; +import { Service } from '../service'; import { Http as JsonapiHttpImported } from '../sources/http.service'; -import { JsonapiConfig } from '../jsonapi-config'; import { StoreService as JsonapiStore } from '../sources/store.service'; -import { Core } from '../core'; -import { Observable, BehaviorSubject, of as observableOf } from 'rxjs'; -import { delay, filter } from 'rxjs/operators'; -import { Service } from '../service'; let test_response_subject = new BehaviorSubject(new HttpResponse()); @@ -36,6 +36,7 @@ class TestService extends Service { super(); this.register(); } + public type = 'test_resources'; public resource = TestResource; public ttl = 0; @@ -92,69 +93,130 @@ describe('core methods', () => { }); }); - it(`the resource should have the correct hasOne and hasMany relationships correspondig to the back end response's included resources, + it(`the resource should have the correct hasOne and hasMany relationships corresponding to the back end response's included resources, including nested relationships`, async () => { - let test_resource = new TestResource(); - test_resource.type = 'test_resources'; - test_resource.id = '1'; - test_resource.attributes = { name: 'test_name' }; - test_resource.relationships.test_resource.data = { id: '2', type: 'test_resources' }; - test_resource.relationships.test_resources.data = [{ id: '3', type: 'test_resources' }, { id: '4', type: 'test_resources' }]; - - // nested relationship - let test_resource_nested_relationship = new TestResource(); - test_resource_nested_relationship.type = 'test_resources'; - test_resource_nested_relationship.id = '4'; - test_resource_nested_relationship.attributes = { name: 'test_name_4' }; - - // format has_one relationship to include - let test_resource_has_one_relationship = new TestResource(); - test_resource_has_one_relationship.type = 'test_resources'; - test_resource_has_one_relationship.id = '2'; - test_resource_has_one_relationship.attributes = { name: 'test_name_2' }; - test_resource_has_one_relationship.relationships.test_resource.data = { id: '4', type: 'test_resources' }; - - // format has_many relationship to include - let test_resource_has_many_relationship_1 = new TestResource(); - test_resource_has_many_relationship_1.type = 'test_resources'; - test_resource_has_many_relationship_1.id = '3'; - test_resource_has_many_relationship_1.attributes = { name: 'test_name_3' }; - test_resource_has_many_relationship_1.relationships.test_resources.data.push({ id: '4', type: 'test_resources' }); - let included = [test_resource_has_one_relationship, test_resource_has_many_relationship_1, test_resource_nested_relationship]; + const response = { + data: { + type: 'test_resources', + id: '1', + attributes: { name: 'test_name' }, + relationships: { + test_resource: { + data: { id: '2', type: 'test_resources' } + }, + test_resources: { + data: [{ id: '3', type: 'test_resources' }, { id: '4', type: 'test_resources' }] + } + } + }, + included: [ + { + type: 'test_resources', + id: '2', + attributes: { name: 'test_name_2' }, + relationships: { + test_resource: { + data: { id: '4', type: 'test_resources' } + } + } + }, + { + type: 'test_resources', + id: '3', + attributes: { name: 'test_name_3' }, + relationships: { + test_resources: { + data: [ + { id: '4', type: 'test_resources' } + ] + } + } + }, + { + type: 'test_resources', + id: '4', + attributes: { name: 'test_name_4' }, + relationships: { + test_resource: { + data: { id: '5', type: 'test_resources' } + }, + test_resources: { + data: [ + { id: '5', type: 'test_resources' } + ] + } + } + }, + { + type: 'test_resources', + id: '5', + attributes: { name: 'test_name_5' }, + relationships: { + test_resource: { + data: { id: '6', type: 'test_resources' } + } + } + }, + { + type: 'test_resources', + id: '6', + attributes: { name: 'test_name_6' } + } + ] + }; let test_service = new TestService(); test_service.clearCacheMemory(); test_service.cachememory.resources = {}; Core.injectedServices.JsonapiStoreService.clearCache(); - test_response_subject.next(new HttpResponse({ body: { data: test_resource, included: included } })); + test_response_subject.next(new HttpResponse({ body: response })); await test_service - .get('1', { include: ['test_resource.test_resource'] }) + .get('1', { include: ['test_resource.test_resource', 'test_resources.test_resource'] }) .toPromise() .then(resource => { - expect(test_resource.type).toBe('test_resources'); - expect(test_resource.id).toBe('1'); + expect(resource.type).toBe('test_resources'); + expect(resource.id).toBe('1'); expect(resource.attributes.name).toBe('test_name'); - expect(resource.relationships.test_resource instanceof DocumentResource).toBeTruthy(); - expect(resource.relationships.test_resources instanceof DocumentCollection).toBeTruthy(); - expect((resource.relationships.test_resource).data.id).toBe('2'); - expect((resource.relationships.test_resource).data.attributes.name).toBe('test_name_2'); - expect( - (resource.relationships.test_resources).data.find(related_resource => related_resource.id === '3') - ).toBeTruthy(); - expect( - (resource.relationships.test_resources).data.find(related_resource => related_resource.id === '3') - .attributes.name - ).toBe('test_name_3'); - let has_one_relationship = (resource.relationships.test_resource).data; - let has_many_relationship = (resource.relationships.test_resources).data; - expect((has_one_relationship.relationships.test_resource.data).id).toBe('4'); - expect((has_many_relationship[0].relationships.test_resources.data[0]).id).toBe('4'); + const has_one_relationship = resource.relationships.test_resource; + expect(has_one_relationship instanceof DocumentResource).toBeTruthy(); + expect(has_one_relationship.data instanceof TestResource).toBeTruthy(); + expect((has_one_relationship.data as TestResource).id).toBe('2'); + expect((has_one_relationship.data as TestResource).type).toBe('test_resources'); + expect((has_one_relationship.data as TestResource).attributes.name).toBe('test_name_2'); + + const has_many_relationship = resource.relationships.test_resources; + expect(has_many_relationship instanceof DocumentCollection).toBeTruthy(); + expect(has_many_relationship.data[0] instanceof TestResource).toBeTruthy(); + expect((has_many_relationship.data[0] as TestResource).id).toBe('3'); + expect((has_many_relationship.data[0] as TestResource).type).toBe('test_resources'); + expect((has_many_relationship.data[0] as TestResource).attributes.name).toBe('test_name_3'); + + const nested_has_many_relationship = has_many_relationship.data[0].relationships.test_resources; + expect(nested_has_many_relationship instanceof DocumentCollection).toBeTruthy(); + expect(nested_has_many_relationship.data[0] instanceof TestResource).toBeTruthy(); + expect((nested_has_many_relationship.data[0] as TestResource).id).toBe('4'); + expect((nested_has_many_relationship.data[0] as TestResource).type).toBe('test_resources'); + expect((nested_has_many_relationship.data[0] as TestResource).attributes.name).toBe('test_name_4'); + + const recursively_nested_has_one_relationship = nested_has_many_relationship.data[0].relationships.test_resource; + expect(recursively_nested_has_one_relationship instanceof DocumentResource).toBeTruthy(); + expect(recursively_nested_has_one_relationship.data instanceof TestResource).toBeTruthy(); + expect((recursively_nested_has_one_relationship.data as TestResource).id).toBe('5'); + expect((recursively_nested_has_one_relationship.data as TestResource).type).toBe('test_resources'); + expect((recursively_nested_has_one_relationship.data as TestResource).attributes.name).toBe('test_name_5'); + + const recursively_nested_has_many_relationship = nested_has_many_relationship.data[0].relationships.test_resources; + expect(recursively_nested_has_many_relationship instanceof DocumentCollection).toBeTruthy(); + expect(recursively_nested_has_many_relationship.data[0] instanceof TestResource).toBeTruthy(); + expect((recursively_nested_has_many_relationship.data[0] as TestResource).id).toBe('5'); + expect((recursively_nested_has_many_relationship.data[0] as TestResource).type).toBe('test_resources'); + expect((recursively_nested_has_many_relationship.data[0] as TestResource).attributes.name).toBe('test_name_5'); }); }); - it(`the resource should have the correct hasOne and hasMany relationships correspondig to the back end response's included resources`, async () => { + it(`the resource should have the correct hasOne and hasMany relationships corresponding to the back end response's included resources`, async () => { let test_resource = new TestResource(); test_resource.type = 'test_resources'; test_resource.id = '1';