Skip to content

Commit becc3ed

Browse files
committed
[ACS-10165] create all libraries component
1 parent 20fba59 commit becc3ed

File tree

6 files changed

+467
-0
lines changed

6 files changed

+467
-0
lines changed

projects/aca-content/assets/app.extensions.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,13 @@
175175
"description": "APP.BROWSE.LIBRARIES.MENU.FAVORITE_LIBRARIES.SIDENAV_LINK.TOOLTIP",
176176
"route": "favorite/libraries"
177177
},
178+
{
179+
"id": "app.navbar.libraries.all",
180+
"order": 350,
181+
"title": "APP.BROWSE.LIBRARIES.MENU.ALL_LIBRARIES.SIDENAV_LINK.LABEL",
182+
"description": "APP.BROWSE.LIBRARIES.MENU.ALL_LIBRARIES.SIDENAV_LINK.TOOLTIP",
183+
"route": "all/libraries"
184+
},
178185
{
179186
"id": "app.navbar.recentFiles",
180187
"order": 400,

projects/aca-content/assets/i18n/en.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@
135135
"FAVORITE_LIBRARIES": {
136136
"TITLE": "No Favorite Libraries",
137137
"TEXT": "Favorite a library that you want to find easily later."
138+
},
139+
"ALL_LIBRARIES": {
140+
"TITLE": "No Libraries",
141+
"TEXT": "There are no libraries you can view."
138142
}
139143
},
140144
"MENU": {
@@ -151,6 +155,13 @@
151155
"LABEL": "Favorite Libraries",
152156
"TOOLTIP": "Access my favorite libraries"
153157
}
158+
},
159+
"ALL_LIBRARIES": {
160+
"TITLE": "All Libraries",
161+
"SIDENAV_LINK": {
162+
"LABEL": "All Libraries",
163+
"TOOLTIP": "Access all libraries"
164+
}
154165
}
155166
},
156167
"ERRORS": {

projects/aca-content/src/lib/aca-content.routes.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import { TrashcanComponent } from './components/trashcan/trashcan.component';
4242
import { ShellLayoutComponent } from '@alfresco/adf-core/shell';
4343
import { SearchAiResultsComponent } from './components/knowledge-retrieval/search-ai/search-ai-results/search-ai-results.component';
4444
import { SavedSearchesSmartListComponent } from './components/search/search-save/list/smart-list/saved-searches-smart-list.component';
45+
import { LibraryListComponent } from './components/library-list/library-list.component';
4546

4647
export const CONTENT_ROUTES: ExtensionRoute[] = [
4748
{
@@ -230,6 +231,24 @@ export const CONTENT_LAYOUT_ROUTES: Route[] = [
230231
...createViewRoutes('libraries')
231232
]
232233
},
234+
{
235+
path: 'all',
236+
children: [
237+
{
238+
path: '',
239+
pathMatch: 'full',
240+
redirectTo: 'libraries'
241+
},
242+
{
243+
path: 'libraries',
244+
component: LibraryListComponent,
245+
data: {
246+
title: 'APP.BROWSE.LIBRARIES.MENU.ALL_LIBRARIES.TITLE',
247+
sortingPreferenceKey: 'all-libraries'
248+
}
249+
}
250+
]
251+
},
233252
{
234253
path: 'favorites',
235254
data: {
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<aca-page-layout>
2+
<div class="aca-page-layout-header">
3+
<h1 class="aca-page-title">
4+
{{ (selectedRowItemsCount < 1 ? 'APP.BROWSE.LIBRARIES.MENU.ALL_LIBRARIES.TITLE' : 'APP.HEADER.SELECTED') | translate: { count: selectedRowItemsCount } }}
5+
</h1>
6+
<aca-toolbar [items]="actions" />
7+
</div>
8+
9+
<div class="aca-page-layout-content">
10+
<div class="aca-main-content">
11+
<adf-document-list
12+
#documentList
13+
acaDocumentList
14+
acaContextActions
15+
[node]="$any(list)"
16+
[loading]="isLoading"
17+
[selectionMode]="'multiple'"
18+
[multiselect]="true"
19+
[navigate]="false"
20+
[sorting]="['title', 'asc']"
21+
[sortingMode]="'client'"
22+
[displayCheckboxesOnHover]="true"
23+
[preselectNodes]="selectedNodesState?.nodes"
24+
(node-dblclick)="handleNodeClick($event)"
25+
[imageResolver]="imageResolver"
26+
(selectedItemsCountChanged)="onSelectedItemsCountChanged($event)"
27+
[isResizingEnabled]="true"
28+
[blurOnResize]="false"
29+
(name-click)="handleNodeClick($event)"
30+
>
31+
<adf-custom-empty-content-template>
32+
<adf-empty-content
33+
icon="library_books"
34+
[title]="'APP.BROWSE.LIBRARIES.EMPTY_STATE.ALL_LIBRARIES.TITLE'"
35+
subtitle="APP.BROWSE.LIBRARIES.EMPTY_STATE.ALL_LIBRARIES.TEXT"
36+
/>
37+
</adf-custom-empty-content-template>
38+
39+
<data-columns>
40+
<ng-container *ngFor="let column of columns; trackBy: trackByColumnId">
41+
<ng-container *ngIf="column.template && !(column.desktopOnly && isSmallScreen)">
42+
<data-column
43+
[id]="column.id"
44+
[draggable]="column.draggable"
45+
[resizable]="column.resizable"
46+
[key]="column.key"
47+
[title]="column.title"
48+
[type]="column.type"
49+
[format]="column.format"
50+
[class]="column.class"
51+
[sortable]="column.sortable"
52+
[isHidden]="column.isHidden"
53+
[sortingKey]="column.sortingKey || column.key"
54+
>
55+
<ng-template let-context>
56+
<adf-dynamic-column [id]="column.template" [context]="context" />
57+
</ng-template>
58+
</data-column>
59+
</ng-container>
60+
61+
<ng-container *ngIf="!column.template && !(column.desktopOnly && isSmallScreen)">
62+
<data-column
63+
[id]="column.id"
64+
[key]="column.key"
65+
[draggable]="column.draggable"
66+
[resizable]="column.resizable"
67+
[title]="column.title"
68+
[type]="column.type"
69+
[format]="column.format"
70+
[class]="column.class"
71+
[sortable]="column.sortable"
72+
[isHidden]="column.isHidden"
73+
[sortingKey]="column.sortingKey || column.key"
74+
/>
75+
</ng-container>
76+
</ng-container>
77+
</data-columns>
78+
</adf-document-list>
79+
80+
<adf-pagination
81+
[target]="documentList"
82+
[pagination]="pagination"
83+
(changePageSize)="onChangePageSize($event)"
84+
(changePageNumber)="onChange($event)"
85+
(nextPage)="onChange($event)"
86+
(prevPage)="onChange($event)"
87+
/>
88+
</div>
89+
90+
<div class="aca-sidebar" *ngIf="infoDrawerOpened$ | async">
91+
<aca-info-drawer [node]="selection.last" />
92+
</div>
93+
</div>
94+
</aca-page-layout>
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/*!
2+
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
3+
*
4+
* Alfresco Example Content Application
5+
*
6+
* This file is part of the Alfresco Example Content Application.
7+
* If the software was purchased under a paid Alfresco license, the terms of
8+
* the paid license agreement will prevail. Otherwise, the software is
9+
* provided under the following open source license terms:
10+
*
11+
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
12+
* it under the terms of the GNU Lesser General Public License as published by
13+
* the Free Software Foundation, either version 3 of the License, or
14+
* (at your option) any later version.
15+
*
16+
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
17+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
18+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19+
* GNU Lesser General Public License for more details.
20+
*
21+
* You should have received a copy of the GNU Lesser General Public License
22+
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
23+
*/
24+
25+
import { ComponentFixture, TestBed } from '@angular/core/testing';
26+
import { Router } from '@angular/router';
27+
import { UnitTestingUtils, UserPreferencesService } from '@alfresco/adf-core';
28+
import { DocumentListComponent, SitesService } from '@alfresco/adf-content-services';
29+
import { LibraryListComponent } from './library-list.component';
30+
import { AppTestingModule } from '../../testing/app-testing.module';
31+
import { AppHookService } from '@alfresco/aca-shared';
32+
import { provideEffects } from '@ngrx/effects';
33+
import { RouterEffects } from '@alfresco/aca-shared/store';
34+
import { of, throwError } from 'rxjs';
35+
import { LibraryEffects } from '../../store/effects';
36+
import { getTitleElementText } from '../../testing/test-utils';
37+
import { MatSnackBarModule } from '@angular/material/snack-bar';
38+
import { Pagination, SiteEntry, SitePaging } from '@alfresco/js-api';
39+
40+
describe('LibraryListComponent', () => {
41+
let fixture: ComponentFixture<LibraryListComponent>;
42+
let component: LibraryListComponent;
43+
let userPreference: UserPreferencesService;
44+
let sitesService: SitesService;
45+
let router: Router;
46+
let appHookService: AppHookService;
47+
let getSitesSpy: jasmine.Spy;
48+
let unitTestingUtils: UnitTestingUtils;
49+
50+
const paging: SitePaging = {
51+
list: {
52+
entries: [
53+
{ entry: { id: '1', guid: '1', title: 'Library 1', visibility: 'public' } },
54+
{ entry: { id: '2', guid: '2', title: 'Library 2', visibility: 'private' } }
55+
],
56+
pagination: { count: 25, skipCount: 0 }
57+
}
58+
};
59+
60+
beforeEach(async () => {
61+
TestBed.configureTestingModule({
62+
imports: [AppTestingModule, LibraryListComponent, MatSnackBarModule],
63+
providers: [provideEffects([RouterEffects, LibraryEffects])]
64+
});
65+
66+
fixture = TestBed.createComponent(LibraryListComponent);
67+
component = fixture.componentInstance;
68+
69+
unitTestingUtils = new UnitTestingUtils(fixture.debugElement);
70+
sitesService = TestBed.inject(SitesService);
71+
userPreference = TestBed.inject(UserPreferencesService);
72+
appHookService = TestBed.inject(AppHookService);
73+
router = TestBed.inject(Router);
74+
75+
getSitesSpy = spyOn(sitesService, 'getSites');
76+
getSitesSpy.and.returnValue(of(paging));
77+
fixture.detectChanges();
78+
});
79+
80+
describe('on initialization', () => {
81+
it('should set data', () => {
82+
expect(component.list).toBe(paging);
83+
expect(component.pagination).toBe(paging.list.pagination);
84+
});
85+
86+
it('should get data with user preference pagination size', () => {
87+
userPreference.paginationSize = 1;
88+
component.ngOnInit();
89+
expect(sitesService.getSites).toHaveBeenCalledWith({ maxItems: userPreference.paginationSize });
90+
});
91+
92+
it('should set data on error', () => {
93+
getSitesSpy.and.returnValue(throwError(() => 'error'));
94+
component.ngOnInit();
95+
96+
expect(component.list).toBe(null);
97+
expect(component.pagination).toBe(null);
98+
expect(component.isLoading).toBe(false);
99+
});
100+
101+
it('should set title based on selectedRowItemsCount', () => {
102+
expect(getTitleElementText(fixture)).toBe('APP.BROWSE.LIBRARIES.MENU.ALL_LIBRARIES.TITLE');
103+
104+
component.selectedRowItemsCount = 5;
105+
fixture.detectChanges();
106+
107+
expect(getTitleElementText(fixture)).toBe('APP.HEADER.SELECTED');
108+
});
109+
110+
it('should handle no columns preset in extensions', () => {
111+
component['extensions'].documentListPresets.libraries = undefined;
112+
component.ngOnInit();
113+
expect(component.columns.length).toBe(0);
114+
});
115+
});
116+
117+
describe('Node navigation', () => {
118+
it('should not navigate when node is null or missing guid', () => {
119+
spyOn(router, 'navigate').and.stub();
120+
component.navigateTo(null);
121+
expect(router.navigate).not.toHaveBeenCalled();
122+
123+
component.navigateTo({ entry: {} } as any);
124+
expect(router.navigate).not.toHaveBeenCalled();
125+
});
126+
127+
it('should dispatch navigation action when node has guid', () => {
128+
spyOn(component['store'], 'dispatch').and.stub();
129+
component.navigateTo({ entry: { guid: 'guid' } } as SiteEntry);
130+
expect(component['store'].dispatch).toHaveBeenCalled();
131+
});
132+
133+
it('should handle node double click', () => {
134+
spyOn(component, 'navigateTo').and.stub();
135+
const documentList = unitTestingUtils.getByDirective(DocumentListComponent);
136+
documentList.triggerEventHandler('node-dblclick', { detail: { node: { entry: { guid: 'guid' } } } });
137+
expect(component.navigateTo).toHaveBeenCalledWith({ entry: { guid: 'guid' } } as SiteEntry);
138+
});
139+
140+
it('should handle name click', () => {
141+
spyOn(component, 'navigateTo').and.stub();
142+
const documentList = unitTestingUtils.getByDirective(DocumentListComponent);
143+
documentList.triggerEventHandler('name-click', { detail: { node: { entry: { guid: 'guid' } } } });
144+
expect(component.navigateTo).toHaveBeenCalledWith({ entry: { guid: 'guid' } } as SiteEntry);
145+
});
146+
});
147+
148+
describe('Reload on actions', () => {
149+
it('should reload on libraryDeleted action', () => {
150+
appHookService.libraryDeleted.next('');
151+
expect(sitesService.getSites).toHaveBeenCalled();
152+
});
153+
154+
it('should reload on libraryUpdated action', () => {
155+
appHookService.libraryUpdated.next(paging.list.entries[0]);
156+
expect(sitesService.getSites).toHaveBeenCalled();
157+
});
158+
159+
it('should reload on libraryJoined action', () => {
160+
appHookService.libraryJoined.next();
161+
expect(sitesService.getSites).toHaveBeenCalled();
162+
});
163+
164+
it('should reload on libraryLeft action', () => {
165+
appHookService.libraryLeft.next('');
166+
expect(sitesService.getSites).toHaveBeenCalled();
167+
});
168+
});
169+
170+
describe('Pagination', () => {
171+
const pagination: Pagination = {
172+
count: 100,
173+
hasMoreItems: true,
174+
totalItems: 300,
175+
skipCount: 25,
176+
maxItems: 25
177+
};
178+
179+
it('should get list with pagination data onChange event', () => {
180+
component.onChange(pagination);
181+
expect(sitesService.getSites).toHaveBeenCalledWith(pagination);
182+
});
183+
184+
it('should get list with pagination data onChangePageSize event', () => {
185+
component.onChangePageSize(pagination);
186+
expect(sitesService.getSites).toHaveBeenCalledWith(pagination);
187+
});
188+
189+
it('should set preference page size onChangePageSize event', () => {
190+
component.onChangePageSize(pagination);
191+
expect(userPreference.paginationSize).toBe(pagination.maxItems);
192+
});
193+
});
194+
});

0 commit comments

Comments
 (0)