From 43011cbd13e04183ddeac9e33698acd743d7a841 Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Tue, 22 Jun 2021 19:49:18 -0400 Subject: [PATCH] chore: add more unit tests & code refactoring AutoTooltip plugin --- .../src/examples/example04.ts | 3 + .../src/examples/example10.ts | 4 + .../interfaces/autoTooltipOption.interface.ts | 4 +- .../__tests__/autoTooltips.plugin.spec.ts | 103 +++++++++++++----- .../common/src/plugins/autoTooltips.plugin.ts | 24 +++- packages/common/src/styles/slick-grid.scss | 3 + 6 files changed, 106 insertions(+), 35 deletions(-) diff --git a/examples/webpack-demo-vanilla-bundle/src/examples/example04.ts b/examples/webpack-demo-vanilla-bundle/src/examples/example04.ts index d68418e0d..9ebb695f5 100644 --- a/examples/webpack-demo-vanilla-bundle/src/examples/example04.ts +++ b/examples/webpack-demo-vanilla-bundle/src/examples/example04.ts @@ -343,6 +343,9 @@ export class Example4 { container: '.demo-container', }, enableAutoTooltip: true, + autoTooltipOptions: { + enableForHeaderCells: true + }, enableAutoSizeColumns: true, enableAutoResize: true, enableCellNavigation: true, diff --git a/examples/webpack-demo-vanilla-bundle/src/examples/example10.ts b/examples/webpack-demo-vanilla-bundle/src/examples/example10.ts index fe27e1c62..fe93a458f 100644 --- a/examples/webpack-demo-vanilla-bundle/src/examples/example10.ts +++ b/examples/webpack-demo-vanilla-bundle/src/examples/example10.ts @@ -125,6 +125,10 @@ export class Example10 { const presetHighestDay = moment().add(20, 'days').format('YYYY-MM-DD'); this.gridOptions = { + enableAutoTooltip: true, + autoTooltipOptions: { + enableForHeaderCells: true + }, enableTranslate: true, translater: this.translateService, // pass the TranslateService instance to the grid enableAutoResize: false, diff --git a/packages/common/src/interfaces/autoTooltipOption.interface.ts b/packages/common/src/interfaces/autoTooltipOption.interface.ts index 488e7cdc5..6dfe83dbe 100644 --- a/packages/common/src/interfaces/autoTooltipOption.interface.ts +++ b/packages/common/src/interfaces/autoTooltipOption.interface.ts @@ -1,9 +1,9 @@ export interface AutoTooltipOption { /** are tooltip enabled for all cells? */ - enableForCells: boolean; + enableForCells?: boolean; /** are tooltip enabled for column headers */ - enableForHeaderCells: boolean; + enableForHeaderCells?: boolean; /** what is the maximum tooltip length in pixels (only type the number) */ maxToolTipLength?: number; diff --git a/packages/common/src/plugins/__tests__/autoTooltips.plugin.spec.ts b/packages/common/src/plugins/__tests__/autoTooltips.plugin.spec.ts index fbc657571..190bde8d0 100644 --- a/packages/common/src/plugins/__tests__/autoTooltips.plugin.spec.ts +++ b/packages/common/src/plugins/__tests__/autoTooltips.plugin.spec.ts @@ -1,8 +1,9 @@ -import { AutoTooltipOption, Column, GridOption, SlickGrid, SlickNamespace } from '../../interfaces/index'; +import { AutoTooltipOption, Column, SlickGrid, SlickNamespace } from '../../interfaces/index'; import { SharedService } from '../../services/shared.service'; import { AutoTooltipsPlugin } from '../autoTooltips.plugin'; declare const Slick: SlickNamespace; + let pluginOptions: AutoTooltipOption = { enableForCells: true, enableForHeaderCells: true, @@ -19,11 +20,6 @@ const gridStub = { onMouseEnter: new Slick.Event(), } as unknown as SlickGrid; -const mockAddon = jest.fn().mockImplementation(() => ({ - init: jest.fn(), - destroy: jest.fn() -})); - const mockColumns = [ // The column definitions { name: 'Short', field: 'short', width: 100 }, { name: 'Medium', field: 'medium', width: 100 }, @@ -34,8 +30,6 @@ const mockColumns = [ // The column definitions ] as Column[]; describe('AutoTooltip Plugin', () => { - jest.mock('slickgrid/plugins/slick.autotooltips', () => mockAddon); - Slick.AutoTooltips = mockAddon; let plugin: AutoTooltipsPlugin; beforeEach(() => { @@ -58,13 +52,53 @@ describe('AutoTooltip Plugin', () => { }); }); - describe('onMouseEnter event', () => { + describe('plugins - autotooltips - header', () => { beforeEach(() => { jest.spyOn(SharedService.prototype, 'slickGrid', 'get').mockReturnValue(gridStub); }); afterEach(() => { - plugin.destroy(); + plugin.dispose(); + }); + + it('should expect title is empty when header column has enough width', () => { + const mockNodeElm = document.createElement('div'); + mockNodeElm.title = ''; + mockNodeElm.textContent = 'some text'; + jest.spyOn(gridStub, 'getCellFromEvent').mockReturnValue({ row: 1, cell: 2 }); + jest.spyOn(gridStub, 'getCellNode').mockReturnValue(mockNodeElm); + Object.defineProperty(mockNodeElm, 'clientWidth', { writable: true, configurable: true, value: 150 }); + Object.defineProperty(mockNodeElm, 'scrollWidth', { writable: true, configurable: true, value: 100 }); + + gridStub.onMouseEnter.notify({ grid: gridStub }, new Slick.EventData()); + + expect(mockNodeElm.title).toBe(''); + }); + + it('title is present when header column is cut off', () => { + const mockNodeElm = document.createElement('div'); + mockNodeElm.title = ''; + mockNodeElm.textContent = 'some text'; + jest.spyOn(gridStub, 'getCellFromEvent').mockReturnValue({ row: 1, cell: 2 }); + jest.spyOn(gridStub, 'getCellNode').mockReturnValue(mockNodeElm); + Object.defineProperty(mockNodeElm, 'clientWidth', { writable: true, configurable: true, value: 150 }); + Object.defineProperty(mockNodeElm, 'scrollWidth', { writable: true, configurable: true, value: 100 }); + + const eventData = new Slick.EventData(); + gridStub.onMouseEnter.notify({ grid: gridStub }, new Slick.EventData()); + gridStub.onHeaderMouseEnter.notify({ column: mockColumns[4], grid: gridStub }, eventData); + + expect(mockNodeElm.title).toBe(''); + }); + }); + + describe('plugins - autotooltips - max tooltip', () => { + beforeEach(() => { + jest.spyOn(SharedService.prototype, 'slickGrid', 'get').mockReturnValue(gridStub); + }); + + afterEach(() => { + plugin.dispose(); }); it('title is empty when cell text has enough room', () => { @@ -94,12 +128,6 @@ describe('AutoTooltip Plugin', () => { expect(mockNodeElm.title).toBe('my super very lon...'); }); - }); - - describe('onHeaderMouseEnter event', () => { - beforeEach(() => { - jest.spyOn(SharedService.prototype, 'slickGrid', 'get').mockReturnValue(gridStub); - }); it('title is empty when header column has enough width', () => { const mockNodeElm = document.createElement('div'); @@ -126,25 +154,46 @@ describe('AutoTooltip Plugin', () => { const mockNodeElm = document.createElement('div'); const mockHeaderElm = document.createElement('div'); const mockHeaderColElm = document.createElement('div'); - mockHeaderColElm.className = 'slick-header-column'; + mockNodeElm.className = 'slick-header-column'; + mockHeaderColElm.className = 'slick-column-name'; mockHeaderElm.title = ''; mockHeaderElm.textContent = 'short text'; mockNodeElm.appendChild(mockHeaderElm); mockHeaderElm.appendChild(mockHeaderColElm); jest.spyOn(gridStub, 'getCellFromEvent').mockReturnValue({ row: 1, cell: 2 }); jest.spyOn(gridStub, 'getCellNode').mockReturnValue(mockNodeElm); - Object.defineProperty(mockNodeElm, 'clientWidth', { writable: true, configurable: true, value: 140 }); - Object.defineProperty(mockNodeElm, 'scrollWidth', { writable: true, configurable: true, value: 175 }); - Object.defineProperty(mockHeaderColElm, 'clientWidth', { writable: true, configurable: true, value: 50 }); - Object.defineProperty(mockHeaderColElm, 'scrollWidth', { writable: true, configurable: true, value: 175 }); + Object.defineProperty(mockNodeElm, 'clientWidth', { writable: true, configurable: true, value: 144 }); + Object.defineProperty(mockHeaderColElm, 'clientWidth', { writable: true, configurable: true, value: 130 }); const eventData = new Slick.EventData(); - Object.defineProperty(eventData, 'target', { writable: true, configurable: true, value: mockNodeElm }); - gridStub.onMouseEnter.notify({ grid: gridStub }, new Slick.EventData()); - gridStub.onHeaderMouseEnter.notify({ column: mockColumns[2], grid: gridStub }, eventData); + Object.defineProperty(eventData, 'target', { writable: true, configurable: true, value: mockHeaderColElm }); + gridStub.onMouseEnter.notify({ grid: gridStub }, eventData); + gridStub.onHeaderMouseEnter.notify({ column: mockColumns[4], grid: gridStub }, eventData); - expect(mockNodeElm.title).toBe('short text'); - expect(mockHeaderElm.title).toBe(''); + expect(mockNodeElm.title).toBe('Long header creates tooltip'); + }); + + it('title is not overridden when header column has pre-defined tooltip', () => { + const mockNodeElm = document.createElement('div'); + const mockHeaderElm = document.createElement('div'); + const mockHeaderColElm = document.createElement('div'); + mockNodeElm.className = 'slick-header-column'; + mockHeaderColElm.className = 'slick-column-name'; + mockHeaderElm.title = ''; + mockHeaderElm.textContent = 'short text'; + mockNodeElm.appendChild(mockHeaderElm); + mockHeaderElm.appendChild(mockHeaderColElm); + jest.spyOn(gridStub, 'getCellFromEvent').mockReturnValue({ row: 1, cell: 2 }); + jest.spyOn(gridStub, 'getCellNode').mockReturnValue(mockNodeElm); + Object.defineProperty(mockNodeElm, 'clientWidth', { writable: true, configurable: true, value: 144 }); + Object.defineProperty(mockHeaderColElm, 'clientWidth', { writable: true, configurable: true, value: 130 }); + + const eventData = new Slick.EventData(); + Object.defineProperty(eventData, 'target', { writable: true, configurable: true, value: mockHeaderColElm }); + gridStub.onMouseEnter.notify({ grid: gridStub }, eventData); + gridStub.onHeaderMouseEnter.notify({ column: mockColumns[5], grid: gridStub }, eventData); + + expect(mockNodeElm.title).toBe(''); }); it('title is present and truncated when cell text is cut off and too long', () => { @@ -165,7 +214,7 @@ describe('AutoTooltip Plugin', () => { const eventData = new Slick.EventData(); Object.defineProperty(eventData, 'target', { writable: true, configurable: true, value: mockNodeElm }); - gridStub.onMouseEnter.notify({ grid: gridStub }, new Slick.EventData()); + gridStub.onMouseEnter.notify({ grid: gridStub }, eventData); gridStub.onHeaderMouseEnter.notify({ column: mockColumns[4], grid: gridStub }, eventData); expect(mockNodeElm.title).toBe('Long header creat...'); diff --git a/packages/common/src/plugins/autoTooltips.plugin.ts b/packages/common/src/plugins/autoTooltips.plugin.ts index 5d4daa9c9..9305161d7 100644 --- a/packages/common/src/plugins/autoTooltips.plugin.ts +++ b/packages/common/src/plugins/autoTooltips.plugin.ts @@ -10,6 +10,13 @@ import { // using external SlickGrid JS libraries declare const Slick: SlickNamespace; +/** + * AutoTooltips plugin to show/hide tooltips when columns are too narrow to fit content. + * @constructor + * @param {boolean} [options.enableForCells=true] - Enable tooltip for grid cells + * @param {boolean} [options.enableForHeaderCells=false] - Enable tooltip for header cells + * @param {number} [options.maxToolTipLength=null] - The maximum length for a tooltip + */ export class AutoTooltipsPlugin { private _eventHandler!: SlickEventHandler; private _grid!: SlickGrid; @@ -38,7 +45,7 @@ export class AutoTooltipsPlugin { /** Initialize plugin. */ init(grid: SlickGrid) { - this._options = { ...this._defaults, ...this._options }; + this._options = { ...this._defaults, ...this.options }; this._grid = grid; if (this._options.enableForCells) { const onMouseEnterHandler = this._grid.onMouseEnter; @@ -50,8 +57,8 @@ export class AutoTooltipsPlugin { } } - /** Destroy (dispose) the SlickGrid 3rd party plugin */ - destroy() { + /** Dispose (destroy) the SlickGrid 3rd party plugin */ + dispose() { this._eventHandler?.unsubscribeAll(); } @@ -90,9 +97,14 @@ export class AutoTooltipsPlugin { */ private handleHeaderMouseEnter(event: Event, args: { column: Column; }) { const column = args.column; - let node = (event.target as HTMLDivElement).querySelector('.slick-header-column'); - if (node && !column?.toolTip) { - node.title = (node.clientWidth < node.scrollWidth) ? column.name ?? '' : ''; + let node: HTMLDivElement | null; + const targetElm = (event.target as HTMLDivElement); + + if (targetElm) { + node = targetElm.closest('.slick-header-column'); + if (node && !(column?.toolTip)) { + node.title = (targetElm.clientWidth < node.clientWidth) ? column?.name ?? '' : ''; + } } node = null; } diff --git a/packages/common/src/styles/slick-grid.scss b/packages/common/src/styles/slick-grid.scss index 925e426dc..615eef1bb 100644 --- a/packages/common/src/styles/slick-grid.scss +++ b/packages/common/src/styles/slick-grid.scss @@ -207,6 +207,9 @@ .slick-column-name { text-overflow: ellipsis; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; } .slick-resizable-handle {