From a66af1d0562d3032d28723588ca01bedc12db11b Mon Sep 17 00:00:00 2001 From: Jesus Navalon Date: Sun, 12 Jul 2026 22:40:48 +0200 Subject: [PATCH] feat: enhance tests with router, HTTP client, and animations support --- package-lock.json | 23 +++ package.json | 1 + src/app/app.spec.ts | 15 +- .../product-actions/product-actions.spec.ts | 73 ++++++-- .../product-description.spec.ts | 69 ++++++-- .../product-detail/product-detail.spec.ts | 73 ++++++-- .../product-image/product-image.spec.ts | 27 ++- .../product-item/product-item.spec.ts | 51 ++++-- .../product-list/product-list.spec.ts | 162 ++++++++++++++++-- .../product-search/product-search.spec.ts | 34 ++-- src/app/layout/header/header.spec.ts | 63 +++++-- 11 files changed, 478 insertions(+), 113 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0d267f5..5ef91ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "mobile-store", "version": "0.0.0", "dependencies": { + "@angular/animations": "v20-lts", "@angular/common": "v20-lts", "@angular/compiler": "v20-lts", "@angular/core": "v20-lts", @@ -681,6 +682,20 @@ "typescript": "*" } }, + "node_modules/@angular/animations": { + "version": "20.3.26", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.26.tgz", + "integrity": "sha512-hfNrX19v8xs/usNkELSqc6q5IwBfS2GGW8sQ4OMpxAmLZDJwaLUxkj48t8VYhUFezsIfzR+sDEi6ZQBOaMIYug==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/core": "20.3.26" + } + }, "node_modules/@angular/build": { "version": "20.3.32", "resolved": "https://registry.npmjs.org/@angular/build/-/build-20.3.32.tgz", @@ -10824,6 +10839,14 @@ "@angular-eslint/bundled-angular-compiler": "21.0.1" } }, + "@angular/animations": { + "version": "20.3.26", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.26.tgz", + "integrity": "sha512-hfNrX19v8xs/usNkELSqc6q5IwBfS2GGW8sQ4OMpxAmLZDJwaLUxkj48t8VYhUFezsIfzR+sDEi6ZQBOaMIYug==", + "requires": { + "tslib": "^2.3.0" + } + }, "@angular/build": { "version": "20.3.32", "resolved": "https://registry.npmjs.org/@angular/build/-/build-20.3.32.tgz", diff --git a/package.json b/package.json index aa2490e..5ec4c01 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ }, "private": true, "dependencies": { + "@angular/animations": "v20-lts", "@angular/common": "v20-lts", "@angular/compiler": "v20-lts", "@angular/core": "v20-lts", diff --git a/src/app/app.spec.ts b/src/app/app.spec.ts index c6ac6df..572821b 100644 --- a/src/app/app.spec.ts +++ b/src/app/app.spec.ts @@ -1,10 +1,21 @@ import { TestBed } from '@angular/core/testing'; +import { provideRouter } from '@angular/router'; +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; +import { provideNoopAnimations } from '@angular/platform-browser/animations'; + import { App } from './app'; describe('App', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [App], + providers: [ + provideRouter([]), + provideHttpClient(), + provideHttpClientTesting(), + provideNoopAnimations(), + ], }).compileComponents(); }); @@ -14,10 +25,10 @@ describe('App', () => { expect(app).toBeTruthy(); }); - it('should render title', () => { + it('should render the header', () => { const fixture = TestBed.createComponent(App); fixture.detectChanges(); const compiled = fixture.nativeElement as HTMLElement; - expect(compiled.querySelector('h1')?.textContent).toContain('Hello, mobile-store'); + expect(compiled.querySelector('app-header')).toBeTruthy(); }); }); diff --git a/src/app/features/product-detail/product-actions/product-actions.spec.ts b/src/app/features/product-detail/product-actions/product-actions.spec.ts index 96a4d89..5e774ac 100644 --- a/src/app/features/product-detail/product-actions/product-actions.spec.ts +++ b/src/app/features/product-detail/product-actions/product-actions.spec.ts @@ -1,23 +1,70 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; +import { provideNoopAnimations } from '@angular/platform-browser/animations'; import { ProductActions } from './product-actions'; +import { ProductOptions } from '../../../core/models/product.model'; +import {environment} from '../../../../environments/environment'; describe('ProductActions', () => { - let component: ProductActions; - let fixture: ComponentFixture; + let httpMock: HttpTestingController; - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [ProductActions] - }) - .compileComponents(); + const options: ProductOptions = { + colors: [ + { code: 1, name: 'Negro' }, + { code: 2, name: 'Blanco' } + ], + storages: [ + { code: 10, name: '16GB' }, + { code: 20, name: '32GB' } + ] + }; - fixture = TestBed.createComponent(ProductActions); - component = fixture.componentInstance; - fixture.detectChanges(); + beforeEach(() => { + localStorage.clear(); + TestBed.configureTestingModule({ + imports: [ProductActions], + providers: [provideHttpClient(), provideHttpClientTesting(), provideNoopAnimations()] + }); + httpMock = TestBed.inject(HttpTestingController); }); - it('should create', () => { - expect(component).toBeTruthy(); + afterEach(() => { + httpMock.verify(); + localStorage.clear(); + }); + + it('preselect the first color and storage option', () => { + const fixture = TestBed.createComponent(ProductActions); + fixture.componentRef.setInput('productId', '1'); + fixture.componentRef.setInput('options', options); + fixture.detectChanges(); + + expect(fixture.componentInstance.selectedColorCode()).toBe(1); + expect(fixture.componentInstance.selectedStorageCode()).toBe(10); + }); + + it('add the product to the cart with the current selection and emit added', async () => { + const fixture = TestBed.createComponent(ProductActions); + fixture.componentRef.setInput('productId', '1'); + fixture.componentRef.setInput('options', options); + fixture.detectChanges(); + + let addedEmitted = false; + fixture.componentInstance.added.subscribe(() => (addedEmitted = true)); + + fixture.componentInstance.selectedColorCode.set(2); + fixture.componentInstance.selectedStorageCode.set(20); + + const promise = fixture.componentInstance.onAddToCart(); + + const req = httpMock.expectOne(`${environment.apiBaseUrl}/cart`); + expect(req.request.body).toEqual({ id: '1', colorCode: 2, storageCode: 20 }); + req.flush({ count: 1 }); + + await promise; + + expect(addedEmitted).toBeTrue(); }); }); diff --git a/src/app/features/product-detail/product-description/product-description.spec.ts b/src/app/features/product-detail/product-description/product-description.spec.ts index a655da5..fc774bd 100644 --- a/src/app/features/product-detail/product-description/product-description.spec.ts +++ b/src/app/features/product-detail/product-description/product-description.spec.ts @@ -1,23 +1,64 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TestBed } from '@angular/core/testing'; import { ProductDescription } from './product-description'; +import { ProductDetail } from '../../../core/models/product.model'; describe('ProductDescription', () => { - let component: ProductDescription; - let fixture: ComponentFixture; + const product: ProductDetail = { + id: '1', + brand: 'Acer', + model: 'Iconia Talk S', + price: '170', + imgUrl: 'a.jpg', + cpu: 'Quad-core', + ram: '2 GB RAM', + displayResolution: '7.0 inches', + displaySize: '720 x 1280 pixels', + primaryCamera: ['13 MP'], + secondaryCmera: ['2 MP'], + dimentions: '191.7 x 101 x 9.4 mm', + weight: '260', + options: { colors: [], storages: [] } + }; - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [ProductDescription] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ProductDescription); - component = fixture.componentInstance; - fixture.detectChanges(); + beforeEach(() => { + TestBed.configureTestingModule({ imports: [ProductDescription] }); }); - it('should create', () => { - expect(component).toBeTruthy(); + it('Show rows with the fields present, omitting the missing ones.', () => { + const fixture = TestBed.createComponent(ProductDescription); + fixture.componentRef.setInput('product', product); + fixture.detectChanges(); + + const text: string = fixture.nativeElement.textContent; + expect(text).toContain('Brand'); + expect(text).toContain('Acer'); + expect(text).toContain('CPU'); + // Campo ausente (os) no debe generar una fila. + expect(text).not.toContain('OS'); + }); + + it('label displayResolution/displaySize according to their actual content (API names swapped)', () => { + const fixture = TestBed.createComponent(ProductDescription); + fixture.componentRef.setInput('product', product); + fixture.detectChanges(); + + const rows = fixture.componentInstance.rows(); + const tamano = rows.find((r) => r.label === 'Screen size'); + const resolucion = rows.find((r) => r.label === 'Resolution'); + + expect(tamano?.value).toBe('7.0 inches'); + expect(resolucion?.value).toBe('720 x 1280 pixels'); + }); + + it('Combine primaryCamera and secondaryCamera into a single camera row.', () => { + const fixture = TestBed.createComponent(ProductDescription); + fixture.componentRef.setInput('product', product); + fixture.detectChanges(); + + const rows = fixture.componentInstance.rows(); + const camaras = rows.find((r) => r.label === 'Cameras'); + + expect(camaras?.value).toBe('13 MP, 2 MP'); }); }); diff --git a/src/app/features/product-detail/product-detail.spec.ts b/src/app/features/product-detail/product-detail.spec.ts index 6373d68..c307d34 100644 --- a/src/app/features/product-detail/product-detail.spec.ts +++ b/src/app/features/product-detail/product-detail.spec.ts @@ -1,23 +1,70 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TestBed } from '@angular/core/testing'; +import { provideRouter } from '@angular/router'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; +import { provideNoopAnimations } from '@angular/platform-browser/animations'; import { ProductDetail } from './product-detail'; +import { ProductDetail as ProductDetailModel } from '../../core/models/product.model'; +import {environment} from '../../../environments/environment'; describe('ProductDetail', () => { - let component: ProductDetail; - let fixture: ComponentFixture; + let httpMock: HttpTestingController; - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [ProductDetail] - }) - .compileComponents(); + const detail: ProductDetailModel = { + id: '1', + brand: 'Acer', + model: 'Iconia Talk S', + price: '170', + imgUrl: 'a.jpg', + options: { colors: [{ code: 1, name: 'Negro' }], storages: [{ code: 1, name: '16GB' }] } + }; - fixture = TestBed.createComponent(ProductDetail); - component = fixture.componentInstance; - fixture.detectChanges(); + beforeEach(() => { + localStorage.clear(); + TestBed.configureTestingModule({ + imports: [ProductDetail], + providers: [ + provideRouter([]), + provideHttpClient(), + provideHttpClientTesting(), + provideNoopAnimations() + ] + }); + httpMock = TestBed.inject(HttpTestingController); }); - it('should create', () => { - expect(component).toBeTruthy(); + afterEach(() => { + httpMock.verify(); + localStorage.clear(); + }); + + it('loads and show the product detail using the id', async () => { + const fixture = TestBed.createComponent(ProductDetail); + fixture.componentRef.setInput('id', '1'); + fixture.detectChanges(); + + httpMock.expectOne(`${environment.apiBaseUrl}/product/1`).flush(detail); + await fixture.whenStable(); + fixture.detectChanges(); + + const text: string = fixture.nativeElement.textContent; + expect(text).toContain('Acer'); + expect(text).toContain('Iconia Talk S'); + expect(fixture.nativeElement.querySelector('app-product-actions')).toBeTruthy(); + }); + + it('it shows an error message if API call fails', async () => { + const fixture = TestBed.createComponent(ProductDetail); + fixture.componentRef.setInput('id', '1'); + fixture.detectChanges(); + + httpMock + .expectOne(`${environment.apiBaseUrl}/product/1`) + .flush('error', { status: 404, statusText: 'Not Found' }); + await fixture.whenStable(); + fixture.detectChanges(); + + expect(fixture.componentInstance.error()).toBeTruthy(); }); }); diff --git a/src/app/features/product-detail/product-image/product-image.spec.ts b/src/app/features/product-detail/product-image/product-image.spec.ts index 65ec4f1..26e7c00 100644 --- a/src/app/features/product-detail/product-image/product-image.spec.ts +++ b/src/app/features/product-detail/product-image/product-image.spec.ts @@ -1,23 +1,20 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TestBed } from '@angular/core/testing'; import { ProductImage } from './product-image'; describe('ProductImage', () => { - let component: ProductImage; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [ProductImage] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ProductImage); - component = fixture.componentInstance; - fixture.detectChanges(); + beforeEach(() => { + TestBed.configureTestingModule({ imports: [ProductImage] }); }); - it('should create', () => { - expect(component).toBeTruthy(); + it('it renders the image with proper src and alt', () => { + const fixture = TestBed.createComponent(ProductImage); + fixture.componentRef.setInput('imgUrl', 'https://example.com/a.jpg'); + fixture.componentRef.setInput('alt', 'Acer Iconia'); + fixture.detectChanges(); + + const img: HTMLImageElement = fixture.nativeElement.querySelector('img'); + expect(img.src).toBe('https://example.com/a.jpg'); + expect(img.alt).toBe('Acer Iconia'); }); }); diff --git a/src/app/features/product-list/product-item/product-item.spec.ts b/src/app/features/product-list/product-item/product-item.spec.ts index e766a4c..5f88f4c 100644 --- a/src/app/features/product-list/product-item/product-item.spec.ts +++ b/src/app/features/product-list/product-item/product-item.spec.ts @@ -1,23 +1,46 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TestBed } from '@angular/core/testing'; +import { provideNoopAnimations } from '@angular/platform-browser/animations'; import { ProductItem } from './product-item'; +import { Product } from '../../../core/models/product.model'; describe('ProductItem', () => { - let component: ProductItem; - let fixture: ComponentFixture; + const product: Product = { + id: '1', + brand: 'Acer', + model: 'Iconia Talk S', + price: '170', + imgUrl: 'https://example.com/a.jpg' + }; - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [ProductItem] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ProductItem); - component = fixture.componentInstance; - fixture.detectChanges(); + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ProductItem], + providers: [provideNoopAnimations()] + }); }); - it('should create', () => { - expect(component).toBeTruthy(); + it('it shows brand, model and product price', () => { + const fixture = TestBed.createComponent(ProductItem); + fixture.componentRef.setInput('product', product); + fixture.detectChanges(); + + const text: string = fixture.nativeElement.textContent; + expect(text).toContain('Acer'); + expect(text).toContain('Iconia Talk S'); + expect(text).toContain('170'); + }); + + it('it emits productSelected with the product when clicking on it', () => { + const fixture = TestBed.createComponent(ProductItem); + fixture.componentRef.setInput('product', product); + fixture.detectChanges(); + + let selected: Product | undefined; + fixture.componentInstance.productSelected.subscribe((p) => (selected = p)); + + fixture.nativeElement.querySelector('mat-card').click(); + + expect(selected).toEqual(product); }); }); diff --git a/src/app/features/product-list/product-list.spec.ts b/src/app/features/product-list/product-list.spec.ts index 15c39f0..1e2c463 100644 --- a/src/app/features/product-list/product-list.spec.ts +++ b/src/app/features/product-list/product-list.spec.ts @@ -1,23 +1,159 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TestBed } from '@angular/core/testing'; +import { provideRouter, Router } from '@angular/router'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; +import { provideNoopAnimations } from '@angular/platform-browser/animations'; import { ProductList } from './product-list'; +import { Product } from '../../core/models/product.model'; +import {environment} from '../../../environments/environment'; describe('ProductList', () => { - let component: ProductList; - let fixture: ComponentFixture; + let httpMock: HttpTestingController; - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [ProductList] - }) - .compileComponents(); + const products: Product[] = [ + { id: '1', brand: 'Acer', model: 'Iconia Talk S', price: '170', imgUrl: 'a.jpg' }, + { id: '2', brand: 'Samsung', model: 'Galaxy S9', price: '300', imgUrl: 'b.jpg' } + ]; - fixture = TestBed.createComponent(ProductList); - component = fixture.componentInstance; - fixture.detectChanges(); + /** Genera `count` productos únicos, útil para probar la paginación del scroll infinito. */ + function buildProducts(count: number): Product[] { + return Array.from({ length: count }, (_, i) => ({ + id: `${i + 1}`, + brand: i % 2 === 0 ? 'Acer' : 'Samsung', + model: `Model ${i + 1}`, + price: '100', + imgUrl: `img${i + 1}.jpg` + })); + } + + /** Fabrica un Event mínimo con las propiedades de scroll que lee onScroll(). */ + function makeScrollEvent(scrollTop: number, clientHeight: number, scrollHeight: number): Event { + return { target: { scrollTop, clientHeight, scrollHeight } } as unknown as Event; + } + + beforeEach(() => { + localStorage.clear(); + TestBed.configureTestingModule({ + imports: [ProductList], + providers: [ + provideRouter([]), + provideHttpClient(), + provideHttpClientTesting(), + provideNoopAnimations() + ] + }); + httpMock = TestBed.inject(HttpTestingController); }); - it('should create', () => { - expect(component).toBeTruthy(); + afterEach(() => { + httpMock.verify(); + localStorage.clear(); + }); + + async function createAndLoad(mockProducts: Product[] = products) { + const fixture = TestBed.createComponent(ProductList); + Object.assign(fixture.nativeElement.style, { + display: 'flex', + flexDirection: 'column', + height: '400px' + }); + fixture.detectChanges(); + httpMock.expectOne(`${environment.apiBaseUrl}/product`).flush(mockProducts); + await fixture.whenStable(); + fixture.detectChanges(); + return fixture; + } + + it('loads and shows all producst when load', async () => { + const fixture = await createAndLoad(); + + expect(fixture.componentInstance.filteredProducts().length).toBe(2); + expect(fixture.nativeElement.querySelectorAll('app-product-item').length).toBe(2); + }); + + it('filtering in live time', async () => { + const fixture = await createAndLoad(); + + fixture.componentInstance.onSearchTermChange('Samsung'); + fixture.detectChanges(); + + const filtered = fixture.componentInstance.filteredProducts(); + expect(filtered.length).toBe(1); + expect(filtered[0].brand).toBe('Samsung'); + }); + + it('goes to detail view', async () => { + const fixture = await createAndLoad(); + const router = TestBed.inject(Router); + const navigateSpy = spyOn(router, 'navigate'); + + fixture.componentInstance.onSelectProduct(products[0]); + + expect(navigateSpy).toHaveBeenCalledWith(['/product', '1']); + }); + + it('Error message when api fails', async () => { + const fixture = TestBed.createComponent(ProductList); + fixture.detectChanges(); + httpMock + .expectOne(`${environment.apiBaseUrl}/product`) + .flush('error', { status: 500, statusText: 'Server Error' }); + await fixture.whenStable(); + fixture.detectChanges(); + + expect(fixture.componentInstance.error()).toBeTruthy(); + }); + + describe('infinite scroll', () => { + it('Only 20 products when too much products', async () => { + const fixture = await createAndLoad(buildProducts(45)); + + expect(fixture.componentInstance.visibleProducts().length).toBe(20); + expect(fixture.componentInstance.hasMore()).toBeTrue(); + expect(fixture.nativeElement.querySelectorAll('app-product-item').length).toBe(20); + }); + + it('loads when close to last element edge', async () => { + const fixture = await createAndLoad(buildProducts(45)); + + // scrollTop + clientHeight a menos de 200px del final -> carga más. + fixture.componentInstance.onScroll(makeScrollEvent(1000, 500, 1650)); + fixture.detectChanges(); + + expect(fixture.componentInstance.visibleProducts().length).toBe(40); + }); + + it('It does not load more items if it is not close to the limit boundary', async () => { + const fixture = await createAndLoad(buildProducts(45)); + + fixture.componentInstance.onScroll(makeScrollEvent(0, 500, 3000)); + fixture.detectChanges(); + + expect(fixture.componentInstance.visibleProducts().length).toBe(20); + }); + + it('It does not exceed the total number of filtered products when revealing more batches.', async () => { + const fixture = await createAndLoad(buildProducts(25)); + + fixture.componentInstance.onScroll(makeScrollEvent(1000, 500, 1650)); + fixture.detectChanges(); + + expect(fixture.componentInstance.visibleProducts().length).toBe(25); + expect(fixture.componentInstance.hasMore()).toBeFalse(); + }); + + it('Reset pagination to 20 when the search term changes.', async () => { + const fixture = await createAndLoad(buildProducts(45)); + + fixture.componentInstance.onScroll(makeScrollEvent(1000, 500, 1650)); + fixture.detectChanges(); + expect(fixture.componentInstance.visibleProducts().length).toBe(40); + + fixture.componentInstance.onSearchTermChange('acer'); + fixture.detectChanges(); + + expect(fixture.componentInstance.visibleProducts().length).toBe(20); + }); }); }); diff --git a/src/app/features/product-list/product-search/product-search.spec.ts b/src/app/features/product-list/product-search/product-search.spec.ts index 232062b..f56ec3b 100644 --- a/src/app/features/product-list/product-search/product-search.spec.ts +++ b/src/app/features/product-list/product-search/product-search.spec.ts @@ -1,23 +1,27 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TestBed } from '@angular/core/testing'; +import { provideNoopAnimations } from '@angular/platform-browser/animations'; import { ProductSearch } from './product-search'; describe('ProductSearch', () => { - let component: ProductSearch; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [ProductSearch] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ProductSearch); - component = fixture.componentInstance; - fixture.detectChanges(); + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ProductSearch], + providers: [provideNoopAnimations()] + }); }); - it('should create', () => { - expect(component).toBeTruthy(); + it('emits searchChange with the inserted text', () => { + const fixture = TestBed.createComponent(ProductSearch); + fixture.detectChanges(); + + const emitted: string[] = []; + fixture.componentInstance.searchChange.subscribe((value) => emitted.push(value)); + + const input: HTMLInputElement = fixture.nativeElement.querySelector('input'); + input.value = 'Samsung'; + input.dispatchEvent(new Event('input')); + + expect(emitted).toEqual(['Samsung']); }); }); diff --git a/src/app/layout/header/header.spec.ts b/src/app/layout/header/header.spec.ts index 7da9515..db1f361 100644 --- a/src/app/layout/header/header.spec.ts +++ b/src/app/layout/header/header.spec.ts @@ -1,23 +1,58 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TestBed } from '@angular/core/testing'; +import { provideRouter } from '@angular/router'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; +import { provideNoopAnimations } from '@angular/platform-browser/animations'; import { Header } from './header'; +import { CartService } from '../../core/services/cart.service'; +import {environment} from '../../../environments/environment'; describe('Header', () => { - let component: Header; - let fixture: ComponentFixture
; + let httpMock: HttpTestingController; - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [Header] - }) - .compileComponents(); - - fixture = TestBed.createComponent(Header); - component = fixture.componentInstance; - fixture.detectChanges(); + beforeEach(() => { + localStorage.clear(); + TestBed.configureTestingModule({ + imports: [Header], + providers: [ + provideRouter([]), + provideHttpClient(), + provideHttpClientTesting(), + provideNoopAnimations() + ] + }); + httpMock = TestBed.inject(HttpTestingController); }); - it('should create', () => { - expect(component).toBeTruthy(); + afterEach(() => { + httpMock.verify(); + localStorage.clear(); + }); + + it('successfully created', () => { + const fixture = TestBed.createComponent(Header); + expect(fixture.componentInstance).toBeTruthy(); + }); + + it('it shows home and cart icon', () => { + const fixture = TestBed.createComponent(Header); + fixture.detectChanges(); + const compiled: HTMLElement = fixture.nativeElement; + + expect(compiled.querySelector('.app-header__brand')).toBeTruthy(); + expect(compiled.querySelector('button[aria-label="Cart"]')).toBeTruthy(); + }); + + it('it shows the counter in the cart', async () => { + const fixture = TestBed.createComponent(Header); + const cartService = TestBed.inject(CartService); + + const promise = cartService.addToCart({ id: '1', colorCode: 1, storageCode: 1 }); + httpMock.expectOne(`${environment.apiBaseUrl}/cart`).flush({ count: 7 }); + await promise; + fixture.detectChanges(); + + expect(fixture.componentInstance.cartCount()).toBe(7); }); });