mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
AAE-10239 showRowContextMenu-datatable-action-called-multiple-times (#10386)
* [AAE-10239] use an action when the datatable component is loaded * [AAE-10239] add test for disabled context menu * [AAE-10239] fixed context menu for tree component * [AAE-10239] added new test for disabled context menu * [AAE-10239] fixed context menu for recent changes for tree component * [AAE-10239] backward compatible approach * [AAE-10239] no need to format the file * [AAE-10239] reverted rename of method * [AAE-10239] renamed file so you can find it better
This commit is contained in:
231
lib/core/src/lib/context-menu/context-menu.directive.spec.ts
Normal file
231
lib/core/src/lib/context-menu/context-menu.directive.spec.ts
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
/*!
|
||||||
|
* @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 } from '@angular/core';
|
||||||
|
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
||||||
|
import { CONTEXT_MENU_DIRECTIVES } from './context-menu.module';
|
||||||
|
import { CoreTestingModule } from '../testing/core.testing.module';
|
||||||
|
import { HarnessLoader } from '@angular/cdk/testing';
|
||||||
|
import { MatIconHarness } from '@angular/material/icon/testing';
|
||||||
|
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'adf-test-component',
|
||||||
|
template: ` <div id="target" [adf-context-menu]="actions" [adf-context-menu-enabled]="isEnabled"></div> `
|
||||||
|
})
|
||||||
|
class TestComponent {
|
||||||
|
actions: any[] | (() => any[]);
|
||||||
|
isEnabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const actions = [
|
||||||
|
{
|
||||||
|
model: {
|
||||||
|
visible: false,
|
||||||
|
title: 'Action 1'
|
||||||
|
},
|
||||||
|
subject: {
|
||||||
|
next: jasmine.createSpy('next')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: {
|
||||||
|
visible: true,
|
||||||
|
disabled: true,
|
||||||
|
title: 'Action 2',
|
||||||
|
icon: null
|
||||||
|
},
|
||||||
|
subject: {
|
||||||
|
next: jasmine.createSpy('next')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: {
|
||||||
|
visible: true,
|
||||||
|
disabled: false,
|
||||||
|
title: 'Action 3',
|
||||||
|
icon: 'action-icon-3'
|
||||||
|
},
|
||||||
|
subject: {
|
||||||
|
next: jasmine.createSpy('next')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: {
|
||||||
|
visible: true,
|
||||||
|
disabled: false,
|
||||||
|
title: 'Action 4',
|
||||||
|
icon: 'action-icon-4'
|
||||||
|
},
|
||||||
|
subject: {
|
||||||
|
next: jasmine.createSpy('next')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: {
|
||||||
|
visible: true,
|
||||||
|
disabled: false,
|
||||||
|
title: 'action-5',
|
||||||
|
icon: 'action-icon-5',
|
||||||
|
tooltip: 'Action 5 tooltip'
|
||||||
|
},
|
||||||
|
subject: {
|
||||||
|
next: jasmine.createSpy('next')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: {
|
||||||
|
visible: true,
|
||||||
|
disabled: false,
|
||||||
|
title: 'action-6',
|
||||||
|
icon: 'action-icon-6'
|
||||||
|
},
|
||||||
|
subject: {
|
||||||
|
next: jasmine.createSpy('next')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const testCases = [
|
||||||
|
{
|
||||||
|
description: 'with actions as an array',
|
||||||
|
actions
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'with actions as a function',
|
||||||
|
actions: () => actions
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
testCases.forEach((testCase) => {
|
||||||
|
describe(`ContextMenuDirective ${testCase.description}`, () => {
|
||||||
|
let fixture: ComponentFixture<TestComponent>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [CoreTestingModule, CONTEXT_MENU_DIRECTIVES],
|
||||||
|
declarations: [TestComponent]
|
||||||
|
});
|
||||||
|
fixture = TestBed.createComponent(TestComponent);
|
||||||
|
fixture.componentInstance.isEnabled = false;
|
||||||
|
fixture.componentInstance.actions = testCase.actions;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not show menu on mouse contextmenu event when context menu is disabled', () => {
|
||||||
|
const targetElement = fixture.debugElement.nativeElement.querySelector('#target');
|
||||||
|
targetElement.dispatchEvent(new CustomEvent('contextmenu'));
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const contextMenu = document.querySelector('.adf-context-menu');
|
||||||
|
expect(contextMenu).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Events', () => {
|
||||||
|
let targetElement: HTMLElement;
|
||||||
|
let contextMenu: HTMLElement | null;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture.componentInstance.isEnabled = true;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
targetElement = fixture.debugElement.nativeElement.querySelector('#target');
|
||||||
|
targetElement.dispatchEvent(new CustomEvent('contextmenu'));
|
||||||
|
fixture.detectChanges();
|
||||||
|
contextMenu = document.querySelector('.adf-context-menu');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show menu on mouse contextmenu event', () => {
|
||||||
|
expect(contextMenu).not.toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set DOM element reference on menu open event', () => {
|
||||||
|
expect(contextMenu?.className).toContain('adf-context-menu');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reset DOM element reference on Escape event', () => {
|
||||||
|
const event = new KeyboardEvent('keydown', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
key: 'Escape'
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelector('.cdk-overlay-backdrop')?.dispatchEvent(event);
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(document.querySelector('.adf-context-menu')).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Contextmenu list', () => {
|
||||||
|
let targetElement: HTMLElement;
|
||||||
|
let contextMenu: HTMLElement | null;
|
||||||
|
let loader: HarnessLoader;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture.componentInstance.isEnabled = true;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
targetElement = fixture.debugElement.nativeElement.querySelector('#target');
|
||||||
|
targetElement.dispatchEvent(new CustomEvent('contextmenu'));
|
||||||
|
fixture.detectChanges();
|
||||||
|
contextMenu = document.querySelector('.adf-context-menu');
|
||||||
|
loader = TestbedHarnessEnvironment.documentRootLoader(fixture);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not render item with visibility property set to false', () => {
|
||||||
|
expect(contextMenu?.querySelectorAll('button').length).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render item as disabled when `disabled` property is set to true', async () => {
|
||||||
|
expect(contextMenu?.querySelectorAll('button')[0].disabled).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set first not disabled item as active', async () => {
|
||||||
|
const icon = await loader.getHarness(MatIconHarness.with({ ancestor: 'adf-context-menu' }));
|
||||||
|
|
||||||
|
expect(await icon.getName()).toEqual('action-icon-3');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not allow action event when item is disabled', () => {
|
||||||
|
contextMenu?.querySelectorAll('button')[0].click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(actions[1].subject.next).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should perform action when item is not disabled', () => {
|
||||||
|
contextMenu?.querySelectorAll('button')[1].click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(actions[2].subject.next).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not render item icon if not set', async () => {
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
await loader.getAllHarnesses(
|
||||||
|
MatIconHarness.with({
|
||||||
|
ancestor: 'adf-context-menu',
|
||||||
|
name: 'Action 1'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).length
|
||||||
|
).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -27,7 +27,7 @@ import { ContextMenuOverlayService } from './context-menu-overlay.service';
|
|||||||
export class ContextMenuDirective {
|
export class ContextMenuDirective {
|
||||||
/** Items for the menu. */
|
/** Items for the menu. */
|
||||||
@Input('adf-context-menu')
|
@Input('adf-context-menu')
|
||||||
links: any[];
|
links: any[] | (() => any[]);
|
||||||
|
|
||||||
/** Is the menu enabled? */
|
/** Is the menu enabled? */
|
||||||
@Input('adf-context-menu-enabled')
|
@Input('adf-context-menu-enabled')
|
||||||
@@ -42,12 +42,15 @@ export class ContextMenuDirective {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.links && this.links.length > 0) {
|
if (this.links) {
|
||||||
|
const actions = typeof this.links === 'function' ? this.links() : this.links;
|
||||||
|
if (actions.length > 0) {
|
||||||
this.contextMenuService.open({
|
this.contextMenuService.open({
|
||||||
source: event,
|
source: event,
|
||||||
data: this.links
|
data: actions
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@@ -1,200 +0,0 @@
|
|||||||
/*!
|
|
||||||
* @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 } from '@angular/core';
|
|
||||||
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
|
||||||
import { CONTEXT_MENU_DIRECTIVES } from './context-menu.module';
|
|
||||||
import { CoreTestingModule } from '../testing/core.testing.module';
|
|
||||||
import { HarnessLoader } from '@angular/cdk/testing';
|
|
||||||
import { MatIconHarness } from '@angular/material/icon/testing';
|
|
||||||
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'adf-test-component',
|
|
||||||
template: ` <div id="target" [adf-context-menu]="actions" [adf-context-menu-enabled]="true"></div> `
|
|
||||||
})
|
|
||||||
class TestComponent {
|
|
||||||
actions;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('ContextMenuDirective', () => {
|
|
||||||
let fixture: ComponentFixture<TestComponent>;
|
|
||||||
const actions = [
|
|
||||||
{
|
|
||||||
model: {
|
|
||||||
visible: false,
|
|
||||||
title: 'Action 1'
|
|
||||||
},
|
|
||||||
subject: {
|
|
||||||
next: jasmine.createSpy('next')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
model: {
|
|
||||||
visible: true,
|
|
||||||
disabled: true,
|
|
||||||
title: 'Action 2',
|
|
||||||
icon: null
|
|
||||||
},
|
|
||||||
subject: {
|
|
||||||
next: jasmine.createSpy('next')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
model: {
|
|
||||||
visible: true,
|
|
||||||
disabled: false,
|
|
||||||
title: 'Action 3',
|
|
||||||
icon: 'action-icon-3'
|
|
||||||
},
|
|
||||||
subject: {
|
|
||||||
next: jasmine.createSpy('next')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
model: {
|
|
||||||
visible: true,
|
|
||||||
disabled: false,
|
|
||||||
title: 'Action 4',
|
|
||||||
icon: 'action-icon-4'
|
|
||||||
},
|
|
||||||
subject: {
|
|
||||||
next: jasmine.createSpy('next')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
model: {
|
|
||||||
visible: true,
|
|
||||||
disabled: false,
|
|
||||||
title: 'action-5',
|
|
||||||
icon: 'action-icon-5',
|
|
||||||
tooltip: 'Action 5 tooltip'
|
|
||||||
},
|
|
||||||
subject: {
|
|
||||||
next: jasmine.createSpy('next')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
model: {
|
|
||||||
visible: true,
|
|
||||||
disabled: false,
|
|
||||||
title: 'action-6',
|
|
||||||
icon: 'action-icon-6'
|
|
||||||
},
|
|
||||||
subject: {
|
|
||||||
next: jasmine.createSpy('next')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
imports: [CoreTestingModule, CONTEXT_MENU_DIRECTIVES],
|
|
||||||
declarations: [TestComponent]
|
|
||||||
});
|
|
||||||
fixture = TestBed.createComponent(TestComponent);
|
|
||||||
fixture.componentInstance.actions = actions;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Events', () => {
|
|
||||||
let targetElement: HTMLElement;
|
|
||||||
let contextMenu: HTMLElement | null;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
targetElement = fixture.debugElement.nativeElement.querySelector('#target');
|
|
||||||
targetElement.dispatchEvent(new CustomEvent('contextmenu'));
|
|
||||||
fixture.detectChanges();
|
|
||||||
contextMenu = document.querySelector('.adf-context-menu');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show menu on mouse contextmenu event', () => {
|
|
||||||
expect(contextMenu).not.toBe(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set DOM element reference on menu open event', () => {
|
|
||||||
expect(contextMenu?.className).toContain('adf-context-menu');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reset DOM element reference on Escape event', () => {
|
|
||||||
const event = new KeyboardEvent('keydown', {
|
|
||||||
bubbles: true,
|
|
||||||
cancelable: true,
|
|
||||||
key: 'Escape'
|
|
||||||
});
|
|
||||||
|
|
||||||
document.querySelector('.cdk-overlay-backdrop')?.dispatchEvent(event);
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(document.querySelector('.adf-context-menu')).toBe(null);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Contextmenu list', () => {
|
|
||||||
let targetElement: HTMLElement;
|
|
||||||
let contextMenu: HTMLElement | null;
|
|
||||||
let loader: HarnessLoader;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
targetElement = fixture.debugElement.nativeElement.querySelector('#target');
|
|
||||||
targetElement.dispatchEvent(new CustomEvent('contextmenu'));
|
|
||||||
fixture.detectChanges();
|
|
||||||
contextMenu = document.querySelector('.adf-context-menu');
|
|
||||||
loader = TestbedHarnessEnvironment.documentRootLoader(fixture);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not render item with visibility property set to false', () => {
|
|
||||||
expect(contextMenu?.querySelectorAll('button').length).toBe(5);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render item as disabled when `disabled` property is set to true', async () => {
|
|
||||||
expect(contextMenu?.querySelectorAll('button')[0].disabled).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set first not disabled item as active', async () => {
|
|
||||||
const icon = await loader.getHarness(MatIconHarness.with({ ancestor: 'adf-context-menu' }));
|
|
||||||
|
|
||||||
expect(await icon.getName()).toEqual('action-icon-3');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not allow action event when item is disabled', () => {
|
|
||||||
contextMenu?.querySelectorAll('button')[0].click();
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
expect(fixture.componentInstance.actions[1].subject.next).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should perform action when item is not disabled', () => {
|
|
||||||
contextMenu?.querySelectorAll('button')[1].click();
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
expect(fixture.componentInstance.actions[2].subject.next).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not render item icon if not set', async () => {
|
|
||||||
expect(
|
|
||||||
(
|
|
||||||
await loader.getAllHarnesses(
|
|
||||||
MatIconHarness.with({
|
|
||||||
ancestor: 'adf-context-menu',
|
|
||||||
name: 'Action 1'
|
|
||||||
})
|
|
||||||
)
|
|
||||||
).length
|
|
||||||
).toBe(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@@ -156,6 +156,20 @@ describe('DataTable', () => {
|
|||||||
fixture.destroy();
|
fixture.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not emit showRowContextMenu when the component is loaded with rows', () => {
|
||||||
|
spyOn(dataTable.showRowContextMenu, 'emit');
|
||||||
|
const newData = new ObjectDataTableAdapter([{ name: 'TEST' }, { name: 'FAKE' }], [new ObjectDataColumn({ key: 'name' })]);
|
||||||
|
dataTable.data = new ObjectDataTableAdapter([{ name: '1' }, { name: '2' }], [new ObjectDataColumn({ key: 'name' })]);
|
||||||
|
|
||||||
|
dataTable.ngOnChanges({
|
||||||
|
data: new SimpleChange(null, newData, false)
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(dataTable.showRowContextMenu.emit).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it('should return only visible columns', () => {
|
it('should return only visible columns', () => {
|
||||||
const columns = [
|
const columns = [
|
||||||
{ key: 'col1', isHidden: false },
|
{ key: 'col1', isHidden: false },
|
||||||
|
@@ -803,10 +803,12 @@ export class DataTableComponent implements OnInit, AfterContentInit, OnChanges,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
getContextMenuActions(row: DataRow, col: DataColumn): any[] {
|
getContextMenuActions(row: DataRow, col: DataColumn): () => any[] {
|
||||||
|
return () => {
|
||||||
const event = new DataCellEvent(row, col, []);
|
const event = new DataCellEvent(row, col, []);
|
||||||
this.showRowContextMenu.emit(event);
|
this.showRowContextMenu.emit(event);
|
||||||
return event.value.actions;
|
return event.value.actions;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getRowActions(row: DataRow, col?: DataColumn): any[] {
|
getRowActions(row: DataRow, col?: DataColumn): any[] {
|
||||||
|
Reference in New Issue
Block a user