mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[ACS-5483] group details view general info and list of assigned users (#9329)
* ACS-5483 Added possibility to load and update group * ACS-5483 Implemented unsaved changes dialog * ACS-5483 Removed console log * ACS-5483 Made dynamic chip list reusable * ACS-5483 Fix for more than one row chips * ACS-5483 Fix for pagination * ACS-5483 Added some fixes * ACS-5483 Fixed displaying tags for node * ACS-5483 Renamed css classes * ACS-5483 Fixed resizing when chips have pagination * ACS-5483 Clearing code * ACS-5483 Documentation for dynamic chip list component * ACS-5483 Documentation for unsaved changes dialog and guard * ACS-5483 Documentation for group service * ACS-5483 Unit tests for GroupService * ACS-5483 Unit tests for dynamic chip list component * ACS-5483 Changed fdescribe to describe * ACS-5483 Unit tests for tag node list component * ACS-5483 Unit tests for unsaved changes dialog component * ACS-5483 Unit tests for unsaved changes guard * ACS-5483 Added description field to group models * ACS-5483 Correction for updating with description * ACS-5483 Fixed lint issues * ACS-5483 Addressed PR comments * ACS-5483 Reduced complexity * ACS-5483 Reduced complexity * ACS-5483 Addressed PR comments
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright © 2005-2023 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 { TestBed } from '@angular/core/testing';
|
||||
import { ContentTestingModule } from '../../testing/content.testing.module';
|
||||
import { GroupService } from '@alfresco/adf-content-services';
|
||||
import { ContentIncludeQuery, GroupEntry } from '@alfresco/js-api';
|
||||
|
||||
describe('GroupService', () => {
|
||||
let service: GroupService;
|
||||
let group: GroupEntry;
|
||||
let returnedGroup: GroupEntry;
|
||||
let opts: ContentIncludeQuery;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ContentTestingModule]
|
||||
});
|
||||
service = TestBed.inject(GroupService);
|
||||
group = {
|
||||
entry: {
|
||||
id: 'some id',
|
||||
displayName: 'some name',
|
||||
description: 'some description'
|
||||
}
|
||||
};
|
||||
returnedGroup = JSON.parse(JSON.stringify(group));
|
||||
opts = {
|
||||
include: ['description']
|
||||
};
|
||||
});
|
||||
|
||||
describe('getGroup', () => {
|
||||
it('should return group returned by GroupsApi', (done) => {
|
||||
spyOn(service.groupsApi, 'getGroup').and.returnValue(Promise.resolve(returnedGroup));
|
||||
|
||||
service.getGroup(group.entry.id, opts).subscribe((groupEntry) => {
|
||||
expect(groupEntry).toBe(returnedGroup);
|
||||
expect(service.groupsApi.getGroup).toHaveBeenCalledWith(group.entry.id, {
|
||||
include: ['description']
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return group returned by GroupsApi when description is not supplied', (done) => {
|
||||
returnedGroup.entry.description = undefined;
|
||||
spyOn(service.groupsApi, 'getGroup').and.returnValue(Promise.resolve(returnedGroup));
|
||||
|
||||
service.getGroup(group.entry.id, opts).subscribe((groupEntry) => {
|
||||
expect(groupEntry).toEqual({
|
||||
entry: {
|
||||
id: returnedGroup.entry.id,
|
||||
displayName: returnedGroup.entry.displayName,
|
||||
description: ''
|
||||
}
|
||||
});
|
||||
expect(service.groupsApi.getGroup).toHaveBeenCalledWith(group.entry.id, {
|
||||
include: ['description']
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateGroup', () => {
|
||||
it('should return updated Group', (done) => {
|
||||
spyOn(service.groupsApi, 'updateGroup').and.returnValue(Promise.resolve(returnedGroup));
|
||||
|
||||
service.updateGroup(group.entry, opts).subscribe((groupEntry) => {
|
||||
expect(groupEntry).toEqual(returnedGroup);
|
||||
expect(service.groupsApi.updateGroup).toHaveBeenCalledWith(group.entry.id, {
|
||||
displayName: group.entry.displayName,
|
||||
description: group.entry.description
|
||||
}, {
|
||||
include: ['description']
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return updated Group when description is not supplied', (done) => {
|
||||
returnedGroup.entry.description = undefined;
|
||||
spyOn(service.groupsApi, 'updateGroup').and.returnValue(Promise.resolve(returnedGroup));
|
||||
|
||||
service.updateGroup(group.entry, opts).subscribe((groupEntry) => {
|
||||
expect(groupEntry).toEqual({
|
||||
entry: {
|
||||
id: returnedGroup.entry.id,
|
||||
displayName: returnedGroup.entry.displayName,
|
||||
description: ''
|
||||
}
|
||||
});
|
||||
expect(service.groupsApi.updateGroup).toHaveBeenCalledWith(group.entry.id, {
|
||||
displayName: group.entry.displayName,
|
||||
description: group.entry.description
|
||||
}, {
|
||||
include: ['description']
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow to update only description', (done) => {
|
||||
spyOn(service.groupsApi, 'updateGroup').and.returnValue(Promise.resolve(returnedGroup));
|
||||
group.entry.displayName = undefined;
|
||||
|
||||
service.updateGroup(group.entry, opts).subscribe((groupEntry) => {
|
||||
expect(groupEntry).toEqual(returnedGroup);
|
||||
expect(service.groupsApi.updateGroup).toHaveBeenCalledWith(group.entry.id, {
|
||||
displayName: group.entry.displayName,
|
||||
description: group.entry.description
|
||||
}, {
|
||||
include: ['description']
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@@ -16,8 +16,10 @@
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { GroupEntry, GroupsApi } from '@alfresco/js-api';
|
||||
import { ContentIncludeQuery, Group, GroupEntry, GroupsApi } from '@alfresco/js-api';
|
||||
import { AlfrescoApiService } from '@alfresco/adf-core';
|
||||
import { from, Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@@ -48,4 +50,35 @@ export class GroupService {
|
||||
return accumulator;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns group for specified id.
|
||||
*
|
||||
* @param id id of group to return.
|
||||
* @param opts additional query parameters
|
||||
* @returns Observable<GroupEntry> group for specified id.
|
||||
*/
|
||||
getGroup(id: string, opts?: ContentIncludeQuery): Observable<GroupEntry> {
|
||||
return from(this.groupsApi.getGroup(id, opts)).pipe(map((group) => {
|
||||
group.entry.description ||= '';
|
||||
return group;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates specified group.
|
||||
*
|
||||
* @param group group to update.
|
||||
* @param opts additional query parameters
|
||||
* @returns Observable<GroupEntry> updated group.
|
||||
*/
|
||||
updateGroup(group: Group, opts?: ContentIncludeQuery): Observable<GroupEntry> {
|
||||
return from(this.groupsApi.updateGroup(group.id, {
|
||||
displayName: group.displayName,
|
||||
description: group.description
|
||||
}, opts)).pipe(map((updatedGroup) => {
|
||||
updatedGroup.entry.description ||= '';
|
||||
return updatedGroup;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
@@ -1,20 +1,7 @@
|
||||
<div class="adf-tag-node-list" [class.adf-flex-column]="limitTagsDisplayed && (!calculationsDone || columnFlexDirection)" #nodeListContainer>
|
||||
<mat-chip-list [class.adf-full-width]="limitTagsDisplayed && !calculationsDone" role="listbox" [attr.aria-label]="'METADATA.BASIC.TAGS' | translate">
|
||||
<mat-chip class="adf-tag-chips"
|
||||
*ngFor="let currentEntry of tagsEntries; let idx = index"
|
||||
(removed)="removeTag(currentEntry.entry.id)">
|
||||
<span id="tag_name_{{idx}}">{{currentEntry.entry.tag}}</span>
|
||||
<mat-icon *ngIf="showDelete" id="tag_chips_delete_{{currentEntry.entry.tag}}"
|
||||
class="adf-tag-chips-delete-icon" matChipRemove>cancel
|
||||
</mat-icon>
|
||||
</mat-chip>
|
||||
</mat-chip-list>
|
||||
<button mat-button
|
||||
[hidden]="!limitTagsDisplayed"
|
||||
[style.left.px]="viewMoreButtonLeftOffset"
|
||||
class="adf-view-more-button"
|
||||
[class.adf-hidden-btn]="!calculationsDone"
|
||||
(click)="displayAllTags($event)"
|
||||
>{{ 'TAG_NODE_LIST.VIEW_MORE' | translate: { count: undisplayedTagsCount} }}
|
||||
</button>
|
||||
</div>
|
||||
<adf-dynamic-chip-list
|
||||
[chips]="tagChips"
|
||||
[limitChipsDisplayed]="limitTagsDisplayed"
|
||||
[showDelete]="showDelete"
|
||||
(displayNext)="refreshTag()"
|
||||
(removedChip)="removeTag($event)">
|
||||
</adf-dynamic-chip-list>
|
||||
|
@@ -1,61 +0,0 @@
|
||||
.adf-tag-node-list {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
width: inherit;
|
||||
|
||||
.adf-view-more-button {
|
||||
margin: 4px;
|
||||
color: var(--adf-theme-foreground-text-color-054);
|
||||
position: absolute;
|
||||
|
||||
&[hidden] {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&.adf-flex-column {
|
||||
flex-direction: column;
|
||||
|
||||
.adf-view-more-button {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.adf-full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.adf-hidden-btn {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.adf-tag-chips {
|
||||
color: var(--theme-primary-color-default-contrast);
|
||||
background-color: var(--theme-primary-color);
|
||||
height: auto;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.adf-tag-chips-delete {
|
||||
overflow: visible;
|
||||
cursor: pointer;
|
||||
height: 17px;
|
||||
width: 20px;
|
||||
float: right;
|
||||
border: 0;
|
||||
background: none;
|
||||
padding: 0;
|
||||
margin: -1px 0 0 10px;
|
||||
}
|
||||
|
||||
.adf-tag-chips-delete-icon {
|
||||
font-size: var(--theme-title-font-size);
|
||||
background-repeat: no-repeat;
|
||||
display: inline-block;
|
||||
fill: currentcolor;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
color: var(--theme-primary-color-default-contrast) !important;
|
||||
}
|
||||
}
|
@@ -18,62 +18,16 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { TagNodeListComponent } from './tag-node-list.component';
|
||||
import { TagService } from './services/tag.service';
|
||||
import { of } from 'rxjs';
|
||||
import { Observable, of, Subject } from 'rxjs';
|
||||
import { ContentTestingModule } from '../testing/content.testing.module';
|
||||
import { Tag, TagEntry, TagPaging } from '@alfresco/js-api';
|
||||
import { DynamicChipListComponent } from '@alfresco/adf-core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { TagEntry } from '@alfresco/js-api';
|
||||
|
||||
describe('TagNodeList', () => {
|
||||
|
||||
const dataTag = {
|
||||
list: {
|
||||
pagination: {
|
||||
count: 3,
|
||||
hasMoreItems: false,
|
||||
totalItems: 3,
|
||||
skipCount: 0,
|
||||
maxItems: 100
|
||||
},
|
||||
entries: [
|
||||
{
|
||||
entry: {tag: 'test1', id: '0ee933fa-57fc-4587-8a77-b787e814f1d2'}
|
||||
},
|
||||
{
|
||||
entry: {tag: 'test2', id: 'fcb92659-1f10-41b4-9b17-851b72a3b597'}
|
||||
},
|
||||
{
|
||||
entry: {tag: 'test3', id: 'fb4213c0-729d-466c-9a6c-ee2e937273bf'}
|
||||
},
|
||||
{
|
||||
entry: {tag: 'test4', id: 'as4213c0-729d-466c-9a6c-ee2e937273as'}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
let component: TagNodeListComponent;
|
||||
let fixture: ComponentFixture<TagNodeListComponent>;
|
||||
let element: HTMLElement;
|
||||
let tagService: TagService;
|
||||
let resizeCallback: ResizeObserverCallback;
|
||||
|
||||
/**
|
||||
* Find 'More' button
|
||||
*
|
||||
* @returns native element
|
||||
*/
|
||||
function findViewMoreButton(): HTMLButtonElement {
|
||||
return element.querySelector('.adf-view-more-button');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tag chips
|
||||
*
|
||||
* @returns native element list
|
||||
*/
|
||||
function findTagChips(): NodeListOf<Element> {
|
||||
return element.querySelectorAll('.adf-tag-chips');
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
@@ -82,218 +36,176 @@ describe('TagNodeList', () => {
|
||||
ContentTestingModule
|
||||
]
|
||||
});
|
||||
const resizeObserverSpy = spyOn(window, 'ResizeObserver').and.callThrough();
|
||||
fixture = TestBed.createComponent(TagNodeListComponent);
|
||||
|
||||
tagService = TestBed.inject(TagService);
|
||||
spyOn(tagService, 'getTagsByNodeId').and.returnValue(of(dataTag));
|
||||
|
||||
element = fixture.nativeElement;
|
||||
component = fixture.componentInstance;
|
||||
component.nodeId = 'fake-node-id';
|
||||
fixture.detectChanges();
|
||||
resizeCallback = resizeObserverSpy.calls.mostRecent().args[0];
|
||||
});
|
||||
|
||||
describe('Rendering tests', () => {
|
||||
|
||||
it('Tag list relative a single node should be rendered', async () => {
|
||||
component.ngOnChanges();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(element.querySelector('#tag_name_0').innerHTML).toBe('test1');
|
||||
expect(element.querySelector('#tag_name_1').innerHTML).toBe('test2');
|
||||
expect(element.querySelector('#tag_name_2').innerHTML).toBe('test3');
|
||||
|
||||
expect(element.querySelector('#tag_chips_delete_test1')).not.toBe(null);
|
||||
expect(element.querySelector('#tag_chips_delete_test2')).not.toBe(null);
|
||||
expect(element.querySelector('#tag_chips_delete_test3')).not.toBe(null);
|
||||
});
|
||||
|
||||
it('Tag list click on delete button should delete the tag', async () => {
|
||||
spyOn(tagService, 'removeTag').and.returnValue(of(undefined));
|
||||
|
||||
component.ngOnChanges();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const deleteButton: any = element.querySelector('#tag_chips_delete_test1');
|
||||
deleteButton.click();
|
||||
|
||||
expect(tagService.removeTag).toHaveBeenCalledWith('fake-node-id', '0ee933fa-57fc-4587-8a77-b787e814f1d2');
|
||||
});
|
||||
|
||||
it('Should not show the delete tag button if showDelete is false', async () => {
|
||||
component.showDelete = false;
|
||||
|
||||
component.ngOnChanges();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const deleteButton: any = element.querySelector('#tag_chips_delete_test1');
|
||||
expect(deleteButton).toBeNull();
|
||||
});
|
||||
|
||||
it('Should show the delete tag button if showDelete is true', async () => {
|
||||
component.showDelete = true;
|
||||
|
||||
component.ngOnChanges();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const deleteButton: any = element.querySelector('#tag_chips_delete_test1');
|
||||
expect(deleteButton).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should not render view more button by default', async () => {
|
||||
component.ngOnChanges();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(findViewMoreButton().hidden).toBeTrue();
|
||||
expect(findTagChips()).toHaveSize(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Limit tags display', () => {
|
||||
let initialEntries: TagEntry[];
|
||||
|
||||
/**
|
||||
* Render tags
|
||||
*
|
||||
* @param entries tags to render
|
||||
*/
|
||||
async function renderTags(entries?: TagEntry[]) {
|
||||
dataTag.list.entries = entries || initialEntries;
|
||||
component.tagsEntries = dataTag.list.entries;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
initialEntries = dataTag.list.entries;
|
||||
});
|
||||
describe('DynamicChipListComponent', () => {
|
||||
let dynamicChipListComponent: DynamicChipListComponent;
|
||||
let tagService: TagService;
|
||||
let getTagsByNodeIdSpy: jasmine.Spy<(nodeId: string) => Observable<TagPaging>>;
|
||||
|
||||
beforeEach(() => {
|
||||
fixture.detectChanges();
|
||||
dynamicChipListComponent = fixture.debugElement.query(By.directive(DynamicChipListComponent)).componentInstance;
|
||||
tagService = TestBed.inject(TagService);
|
||||
getTagsByNodeIdSpy = spyOn(tagService, 'getTagsByNodeId').and.returnValue(new Subject<TagPaging>());
|
||||
});
|
||||
|
||||
it('should have assigned limitChipsDisplayed to true if true is passed to limitTagsDisplayed', () => {
|
||||
component.limitTagsDisplayed = true;
|
||||
component.ngOnInit();
|
||||
element.style.maxWidth = '309px';
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(dynamicChipListComponent.limitChipsDisplayed).toBeTrue();
|
||||
});
|
||||
|
||||
it('should render view more button when limiting is enabled', async () => {
|
||||
await renderTags();
|
||||
component.ngOnChanges();
|
||||
it('should have assigned limitChipsDisplayed to false if false is passed to limitTagsDisplayed', () => {
|
||||
component.limitTagsDisplayed = true;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
expect(findViewMoreButton().hidden).toBeFalse();
|
||||
expect(findTagChips()).toHaveSize(component.tagsEntries.length);
|
||||
component.limitTagsDisplayed = false;
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(dynamicChipListComponent.limitChipsDisplayed).toBeFalse();
|
||||
});
|
||||
|
||||
it('should not render view more button when limiting is enabled and all tags fits into container', async () => {
|
||||
await renderTags();
|
||||
element.style.maxWidth = '800px';
|
||||
|
||||
component.ngOnChanges();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(findViewMoreButton().hidden).toBeTrue();
|
||||
expect(findTagChips()).toHaveSize(4);
|
||||
it('should have assigned limitChipsDisplayed to false by default if limitTagsDisplayed is not delivered', () => {
|
||||
expect(dynamicChipListComponent.limitChipsDisplayed).toBeFalse();
|
||||
});
|
||||
|
||||
it('should display all tags when view more button is clicked', async () => {
|
||||
await renderTags();
|
||||
component.ngOnChanges();
|
||||
it('should have assigned showDelete to true if true is passed to showDelete of tag node list', () => {
|
||||
component.showDelete = false;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
component.showDelete = true;
|
||||
|
||||
let viewMoreButton = findViewMoreButton();
|
||||
viewMoreButton.click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
viewMoreButton = findViewMoreButton();
|
||||
expect(viewMoreButton.hidden).toBeTrue();
|
||||
expect(findTagChips()).toHaveSize(4);
|
||||
expect(dynamicChipListComponent.showDelete).toBeTrue();
|
||||
});
|
||||
|
||||
it('should not render view more button when tag takes more than one line and there are no more tags', async () => {
|
||||
await renderTags([{
|
||||
entry: {
|
||||
tag: 'VeryLongTag VeryLongTag VeryLongTag VeryLongTag VeryLongTag VeryLongTag VeryLongTag VeryLongTag',
|
||||
id: '0ee933fa-57fc-4587-8a77-b787e814f1d2'
|
||||
}
|
||||
}]);
|
||||
component.ngOnChanges();
|
||||
it('should have assigned showDelete to false if false is passed to showDelete of tag node list', () => {
|
||||
component.showDelete = false;
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
expect(findViewMoreButton().hidden).toBeTrue();
|
||||
expect(findTagChips()).toHaveSize(component.tagsEntries.length);
|
||||
expect(dynamicChipListComponent.showDelete).toBeFalse();
|
||||
});
|
||||
|
||||
it('should render view more button when tag takes more than one line and there are more tags', async () => {
|
||||
await renderTags([{
|
||||
entry: {
|
||||
tag: 'VeryLongTag VeryLongTag VeryLongTag VeryLongTag VeryLongTag VeryLongTag VeryLongTag VeryLongTag',
|
||||
id: '0ee933fa-57fc-4587-8a77-b787e814f1d2'
|
||||
}
|
||||
}, {
|
||||
entry: {
|
||||
tag: 'Some other tag',
|
||||
id: '0ee933fa-57fc-4587-8a77-b787e814f1d3'
|
||||
}
|
||||
}]);
|
||||
component.ngOnChanges();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const viewMoreButton = findViewMoreButton();
|
||||
expect(viewMoreButton.hidden).toBeFalse();
|
||||
expect(viewMoreButton.style.left).toBe('0px');
|
||||
expect(findTagChips()).toHaveSize(component.tagsEntries.length);
|
||||
it('should have assigned showDelete to true by default if showDelete of tag node list is not delivered', () => {
|
||||
expect(dynamicChipListComponent.showDelete).toBeTrue();
|
||||
});
|
||||
|
||||
it('should not render view more button when there is enough space after resizing', async () => {
|
||||
await renderTags();
|
||||
component.ngOnChanges();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
element.style.maxWidth = '800px';
|
||||
resizeCallback([], null);
|
||||
fixture.detectChanges();
|
||||
const viewMoreButton = findViewMoreButton();
|
||||
expect(viewMoreButton.hidden).toBeTrue();
|
||||
expect(findTagChips()).toHaveSize(4);
|
||||
});
|
||||
describe('Assigning chips', () => {
|
||||
let tagEntry: Tag;
|
||||
let tagEntries: TagEntry[];
|
||||
|
||||
it('should render view more button when there is not enough space after resizing', async () => {
|
||||
await renderTags();
|
||||
element.style.maxWidth = '800px';
|
||||
beforeEach(() => {
|
||||
tagEntry = {
|
||||
id: 'some id',
|
||||
tag: 'some tag'
|
||||
};
|
||||
tagEntries = [{
|
||||
entry: tagEntry
|
||||
}];
|
||||
getTagsByNodeIdSpy.and.returnValue(of({
|
||||
list: {
|
||||
entries: tagEntries,
|
||||
pagination: undefined
|
||||
}
|
||||
}));
|
||||
spyOn(component.results, 'emit');
|
||||
});
|
||||
|
||||
component.ngOnChanges();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
element.style.maxWidth = '309px';
|
||||
resizeCallback([], null);
|
||||
fixture.detectChanges();
|
||||
it('should have assigned correct chips initially', () => {
|
||||
component.nodeId = 'some node id';
|
||||
tagService.refresh.emit();
|
||||
|
||||
expect(findViewMoreButton().hidden).toBeFalse();
|
||||
expect(findTagChips()).toHaveSize(component.tagsEntries.length);
|
||||
});
|
||||
fixture.detectChanges();
|
||||
expect(dynamicChipListComponent.chips).toEqual([{
|
||||
id: tagEntry.id,
|
||||
name: tagEntry.tag
|
||||
}]);
|
||||
expect(tagService.getTagsByNodeId).toHaveBeenCalledWith(component.nodeId);
|
||||
expect(component.results.emit).toHaveBeenCalledWith(tagEntries);
|
||||
});
|
||||
|
||||
it('should not render view more button again resizing when there is not enough space if user requested to see all tags', async () => {
|
||||
await renderTags();
|
||||
component.ngOnChanges();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const viewMoreButton = findViewMoreButton();
|
||||
viewMoreButton.click();
|
||||
fixture.detectChanges();
|
||||
element.style.maxWidth = '309px';
|
||||
resizeCallback([], null);
|
||||
fixture.detectChanges();
|
||||
expect(viewMoreButton.hidden).toBeTrue();
|
||||
expect(findTagChips()).toHaveSize(4);
|
||||
it('should not have assigned chips initially if nodeId is not specified', () => {
|
||||
tagService.refresh.emit();
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(dynamicChipListComponent.chips).toEqual([]);
|
||||
expect(tagService.getTagsByNodeId).not.toHaveBeenCalled();
|
||||
expect(component.results.emit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should have assigned correct chips when ngOnChanges is called', () => {
|
||||
component.nodeId = 'some node id';
|
||||
component.ngOnChanges();
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(dynamicChipListComponent.chips).toEqual([{
|
||||
id: tagEntry.id,
|
||||
name: tagEntry.tag
|
||||
}]);
|
||||
expect(tagService.getTagsByNodeId).toHaveBeenCalledWith(component.nodeId);
|
||||
expect(component.results.emit).toHaveBeenCalledWith(tagEntries);
|
||||
});
|
||||
|
||||
it('should not have assigned chips when ngOnChanges is called and nodeId is not specified', () => {
|
||||
component.ngOnChanges();
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(dynamicChipListComponent.chips).toEqual([]);
|
||||
expect(tagService.getTagsByNodeId).not.toHaveBeenCalled();
|
||||
expect(component.results.emit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should have assigned correct chips when displayNext event from DynamicChipList is triggered', () => {
|
||||
component.nodeId = 'some node id';
|
||||
dynamicChipListComponent.displayNext.emit();
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(dynamicChipListComponent.chips).toEqual([{
|
||||
id: tagEntry.id,
|
||||
name: tagEntry.tag
|
||||
}]);
|
||||
expect(tagService.getTagsByNodeId).toHaveBeenCalledWith(component.nodeId);
|
||||
expect(component.results.emit).toHaveBeenCalledWith(tagEntries);
|
||||
});
|
||||
|
||||
it('should not have assigned chips when displayNext event from DynamicChipList is triggered and nodeId is not specified', () => {
|
||||
dynamicChipListComponent.displayNext.emit();
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(dynamicChipListComponent.chips).toEqual([]);
|
||||
expect(tagService.getTagsByNodeId).not.toHaveBeenCalled();
|
||||
expect(component.results.emit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should have assigned correct chips when removeTag event from DynamicChipList is triggered', () => {
|
||||
component.nodeId = 'some node id';
|
||||
spyOn(tagService, 'removeTag').and.returnValue(of(undefined));
|
||||
const tag = 'some tag';
|
||||
dynamicChipListComponent.removedChip.emit(tag);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(dynamicChipListComponent.chips).toEqual([{
|
||||
id: tagEntry.id,
|
||||
name: tagEntry.tag
|
||||
}]);
|
||||
expect(tagService.removeTag).toHaveBeenCalledWith(component.nodeId, tag);
|
||||
expect(tagService.getTagsByNodeId).toHaveBeenCalledWith(component.nodeId);
|
||||
expect(component.results.emit).toHaveBeenCalledWith(tagEntries);
|
||||
});
|
||||
|
||||
it('should not have assigned chips when removeTag event from DynamicChipList is triggered and nodeId is not specified', () => {
|
||||
spyOn(tagService, 'removeTag').and.returnValue(of(undefined));
|
||||
const tag = 'some tag';
|
||||
dynamicChipListComponent.removedChip.emit(tag);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(dynamicChipListComponent.chips).toEqual([]);
|
||||
expect(tagService.removeTag).toHaveBeenCalledWith(component.nodeId, tag);
|
||||
expect(tagService.getTagsByNodeId).not.toHaveBeenCalled();
|
||||
expect(component.results.emit).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -15,27 +15,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnChanges,
|
||||
Output,
|
||||
ViewEncapsulation,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
ViewChild,
|
||||
ElementRef,
|
||||
ViewChildren,
|
||||
QueryList,
|
||||
ChangeDetectorRef,
|
||||
AfterViewInit
|
||||
} from '@angular/core';
|
||||
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, ViewEncapsulation } from '@angular/core';
|
||||
import { TagService } from './services/tag.service';
|
||||
import { TagEntry } from '@alfresco/js-api';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { MatChip } from '@angular/material/chips';
|
||||
import { Chip } from '@alfresco/adf-core';
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -45,11 +30,9 @@ import { MatChip } from '@angular/material/chips';
|
||||
@Component({
|
||||
selector: 'adf-tag-node-list',
|
||||
templateUrl: './tag-node-list.component.html',
|
||||
styleUrls: ['./tag-node-list.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class TagNodeListComponent implements OnChanges, OnDestroy, OnInit, AfterViewInit {
|
||||
/* eslint no-underscore-dangle: ["error", { "allow": ["_elementRef"] }]*/
|
||||
export class TagNodeListComponent implements OnChanges, OnDestroy, OnInit {
|
||||
/** The identifier of a node. */
|
||||
@Input()
|
||||
nodeId: string;
|
||||
@@ -62,121 +45,49 @@ export class TagNodeListComponent implements OnChanges, OnDestroy, OnInit, After
|
||||
@Input()
|
||||
limitTagsDisplayed = false;
|
||||
|
||||
@ViewChild('nodeListContainer')
|
||||
containerView: ElementRef;
|
||||
|
||||
@ViewChildren(MatChip)
|
||||
tagChips: QueryList<MatChip>;
|
||||
|
||||
tagsEntries: TagEntry[] = [];
|
||||
calculationsDone = false;
|
||||
columnFlexDirection = false;
|
||||
undisplayedTagsCount = 0;
|
||||
viewMoreButtonLeftOffset: number;
|
||||
|
||||
/** Emitted when a tag is selected. */
|
||||
@Output()
|
||||
results = new EventEmitter();
|
||||
results = new EventEmitter<TagEntry[]>();
|
||||
|
||||
private onDestroy$ = new Subject<boolean>();
|
||||
private initialLimitTagsDisplayed: boolean;
|
||||
private initialTagsEntries: TagEntry[] = [];
|
||||
private viewMoreButtonLeftOffsetBeforeFlexDirection: number;
|
||||
private requestedDisplayingAllTags = false;
|
||||
private resizeObserver = new ResizeObserver(() => {
|
||||
this.calculateTagsToDisplay();
|
||||
this.changeDetectorRef.detectChanges();
|
||||
});
|
||||
private _tagChips: Chip[] = [];
|
||||
|
||||
constructor(private tagService: TagService, private changeDetectorRef: ChangeDetectorRef) {
|
||||
get tagChips(): Chip[] {
|
||||
return this._tagChips;
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
constructor(private tagService: TagService) {}
|
||||
|
||||
ngOnChanges(): void {
|
||||
this.refreshTag();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.initialLimitTagsDisplayed = this.limitTagsDisplayed;
|
||||
ngOnInit(): void {
|
||||
this.tagService.refresh
|
||||
.pipe(takeUntil(this.onDestroy$))
|
||||
.subscribe(() => this.refreshTag());
|
||||
|
||||
this.results
|
||||
.pipe(takeUntil(this.onDestroy$))
|
||||
.subscribe(() => {
|
||||
if (this.limitTagsDisplayed && this.tagsEntries.length > 0) {
|
||||
this.calculateTagsToDisplay();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.resizeObserver.observe(this.containerView.nativeElement);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
ngOnDestroy(): void {
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
this.resizeObserver.unobserve(this.containerView.nativeElement);
|
||||
}
|
||||
|
||||
refreshTag() {
|
||||
refreshTag(): void {
|
||||
if (this.nodeId) {
|
||||
this.tagService.getTagsByNodeId(this.nodeId).subscribe((tagPaging) => {
|
||||
this.tagsEntries = tagPaging.list.entries;
|
||||
this.initialTagsEntries = tagPaging.list.entries;
|
||||
this.results.emit(this.tagsEntries);
|
||||
this._tagChips = tagPaging.list.entries.map((tag) => ({
|
||||
id: tag.entry.id,
|
||||
name: tag.entry.tag
|
||||
}));
|
||||
this.results.emit(tagPaging.list.entries);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
removeTag(tag: string) {
|
||||
removeTag(tag: string): void {
|
||||
this.tagService.removeTag(this.nodeId, tag).subscribe(() => {
|
||||
this.refreshTag();
|
||||
});
|
||||
}
|
||||
|
||||
displayAllTags(event: Event): void {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.limitTagsDisplayed = false;
|
||||
this.requestedDisplayingAllTags = true;
|
||||
this.resizeObserver.unobserve(this.containerView.nativeElement);
|
||||
this.refreshTag();
|
||||
}
|
||||
|
||||
private calculateTagsToDisplay() {
|
||||
if (!this.requestedDisplayingAllTags) {
|
||||
this.tagsEntries = this.initialTagsEntries;
|
||||
this.changeDetectorRef.detectChanges();
|
||||
this.undisplayedTagsCount = 0;
|
||||
let tagsToDisplay = 1;
|
||||
const containerWidth: number = this.containerView.nativeElement.clientWidth;
|
||||
const viewMoreBtnWidth: number = this.containerView.nativeElement.children[1].offsetWidth;
|
||||
const firstTag = this.tagChips.get(0);
|
||||
const tagChipMargin = firstTag ? this.getTagChipMargin(this.tagChips.get(0)) : 0;
|
||||
const tagChipsWidth: number = this.tagChips.reduce((width, val, index) => {
|
||||
width += val._elementRef.nativeElement.offsetWidth + tagChipMargin;
|
||||
if (containerWidth - viewMoreBtnWidth > width) {
|
||||
tagsToDisplay = index + 1;
|
||||
this.viewMoreButtonLeftOffset = width;
|
||||
this.viewMoreButtonLeftOffsetBeforeFlexDirection = width;
|
||||
}
|
||||
return width;
|
||||
}, 0);
|
||||
if ((containerWidth - tagChipsWidth) <= 0) {
|
||||
this.columnFlexDirection = tagsToDisplay === 1 && (containerWidth < (this.tagChips.get(0)._elementRef.nativeElement.offsetWidth + viewMoreBtnWidth));
|
||||
this.undisplayedTagsCount = this.tagsEntries.length - tagsToDisplay;
|
||||
this.tagsEntries = this.tagsEntries.slice(0, tagsToDisplay);
|
||||
}
|
||||
this.limitTagsDisplayed = this.undisplayedTagsCount ? this.initialLimitTagsDisplayed : false;
|
||||
this.viewMoreButtonLeftOffset = this.columnFlexDirection ? 0 : this.viewMoreButtonLeftOffsetBeforeFlexDirection;
|
||||
this.calculationsDone = true;
|
||||
}
|
||||
}
|
||||
|
||||
private getTagChipMargin(chip: MatChip): number {
|
||||
const tagChipStyles = window.getComputedStyle(chip._elementRef.nativeElement);
|
||||
return parseInt(tagChipStyles.marginLeft, 10) + parseInt(tagChipStyles.marginRight, 10);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user