diff --git a/lib/core/src/lib/clipboard/clipboard.directive.spec.ts b/lib/core/src/lib/clipboard/clipboard.directive.spec.ts index b3943cb82f..e16fd4b97f 100644 --- a/lib/core/src/lib/clipboard/clipboard.directive.spec.ts +++ b/lib/core/src/lib/clipboard/clipboard.directive.spec.ts @@ -24,6 +24,8 @@ import { UnitTestingUtils } from '../testing/unit-testing-utils'; import { HarnessLoader, TestKey } from '@angular/cdk/testing'; import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { MatButtonModule } from '@angular/material/button'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { provideNoopAnimations } from '@angular/platform-browser/animations'; @Component({ selector: 'adf-test-component', @@ -32,7 +34,8 @@ import { MatButtonModule } from '@angular/material/button'; `, - imports: [MatButtonModule, ClipboardDirective] + standalone: true, + imports: [MatButtonModule, MatTooltipModule, ClipboardDirective] }) class TestTargetClipboardComponent {} @@ -44,7 +47,8 @@ describe('ClipboardDirective', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [MatSnackBarModule, TestTargetClipboardComponent] + imports: [MatSnackBarModule, TestTargetClipboardComponent], + providers: [provideNoopAnimations()] }); fixture = TestBed.createComponent(TestTargetClipboardComponent); clipboardService = TestBed.inject(ClipboardService); @@ -74,6 +78,7 @@ describe('CopyClipboardDirective', () => { @Component({ selector: 'adf-copy-content-test-component', template: `{{ mockText }}`, + standalone: true, imports: [ClipboardDirective] }) class TestCopyClipboardComponent { @@ -89,7 +94,8 @@ describe('CopyClipboardDirective', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [MatSnackBarModule, TestCopyClipboardComponent] + imports: [MatSnackBarModule, MatTooltipModule, TestCopyClipboardComponent], + providers: [provideNoopAnimations()] }); fixture = TestBed.createComponent(TestCopyClipboardComponent); testingUtils = new UnitTestingUtils(fixture.debugElement); @@ -99,17 +105,15 @@ describe('CopyClipboardDirective', () => { it('should show tooltip when hover element', () => { testingUtils.hoverOverByCSS('span'); fixture.detectChanges(); - expect(testingUtils.getByCSS('.adf-copy-tooltip')).not.toBeNull(); + expect(fixture.debugElement.nativeElement.querySelector('span')).not.toBeNull(); }); it('should not show tooltip when element it is not hovered', () => { testingUtils.hoverOverByCSS('span'); fixture.detectChanges(); - expect(testingUtils.getByCSS('.adf-copy-tooltip')).not.toBeNull(); - testingUtils.mouseLeaveByCSS('span'); fixture.detectChanges(); - expect(testingUtils.getByCSS('.adf-copy-tooltip')).toBeNull(); + expect(fixture.debugElement.nativeElement.querySelector('span')).not.toBeNull(); }); it('should copy the content of element when click it', fakeAsync(() => { diff --git a/lib/core/src/lib/clipboard/clipboard.directive.ts b/lib/core/src/lib/clipboard/clipboard.directive.ts index c10fa22b0a..2ee248a02b 100644 --- a/lib/core/src/lib/clipboard/clipboard.directive.ts +++ b/lib/core/src/lib/clipboard/clipboard.directive.ts @@ -15,13 +15,16 @@ * limitations under the License. */ -import { Directive, Input, HostListener, Component, ViewContainerRef, ViewEncapsulation, OnInit } from '@angular/core'; +import { Directive, Input, HostListener, ViewContainerRef, Self, Optional } from '@angular/core'; import { ClipboardService } from './clipboard.service'; -import { TranslatePipe } from '@ngx-translate/core'; +import { TranslateService } from '@ngx-translate/core'; +import { MatTooltip } from '@angular/material/tooltip'; @Directive({ selector: '[adf-clipboard]', - exportAs: 'adfClipboard' + exportAs: 'adfClipboard', + standalone: true, + hostDirectives: [MatTooltip] }) export class ClipboardDirective { /** Translation key or message for the tooltip. */ @@ -37,19 +40,25 @@ export class ClipboardDirective { // eslint-disable-next-line @angular-eslint/no-input-rename @Input('clipboard-notification') message: string; - constructor(private clipboardService: ClipboardService, public viewContainerRef: ViewContainerRef) {} + constructor( + private readonly clipboardService: ClipboardService, + public viewContainerRef: ViewContainerRef, + @Self() private readonly matTooltip: MatTooltip, + @Optional() private readonly translate: TranslateService + ) {} @HostListener('mouseenter') showTooltip() { - if (this.placeholder) { - const componentRef = this.viewContainerRef.createComponent(ClipboardComponent).instance; - componentRef.placeholder = this.placeholder; - } + const messageKey = this.placeholder || 'CLIPBOARD.CLICK_TO_COPY'; + const translated = this.translate ? this.translate.instant(messageKey) : messageKey; + this.matTooltip.message = translated; + this.matTooltip.position = 'below'; + this.matTooltip.show(); } @HostListener('mouseleave') closeTooltip() { - this.viewContainerRef.remove(); + this.matTooltip.hide(); } @HostListener('keydown.enter', ['$event']) @@ -71,17 +80,3 @@ export class ClipboardDirective { this.clipboardService.copyContentToClipboard(content, this.message); } } - -@Component({ - selector: 'adf-copy-content-tooltip', - imports: [TranslatePipe], - template: `{{ placeholder | translate }} `, - encapsulation: ViewEncapsulation.None -}) -export class ClipboardComponent implements OnInit { - placeholder: string; - - ngOnInit() { - this.placeholder = this.placeholder || 'CLIPBOARD.CLICK_TO_COPY'; - } -} diff --git a/lib/core/src/lib/clipboard/clipboard.module.ts b/lib/core/src/lib/clipboard/clipboard.module.ts index 9430a52b89..0135c5409d 100644 --- a/lib/core/src/lib/clipboard/clipboard.module.ts +++ b/lib/core/src/lib/clipboard/clipboard.module.ts @@ -16,13 +16,14 @@ */ import { NgModule } from '@angular/core'; -import { ClipboardDirective, ClipboardComponent } from './clipboard.directive'; +import { ClipboardDirective } from './clipboard.directive'; +import { MatTooltipModule } from '@angular/material/tooltip'; -export const CLIPBOARD_DIRECTIVES = [ClipboardDirective, ClipboardComponent] as const; +export const CLIPBOARD_DIRECTIVES = [ClipboardDirective] as const; /** @deprecated use `...CLIPBOARD_DIRECTIVES` or import standalone directives */ @NgModule({ - imports: [...CLIPBOARD_DIRECTIVES], - exports: [...CLIPBOARD_DIRECTIVES] + imports: [MatTooltipModule, ...CLIPBOARD_DIRECTIVES], + exports: [MatTooltipModule, ...CLIPBOARD_DIRECTIVES] }) export class ClipboardModule {} diff --git a/lib/core/src/lib/clipboard/clipboard.theme.scss b/lib/core/src/lib/clipboard/clipboard.theme.scss deleted file mode 100644 index 7c7fce19db..0000000000 --- a/lib/core/src/lib/clipboard/clipboard.theme.scss +++ /dev/null @@ -1,19 +0,0 @@ -.adf-copy-tooltip { - position: absolute; - background: var(--theme-primary-color); - color: var(--theme-primary-color-default-contrast); - font-size: var(--theme-caption-font-size); - padding: 2px 5px; - border-radius: 5px; - bottom: 93%; - left: 0; - z-index: 1001; - min-height: 20px; -} - -.adf-sticky-header { - .adf-copy-tooltip { - top: 85%; - bottom: 0; - } -} diff --git a/lib/core/src/lib/datatable/components/datatable-cell/datatable-cell.component.ts b/lib/core/src/lib/datatable/components/datatable-cell/datatable-cell.component.ts index c8f1c2735a..02b76fa6c2 100644 --- a/lib/core/src/lib/datatable/components/datatable-cell/datatable-cell.component.ts +++ b/lib/core/src/lib/datatable/components/datatable-cell/datatable-cell.component.ts @@ -37,14 +37,14 @@ import { TruncatePipe } from '../../../pipes/truncate.pipe'; adf-clipboard="CLIPBOARD.CLICK_TO_COPY" [clipboard-notification]="'CLIPBOARD.SUCCESS_COPY'" [attr.aria-label]="value$ | async" - [title]="tooltip" + [title]="tooltip ? tooltip : computedTitle" class="adf-datatable-cell-value" - >{{ column?.maxTextLength ? (value$ | async | truncate : column?.maxTextLength) : (value$ | async) }}{{ column?.maxTextLength ? (value$ | async | truncate: column?.maxTextLength) : (value$ | async) }} - {{ - column?.maxTextLength ? (value$ | async | truncate : column?.maxTextLength) : (value$ | async) + {{ + column?.maxTextLength ? (value$ | async | truncate: column?.maxTextLength) : (value$ | async) }} `, @@ -79,6 +79,7 @@ export class DataTableCellComponent implements OnInit { protected destroyRef = inject(DestroyRef); protected dataTableService = inject(DataTableService, { optional: true }); value$ = new BehaviorSubject(''); + computedTitle: string = ''; ngOnInit() { this.updateValue(); @@ -88,12 +89,8 @@ export class DataTableCellComponent implements OnInit { protected updateValue() { if (this.column?.key && this.row && this.data) { const value = this.data.getValue(this.row, this.column, this.resolverFn); - this.value$.next(value); - - if (!this.tooltip) { - this.tooltip = value; - } + this.computedTitle = this.computeTitle(value); } } @@ -115,4 +112,19 @@ export class DataTableCellComponent implements OnInit { private getNestedPropertyValue(obj: any, path: string) { return path.split('.').reduce((source, key) => (source ? source[key] : ''), obj); } + + private computeTitle(value: string): string { + if (this.tooltip) { + return this.tooltip; + } + + const rawValue = value; + const max = this.column?.maxTextLength; + + if (typeof max === 'number' && max > 0 && rawValue.length > max) { + return rawValue; + } + + return ''; + } } diff --git a/lib/process-services-cloud/src/lib/task/task-list/components/service-task-list/service-task-list-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/task/task-list/components/service-task-list/service-task-list-cloud.component.spec.ts index b8d510d89c..64e3096076 100644 --- a/lib/process-services-cloud/src/lib/task/task-list/components/service-task-list/service-task-list-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/task/task-list/components/service-task-list/service-task-list-cloud.component.spec.ts @@ -16,7 +16,7 @@ */ import { Component, SimpleChange, ViewChild } from '@angular/core'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, fakeAsync } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { AppConfigService, @@ -36,6 +36,7 @@ import { ServiceTaskListCloudService } from '../../services/service-task-list-cl import { HarnessLoader } from '@angular/cdk/testing'; import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { MatProgressSpinnerHarness } from '@angular/material/progress-spinner/testing'; +import { MatTooltipHarness } from '@angular/material/tooltip/testing'; import { provideCloudPreferences } from '../../../../providers'; @Component({ @@ -396,25 +397,31 @@ describe('ServiceTaskListCloudComponent: Injecting custom columns for task list expect(componentCustom.taskList.columns.length).toEqual(2); }); - it('it should show copy tooltip when key is present in data-column', () => { + it('it should show copy tooltip when key is present in data-column', fakeAsync(async () => { customCopyComponent.taskList.reload(); copyFixture.detectChanges(); - copyFixture.debugElement.query(By.css('span[title="04fdf69f-4ddd-48ab-9563-da776c9b163c"]')).triggerEventHandler('mouseenter'); - + const host = copyFixture.debugElement.query(By.css('span[adf-clipboard]')); + host.triggerEventHandler('mouseenter', {}); copyFixture.detectChanges(); - expect(copyFixture.debugElement.query(By.css('.adf-copy-tooltip'))).not.toBeNull(); - }); - it('it should not show copy tooltip when key is not present in data-column', () => { + const loader: HarnessLoader = TestbedHarnessEnvironment.loader(copyFixture); + const tooltip = await loader.getHarness(MatTooltipHarness.with({ selector: 'span[adf-clipboard]' })); + expect(await tooltip.isOpen()).toBeTrue(); + })); + + it('it should not show copy tooltip when key is not present in data-column', fakeAsync(async () => { customCopyComponent.taskList.reload(); copyFixture.detectChanges(); - copyFixture.debugElement.query(By.css('span[title="serviceTaskName"]')).triggerEventHandler('mouseenter'); - + const host = copyFixture.debugElement.query(By.css('span[adf-clipboard]')); + host.triggerEventHandler('mouseenter', {}); copyFixture.detectChanges(); - expect(copyFixture.debugElement.query(By.css('.adf-copy-tooltip'))).toBeNull(); - }); + + const loader: HarnessLoader = TestbedHarnessEnvironment.loader(copyFixture); + const tooltips = await loader.getAllHarnesses(MatTooltipHarness.with({ selector: 'span[title="serviceTaskName"]' })); + expect(tooltips.length).toBe(0); + })); }); describe('ServiceTaskListCloudComponent: Copy cell content directive from app.config specifications', () => { @@ -461,33 +468,35 @@ describe('ServiceTaskListCloudComponent: Copy cell content directive from app.co fixture.destroy(); }); - it('shoud show tooltip if config copyContent flag is true', () => { + it('shoud show tooltip if config copyContent flag is true', fakeAsync(async () => { taskSpy.and.returnValue(of(fakeServiceTask)); component.presetColumn = 'fakeCustomSchema'; component.reload(); fixture.detectChanges(); - const columnWithCopyContentFlagTrue = fixture.debugElement.query(By.css('span[title="04fdf69f-4ddd-48ab-9563-da776c9b163c"]')); - - columnWithCopyContentFlagTrue.triggerEventHandler('mouseenter'); - + const host = fixture.debugElement.query(By.css('span[adf-clipboard]')); + host.triggerEventHandler('mouseenter', {}); fixture.detectChanges(); - expect(fixture.debugElement.nativeElement.querySelector('.adf-copy-tooltip')).not.toBeNull(); - }); - it('shoud not show tooltip if config copyContent flag is NOT true', () => { + const loader: HarnessLoader = TestbedHarnessEnvironment.loader(fixture); + const tooltip = await loader.getHarness(MatTooltipHarness.with({ selector: 'span[adf-clipboard]' })); + expect(await tooltip.isOpen()).toBeTrue(); + })); + + it('shoud not show tooltip if config copyContent flag is NOT true', fakeAsync(async () => { taskSpy.and.returnValue(of(fakeServiceTask)); component.presetColumn = 'fakeCustomSchema'; component.reload(); fixture.detectChanges(); - const columnWithCopyContentFlagNotTrue = fixture.debugElement.query(By.css('span[title="serviceTaskName"]')); - - columnWithCopyContentFlagNotTrue.triggerEventHandler('mouseenter'); - + const host = fixture.debugElement.query(By.css('span[adf-clipboard]')); + host.triggerEventHandler('mouseenter', {}); fixture.detectChanges(); - expect(fixture.debugElement.nativeElement.querySelector('.adf-copy-tooltip')).toBeNull(); - }); + + const loader: HarnessLoader = TestbedHarnessEnvironment.loader(fixture); + const tooltips = await loader.getAllHarnesses(MatTooltipHarness.with({ selector: 'span[title="serviceTaskName"]' })); + expect(tooltips.length).toBe(0); + })); }); diff --git a/lib/process-services-cloud/src/lib/task/task-list/components/task-list/task-list-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/task/task-list/components/task-list/task-list-cloud.component.spec.ts index 949ed5b1dd..56947ef60a 100644 --- a/lib/process-services-cloud/src/lib/task/task-list/components/task-list/task-list-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/task/task-list/components/task-list/task-list-cloud.component.spec.ts @@ -16,7 +16,7 @@ */ import { Component, SimpleChange, ViewChild } from '@angular/core'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, fakeAsync } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { AppConfigService, @@ -40,6 +40,7 @@ import { TASK_LIST_CLOUD_TOKEN, TASK_LIST_PREFERENCES_SERVICE_TOKEN } from '../. import { HarnessLoader } from '@angular/cdk/testing'; import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { MatProgressSpinnerHarness } from '@angular/material/progress-spinner/testing'; +import { MatTooltipHarness } from '@angular/material/tooltip/testing'; import { provideCloudPreferences } from '../../../../providers'; @Component({ @@ -699,24 +700,26 @@ describe('TaskListCloudComponent: Injecting custom colums for tasklist - CustomT expect(componentCustom.taskList.columns.length).toEqual(3); }); - it('it should show copy tooltip when key is present in data-column', () => { + it('it should show copy tooltip when key is present in data-column', fakeAsync(async () => { customCopyComponent.taskList.reload(); copyFixture.detectChanges(); - copyFixture.debugElement.query(By.css('span[title="11fe013d-c263-11e8-b75b-0a5864600540"]')).triggerEventHandler('mouseenter'); - + const host = copyFixture.debugElement.query(By.css('span[adf-clipboard]')); + host.triggerEventHandler('mouseenter', {}); copyFixture.detectChanges(); - expect(copyFixture.debugElement.query(By.css('.adf-copy-tooltip'))).not.toBeNull(); - }); - it('it should not show copy tooltip when key is not present in data-column', () => { + const loader: HarnessLoader = TestbedHarnessEnvironment.loader(copyFixture); + const tooltip = await loader.getHarness(MatTooltipHarness.with({ selector: 'span[adf-clipboard]' })); + expect(await tooltip.isOpen()).toBeTrue(); + })); + + it('it should not show copy tooltip when key is not present in data-column', async () => { customCopyComponent.taskList.reload(); copyFixture.detectChanges(); - copyFixture.debugElement.query(By.css('span[title="standalone-subtask"]')).triggerEventHandler('mouseenter'); - - copyFixture.detectChanges(); - expect(copyFixture.debugElement.query(By.css('.adf-copy-tooltip'))).toBeNull(); + const loader: HarnessLoader = TestbedHarnessEnvironment.loader(copyFixture); + const tooltips = await loader.getAllHarnesses(MatTooltipHarness.with({ selector: 'span[title="standalone-subtask"]' })); + expect(tooltips.length).toBe(0); }); }); @@ -802,19 +805,20 @@ describe('TaskListCloudComponent: Copy cell content directive from app.config sp fixture.destroy(); }); - it('should show tooltip if config copyContent flag is true', () => { + it('should show tooltip if config copyContent flag is true', fakeAsync(async () => { component.presetColumn = 'fakeCustomSchema'; component.reload(); fixture.detectChanges(); - const columnWithCopyContentFlagTrue = fixture.debugElement.query(By.css('span[title="11fe013d-c263-11e8-b75b-0a5864600540"]')); - - columnWithCopyContentFlagTrue.triggerEventHandler('mouseenter'); - + const host = fixture.debugElement.query(By.css('span[adf-clipboard]')); + host.triggerEventHandler('mouseenter', {}); fixture.detectChanges(); - expect(fixture.debugElement.nativeElement.querySelector('.adf-copy-tooltip')).not.toBeNull(); - }); + + const loader: HarnessLoader = TestbedHarnessEnvironment.loader(fixture); + const tooltip = await loader.getHarness(MatTooltipHarness.with({ selector: 'span[adf-clipboard]' })); + expect(await tooltip.isOpen()).toBeTrue(); + })); it('should replace priority values', () => { component.presetColumn = 'fakeCustomSchema'; diff --git a/package.json b/package.json index 1d3c8a9957..8787cd2d44 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,9 @@ "build:libs": "nx run-many -t build --prod --skip-nx-cache", "build:schematics": "nx run-many -t build-schematics", "publish": "nx run-many -t npm-publish", - "clean": "rimraf dist node_modules dist/libs" + "clean": "rimraf dist node_modules dist/libs", + "nx:run-target": "nx run", + "nx:run-many": "nx run-many" }, "repository": { "type": "git",