From da283f631b703404d93353d315126a40202a7ab6 Mon Sep 17 00:00:00 2001 From: Francisco de la Vega Date: Tue, 15 Sep 2026 12:34:10 +0200 Subject: [PATCH 1/2] Select category children from browse view --- src/app/data/availableFilters.ts | 2 + src/app/pages/browse/browse.component.spec.ts | 78 +++++++++++++++++++ src/app/pages/browse/browse.component.ts | 27 +++++-- src/app/pages/search/search.component.ts | 4 +- 4 files changed, 104 insertions(+), 7 deletions(-) create mode 100644 src/app/pages/browse/browse.component.spec.ts diff --git a/src/app/data/availableFilters.ts b/src/app/data/availableFilters.ts index d1e21352..b62dc03d 100644 --- a/src/app/data/availableFilters.ts +++ b/src/app/data/availableFilters.ts @@ -33,6 +33,8 @@ const DEFAULT_AVAILABLE_FILTERS: Filter[] = [] export const availableFilters: Filter[] = cloneFilters(DEFAULT_AVAILABLE_FILTERS) +export const SEARCH_ACTIVE_CATEGORY_STORAGE_KEY = 'search_active_category_id' + type RuntimeSearchFiltersConfig = { primaryCategoriesMode?: unknown primaryRootName?: unknown diff --git a/src/app/pages/browse/browse.component.spec.ts b/src/app/pages/browse/browse.component.spec.ts new file mode 100644 index 00000000..ceb6d72f --- /dev/null +++ b/src/app/pages/browse/browse.component.spec.ts @@ -0,0 +1,78 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TranslateModule } from '@ngx-translate/core'; +import { Router } from '@angular/router'; + +import { BrowseComponent } from './browse.component'; +import { ApiServiceService } from 'src/app/services/product-service.service'; +import { LocalStorageService } from 'src/app/services/local-storage.service'; +import { SEARCH_ACTIVE_CATEGORY_STORAGE_KEY } from 'src/app/data/availableFilters'; + +describe('BrowseComponent', () => { + let component: BrowseComponent; + let fixture: ComponentFixture; + let apiSpy: jasmine.SpyObj; + let routerSpy: jasmine.SpyObj; + let localStorageSpy: jasmine.SpyObj; + + beforeEach(async () => { + apiSpy = jasmine.createSpyObj('ApiServiceService', [ + 'getCategoriesByParentId', + 'getDefaultCategories', + 'getProducts', + 'getProductsDetails', + ]); + routerSpy = jasmine.createSpyObj('Router', ['navigate']); + localStorageSpy = jasmine.createSpyObj('LocalStorageService', [ + 'addCategoryFilter', + 'removeItem', + 'setItem', + ]); + + apiSpy.getDefaultCategories.and.resolveTo([]); + apiSpy.getProducts.and.resolveTo([]); + apiSpy.getProductsDetails.and.resolveTo([]); + apiSpy.getCategoriesByParentId.and.resolveTo([]); + + await TestBed.configureTestingModule({ + imports: [BrowseComponent, TranslateModule.forRoot()], + providers: [ + { provide: ApiServiceService, useValue: apiSpy }, + { provide: Router, useValue: routerSpy }, + { provide: LocalStorageService, useValue: localStorageSpy }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(BrowseComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should select the clicked category children before navigating to search', async () => { + const parent = { id: 'parent-1', name: 'Cloud' }; + const childA = { id: 'child-1', name: 'IaaS' }; + const childB = { id: 'child-2', name: 'PaaS' }; + apiSpy.getCategoriesByParentId.and.resolveTo([childA, childB]); + + await component.onCategoryClick(parent); + + expect(localStorageSpy.removeItem).toHaveBeenCalledWith('selected_categories'); + expect(localStorageSpy.setItem).toHaveBeenCalledWith(SEARCH_ACTIVE_CATEGORY_STORAGE_KEY, 'parent-1'); + expect(localStorageSpy.addCategoryFilter).toHaveBeenCalledWith(childA); + expect(localStorageSpy.addCategoryFilter).toHaveBeenCalledWith(childB); + expect(localStorageSpy.addCategoryFilter).not.toHaveBeenCalledWith(parent); + expect(routerSpy.navigate).toHaveBeenCalledWith(['/search']); + }); + + it('should fall back to selecting the parent category when no children are available', async () => { + const parent = { id: 'parent-1', name: 'Cloud' }; + apiSpy.getCategoriesByParentId.and.resolveTo([]); + + await component.onCategoryClick(parent); + + expect(localStorageSpy.addCategoryFilter).toHaveBeenCalledOnceWith(parent); + expect(routerSpy.navigate).toHaveBeenCalledWith(['/search']); + }); +}); diff --git a/src/app/pages/browse/browse.component.ts b/src/app/pages/browse/browse.component.ts index 3229a6a6..d1be8170 100644 --- a/src/app/pages/browse/browse.component.ts +++ b/src/app/pages/browse/browse.component.ts @@ -9,7 +9,7 @@ import { ApiServiceService } from 'src/app/services/product-service.service'; import { LocalStorageService } from 'src/app/services/local-storage.service'; import { Category } from 'src/app/models/interfaces'; import { iconForCategory } from 'src/app/data/categoryIcons'; -import { searchCategoriesConfig } from 'src/app/data/availableFilters'; +import { searchCategoriesConfig, SEARCH_ACTIVE_CATEGORY_STORAGE_KEY } from 'src/app/data/availableFilters'; interface PopularOffer { id: string; @@ -188,14 +188,31 @@ export class BrowseComponent implements OnInit { } } - onCategoryClick(category: Category) { - localStorage.removeItem('selected_categories'); - this.localStorage.addCategoryFilter(category); + async onCategoryClick(category: Category) { + this.localStorage.removeItem('selected_categories'); + if (category.id) { + this.localStorage.setItem(SEARCH_ACTIVE_CATEGORY_STORAGE_KEY, category.id); + const children = await this.api.getCategoriesByParentId(category.id).catch(() => []); + const childList: Category[] = Array.isArray(children) ? children : []; + + if (childList.length > 0) { + for (const child of childList) { + if (child?.id) { + this.localStorage.addCategoryFilter(child); + } + } + } else { + this.localStorage.addCategoryFilter(category); + } + } else { + this.localStorage.addCategoryFilter(category); + } this.router.navigate(['/search']); } onShowAll() { - localStorage.removeItem('selected_categories'); + this.localStorage.removeItem('selected_categories'); + this.localStorage.removeItem(SEARCH_ACTIVE_CATEGORY_STORAGE_KEY); this.router.navigate(['/search']); } } diff --git a/src/app/pages/search/search.component.ts b/src/app/pages/search/search.component.ts index b4f71df4..351c2e73 100644 --- a/src/app/pages/search/search.component.ts +++ b/src/app/pages/search/search.component.ts @@ -21,7 +21,7 @@ import { takeUntil } from 'rxjs/operators'; import { AiSearchService } from 'src/app/services/ai-search.service'; import { PriceServiceService } from 'src/app/services/price-service.service'; -import { availableFilters, searchCategoriesConfig } from 'src/app/data/availableFilters'; +import { availableFilters, searchCategoriesConfig, SEARCH_ACTIVE_CATEGORY_STORAGE_KEY } from 'src/app/data/availableFilters'; import { iconForCategory } from 'src/app/data/categoryIcons'; import { ThemeService } from 'src/app/services/theme.service'; @@ -73,7 +73,7 @@ export class SearchComponent implements OnInit, OnDestroy { procurementFilterKey = 'procurement_type'; private procurementCache = new Map(); private productsRequestVersion = 0; - private readonly activeCategoryStorageKey = 'search_active_category_id'; + private readonly activeCategoryStorageKey = SEARCH_ACTIVE_CATEGORY_STORAGE_KEY; showSortDropdown = false; sortOption: 'name' | 'date_new' | 'date_old' = 'date_new'; From a49a6f042e9a414e67c4e3b73839bf03923ae532 Mon Sep 17 00:00:00 2001 From: Francisco de la Vega Date: Tue, 15 Sep 2026 12:45:49 +0200 Subject: [PATCH 2/2] Fix compilation issues --- src/app/pages/search/search.component.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app/pages/search/search.component.ts b/src/app/pages/search/search.component.ts index d25de0d4..7caba030 100644 --- a/src/app/pages/search/search.component.ts +++ b/src/app/pages/search/search.component.ts @@ -21,8 +21,6 @@ import { AiSearchService } from 'src/app/services/ai-search.service'; import { PriceServiceService } from 'src/app/services/price-service.service'; import { availableFilters, searchCategoriesConfig, SEARCH_ACTIVE_CATEGORY_STORAGE_KEY } from 'src/app/data/availableFilters'; import { iconForCategory } from 'src/app/data/categoryIcons'; -import { AiSearchService } from 'src/app/services/ai-search.service'; -import { PriceServiceService } from 'src/app/services/price-service.service'; import { ThemeService } from 'src/app/services/theme.service'; type ToolbarFilter = {