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:
Michaela Kröber
2024-11-21 15:32:25 +01:00
committed by GitHub
parent 7f41675d1c
commit afc5830c31
5 changed files with 260 additions and 210 deletions

View 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);
});
});
});
});

View File

@@ -27,7 +27,7 @@ import { ContextMenuOverlayService } from './context-menu-overlay.service';
export class ContextMenuDirective {
/** Items for the menu. */
@Input('adf-context-menu')
links: any[];
links: any[] | (() => any[]);
/** Is the menu enabled? */
@Input('adf-context-menu-enabled')
@@ -42,12 +42,15 @@ export class ContextMenuDirective {
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({
source: event,
data: this.links
data: actions
});
}
}
}
}
}

View File

@@ -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);
});
});
});

View File

@@ -156,6 +156,20 @@ describe('DataTable', () => {
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', () => {
const columns = [
{ key: 'col1', isHidden: false },

View File

@@ -803,10 +803,12 @@ export class DataTableComponent implements OnInit, AfterContentInit, OnChanges,
return false;
}
getContextMenuActions(row: DataRow, col: DataColumn): any[] {
getContextMenuActions(row: DataRow, col: DataColumn): () => any[] {
return () => {
const event = new DataCellEvent(row, col, []);
this.showRowContextMenu.emit(event);
return event.value.actions;
};
}
getRowActions(row: DataRow, col?: DataColumn): any[] {