(quote); @@ -39,24 +18,15 @@ class MockCommerceQuotesFacade implements Partial{ } } -@Directive({ - selector: '[cxOutlet]', -}) -class MockOutletDirective implements Partial { - @Input() cxOutlet: string; - @Input() cxOutletContext: string; -} - describe('QuoteHeaderPriceComponent', () => { let fixture: ComponentFixture ; let htmlElem: HTMLElement; let component: QuoteHeaderPriceComponent; - let facade: QuoteFacade; beforeEach(() => { TestBed.configureTestingModule({ imports: [I18nTestingModule], - declarations: [QuoteHeaderPriceComponent, MockOutletDirective], + declarations: [QuoteHeaderPriceComponent], providers: [ { provide: QuoteFacade, @@ -70,13 +40,119 @@ describe('QuoteHeaderPriceComponent', () => { fixture = TestBed.createComponent(QuoteHeaderPriceComponent); htmlElem = fixture.nativeElement; component = fixture.componentInstance; - facade = TestBed.inject(QuoteFacade); - mockQuoteDetails$.next(quote); + withPrices(); + fixture.detectChanges(); }); + function withPrices() { + quote.totalPrice = { value: 1000, formattedValue: '$1,000.00' }; + quote.orderDiscounts = { value: 5.99, formattedValue: '$5.99' }; + quote.productDiscounts = { value: 50, formattedValue: '$50.00' }; + quote.quoteDiscounts = { value: 100, formattedValue: '$100.00' }; + } + it('should create component', () => { expect(component).toBeDefined(); - expect(facade).toBeDefined(); + }); + + it('should display all prices and discounts when present', () => { + TestUtil.expectNumberOfElementsPresent( + expect, + htmlElem, + '.cx-price-row', + 5 + ); + TestUtil.expectNumberOfElementsPresent( + expect, + htmlElem, + '.cx-price-savings', + 3 + ); + + TestUtil.expectElementToContainText( + expect, + htmlElem, + '.cx-price-row', + '.subtotal $1,000.00', + 0 + ); + TestUtil.expectElementToContainText( + expect, + htmlElem, + '.cx-price-row', + '.orderDiscount $5.99', + 1 + ); + TestUtil.expectElementToContainText( + expect, + htmlElem, + '.cx-price-row', + '.productDiscount $50.00', + 2 + ); + TestUtil.expectElementToContainText( + expect, + htmlElem, + '.cx-price-row', + '.quoteDiscount $100.00', + 3 + ); + TestUtil.expectElementToContainText( + expect, + htmlElem, + '.cx-price-row', + '.total $1,000.00', + 4 + ); + }); + + it('should display only totals when discounts are zero', () => { + quote.orderDiscounts = {}; + quote.productDiscounts = undefined; + quote.quoteDiscounts = { value: 0, formattedValue: '$0.00' }; + fixture.detectChanges(); + + TestUtil.expectNumberOfElementsPresent( + expect, + htmlElem, + '.cx-price-row', + 2 + ); + TestUtil.expectElementNotPresent(expect, htmlElem, '.cx-price-savings'); + + TestUtil.expectElementToContainText( + expect, + htmlElem, + '.cx-price-row', + '.subtotal $1,000.00', + 0 + ); + + TestUtil.expectElementToContainText( + expect, + htmlElem, + '.cx-price-row', + '.total $1,000.00', + 1 + ); + }); + + describe('hasNonZeroPriceValue', () => { + it('should return true if price value is present', () => { + expect(component.hasNonZeroPriceValue({ value: 99.99 })).toBe(true); + }); + + it('should return false if price is not present', () => { + expect(component.hasNonZeroPriceValue(undefined)).toBe(false); + }); + + it('should return false if price value is not present', () => { + expect(component.hasNonZeroPriceValue({})).toBe(false); + }); + + it('should return false if price value is zero', () => { + expect(component.hasNonZeroPriceValue({ value: 0.0 })).toBe(false); + }); }); describe('Ghost animation', () => { @@ -84,39 +160,35 @@ describe('QuoteHeaderPriceComponent', () => { component.quoteDetails$ = NEVER; fixture.detectChanges(); - CommonQuoteTestUtilsService.expectElementPresent( + TestUtil.expectElementPresent( expect, htmlElem, '.cx-ghost-summary-heading' ); - CommonQuoteTestUtilsService.expectElementPresent( - expect, - htmlElem, - '.cx-ghost-title' - ); + TestUtil.expectElementPresent(expect, htmlElem, '.cx-ghost-title'); - CommonQuoteTestUtilsService.expectElementPresent( + TestUtil.expectElementPresent( expect, htmlElem, '.cx-ghost-summary-partials' ); - CommonQuoteTestUtilsService.expectNumberOfElementsPresent( + TestUtil.expectNumberOfElementsPresent( expect, htmlElem, '.cx-ghost-row', 4 ); - CommonQuoteTestUtilsService.expectNumberOfElementsPresent( + TestUtil.expectNumberOfElementsPresent( expect, htmlElem, '.cx-ghost-summary-label', 4 ); - CommonQuoteTestUtilsService.expectNumberOfElementsPresent( + TestUtil.expectNumberOfElementsPresent( expect, htmlElem, '.cx-ghost-summary-amount', diff --git a/feature-libs/quote/components/header/price/quote-header-price.component.ts b/feature-libs/quote/components/header/price/quote-header-price.component.ts index 34e9f695b9c..b1ec18dde74 100644 --- a/feature-libs/quote/components/header/price/quote-header-price.component.ts +++ b/feature-libs/quote/components/header/price/quote-header-price.component.ts @@ -5,7 +5,7 @@ */ import { Component, inject } from '@angular/core'; -import { CartOutlets } from '@spartacus/cart/base/root'; +import { Price } from '@spartacus/core'; import { QuoteFacade } from '@spartacus/quote/root'; @Component({ @@ -15,6 +15,15 @@ import { QuoteFacade } from '@spartacus/quote/root'; export class QuoteHeaderPriceComponent { protected quoteFacade = inject(QuoteFacade); - readonly cartOutlets = CartOutlets; quoteDetails$ = this.quoteFacade.getQuoteDetails(); + + /** + * Checks whether the price has a non-zero value + * + * @param price to check + * @returns true, only if the price has a non zero value + */ + hasNonZeroPriceValue(price?: Price): boolean { + return !!price && !!price.value && price.value > 0; + } } diff --git a/feature-libs/quote/components/header/price/quote-header-price.module.ts b/feature-libs/quote/components/header/price/quote-header-price.module.ts index 370fd5261ac..059e268e332 100644 --- a/feature-libs/quote/components/header/price/quote-header-price.module.ts +++ b/feature-libs/quote/components/header/price/quote-header-price.module.ts @@ -12,11 +12,10 @@ import { I18nModule, provideDefaultConfig, } from '@spartacus/core'; -import { OutletModule } from '@spartacus/storefront'; import { QuoteHeaderPriceComponent } from './quote-header-price.component'; @NgModule({ - imports: [CommonModule, I18nModule, OutletModule], + imports: [CommonModule, I18nModule], providers: [ provideDefaultConfig( { cmsComponents: { diff --git a/feature-libs/quote/styles/_quote-header-price.scss b/feature-libs/quote/styles/_quote-header-price.scss index 855daa7064a..f07d05cf87d 100644 --- a/feature-libs/quote/styles/_quote-header-price.scss +++ b/feature-libs/quote/styles/_quote-header-price.scss @@ -1,4 +1,26 @@ %cx-quote-header-price { + > div { + padding-block-end: 0.5rem; + } + .cx-price-heading { + @include type('4'); + font-weight: var(--cx-font-weight-bold); + } + .cx-price-footer { + font-style: italic; + } + .cx-price-row { + display: flex; + flex-wrap: nowrap; + justify-content: space-between; + } + .cx-price-savings { + color: var(--cx-color-success); + } + .cx-price-total { + font-weight: var(--cx-font-weight-bold); + } + &:not(:empty) { .cx-ghost-title, .cx-ghost-summary-label,