diff --git a/docs/concepts/configuration.md b/docs/concepts/configuration.md index 40298f8591..322bf7ac56 100644 --- a/docs/concepts/configuration.md +++ b/docs/concepts/configuration.md @@ -161,7 +161,6 @@ Of course, the ICM server must supply appropriate REST resources to leverage fun | rating | display product ratings | | recently | display recently viewed products (additional configuration via `dataRetention` configuration options) | | **B2B Features** | | -| advancedVariationHandling | handle product variations as individual products in listings and product detail pages | | businessCustomerRegistration | create business customers on registration | | costCenters | cost center feature | | orderTemplates | order template feature | diff --git a/docs/guides/migrations.md b/docs/guides/migrations.md index 6ab94d647b..959cf8b1b1 100644 --- a/docs/guides/migrations.md +++ b/docs/guides/migrations.md @@ -37,6 +37,10 @@ Now we removed the obsolete form components. If you want to use the obsolete form components in your project nevertheless, skip the commit `remove obsolete form components`. For more information concerning Formly please refer to our [Formly - Guide](./formly.md)). +The feature toggle 'advancedVariationHandling' has been removed. +Instead the ICM channel preference 'AdvancedVariationHandling' is used to configure it. +You will find this preference as 'List View' in the ICM backoffice under Channel Preferences -> Product Variations. + ## 1.1 to 1.2 The `dist` folder now only contains results of the build process (except for `healthcheck.js`). diff --git a/docs/guides/multi-site-configurations.md b/docs/guides/multi-site-configurations.md index 66579176ae..00d47bc170 100644 --- a/docs/guides/multi-site-configurations.md +++ b/docs/guides/multi-site-configurations.md @@ -184,14 +184,14 @@ To see what is possible through multi-site handling, have a look at this extende - baseHref: /us lang: en_US channel: inSPIRED-inTRONICS_Business-Site - features: quoting,businessCustomerRegistration,advancedVariationHandling + features: quoting,businessCustomerRegistration .+\.de: channel: inSPIRED-inTRONICS-Site lang: de_DE theme: b2c .+\.com: channel: inSPIRED-inTRONICS_Business-Site - features: quoting,businessCustomerRegistration,advancedVariationHandling + features: quoting,businessCustomerRegistration .+\.fr: channel: inSPIRED-inTRONICS_Business-Site lang: fr_FR diff --git a/nginx/README.md b/nginx/README.md index 44feeb895d..46a5f461c4 100644 --- a/nginx/README.md +++ b/nginx/README.md @@ -16,7 +16,7 @@ Please refer to the [documentation](../docs/guides/nginx-startup.md) for configu theme: 'b2c' .+\.com: channel: inSPIRED-inTRONICS_Business-Site - features: quoting,businessCustomerRegistration,advancedVariationHandling + features: quoting,businessCustomerRegistration .+\.fr: channel: inSPIRED-inTRONICS-Site lang: fr_FR diff --git a/src/app/core/facades/selected-product-context.facade.ts b/src/app/core/facades/selected-product-context.facade.ts index eb9bf89c9c..6b4587716a 100644 --- a/src/app/core/facades/selected-product-context.facade.ts +++ b/src/app/core/facades/selected-product-context.facade.ts @@ -1,10 +1,9 @@ import { Injectable, Injector } from '@angular/core'; import { Router } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; -import { filter, map, skip, withLatestFrom } from 'rxjs/operators'; +import { concatMap, filter, map, skip, withLatestFrom } from 'rxjs/operators'; import { ProductVariationHelper } from 'ish-core/models/product-variation/product-variation.helper'; -import { FeatureToggleService } from 'ish-core/utils/feature-toggle/feature-toggle.service'; import { AppFacade } from './app.facade'; import { ProductContextFacade } from './product-context.facade'; @@ -16,7 +15,6 @@ export class SelectedProductContextFacade extends ProductContextFacade { shoppingFacade: ShoppingFacade, translate: TranslateService, injector: Injector, - private featureToggleService: FeatureToggleService, private router: Router, private appFacade: AppFacade ) { @@ -28,9 +26,13 @@ export class SelectedProductContextFacade extends ProductContextFacade { this.connect( 'sku', this.select('product').pipe( - filter(() => !this.featureToggleService.enabled('advancedVariationHandling')), filter(ProductVariationHelper.hasDefaultVariation), - map(p => p.defaultVariationSKU) + concatMap(p => + this.appFacade.serverSetting$('preferences.ChannelPreferences.EnableAdvancedVariationHandling').pipe( + filter(advancedVariationHandling => advancedVariationHandling !== undefined && !advancedVariationHandling), + map(() => p.defaultVariationSKU) + ) + ) ) ); diff --git a/src/app/core/services/filter/filter.service.spec.ts b/src/app/core/services/filter/filter.service.spec.ts index 74c82652e2..5e2a1dc928 100644 --- a/src/app/core/services/filter/filter.service.spec.ts +++ b/src/app/core/services/filter/filter.service.spec.ts @@ -1,7 +1,8 @@ import { TestBed } from '@angular/core/testing'; import { of } from 'rxjs'; -import { anything, capture, instance, mock, verify, when } from 'ts-mockito'; +import { anyString, anything, capture, instance, mock, verify, when } from 'ts-mockito'; +import { AppFacade } from 'ish-core/facades/app.facade'; import { FilterNavigationData } from 'ish-core/models/filter-navigation/filter-navigation.interface'; import { ApiService, AvailableOptions } from 'ish-core/services/api/api.service'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; @@ -12,6 +13,8 @@ import { FilterService } from './filter.service'; describe('Filter Service', () => { let apiService: ApiService; let filterService: FilterService; + let appFacadeMock: AppFacade; + const productsMock = { elements: [ { uri: 'products/123', attributes: [{ name: 'sku', value: '123' }] }, @@ -36,12 +39,18 @@ describe('Filter Service', () => { beforeEach(() => { apiService = mock(ApiService); + appFacadeMock = mock(AppFacade); TestBed.configureTestingModule({ imports: [CoreStoreModule.forTesting(['configuration'])], - providers: [{ provide: ApiService, useFactory: () => instance(apiService) }], + providers: [ + { provide: ApiService, useFactory: () => instance(apiService) }, + { provide: AppFacade, useFactory: () => instance(appFacadeMock) }, + ], }); filterService = TestBed.inject(FilterService); + + when(appFacadeMock.serverSetting$(anyString())).thenReturn(of(false)); }); it('should be created', () => { diff --git a/src/app/core/services/filter/filter.service.ts b/src/app/core/services/filter/filter.service.ts index 1582533435..baf5fdd18b 100644 --- a/src/app/core/services/filter/filter.service.ts +++ b/src/app/core/services/filter/filter.service.ts @@ -1,8 +1,9 @@ import { HttpParams } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { Observable, identity } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { Observable } from 'rxjs'; +import { map, withLatestFrom } from 'rxjs/operators'; +import { AppFacade } from 'ish-core/facades/app.facade'; import { AttributeGroupTypes } from 'ish-core/models/attribute-group/attribute-group.types'; import { CategoryHelper } from 'ish-core/models/category/category.model'; import { FilterNavigationData } from 'ish-core/models/filter-navigation/filter-navigation.interface'; @@ -14,7 +15,6 @@ import { ProductMapper } from 'ish-core/models/product/product.mapper'; import { Product, ProductHelper } from 'ish-core/models/product/product.model'; import { ApiService } from 'ish-core/services/api/api.service'; import { ProductsService } from 'ish-core/services/products/products.service'; -import { FeatureToggleService } from 'ish-core/utils/feature-toggle/feature-toggle.service'; import { omit } from 'ish-core/utils/functions'; import { URLFormParams, appendFormParamsToHttpParams } from 'ish-core/utils/url-form-params'; @@ -24,7 +24,7 @@ export class FilterService { private apiService: ApiService, private filterNavigationMapper: FilterNavigationMapper, private productMapper: ProductMapper, - private featureToggleService: FeatureToggleService + private appFacade: AppFacade ) {} getFilterForCategory(categoryUniqueId: string): Observable { @@ -91,13 +91,14 @@ export class FilterService { total: x.total, sortableAttributes: Object.values(x.sortableAttributes || {}), })), - params.has('MasterSKU') - ? identity - : map(({ products, sortableAttributes, total }) => ({ - products: this.postProcessMasters(products), - sortableAttributes, - total, - })) + withLatestFrom( + this.appFacade.serverSetting$('preferences.ChannelPreferences.EnableAdvancedVariationHandling') + ), + map(([{ products, sortableAttributes, total }, advancedVariationHandling]) => ({ + products: params.has('MasterSKU') ? products : this.postProcessMasters(products, advancedVariationHandling), + sortableAttributes, + total, + })) ); } @@ -105,8 +106,8 @@ export class FilterService { * exchange single-return variation products to master products for B2B * TODO: this is a work-around */ - private postProcessMasters(products: Partial[]): Product[] { - if (this.featureToggleService.enabled('advancedVariationHandling')) { + private postProcessMasters(products: Partial[], advancedVariationHandling: boolean): Product[] { + if (advancedVariationHandling) { return products.map(p => ProductHelper.isVariationProduct(p) ? { sku: p.productMasterSKU, completenessLevel: 0 } : p ) as Product[]; diff --git a/src/app/core/services/products/products.service.spec.ts b/src/app/core/services/products/products.service.spec.ts index c6e55c875a..533c89687d 100644 --- a/src/app/core/services/products/products.service.spec.ts +++ b/src/app/core/services/products/products.service.spec.ts @@ -1,7 +1,8 @@ import { TestBed } from '@angular/core/testing'; import { of } from 'rxjs'; -import { anything, capture, instance, mock, verify, when } from 'ts-mockito'; +import { anyString, anything, capture, instance, mock, verify, when } from 'ts-mockito'; +import { AppFacade } from 'ish-core/facades/app.facade'; import { Product } from 'ish-core/models/product/product.model'; import { ApiService, AvailableOptions } from 'ish-core/services/api/api.service'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; @@ -13,6 +14,7 @@ import { ProductsService } from './products.service'; describe('Products Service', () => { let productsService: ProductsService; let apiServiceMock: ApiService; + let appFacadeMock: AppFacade; const productSku = 'SKU'; const categoryId = 'CategoryID'; @@ -61,14 +63,21 @@ describe('Products Service', () => { beforeEach(() => { apiServiceMock = mock(ApiService); + appFacadeMock = mock(AppFacade); + TestBed.configureTestingModule({ imports: [ CoreStoreModule.forTesting(['configuration'], [ProductListingEffects]), ShoppingStoreModule.forTesting('productListing'), ], - providers: [{ provide: ApiService, useFactory: () => instance(apiServiceMock) }], + providers: [ + { provide: ApiService, useFactory: () => instance(apiServiceMock) }, + { provide: AppFacade, useFactory: () => instance(appFacadeMock) }, + ], }); productsService = TestBed.inject(ProductsService); + + when(appFacadeMock.serverSetting$(anyString())).thenReturn(of(false)); }); it("should get Product data when 'getProduct' is called", done => { diff --git a/src/app/core/services/products/products.service.ts b/src/app/core/services/products/products.service.ts index e889b5a2d8..92565ee156 100644 --- a/src/app/core/services/products/products.service.ts +++ b/src/app/core/services/products/products.service.ts @@ -2,8 +2,9 @@ import { HttpParams } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { flatten, range } from 'lodash-es'; import { Observable, from, identity, of, throwError } from 'rxjs'; -import { defaultIfEmpty, map, mergeMap, switchMap, toArray } from 'rxjs/operators'; +import { defaultIfEmpty, map, mergeMap, switchMap, toArray, withLatestFrom } from 'rxjs/operators'; +import { AppFacade } from 'ish-core/facades/app.facade'; import { AttributeGroupTypes } from 'ish-core/models/attribute-group/attribute-group.types'; import { CategoryHelper } from 'ish-core/models/category/category.model'; import { Link } from 'ish-core/models/link/link.model'; @@ -19,7 +20,6 @@ import { VariationProductMaster, } from 'ish-core/models/product/product.model'; import { ApiService, unpackEnvelope } from 'ish-core/services/api/api.service'; -import { FeatureToggleService } from 'ish-core/utils/feature-toggle/feature-toggle.service'; import { mapToProperty } from 'ish-core/utils/operators'; /** @@ -30,11 +30,7 @@ export class ProductsService { static STUB_ATTRS = 'sku,salePrice,listPrice,availability,manufacturer,image,minOrderQuantity,maxOrderQuantity,stepOrderQuantity,inStock,promotions,packingUnit,mastered,productMaster,productMasterSKU,roundedAverageRating,retailSet'; - constructor( - private apiService: ApiService, - private productMapper: ProductMapper, - private featureToggleService: FeatureToggleService - ) {} + constructor(private apiService: ApiService, private productMapper: ProductMapper, private appFacade: AppFacade) {} /** * Get the full Product data for the given Product SKU. @@ -96,8 +92,11 @@ export class ProductsService { sortableAttributes: Object.values(response.sortableAttributes || {}), total: response.total ? response.total : response.elements.length, })), - map(({ products, sortableAttributes, total }) => ({ - products: this.postProcessMasters(products), + withLatestFrom( + this.appFacade.serverSetting$('preferences.ChannelPreferences.EnableAdvancedVariationHandling') + ), + map(([{ products, sortableAttributes, total }, advancedVariationHandling]) => ({ + products: this.postProcessMasters(products, advancedVariationHandling), sortableAttributes, total, })) @@ -146,8 +145,11 @@ export class ProductsService { sortableAttributes: Object.values(response.sortableAttributes || {}), total: response.total ? response.total : response.elements.length, })), - map(({ products, sortableAttributes, total }) => ({ - products: this.postProcessMasters(products), + withLatestFrom( + this.appFacade.serverSetting$('preferences.ChannelPreferences.EnableAdvancedVariationHandling') + ), + map(([{ products, sortableAttributes, total }, advancedVariationHandling]) => ({ + products: this.postProcessMasters(products, advancedVariationHandling), sortableAttributes, total, })) @@ -194,8 +196,8 @@ export class ProductsService { * exchange single-return variation products to master products for B2B * TODO: this is a work-around */ - private postProcessMasters(products: Partial[]): Product[] { - if (this.featureToggleService.enabled('advancedVariationHandling')) { + private postProcessMasters(products: Partial[], advancedVariationHandling: boolean): Product[] { + if (advancedVariationHandling) { return products.map(p => ProductHelper.isVariationProduct(p) ? { sku: p.productMasterSKU, completenessLevel: 0 } : p ) as Product[]; diff --git a/src/app/core/store/core/server-config/server-config.selectors.ts b/src/app/core/store/core/server-config/server-config.selectors.ts index ff5727f517..ad1ff00e6c 100644 --- a/src/app/core/store/core/server-config/server-config.selectors.ts +++ b/src/app/core/store/core/server-config/server-config.selectors.ts @@ -4,7 +4,7 @@ import { getCoreState } from 'ish-core/store/core/core-store'; const getServerConfigState = createSelector(getCoreState, state => state.serverConfig); -const getServerConfig = createSelector(getServerConfigState, state => state._config); +export const getServerConfig = createSelector(getServerConfigState, state => state._config); export const isServerConfigurationLoaded = createSelector(getServerConfig, serverConfig => !!serverConfig); diff --git a/src/app/core/store/shopping/recently/recently.effects.spec.ts b/src/app/core/store/shopping/recently/recently.effects.spec.ts index 1ee60059ec..969e274326 100644 --- a/src/app/core/store/shopping/recently/recently.effects.spec.ts +++ b/src/app/core/store/shopping/recently/recently.effects.spec.ts @@ -4,6 +4,7 @@ import { cold } from 'jest-marbles'; import { FeatureToggleModule } from 'ish-core/feature-toggle.module'; import { ProductView } from 'ish-core/models/product-view/product-view.model'; +import { getServerConfig } from 'ish-core/store/core/server-config'; import { getSelectedProduct } from 'ish-core/store/shopping/products'; import { addToRecently } from './recently.actions'; @@ -21,6 +22,11 @@ describe('Recently Effects', () => { effects = TestBed.inject(RecentlyEffects); store$ = TestBed.inject(MockStore); + + // workaround: overrideSelector is not working for selectors with parameters https://github.com/ngrx/platform/issues/2717 + store$.overrideSelector(getServerConfig, { + _config: { preferences: { ChannelPreferences: { EnableAdvancedVariationHandling: true } } }, + }); }); describe('viewedProduct$', () => { diff --git a/src/app/core/store/shopping/recently/recently.effects.ts b/src/app/core/store/shopping/recently/recently.effects.ts index 664496fa6e..721aeca5da 100644 --- a/src/app/core/store/shopping/recently/recently.effects.ts +++ b/src/app/core/store/shopping/recently/recently.effects.ts @@ -1,18 +1,18 @@ import { Injectable } from '@angular/core'; import { createEffect } from '@ngrx/effects'; import { Store, select } from '@ngrx/store'; -import { distinctUntilKeyChanged, filter, map } from 'rxjs/operators'; +import { distinctUntilKeyChanged, filter, map, withLatestFrom } from 'rxjs/operators'; import { ProductHelper } from 'ish-core/models/product/product.model'; +import { getServerConfigParameter } from 'ish-core/store/core/server-config'; import { getSelectedProduct } from 'ish-core/store/shopping/products/products.selectors'; -import { FeatureToggleService } from 'ish-core/utils/feature-toggle/feature-toggle.service'; import { whenTruthy } from 'ish-core/utils/operators'; import { addToRecently } from './recently.actions'; @Injectable() export class RecentlyEffects { - constructor(private store: Store, private featureToggleService: FeatureToggleService) {} + constructor(private store: Store) {} viewedProduct$ = createEffect(() => this.store.pipe( @@ -20,11 +20,15 @@ export class RecentlyEffects { whenTruthy(), filter(p => !ProductHelper.isFailedLoading(p)), distinctUntilKeyChanged('sku'), + withLatestFrom( + this.store.pipe( + select(getServerConfigParameter('preferences.ChannelPreferences.EnableAdvancedVariationHandling')) + ) + ), filter( - product => - this.featureToggleService.enabled('advancedVariationHandling') || !ProductHelper.isMasterProduct(product) + ([product, advancedVariationHandling]) => advancedVariationHandling || !ProductHelper.isMasterProduct(product) ), - map(product => ({ + map(([product]) => ({ sku: product.sku, group: (ProductHelper.isVariationProduct(product) && product.productMasterSKU) || undefined, })), diff --git a/src/app/core/store/shopping/recently/recently.selectors.spec.ts b/src/app/core/store/shopping/recently/recently.selectors.spec.ts index ce85f771e9..7966201011 100644 --- a/src/app/core/store/shopping/recently/recently.selectors.spec.ts +++ b/src/app/core/store/shopping/recently/recently.selectors.spec.ts @@ -25,7 +25,7 @@ describe('Recently Selectors', () => { TestBed.configureTestingModule({ declarations: [DummyComponent], imports: [ - CoreStoreModule.forTesting(['router', 'configuration'], [RecentlyEffects]), + CoreStoreModule.forTesting(['router', 'configuration', 'serverConfig'], [RecentlyEffects]), RouterTestingModule.withRoutes([{ path: 'product/:sku', component: DummyComponent }]), ShoppingStoreModule.forTesting('_recently', 'categories', 'products'), ], diff --git a/src/app/core/store/shopping/shopping-store.spec.ts b/src/app/core/store/shopping/shopping-store.spec.ts index 8a668b2899..6dfb55d793 100644 --- a/src/app/core/store/shopping/shopping-store.spec.ts +++ b/src/app/core/store/shopping/shopping-store.spec.ts @@ -106,7 +106,7 @@ describe('Shopping Store', () => { }); const configurationServiceMock = mock(ConfigurationService); - when(configurationServiceMock.getServerConfiguration()).thenReturn(of({})); + when(configurationServiceMock.getServerConfiguration()).thenReturn(EMPTY); const countryServiceMock = mock(CountryService); when(countryServiceMock.getCountries()).thenReturn(EMPTY); @@ -143,7 +143,7 @@ describe('Shopping Store', () => { TestBed.configureTestingModule({ declarations: [DummyComponent], imports: [ - CoreStoreModule.forTesting(['router', 'configuration'], true), + CoreStoreModule.forTesting(['router', 'configuration', 'serverConfig'], true), RouterTestingModule.withRoutes([ { path: 'home', diff --git a/src/app/pages/product/product-detail-variations/product-detail-variations.component.html b/src/app/pages/product/product-detail-variations/product-detail-variations.component.html index c096132162..e3c860e77d 100644 --- a/src/app/pages/product/product-detail-variations/product-detail-variations.component.html +++ b/src/app/pages/product/product-detail-variations/product-detail-variations.component.html @@ -1,5 +1,7 @@
- + diff --git a/src/app/pages/product/product-detail-variations/product-detail-variations.component.spec.ts b/src/app/pages/product/product-detail-variations/product-detail-variations.component.spec.ts index d31bf2baaf..be5ae65de0 100644 --- a/src/app/pages/product/product-detail-variations/product-detail-variations.component.spec.ts +++ b/src/app/pages/product/product-detail-variations/product-detail-variations.component.spec.ts @@ -1,10 +1,10 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { MockComponent } from 'ng-mocks'; +import { MockComponent, MockPipe } from 'ng-mocks'; import { of } from 'rxjs'; import { instance, mock, when } from 'ts-mockito'; import { ProductContextFacade } from 'ish-core/facades/product-context.facade'; -import { FeatureToggleModule } from 'ish-core/feature-toggle.module'; +import { ServerSettingPipe } from 'ish-core/pipes/server-setting.pipe'; import { findAllCustomElements } from 'ish-core/utils/dev/html-query-utils'; import { ProductVariationDisplayComponent } from 'ish-shared/components/product/product-variation-display/product-variation-display.component'; import { ProductVariationSelectComponent } from 'ish-shared/components/product/product-variation-select/product-variation-select.component'; @@ -19,60 +19,83 @@ describe('Product Detail Variations Component', () => { let element: HTMLElement; let context: ProductContextFacade; - beforeEach(async () => { + async function prepareTestbed(serverSetting: boolean) { context = mock(ProductContextFacade); await TestBed.configureTestingModule({ - imports: [FeatureToggleModule.forTesting()], declarations: [ MockComponent(ProductMasterLinkComponent), MockComponent(ProductVariationDisplayComponent), MockComponent(ProductVariationSelectComponent), + MockPipe(ServerSettingPipe, () => serverSetting), ProductDetailVariationsComponent, ], providers: [{ provide: ProductContextFacade, useFactory: () => instance(context) }], }).compileComponents(); - }); + } - beforeEach(() => { - fixture = TestBed.createComponent(ProductDetailVariationsComponent); - component = fixture.componentInstance; - element = fixture.nativeElement; - }); + describe('b2c variation handling', () => { + beforeEach(async () => { + prepareTestbed(false); + }); - it('should be created', () => { - expect(component).toBeTruthy(); - expect(element).toBeTruthy(); - expect(() => fixture.detectChanges()).not.toThrow(); - }); + beforeEach(() => { + fixture = TestBed.createComponent(ProductDetailVariationsComponent); + component = fixture.componentInstance; + element = fixture.nativeElement; + }); - it('should not render if display is false', () => { - fixture.detectChanges(); + it('should be created', () => { + expect(component).toBeTruthy(); + expect(element).toBeTruthy(); + expect(() => fixture.detectChanges()).not.toThrow(); + }); - expect(element).toMatchInlineSnapshot(`N/A`); - }); + it('should not render if display is false', () => { + fixture.detectChanges(); - it('should always render select by default', () => { - when(context.select('displayProperties', 'variations')).thenReturn(of(true)); - fixture.detectChanges(); + expect(element).toMatchInlineSnapshot(`N/A`); + }); - expect(findAllCustomElements(element)).toMatchInlineSnapshot(` + it('should always render select by default', () => { + when(context.select('displayProperties', 'variations')).thenReturn(of(true)); + fixture.detectChanges(); + + expect(findAllCustomElements(element)).toMatchInlineSnapshot(` Array [ "ish-product-variation-select", ] `); + }); }); - it('should always render display for advanced variation handling', () => { - when(context.select('displayProperties', 'variations')).thenReturn(of(true)); - FeatureToggleModule.switchTestingFeatures('advancedVariationHandling'); - fixture.detectChanges(); + describe('advanced variation handling', () => { + beforeEach(async () => { + prepareTestbed(true); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ProductDetailVariationsComponent); + component = fixture.componentInstance; + element = fixture.nativeElement; + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + expect(element).toBeTruthy(); + expect(() => fixture.detectChanges()).not.toThrow(); + }); + + it('should always render display for advanced variation handling', () => { + when(context.select('displayProperties', 'variations')).thenReturn(of(true)); + fixture.detectChanges(); - expect(findAllCustomElements(element)).toMatchInlineSnapshot(` + expect(findAllCustomElements(element)).toMatchInlineSnapshot(` Array [ "ish-product-variation-display", "ish-product-master-link", ] `); + }); }); }); diff --git a/src/app/shared/components/line-item/line-item-list-element/line-item-list-element.component.html b/src/app/shared/components/line-item/line-item-list-element/line-item-list-element.component.html index cc4181ba89..c000a6dcbc 100644 --- a/src/app/shared/components/line-item/line-item-list-element/line-item-list-element.component.html +++ b/src/app/shared/components/line-item/line-item-list-element/line-item-list-element.component.html @@ -40,7 +40,7 @@ { let element: HTMLElement; let context: ProductContextFacade; - beforeEach(async () => { + async function prepareTestbed(serverSetting: boolean) { context = mock(ProductContextFacade); when(context.select('product')).thenReturn(of({} as ProductView)); when(context.select('quantity')).thenReturn(EMPTY); await TestBed.configureTestingModule({ - imports: [FeatureToggleModule.forTesting(), TranslateModule.forRoot()], + imports: [TranslateModule.forRoot()], declarations: [ LineItemListElementComponent, MockComponent(BasketPromotionComponent), @@ -60,6 +60,7 @@ describe('Line Item List Element Component', () => { MockComponent(ProductShipmentComponent), MockComponent(ProductVariationDisplayComponent), MockDirective(ProductContextDirective), + MockPipe(ServerSettingPipe, () => serverSetting), MockPipe(PricePipe), ], providers: [ @@ -67,68 +68,73 @@ describe('Line Item List Element Component', () => { { provide: CheckoutFacade, useFactory: () => instance(mock(CheckoutFacade)) }, ], }).compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(LineItemListElementComponent); - component = fixture.componentInstance; - element = fixture.nativeElement; - component.pli = BasketMockData.getBasketItem(); - }); + } - it('should be created', () => { - expect(component).toBeTruthy(); - expect(element).toBeTruthy(); - expect(() => fixture.detectChanges()).not.toThrow(); - }); + describe('b2c variation handling', () => { + beforeEach(async () => { + prepareTestbed(false); + }); - describe('editable', () => { beforeEach(() => { - component.editable = true; + fixture = TestBed.createComponent(LineItemListElementComponent); + component = fixture.componentInstance; + element = fixture.nativeElement; + component.pli = BasketMockData.getBasketItem(); }); - it('should render item quantity change input field if editable === true', () => { - fixture.detectChanges(); - expect(element.querySelector('ish-product-quantity')).toBeTruthy(); + it('should be created', () => { + expect(component).toBeTruthy(); + expect(element).toBeTruthy(); + expect(() => fixture.detectChanges()).not.toThrow(); }); - it('should not render item quantity change input field if editable === false', () => { - component.editable = false; - fixture.detectChanges(); - expect(element.querySelector('ish-product-quantity')).not.toBeTruthy(); + describe('editable', () => { + beforeEach(() => { + component.editable = true; + }); + + it('should render item quantity change input field if editable === true', () => { + fixture.detectChanges(); + expect(element.querySelector('ish-product-quantity')).toBeTruthy(); + }); + + it('should not render item quantity change input field if editable === false', () => { + component.editable = false; + fixture.detectChanges(); + expect(element.querySelector('ish-product-quantity')).not.toBeTruthy(); + }); + + it('should render item delete button if editable === true', () => { + fixture.detectChanges(); + expect(element.querySelector('fa-icon[ng-reflect-icon="fas,trash-alt"]')).toBeTruthy(); + }); + + it('should not render item delete button if editable === false', () => { + component.editable = false; + fixture.detectChanges(); + expect(element.querySelector('fa-icon[ng-reflect-icon="fas,trash-alt"]')).toBeFalsy(); + }); }); - it('should render item delete button if editable === true', () => { + it('should give correct sku to productIdComponent', () => { fixture.detectChanges(); - expect(element.querySelector('fa-icon[ng-reflect-icon="fas,trash-alt"]')).toBeTruthy(); + expect(element.querySelector('ish-product-id')).toMatchInlineSnapshot(``); }); - it('should not render item delete button if editable === false', () => { - component.editable = false; + it('should hold itemSurcharges for the line item', () => { fixture.detectChanges(); - expect(element.querySelector('fa-icon[ng-reflect-icon="fas,trash-alt"]')).toBeFalsy(); + expect(element.querySelectorAll('.details-tooltip')).toHaveLength(1); }); - }); - it('should give correct sku to productIdComponent', () => { - fixture.detectChanges(); - expect(element.querySelector('ish-product-id')).toMatchInlineSnapshot(``); - }); - - it('should hold itemSurcharges for the line item', () => { - fixture.detectChanges(); - expect(element.querySelectorAll('.details-tooltip')).toHaveLength(1); - }); - - it('should not display itemSurcharges for the line item if not available', () => { - component.pli = { ...BasketMockData.getBasketItem(), itemSurcharges: undefined }; - expect(() => fixture.detectChanges()).not.toThrow(); - expect(element.querySelectorAll('.details-tooltip')).toHaveLength(0); - }); + it('should not display itemSurcharges for the line item if not available', () => { + component.pli = { ...BasketMockData.getBasketItem(), itemSurcharges: undefined }; + expect(() => fixture.detectChanges()).not.toThrow(); + expect(element.querySelectorAll('.details-tooltip')).toHaveLength(0); + }); - it('should display standard elements for normal products', () => { - fixture.detectChanges(); - expect(findAllCustomElements(element)).toMatchInlineSnapshot(` + it('should display standard elements for normal products', () => { + fixture.detectChanges(); + expect(findAllCustomElements(element)).toMatchInlineSnapshot(` Array [ "ish-product-image", "ish-product-name", @@ -146,18 +152,38 @@ describe('Line Item List Element Component', () => { "ish-product-quantity", ] `); - }); + }); - it('should display bundle parts for bundle products', () => { - when(context.select('product')).thenReturn(of({ type: 'Bundle' } as ProductView)); - fixture.detectChanges(); - expect(findAllCustomElements(element)).toContain('ish-product-bundle-display'); + it('should display bundle parts for bundle products', () => { + when(context.select('product')).thenReturn(of({ type: 'Bundle' } as ProductView)); + fixture.detectChanges(); + expect(findAllCustomElements(element)).toContain('ish-product-bundle-display'); + }); }); - it('should not display edit component for variation products with advanced variation handling', () => { - when(context.select('product')).thenReturn(of({ type: 'VariationProduct' } as ProductView)); - FeatureToggleModule.switchTestingFeatures('advancedVariationHandling'); - fixture.detectChanges(); - expect(findAllCustomElements(element)).not.toContain('ish-line-item-edit'); + describe('advanced variation handling', () => { + beforeEach(async () => { + prepareTestbed(true); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(LineItemListElementComponent); + component = fixture.componentInstance; + element = fixture.nativeElement; + component.pli = BasketMockData.getBasketItem(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + expect(element).toBeTruthy(); + expect(() => fixture.detectChanges()).not.toThrow(); + }); + + it('should not display edit component for variation products with advanced variation handling', () => { + when(context.select('product')).thenReturn(of({ type: 'VariationProduct' } as ProductView)); + + fixture.detectChanges(); + expect(findAllCustomElements(element)).not.toContain('ish-line-item-edit'); + }); }); }); diff --git a/src/app/shared/components/product/product-item-variations/product-item-variations.component.html b/src/app/shared/components/product/product-item-variations/product-item-variations.component.html index 6570c0b383..13da8995d8 100644 --- a/src/app/shared/components/product/product-item-variations/product-item-variations.component.html +++ b/src/app/shared/components/product/product-item-variations/product-item-variations.component.html @@ -3,7 +3,12 @@
- + diff --git a/src/app/shared/components/product/product-item-variations/product-item-variations.component.spec.ts b/src/app/shared/components/product/product-item-variations/product-item-variations.component.spec.ts index de37d8416e..40188aff88 100644 --- a/src/app/shared/components/product/product-item-variations/product-item-variations.component.spec.ts +++ b/src/app/shared/components/product/product-item-variations/product-item-variations.component.spec.ts @@ -1,12 +1,12 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { TranslateModule } from '@ngx-translate/core'; -import { MockComponent } from 'ng-mocks'; +import { MockComponent, MockPipe } from 'ng-mocks'; import { of } from 'rxjs'; import { instance, mock, when } from 'ts-mockito'; import { ProductContextFacade } from 'ish-core/facades/product-context.facade'; -import { FeatureToggleModule } from 'ish-core/feature-toggle.module'; import { ProductView } from 'ish-core/models/product-view/product-view.model'; +import { ServerSettingPipe } from 'ish-core/pipes/server-setting.pipe'; import { findAllCustomElements } from 'ish-core/utils/dev/html-query-utils'; import { ProductVariationDisplayComponent } from 'ish-shared/components/product/product-variation-display/product-variation-display.component'; import { ProductVariationSelectComponent } from 'ish-shared/components/product/product-variation-select/product-variation-select.component'; @@ -19,75 +19,98 @@ describe('Product Item Variations Component', () => { let element: HTMLElement; let context: ProductContextFacade; - beforeEach(async () => { + async function prepareTestbed(serverSetting: boolean) { context = mock(ProductContextFacade); when(context.select('displayProperties', 'variations')).thenReturn(of(true)); when(context.select('product')).thenReturn(of({ type: 'VariationProduct' } as ProductView)); await TestBed.configureTestingModule({ - imports: [FeatureToggleModule.forTesting(), TranslateModule.forRoot()], + imports: [TranslateModule.forRoot()], declarations: [ MockComponent(ProductVariationDisplayComponent), MockComponent(ProductVariationSelectComponent), + MockPipe(ServerSettingPipe, () => serverSetting), ProductItemVariationsComponent, ], providers: [{ provide: ProductContextFacade, useFactory: () => instance(context) }], }).compileComponents(); - }); + } - beforeEach(() => { - fixture = TestBed.createComponent(ProductItemVariationsComponent); - component = fixture.componentInstance; - element = fixture.nativeElement; - }); + describe('advanced variation handling', () => { + beforeEach(async () => { + prepareTestbed(true); + }); - it('should be created', () => { - expect(component).toBeTruthy(); - expect(element).toBeTruthy(); - expect(() => fixture.detectChanges()).not.toThrow(); - }); + beforeEach(() => { + fixture = TestBed.createComponent(ProductItemVariationsComponent); + component = fixture.componentInstance; + element = fixture.nativeElement; + }); - it('should render select for variation product', () => { - fixture.detectChanges(); + it('should be created', () => { + expect(component).toBeTruthy(); + expect(element).toBeTruthy(); + expect(() => fixture.detectChanges()).not.toThrow(); + }); - expect(findAllCustomElements(element)).toMatchInlineSnapshot(` - Array [ - "ish-product-variation-select", - ] - `); - expect(element?.textContent).toBeEmpty(); - }); + it('should render display for variation product in readOnly mode', () => { + when(context.select('displayProperties', 'readOnly')).thenReturn(of(true)); + fixture.detectChanges(); - it('should render display for variation product in readOnly mode', () => { - when(context.select('displayProperties', 'readOnly')).thenReturn(of(true)); - fixture.detectChanges(); + expect(findAllCustomElements(element)).toMatchInlineSnapshot(` + Array [ + "ish-product-variation-display", + ] + `); + expect(element?.textContent).toBeEmpty(); + }); - expect(findAllCustomElements(element)).toMatchInlineSnapshot(` - Array [ - "ish-product-variation-display", - ] - `); - expect(element?.textContent).toBeEmpty(); - }); + it('should render display for variation product', () => { + fixture.detectChanges(); - it('should render display for variation product for advanced variation handling', () => { - FeatureToggleModule.switchTestingFeatures('advancedVariationHandling'); - fixture.detectChanges(); + expect(findAllCustomElements(element)).toMatchInlineSnapshot(` + Array [ + "ish-product-variation-display", + ] + `); + expect(element?.textContent).toBeEmpty(); + }); - expect(findAllCustomElements(element)).toMatchInlineSnapshot(` - Array [ - "ish-product-variation-display", - ] - `); - expect(element?.textContent).toBeEmpty(); + it('should render variation count for variation product master', () => { + when(context.select('product')).thenReturn(of({ type: 'VariationProductMaster' } as ProductView)); + when(context.select('variationCount')).thenReturn(of(1234)); + fixture.detectChanges(); + + expect(element?.textContent).toContain('1234'); + }); }); - it('should render variation count for variation product master for advanced variation handling', () => { - FeatureToggleModule.switchTestingFeatures('advancedVariationHandling'); - when(context.select('product')).thenReturn(of({ type: 'VariationProductMaster' } as ProductView)); - when(context.select('variationCount')).thenReturn(of(1234)); - fixture.detectChanges(); + describe('b2c variation handling', () => { + beforeEach(async () => { + prepareTestbed(false); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ProductItemVariationsComponent); + component = fixture.componentInstance; + element = fixture.nativeElement; + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + expect(element).toBeTruthy(); + expect(() => fixture.detectChanges()).not.toThrow(); + }); + + it('should render select for variation product', () => { + fixture.detectChanges(); - expect(element?.textContent).toContain('1234'); + expect(findAllCustomElements(element)).toMatchInlineSnapshot(` + Array [ + "ish-product-variation-select", + ] + `); + expect(element?.textContent).toBeEmpty(); + }); }); }); diff --git a/src/environments/environment.b2b.ts b/src/environments/environment.b2b.ts index a8c3c2ea2e..4e6df44ae4 100644 --- a/src/environments/environment.b2b.ts +++ b/src/environments/environment.b2b.ts @@ -12,7 +12,6 @@ export const environment: Environment = { 'compare', 'recently', 'rating', - 'advancedVariationHandling', 'businessCustomerRegistration', 'costCenters', 'quoting', diff --git a/src/environments/environment.model.ts b/src/environments/environment.model.ts index 2bcbba2388..e03ceea481 100644 --- a/src/environments/environment.model.ts +++ b/src/environments/environment.model.ts @@ -26,7 +26,6 @@ export interface Environment { | 'rating' | 'recently' /* B2B features */ - | 'advancedVariationHandling' | 'businessCustomerRegistration' | 'costCenters' | 'quoting'