mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-31 17:38:48 +00:00
ACS-7403: migrate site dropdown to standalone (#9847)
This commit is contained in:
@@ -21,15 +21,15 @@ import { By } from '@angular/platform-browser';
|
||||
import { Node, NodeEntry, NodePaging, RequestScope, ResultSetPaging, SiteEntry, SitePaging, SitePagingList } from '@alfresco/js-api';
|
||||
import { of } from 'rxjs';
|
||||
import { ContentNodeSelectorPanelComponent } from './content-node-selector-panel.component';
|
||||
import { ContentTestingModule } from '../testing/content.testing.module';
|
||||
import { DocumentListService } from '../document-list/services/document-list.service';
|
||||
import { DocumentListComponent } from '../document-list/components/document-list.component';
|
||||
import { CustomResourcesService } from '../document-list/services/custom-resources.service';
|
||||
import { NodeEntryEvent, ShareDataRow } from '../document-list';
|
||||
import { SearchQueryBuilderService } from '../search';
|
||||
import { mockSearchRequest } from '../mock/search-query.mock';
|
||||
import { SitesService } from '../common/services/sites.service';
|
||||
import { NodesApiService } from '../common/services/nodes-api.service';
|
||||
import { ContentTestingModule } from '../../testing/content.testing.module';
|
||||
import { DocumentListService } from '../../document-list/services/document-list.service';
|
||||
import { DocumentListComponent } from '../../document-list/components/document-list.component';
|
||||
import { CustomResourcesService } from '../../document-list/services/custom-resources.service';
|
||||
import { NodeEntryEvent, ShareDataRow } from '../../document-list';
|
||||
import { SearchQueryBuilderService } from '../../search';
|
||||
import { mockSearchRequest } from '../../mock/search-query.mock';
|
||||
import { SitesService } from '../../common/services/sites.service';
|
||||
import { NodesApiService } from '../../common/services/nodes-api.service';
|
||||
|
||||
const fakeResultSetPaging: ResultSetPaging = {
|
||||
list: {
|
@@ -147,8 +147,7 @@ h2.adf-search-results-label {
|
||||
.adf-datatable-body .adf-datatable-row {
|
||||
min-height: 40px;
|
||||
|
||||
@media screen and (-ms-high-contrast: active),
|
||||
screen and (-ms-high-contrast: none) {
|
||||
@media screen and (-ms-high-contrast: active), screen and (-ms-high-contrast: none) {
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
@@ -20,17 +20,17 @@ import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testin
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Node, NodeEntry, NodePaging, ResultSetPaging, Site, SiteEntry, SitePaging, SitePagingList, UserInfo } from '@alfresco/js-api';
|
||||
import { DataRow, ThumbnailService, DataColumn } from '@alfresco/adf-core';
|
||||
import { ContentService, UploadService, NodesApiService, SitesService, FileModel, FileUploadStatus, FileUploadCompleteEvent } from '../common';
|
||||
import { ContentService, UploadService, NodesApiService, SitesService, FileModel, FileUploadStatus, FileUploadCompleteEvent } from '../../common';
|
||||
import { of, throwError } from 'rxjs';
|
||||
import { DropdownBreadcrumbComponent } from '../breadcrumb';
|
||||
import { DropdownBreadcrumbComponent } from '../../breadcrumb';
|
||||
import { ContentNodeSelectorPanelComponent } from './content-node-selector-panel.component';
|
||||
import { ContentTestingModule } from '../testing/content.testing.module';
|
||||
import { DocumentListService } from '../document-list/services/document-list.service';
|
||||
import { ContentTestingModule } from '../../testing/content.testing.module';
|
||||
import { DocumentListService } from '../../document-list/services/document-list.service';
|
||||
import { DropdownSitesComponent } from '../site-dropdown/sites-dropdown.component';
|
||||
import { NodeEntryEvent, ShareDataRow, ShareDataTableAdapter } from '../document-list';
|
||||
import { SearchQueryBuilderService } from '../search';
|
||||
import { NodeEntryEvent, ShareDataRow, ShareDataTableAdapter } from '../../document-list';
|
||||
import { SearchQueryBuilderService } from '../../search';
|
||||
import { ContentNodeSelectorPanelService } from './content-node-selector-panel.service';
|
||||
import { mockContentModelTextProperty } from '../mock/content-model.mock';
|
||||
import { mockContentModelTextProperty } from '../../mock/content-model.mock';
|
||||
|
||||
const fakeResultSetPaging: ResultSetPaging = {
|
||||
list: {
|
@@ -25,18 +25,18 @@ import {
|
||||
DataSorting,
|
||||
ShowHeaderMode
|
||||
} from '@alfresco/adf-core';
|
||||
import { NodesApiService, UploadService, FileUploadCompleteEvent, FileUploadDeleteEvent, SitesService } from '../common';
|
||||
import { NodesApiService, UploadService, FileUploadCompleteEvent, FileUploadDeleteEvent, SitesService } from '../../common';
|
||||
import { UntypedFormControl } from '@angular/forms';
|
||||
import { Node, NodePaging, Pagination, SiteEntry, SitePaging, NodeEntry, SearchRequest, RequestScope } from '@alfresco/js-api';
|
||||
import { DocumentListComponent } from '../document-list/components/document-list.component';
|
||||
import { RowFilter } from '../document-list/data/row-filter.model';
|
||||
import { ImageResolver } from '../document-list/data/image-resolver.model';
|
||||
import { CustomResourcesService } from '../document-list/services/custom-resources.service';
|
||||
import { ShareDataRow } from '../document-list/data/share-data-row.model';
|
||||
import { NodeEntryEvent } from '../document-list/components/node.event';
|
||||
import { DocumentListComponent } from '../../document-list/components/document-list.component';
|
||||
import { RowFilter } from '../../document-list/data/row-filter.model';
|
||||
import { ImageResolver } from '../../document-list/data/image-resolver.model';
|
||||
import { CustomResourcesService } from '../../document-list/services/custom-resources.service';
|
||||
import { ShareDataRow } from '../../document-list/data/share-data-row.model';
|
||||
import { NodeEntryEvent } from '../../document-list/components/node.event';
|
||||
import { debounceTime, takeUntil } from 'rxjs/operators';
|
||||
import { Subject } from 'rxjs';
|
||||
import { SearchQueryBuilderService } from '../search';
|
||||
import { SearchQueryBuilderService } from '../../search';
|
||||
import { ContentNodeSelectorPanelService } from './content-node-selector-panel.service';
|
||||
|
||||
export type ValidationFunction = (entry: Node) => boolean;
|
@@ -16,10 +16,9 @@
|
||||
*/
|
||||
|
||||
import { ContentNodeSelectorPanelService } from './content-node-selector-panel.service';
|
||||
import { mockContentModelDateProperty, mockContentModelTextProperty, mockConvertedSearchCategoriesFromModels } from '../mock/content-model.mock';
|
||||
import { mockContentModelDateProperty, mockContentModelTextProperty, mockConvertedSearchCategoriesFromModels } from '../../mock/content-model.mock';
|
||||
|
||||
describe('ContentNodeSelectorPanelService', () => {
|
||||
|
||||
const contentNodeSelectorPanelService = new ContentNodeSelectorPanelService();
|
||||
|
||||
it('should support text type', () => {
|
||||
@@ -42,7 +41,7 @@ describe('ContentNodeSelectorPanelService', () => {
|
||||
});
|
||||
|
||||
it('should modelPropertyTypeToSearchFilterTypeMap contain only the supported types', () => {
|
||||
const expectedSupportedTypesMap = new Map<string, string> ();
|
||||
const expectedSupportedTypesMap = new Map<string, string>();
|
||||
expectedSupportedTypesMap.set('d:text', 'text');
|
||||
expectedSupportedTypesMap.set('d:date', 'date-range');
|
||||
expectedSupportedTypesMap.set('d:datetime', 'datetime-range');
|
@@ -16,15 +16,14 @@
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { SearchCategory } from '../search/models/search-category.interface';
|
||||
import { SearchCategory } from '../../search/models/search-category.interface';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ContentNodeSelectorPanelService {
|
||||
|
||||
propertyTypes = ['d:text', 'd:date', 'd:datetime'];
|
||||
modelPropertyTypeToSearchFilterTypeMap = new Map<string, string> ();
|
||||
modelPropertyTypeToSearchFilterTypeMap = new Map<string, string>();
|
||||
customModels: any[];
|
||||
|
||||
constructor() {
|
||||
@@ -35,7 +34,7 @@ export class ContentNodeSelectorPanelService {
|
||||
|
||||
convertCustomModelPropertiesToSearchCategories(): SearchCategory[] {
|
||||
const searchConfig: SearchCategory[] = [];
|
||||
this.customModels?.forEach( (propertyModel) => {
|
||||
this.customModels?.forEach((propertyModel) => {
|
||||
searchConfig.push(this.convertModelPropertyIntoSearchFilter(propertyModel));
|
||||
});
|
||||
|
||||
@@ -46,7 +45,7 @@ export class ContentNodeSelectorPanelService {
|
||||
let filterSearch: SearchCategory;
|
||||
if (this.isTypeSupported(modelProperty.dataType)) {
|
||||
filterSearch = {
|
||||
id : modelProperty.prefixedName,
|
||||
id: modelProperty.prefixedName,
|
||||
name: modelProperty.prefixedName,
|
||||
expanded: false,
|
||||
enabled: true,
|
||||
@@ -66,5 +65,4 @@ export class ContentNodeSelectorPanelService {
|
||||
isTypeSupported(dataType: string): boolean {
|
||||
return this.propertyTypes.includes(dataType);
|
||||
}
|
||||
|
||||
}
|
@@ -29,7 +29,7 @@ import { ContentTestingModule } from '../testing/content.testing.module';
|
||||
import { DocumentListService } from '../document-list/services/document-list.service';
|
||||
import { DocumentListComponent } from '../document-list/components/document-list.component';
|
||||
import { UploadModule } from '../upload';
|
||||
import { ContentNodeSelectorPanelComponent } from './content-node-selector-panel.component';
|
||||
import { ContentNodeSelectorPanelComponent } from './content-node-selector-panel/content-node-selector-panel.component';
|
||||
import { NodeAction } from '../document-list/models/node-action.enum';
|
||||
import { SitesService } from '../common/services/sites.service';
|
||||
import { NodesApiService } from '../common/services/nodes-api.service';
|
||||
|
@@ -19,10 +19,8 @@ import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MaterialModule } from '../material.module';
|
||||
|
||||
import { ContentNodeSelectorPanelComponent } from './content-node-selector-panel.component';
|
||||
import { ContentNodeSelectorPanelComponent } from './content-node-selector-panel/content-node-selector-panel.component';
|
||||
import { ContentNodeSelectorComponent } from './content-node-selector.component';
|
||||
import { SitesDropdownModule } from '../site-dropdown/sites-dropdown.module';
|
||||
import { BreadcrumbModule } from '../breadcrumb/breadcrumb.module';
|
||||
import { SearchModule } from '../search/search.module';
|
||||
import { CoreModule } from '@alfresco/adf-core';
|
||||
@@ -31,6 +29,7 @@ import { NameLocationCellComponent } from './name-location-cell/name-location-ce
|
||||
import { UploadModule } from '../upload/upload.module';
|
||||
import { SearchQueryBuilderService } from '../search/services/search-query-builder.service';
|
||||
import { ContentDirectiveModule } from '../directives/content-directive.module';
|
||||
import { DropdownSitesComponent } from './site-dropdown/sites-dropdown.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -39,23 +38,16 @@ import { ContentDirectiveModule } from '../directives/content-directive.module';
|
||||
CoreModule,
|
||||
CommonModule,
|
||||
MaterialModule,
|
||||
SitesDropdownModule,
|
||||
DropdownSitesComponent,
|
||||
BreadcrumbModule,
|
||||
SearchModule,
|
||||
DocumentListModule,
|
||||
UploadModule,
|
||||
ContentDirectiveModule
|
||||
],
|
||||
exports: [
|
||||
ContentNodeSelectorPanelComponent,
|
||||
NameLocationCellComponent,
|
||||
ContentNodeSelectorComponent
|
||||
],
|
||||
declarations: [
|
||||
ContentNodeSelectorPanelComponent,
|
||||
NameLocationCellComponent,
|
||||
ContentNodeSelectorComponent
|
||||
ContentDirectiveModule,
|
||||
NameLocationCellComponent
|
||||
],
|
||||
exports: [ContentNodeSelectorPanelComponent, NameLocationCellComponent, ContentNodeSelectorComponent],
|
||||
declarations: [ContentNodeSelectorPanelComponent, ContentNodeSelectorComponent],
|
||||
providers: [SearchQueryBuilderService]
|
||||
})
|
||||
export class ContentNodeSelectorModule {}
|
||||
|
@@ -27,9 +27,7 @@ describe('NameLocationCellComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
NameLocationCellComponent
|
||||
]
|
||||
imports: [NameLocationCellComponent]
|
||||
});
|
||||
fixture = TestBed.createComponent(NameLocationCellComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@@ -20,6 +20,7 @@ import { DataRow } from '@alfresco/adf-core';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-name-location-cell',
|
||||
standalone: true,
|
||||
template: `
|
||||
<div class="adf-name-location-cell-name adf-datatable-cell-value" [title]="name">{{ name }}</div>
|
||||
<div class="adf-name-location-cell-location adf-datatable-cell-value" [title]="path">{{ path }}</div>
|
||||
@@ -30,7 +31,6 @@ import { DataRow } from '@alfresco/adf-core';
|
||||
host: { class: 'adf-name-location-cell adf-datatable-content-cell' }
|
||||
})
|
||||
export class NameLocationCellComponent implements OnInit {
|
||||
|
||||
name: string = '';
|
||||
path: string = '';
|
||||
|
||||
|
@@ -16,10 +16,11 @@
|
||||
*/
|
||||
|
||||
export * from './name-location-cell/name-location-cell.component';
|
||||
export * from './site-dropdown/sites-dropdown.component';
|
||||
export * from './content-node-selector.component-data.interface';
|
||||
export * from './content-node-selector-panel.component';
|
||||
export * from './content-node-selector-panel/content-node-selector-panel.component';
|
||||
export * from './content-node-selector.component';
|
||||
export * from './content-node-dialog.service';
|
||||
export * from './content-node-selector-panel.service';
|
||||
export * from './content-node-selector-panel/content-node-selector-panel.service';
|
||||
|
||||
export * from './content-node-selector.module';
|
||||
|
@@ -0,0 +1,24 @@
|
||||
<div id="site-dropdown-container" class="adf-site-dropdown-container">
|
||||
<mat-form-field class="adf-sites-dropdown-form-field">
|
||||
<mat-label>{{ 'NODE_SELECTOR.LOCATION' | translate }}</mat-label>
|
||||
<mat-select
|
||||
adf-infinite-select-scroll
|
||||
(scrollEnd)="loadAllOnScroll()"
|
||||
data-automation-id="site-my-files-option"
|
||||
class="adf-site-dropdown-list-element"
|
||||
id="site-dropdown"
|
||||
placeholder="{{placeholder | translate}}"
|
||||
[(value)]="selected"
|
||||
(selectionChange)="selectedSite($event)">
|
||||
<mat-select-trigger class="adf-sites-dropdown-select-trigger">
|
||||
{{ selected?.entry.title | translate}}
|
||||
</mat-select-trigger>
|
||||
<mat-option *ngFor="let site of siteList?.list.entries;" [value]="site">
|
||||
{{ site.entry.title | translate}}
|
||||
</mat-option>
|
||||
<mat-option *ngIf="showLoading()" disabled="true" data-automation-id="site-loading">
|
||||
{{ 'ADF_DROPDOWN.LOADING' | translate}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
@@ -0,0 +1,310 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { DropdownSitesComponent, Relations } from './sites-dropdown.component';
|
||||
import { AuthenticationService } from '@alfresco/adf-core';
|
||||
import { of } from 'rxjs';
|
||||
import {
|
||||
getFakeSitePaging,
|
||||
getFakeSitePagingNoMoreItems,
|
||||
getFakeSitePagingFirstPage,
|
||||
getFakeSitePagingLastPage,
|
||||
getFakeSitePagingWithMembers
|
||||
} from '../../mock';
|
||||
import { ContentTestingModule } from '../../testing/content.testing.module';
|
||||
import { SitesService } from '../../common/services/sites.service';
|
||||
import { HarnessLoader } from '@angular/cdk/testing';
|
||||
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
|
||||
import { MatSelectHarness } from '@angular/material/select/testing';
|
||||
import { SiteEntry } from '@alfresco/js-api';
|
||||
|
||||
const customSiteList = {
|
||||
list: {
|
||||
entries: [
|
||||
{
|
||||
entry: {
|
||||
guid: '-my-',
|
||||
title: 'PERSONAL_FILES'
|
||||
}
|
||||
},
|
||||
{
|
||||
entry: {
|
||||
guid: '-mysites-',
|
||||
title: 'FILE_LIBRARIES'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
describe('DropdownSitesComponent', () => {
|
||||
let loader: HarnessLoader;
|
||||
let component: any;
|
||||
let fixture: ComponentFixture<DropdownSitesComponent>;
|
||||
let element: HTMLElement;
|
||||
let siteService: SitesService;
|
||||
let authService: AuthenticationService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ContentTestingModule]
|
||||
});
|
||||
});
|
||||
|
||||
describe('Rendering tests', () => {
|
||||
describe('Infinite Loading', () => {
|
||||
beforeEach(() => {
|
||||
siteService = TestBed.inject(SitesService);
|
||||
fixture = TestBed.createComponent(DropdownSitesComponent);
|
||||
element = fixture.nativeElement;
|
||||
component = fixture.componentInstance;
|
||||
spyOn(siteService, 'getSites').and.returnValue(of(getFakeSitePaging()));
|
||||
loader = TestbedHarnessEnvironment.loader(fixture);
|
||||
});
|
||||
|
||||
it('Should show loading item if there are more items', async () => {
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(element.querySelector('[data-automation-id="site-loading"]')).toBeDefined();
|
||||
});
|
||||
|
||||
it('Should not show loading item if there are more items', async () => {
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('[data-automation-id="site-loading"]')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Sites', () => {
|
||||
beforeEach(() => {
|
||||
siteService = TestBed.inject(SitesService);
|
||||
spyOn(siteService, 'getSites').and.returnValue(of(getFakeSitePagingNoMoreItems()));
|
||||
|
||||
fixture = TestBed.createComponent(DropdownSitesComponent);
|
||||
element = fixture.nativeElement;
|
||||
component = fixture.componentInstance;
|
||||
loader = TestbedHarnessEnvironment.loader(fixture);
|
||||
});
|
||||
|
||||
it('Dropdown sites should be rendered', async () => {
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(element.querySelector('#site-dropdown-container')).toBeDefined();
|
||||
expect(element.querySelector('#site-dropdown')).toBeDefined();
|
||||
expect(element.querySelector('#site-dropdown-container')).not.toBeNull();
|
||||
expect(element.querySelector('#site-dropdown')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should show the "My files" option by default', async () => {
|
||||
component.hideMyFiles = false;
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const select = await loader.getHarness(MatSelectHarness);
|
||||
await select.open();
|
||||
|
||||
const options = await select.getOptions();
|
||||
expect(await options[0].getText()).toContain('DROPDOWN.MY_FILES_OPTION');
|
||||
});
|
||||
|
||||
it('should hide the "My files" option if the developer desires that way', async () => {
|
||||
component.hideMyFiles = true;
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const select = await loader.getHarness(MatSelectHarness);
|
||||
await select.open();
|
||||
|
||||
const options = await select.getOptions();
|
||||
expect(await options[0].getText()).not.toContain('DROPDOWN.MY_FILES_OPTION');
|
||||
});
|
||||
|
||||
it('should show the default placeholder label by default', async () => {
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const select = await loader.getHarness(MatSelectHarness);
|
||||
await select.open();
|
||||
|
||||
expect(fixture.nativeElement.innerText.trim()).toContain('NODE_SELECTOR.LOCATION');
|
||||
});
|
||||
|
||||
it('should show custom placeholder label when the "placeholder" input property is given a value', async () => {
|
||||
component.placeholder = 'NODE_SELECTOR.SELECT_LIBRARY';
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const select = await loader.getHarness(MatSelectHarness);
|
||||
await select.open();
|
||||
|
||||
expect(fixture.nativeElement.innerText.trim()).toContain('NODE_SELECTOR.LOCATION');
|
||||
});
|
||||
|
||||
it('should load custom sites when the "siteList" input property is given a value', async () => {
|
||||
component.siteList = customSiteList;
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const select = await loader.getHarness(MatSelectHarness);
|
||||
await select.open();
|
||||
|
||||
const options = await select.getOptions();
|
||||
|
||||
expect(await options[0].getText()).toContain('PERSONAL_FILES');
|
||||
expect(await options[1].getText()).toContain('FILE_LIBRARIES');
|
||||
});
|
||||
|
||||
it('should load sites by default', async () => {
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const select = await loader.getHarness(MatSelectHarness);
|
||||
await select.open();
|
||||
|
||||
const options = await select.getOptions();
|
||||
|
||||
expect(await options[1].getText()).toContain('fake-test-site');
|
||||
expect(await options[2].getText()).toContain('fake-test-2');
|
||||
});
|
||||
|
||||
it('should raise an event when a site is selected', async () => {
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
let site: SiteEntry;
|
||||
component.change.subscribe((value) => (site = value));
|
||||
|
||||
const select = await loader.getHarness(MatSelectHarness);
|
||||
await select.open();
|
||||
|
||||
const options = await select.getOptions();
|
||||
await options[1].click();
|
||||
|
||||
expect(site.entry.guid).toBe('fake-1');
|
||||
});
|
||||
|
||||
it('should be possible to select the default value', async () => {
|
||||
component.value = 'swsdp';
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(component.selected.entry.title).toBe('fake-test-2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Default value', () => {
|
||||
beforeEach(() => {
|
||||
siteService = TestBed.inject(SitesService);
|
||||
spyOn(siteService, 'getSites').and.returnValues(of(getFakeSitePagingFirstPage()), of(getFakeSitePagingLastPage()));
|
||||
|
||||
fixture = TestBed.createComponent(DropdownSitesComponent);
|
||||
component = fixture.componentInstance;
|
||||
loader = TestbedHarnessEnvironment.loader(fixture);
|
||||
});
|
||||
|
||||
it('should load new sites if default value is not in the first page', (done) => {
|
||||
component.value = 'fake-test-4';
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
expect(component.selected.entry.title).toBe('fake-test-4');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should NOT reload infinitely if default value is NOT found after all sites are loaded', (done) => {
|
||||
component.value = 'nonexistent-site';
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
expect(component.selected).toBeUndefined();
|
||||
expect(component.loading).toBeFalsy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Sites with members', () => {
|
||||
beforeEach(() => {
|
||||
siteService = TestBed.inject(SitesService);
|
||||
spyOn(siteService, 'getSites').and.returnValue(of(getFakeSitePagingWithMembers()));
|
||||
|
||||
fixture = TestBed.createComponent(DropdownSitesComponent);
|
||||
element = fixture.nativeElement;
|
||||
component = fixture.componentInstance;
|
||||
loader = TestbedHarnessEnvironment.loader(fixture);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
describe('No relations', () => {
|
||||
beforeEach(() => {
|
||||
component.relations = Relations.Members;
|
||||
authService = TestBed.inject(AuthenticationService);
|
||||
});
|
||||
|
||||
it('should show only sites which logged user is member of when member relation is set', async () => {
|
||||
spyOn(authService, 'getEcmUsername').and.returnValue('test');
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const select = await loader.getHarness(MatSelectHarness);
|
||||
await select.open();
|
||||
|
||||
const options = await select.getOptions();
|
||||
|
||||
expect(await options[1].getText()).toContain('FAKE-SITE-PUBLIC');
|
||||
expect(await options[2].getText()).toContain('FAKE-PRIVATE-SITE-MEMBER');
|
||||
});
|
||||
});
|
||||
|
||||
describe('No relations', () => {
|
||||
beforeEach(() => {
|
||||
component.relations = [];
|
||||
authService = TestBed.inject(AuthenticationService);
|
||||
});
|
||||
|
||||
it('should show all the sites if no relation is set', async () => {
|
||||
spyOn(authService, 'getEcmUsername').and.returnValue('test');
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const select = await loader.getHarness(MatSelectHarness);
|
||||
await select.open();
|
||||
|
||||
const options = await select.getOptions();
|
||||
|
||||
expect(await options[1].getText()).toContain('FAKE-MODERATED-SITE');
|
||||
expect(await options[2].getText()).toContain('FAKE-SITE-PUBLIC');
|
||||
expect(await options[3].getText()).toContain('FAKE-PRIVATE-SITE-MEMBER');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,196 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
|
||||
import { InfiniteSelectScrollDirective, AuthenticationService } from '@alfresco/adf-core';
|
||||
import { SitePaging, SiteEntry, Site } from '@alfresco/js-api';
|
||||
import { MatSelectChange, MatSelectModule } from '@angular/material/select';
|
||||
import { LiveAnnouncer } from '@angular/cdk/a11y';
|
||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
import { SitesService } from '../../common/services/sites.service';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
|
||||
/* eslint-disable no-shadow */
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
export enum Relations {
|
||||
Members = 'members',
|
||||
Containers = 'containers'
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'adf-sites-dropdown',
|
||||
standalone: true,
|
||||
imports: [CommonModule, TranslateModule, MatFormFieldModule, MatSelectModule, InfiniteSelectScrollDirective],
|
||||
templateUrl: './sites-dropdown.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'adf-sites-dropdown' }
|
||||
})
|
||||
export class DropdownSitesComponent implements OnInit {
|
||||
/** Hide the "My Files" option. */
|
||||
@Input()
|
||||
hideMyFiles: boolean = false;
|
||||
|
||||
/**
|
||||
* A custom list of sites to be displayed by the dropdown. If no value
|
||||
* is given, the sites of the current user are displayed by default. A
|
||||
* list of objects only with properties 'title' and 'guid' is enough to
|
||||
* be able to display the dropdown.
|
||||
*/
|
||||
@Input()
|
||||
siteList: SitePaging = null;
|
||||
|
||||
/** Id of the selected site */
|
||||
@Input()
|
||||
value: string = null;
|
||||
|
||||
/**
|
||||
* Text or a translation key to act as a placeholder. Default value is the
|
||||
* key "DROPDOWN.PLACEHOLDER_LABEL".
|
||||
*/
|
||||
@Input()
|
||||
placeholder: string = 'DROPDOWN.PLACEHOLDER_LABEL';
|
||||
|
||||
/**
|
||||
* Filter for the results of the sites query. Possible values are
|
||||
* "members" and "containers". When "members" is used, the site list
|
||||
* will be restricted to the sites that the user is a member of.
|
||||
*/
|
||||
@Input()
|
||||
relations: string;
|
||||
|
||||
/**
|
||||
* Emitted when the user selects a site. When the default option is selected,
|
||||
* an empty model is emitted.
|
||||
*/
|
||||
@Output()
|
||||
change = new EventEmitter<SiteEntry>();
|
||||
|
||||
@Output()
|
||||
error = new EventEmitter<any>();
|
||||
|
||||
private loading = true;
|
||||
private skipCount = 0;
|
||||
|
||||
selected: SiteEntry = null;
|
||||
MY_FILES_VALUE = '-my-';
|
||||
|
||||
constructor(
|
||||
private authService: AuthenticationService,
|
||||
private sitesService: SitesService,
|
||||
private liveAnnouncer: LiveAnnouncer,
|
||||
private translateService: TranslateService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
if (!this.siteList) {
|
||||
this.loadSiteList();
|
||||
}
|
||||
}
|
||||
|
||||
loadAllOnScroll() {
|
||||
if (this.isInfiniteScrollingEnabled()) {
|
||||
this.loading = true;
|
||||
this.loadSiteList();
|
||||
}
|
||||
}
|
||||
|
||||
selectedSite(event: MatSelectChange) {
|
||||
this.liveAnnouncer.announce(
|
||||
this.translateService.instant('ADF_DROPDOWN.SELECTION_ARIA_LABEL', {
|
||||
placeholder: this.translateService.instant(this.placeholder),
|
||||
selectedOption: this.translateService.instant(event.value.entry.title)
|
||||
})
|
||||
);
|
||||
this.change.emit(event.value);
|
||||
}
|
||||
|
||||
private loadSiteList() {
|
||||
const extendedOptions: any = {
|
||||
skipCount: this.skipCount,
|
||||
maxItems: InfiniteSelectScrollDirective.MAX_ITEMS
|
||||
};
|
||||
|
||||
this.skipCount += InfiniteSelectScrollDirective.MAX_ITEMS;
|
||||
|
||||
if (this.relations) {
|
||||
extendedOptions.relations = [this.relations];
|
||||
}
|
||||
|
||||
this.sitesService.getSites(extendedOptions).subscribe(
|
||||
(sitePaging: SitePaging) => {
|
||||
if (!this.siteList) {
|
||||
this.siteList = this.relations === Relations.Members ? this.filteredResultsByMember(sitePaging) : sitePaging;
|
||||
|
||||
if (!this.hideMyFiles) {
|
||||
const siteEntry = new SiteEntry({
|
||||
entry: new Site({ id: this.MY_FILES_VALUE, guid: this.MY_FILES_VALUE, title: 'DROPDOWN.MY_FILES_OPTION' })
|
||||
});
|
||||
|
||||
this.siteList.list.entries.unshift(siteEntry);
|
||||
|
||||
if (!this.value) {
|
||||
this.value = this.MY_FILES_VALUE;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const siteList: SitePaging = this.relations === Relations.Members ? this.filteredResultsByMember(sitePaging) : sitePaging;
|
||||
|
||||
this.siteList.list.entries = this.siteList.list.entries.concat(siteList.list.entries);
|
||||
this.siteList.list.pagination = sitePaging.list.pagination;
|
||||
}
|
||||
|
||||
this.selected = this.siteList.list.entries.find((site: SiteEntry) => site.entry.id === this.value);
|
||||
|
||||
if (this.value && !this.selected && this.siteListHasMoreItems()) {
|
||||
this.loadSiteList();
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
},
|
||||
(error) => {
|
||||
this.error.emit(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
showLoading(): boolean {
|
||||
return this.loading && this.siteListHasMoreItems();
|
||||
}
|
||||
|
||||
isInfiniteScrollingEnabled(): boolean {
|
||||
return !this.loading && this.siteListHasMoreItems();
|
||||
}
|
||||
|
||||
private siteListHasMoreItems(): boolean {
|
||||
return this.siteList?.list.pagination?.hasMoreItems;
|
||||
}
|
||||
|
||||
private filteredResultsByMember(sites: SitePaging): SitePaging {
|
||||
const loggedUserName = this.authService.getEcmUsername();
|
||||
sites.list.entries = sites.list.entries.filter((site) => this.isCurrentUserMember(site, loggedUserName));
|
||||
return sites;
|
||||
}
|
||||
|
||||
private isCurrentUserMember(site: SiteEntry, loggedUserName: string): boolean {
|
||||
return (
|
||||
site.entry.visibility === 'PUBLIC' ||
|
||||
!!site.relations.members.list.entries.find((member) => member.entry.id.toLowerCase() === loggedUserName.toLowerCase())
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user