Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 141 additions & 5 deletions libs/product/driver/in-memory/src/backend/product.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import {
PLATFORM_ID,
TransferState,
} from '@angular/core';
import { TestBed } from '@angular/core/testing';

import {
Expand All @@ -7,6 +11,7 @@ import {
} from '@daffodil/product/testing';

import { DaffInMemoryBackendProductService } from './product.service';
import { TRANSFER_STATE_KEY } from './transfer-state-key';

describe('Driver | InMemory | Product | DaffInMemoryBackendProductService', () => {
let productTestingService;
Expand All @@ -26,6 +31,91 @@ describe('Driver | InMemory | Product | DaffInMemoryBackendProductService', () =
expect(productTestingService).toBeTruthy();
});

describe('transferring state from the server to the browser', () => {
describe('on browser platform', () => {
let transferState: TransferState;
let mockProducts;

beforeEach(() => {
mockProducts = [
{ id: '1', url: '/product-1.html', name: 'Product 1' },
{ id: '2', url: '/product-2.html', name: 'Product 2' },
];

TestBed.resetTestingModule();
TestBed.configureTestingModule({
providers: [
DaffInMemoryBackendProductService,
provideDaffProductExtraFactoryTypes(DaffProductFactory),
{ provide: PLATFORM_ID, useValue: 'browser' },
],
});

transferState = TestBed.inject(TransferState);
transferState.set(TRANSFER_STATE_KEY, { data: JSON.stringify(mockProducts) });

productTestingService = TestBed.inject(DaffInMemoryBackendProductService);
});

it('should load products from transfer state', () => {
expect(productTestingService.products).toEqual(mockProducts);
});

it('should not create new products', () => {
expect(productTestingService.products.length).toEqual(2);
});
});

describe('on browser platform without transfer state', () => {
beforeEach(() => {
TestBed.resetTestingModule();
TestBed.configureTestingModule({
providers: [
DaffInMemoryBackendProductService,
provideDaffProductExtraFactoryTypes(DaffProductFactory),
{ provide: PLATFORM_ID, useValue: 'browser' },
],
});

productTestingService = TestBed.inject(DaffInMemoryBackendProductService);
});

it('should create default products from factory', () => {
expect(productTestingService.products.length).toEqual(35);
});
});

describe('on server platform', () => {
let transferState: TransferState;

beforeEach(() => {
TestBed.resetTestingModule();
TestBed.configureTestingModule({
providers: [
DaffInMemoryBackendProductService,
provideDaffProductExtraFactoryTypes(DaffProductFactory),
{ provide: PLATFORM_ID, useValue: 'server' },
],
});

transferState = TestBed.inject(TransferState);
productTestingService = TestBed.inject(DaffInMemoryBackendProductService);
});

it('should set products in transfer state', () => {
const transferStateData = transferState.get(TRANSFER_STATE_KEY, null);
expect(transferStateData).toBeTruthy();
expect(transferStateData.data).toBeDefined();
});

it('should serialize products as JSON in transfer state', () => {
const transferStateData = transferState.get(TRANSFER_STATE_KEY, null);
const products = JSON.parse(transferStateData.data);
expect(Array.isArray(products)).toBe(true);
});
});
});

describe('createDb', () => {
let result;

Expand All @@ -42,14 +132,61 @@ describe('Driver | InMemory | Product | DaffInMemoryBackendProductService', () =

describe('get', () => {

describe('when reqInfo.id is "best-sellers"', () => {
describe('when reqInfo.id contains ".html" and matches a product URL', () => {

let reqInfoStub;
let result;
let testProduct;

beforeEach(() => {
testProduct = productTestingService.products[0];
const productUrl = testProduct.url.startsWith('/') ? testProduct.url.slice(1) : testProduct.url;

reqInfoStub = {
id: productUrl,
utils: {
createResponse$: (func) => func(),
},
};

result = productTestingService.get(reqInfoStub);
});

it('should return the matching product', () => {
expect(result.body).toEqual(testProduct);
expect(result.status).toEqual(200);
});
});

describe('when reqInfo.id contains ".html" but does not match any product URL', () => {

let reqInfoStub;
let result;

beforeEach(() => {
reqInfoStub = {
id: 'non-existent-product.html',
utils: {
createResponse$: (func) => func(),
},
};

result = productTestingService.get(reqInfoStub);
});

it('should return undefined', () => {
expect(result).toBeUndefined();
});
});

describe('when reqInfo.id does not contain ".html"', () => {

let reqInfoStub;
let result;

beforeEach(() => {
reqInfoStub = {
id: 'best-sellers',
id: 'some-id',
utils: {
createResponse$: (func) => func(),
},
Expand All @@ -58,9 +195,8 @@ describe('Driver | InMemory | Product | DaffInMemoryBackendProductService', () =
result = productTestingService.get(reqInfoStub);
});

it('should return an Array of 4 products', () => {
expect(result.body).toEqual(jasmine.any(Array));
expect(result.body.length).toEqual(4);
it('should return undefined', () => {
expect(result).toBeUndefined();
});
});
});
Expand Down
87 changes: 37 additions & 50 deletions libs/product/driver/in-memory/src/backend/product.service.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import { Injectable } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import {
inject,
Injectable,
PLATFORM_ID,
TransferState,
} from '@angular/core';
import {
InMemoryDbService,
RequestInfoUtilities,
ParsedRequestUrl,
RequestInfo,
STATUS,
} from 'angular-in-memory-web-api';

import { daffUriTruncateLeadingSlash } from '@daffodil/core/routing';
import { DaffInMemorySingleRouteableBackend } from '@daffodil/driver/in-memory';
import { DaffProduct } from '@daffodil/product';
import {
DaffProductImageFactory,
DaffProductExtensionFactory,
} from '@daffodil/product/testing';
import { DaffProductExtensionFactory } from '@daffodil/product/testing';

import { DAFF_PRODUCT_IN_MEMORY_COLLECTION_NAME } from '../collection-name.const';

import { TRANSFER_STATE_KEY } from './transfer-state-key';
/**
* An in-memory service that stubs out the backend services for getting products.
*
Expand All @@ -37,46 +42,23 @@ export class DaffInMemoryBackendProductService implements InMemoryDbService, Daf
return this._products;
};

constructor(
private productFactory: DaffProductExtensionFactory,
private productImageFactory: DaffProductImageFactory) {
this._products = [
'1001',
'1002',
'1003',
'1004',
'1005',
'1006',
'1007',
'1008',
'1009',
'1010',
'1011',
'1012',
'1013',
'1014',
'1015',
'1016',
'1017',
'1018',
'1019',
'1020',
'1021',
'1022',
'1023',
'1024',
'1025',
'1026',
'1027',
'1028',
'1029',
'1030',
'1031',
'1032',
'1033',
'1034',
'1035',
].map(id => this.productFactory.create({ id, url: `/${id}` }));
transferState = inject(TransferState);

platform = inject(PLATFORM_ID);

productFactory = inject(DaffProductExtensionFactory);
Comment on lines +45 to +49
Copy link
Member

Choose a reason for hiding this comment

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

It doesn't seem like these need to be fields of this class, just inject in constructor and use locally.


constructor(){
if(isPlatformBrowser(this.platform)) {
this._products = JSON.parse(
this.transferState.get(
TRANSFER_STATE_KEY,
{ data: JSON.stringify(this.productFactory.createMany(35)) },
Copy link
Member

Choose a reason for hiding this comment

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

Do we need to do serialization ourselves? TransferState should handle that:

The values in the store are serialized/deserialized using JSON.stringify/JSON.parse. So only boolean, number, string, null and non-class objects will be serialized and deserialized in a non-lossy manner.

).data,
);
} else {
this.transferState.set(TRANSFER_STATE_KEY, { data: JSON.stringify(this._products) });
Copy link
Member

Choose a reason for hiding this comment

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

Where are the products actually being created?

}
}

/**
Expand Down Expand Up @@ -109,14 +91,19 @@ export class DaffInMemoryBackendProductService implements InMemoryDbService, Daf
* @param reqInfo request object
* @returns An http response object
*/
get(reqInfo: any) {
if (reqInfo.id === 'best-sellers') {
get(reqInfo: RequestInfo) {
if(reqInfo.id?.includes('.html')) {
const product = this._products.find((p) => daffUriTruncateLeadingSlash(p.url) === reqInfo.id);

if(!product) {
return undefined;
}

return reqInfo.utils.createResponse$(() => ({
body: this._products.slice(0,4),
body: product,
status: STATUS.OK,
}));
}

return undefined;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { makeStateKey } from '@angular/core';

export const TRANSFER_STATE_KEY = makeStateKey<{data: string}>('DAFF_PRODUCTS_INMEMORY_DATA');
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

import { daffUriTruncateLeadingSlash } from '@daffodil/core/routing';
import { DaffInMemoryDriverBase } from '@daffodil/driver/in-memory';
import { DaffProduct } from '@daffodil/product';
import {
Expand Down Expand Up @@ -48,7 +49,7 @@ export class DaffInMemoryProductService extends DaffInMemoryDriverBase implement
}

getByUrl(url: DaffProduct['url']): Observable<DaffProductDriverResponse> {
return this.http.get<DaffProduct>(`${this.url}${url}`).pipe(
return this.http.get<DaffProduct>(`${this.url}/${daffUriTruncateLeadingSlash(url)}`).pipe(
map(this.transform),
);
}
Expand Down