diff --git a/angular.json b/angular.json index b054592697..f46f87eb53 100644 --- a/angular.json +++ b/angular.json @@ -625,6 +625,45 @@ } } } + }, + "cross-sell-products": { + "root": "libs/cross-sell-products", + "sourceRoot": "libs/cross-sell-products", + "projectType": "library", + "prefix": "@daffodil", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:ng-packagr", + "options": { + "tsConfig": "libs/cross-sell-products/tsconfig.lib.json", + "project": "libs/cross-sell-products/ng-package.json" + }, + "configurations": { + "production": { + "project": "libs/cross-sell-products/ng-package.prod.json", + "tsConfig": "libs/cross-sell-products/tsconfig.lib.prod.json" + } + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "libs/cross-sell-products/test.ts", + "codeCoverage": true, + "tsConfig": "libs/cross-sell-products/tsconfig.spec.json", + "karmaConfig": "libs/cross-sell-products/karma.conf.js" + } + }, + "lint": { + "builder": "@angular-eslint/builder:lint", + "options": { + "lintFilePatterns": [ + "libs/cross-sell-products/**/*.ts", + "libs/cross-sell-products/**/*.html" + ] + } + } + } }, "product-configurable": { "root": "libs/product-configurable", diff --git a/libs/cart/driver/in-memory/src/injection-tokens/extra-attributes-hook.token.ts b/libs/cart/driver/in-memory/src/injection-tokens/extra-attributes-hook.token.ts index feda90f599..9ac4aa11d8 100644 --- a/libs/cart/driver/in-memory/src/injection-tokens/extra-attributes-hook.token.ts +++ b/libs/cart/driver/in-memory/src/injection-tokens/extra-attributes-hook.token.ts @@ -29,6 +29,7 @@ export const { * Provider function for {@link DAFF_CART_IN_MEMORY_EXTRA_ATTRIBUTES_HOOK}. */ provider: provideDaffCartInMemoryExtraAttributesHook, + factoryProvider: provideDaffCartInMemoryExtraAttributesHookFactory, } = createSingleInjectionToken( 'DAFF_CART_IN_MEMORY_EXTRA_ATTRIBUTES_HOOK', { factory: () => (reqInfo, cart) => ({}) }, diff --git a/libs/cart/driver/in-memory/src/injection-tokens/public_api.ts b/libs/cart/driver/in-memory/src/injection-tokens/public_api.ts index 0a421786dd..09b50640fb 100644 --- a/libs/cart/driver/in-memory/src/injection-tokens/public_api.ts +++ b/libs/cart/driver/in-memory/src/injection-tokens/public_api.ts @@ -1,4 +1,6 @@ export { DAFF_CART_IN_MEMORY_EXTRA_ATTRIBUTES_HOOK, DaffCartInMemoryExtraAttributesHook, + provideDaffCartInMemoryExtraAttributesHook, + provideDaffCartInMemoryExtraAttributesHookFactory, } from './extra-attributes-hook.token'; diff --git a/libs/cross-sell-products/README.md b/libs/cross-sell-products/README.md new file mode 100644 index 0000000000..b8ca926ce4 --- /dev/null +++ b/libs/cross-sell-products/README.md @@ -0,0 +1,16 @@ +# @daffodil/cross-sell-products +`@daffodil/cross-sell-products` is an optional package that adds the cross-sell products feature on to `@daffodil/product`. + +## Installation +To install `@daffodil/cross-sell-products`, use the following commands in your terminal. + +Install with npm: +```bash +npm install @daffodil/cross-sell-products --save +``` + +Install with yarn: + +```bash +yarn add @daffodil/cross-sell-products +``` diff --git a/libs/cross-sell-products/driver/in-memory/ng-package.json b/libs/cross-sell-products/driver/in-memory/ng-package.json new file mode 100644 index 0000000000..7dcb29e536 --- /dev/null +++ b/libs/cross-sell-products/driver/in-memory/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../../node_modules/ng-packagr/ng-entrypoint.schema.json", + "lib": { + "entryFile": "src/index.ts" + } +} diff --git a/libs/cross-sell-products/driver/in-memory/src/index.ts b/libs/cross-sell-products/driver/in-memory/src/index.ts new file mode 100644 index 0000000000..4aaf8f92ed --- /dev/null +++ b/libs/cross-sell-products/driver/in-memory/src/index.ts @@ -0,0 +1 @@ +export * from './public_api'; diff --git a/libs/cross-sell-products/driver/in-memory/src/provider.ts b/libs/cross-sell-products/driver/in-memory/src/provider.ts new file mode 100644 index 0000000000..cbfa9307c6 --- /dev/null +++ b/libs/cross-sell-products/driver/in-memory/src/provider.ts @@ -0,0 +1,33 @@ +import { + inject, + makeEnvironmentProviders, +} from '@angular/core'; +import { RequestInfo } from 'angular-in-memory-web-api'; + +import { DaffCart } from '@daffodil/cart'; +import { provideDaffCartInMemoryExtraAttributesHookFactory } from '@daffodil/cart/driver/in-memory'; +import { DaffCartWithCrossSellProducts } from '@daffodil/cross-sell-products'; +import { DaffInMemoryBackendProductService } from '@daffodil/product/driver/in-memory'; + +/** + * Main provider entrypoint for `@daffodil/cross-sell-products/driver/in-memory`. + */ +export const provideInMemoryCrossSellProductsDriver = () => makeEnvironmentProviders([ + provideDaffCartInMemoryExtraAttributesHookFactory(() => { + const productBackend = inject(DaffInMemoryBackendProductService); + return (reqInfo: RequestInfo, cart: DaffCart): DaffCartWithCrossSellProducts => { + // look up products in the backend via cart items' product ID + // this is dependent on cart add item actually verifying that the products + // exist in the backend before adding to the cart + const crossSells = cart.items + ?.map((item) => productBackend.products.find((product) => product.id === item.product_id)) + .filter((e) => !!e) + ?? []; + return { + ...cart, + crossSells, + crossSellIds: crossSells.map((product) => product.id), + }; + }; + }), +]); diff --git a/libs/cross-sell-products/driver/in-memory/src/public_api.ts b/libs/cross-sell-products/driver/in-memory/src/public_api.ts new file mode 100644 index 0000000000..93bfa469a0 --- /dev/null +++ b/libs/cross-sell-products/driver/in-memory/src/public_api.ts @@ -0,0 +1 @@ +export { provideInMemoryCrossSellProductsDriver } from './provider'; diff --git a/libs/cross-sell-products/driver/magento/ng-package.json b/libs/cross-sell-products/driver/magento/ng-package.json new file mode 100644 index 0000000000..7dcb29e536 --- /dev/null +++ b/libs/cross-sell-products/driver/magento/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../../node_modules/ng-packagr/ng-entrypoint.schema.json", + "lib": { + "entryFile": "src/index.ts" + } +} diff --git a/libs/cross-sell-products/driver/magento/src/index.ts b/libs/cross-sell-products/driver/magento/src/index.ts new file mode 100644 index 0000000000..4aaf8f92ed --- /dev/null +++ b/libs/cross-sell-products/driver/magento/src/index.ts @@ -0,0 +1 @@ +export * from './public_api'; diff --git a/libs/cross-sell-products/driver/magento/src/models/cart-item-with-cross-sell.type.ts b/libs/cross-sell-products/driver/magento/src/models/cart-item-with-cross-sell.type.ts new file mode 100644 index 0000000000..1035c9e9f5 --- /dev/null +++ b/libs/cross-sell-products/driver/magento/src/models/cart-item-with-cross-sell.type.ts @@ -0,0 +1,11 @@ +import { MagentoCartItem } from '@daffodil/cart/driver/magento'; +import { MagentoProductPreview } from '@daffodil/product/driver/magento'; + +/** + * A cart whose items include a list of cross-sell products. + */ +export interface MagentoCartItemWithCrossSell extends MagentoCartItem { + product: MagentoCartItem['product'] & { + crosssell_products: Array; + }; +} diff --git a/libs/cross-sell-products/driver/magento/src/models/cart-with-cross-sell.type.ts b/libs/cross-sell-products/driver/magento/src/models/cart-with-cross-sell.type.ts new file mode 100644 index 0000000000..1ec9510904 --- /dev/null +++ b/libs/cross-sell-products/driver/magento/src/models/cart-with-cross-sell.type.ts @@ -0,0 +1,7 @@ +import { MagentoCart } from '@daffodil/cart/driver/magento'; + +import { MagentoCartItemWithCrossSell } from './cart-item-with-cross-sell.type'; + +export interface MagentoCartWithCrossSell extends MagentoCart { + items: Array; +} diff --git a/libs/cross-sell-products/driver/magento/src/models/public_api.ts b/libs/cross-sell-products/driver/magento/src/models/public_api.ts new file mode 100644 index 0000000000..7f467a2cc4 --- /dev/null +++ b/libs/cross-sell-products/driver/magento/src/models/public_api.ts @@ -0,0 +1,2 @@ +export { MagentoCartItemWithCrossSell } from './cart-item-with-cross-sell.type'; +export { MagentoCartWithCrossSell } from './cart-with-cross-sell.type'; diff --git a/libs/cross-sell-products/driver/magento/src/provider.ts b/libs/cross-sell-products/driver/magento/src/provider.ts new file mode 100644 index 0000000000..b2d94fe7f6 --- /dev/null +++ b/libs/cross-sell-products/driver/magento/src/provider.ts @@ -0,0 +1,45 @@ +import { + inject, + makeEnvironmentProviders, +} from '@angular/core'; + +import { DaffCart } from '@daffodil/cart'; +import { + provideDaffCartMagentoExtraCartFragmentFactories, + provideDaffCartMagentoCartTransformFactories, +} from '@daffodil/cart/driver/magento'; +import { DaffCartWithCrossSellProducts } from '@daffodil/cross-sell-products'; +import { DaffProduct } from '@daffodil/product'; +import { + DAFF_PRODUCT_MAGENTO_EXTRA_PRODUCT_FRAGMENTS, + DAFF_PRODUCT_MAGENTO_EXTRA_PRODUCT_PREVIEW_FRAGMENTS, + DaffMagentoProductsTransformer, + MAGENTO_PRODUCT_CONFIG_TOKEN, +} from '@daffodil/product/driver/magento'; + +import { MagentoCartWithCrossSell } from './models/public_api'; +import { magentoCrossSellProductsFragment } from './queries/fragments/cross-sell-products'; + +/** + * Main provider entrypoint for `@daffodil/cross-sell-products/driver/magento`. + */ +export const provideMagentoCrossSellProductsDriver = () => makeEnvironmentProviders([ + provideDaffCartMagentoExtraCartFragmentFactories(() => + magentoCrossSellProductsFragment([ + ...inject(DAFF_PRODUCT_MAGENTO_EXTRA_PRODUCT_PREVIEW_FRAGMENTS), + ...inject(DAFF_PRODUCT_MAGENTO_EXTRA_PRODUCT_FRAGMENTS), + ]), + ), + provideDaffCartMagentoCartTransformFactories(() => { + const transformer = inject(DaffMagentoProductsTransformer); + const config = inject(MAGENTO_PRODUCT_CONFIG_TOKEN); + return (daffCart: DaffCart, magentoCart: MagentoCartWithCrossSell): DaffCartWithCrossSellProducts => { + const crossSells = magentoCart.items.reduce((acc, item) => acc.concat(item.product.crosssell_products.map((p) => transformer.transformMagentoProduct(p, config.baseMediaUrl))), >[]); + return { + ...daffCart, + crossSells, + crossSellIds: crossSells.map((p) => p.id), + }; + }; + }), +]); diff --git a/libs/cross-sell-products/driver/magento/src/public_api.ts b/libs/cross-sell-products/driver/magento/src/public_api.ts new file mode 100644 index 0000000000..dd4d358cc1 --- /dev/null +++ b/libs/cross-sell-products/driver/magento/src/public_api.ts @@ -0,0 +1,4 @@ +export * from './models/public_api'; + +export { magentoCrossSellProductsFragment } from './queries/fragments/cross-sell-products'; +export { provideMagentoCrossSellProductsDriver } from './provider'; diff --git a/libs/cross-sell-products/driver/magento/src/queries/fragments/cross-sell-products.ts b/libs/cross-sell-products/driver/magento/src/queries/fragments/cross-sell-products.ts new file mode 100644 index 0000000000..bab0929198 --- /dev/null +++ b/libs/cross-sell-products/driver/magento/src/queries/fragments/cross-sell-products.ts @@ -0,0 +1,23 @@ +import { gql } from 'apollo-angular'; +import { DocumentNode } from 'graphql'; + +import { + daffBuildFragmentDefinition, + daffBuildFragmentNameSpread, +} from '@daffodil/core/graphql'; +import { magentoProductPreviewFragment } from '@daffodil/product/driver/magento'; + +export const magentoCrossSellProductsFragment = (extraProductFragments: DocumentNode[] = []) => gql` + fragment crossSellProducts on Cart { + items { + product { + crosssell_products { + ...magentoProductPreview + ${daffBuildFragmentNameSpread(...extraProductFragments)} + } + } + } + } + ${magentoProductPreviewFragment} + ${daffBuildFragmentDefinition(...extraProductFragments)} +`; diff --git a/libs/cross-sell-products/driver/ng-package.json b/libs/cross-sell-products/driver/ng-package.json new file mode 100644 index 0000000000..0f621f8520 --- /dev/null +++ b/libs/cross-sell-products/driver/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-entrypoint.schema.json", + "lib": { + "entryFile": "src/index.ts" + } +} diff --git a/libs/cross-sell-products/driver/src/index.ts b/libs/cross-sell-products/driver/src/index.ts new file mode 100644 index 0000000000..4aaf8f92ed --- /dev/null +++ b/libs/cross-sell-products/driver/src/index.ts @@ -0,0 +1 @@ +export * from './public_api'; diff --git a/libs/cross-sell-products/driver/src/public_api.ts b/libs/cross-sell-products/driver/src/public_api.ts new file mode 100644 index 0000000000..cb0ff5c3b5 --- /dev/null +++ b/libs/cross-sell-products/driver/src/public_api.ts @@ -0,0 +1 @@ +export {}; diff --git a/libs/cross-sell-products/integration-tests/state/dedupe-cross-sell-products.spec.ts b/libs/cross-sell-products/integration-tests/state/dedupe-cross-sell-products.spec.ts new file mode 100644 index 0000000000..20647bd150 --- /dev/null +++ b/libs/cross-sell-products/integration-tests/state/dedupe-cross-sell-products.spec.ts @@ -0,0 +1,55 @@ +import { TestBed } from '@angular/core/testing'; +import { EffectsModule } from '@ngrx/effects'; +import { + StoreModule, + Store, +} from '@ngrx/store'; + +import { DaffTestingCartDriverModule } from '@daffodil/cart/driver/testing'; +import { + DAFF_CART_STORE_FEATURE_KEY , + DaffResolveCartSuccess, +} from '@daffodil/cart/state'; +import { DaffCartWithCrossSellProducts } from '@daffodil/cross-sell-products'; +import { + DaffCrossSellProductStateModule, + DaffCrossSellProductStateRootSlice, +} from '@daffodil/cross-sell-products/state'; +import { DaffCartWithCrossSellProductsFactory } from '@daffodil/cross-sell-products/testing'; +import { DaffProductTestingDriverModule } from '@daffodil/product/driver/testing'; + +describe('@daffodil/cross-sell-products/state | Deduping CrossSell Products Only from Product Reducers', () => { + let store: Store; + let cart: DaffCartWithCrossSellProducts; + let crossSellProductFactory: DaffCartWithCrossSellProductsFactory; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + StoreModule.forRoot({}), + EffectsModule.forRoot(), + DaffCrossSellProductStateModule, + DaffProductTestingDriverModule.forRoot(), + DaffTestingCartDriverModule.forRoot(), + ], + }); + + store = TestBed.inject(Store); + crossSellProductFactory = TestBed.inject(DaffCartWithCrossSellProductsFactory); + + cart = crossSellProductFactory.create(); + }); + + describe('when the cart is loaded with cross-sell products', () => { + beforeEach(() => { + store.dispatch(new DaffResolveCartSuccess(cart)); + }); + + xit('should not store nested cross-sell products in cart state', done => { + store.subscribe((state) => { + expect(state[DAFF_CART_STORE_FEATURE_KEY].cart.cart?.crossSells).toBeFalsy(); + done(); + }); + }); + }); +}); diff --git a/libs/cross-sell-products/karma.conf.js b/libs/cross-sell-products/karma.conf.js new file mode 100644 index 0000000000..3b0f443a83 --- /dev/null +++ b/libs/cross-sell-products/karma.conf.js @@ -0,0 +1,10 @@ +baseConfiguration = require('../../tools/karma/karma.conf'); + +module.exports = function (config) { + baseConfiguration(config); + config.set({ + coverageIstanbulReporter: { + dir: require('path').join(__dirname, '../../coverage/libs/cross-sell-products'), + }, + }); +}; diff --git a/libs/cross-sell-products/ng-package.json b/libs/cross-sell-products/ng-package.json new file mode 100644 index 0000000000..457d6d5c2e --- /dev/null +++ b/libs/cross-sell-products/ng-package.json @@ -0,0 +1,8 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "dest": "../../dist/cross-sell-products", + "deleteDestPath": false, + "lib": { + "entryFile": "src/index.ts" + } +} diff --git a/libs/cross-sell-products/ng-package.prod.json b/libs/cross-sell-products/ng-package.prod.json new file mode 100644 index 0000000000..a5df029bd2 --- /dev/null +++ b/libs/cross-sell-products/ng-package.prod.json @@ -0,0 +1,7 @@ +{ + "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", + "dest": "../../dist/cross-sell-products", + "lib": { + "entryFile": "src/index.ts" + } +} diff --git a/libs/cross-sell-products/package.json b/libs/cross-sell-products/package.json new file mode 100644 index 0000000000..3672b8554e --- /dev/null +++ b/libs/cross-sell-products/package.json @@ -0,0 +1,58 @@ +{ + "name": "@daffodil/cross-sell-products", + "nx": { + "targets": { + "build": { + "outputs": [ + "{workspaceRoot}/dist/cross-sell-products" + ] + } + } + }, + "version": "0.0.0-PLACEHOLDER", + "description": "Platform-agnostic models, state management, and drivers for cross-sell products, extending @daffodil/product. Part of the Daffodil ecommerce framework.", + "repository": { + "type": "git", + "url": "https://github.com/graycoreio/daffodil" + }, + "author": "Graycore LLC", + "license": "MIT", + "bugs": { + "url": "https://github.com/graycoreio/daffodil/issues" + }, + "publishConfig": { + "directory": "../../dist/cross-sell-products" + }, + "scripts": { + "build": "ng build cross-sell-products --configuration production", + "lint": "cd ../.. && ng lint cross-sell-products", + "lint:fix": "npm run lint -- --fix", + "test": "ng test cross-sell-products --watch=false --browsers=ChromeHeadless", + "publish": "cd ../../dist/cross-sell-products && npm publish --access=public" + }, + "homepage": "https://github.com/graycoreio/daffodil", + "peerDependencies": { + "@angular/common": "0.0.0-PLACEHOLDER", + "@angular/core": "0.0.0-PLACEHOLDER", + "@daffodil/core": "0.0.0-PLACEHOLDER", + "@daffodil/cart": "0.0.0-PLACEHOLDER", + "@daffodil/product": "0.0.0-PLACEHOLDER", + "@ngrx/effects": "0.0.0-PLACEHOLDER", + "@ngrx/entity": "0.0.0-PLACEHOLDER", + "@ngrx/store": "0.0.0-PLACEHOLDER", + "rxjs": "0.0.0-PLACEHOLDER" + }, + "optionalDependencies": { + "@apollo/client": "0.0.0-PLACEHOLDER", + "@daffodil/driver": "0.0.0-PLACEHOLDER", + "angular-in-memory-web-api": "0.0.0-PLACEHOLDER", + "apollo-angular": "0.0.0-PLACEHOLDER", + "@faker-js/faker": "0.0.0-PLACEHOLDER" + }, + "devDependencies": { + "@daffodil/core": "0.0.0-PLACEHOLDER", + "@daffodil/cart": "0.0.0-PLACEHOLDER", + "@daffodil/driver": "0.0.0-PLACEHOLDER", + "@daffodil/product": "0.0.0-PLACEHOLDER" + } +} diff --git a/libs/cross-sell-products/src/index.ts b/libs/cross-sell-products/src/index.ts new file mode 100644 index 0000000000..4aaf8f92ed --- /dev/null +++ b/libs/cross-sell-products/src/index.ts @@ -0,0 +1 @@ +export * from './public_api'; diff --git a/libs/cross-sell-products/src/models/cart-with-cross-sell.interface.ts b/libs/cross-sell-products/src/models/cart-with-cross-sell.interface.ts new file mode 100644 index 0000000000..1ff05e9081 --- /dev/null +++ b/libs/cross-sell-products/src/models/cart-with-cross-sell.interface.ts @@ -0,0 +1,13 @@ +import { DaffCart } from '@daffodil/cart'; +import { DaffProduct } from '@daffodil/product'; + +/** + * An extension of a {@link DaffCart} that includes a list of cross-sell products. + */ +export interface DaffCartWithCrossSellProducts extends DaffCart { + /** + * A list of cross-sell products. + */ + crossSells: Array; + crossSellIds: Array; +} diff --git a/libs/cross-sell-products/src/models/public_api.ts b/libs/cross-sell-products/src/models/public_api.ts new file mode 100644 index 0000000000..6f3c955d7d --- /dev/null +++ b/libs/cross-sell-products/src/models/public_api.ts @@ -0,0 +1 @@ +export { DaffCartWithCrossSellProducts } from './cart-with-cross-sell.interface'; diff --git a/libs/cross-sell-products/src/public_api.ts b/libs/cross-sell-products/src/public_api.ts new file mode 100644 index 0000000000..15a170da9d --- /dev/null +++ b/libs/cross-sell-products/src/public_api.ts @@ -0,0 +1 @@ +export * from './models/public_api'; diff --git a/libs/cross-sell-products/state/ng-package.json b/libs/cross-sell-products/state/ng-package.json new file mode 100644 index 0000000000..0f621f8520 --- /dev/null +++ b/libs/cross-sell-products/state/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-entrypoint.schema.json", + "lib": { + "entryFile": "src/index.ts" + } +} diff --git a/libs/cross-sell-products/state/src/facades/cross-sell-product-facade.interface.ts b/libs/cross-sell-products/state/src/facades/cross-sell-product-facade.interface.ts new file mode 100644 index 0000000000..46d325098a --- /dev/null +++ b/libs/cross-sell-products/state/src/facades/cross-sell-product-facade.interface.ts @@ -0,0 +1,16 @@ +import { Action } from '@ngrx/store'; +import { Observable } from 'rxjs'; + +import { DaffStoreFacade } from '@daffodil/core/state'; +import { DaffProduct } from '@daffodil/product'; + + +/** + * A facade for getting state about a particular product's cross-sell products. + */ +export interface DaffCrossSellProductsFacadeInterface extends DaffStoreFacade { + /** + * A list of cross-sell products of the current product. + */ + crossSellProducts$: Observable; +} diff --git a/libs/cross-sell-products/state/src/facades/cross-sell-product.facade.ts b/libs/cross-sell-products/state/src/facades/cross-sell-product.facade.ts new file mode 100644 index 0000000000..0279333e15 --- /dev/null +++ b/libs/cross-sell-products/state/src/facades/cross-sell-product.facade.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@angular/core'; +import { + Action, + Store, + select, +} from '@ngrx/store'; +import { Observable } from 'rxjs'; + +import { DaffProduct } from '@daffodil/product'; + +import { DaffCrossSellProductsFacadeInterface } from './cross-sell-product-facade.interface'; +import { DaffCrossSellProductStateRootSlice } from '../reducers/reducers-state.interface'; +import { getDaffCrossSellProductsSelectors } from '../selectors/public_api'; + +/** + * @inheritdoc + */ +@Injectable({ + providedIn: 'root', +}) +export class DaffCrossSellProductsFacade implements DaffCrossSellProductsFacadeInterface { + crossSellProducts$: Observable; + + constructor(private store: Store>) { + const selectors = getDaffCrossSellProductsSelectors(); + this.crossSellProducts$ = this.store.pipe(select(selectors.selectCrossSellProducts)); + } + + dispatch(action: Action) { + return this.store.dispatch(action); + } +} diff --git a/libs/cross-sell-products/state/src/facades/public_api.ts b/libs/cross-sell-products/state/src/facades/public_api.ts new file mode 100644 index 0000000000..3cf6535bf5 --- /dev/null +++ b/libs/cross-sell-products/state/src/facades/public_api.ts @@ -0,0 +1,2 @@ +export { DaffCrossSellProductsFacadeInterface } from './cross-sell-product-facade.interface'; +export { DaffCrossSellProductsFacade } from './cross-sell-product.facade'; diff --git a/libs/cross-sell-products/state/src/index.ts b/libs/cross-sell-products/state/src/index.ts new file mode 100644 index 0000000000..4aaf8f92ed --- /dev/null +++ b/libs/cross-sell-products/state/src/index.ts @@ -0,0 +1 @@ +export * from './public_api'; diff --git a/libs/cross-sell-products/state/src/injection-tokens/error-matcher.token.ts b/libs/cross-sell-products/state/src/injection-tokens/error-matcher.token.ts new file mode 100644 index 0000000000..f93c9dc46d --- /dev/null +++ b/libs/cross-sell-products/state/src/injection-tokens/error-matcher.token.ts @@ -0,0 +1,10 @@ +import { createSingleInjectionToken } from '@daffodil/core'; +import { daffTransformErrorToStateError } from '@daffodil/core/state'; + +export const { + token: DAFF_CROSS_SELL_PRODUCTS_ERROR_MATCHER, + provider: provideDaffCrossSellProductsErrorMatcher, +} = createSingleInjectionToken( + 'DAFF_CROSS_SELL_PRODUCTS_ERROR_MATCHER', + { factory: () => daffTransformErrorToStateError }, +); diff --git a/libs/cross-sell-products/state/src/injection-tokens/public_api.ts b/libs/cross-sell-products/state/src/injection-tokens/public_api.ts new file mode 100644 index 0000000000..315411d8fd --- /dev/null +++ b/libs/cross-sell-products/state/src/injection-tokens/public_api.ts @@ -0,0 +1 @@ +export * from './error-matcher.token'; diff --git a/libs/cross-sell-products/state/src/public_api.ts b/libs/cross-sell-products/state/src/public_api.ts new file mode 100644 index 0000000000..2b6cacd0e7 --- /dev/null +++ b/libs/cross-sell-products/state/src/public_api.ts @@ -0,0 +1,5 @@ +export * from './facades/public_api'; +export * from './reducers/public_api'; +export * from './selectors/public_api'; + +export { DaffCrossSellProductStateModule } from './state.module'; diff --git a/libs/cross-sell-products/state/src/reducers/meta-reducers.spec.ts b/libs/cross-sell-products/state/src/reducers/meta-reducers.spec.ts new file mode 100644 index 0000000000..2d85a910fc --- /dev/null +++ b/libs/cross-sell-products/state/src/reducers/meta-reducers.spec.ts @@ -0,0 +1,65 @@ +// import { TestBed } from '@angular/core/testing'; + +// import { DaffCrossSellProduct } from '@daffodil/cross-sell-products'; +// import { DaffCrossSellProductFactory } from '@daffodil/cross-sell-products/testing'; +// import { DaffProduct } from '@daffodil/product'; +// import { DaffProductPageLoadSuccess } from '@daffodil/product/state'; +// import { +// DaffProductFactory, +// DaffProductTestingModule, +// } from '@daffodil/product/testing'; + +// import { daffCrossSellProductsDedupeMetaReducer } from './meta-reducers'; + +// interface MockState { +// product: DaffProduct; +// } + +// const initialState: MockState = { +// product: null, +// }; + +// function reducer(state: MockState = initialState, action: DaffProductPageLoadSuccess): MockState { +// return { +// ...state, +// product: action.payload.products[0], +// }; +// } + +// describe('@daffodil/cross-sell-products/state | daffCrossSellProductsDedupeMetaReducer', () => { +// let mockCrossSellProduct: DaffCrossSellProduct; +// let crossSellProductFactory: DaffCrossSellProductFactory; +// let productFactory: DaffProductFactory; + +// let result: MockState; + +// beforeEach(() => { +// TestBed.configureTestingModule({ +// imports: [ +// DaffProductTestingModule, +// ], +// }); + +// productFactory = TestBed.inject(DaffProductFactory); +// crossSellProductFactory = TestBed.inject(DaffCrossSellProductFactory); +// mockCrossSellProduct = crossSellProductFactory.create({ +// crossSell: productFactory.createMany(3), +// }); + +// result = daffCrossSellProductsDedupeMetaReducer(reducer)( +// initialState, +// new DaffProductPageLoadSuccess({ +// id: mockCrossSellProduct.id, +// products: [mockCrossSellProduct], +// }), +// ); +// }); + +// it('should remove the nested cross-sell products from the payload', () => { +// expect((result.product).crossSell).toBeFalsy(); +// }); + +// it('should not mutate the original product object', () => { +// expect(mockCrossSellProduct.crossSell).toBeTruthy(); +// }); +// }); diff --git a/libs/cross-sell-products/state/src/reducers/meta-reducers.ts b/libs/cross-sell-products/state/src/reducers/meta-reducers.ts new file mode 100644 index 0000000000..f60155f49e --- /dev/null +++ b/libs/cross-sell-products/state/src/reducers/meta-reducers.ts @@ -0,0 +1,59 @@ +import { + Action, + ActionReducer, + MetaReducer, +} from '@ngrx/store'; + +import { + DaffCartReducersState, + DaffCartRetrievalActionInjection, + daffCartRetrievalGetResponse, +} from '@daffodil/cart/state'; +import { DaffCartWithCrossSellProducts } from '@daffodil/cross-sell-products'; +import { + daffProductEntitiesAdapter, + DaffProductReducersState, +} from '@daffodil/product/state'; + + +/** + * A meta reducer that ingests the cross sell products on the cart + * and adds them to product entities state. + */ +export function daffCrossSellProductEntitiesMetaReducerFactory(retrievalActions: Array): MetaReducer> { + return (reducer: ActionReducer>): ActionReducer> => (state: T, action: Action) => { + const cart = daffCartRetrievalGetResponse(action, retrievalActions); + if (cart && cart.crossSells) { + const adapter = daffProductEntitiesAdapter(); + return reducer( + { + ...state, + products: adapter.upsertMany(cart.crossSells, state.products), + }, + action, + ); + } else { + return reducer(state, action); + } + }; +} + +/** + * A meta reducer that removes cross sells from the cart so that they are not + * duplicated in state. + */ +export function daffCrossSellProductsCartMetaReducerFactory(retrievalActions: Array): MetaReducer> { + return (reducer: ActionReducer>): ActionReducer> => (state: T, action: Action) => { + const cart = daffCartRetrievalGetResponse(action, retrievalActions); + if (cart && cart.crossSells) { + // TODO: figure out how to do this without mutating state + // cart.crossSells = undefined; + return reducer( + state, + action, + ); + } else { + return reducer(state, action); + } + }; +} diff --git a/libs/cross-sell-products/state/src/reducers/public_api.ts b/libs/cross-sell-products/state/src/reducers/public_api.ts new file mode 100644 index 0000000000..13ef92665a --- /dev/null +++ b/libs/cross-sell-products/state/src/reducers/public_api.ts @@ -0,0 +1,5 @@ +export { + daffCrossSellProductsCartMetaReducerFactory, + daffCrossSellProductEntitiesMetaReducerFactory, +} from './meta-reducers'; +export { DaffCrossSellProductStateRootSlice } from './reducers-state.interface'; diff --git a/libs/cross-sell-products/state/src/reducers/reducers-state.interface.ts b/libs/cross-sell-products/state/src/reducers/reducers-state.interface.ts new file mode 100644 index 0000000000..969f122df8 --- /dev/null +++ b/libs/cross-sell-products/state/src/reducers/reducers-state.interface.ts @@ -0,0 +1,9 @@ +import { DaffCartStateRootSlice } from '@daffodil/cart/types/daffodil-cart-state'; +import { DaffCartWithCrossSellProducts } from '@daffodil/cross-sell-products'; +import { DaffProduct } from '@daffodil/product'; +import { DaffProductStateRootSlice } from '@daffodil/product/state'; + +/** + * The footprint of the cross-sell product feature in the root state. + */ +export interface DaffCrossSellProductStateRootSlice extends DaffProductStateRootSlice, DaffCartStateRootSlice> {} diff --git a/libs/cross-sell-products/state/src/selectors/all-selectors.selectors.ts b/libs/cross-sell-products/state/src/selectors/all-selectors.selectors.ts new file mode 100644 index 0000000000..c4c4ca9b8e --- /dev/null +++ b/libs/cross-sell-products/state/src/selectors/all-selectors.selectors.ts @@ -0,0 +1,21 @@ +import { DaffProduct } from '@daffodil/product'; + +import { + DaffCrossSellProductsMemoizedSelectors, + getDaffCrossSellProductsPageSelectors, +} from './cross-sell-products/selectors'; + +/** + * An interface for all selectors in the entire cross-sell products feature area. + */ +export interface DaffCrossSellProductsAllSelectors extends + DaffCrossSellProductsMemoizedSelectors +{} + +/** + * A function that returns all selectors in the entire cross-sell products feature area. + * Returns {@link DaffCrossSellProductsAllSelectors}. + */ +export const getDaffCrossSellProductsSelectors = (): DaffCrossSellProductsAllSelectors => ({ + ...getDaffCrossSellProductsPageSelectors(), +}); diff --git a/libs/cross-sell-products/state/src/selectors/cross-sell-products/selectors.spec.ts b/libs/cross-sell-products/state/src/selectors/cross-sell-products/selectors.spec.ts new file mode 100644 index 0000000000..ce71f7efc4 --- /dev/null +++ b/libs/cross-sell-products/state/src/selectors/cross-sell-products/selectors.spec.ts @@ -0,0 +1,73 @@ +import { TestBed } from '@angular/core/testing'; +import { EffectsModule } from '@ngrx/effects'; +import { + StoreModule, + Store, + select, +} from '@ngrx/store'; +import { cold } from 'jasmine-marbles'; + +import { DaffTestingCartDriverModule } from '@daffodil/cart/driver/testing'; +import { DaffResolveCartSuccess } from '@daffodil/cart/state'; +import { DaffCartWithCrossSellProducts } from '@daffodil/cross-sell-products'; +import { + DaffCrossSellProductStateModule, + DaffCrossSellProductStateRootSlice, +} from '@daffodil/cross-sell-products/state'; +import { DaffCartWithCrossSellProductsFactory } from '@daffodil/cross-sell-products/testing'; +import { DaffProductTestingDriverModule } from '@daffodil/product/driver/testing'; +import { DAFF_PRODUCT_STORE_FEATURE_KEY } from '@daffodil/product/state'; + +import { getDaffCrossSellProductsPageSelectors } from './selectors'; + +describe('selectCrossSellProductsState', () => { + let store: Store; + let crossSellProductFactory: DaffCartWithCrossSellProductsFactory; + let mockCart: DaffCartWithCrossSellProducts; + const { + selectCrossSellProductIds, + selectCrossSellProducts, + } = getDaffCrossSellProductsPageSelectors(); + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + StoreModule.forRoot({}), + EffectsModule.forRoot(), + DaffCrossSellProductStateModule, + DaffProductTestingDriverModule.forRoot(), + DaffTestingCartDriverModule.forRoot(), + ], + }); + + store = TestBed.inject(Store); + crossSellProductFactory = TestBed.inject(DaffCartWithCrossSellProductsFactory); + + mockCart = crossSellProductFactory.create(); + + store.dispatch(new DaffResolveCartSuccess(mockCart)); + }); + + describe('selectCrossSellProductIds', () => { + + it('returns the cross-sell product IDs', () => { + const selector = store.pipe(select(selectCrossSellProductIds)); + const expected = cold('a', { a: mockCart.crossSellIds }); + + expect(selector).toBeObservable(expected); + }); + }); + + describe('selectCrossSellProducts', () => { + + it('returns the cross-sell products', (done) => { + store.subscribe((state) => { + const selector = store.pipe(select(selectCrossSellProducts)); + const expected = cold('a', { a: mockCart.crossSellIds.map((id) => state[DAFF_PRODUCT_STORE_FEATURE_KEY].products.entities[id]) }); + + expect(selector).toBeObservable(expected); + done(); + }); + }); + }); +}); diff --git a/libs/cross-sell-products/state/src/selectors/cross-sell-products/selectors.ts b/libs/cross-sell-products/state/src/selectors/cross-sell-products/selectors.ts new file mode 100644 index 0000000000..f11b63fd30 --- /dev/null +++ b/libs/cross-sell-products/state/src/selectors/cross-sell-products/selectors.ts @@ -0,0 +1,63 @@ +import { + createSelector, + MemoizedSelector, + defaultMemoize, +} from '@ngrx/store'; + +import { getDaffCartSelectors } from '@daffodil/cart/state'; +import { DaffCartWithCrossSellProducts } from '@daffodil/cross-sell-products'; +import { DaffProduct } from '@daffodil/product'; +import { getDaffProductSelectors } from '@daffodil/product/state'; + +import { DaffCrossSellProductStateRootSlice } from '../../reducers/reducers-state.interface'; + +/** + * An interface for selectors of cross-sell products for the current product page. + */ +export interface DaffCrossSellProductsMemoizedSelectors { + /** + * Selects the IDs of the cross-sell products. + */ + selectCrossSellProductIds: MemoizedSelector, T['id'][]>; + + /** + * Selects the cross-sell products. + */ + selectCrossSellProducts: MemoizedSelector, T[]>; +} + +const createCrossSellProductSelectors = (): DaffCrossSellProductsMemoizedSelectors => { + + const { + selectCartValue, + } = getDaffCartSelectors>(); + const { + selectProduct, + selectProductEntities, + } = getDaffProductSelectors(); + + const selectCrossSellProductIds = createSelector, DaffCartWithCrossSellProducts, Array>( + selectCartValue, + (state) => state.crossSellIds ?? [], + ); + + const selectCrossSellProducts = createSelector( + selectCrossSellProductIds, + selectProductEntities, + (crossSellIds, entites) => + crossSellIds.map(id => + selectProduct(id).projector(entites), + ), + ); + + return { + selectCrossSellProductIds, + selectCrossSellProducts, + }; +}; + +/** + * A function that returns all selectors of cross-sell products for the current product page. + * Returns {@link DaffCrossSellProductsMemoizedSelectors}. + */ +export const getDaffCrossSellProductsPageSelectors: () => DaffCrossSellProductsMemoizedSelectors = defaultMemoize(() => createCrossSellProductSelectors()).memoized; diff --git a/libs/cross-sell-products/state/src/selectors/public_api.ts b/libs/cross-sell-products/state/src/selectors/public_api.ts new file mode 100644 index 0000000000..54956fa650 --- /dev/null +++ b/libs/cross-sell-products/state/src/selectors/public_api.ts @@ -0,0 +1,2 @@ +export * from './all-selectors.selectors'; +export * from './cross-sell-products/selectors'; diff --git a/libs/cross-sell-products/state/src/state.module.ts b/libs/cross-sell-products/state/src/state.module.ts new file mode 100644 index 0000000000..0b74168b55 --- /dev/null +++ b/libs/cross-sell-products/state/src/state.module.ts @@ -0,0 +1,34 @@ +import { + inject, + NgModule, +} from '@angular/core'; + +import { + DAFF_CART_RETRIEVAL_ACTIONS, + daffCartProvideMetaReducerFactories, + DaffCartStateModule, +} from '@daffodil/cart/state'; +import { + daffProductProvideMetaReducerFactories, + DaffProductStateModule, +} from '@daffodil/product/state'; + +import { + daffCrossSellProductsCartMetaReducerFactory, + daffCrossSellProductEntitiesMetaReducerFactory, +} from './reducers/public_api'; + +/** + * A module that provides the default reducers for the cross-sell product redux state. + */ +@NgModule({ + imports: [ + DaffProductStateModule, + DaffCartStateModule, + ], + providers: [ + daffProductProvideMetaReducerFactories(() => daffCrossSellProductEntitiesMetaReducerFactory(inject(DAFF_CART_RETRIEVAL_ACTIONS))), + daffCartProvideMetaReducerFactories(() => daffCrossSellProductsCartMetaReducerFactory(inject(DAFF_CART_RETRIEVAL_ACTIONS))), + ], +}) +export class DaffCrossSellProductStateModule {} diff --git a/libs/cross-sell-products/state/testing/ng-package.json b/libs/cross-sell-products/state/testing/ng-package.json new file mode 100644 index 0000000000..7dcb29e536 --- /dev/null +++ b/libs/cross-sell-products/state/testing/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../../node_modules/ng-packagr/ng-entrypoint.schema.json", + "lib": { + "entryFile": "src/index.ts" + } +} diff --git a/libs/cross-sell-products/state/testing/src/index.ts b/libs/cross-sell-products/state/testing/src/index.ts new file mode 100644 index 0000000000..4aaf8f92ed --- /dev/null +++ b/libs/cross-sell-products/state/testing/src/index.ts @@ -0,0 +1 @@ +export * from './public_api'; diff --git a/libs/cross-sell-products/state/testing/src/mock-cross-sell-products.facade.ts b/libs/cross-sell-products/state/testing/src/mock-cross-sell-products.facade.ts new file mode 100644 index 0000000000..5cf83a41e4 --- /dev/null +++ b/libs/cross-sell-products/state/testing/src/mock-cross-sell-products.facade.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; + +import { DaffCrossSellProductsFacadeInterface } from '@daffodil/cross-sell-products/state'; +import { DaffProduct } from '@daffodil/product'; + +/** + * Mocks the {@link DaffCrossSellProductsFacadeInterface} for testing purposes. + * + * @inheritdoc + */ +@Injectable({ providedIn: 'root' }) +export class MockDaffCrossSellProductsFacade implements DaffCrossSellProductsFacadeInterface { + crossSellProducts$ = new BehaviorSubject([]); + dispatch(action) {}; +} diff --git a/libs/cross-sell-products/state/testing/src/public_api.ts b/libs/cross-sell-products/state/testing/src/public_api.ts new file mode 100644 index 0000000000..b084da15d2 --- /dev/null +++ b/libs/cross-sell-products/state/testing/src/public_api.ts @@ -0,0 +1,2 @@ +export { MockDaffCrossSellProductsFacade } from './mock-cross-sell-products.facade'; +export { DaffCrossSellProductsStateTestingModule } from './state-testing.module'; diff --git a/libs/cross-sell-products/state/testing/src/state-testing.module.ts b/libs/cross-sell-products/state/testing/src/state-testing.module.ts new file mode 100644 index 0000000000..d6ed5ab1a4 --- /dev/null +++ b/libs/cross-sell-products/state/testing/src/state-testing.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from '@angular/core'; + +import { DaffCrossSellProductsFacade } from '@daffodil/cross-sell-products/state'; + +import { MockDaffCrossSellProductsFacade } from './mock-cross-sell-products.facade'; + + +@NgModule({ + providers: [ + { provide: DaffCrossSellProductsFacade, useExisting: MockDaffCrossSellProductsFacade }, + ], +}) +export class DaffCrossSellProductsStateTestingModule {} diff --git a/libs/cross-sell-products/test.ts b/libs/cross-sell-products/test.ts new file mode 100644 index 0000000000..2f5f660b3d --- /dev/null +++ b/libs/cross-sell-products/test.ts @@ -0,0 +1,18 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + + +// eslint-disable-next-line import/no-unassigned-import +import 'zone.js'; +// eslint-disable-next-line import/no-unassigned-import +import 'zone.js/testing'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting, +} from '@angular/platform-browser-dynamic/testing'; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting(), +); diff --git a/libs/cross-sell-products/testing/ng-package.json b/libs/cross-sell-products/testing/ng-package.json new file mode 100644 index 0000000000..7dcb29e536 --- /dev/null +++ b/libs/cross-sell-products/testing/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../../node_modules/ng-packagr/ng-entrypoint.schema.json", + "lib": { + "entryFile": "src/index.ts" + } +} diff --git a/libs/cross-sell-products/testing/src/factories/cross-sell-product.factory.spec.ts b/libs/cross-sell-products/testing/src/factories/cross-sell-product.factory.spec.ts new file mode 100644 index 0000000000..2d2e3602bc --- /dev/null +++ b/libs/cross-sell-products/testing/src/factories/cross-sell-product.factory.spec.ts @@ -0,0 +1,47 @@ +import { TestBed } from '@angular/core/testing'; + +import { DaffCartWithCrossSellProducts } from '@daffodil/cross-sell-products'; +import { DaffProductTestingModule } from '@daffodil/product/testing'; + +import { DaffCartWithCrossSellProductsFactory } from './cross-sell-product.factory'; + +describe('@daffodil/cross-sell-products/testing | DaffCartWithCrossSellProductsFactory', () => { + let factory: DaffCartWithCrossSellProductsFactory; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + DaffProductTestingModule, + ], + providers: [ + DaffCartWithCrossSellProductsFactory, + ], + }); + + factory = TestBed.inject(DaffCartWithCrossSellProductsFactory); + }); + + it('should be created', () => { + expect(factory).toBeTruthy(); + }); + + describe('create', () => { + + let result: DaffCartWithCrossSellProducts; + + beforeEach(() => { + result = factory.create(); + }); + + it('should return at least one cross-sell product', () => { + expect(result.crossSells.length).toBeGreaterThan(0); + expect(result.crossSellIds.length).toBeGreaterThan(0); + }); + + it('should use product IDs from the list of cross sells', () => { + result.crossSellIds.forEach((id) => { + expect(result.crossSells).toContain(jasmine.objectContaining({ id })); + }); + }); + }); +}); diff --git a/libs/cross-sell-products/testing/src/factories/cross-sell-product.factory.ts b/libs/cross-sell-products/testing/src/factories/cross-sell-product.factory.ts new file mode 100644 index 0000000000..d984364c38 --- /dev/null +++ b/libs/cross-sell-products/testing/src/factories/cross-sell-product.factory.ts @@ -0,0 +1,42 @@ +import { Injectable } from '@angular/core'; + +import { + DaffCartShippingRateFactory, + DaffCartTotalFactory, + MockCart, +} from '@daffodil/cart/testing'; +import { DaffModelFactory } from '@daffodil/core/testing'; +import { DaffCartWithCrossSellProducts } from '@daffodil/cross-sell-products'; +import { DaffProductKindFactory } from '@daffodil/product/testing'; + +/** + * Mocked DaffCartWithCrossSellProducts object. + */ +export class MockCartWithCrossSellProducts extends MockCart implements DaffCartWithCrossSellProducts { + crossSells = this.productFactory.createMany(3); + crossSellIds = this.crossSells.map((p) => p.id); + + constructor( + protected productFactory: DaffProductKindFactory, + totalFactory: DaffCartTotalFactory, + shippingInformationFactory: DaffCartShippingRateFactory, + ) { + super(totalFactory, shippingInformationFactory); + } +} + +/** + * Factory for creating DaffCartWithCrossSellProductss. + */ +@Injectable({ + providedIn: 'root', +}) +export class DaffCartWithCrossSellProductsFactory extends DaffModelFactory{ + constructor( + productFactory: DaffProductKindFactory, + totalFactory: DaffCartTotalFactory, + shippingInformationFactory: DaffCartShippingRateFactory, + ) { + super(MockCartWithCrossSellProducts, productFactory, totalFactory, shippingInformationFactory); + } +} diff --git a/libs/cross-sell-products/testing/src/factories/public_api.ts b/libs/cross-sell-products/testing/src/factories/public_api.ts new file mode 100644 index 0000000000..87903d9073 --- /dev/null +++ b/libs/cross-sell-products/testing/src/factories/public_api.ts @@ -0,0 +1 @@ +export { DaffCartWithCrossSellProductsFactory } from './cross-sell-product.factory'; diff --git a/libs/cross-sell-products/testing/src/index.ts b/libs/cross-sell-products/testing/src/index.ts new file mode 100644 index 0000000000..4aaf8f92ed --- /dev/null +++ b/libs/cross-sell-products/testing/src/index.ts @@ -0,0 +1 @@ +export * from './public_api'; diff --git a/libs/cross-sell-products/testing/src/public_api.ts b/libs/cross-sell-products/testing/src/public_api.ts new file mode 100644 index 0000000000..b834ec22bb --- /dev/null +++ b/libs/cross-sell-products/testing/src/public_api.ts @@ -0,0 +1 @@ +export * from './factories/public_api'; diff --git a/libs/cross-sell-products/tsconfig.json b/libs/cross-sell-products/tsconfig.json new file mode 100644 index 0000000000..a22300eb28 --- /dev/null +++ b/libs/cross-sell-products/tsconfig.json @@ -0,0 +1,32 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": "../..", + "paths": { + "@daffodil/*": [ + "dist/*" + ], + "@daffodil/cross-sell-products": [ + "libs/cross-sell-products/src" + ], + "@daffodil/cross-sell-products/state": [ + "libs/cross-sell-products/state/src" + ], + "@daffodil/cross-sell-products/state/testing": [ + "libs/cross-sell-products/state/testing/src" + ], + "@daffodil/cross-sell-products/driver": [ + "libs/cross-sell-products/driver/src" + ], + "@daffodil/cross-sell-products/driver/magento": [ + "libs/cross-sell-products/driver/magento/src" + ], + "@daffodil/cross-sell-products/driver/in-memory": [ + "libs/cross-sell-products/driver/in-memory/src" + ], + "@daffodil/cross-sell-products/testing": [ + "libs/cross-sell-products/testing/src" + ], + } + } +} diff --git a/libs/cross-sell-products/tsconfig.lib.json b/libs/cross-sell-products/tsconfig.lib.json new file mode 100644 index 0000000000..7c8e1eea35 --- /dev/null +++ b/libs/cross-sell-products/tsconfig.lib.json @@ -0,0 +1,27 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc/libs/cross-sell-products", + "declarationMap": true, + "declaration": true, + "sourceMap": true, + "inlineSources": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "importHelpers": true, + "types": [], + }, + "angularCompilerOptions": { + "annotateForClosureCompiler": true, + "skipTemplateCodegen": true, + "strictMetadataEmit": true, + "fullTemplateTypeCheck": true, + "strictInjectionParameters": true, + "flatModuleId": "AUTOGENERATED", + "flatModuleOutFile": "AUTOGENERATED" + }, + "exclude": [ + "test.ts", + "**/*.spec.ts" + ] +} diff --git a/libs/cross-sell-products/tsconfig.lib.prod.json b/libs/cross-sell-products/tsconfig.lib.prod.json new file mode 100644 index 0000000000..d17803f43a --- /dev/null +++ b/libs/cross-sell-products/tsconfig.lib.prod.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.lib.json", + "angularCompilerOptions": { + "compilationMode": "partial" + }, + "compilerOptions": { + "declarationMap": false + } +} diff --git a/libs/cross-sell-products/tsconfig.spec.json b/libs/cross-sell-products/tsconfig.spec.json new file mode 100644 index 0000000000..ee629213fe --- /dev/null +++ b/libs/cross-sell-products/tsconfig.spec.json @@ -0,0 +1,19 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc/libs/cross-sell-products", + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, + "types": [ + "jasmine", + "node" + ] + }, + "files": [ + "test.ts" + ], + "include": [ + "**/*.spec.ts", + "**/*.d.ts" + ] +}