From a1249d4a19316ace0ab4eac55868a0e709c1ff73 Mon Sep 17 00:00:00 2001 From: g-jaskowski Date: Thu, 2 Oct 2025 14:47:26 +0200 Subject: [PATCH 1/7] [ACS-10165] create all libraries component --- .../aca-content/assets/app.extensions.json | 7 + projects/aca-content/assets/i18n/en.json | 11 + .../aca-content/src/lib/aca-content.routes.ts | 19 ++ .../library-list/library-list.component.html | 94 +++++++++ .../library-list.component.spec.ts | 194 ++++++++++++++++++ .../library-list/library-list.component.ts | 142 +++++++++++++ 6 files changed, 467 insertions(+) create mode 100644 projects/aca-content/src/lib/components/library-list/library-list.component.html create mode 100644 projects/aca-content/src/lib/components/library-list/library-list.component.spec.ts create mode 100644 projects/aca-content/src/lib/components/library-list/library-list.component.ts diff --git a/projects/aca-content/assets/app.extensions.json b/projects/aca-content/assets/app.extensions.json index e8aa3798e8..e8f6cd30f2 100644 --- a/projects/aca-content/assets/app.extensions.json +++ b/projects/aca-content/assets/app.extensions.json @@ -175,6 +175,13 @@ "description": "APP.BROWSE.LIBRARIES.MENU.FAVORITE_LIBRARIES.SIDENAV_LINK.TOOLTIP", "route": "favorite/libraries" }, + { + "id": "app.navbar.libraries.all", + "order": 350, + "title": "APP.BROWSE.LIBRARIES.MENU.ALL_LIBRARIES.SIDENAV_LINK.LABEL", + "description": "APP.BROWSE.LIBRARIES.MENU.ALL_LIBRARIES.SIDENAV_LINK.TOOLTIP", + "route": "all/libraries" + }, { "id": "app.navbar.recentFiles", "order": 400, diff --git a/projects/aca-content/assets/i18n/en.json b/projects/aca-content/assets/i18n/en.json index c5353b36c9..e14e0b49fd 100644 --- a/projects/aca-content/assets/i18n/en.json +++ b/projects/aca-content/assets/i18n/en.json @@ -135,6 +135,10 @@ "FAVORITE_LIBRARIES": { "TITLE": "No Favorite Libraries", "TEXT": "Favorite a library that you want to find easily later." + }, + "ALL_LIBRARIES": { + "TITLE": "No Libraries", + "TEXT": "There are no libraries you can view." } }, "MENU": { @@ -151,6 +155,13 @@ "LABEL": "Favorite Libraries", "TOOLTIP": "Access my favorite libraries" } + }, + "ALL_LIBRARIES": { + "TITLE": "All Libraries", + "SIDENAV_LINK": { + "LABEL": "All Libraries", + "TOOLTIP": "Access all libraries" + } } }, "ERRORS": { diff --git a/projects/aca-content/src/lib/aca-content.routes.ts b/projects/aca-content/src/lib/aca-content.routes.ts index bbc4f25ac4..482286e986 100644 --- a/projects/aca-content/src/lib/aca-content.routes.ts +++ b/projects/aca-content/src/lib/aca-content.routes.ts @@ -42,6 +42,7 @@ import { TrashcanComponent } from './components/trashcan/trashcan.component'; import { ShellLayoutComponent } from '@alfresco/adf-core/shell'; import { SearchAiResultsComponent } from './components/knowledge-retrieval/search-ai/search-ai-results/search-ai-results.component'; import { SavedSearchesSmartListComponent } from './components/search/search-save/list/smart-list/saved-searches-smart-list.component'; +import { LibraryListComponent } from './components/library-list/library-list.component'; export const CONTENT_ROUTES: ExtensionRoute[] = [ { @@ -230,6 +231,24 @@ export const CONTENT_LAYOUT_ROUTES: Route[] = [ ...createViewRoutes('libraries') ] }, + { + path: 'all', + children: [ + { + path: '', + pathMatch: 'full', + redirectTo: 'libraries' + }, + { + path: 'libraries', + component: LibraryListComponent, + data: { + title: 'APP.BROWSE.LIBRARIES.MENU.ALL_LIBRARIES.TITLE', + sortingPreferenceKey: 'all-libraries' + } + } + ] + }, { path: 'favorites', data: { diff --git a/projects/aca-content/src/lib/components/library-list/library-list.component.html b/projects/aca-content/src/lib/components/library-list/library-list.component.html new file mode 100644 index 0000000000..692868f8a4 --- /dev/null +++ b/projects/aca-content/src/lib/components/library-list/library-list.component.html @@ -0,0 +1,94 @@ + +
+

+ {{ (selectedRowItemsCount < 1 ? 'APP.BROWSE.LIBRARIES.MENU.ALL_LIBRARIES.TITLE' : 'APP.HEADER.SELECTED') | translate: { count: selectedRowItemsCount } }} +

+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
diff --git a/projects/aca-content/src/lib/components/library-list/library-list.component.spec.ts b/projects/aca-content/src/lib/components/library-list/library-list.component.spec.ts new file mode 100644 index 0000000000..dd6515c298 --- /dev/null +++ b/projects/aca-content/src/lib/components/library-list/library-list.component.spec.ts @@ -0,0 +1,194 @@ +/*! + * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Alfresco Example Content Application + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * from Hyland Software. If not, see . + */ + +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Router } from '@angular/router'; +import { UnitTestingUtils, UserPreferencesService } from '@alfresco/adf-core'; +import { DocumentListComponent, SitesService } from '@alfresco/adf-content-services'; +import { LibraryListComponent } from './library-list.component'; +import { AppTestingModule } from '../../testing/app-testing.module'; +import { AppHookService } from '@alfresco/aca-shared'; +import { provideEffects } from '@ngrx/effects'; +import { RouterEffects } from '@alfresco/aca-shared/store'; +import { of, throwError } from 'rxjs'; +import { LibraryEffects } from '../../store/effects'; +import { getTitleElementText } from '../../testing/test-utils'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { Pagination, SiteEntry, SitePaging } from '@alfresco/js-api'; + +describe('LibraryListComponent', () => { + let fixture: ComponentFixture; + let component: LibraryListComponent; + let userPreference: UserPreferencesService; + let sitesService: SitesService; + let router: Router; + let appHookService: AppHookService; + let getSitesSpy: jasmine.Spy; + let unitTestingUtils: UnitTestingUtils; + + const paging: SitePaging = { + list: { + entries: [ + { entry: { id: '1', guid: '1', title: 'Library 1', visibility: 'public' } }, + { entry: { id: '2', guid: '2', title: 'Library 2', visibility: 'private' } } + ], + pagination: { count: 25, skipCount: 0 } + } + }; + + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [AppTestingModule, LibraryListComponent, MatSnackBarModule], + providers: [provideEffects([RouterEffects, LibraryEffects])] + }); + + fixture = TestBed.createComponent(LibraryListComponent); + component = fixture.componentInstance; + + unitTestingUtils = new UnitTestingUtils(fixture.debugElement); + sitesService = TestBed.inject(SitesService); + userPreference = TestBed.inject(UserPreferencesService); + appHookService = TestBed.inject(AppHookService); + router = TestBed.inject(Router); + + getSitesSpy = spyOn(sitesService, 'getSites'); + getSitesSpy.and.returnValue(of(paging)); + fixture.detectChanges(); + }); + + describe('on initialization', () => { + it('should set data', () => { + expect(component.list).toBe(paging); + expect(component.pagination).toBe(paging.list.pagination); + }); + + it('should get data with user preference pagination size', () => { + userPreference.paginationSize = 1; + component.ngOnInit(); + expect(sitesService.getSites).toHaveBeenCalledWith({ maxItems: userPreference.paginationSize }); + }); + + it('should set data on error', () => { + getSitesSpy.and.returnValue(throwError(() => 'error')); + component.ngOnInit(); + + expect(component.list).toBe(null); + expect(component.pagination).toBe(null); + expect(component.isLoading).toBe(false); + }); + + it('should set title based on selectedRowItemsCount', () => { + expect(getTitleElementText(fixture)).toBe('APP.BROWSE.LIBRARIES.MENU.ALL_LIBRARIES.TITLE'); + + component.selectedRowItemsCount = 5; + fixture.detectChanges(); + + expect(getTitleElementText(fixture)).toBe('APP.HEADER.SELECTED'); + }); + + it('should handle no columns preset in extensions', () => { + component['extensions'].documentListPresets.libraries = undefined; + component.ngOnInit(); + expect(component.columns.length).toBe(0); + }); + }); + + describe('Node navigation', () => { + it('should not navigate when node is null or missing guid', () => { + spyOn(router, 'navigate').and.stub(); + component.navigateTo(null); + expect(router.navigate).not.toHaveBeenCalled(); + + component.navigateTo({ entry: {} } as any); + expect(router.navigate).not.toHaveBeenCalled(); + }); + + it('should dispatch navigation action when node has guid', () => { + spyOn(component['store'], 'dispatch').and.stub(); + component.navigateTo({ entry: { guid: 'guid' } } as SiteEntry); + expect(component['store'].dispatch).toHaveBeenCalled(); + }); + + it('should handle node double click', () => { + spyOn(component, 'navigateTo').and.stub(); + const documentList = unitTestingUtils.getByDirective(DocumentListComponent); + documentList.triggerEventHandler('node-dblclick', { detail: { node: { entry: { guid: 'guid' } } } }); + expect(component.navigateTo).toHaveBeenCalledWith({ entry: { guid: 'guid' } } as SiteEntry); + }); + + it('should handle name click', () => { + spyOn(component, 'navigateTo').and.stub(); + const documentList = unitTestingUtils.getByDirective(DocumentListComponent); + documentList.triggerEventHandler('name-click', { detail: { node: { entry: { guid: 'guid' } } } }); + expect(component.navigateTo).toHaveBeenCalledWith({ entry: { guid: 'guid' } } as SiteEntry); + }); + }); + + describe('Reload on actions', () => { + it('should reload on libraryDeleted action', () => { + appHookService.libraryDeleted.next(''); + expect(sitesService.getSites).toHaveBeenCalled(); + }); + + it('should reload on libraryUpdated action', () => { + appHookService.libraryUpdated.next(paging.list.entries[0]); + expect(sitesService.getSites).toHaveBeenCalled(); + }); + + it('should reload on libraryJoined action', () => { + appHookService.libraryJoined.next(); + expect(sitesService.getSites).toHaveBeenCalled(); + }); + + it('should reload on libraryLeft action', () => { + appHookService.libraryLeft.next(''); + expect(sitesService.getSites).toHaveBeenCalled(); + }); + }); + + describe('Pagination', () => { + const pagination: Pagination = { + count: 100, + hasMoreItems: true, + totalItems: 300, + skipCount: 25, + maxItems: 25 + }; + + it('should get list with pagination data onChange event', () => { + component.onChange(pagination); + expect(sitesService.getSites).toHaveBeenCalledWith(pagination); + }); + + it('should get list with pagination data onChangePageSize event', () => { + component.onChangePageSize(pagination); + expect(sitesService.getSites).toHaveBeenCalledWith(pagination); + }); + + it('should set preference page size onChangePageSize event', () => { + component.onChangePageSize(pagination); + expect(userPreference.paginationSize).toBe(pagination.maxItems); + }); + }); +}); diff --git a/projects/aca-content/src/lib/components/library-list/library-list.component.ts b/projects/aca-content/src/lib/components/library-list/library-list.component.ts new file mode 100644 index 0000000000..ef8af0145a --- /dev/null +++ b/projects/aca-content/src/lib/components/library-list/library-list.component.ts @@ -0,0 +1,142 @@ +/*! + * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Alfresco Example Content Application + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * from Hyland Software. If not, see . + */ + +import { ChangeDetectorRef, Component, OnInit, ViewEncapsulation } from '@angular/core'; +import { Pagination, SiteEntry, SitePaging } from '@alfresco/js-api'; +import { + AppHookService, + ContextActionsDirective, + InfoDrawerComponent, + PageComponent, + PageLayoutComponent, + ToolbarComponent +} from '@alfresco/aca-shared'; +import { NavigateLibraryAction } from '@alfresco/aca-shared/store'; +import { + CustomEmptyContentTemplateDirective, + DataColumnComponent, + DataColumnListComponent, + EmptyContentComponent, + PaginationComponent, + UserPreferencesService +} from '@alfresco/adf-core'; +import { DocumentListPresetRef, DynamicColumnComponent } from '@alfresco/adf-extensions'; +import { CommonModule } from '@angular/common'; +import { DocumentListDirective } from '../../directives/document-list.directive'; +import { TranslatePipe } from '@ngx-translate/core'; +import { DocumentListComponent, SitesService } from '@alfresco/adf-content-services'; +import { merge } from 'rxjs'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; + +@Component({ + imports: [ + CommonModule, + DocumentListDirective, + ContextActionsDirective, + PaginationComponent, + InfoDrawerComponent, + PageLayoutComponent, + TranslatePipe, + ToolbarComponent, + EmptyContentComponent, + DynamicColumnComponent, + DataColumnListComponent, + DataColumnComponent, + DocumentListComponent, + CustomEmptyContentTemplateDirective + ], + templateUrl: './library-list.component.html', + encapsulation: ViewEncapsulation.None +}) +export class LibraryListComponent extends PageComponent implements OnInit { + pagination: Pagination = new Pagination({ + skipCount: 0, + maxItems: 25, + totalItems: 0 + }); + isLoading = false; + list: SitePaging; + columns: DocumentListPresetRef[] = []; + + constructor( + private appHookService: AppHookService, + private preferences: UserPreferencesService, + private changeDetectorRef: ChangeDetectorRef, + private sitesService: SitesService + ) { + super(); + } + + ngOnInit() { + super.ngOnInit(); + + this.getList({ maxItems: this.preferences.paginationSize }); + + merge(this.appHookService.libraryDeleted, this.appHookService.libraryUpdated, this.appHookService.libraryJoined, this.appHookService.libraryLeft) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(() => this.reloadList()); + this.columns = this.extensions.documentListPresets.libraries || []; + } + + navigateTo(node: SiteEntry) { + if (node?.entry?.guid) { + this.store.dispatch(new NavigateLibraryAction(node.entry.guid)); + } + } + + handleNodeClick(event: Event) { + this.navigateTo((event as CustomEvent).detail?.node); + } + + onChangePageSize(pagination: Pagination) { + this.preferences.paginationSize = pagination.maxItems; + this.getList(pagination); + } + + onChange(pagination: Pagination) { + this.getList(pagination); + } + + private getList(pagination: Pagination) { + this.isLoading = true; + this.sitesService.getSites(pagination).subscribe({ + next: (libraryList: SitePaging) => { + this.list = libraryList; + this.pagination = libraryList.list.pagination; + this.isLoading = false; + this.changeDetectorRef.detectChanges(); + }, + error: () => { + this.list = null; + this.pagination = null; + this.isLoading = false; + } + }); + } + + private reloadList() { + this.reload(); + this.getList(this.pagination); + } +} From 1006a9fede63cf93f76960e2f412c68cc14b5bae Mon Sep 17 00:00:00 2001 From: g-jaskowski Date: Fri, 10 Oct 2025 17:32:23 +0200 Subject: [PATCH 2/7] [ACS-10165] move translations, apply cr comments --- .../aca-content/assets/app.extensions.json | 6 +-- projects/aca-content/assets/i18n/ar.json | 10 +++++ projects/aca-content/assets/i18n/cs.json | 10 +++++ projects/aca-content/assets/i18n/da.json | 10 +++++ projects/aca-content/assets/i18n/de.json | 10 +++++ projects/aca-content/assets/i18n/en.json | 4 +- projects/aca-content/assets/i18n/es.json | 10 +++++ projects/aca-content/assets/i18n/fi.json | 10 +++++ projects/aca-content/assets/i18n/fr.json | 10 +++++ projects/aca-content/assets/i18n/it.json | 10 +++++ projects/aca-content/assets/i18n/ja.json | 10 +++++ projects/aca-content/assets/i18n/nb.json | 10 +++++ projects/aca-content/assets/i18n/nl.json | 10 +++++ projects/aca-content/assets/i18n/no.json | 10 +++++ projects/aca-content/assets/i18n/pl.json | 10 +++++ projects/aca-content/assets/i18n/pt-BR.json | 10 +++++ projects/aca-content/assets/i18n/ru.json | 10 +++++ projects/aca-content/assets/i18n/sv.json | 10 +++++ projects/aca-content/assets/i18n/zh-CN.json | 10 +++++ .../library-list/library-list.component.html | 30 +++++++------ .../library-list.component.spec.ts | 44 ++++++++++++++----- .../library-list/library-list.component.ts | 29 +++++++----- 22 files changed, 240 insertions(+), 43 deletions(-) diff --git a/projects/aca-content/assets/app.extensions.json b/projects/aca-content/assets/app.extensions.json index e8f6cd30f2..ba5eefd0aa 100644 --- a/projects/aca-content/assets/app.extensions.json +++ b/projects/aca-content/assets/app.extensions.json @@ -511,8 +511,7 @@ "component": "app.toolbar.toggleInfoDrawer", "rules": { "visible": [ - "app.selection.library", - "isLibraryManager" + "app.selection.library" ] } }, @@ -1590,8 +1589,7 @@ "component": "app.components.tabs.library.metadata", "rules": { "visible": [ - "app.selection.library", - "isLibraryManager" + "app.selection.library" ] } } diff --git a/projects/aca-content/assets/i18n/ar.json b/projects/aca-content/assets/i18n/ar.json index df49bde265..9b73e25950 100644 --- a/projects/aca-content/assets/i18n/ar.json +++ b/projects/aca-content/assets/i18n/ar.json @@ -133,6 +133,10 @@ "FAVORITE_LIBRARIES": { "TITLE": "لا توجد مكتبات مفضلة", "TEXT": "حدد مكتبة تريد العثور عليها بسهولة لاحقًا كمفضلة." + }, + "ALL_LIBRARIES":{ + "TITLE": "لم يتم العثور على مكتبة", + "TEXT": "أنشئ مكتبة جديدة للبدء" } }, "MENU": { @@ -149,6 +153,12 @@ "LABEL": "المكتبات المفضلة", "TOOLTIP": "الوصول إلى مكتباتي المفضلة" } + }, + "ALL_LIBRARIES": { + "TITLE": "كل المكتبات", + "SIDENAV_LINK": { + "LABEL": "كل المكتبات" + } } }, "ERRORS": { diff --git a/projects/aca-content/assets/i18n/cs.json b/projects/aca-content/assets/i18n/cs.json index 4c2444c6ba..c1849911e2 100644 --- a/projects/aca-content/assets/i18n/cs.json +++ b/projects/aca-content/assets/i18n/cs.json @@ -133,6 +133,10 @@ "FAVORITE_LIBRARIES": { "TITLE": "Žádné oblíbené knihovny souborů", "TEXT": "Pro snadnější dostupnost můžete označit vybrané knihovny za oblíbené." + }, + "ALL_LIBRARIES": { + "TITLE": "Nebyla nalezena žádná knihovna", + "TEXT": "Začněte vytvořením nové knihovny" } }, "MENU": { @@ -149,6 +153,12 @@ "LABEL": "Oblíbené knihovny", "TOOLTIP": "Přejít do oblíbených knihoven" } + }, + "ALL_LIBRARIES": { + "TITLE": "Všechny knihovny", + "SIDENAV_LINK": { + "LABEL": "Všechny knihovny" + } } }, "ERRORS": { diff --git a/projects/aca-content/assets/i18n/da.json b/projects/aca-content/assets/i18n/da.json index aea914d17e..33cdd6243a 100644 --- a/projects/aca-content/assets/i18n/da.json +++ b/projects/aca-content/assets/i18n/da.json @@ -133,6 +133,10 @@ "FAVORITE_LIBRARIES": { "TITLE": "Du har ikke angivet nogen biblioteker som favorit", "TEXT": "Angiv et bibliotek som favorit for at gøre det nemt at finde biblioteket igen." + }, + "ALL_LIBRARIES": { + "TITLE": "Intet bibliotek fundet", + "TEXT": "Opret et ny bibliotek, og kom i gang" } }, "MENU": { @@ -149,6 +153,12 @@ "LABEL": "Biblioteker, der er angivet som favorit", "TOOLTIP": "Adgang til de biblioteker, jeg har angivet som favorit" } + }, + "ALL_LIBRARIES": { + "TITLE": "Alle biblioteker", + "SIDENAV_LINK": { + "LABEL": "Alle biblioteker" + } } }, "ERRORS": { diff --git a/projects/aca-content/assets/i18n/de.json b/projects/aca-content/assets/i18n/de.json index e079936166..d26e5eb64c 100644 --- a/projects/aca-content/assets/i18n/de.json +++ b/projects/aca-content/assets/i18n/de.json @@ -133,6 +133,10 @@ "FAVORITE_LIBRARIES": { "TITLE": "Keine Bibliotheken als Favoriten markiert", "TEXT": "Markieren Sie eine Bibliothek als Favorit, um sie später leichter zu finden." + }, + "ALL_LIBRARIES": { + "TITLE": "Keine Bibliothek gefunden", + "TEXT": "Erstellen Sie zu Beginn eine neue Bibliothek." } }, "MENU": { @@ -149,6 +153,12 @@ "LABEL": "Als Favoriten markierte Bibliotheken", "TOOLTIP": "Auf meine als Favoriten markierten Bibliotheken zugreifen" } + }, + "ALL_LIBRARIES": { + "TITLE": "Alle Bibliotheken", + "SIDENAV_LINK": { + "LABEL": "Alle Bibliotheken" + } } }, "ERRORS": { diff --git a/projects/aca-content/assets/i18n/en.json b/projects/aca-content/assets/i18n/en.json index e14e0b49fd..83c1c87857 100644 --- a/projects/aca-content/assets/i18n/en.json +++ b/projects/aca-content/assets/i18n/en.json @@ -137,8 +137,8 @@ "TEXT": "Favorite a library that you want to find easily later." }, "ALL_LIBRARIES": { - "TITLE": "No Libraries", - "TEXT": "There are no libraries you can view." + "TITLE": "No library found", + "TEXT": "Create a new library to get started." } }, "MENU": { diff --git a/projects/aca-content/assets/i18n/es.json b/projects/aca-content/assets/i18n/es.json index 2c4cd81583..dd23bacad2 100644 --- a/projects/aca-content/assets/i18n/es.json +++ b/projects/aca-content/assets/i18n/es.json @@ -133,6 +133,10 @@ "FAVORITE_LIBRARIES": { "TITLE": "No hay bibliotecas favoritas", "TEXT": "Marcar como favorita una biblioteca para encontrarla más rápidamente." + }, + "ALL_LIBRARIES": { + "TITLE": "No se ha encontrado ninguna biblioteca", + "TEXT ": "Crear una biblioteca nueva para empezar" } }, "MENU": { @@ -149,6 +153,12 @@ "LABEL": "Bibliotecas favoritas", "TOOLTIP": "Acceder a mis bibliotecas favoritas" } + }, + "ALL_LIBRARIES": { + "TITLE": "Todas las bibliotecas", + "SIDENAV_LINK": { + "LABEL": "Todas las bibliotecas" + } } }, "ERRORS": { diff --git a/projects/aca-content/assets/i18n/fi.json b/projects/aca-content/assets/i18n/fi.json index c71a837089..366468b102 100644 --- a/projects/aca-content/assets/i18n/fi.json +++ b/projects/aca-content/assets/i18n/fi.json @@ -133,6 +133,10 @@ "FAVORITE_LIBRARIES": { "TITLE": "Ei suosikkikirjastoja", "TEXT": "Voit lisätä suosikkeihin kirjastoja, jotka haluat löytää helposti myöhemmin." + }, + "ALL_LIBRARIES": { + "TITLE": "Yhtään kirjastoa ei löytynyt", + "TEXT": "Luo uusi kirjasto aloittaaksesi" } }, "MENU": { @@ -149,6 +153,12 @@ "LABEL": "Suosikkikirjastot", "TOOLTIP": "Siirry omiin suosikkikirjastoihin" } + }, + "ALL_LIBRARIES": { + "TITLE": "Kaikki kirjastot", + "SIDENAV_LINK": { + "LABEL": "Kaikki kirjastot" + } } }, "ERRORS": { diff --git a/projects/aca-content/assets/i18n/fr.json b/projects/aca-content/assets/i18n/fr.json index e8b9ea46d1..b78a23081b 100644 --- a/projects/aca-content/assets/i18n/fr.json +++ b/projects/aca-content/assets/i18n/fr.json @@ -133,6 +133,10 @@ "FAVORITE_LIBRARIES": { "TITLE": "Pas de bibliothèques favorites", "TEXT": "Ajouter aux favoris une bibliothèque pour la retrouver facilement par la suite." + }, + "ALL_LIBRARIES": { + "TITLE": "Aucune bibliothèque trouvée", + "TEXT": "Créez une nouvelle bibliothèque pour commencer" } }, "MENU": { @@ -149,6 +153,12 @@ "LABEL": "Bibliothèques favorites", "TOOLTIP": "Accéder à mes bibliothèques favorites" } + }, + "ALL_LIBRARIES": { + "TITLE": "Toutes les bibliothèques", + "SIDENAV_LINK": { + "LABEL": "Toutes les bibliothèques" + } } }, "ERRORS": { diff --git a/projects/aca-content/assets/i18n/it.json b/projects/aca-content/assets/i18n/it.json index 3d25f5bc73..571d28d83f 100644 --- a/projects/aca-content/assets/i18n/it.json +++ b/projects/aca-content/assets/i18n/it.json @@ -133,6 +133,10 @@ "FAVORITE_LIBRARIES": { "TITLE": "Nessuna libreria preferita", "TEXT": "Imposta una libreria come preferita per trovarla facilmente in seguito." + }, + "ALL_LIBRARIES": { + "TITLE": "Nessuna libreria trovata", + "TEXT": "Crea una nuova libreria per iniziare" } }, "MENU": { @@ -149,6 +153,12 @@ "LABEL": "Librerie preferite", "TOOLTIP": "Accedi alle librerie preferite" } + }, + "ALL_LIBRARIES": { + "TITLE": "Tutte le librerie", + "SIDENAV_LINK": { + "LABEL": "Tutte le librerie" + } } }, "ERRORS": { diff --git a/projects/aca-content/assets/i18n/ja.json b/projects/aca-content/assets/i18n/ja.json index 073ff80be7..e065bb5f15 100644 --- a/projects/aca-content/assets/i18n/ja.json +++ b/projects/aca-content/assets/i18n/ja.json @@ -133,6 +133,10 @@ "FAVORITE_LIBRARIES": { "TITLE": "お気に入りのライブラリはありません", "TEXT": "後で簡単に見つけられるようにライブラリをお気に入りに追加します。" + }, + "ALL_LIBRARIES": { + "TITLE": "ライブラリが見つかりません", + "TEXT": "最初に新しいライブラリを作成してください" } }, "MENU": { @@ -149,6 +153,12 @@ "LABEL": "お気に入りのライブラリ", "TOOLTIP": "お気に入りのライブラリにアクセス" } + }, + "ALL_LIBRARIES": { + "TITLE": "すべてのライブラリ", + "SIDENAV_LINK": { + "LABEL": "すべてのライブラリ" + } } }, "ERRORS": { diff --git a/projects/aca-content/assets/i18n/nb.json b/projects/aca-content/assets/i18n/nb.json index 24f5fb0e7b..ce867053ea 100644 --- a/projects/aca-content/assets/i18n/nb.json +++ b/projects/aca-content/assets/i18n/nb.json @@ -133,6 +133,10 @@ "FAVORITE_LIBRARIES": { "TITLE": "Ingen favorittbiblioteker", "TEXT": "Legg et bibliotek til som favoritt, slik at du lett kan finne det senere." + }, + "ALL_LIBRARIES": { + "TITLE": "Fant ingen biblioteker", + "TEXT": "Opprett et nytt bibliotek for å komme i gang" } }, "MENU": { @@ -149,6 +153,12 @@ "LABEL": "Favorittbiblioteker", "TOOLTIP": "Gå inn i mine favorittbiblioteker" } + }, + "ALL_LIBRARIES": { + "TITLE": "Alle biblioteker", + "SIDENAV_LINK": { + "LABEL": "Alle biblioteker" + } } } }, diff --git a/projects/aca-content/assets/i18n/nl.json b/projects/aca-content/assets/i18n/nl.json index 0888c7a167..2407d244bd 100644 --- a/projects/aca-content/assets/i18n/nl.json +++ b/projects/aca-content/assets/i18n/nl.json @@ -133,6 +133,10 @@ "FAVORITE_LIBRARIES": { "TITLE": "Geen favoriete bibliotheken", "TEXT": "Voeg een bibliotheek toe aan uw favorieten zodat u deze later gemakkelijk kunt vinden." + }, + "ALL_LIBRARIES": { + "TITLE": "Geen bibliotheek gevonden", + "TEXT": "Maak een nieuwe bibliotheek om aan de slag te gaan" } }, "MENU": { @@ -149,6 +153,12 @@ "LABEL": "Favoriete bibliotheken", "TOOLTIP": "Toegang krijgen tot mijn favoriete bibliotheken" } + }, + "ALL_LIBRARIES": { + "TITLE": "Alle bibliotheken", + "SIDENAV_LINK": { + "LABEL": "Alle bibliotheken" + } } }, "ERRORS": { diff --git a/projects/aca-content/assets/i18n/no.json b/projects/aca-content/assets/i18n/no.json index 1bcf46ff37..e987e6f7af 100644 --- a/projects/aca-content/assets/i18n/no.json +++ b/projects/aca-content/assets/i18n/no.json @@ -133,6 +133,10 @@ "FAVORITE_LIBRARIES": { "TITLE": "Ingen favorittbiblioteker", "TEXT": "Merk et bibliotek som favoritt for å enkelt finne det senere." + }, + "ALL_LIBRARIES": { + "TITLE": "Fant ingen bibliotek", + "TEXT": "Opprett et nytt bibliotek for å komme i gang" } }, "MENU": { @@ -149,6 +153,12 @@ "LABEL": "Favorittbiblioteker", "TOOLTIP": "Få tilgang til mine favorittbiblioteker" } + }, + "ALL_LIBRARIES": { + "TITLE": "Alle biblioteker", + "SIDENAV_LINK": { + "LABEL": "Alle biblioteker" + } } }, "ERRORS": { diff --git a/projects/aca-content/assets/i18n/pl.json b/projects/aca-content/assets/i18n/pl.json index ea9f33b948..f0dcc388c8 100644 --- a/projects/aca-content/assets/i18n/pl.json +++ b/projects/aca-content/assets/i18n/pl.json @@ -133,6 +133,10 @@ "FAVORITE_LIBRARIES": { "TITLE": "Brak ulubionych bibliotek", "TEXT": "Dodaj do ulubionych bibliotekę, którą później chcesz łatwo znaleźć." + }, + "ALL_LIBRARIES": { + "TITLE": "Nie znaleziono biblioteki", + "TEXT": "Utwórz nową bibliotekę, aby rozpocząć" } }, "MENU": { @@ -149,6 +153,12 @@ "LABEL": "Ulubione biblioteki", "TOOLTIP": "Uzyskaj dostęp do moich ulubionych bibliotek" } + }, + "ALL_LIBRARIES": { + "TITLE": "Wszystkie biblioteki", + "SIDENAV_LINK": { + "LABEL": "Wszystkie biblioteki" + } } }, "ERRORS": { diff --git a/projects/aca-content/assets/i18n/pt-BR.json b/projects/aca-content/assets/i18n/pt-BR.json index 51b13c8e27..2da5efbed7 100644 --- a/projects/aca-content/assets/i18n/pt-BR.json +++ b/projects/aca-content/assets/i18n/pt-BR.json @@ -133,6 +133,10 @@ "FAVORITE_LIBRARIES": { "TITLE": "Nenhuma biblioteca favorita", "TEXT": "Adicione uma biblioteca como favorita para facilitar o acesso a ela." + }, + "ALL_LIBRARIES": { + "TITLE": "Nenhuma biblioteca encontrada", + "TEXT": "Crie uma nova biblioteca para iniciar" } }, "MENU": { @@ -149,6 +153,12 @@ "LABEL": "Bibliotecas favoritas", "TOOLTIP": "Acessar minhas bibliotecas favoritas" } + }, + "ALL_LIBRARIES": { + "TITLE": "Todas as bibliotecas", + "SIDENAV_LINK": { + "LABEL": "Todas as bibliotecas" + } } }, "ERRORS": { diff --git a/projects/aca-content/assets/i18n/ru.json b/projects/aca-content/assets/i18n/ru.json index b90599efb4..05cb887388 100644 --- a/projects/aca-content/assets/i18n/ru.json +++ b/projects/aca-content/assets/i18n/ru.json @@ -133,6 +133,10 @@ "FAVORITE_LIBRARIES": { "TITLE": "Нет избранных библиотек", "TEXT": "Добавляйте библиотеки в избранные, чтобы быстро находить их, когда они понадобятся снова." + }, + "ALL_LIBRARIES": { + "TITLE": "Библиотеки не найдены", + "TEXT": "Создайте новую библиотеку, чтобы приступить к работе" } }, "MENU": { @@ -149,6 +153,12 @@ "LABEL": "Избранные библиотеки", "TOOLTIP": "Доступ к моим избранным библиотекам" } + }, + "ALL_LIBRARIES": { + "TITLE": "Все библиотеки", + "SIDENAV_LINK": { + "LABEL": "Все библиотеки" + } } }, "ERRORS": { diff --git a/projects/aca-content/assets/i18n/sv.json b/projects/aca-content/assets/i18n/sv.json index 6eb4bae178..540e68a808 100644 --- a/projects/aca-content/assets/i18n/sv.json +++ b/projects/aca-content/assets/i18n/sv.json @@ -133,6 +133,10 @@ "FAVORITE_LIBRARIES": { "TITLE": "Inga favoritbibliotek", "TEXT": "Favoritmarkera ett bibliotek som du enkelt vill hitta senare." + }, + "ALL_LIBRARIES": { + "TITLE": "Inget bibliotek hittades", + "TEXT": "Skapa ett nytt bibliotek för att komma igång" } }, "MENU": { @@ -149,6 +153,12 @@ "LABEL": "Favoritbibliotek", "TOOLTIP": "Kom åt mina favoritbibliotek" } + }, + "ALL_LIBRARIES": { + "TITLE": "Alla bibliotek", + "SIDENAV_LINK": { + "LABEL": "Alla bibliotek" + } } }, "ERRORS": { diff --git a/projects/aca-content/assets/i18n/zh-CN.json b/projects/aca-content/assets/i18n/zh-CN.json index 84e491eeba..b88292b8f3 100644 --- a/projects/aca-content/assets/i18n/zh-CN.json +++ b/projects/aca-content/assets/i18n/zh-CN.json @@ -133,6 +133,10 @@ "FAVORITE_LIBRARIES": { "TITLE": "无收藏库", "TEXT": "收藏您希望以后轻松查找的库" + }, + "ALL_LIBRARIES": { + "TITLE": "未发现任何库", + "TEXT": "创建新库以开始使用" } }, "MENU": { @@ -149,6 +153,12 @@ "LABEL": "收藏库", "TOOLTIP": "访问我的收藏库" } + }, + "ALL_LIBRARIES": { + "TITLE": "所有库", + "SIDENAV_LINK": { + "LABEL": "所有库" + } } }, "ERRORS": { diff --git a/projects/aca-content/src/lib/components/library-list/library-list.component.html b/projects/aca-content/src/lib/components/library-list/library-list.component.html index 692868f8a4..031d2c1e32 100644 --- a/projects/aca-content/src/lib/components/library-list/library-list.component.html +++ b/projects/aca-content/src/lib/components/library-list/library-list.component.html @@ -1,7 +1,11 @@

- {{ (selectedRowItemsCount < 1 ? 'APP.BROWSE.LIBRARIES.MENU.ALL_LIBRARIES.TITLE' : 'APP.HEADER.SELECTED') | translate: { count: selectedRowItemsCount } }} + @if (selectedRowItemsCount < 1) { + {{ 'APP.BROWSE.LIBRARIES.MENU.ALL_LIBRARIES.TITLE' | translate: { count: selectedRowItemsCount } }} + } @else { + {{ 'APP.HEADER.SELECTED' | translate: { count: selectedRowItemsCount } }} + }

@@ -37,8 +41,8 @@

- - + @for (column of columns; track column.id) { + @if (column.template && !(column.desktopOnly && isSmallScreen)) { - - - + } @else if (!column.template && !(column.desktopOnly && isSmallScreen)) { [isHidden]="column.isHidden" [sortingKey]="column.sortingKey || column.key" /> - - + } + } @@ -81,14 +83,14 @@

[target]="documentList" [pagination]="pagination" (changePageSize)="onChangePageSize($event)" - (changePageNumber)="onChange($event)" - (nextPage)="onChange($event)" - (prevPage)="onChange($event)" + (changePageNumber)="getList($event)" + (nextPage)="getList($event)" + (prevPage)="getList($event)" /> -
- -
+ @if (infoDrawerOpened$ | async) { + + } diff --git a/projects/aca-content/src/lib/components/library-list/library-list.component.spec.ts b/projects/aca-content/src/lib/components/library-list/library-list.component.spec.ts index dd6515c298..83c539b0df 100644 --- a/projects/aca-content/src/lib/components/library-list/library-list.component.spec.ts +++ b/projects/aca-content/src/lib/components/library-list/library-list.component.spec.ts @@ -24,14 +24,14 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { Router } from '@angular/router'; -import { UnitTestingUtils, UserPreferencesService } from '@alfresco/adf-core'; +import { AppConfigService, UnitTestingUtils, UserPreferencesService } from '@alfresco/adf-core'; import { DocumentListComponent, SitesService } from '@alfresco/adf-content-services'; import { LibraryListComponent } from './library-list.component'; import { AppTestingModule } from '../../testing/app-testing.module'; import { AppHookService } from '@alfresco/aca-shared'; import { provideEffects } from '@ngrx/effects'; import { RouterEffects } from '@alfresco/aca-shared/store'; -import { of, throwError } from 'rxjs'; +import { Observable, of, throwError } from 'rxjs'; import { LibraryEffects } from '../../store/effects'; import { getTitleElementText } from '../../testing/test-utils'; import { MatSnackBarModule } from '@angular/material/snack-bar'; @@ -44,20 +44,24 @@ describe('LibraryListComponent', () => { let sitesService: SitesService; let router: Router; let appHookService: AppHookService; - let getSitesSpy: jasmine.Spy; + let getSitesSpy: jasmine.Spy<(options?: any) => Observable>; let unitTestingUtils: UnitTestingUtils; + let appConfig: AppConfigService; + let getLegalHoldsEnabledSpy: jasmine.Spy<(key: string, defaultValue?: string | boolean) => string | boolean>; const paging: SitePaging = { list: { entries: [ - { entry: { id: '1', guid: '1', title: 'Library 1', visibility: 'public' } }, + { entry: { id: '1', guid: '1', title: 'Library 1', visibility: 'public', preset: 'site-dashboard' } }, { entry: { id: '2', guid: '2', title: 'Library 2', visibility: 'private' } } ], pagination: { count: 25, skipCount: 0 } } }; - beforeEach(async () => { + const whereOpt = `(preset='site-dashboard')`; + + beforeEach(() => { TestBed.configureTestingModule({ imports: [AppTestingModule, LibraryListComponent, MatSnackBarModule], providers: [provideEffects([RouterEffects, LibraryEffects])] @@ -70,10 +74,13 @@ describe('LibraryListComponent', () => { sitesService = TestBed.inject(SitesService); userPreference = TestBed.inject(UserPreferencesService); appHookService = TestBed.inject(AppHookService); + appConfig = TestBed.inject(AppConfigService); router = TestBed.inject(Router); getSitesSpy = spyOn(sitesService, 'getSites'); getSitesSpy.and.returnValue(of(paging)); + getLegalHoldsEnabledSpy = spyOn(appConfig, 'get'); + getLegalHoldsEnabledSpy.and.returnValue(false); fixture.detectChanges(); }); @@ -86,15 +93,15 @@ describe('LibraryListComponent', () => { it('should get data with user preference pagination size', () => { userPreference.paginationSize = 1; component.ngOnInit(); - expect(sitesService.getSites).toHaveBeenCalledWith({ maxItems: userPreference.paginationSize }); + expect(sitesService.getSites).toHaveBeenCalledWith({ maxItems: 1, where: whereOpt }); }); it('should set data on error', () => { getSitesSpy.and.returnValue(throwError(() => 'error')); component.ngOnInit(); - expect(component.list).toBe(null); - expect(component.pagination).toBe(null); + expect(component.list).toBeNull(); + expect(component.pagination).toBeNull(); expect(component.isLoading).toBe(false); }); @@ -114,13 +121,26 @@ describe('LibraryListComponent', () => { }); }); + describe('Legal Holds flag', () => { + it('should call getSites with where option when legal holds disabled', () => { + component.ngOnInit(); + expect(sitesService.getSites).toHaveBeenCalledWith(jasmine.objectContaining({ where: whereOpt })); + }); + + it('should not call getSites with where option when legal holds enabled', () => { + getLegalHoldsEnabledSpy.and.returnValue(true); + component.ngOnInit(); + expect(sitesService.getSites).toHaveBeenCalledWith(jasmine.objectContaining({ where: undefined })); + }); + }); + describe('Node navigation', () => { it('should not navigate when node is null or missing guid', () => { spyOn(router, 'navigate').and.stub(); component.navigateTo(null); expect(router.navigate).not.toHaveBeenCalled(); - component.navigateTo({ entry: {} } as any); + component.navigateTo({ entry: {} } as SiteEntry); expect(router.navigate).not.toHaveBeenCalled(); }); @@ -177,13 +197,13 @@ describe('LibraryListComponent', () => { }; it('should get list with pagination data onChange event', () => { - component.onChange(pagination); - expect(sitesService.getSites).toHaveBeenCalledWith(pagination); + component.getList(pagination); + expect(sitesService.getSites).toHaveBeenCalledWith({ ...pagination, where: whereOpt }); }); it('should get list with pagination data onChangePageSize event', () => { component.onChangePageSize(pagination); - expect(sitesService.getSites).toHaveBeenCalledWith(pagination); + expect(sitesService.getSites).toHaveBeenCalledWith({ ...pagination, where: whereOpt }); }); it('should set preference page size onChangePageSize event', () => { diff --git a/projects/aca-content/src/lib/components/library-list/library-list.component.ts b/projects/aca-content/src/lib/components/library-list/library-list.component.ts index ef8af0145a..070450a579 100644 --- a/projects/aca-content/src/lib/components/library-list/library-list.component.ts +++ b/projects/aca-content/src/lib/components/library-list/library-list.component.ts @@ -34,6 +34,7 @@ import { } from '@alfresco/aca-shared'; import { NavigateLibraryAction } from '@alfresco/aca-shared/store'; import { + AppConfigService, CustomEmptyContentTemplateDirective, DataColumnComponent, DataColumnListComponent, @@ -70,7 +71,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; encapsulation: ViewEncapsulation.None }) export class LibraryListComponent extends PageComponent implements OnInit { - pagination: Pagination = new Pagination({ + pagination = new Pagination({ skipCount: 0, maxItems: 25, totalItems: 0 @@ -79,11 +80,14 @@ export class LibraryListComponent extends PageComponent implements OnInit { list: SitePaging; columns: DocumentListPresetRef[] = []; + private legalHoldsEnabled = false; + constructor( - private appHookService: AppHookService, - private preferences: UserPreferencesService, - private changeDetectorRef: ChangeDetectorRef, - private sitesService: SitesService + private readonly appHookService: AppHookService, + private readonly preferences: UserPreferencesService, + private readonly changeDetectorRef: ChangeDetectorRef, + private readonly sitesService: SitesService, + private readonly appConfig: AppConfigService ) { super(); } @@ -91,6 +95,9 @@ export class LibraryListComponent extends PageComponent implements OnInit { ngOnInit() { super.ngOnInit(); + const legalHoldFlag = this.appConfig.get('plugins.legalHoldEnabled', false); + this.legalHoldsEnabled = legalHoldFlag === true || legalHoldFlag === 'true'; + this.getList({ maxItems: this.preferences.paginationSize }); merge(this.appHookService.libraryDeleted, this.appHookService.libraryUpdated, this.appHookService.libraryJoined, this.appHookService.libraryLeft) @@ -114,13 +121,13 @@ export class LibraryListComponent extends PageComponent implements OnInit { this.getList(pagination); } - onChange(pagination: Pagination) { - this.getList(pagination); - } - - private getList(pagination: Pagination) { + getList(pagination: Pagination) { this.isLoading = true; - this.sitesService.getSites(pagination).subscribe({ + let where: string | undefined; + if (!this.legalHoldsEnabled) { + where = `(preset='site-dashboard')`; + } + this.sitesService.getSites({ ...pagination, where: where }).subscribe({ next: (libraryList: SitePaging) => { this.list = libraryList; this.pagination = libraryList.list.pagination; From ec9232716ef2582cfec8892d2c767c55151c6f68 Mon Sep 17 00:00:00 2001 From: g-jaskowski Date: Fri, 10 Oct 2025 18:12:19 +0200 Subject: [PATCH 3/7] [ACS-10165] fix column tracking --- .../src/lib/components/library-list/library-list.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/aca-content/src/lib/components/library-list/library-list.component.html b/projects/aca-content/src/lib/components/library-list/library-list.component.html index 031d2c1e32..b65649aa76 100644 --- a/projects/aca-content/src/lib/components/library-list/library-list.component.html +++ b/projects/aca-content/src/lib/components/library-list/library-list.component.html @@ -41,7 +41,7 @@

- @for (column of columns; track column.id) { + @for (column of columns; track trackByColumnId($index, column)) { @if (column.template && !(column.desktopOnly && isSmallScreen)) { Date: Wed, 15 Oct 2025 16:41:53 +0200 Subject: [PATCH 4/7] [ACS-10165] remove flag --- .../library-list.component.spec.ts | 28 +++---------------- .../library-list/library-list.component.ts | 17 ++--------- 2 files changed, 7 insertions(+), 38 deletions(-) diff --git a/projects/aca-content/src/lib/components/library-list/library-list.component.spec.ts b/projects/aca-content/src/lib/components/library-list/library-list.component.spec.ts index 83c539b0df..66c3184190 100644 --- a/projects/aca-content/src/lib/components/library-list/library-list.component.spec.ts +++ b/projects/aca-content/src/lib/components/library-list/library-list.component.spec.ts @@ -24,7 +24,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { Router } from '@angular/router'; -import { AppConfigService, UnitTestingUtils, UserPreferencesService } from '@alfresco/adf-core'; +import { UnitTestingUtils, UserPreferencesService } from '@alfresco/adf-core'; import { DocumentListComponent, SitesService } from '@alfresco/adf-content-services'; import { LibraryListComponent } from './library-list.component'; import { AppTestingModule } from '../../testing/app-testing.module'; @@ -46,8 +46,6 @@ describe('LibraryListComponent', () => { let appHookService: AppHookService; let getSitesSpy: jasmine.Spy<(options?: any) => Observable>; let unitTestingUtils: UnitTestingUtils; - let appConfig: AppConfigService; - let getLegalHoldsEnabledSpy: jasmine.Spy<(key: string, defaultValue?: string | boolean) => string | boolean>; const paging: SitePaging = { list: { @@ -59,8 +57,6 @@ describe('LibraryListComponent', () => { } }; - const whereOpt = `(preset='site-dashboard')`; - beforeEach(() => { TestBed.configureTestingModule({ imports: [AppTestingModule, LibraryListComponent, MatSnackBarModule], @@ -74,13 +70,10 @@ describe('LibraryListComponent', () => { sitesService = TestBed.inject(SitesService); userPreference = TestBed.inject(UserPreferencesService); appHookService = TestBed.inject(AppHookService); - appConfig = TestBed.inject(AppConfigService); router = TestBed.inject(Router); getSitesSpy = spyOn(sitesService, 'getSites'); getSitesSpy.and.returnValue(of(paging)); - getLegalHoldsEnabledSpy = spyOn(appConfig, 'get'); - getLegalHoldsEnabledSpy.and.returnValue(false); fixture.detectChanges(); }); @@ -93,7 +86,7 @@ describe('LibraryListComponent', () => { it('should get data with user preference pagination size', () => { userPreference.paginationSize = 1; component.ngOnInit(); - expect(sitesService.getSites).toHaveBeenCalledWith({ maxItems: 1, where: whereOpt }); + expect(sitesService.getSites).toHaveBeenCalledWith({ maxItems: 1 }); }); it('should set data on error', () => { @@ -121,19 +114,6 @@ describe('LibraryListComponent', () => { }); }); - describe('Legal Holds flag', () => { - it('should call getSites with where option when legal holds disabled', () => { - component.ngOnInit(); - expect(sitesService.getSites).toHaveBeenCalledWith(jasmine.objectContaining({ where: whereOpt })); - }); - - it('should not call getSites with where option when legal holds enabled', () => { - getLegalHoldsEnabledSpy.and.returnValue(true); - component.ngOnInit(); - expect(sitesService.getSites).toHaveBeenCalledWith(jasmine.objectContaining({ where: undefined })); - }); - }); - describe('Node navigation', () => { it('should not navigate when node is null or missing guid', () => { spyOn(router, 'navigate').and.stub(); @@ -198,12 +178,12 @@ describe('LibraryListComponent', () => { it('should get list with pagination data onChange event', () => { component.getList(pagination); - expect(sitesService.getSites).toHaveBeenCalledWith({ ...pagination, where: whereOpt }); + expect(sitesService.getSites).toHaveBeenCalledWith(pagination); }); it('should get list with pagination data onChangePageSize event', () => { component.onChangePageSize(pagination); - expect(sitesService.getSites).toHaveBeenCalledWith({ ...pagination, where: whereOpt }); + expect(sitesService.getSites).toHaveBeenCalledWith(pagination); }); it('should set preference page size onChangePageSize event', () => { diff --git a/projects/aca-content/src/lib/components/library-list/library-list.component.ts b/projects/aca-content/src/lib/components/library-list/library-list.component.ts index 070450a579..09894487d7 100644 --- a/projects/aca-content/src/lib/components/library-list/library-list.component.ts +++ b/projects/aca-content/src/lib/components/library-list/library-list.component.ts @@ -34,7 +34,6 @@ import { } from '@alfresco/aca-shared'; import { NavigateLibraryAction } from '@alfresco/aca-shared/store'; import { - AppConfigService, CustomEmptyContentTemplateDirective, DataColumnComponent, DataColumnListComponent, @@ -80,14 +79,11 @@ export class LibraryListComponent extends PageComponent implements OnInit { list: SitePaging; columns: DocumentListPresetRef[] = []; - private legalHoldsEnabled = false; - constructor( private readonly appHookService: AppHookService, private readonly preferences: UserPreferencesService, private readonly changeDetectorRef: ChangeDetectorRef, - private readonly sitesService: SitesService, - private readonly appConfig: AppConfigService + private readonly sitesService: SitesService ) { super(); } @@ -95,9 +91,6 @@ export class LibraryListComponent extends PageComponent implements OnInit { ngOnInit() { super.ngOnInit(); - const legalHoldFlag = this.appConfig.get('plugins.legalHoldEnabled', false); - this.legalHoldsEnabled = legalHoldFlag === true || legalHoldFlag === 'true'; - this.getList({ maxItems: this.preferences.paginationSize }); merge(this.appHookService.libraryDeleted, this.appHookService.libraryUpdated, this.appHookService.libraryJoined, this.appHookService.libraryLeft) @@ -108,7 +101,7 @@ export class LibraryListComponent extends PageComponent implements OnInit { navigateTo(node: SiteEntry) { if (node?.entry?.guid) { - this.store.dispatch(new NavigateLibraryAction(node.entry.guid)); + this.store.dispatch(new NavigateLibraryAction(node.entry)); } } @@ -123,11 +116,7 @@ export class LibraryListComponent extends PageComponent implements OnInit { getList(pagination: Pagination) { this.isLoading = true; - let where: string | undefined; - if (!this.legalHoldsEnabled) { - where = `(preset='site-dashboard')`; - } - this.sitesService.getSites({ ...pagination, where: where }).subscribe({ + this.sitesService.getSites(pagination).subscribe({ next: (libraryList: SitePaging) => { this.list = libraryList; this.pagination = libraryList.list.pagination; From 4f6b2e6804fb66bd37a8222fd65c6c4c1dd8130d Mon Sep 17 00:00:00 2001 From: g-jaskowski Date: Wed, 22 Oct 2025 10:30:05 +0200 Subject: [PATCH 5/7] [ACS-10165] create base component for library pages --- .../favorite-libraries.component.html | 108 ++----------- .../favorite-libraries.component.spec.ts | 152 ++++++------------ .../favorite-libraries.component.ts | 71 ++------ .../libraries-base.component.html | 96 +++++++++++ .../libraries-base.component.spec.ts | 145 +++++++++++++++++ .../libraries-base.component.ts | 102 ++++++++++++ .../libraries/libraries.component.html | 93 +---------- .../libraries/libraries.component.spec.ts | 67 ++++---- .../libraries/libraries.component.ts | 60 +------ .../library-list/library-list.component.html | 109 ++----------- .../library-list.component.spec.ts | 140 +++++----------- .../library-list/library-list.component.ts | 72 ++------- .../src/lib/mock/libraries-mock.ts | 66 ++++++++ 13 files changed, 597 insertions(+), 684 deletions(-) create mode 100644 projects/aca-content/src/lib/components/libraries-base/libraries-base.component.html create mode 100644 projects/aca-content/src/lib/components/libraries-base/libraries-base.component.spec.ts create mode 100644 projects/aca-content/src/lib/components/libraries-base/libraries-base.component.ts create mode 100644 projects/aca-content/src/lib/mock/libraries-mock.ts diff --git a/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.html b/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.html index fcf74cab88..693f72c70a 100644 --- a/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.html +++ b/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.html @@ -1,94 +1,14 @@ - -
-

- {{ (selectedRowItemsCount < 1 ? 'APP.BROWSE.LIBRARIES.MENU.FAVORITE_LIBRARIES.TITLE' : 'APP.HEADER.SELECTED') | translate: { count: selectedRowItemsCount } }} -

- -
- -
-
- - - - - - - - - - - - - - - - - - - - - - - -
- -
- -
-
-
+ diff --git a/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.spec.ts b/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.spec.ts index 47d211d296..3b8528bfe3 100644 --- a/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.spec.ts +++ b/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.spec.ts @@ -23,189 +23,129 @@ */ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { Router } from '@angular/router'; import { UserPreferencesService } from '@alfresco/adf-core'; -import { AlfrescoApiService } from '@alfresco/adf-content-services'; import { FavoriteLibrariesComponent } from './favorite-libraries.component'; import { AppTestingModule } from '../../testing/app-testing.module'; -import { AppHookService, ContentApiService } from '@alfresco/aca-shared'; +import { AppExtensionService, AppHookService, ContentApiService } from '@alfresco/aca-shared'; import { provideEffects } from '@ngrx/effects'; -import { RouterEffects } from '@alfresco/aca-shared/store'; import { of, throwError } from 'rxjs'; import { LibraryEffects } from '../../store/effects'; import { NodeEntry } from '@alfresco/js-api'; -import { getTitleElementText } from '../../testing/test-utils'; import { MatSnackBarModule } from '@angular/material/snack-bar'; -import { SiteEntry } from '@alfresco/js-api/typings'; +import { libraryColumnsPresetMock, favoriteLibrariesMock, libraryPaginationMock } from '../../mock/libraries-mock'; describe('FavoriteLibrariesComponent', () => { let fixture: ComponentFixture; let component: FavoriteLibrariesComponent; - let alfrescoApi: AlfrescoApiService; let userPreference: UserPreferencesService; let contentApiService: ContentApiService; - let router: Router; - let page; let appHookService: AppHookService; - - beforeEach(() => { - page = { - list: { - entries: [{ entry: { id: 1 } }, { entry: { id: 2 } }], - pagination: { data: 'data' } - } - }; - }); + let appExtensionService: AppExtensionService; beforeEach(() => { TestBed.configureTestingModule({ imports: [AppTestingModule, FavoriteLibrariesComponent, MatSnackBarModule], - providers: [provideEffects([RouterEffects, LibraryEffects])] + providers: [provideEffects([LibraryEffects])] }); fixture = TestBed.createComponent(FavoriteLibrariesComponent); component = fixture.componentInstance; - alfrescoApi = TestBed.inject(AlfrescoApiService); contentApiService = TestBed.inject(ContentApiService); userPreference = TestBed.inject(UserPreferencesService); appHookService = TestBed.inject(AppHookService); - alfrescoApi.reset(); - router = TestBed.inject(Router); + appExtensionService = TestBed.inject(AppExtensionService); spyOn(contentApiService, 'getNode').and.returnValue(of({ entry: { id: 'libraryId' } } as NodeEntry)); }); - describe('on initialization', () => { - it('should set data', () => { - spyOn(contentApiService, 'getFavoriteLibraries').and.returnValue(of(page)); - fixture.detectChanges(); - - expect(component.list).toBe(page); - expect(component.pagination).toBe(page.list.pagination); - }); - - it('should get data with user preference pagination size', () => { - userPreference.paginationSize = 1; - spyOn(contentApiService, 'getFavoriteLibraries').and.returnValue(of(page)); + it('should set data', () => { + spyOn(contentApiService, 'getFavoriteLibraries').and.returnValue(of(favoriteLibrariesMock)); + fixture.detectChanges(); - fixture.detectChanges(); + expect(component.list).toBe(favoriteLibrariesMock); + expect(component.pagination).toBe(favoriteLibrariesMock.list.pagination); + }); - expect(contentApiService.getFavoriteLibraries).toHaveBeenCalledWith('-me-', { - maxItems: userPreference.paginationSize - }); - }); + it('should get data with user preference pagination size', () => { + userPreference.paginationSize = 1; + spyOn(contentApiService, 'getFavoriteLibraries').and.returnValue(of(favoriteLibrariesMock)); - it('should set data on error', () => { - spyOn(contentApiService, 'getFavoriteLibraries').and.returnValue(throwError('error')); - fixture.detectChanges(); + fixture.detectChanges(); - expect(component.list).toBe(null); - expect(component.pagination).toBe(null); - expect(component.isLoading).toBe(false); + expect(contentApiService.getFavoriteLibraries).toHaveBeenCalledWith('-me-', { + maxItems: userPreference.paginationSize }); + }); - it('should set title based on selectedRowItemsCount', () => { - fixture.detectChanges(); - - expect(getTitleElementText(fixture)).toBe('APP.BROWSE.LIBRARIES.MENU.FAVORITE_LIBRARIES.TITLE'); + it('should set data on error', () => { + spyOn(contentApiService, 'getFavoriteLibraries').and.returnValue(throwError('error')); + fixture.detectChanges(); - component.selectedRowItemsCount = 5; - fixture.detectChanges(); - - expect(getTitleElementText(fixture)).toBe('APP.HEADER.SELECTED'); - }); + expect(component.list).toBe(null); + expect(component.pagination).toBe(null); + expect(component.isLoading).toBe(false); }); - describe('Node navigation', () => { - it('does not navigate when id is not passed', () => { - spyOn(router, 'navigate').and.stub(); - component.navigateTo(null); - - expect(router.navigate).not.toHaveBeenCalled(); - }); + it('should set columns from extensions on init', () => { + appExtensionService.documentListPresets.libraries = libraryColumnsPresetMock; + fixture.detectChanges(); + expect(component.columns).toEqual(appExtensionService.documentListPresets.favoriteLibraries); + }); - it('does not navigate when id is not passed', () => { - spyOn(router, 'navigate').and.stub(); - component.navigateTo({ - entry: { - guid: 'test-guid', - visibility: 'PUBLIC', - role: 'SiteConsumer' - } - } as SiteEntry); - - expect(router.navigate).toHaveBeenCalledWith(['favorite/libraries', 'libraryId']); - }); + it('should handle no columns preset in extensions', () => { + appExtensionService.documentListPresets.favoriteLibraries = undefined; + component.ngOnInit(); + expect(component.columns.length).toBe(0); }); - describe('Reload on actions', () => { + describe('Library hooks', () => { beforeEach(() => { - spyOn(contentApiService, 'getFavoriteLibraries').and.returnValue(of(page)); + spyOn(contentApiService, 'getFavoriteLibraries').and.returnValue(of(favoriteLibrariesMock)); fixture.detectChanges(); }); - it('should reload on libraryDeleted action', () => { + it('should reload on libraryDeleted hook', () => { appHookService.libraryDeleted.next(''); expect(contentApiService.getFavoriteLibraries).toHaveBeenCalled(); }); - it('should reload on libraryUpdated action', () => { + it('should reload on libraryUpdated hook', () => { appHookService.libraryUpdated.next({} as any); expect(contentApiService.getFavoriteLibraries).toHaveBeenCalled(); }); - it('should reload on favoriteLibraryToggle action', () => { + it('should reload on favoriteLibraryToggle hook', () => { appHookService.favoriteLibraryToggle.next(); expect(contentApiService.getFavoriteLibraries).toHaveBeenCalled(); }); - it('should reload on libraryJoined action', () => { + it('should reload on libraryJoined hook', () => { appHookService.libraryJoined.next(); expect(contentApiService.getFavoriteLibraries).toHaveBeenCalled(); }); - it('should reload on libraryLeft action', () => { + it('should reload on libraryLeft hook', () => { appHookService.libraryLeft.next({} as any); expect(contentApiService.getFavoriteLibraries).toHaveBeenCalled(); }); }); describe('Pagination', () => { - let pagination; - - beforeEach(() => { - pagination = { - count: 100, - hasMoreItems: true, - totalItems: 300, - skipCount: 25, - maxItems: 25 - }; - }); - - it('should get list with pagination data onChange event', () => { - spyOn(contentApiService, 'getFavoriteLibraries').and.returnValue(of(page)); - - component.onChange(pagination); - - expect(contentApiService.getFavoriteLibraries).toHaveBeenCalledWith('-me-', pagination); - }); - it('should get list with pagination data onChangePageSize event', () => { - spyOn(contentApiService, 'getFavoriteLibraries').and.returnValue(of(page)); + spyOn(contentApiService, 'getFavoriteLibraries').and.returnValue(of(favoriteLibrariesMock)); - component.onChangePageSize(pagination); + component.onChangePageSize(libraryPaginationMock); - expect(contentApiService.getFavoriteLibraries).toHaveBeenCalledWith('-me-', pagination); + expect(contentApiService.getFavoriteLibraries).toHaveBeenCalledWith('-me-', libraryPaginationMock); }); it('should set preference page size onChangePageSize event', () => { - spyOn(contentApiService, 'getFavoriteLibraries').and.returnValue(of(page)); + spyOn(contentApiService, 'getFavoriteLibraries').and.returnValue(of(favoriteLibrariesMock)); - component.onChangePageSize(pagination); + component.onChangePageSize(libraryPaginationMock); - expect(userPreference.paginationSize).toBe(pagination.maxItems); + expect(userPreference.paginationSize).toBe(libraryPaginationMock.maxItems); }); }); }); diff --git a/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.ts b/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.ts index 250547a2c9..1b2f1fafe3 100644 --- a/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.ts +++ b/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.ts @@ -23,52 +23,20 @@ */ import { ChangeDetectorRef, Component, OnInit, ViewEncapsulation } from '@angular/core'; -import { FavoritePaging, Pagination, SiteEntry } from '@alfresco/js-api'; -import { - AppHookService, - ContentApiService, - ContextActionsDirective, - InfoDrawerComponent, - PageComponent, - PageLayoutComponent, - ToolbarComponent -} from '@alfresco/aca-shared'; -import { NavigateLibraryAction } from '@alfresco/aca-shared/store'; -import { - CustomEmptyContentTemplateDirective, - DataColumnComponent, - DataColumnListComponent, - EmptyContentComponent, - PaginationComponent, - UserPreferencesService -} from '@alfresco/adf-core'; -import { DocumentListPresetRef, DynamicColumnComponent } from '@alfresco/adf-extensions'; -import { CommonModule } from '@angular/common'; -import { DocumentListDirective } from '../../directives/document-list.directive'; -import { TranslatePipe } from '@ngx-translate/core'; -import { DocumentListComponent } from '@alfresco/adf-content-services'; +import { FavoritePaging, Pagination } from '@alfresco/js-api'; +import { ContentApiService } from '@alfresco/aca-shared'; +import { UserPreferencesService } from '@alfresco/adf-core'; +import { DocumentListPresetRef } from '@alfresco/adf-extensions'; +import { LibrariesBaseComponent } from '../libraries-base/libraries-base.component'; @Component({ - imports: [ - CommonModule, - DocumentListDirective, - ContextActionsDirective, - PaginationComponent, - InfoDrawerComponent, - PageLayoutComponent, - TranslatePipe, - ToolbarComponent, - EmptyContentComponent, - DynamicColumnComponent, - DataColumnListComponent, - DataColumnComponent, - DocumentListComponent, - CustomEmptyContentTemplateDirective - ], + selector: 'aca-favorite-libraries', + standalone: true, templateUrl: './favorite-libraries.component.html', + imports: [LibrariesBaseComponent], encapsulation: ViewEncapsulation.None }) -export class FavoriteLibrariesComponent extends PageComponent implements OnInit { +export class FavoriteLibrariesComponent extends LibrariesBaseComponent implements OnInit { pagination: Pagination = new Pagination({ skipCount: 0, maxItems: 25, @@ -79,7 +47,6 @@ export class FavoriteLibrariesComponent extends PageComponent implements OnInit columns: DocumentListPresetRef[] = []; constructor( - private appHookService: AppHookService, private contentApiService: ContentApiService, private preferences: UserPreferencesService, private changeDetectorRef: ChangeDetectorRef @@ -92,36 +59,22 @@ export class FavoriteLibrariesComponent extends PageComponent implements OnInit this.getList({ maxItems: this.preferences.paginationSize }); - this.subscriptions = this.subscriptions.concat([ + this.subscriptions.push( this.appHookService.libraryDeleted.subscribe(() => this.reloadList()), this.appHookService.libraryUpdated.subscribe(() => this.reloadList()), this.appHookService.libraryJoined.subscribe(() => this.reloadList()), this.appHookService.libraryLeft.subscribe(() => this.reloadList()), this.appHookService.favoriteLibraryToggle.subscribe(() => this.reloadList()) - ]); + ); this.columns = this.extensions.documentListPresets.favoriteLibraries || []; } - navigateTo(node: SiteEntry) { - if (node?.entry?.guid) { - this.store.dispatch(new NavigateLibraryAction(node.entry, 'favorite/libraries')); - } - } - - handleNodeClick(event: Event) { - this.navigateTo((event as CustomEvent).detail?.node); - } - onChangePageSize(pagination: Pagination) { this.preferences.paginationSize = pagination.maxItems; this.getList(pagination); } - onChange(pagination: Pagination) { - this.getList(pagination); - } - - private getList(pagination: Pagination) { + getList(pagination: Pagination) { this.isLoading = true; this.contentApiService.getFavoriteLibraries('-me-', pagination).subscribe( (favoriteLibraries: FavoritePaging) => { diff --git a/projects/aca-content/src/lib/components/libraries-base/libraries-base.component.html b/projects/aca-content/src/lib/components/libraries-base/libraries-base.component.html new file mode 100644 index 0000000000..efa6c8ed89 --- /dev/null +++ b/projects/aca-content/src/lib/components/libraries-base/libraries-base.component.html @@ -0,0 +1,96 @@ + +
+

+ @if (selectedRowItemsCount < 1) { + {{ titleKey | translate: { count: selectedRowItemsCount } }} + } @else { + {{ 'APP.HEADER.SELECTED' | translate: { count: selectedRowItemsCount } }} + } +

+ +
+ +
+
+ + + + + + + @for (column of columns; track trackByColumnId($index, column)) { + @if (column.template && !(column.desktopOnly && isSmallScreen)) { + + + + + + } @else if (!column.template && !(column.desktopOnly && isSmallScreen)) { + + } + } + + + + +
+ + @if (infoDrawerOpened$ | async) { + + } +
+
diff --git a/projects/aca-content/src/lib/components/libraries-base/libraries-base.component.spec.ts b/projects/aca-content/src/lib/components/libraries-base/libraries-base.component.spec.ts new file mode 100644 index 0000000000..8dd6094771 --- /dev/null +++ b/projects/aca-content/src/lib/components/libraries-base/libraries-base.component.spec.ts @@ -0,0 +1,145 @@ +/*! + * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Alfresco Example Content Application + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * from Hyland Software. If not, see . + */ + +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { LibrariesBaseComponent } from './libraries-base.component'; +import { AppTestingModule } from '../../testing/app-testing.module'; +import { CustomEmptyContentTemplateDirective, PaginationComponent, UnitTestingUtils } from '@alfresco/adf-core'; +import { DocumentListComponent } from '@alfresco/adf-content-services'; +import { SiteEntry } from '@alfresco/js-api'; +import { Router } from '@angular/router'; +import { provideEffects } from '@ngrx/effects'; +import { RouterEffects } from '@alfresco/aca-shared/store'; +import { LibraryEffects } from '../../store/effects'; +import { getTitleElementText } from '../../testing/test-utils'; +import { librariesMock, libraryPaginationMock } from '../../mock/libraries-mock'; +import { DebugElement } from '@angular/core'; + +describe('LibrariesBaseComponent', () => { + let component: LibrariesBaseComponent; + let fixture: ComponentFixture; + let unitTestingUtils: UnitTestingUtils; + let router: Router; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AppTestingModule, LibrariesBaseComponent], + providers: [provideEffects([RouterEffects, LibraryEffects])] + }).compileComponents(); + fixture = TestBed.createComponent(LibrariesBaseComponent); + component = fixture.componentInstance; + + router = TestBed.inject(Router); + unitTestingUtils = new UnitTestingUtils(fixture.debugElement); + + component.list = librariesMock; + fixture.detectChanges(); + }); + + it('should set title based on selectedRowItemsCount', () => { + component.titleKey = 'TITLE_TRANSLATION_KEY'; + fixture.detectChanges(); + + expect(getTitleElementText(fixture)).toBe('TITLE_TRANSLATION_KEY'); + + component.selectedRowItemsCount = 5; + fixture.detectChanges(); + + expect(getTitleElementText(fixture)).toBe('APP.HEADER.SELECTED'); + }); + + it('should show empty state when list is empty', () => { + component.list = null; + component.emptyTitleKey = 'EMPTY_TITLE'; + component.emptySubtitleKey = 'EMPTY_SUBTITLE'; + fixture.detectChanges(); + const emptyContent = unitTestingUtils.getByDirective(CustomEmptyContentTemplateDirective); + + expect(emptyContent.nativeElement.textContent).toContain('EMPTY_TITLE'); + expect(emptyContent.nativeElement.textContent).toContain('EMPTY_SUBTITLE'); + }); + + describe('Pagination events', () => { + let paginationComponent: DebugElement; + + beforeEach(() => { + paginationComponent = unitTestingUtils.getByDirective(PaginationComponent); + }); + + it('should emit changePageSize event', () => { + spyOn(component.changePageSize, 'emit'); + paginationComponent.triggerEventHandler('changePageSize', libraryPaginationMock); + expect(component.changePageSize.emit).toHaveBeenCalledWith(libraryPaginationMock); + }); + + it('should emit changePageNumber event', () => { + spyOn(component.changePageNumber, 'emit'); + paginationComponent.triggerEventHandler('changePageNumber', libraryPaginationMock); + expect(component.changePageNumber.emit).toHaveBeenCalledWith(libraryPaginationMock); + }); + + it('should emit nextPage event', () => { + spyOn(component.nextPage, 'emit'); + paginationComponent.triggerEventHandler('nextPage', libraryPaginationMock); + expect(component.nextPage.emit).toHaveBeenCalledWith(libraryPaginationMock); + }); + + it('should emit prevPage event', () => { + spyOn(component.prevPage, 'emit'); + paginationComponent.triggerEventHandler('prevPage', libraryPaginationMock); + expect(component.prevPage.emit).toHaveBeenCalledWith(libraryPaginationMock); + }); + }); + + describe('Node navigation', () => { + it('should not navigate when node is null or missing guid', () => { + spyOn(router, 'navigate').and.stub(); + component.navigateTo(null); + expect(router.navigate).not.toHaveBeenCalled(); + + component.navigateTo({ entry: {} } as SiteEntry); + expect(router.navigate).not.toHaveBeenCalled(); + }); + + it('should dispatch navigation action when node has guid', () => { + spyOn(component['store'], 'dispatch').and.stub(); + component.navigateTo({ entry: { guid: 'guid' } } as SiteEntry); + expect(component['store'].dispatch).toHaveBeenCalled(); + }); + + it('should handle node double click', () => { + spyOn(component, 'navigateTo').and.stub(); + const documentList = unitTestingUtils.getByDirective(DocumentListComponent); + documentList.triggerEventHandler('node-dblclick', { detail: { node: { entry: { guid: 'guid' } } } }); + expect(component.navigateTo).toHaveBeenCalledWith({ entry: { guid: 'guid' } } as SiteEntry); + }); + + it('should handle name click', () => { + spyOn(component, 'navigateTo').and.stub(); + const documentList = unitTestingUtils.getByDirective(DocumentListComponent); + documentList.triggerEventHandler('name-click', { detail: { node: { entry: { guid: 'guid' } } } }); + expect(component.navigateTo).toHaveBeenCalledWith({ entry: { guid: 'guid' } } as SiteEntry); + }); + }); +}); diff --git a/projects/aca-content/src/lib/components/libraries-base/libraries-base.component.ts b/projects/aca-content/src/lib/components/libraries-base/libraries-base.component.ts new file mode 100644 index 0000000000..a2c4eb6867 --- /dev/null +++ b/projects/aca-content/src/lib/components/libraries-base/libraries-base.component.ts @@ -0,0 +1,102 @@ +/*! + * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Alfresco Example Content Application + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * from Hyland Software. If not, see . + */ + +import { CommonModule } from '@angular/common'; +import { Component, Input, Output, EventEmitter, ViewEncapsulation, inject } from '@angular/core'; +import { TranslatePipe } from '@ngx-translate/core'; +import { + ContextActionsDirective, + ToolbarComponent, + InfoDrawerComponent, + PageLayoutComponent, + PageComponent, + AppHookService +} from '@alfresco/aca-shared'; +import { DocumentListDirective } from '../../directives/document-list.directive'; +import { + CustomEmptyContentTemplateDirective, + DataColumnComponent, + DataColumnListComponent, + EmptyContentComponent, + PaginationComponent +} from '@alfresco/adf-core'; +import { DocumentListComponent } from '@alfresco/adf-content-services'; +import { DocumentListPresetRef, DynamicColumnComponent } from '@alfresco/adf-extensions'; +import { NavigateLibraryAction } from '@alfresco/aca-shared/store'; +import { FavoritePaging, Pagination, SiteEntry, SitePaging } from '@alfresco/js-api'; + +@Component({ + selector: 'aca-libraries-base', + standalone: true, + templateUrl: './libraries-base.component.html', + imports: [ + CommonModule, + TranslatePipe, + ToolbarComponent, + InfoDrawerComponent, + PageLayoutComponent, + DocumentListDirective, + ContextActionsDirective, + DocumentListComponent, + DataColumnListComponent, + DataColumnComponent, + DynamicColumnComponent, + CustomEmptyContentTemplateDirective, + EmptyContentComponent, + PaginationComponent + ], + encapsulation: ViewEncapsulation.None +}) +export class LibrariesBaseComponent extends PageComponent { + @Input() titleKey: string; + @Input() list: SitePaging | FavoritePaging; + @Input() isLoading: boolean; + @Input() emptyTitleKey: string; + @Input() emptySubtitleKey: string; + @Input() pagination: Pagination; + @Input() currentFolderId: string; + @Input() columns: DocumentListPresetRef[]; + @Input() navigateRoute: string; + + @Output() changePageSize = new EventEmitter(); + @Output() changePageNumber = new EventEmitter(); + @Output() nextPage = new EventEmitter(); + @Output() prevPage = new EventEmitter(); + + protected appHookService = inject(AppHookService); + + constructor() { + super(); + } + + navigateTo(node: SiteEntry) { + if (node?.entry?.guid) { + this.store.dispatch(new NavigateLibraryAction(node.entry, this.navigateRoute)); + } + } + + handleNodeClick(event: Event) { + this.navigateTo((event as CustomEvent).detail?.node); + } +} diff --git a/projects/aca-content/src/lib/components/libraries/libraries.component.html b/projects/aca-content/src/lib/components/libraries/libraries.component.html index fe98257102..73eb0975f1 100644 --- a/projects/aca-content/src/lib/components/libraries/libraries.component.html +++ b/projects/aca-content/src/lib/components/libraries/libraries.component.html @@ -1,86 +1,7 @@ - -
-

- {{ (selectedRowItemsCount < 1 ? 'APP.BROWSE.LIBRARIES.MENU.MY_LIBRARIES.TITLE' : 'APP.HEADER.SELECTED') | translate: { count: selectedRowItemsCount } }} -

- -
- -
-
- - - - - - - - - - - - - - - - - - - - - - - -
- -
- -
-
-
+ diff --git a/projects/aca-content/src/lib/components/libraries/libraries.component.spec.ts b/projects/aca-content/src/lib/components/libraries/libraries.component.spec.ts index 40de31f9bb..7a7e2fe177 100644 --- a/projects/aca-content/src/lib/components/libraries/libraries.component.spec.ts +++ b/projects/aca-content/src/lib/components/libraries/libraries.component.spec.ts @@ -23,32 +23,20 @@ */ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { Router } from '@angular/router'; -import { AlfrescoApiService } from '@alfresco/adf-content-services'; import { LibrariesComponent } from './libraries.component'; import { AppTestingModule } from '../../testing/app-testing.module'; import { provideEffects } from '@ngrx/effects'; import { LibraryEffects } from '../../store/effects'; -import { ContentApiService } from '@alfresco/aca-shared'; -import { getTitleElementText } from '../../testing/test-utils'; +import { AppExtensionService, AppHookService, ContentApiService } from '@alfresco/aca-shared'; import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { libraryColumnsPresetMock, librariesMock } from '../../mock/libraries-mock'; describe('LibrariesComponent', () => { let fixture: ComponentFixture; let component: LibrariesComponent; - let alfrescoApi: AlfrescoApiService; let contentApiService: ContentApiService; - let router: Router; - let page; - - beforeEach(() => { - page = { - list: { - entries: [{ entry: { id: 1 } }, { entry: { id: 2 } }], - pagination: { data: 'data' } - } - }; - }); + let appHookService: AppHookService; + let appExtensionService: AppExtensionService; beforeEach(() => { TestBed.configureTestingModule({ @@ -59,34 +47,49 @@ describe('LibrariesComponent', () => { fixture = TestBed.createComponent(LibrariesComponent); component = fixture.componentInstance; - alfrescoApi = TestBed.inject(AlfrescoApiService); contentApiService = TestBed.inject(ContentApiService); - alfrescoApi.reset(); - router = TestBed.inject(Router); + appHookService = TestBed.inject(AppHookService); + appExtensionService = TestBed.inject(AppExtensionService); const sitesApi = contentApiService.sitesApi; - spyOn(sitesApi, 'listSites').and.returnValue(Promise.resolve(page)); + spyOn(sitesApi, 'listSites').and.returnValue(Promise.resolve(librariesMock)); spyOn(sitesApi, 'listSiteMembershipsForPerson').and.returnValue(Promise.resolve({})); }); - describe('Initialization', () => { - it('should set title to MY_LIBRARIES.TITLE based on selectedRowItemsCount', () => { - fixture.detectChanges(); - expect(getTitleElementText(fixture)).toBe('APP.BROWSE.LIBRARIES.MENU.MY_LIBRARIES.TITLE'); + it('should set columns from extensions on init', () => { + appExtensionService.documentListPresets.libraries = libraryColumnsPresetMock; + fixture.detectChanges(); + expect(component.columns).toEqual(appExtensionService.documentListPresets.libraries); + }); + + it('should handle no columns preset in extensions', () => { + appExtensionService.documentListPresets.libraries = undefined; + component.ngOnInit(); + expect(component.columns.length).toBe(0); + }); + + describe('Library hooks', () => { + let reloadSpy: jasmine.Spy<() => void>; - component.selectedRowItemsCount = 2; + beforeEach(() => { + reloadSpy = spyOn(component, 'reload'); fixture.detectChanges(); - expect(getTitleElementText(fixture)).toBe('APP.HEADER.SELECTED'); }); - }); - describe('Node navigation', () => { - it('does not navigate when id is not passed', () => { - spyOn(router, 'navigate').and.stub(); - component.navigateTo(null); + it('should reload on libraryDeleted hook', () => { + appHookService.libraryDeleted.next(''); + expect(reloadSpy).toHaveBeenCalled(); + }); + + it('should reload on libraryUpdated hook', () => { + appHookService.libraryUpdated.next(librariesMock.list.entries[0]); + expect(reloadSpy).toHaveBeenCalled(); + }); - expect(router.navigate).not.toHaveBeenCalled(); + it('should reload on libraryLeft hook', () => { + appHookService.libraryLeft.next(''); + expect(reloadSpy).toHaveBeenCalled(); }); }); }); diff --git a/projects/aca-content/src/lib/components/libraries/libraries.component.ts b/projects/aca-content/src/lib/components/libraries/libraries.component.ts index eb9bde8e53..7b64c33adc 100644 --- a/projects/aca-content/src/lib/components/libraries/libraries.component.ts +++ b/projects/aca-content/src/lib/components/libraries/libraries.component.ts @@ -22,56 +22,18 @@ * from Hyland Software. If not, see . */ -import { NavigateLibraryAction } from '@alfresco/aca-shared/store'; -import { SiteEntry } from '@alfresco/js-api'; import { Component, OnInit, ViewEncapsulation } from '@angular/core'; -import { - AppHookService, - ContextActionsDirective, - InfoDrawerComponent, - PageComponent, - PageLayoutComponent, - PaginationDirective, - ToolbarComponent -} from '@alfresco/aca-shared'; -import { DocumentListPresetRef, DynamicColumnComponent } from '@alfresco/adf-extensions'; -import { CommonModule } from '@angular/common'; -import { - CustomEmptyContentTemplateDirective, - DataColumnComponent, - DataColumnListComponent, - EmptyContentComponent, - PaginationComponent -} from '@alfresco/adf-core'; -import { DocumentListDirective } from '../../directives/document-list.directive'; -import { TranslatePipe } from '@ngx-translate/core'; -import { DocumentListComponent } from '@alfresco/adf-content-services'; +import { LibrariesBaseComponent } from '../libraries-base/libraries-base.component'; @Component({ - imports: [ - CommonModule, - DocumentListDirective, - ContextActionsDirective, - PaginationComponent, - PaginationDirective, - InfoDrawerComponent, - PageLayoutComponent, - TranslatePipe, - ToolbarComponent, - EmptyContentComponent, - DynamicColumnComponent, - DocumentListComponent, - DataColumnComponent, - DataColumnListComponent, - CustomEmptyContentTemplateDirective - ], + selector: 'aca-libraries', + standalone: true, templateUrl: './libraries.component.html', + imports: [LibrariesBaseComponent], encapsulation: ViewEncapsulation.None }) -export class LibrariesComponent extends PageComponent implements OnInit { - columns: DocumentListPresetRef[] = []; - - constructor(private appHookService: AppHookService) { +export class LibrariesComponent extends LibrariesBaseComponent implements OnInit { + constructor() { super(); } @@ -86,14 +48,4 @@ export class LibrariesComponent extends PageComponent implements OnInit { this.columns = this.extensions.documentListPresets.libraries || []; } - - navigateTo(node: SiteEntry) { - if (node?.entry?.guid) { - this.store.dispatch(new NavigateLibraryAction(node.entry)); - } - } - - handleNodeClick(event: Event) { - this.navigateTo((event as CustomEvent).detail?.node); - } } diff --git a/projects/aca-content/src/lib/components/library-list/library-list.component.html b/projects/aca-content/src/lib/components/library-list/library-list.component.html index b65649aa76..0fbe7923af 100644 --- a/projects/aca-content/src/lib/components/library-list/library-list.component.html +++ b/projects/aca-content/src/lib/components/library-list/library-list.component.html @@ -1,96 +1,13 @@ - -
-

- @if (selectedRowItemsCount < 1) { - {{ 'APP.BROWSE.LIBRARIES.MENU.ALL_LIBRARIES.TITLE' | translate: { count: selectedRowItemsCount } }} - } @else { - {{ 'APP.HEADER.SELECTED' | translate: { count: selectedRowItemsCount } }} - } -

- -
- -
-
- - - - - - - @for (column of columns; track trackByColumnId($index, column)) { - @if (column.template && !(column.desktopOnly && isSmallScreen)) { - - - - - - } @else if (!column.template && !(column.desktopOnly && isSmallScreen)) { - - } - } - - - - -
- - @if (infoDrawerOpened$ | async) { - - } -
-
+ diff --git a/projects/aca-content/src/lib/components/library-list/library-list.component.spec.ts b/projects/aca-content/src/lib/components/library-list/library-list.component.spec.ts index 66c3184190..f8fdf3d8ce 100644 --- a/projects/aca-content/src/lib/components/library-list/library-list.component.spec.ts +++ b/projects/aca-content/src/lib/components/library-list/library-list.component.spec.ts @@ -23,136 +23,86 @@ */ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { Router } from '@angular/router'; -import { UnitTestingUtils, UserPreferencesService } from '@alfresco/adf-core'; -import { DocumentListComponent, SitesService } from '@alfresco/adf-content-services'; +import { UserPreferencesService } from '@alfresco/adf-core'; +import { SitesService } from '@alfresco/adf-content-services'; import { LibraryListComponent } from './library-list.component'; import { AppTestingModule } from '../../testing/app-testing.module'; -import { AppHookService } from '@alfresco/aca-shared'; +import { AppExtensionService, AppHookService } from '@alfresco/aca-shared'; import { provideEffects } from '@ngrx/effects'; -import { RouterEffects } from '@alfresco/aca-shared/store'; import { Observable, of, throwError } from 'rxjs'; import { LibraryEffects } from '../../store/effects'; -import { getTitleElementText } from '../../testing/test-utils'; import { MatSnackBarModule } from '@angular/material/snack-bar'; -import { Pagination, SiteEntry, SitePaging } from '@alfresco/js-api'; +import { SitePaging } from '@alfresco/js-api'; +import { libraryColumnsPresetMock, librariesMock, libraryPaginationMock } from '../../mock/libraries-mock'; describe('LibraryListComponent', () => { let fixture: ComponentFixture; let component: LibraryListComponent; let userPreference: UserPreferencesService; let sitesService: SitesService; - let router: Router; let appHookService: AppHookService; let getSitesSpy: jasmine.Spy<(options?: any) => Observable>; - let unitTestingUtils: UnitTestingUtils; - - const paging: SitePaging = { - list: { - entries: [ - { entry: { id: '1', guid: '1', title: 'Library 1', visibility: 'public', preset: 'site-dashboard' } }, - { entry: { id: '2', guid: '2', title: 'Library 2', visibility: 'private' } } - ], - pagination: { count: 25, skipCount: 0 } - } - }; + let appExtensionService: AppExtensionService; beforeEach(() => { TestBed.configureTestingModule({ imports: [AppTestingModule, LibraryListComponent, MatSnackBarModule], - providers: [provideEffects([RouterEffects, LibraryEffects])] + providers: [provideEffects([LibraryEffects])] }); fixture = TestBed.createComponent(LibraryListComponent); component = fixture.componentInstance; - unitTestingUtils = new UnitTestingUtils(fixture.debugElement); sitesService = TestBed.inject(SitesService); userPreference = TestBed.inject(UserPreferencesService); appHookService = TestBed.inject(AppHookService); - router = TestBed.inject(Router); + appExtensionService = TestBed.inject(AppExtensionService); getSitesSpy = spyOn(sitesService, 'getSites'); - getSitesSpy.and.returnValue(of(paging)); + getSitesSpy.and.returnValue(of(librariesMock)); fixture.detectChanges(); }); - describe('on initialization', () => { - it('should set data', () => { - expect(component.list).toBe(paging); - expect(component.pagination).toBe(paging.list.pagination); - }); - - it('should get data with user preference pagination size', () => { - userPreference.paginationSize = 1; - component.ngOnInit(); - expect(sitesService.getSites).toHaveBeenCalledWith({ maxItems: 1 }); - }); - - it('should set data on error', () => { - getSitesSpy.and.returnValue(throwError(() => 'error')); - component.ngOnInit(); - - expect(component.list).toBeNull(); - expect(component.pagination).toBeNull(); - expect(component.isLoading).toBe(false); - }); - - it('should set title based on selectedRowItemsCount', () => { - expect(getTitleElementText(fixture)).toBe('APP.BROWSE.LIBRARIES.MENU.ALL_LIBRARIES.TITLE'); - - component.selectedRowItemsCount = 5; - fixture.detectChanges(); - - expect(getTitleElementText(fixture)).toBe('APP.HEADER.SELECTED'); - }); + it('should set data', () => { + expect(component.list).toBe(librariesMock); + expect(component.pagination).toBe(librariesMock.list.pagination); + }); - it('should handle no columns preset in extensions', () => { - component['extensions'].documentListPresets.libraries = undefined; - component.ngOnInit(); - expect(component.columns.length).toBe(0); - }); + it('should get data with user preference pagination size', () => { + userPreference.paginationSize = 1; + component.ngOnInit(); + expect(sitesService.getSites).toHaveBeenCalledWith({ maxItems: 1 }); }); - describe('Node navigation', () => { - it('should not navigate when node is null or missing guid', () => { - spyOn(router, 'navigate').and.stub(); - component.navigateTo(null); - expect(router.navigate).not.toHaveBeenCalled(); + it('should set data on error', () => { + getSitesSpy.and.returnValue(throwError(() => 'error')); + component.ngOnInit(); - component.navigateTo({ entry: {} } as SiteEntry); - expect(router.navigate).not.toHaveBeenCalled(); - }); - - it('should dispatch navigation action when node has guid', () => { - spyOn(component['store'], 'dispatch').and.stub(); - component.navigateTo({ entry: { guid: 'guid' } } as SiteEntry); - expect(component['store'].dispatch).toHaveBeenCalled(); - }); + expect(component.list).toBeNull(); + expect(component.pagination).toBeNull(); + expect(component.isLoading).toBe(false); + }); - it('should handle node double click', () => { - spyOn(component, 'navigateTo').and.stub(); - const documentList = unitTestingUtils.getByDirective(DocumentListComponent); - documentList.triggerEventHandler('node-dblclick', { detail: { node: { entry: { guid: 'guid' } } } }); - expect(component.navigateTo).toHaveBeenCalledWith({ entry: { guid: 'guid' } } as SiteEntry); - }); + it('should set columns from extensions on init', () => { + appExtensionService.documentListPresets.libraries = libraryColumnsPresetMock; + component.ngOnInit(); + expect(component.columns).toEqual(appExtensionService.documentListPresets.libraries); + }); - it('should handle name click', () => { - spyOn(component, 'navigateTo').and.stub(); - const documentList = unitTestingUtils.getByDirective(DocumentListComponent); - documentList.triggerEventHandler('name-click', { detail: { node: { entry: { guid: 'guid' } } } }); - expect(component.navigateTo).toHaveBeenCalledWith({ entry: { guid: 'guid' } } as SiteEntry); - }); + it('should handle no columns preset in extensions', () => { + appExtensionService.documentListPresets.libraries = undefined; + component.ngOnInit(); + expect(component.columns.length).toBe(0); }); - describe('Reload on actions', () => { + describe('Library hooks', () => { it('should reload on libraryDeleted action', () => { appHookService.libraryDeleted.next(''); expect(sitesService.getSites).toHaveBeenCalled(); }); it('should reload on libraryUpdated action', () => { - appHookService.libraryUpdated.next(paging.list.entries[0]); + appHookService.libraryUpdated.next(librariesMock.list.entries[0]); expect(sitesService.getSites).toHaveBeenCalled(); }); @@ -168,27 +118,19 @@ describe('LibraryListComponent', () => { }); describe('Pagination', () => { - const pagination: Pagination = { - count: 100, - hasMoreItems: true, - totalItems: 300, - skipCount: 25, - maxItems: 25 - }; - it('should get list with pagination data onChange event', () => { - component.getList(pagination); - expect(sitesService.getSites).toHaveBeenCalledWith(pagination); + component.getList(libraryPaginationMock); + expect(sitesService.getSites).toHaveBeenCalledWith(libraryPaginationMock); }); it('should get list with pagination data onChangePageSize event', () => { - component.onChangePageSize(pagination); - expect(sitesService.getSites).toHaveBeenCalledWith(pagination); + component.onChangePageSize(libraryPaginationMock); + expect(sitesService.getSites).toHaveBeenCalledWith(libraryPaginationMock); }); it('should set preference page size onChangePageSize event', () => { - component.onChangePageSize(pagination); - expect(userPreference.paginationSize).toBe(pagination.maxItems); + component.onChangePageSize(libraryPaginationMock); + expect(userPreference.paginationSize).toBe(libraryPaginationMock.maxItems); }); }); }); diff --git a/projects/aca-content/src/lib/components/library-list/library-list.component.ts b/projects/aca-content/src/lib/components/library-list/library-list.component.ts index 09894487d7..8bfb4f6358 100644 --- a/projects/aca-content/src/lib/components/library-list/library-list.component.ts +++ b/projects/aca-content/src/lib/components/library-list/library-list.component.ts @@ -23,53 +23,19 @@ */ import { ChangeDetectorRef, Component, OnInit, ViewEncapsulation } from '@angular/core'; -import { Pagination, SiteEntry, SitePaging } from '@alfresco/js-api'; -import { - AppHookService, - ContextActionsDirective, - InfoDrawerComponent, - PageComponent, - PageLayoutComponent, - ToolbarComponent -} from '@alfresco/aca-shared'; -import { NavigateLibraryAction } from '@alfresco/aca-shared/store'; -import { - CustomEmptyContentTemplateDirective, - DataColumnComponent, - DataColumnListComponent, - EmptyContentComponent, - PaginationComponent, - UserPreferencesService -} from '@alfresco/adf-core'; -import { DocumentListPresetRef, DynamicColumnComponent } from '@alfresco/adf-extensions'; -import { CommonModule } from '@angular/common'; -import { DocumentListDirective } from '../../directives/document-list.directive'; -import { TranslatePipe } from '@ngx-translate/core'; -import { DocumentListComponent, SitesService } from '@alfresco/adf-content-services'; -import { merge } from 'rxjs'; -import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { Pagination, SitePaging } from '@alfresco/js-api'; +import { UserPreferencesService } from '@alfresco/adf-core'; +import { SitesService } from '@alfresco/adf-content-services'; +import { LibrariesBaseComponent } from '../libraries-base/libraries-base.component'; @Component({ - imports: [ - CommonModule, - DocumentListDirective, - ContextActionsDirective, - PaginationComponent, - InfoDrawerComponent, - PageLayoutComponent, - TranslatePipe, - ToolbarComponent, - EmptyContentComponent, - DynamicColumnComponent, - DataColumnListComponent, - DataColumnComponent, - DocumentListComponent, - CustomEmptyContentTemplateDirective - ], + selector: 'aca-library-list', + standalone: true, templateUrl: './library-list.component.html', + imports: [LibrariesBaseComponent], encapsulation: ViewEncapsulation.None }) -export class LibraryListComponent extends PageComponent implements OnInit { +export class LibraryListComponent extends LibrariesBaseComponent implements OnInit { pagination = new Pagination({ skipCount: 0, maxItems: 25, @@ -77,10 +43,8 @@ export class LibraryListComponent extends PageComponent implements OnInit { }); isLoading = false; list: SitePaging; - columns: DocumentListPresetRef[] = []; constructor( - private readonly appHookService: AppHookService, private readonly preferences: UserPreferencesService, private readonly changeDetectorRef: ChangeDetectorRef, private readonly sitesService: SitesService @@ -92,23 +56,15 @@ export class LibraryListComponent extends PageComponent implements OnInit { super.ngOnInit(); this.getList({ maxItems: this.preferences.paginationSize }); - - merge(this.appHookService.libraryDeleted, this.appHookService.libraryUpdated, this.appHookService.libraryJoined, this.appHookService.libraryLeft) - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe(() => this.reloadList()); + this.subscriptions.push( + this.appHookService.libraryDeleted.subscribe(() => this.reloadList()), + this.appHookService.libraryUpdated.subscribe(() => this.reloadList()), + this.appHookService.libraryJoined.subscribe(() => this.reloadList()), + this.appHookService.libraryLeft.subscribe(() => this.reloadList()) + ); this.columns = this.extensions.documentListPresets.libraries || []; } - navigateTo(node: SiteEntry) { - if (node?.entry?.guid) { - this.store.dispatch(new NavigateLibraryAction(node.entry)); - } - } - - handleNodeClick(event: Event) { - this.navigateTo((event as CustomEvent).detail?.node); - } - onChangePageSize(pagination: Pagination) { this.preferences.paginationSize = pagination.maxItems; this.getList(pagination); diff --git a/projects/aca-content/src/lib/mock/libraries-mock.ts b/projects/aca-content/src/lib/mock/libraries-mock.ts new file mode 100644 index 0000000000..2c273525bf --- /dev/null +++ b/projects/aca-content/src/lib/mock/libraries-mock.ts @@ -0,0 +1,66 @@ +/*! + * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Alfresco Example Content Application + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * from Hyland Software. If not, see . + */ + +import { DocumentListPresetRef } from '@alfresco/adf-extensions'; +import { FavoritePaging, Pagination, SitePaging } from '@alfresco/js-api'; + +export const librariesMock: SitePaging = { + list: { + entries: [ + { entry: { id: '1', guid: '1', title: 'Library 1', visibility: 'public' } }, + { entry: { id: '2', guid: '2', title: 'Library 2', visibility: 'private' } } + ], + pagination: { count: 25, skipCount: 0 } + } +}; + +export const favoriteLibrariesMock: FavoritePaging = { + list: { + entries: [ + { entry: { id: '1', targetGuid: '1', title: 'Favorite Library 1', visibility: 'public' } }, + { entry: { id: '2', targetGuid: '2', title: 'Favorite Library 2', visibility: 'private' } } + ], + pagination: { count: 25, skipCount: 0 } + } +} as any as FavoritePaging; + +export const libraryColumnsPresetMock: DocumentListPresetRef[] = [ + { + key: 'name', + type: 'text', + sortable: false, + template: '', + desktopOnly: false, + sortingKey: '', + id: '' + } +]; + +export const libraryPaginationMock: Pagination = { + count: 100, + hasMoreItems: true, + totalItems: 300, + skipCount: 25, + maxItems: 25 +}; From 925958a296b16ae048e12b8a4bb91b9f0d342cdd Mon Sep 17 00:00:00 2001 From: g-jaskowski Date: Wed, 22 Oct 2025 10:42:49 +0200 Subject: [PATCH 6/7] [ACS-10165] fix build issue --- .../favorite-libraries/favorite-libraries.component.ts | 2 +- .../src/lib/components/library-list/library-list.component.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.ts b/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.ts index 1b2f1fafe3..0b1027f64b 100644 --- a/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.ts +++ b/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.ts @@ -43,7 +43,7 @@ export class FavoriteLibrariesComponent extends LibrariesBaseComponent implement totalItems: 0 }); isLoading = false; - list: FavoritePaging; + list: FavoritePaging = null; columns: DocumentListPresetRef[] = []; constructor( diff --git a/projects/aca-content/src/lib/components/library-list/library-list.component.ts b/projects/aca-content/src/lib/components/library-list/library-list.component.ts index 8bfb4f6358..6a0edf37a4 100644 --- a/projects/aca-content/src/lib/components/library-list/library-list.component.ts +++ b/projects/aca-content/src/lib/components/library-list/library-list.component.ts @@ -42,7 +42,7 @@ export class LibraryListComponent extends LibrariesBaseComponent implements OnIn totalItems: 0 }); isLoading = false; - list: SitePaging; + list: SitePaging = null; constructor( private readonly preferences: UserPreferencesService, From 62c319b0f41a4cc04a220f3e6ef26c23f752a44d Mon Sep 17 00:00:00 2001 From: g-jaskowski Date: Wed, 22 Oct 2025 15:32:16 +0200 Subject: [PATCH 7/7] [ACS-10165] code review fixes --- .../favorite-libraries.component.spec.ts | 2 +- .../libraries-base.component.spec.ts | 48 +++++++++---------- .../libraries-base.component.ts | 6 +-- .../libraries/libraries.component.ts | 4 -- .../src/lib/mock/libraries-mock.ts | 6 +-- 5 files changed, 29 insertions(+), 37 deletions(-) diff --git a/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.spec.ts b/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.spec.ts index 3b8528bfe3..45e415b643 100644 --- a/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.spec.ts +++ b/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.spec.ts @@ -79,7 +79,7 @@ describe('FavoriteLibrariesComponent', () => { }); it('should set data on error', () => { - spyOn(contentApiService, 'getFavoriteLibraries').and.returnValue(throwError('error')); + spyOn(contentApiService, 'getFavoriteLibraries').and.returnValue(throwError(() => 'error')); fixture.detectChanges(); expect(component.list).toBe(null); diff --git a/projects/aca-content/src/lib/components/libraries-base/libraries-base.component.spec.ts b/projects/aca-content/src/lib/components/libraries-base/libraries-base.component.spec.ts index 8dd6094771..b748604a39 100644 --- a/projects/aca-content/src/lib/components/libraries-base/libraries-base.component.spec.ts +++ b/projects/aca-content/src/lib/components/libraries-base/libraries-base.component.spec.ts @@ -27,31 +27,31 @@ import { LibrariesBaseComponent } from './libraries-base.component'; import { AppTestingModule } from '../../testing/app-testing.module'; import { CustomEmptyContentTemplateDirective, PaginationComponent, UnitTestingUtils } from '@alfresco/adf-core'; import { DocumentListComponent } from '@alfresco/adf-content-services'; -import { SiteEntry } from '@alfresco/js-api'; -import { Router } from '@angular/router'; import { provideEffects } from '@ngrx/effects'; -import { RouterEffects } from '@alfresco/aca-shared/store'; +import { NavigateLibraryAction, RouterEffects } from '@alfresco/aca-shared/store'; import { LibraryEffects } from '../../store/effects'; import { getTitleElementText } from '../../testing/test-utils'; import { librariesMock, libraryPaginationMock } from '../../mock/libraries-mock'; import { DebugElement } from '@angular/core'; +import { MockStore, provideMockStore } from '@ngrx/store/testing'; +import { initialState } from '@alfresco/aca-shared'; describe('LibrariesBaseComponent', () => { let component: LibrariesBaseComponent; let fixture: ComponentFixture; let unitTestingUtils: UnitTestingUtils; - let router: Router; + let store: MockStore; beforeEach(async () => { await TestBed.configureTestingModule({ imports: [AppTestingModule, LibrariesBaseComponent], - providers: [provideEffects([RouterEffects, LibraryEffects])] + providers: [provideEffects([RouterEffects, LibraryEffects]), provideMockStore({ initialState })] }).compileComponents(); fixture = TestBed.createComponent(LibrariesBaseComponent); component = fixture.componentInstance; - router = TestBed.inject(Router); unitTestingUtils = new UnitTestingUtils(fixture.debugElement); + store = TestBed.inject(MockStore); component.list = librariesMock; fixture.detectChanges(); @@ -113,33 +113,33 @@ describe('LibrariesBaseComponent', () => { }); describe('Node navigation', () => { - it('should not navigate when node is null or missing guid', () => { - spyOn(router, 'navigate').and.stub(); - component.navigateTo(null); - expect(router.navigate).not.toHaveBeenCalled(); + let documentListComponent: DebugElement; - component.navigateTo({ entry: {} } as SiteEntry); - expect(router.navigate).not.toHaveBeenCalled(); + beforeEach(() => { + documentListComponent = unitTestingUtils.getByDirective(DocumentListComponent); }); - it('should dispatch navigation action when node has guid', () => { - spyOn(component['store'], 'dispatch').and.stub(); - component.navigateTo({ entry: { guid: 'guid' } } as SiteEntry); - expect(component['store'].dispatch).toHaveBeenCalled(); + it('should not navigate when node is null or missing guid', () => { + spyOn(store, 'dispatch').and.stub(); + documentListComponent.triggerEventHandler('node-dblclick', { detail: { node: null } }); + expect(store.dispatch).not.toHaveBeenCalled(); + + documentListComponent.triggerEventHandler('node-dblclick', { detail: { node: { entry: {} } } }); + expect(store.dispatch).not.toHaveBeenCalled(); }); it('should handle node double click', () => { - spyOn(component, 'navigateTo').and.stub(); - const documentList = unitTestingUtils.getByDirective(DocumentListComponent); - documentList.triggerEventHandler('node-dblclick', { detail: { node: { entry: { guid: 'guid' } } } }); - expect(component.navigateTo).toHaveBeenCalledWith({ entry: { guid: 'guid' } } as SiteEntry); + spyOn(store, 'dispatch').and.stub(); + const siteEntry = librariesMock.list.entries[0].entry; + documentListComponent.triggerEventHandler('node-dblclick', { detail: { node: { entry: siteEntry } } }); + expect(store.dispatch).toHaveBeenCalledWith(jasmine.objectContaining({ ...new NavigateLibraryAction(siteEntry) })); }); it('should handle name click', () => { - spyOn(component, 'navigateTo').and.stub(); - const documentList = unitTestingUtils.getByDirective(DocumentListComponent); - documentList.triggerEventHandler('name-click', { detail: { node: { entry: { guid: 'guid' } } } }); - expect(component.navigateTo).toHaveBeenCalledWith({ entry: { guid: 'guid' } } as SiteEntry); + spyOn(store, 'dispatch').and.stub(); + const siteEntry = librariesMock.list.entries[0].entry; + documentListComponent.triggerEventHandler('name-click', { detail: { node: { entry: siteEntry } } }); + expect(store.dispatch).toHaveBeenCalledWith(jasmine.objectContaining({ ...new NavigateLibraryAction(siteEntry) })); }); }); }); diff --git a/projects/aca-content/src/lib/components/libraries-base/libraries-base.component.ts b/projects/aca-content/src/lib/components/libraries-base/libraries-base.component.ts index a2c4eb6867..06d7a795a7 100644 --- a/projects/aca-content/src/lib/components/libraries-base/libraries-base.component.ts +++ b/projects/aca-content/src/lib/components/libraries-base/libraries-base.component.ts @@ -86,11 +86,7 @@ export class LibrariesBaseComponent extends PageComponent { protected appHookService = inject(AppHookService); - constructor() { - super(); - } - - navigateTo(node: SiteEntry) { + private navigateTo(node: SiteEntry) { if (node?.entry?.guid) { this.store.dispatch(new NavigateLibraryAction(node.entry, this.navigateRoute)); } diff --git a/projects/aca-content/src/lib/components/libraries/libraries.component.ts b/projects/aca-content/src/lib/components/libraries/libraries.component.ts index 7b64c33adc..935ad23126 100644 --- a/projects/aca-content/src/lib/components/libraries/libraries.component.ts +++ b/projects/aca-content/src/lib/components/libraries/libraries.component.ts @@ -33,10 +33,6 @@ import { LibrariesBaseComponent } from '../libraries-base/libraries-base.compone encapsulation: ViewEncapsulation.None }) export class LibrariesComponent extends LibrariesBaseComponent implements OnInit { - constructor() { - super(); - } - ngOnInit() { super.ngOnInit(); diff --git a/projects/aca-content/src/lib/mock/libraries-mock.ts b/projects/aca-content/src/lib/mock/libraries-mock.ts index 2c273525bf..5c7acd2047 100644 --- a/projects/aca-content/src/lib/mock/libraries-mock.ts +++ b/projects/aca-content/src/lib/mock/libraries-mock.ts @@ -38,12 +38,12 @@ export const librariesMock: SitePaging = { export const favoriteLibrariesMock: FavoritePaging = { list: { entries: [ - { entry: { id: '1', targetGuid: '1', title: 'Favorite Library 1', visibility: 'public' } }, - { entry: { id: '2', targetGuid: '2', title: 'Favorite Library 2', visibility: 'private' } } + { entry: { targetGuid: '1', target: { id: '1', guid: '1', title: 'Favorite Library 1', visibility: 'public' } } }, + { entry: { targetGuid: '2', target: { id: '2', guid: '2', title: 'Favorite Library 2', visibility: 'private' } } } ], pagination: { count: 25, skipCount: 0 } } -} as any as FavoritePaging; +}; export const libraryColumnsPresetMock: DocumentListPresetRef[] = [ {