Skip to content

Commit 85cabd7

Browse files
committed
fix: allow multiple tooltips per grid cell
- for some unknown reasons the tooltips are shown correctly in the browser but not in Cypress, I had to use a few force click
1 parent 9df1932 commit 85cabd7

8 files changed

+85
-48
lines changed

src/app/examples/grid-base-row-editing.component.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
21
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
32
import { TranslateService } from '@ngx-translate/core';
3+
import { SlickCustomTooltip } from '@slickgrid-universal/custom-tooltip-plugin';
44
import { Subscription } from 'rxjs';
55

66
import {
@@ -198,6 +198,7 @@ export class GridBaseRowEditingComponent implements OnInit {
198198
deleteButtonPrompt: 'Are you sure you want to delete this row?',
199199
},
200200
},
201+
externalResources: [new SlickCustomTooltip()],
201202
};
202203
}
203204

src/app/examples/grid-composite-editor.component.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
22
import { HttpClient } from '@angular/common/http';
33
import { ExcelExportService } from '@slickgrid-universal/excel-export';
4+
import { SlickCustomTooltip } from '@slickgrid-universal/custom-tooltip-plugin';
45
import { SlickCompositeEditor, SlickCompositeEditorComponent } from '@slickgrid-universal/composite-editor-component';
56

67
import {
@@ -64,7 +65,7 @@ const customEditableInputFormatter: Formatter = (_row, _cell, value, columnDef,
6465
const gridOptions = grid && grid.getOptions && grid.getOptions();
6566
const isEditableLine = gridOptions.editable && columnDef.editor;
6667
value = (value === null || value === undefined) ? '' : value;
67-
return isEditableLine ? { text: value, addClasses: 'editable-field', toolTip: 'Click to Edit' } : value;
68+
return isEditableLine ? { text: value, addClasses: 'editable-field' } : value;
6869
};
6970

7071
// you can create custom validator to pass to an inline editor
@@ -131,7 +132,8 @@ export class GridCompositeEditorComponent implements OnDestroy, OnInit {
131132
prepareGrid() {
132133
this.columnDefinitions = [
133134
{
134-
id: 'title', name: 'Title', field: 'title', sortable: true, type: FieldType.string, minWidth: 75,
135+
id: 'title', name: '<span title="Task must always be followed by a number" class="color-warning-dark fa fa-exclamation-triangle"></span> Title <span title="Title is always rendered as UPPERCASE" class="fa fa-info-circle"></span>',
136+
field: 'title', sortable: true, type: FieldType.string, minWidth: 75,
135137
cssClass: 'text-uppercase fw-bold', columnGroup: 'Common Factor',
136138
filterable: true, filter: { model: Filters.compoundInputText },
137139
editor: {
@@ -397,7 +399,7 @@ export class GridCompositeEditorComponent implements OnDestroy, OnInit {
397399
excelExportOptions: {
398400
exportWithFormatter: false
399401
},
400-
externalResources: [new ExcelExportService(), this.compositeEditorInstance],
402+
externalResources: [new ExcelExportService(), new SlickCustomTooltip(), this.compositeEditorInstance],
401403
enableFiltering: true,
402404
rowSelectionOptions: {
403405
// True (Single Selection), False (Multiple Selections)

src/app/examples/grid-custom-tooltip.component.scss

+3
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ $slick-button-border-color: #ababab !default;
3535

3636
// it's preferable to use CSS Variables (or SASS) but if you want to change colors of your tooltip for 1 column in particular you can do it this way
3737
// e.g. change css of 5th column 4 (zero index: l4)
38+
.l4 {
39+
--slick-tooltip-color: #fff;
40+
}
3841
.l4 .header-tooltip-title,
3942
.l4 .headerrow-tooltip-title {
4043
color: #ffffff;

src/app/examples/grid-custom-tooltip.component.ts

+11-5
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ export class GridCustomTooltipComponent implements OnInit {
110110
// define tooltip options here OR for the entire grid via the grid options (cell tooltip options will have precedence over grid options)
111111
customTooltip: {
112112
useRegularTooltip: true, // note regular tooltip will try to find a "title" attribute in the cell formatter (it won't work without a cell formatter)
113+
useRegularTooltipFromCellTextOnly: true,
113114
},
114115
},
115116
{
@@ -158,7 +159,12 @@ export class GridCustomTooltipComponent implements OnInit {
158159
formatter: Formatters.percentCompleteBar,
159160
sortable: true, filterable: true,
160161
filter: { model: Filters.slider, operator: '>=' },
161-
customTooltip: { useRegularTooltip: true, },
162+
customTooltip: {
163+
position: 'center',
164+
formatter: (_row, _cell, value) => typeof value === 'string' && value.includes('%') ? value : `${value}%`,
165+
headerFormatter: undefined,
166+
headerRowFormatter: undefined
167+
},
162168
},
163169
{
164170
id: 'start', name: 'Start', field: 'start', sortable: true,
@@ -434,12 +440,12 @@ export class GridCustomTooltipComponent implements OnInit {
434440

435441
tooltipFormatter(row: number, cell: number, value: any, column: Column, dataContext: any, grid: SlickGrid) {
436442
const tooltipTitle = 'Custom Tooltip';
437-
const effortDrivenHtml = Formatters.checkmarkMaterial(row, cell, dataContext.effortDriven, column, dataContext, grid);
443+
const effortDrivenHtml = Formatters.checkmarkMaterial(row, cell, dataContext.effortDriven, column, dataContext, grid) as HTMLElement;
438444

439445
return `<div class="header-tooltip-title">${tooltipTitle}</div>
440446
<div class="tooltip-2cols-row"><div>Id:</div> <div>${dataContext.id}</div></div>
441447
<div class="tooltip-2cols-row"><div>Title:</div> <div>${dataContext.title}</div></div>
442-
<div class="tooltip-2cols-row"><div>Effort Driven:</div> <div>${effortDrivenHtml}</div></div>
448+
<div class="tooltip-2cols-row"><div>Effort Driven:</div> <div>${effortDrivenHtml.outerHTML || ''}</div></div>
443449
<div class="tooltip-2cols-row"><div>Completion:</div> <div>${this.loadCompletionIcons(dataContext.percentComplete)}</div></div>
444450
`;
445451
}
@@ -449,9 +455,9 @@ export class GridCustomTooltipComponent implements OnInit {
449455

450456
// use a 2nd Formatter to get the percent completion
451457
// any properties provided from the `asyncPost` will end up in the `__params` property (unless a different prop name is provided via `asyncParamsPropName`)
452-
const completionBar = Formatters.percentCompleteBarWithText(row, cell, dataContext.percentComplete, column, dataContext, grid);
458+
const completionBar = Formatters.percentCompleteBarWithText(row, cell, dataContext.percentComplete, column, dataContext, grid) as HTMLElement;
453459
const out = `<div class="color-sf-primary-dark header-tooltip-title">${tooltipTitle}</div>
454-
<div class="tooltip-2cols-row"><div>Completion:</div> <div>${completionBar}</div></div>
460+
<div class="tooltip-2cols-row"><div>Completion:</div> <div>${completionBar.outerHTML || ''}</div></div>
455461
<div class="tooltip-2cols-row"><div>Lifespan:</div> <div>${dataContext.__params.lifespan.toFixed(2)}</div></div>
456462
<div class="tooltip-2cols-row"><div>Ratio:</div> <div>${dataContext.__params.ratio.toFixed(2)}</div></div>
457463
`;

test/cypress/e2e/example30.cy.ts

+19-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { changeTimezone, zeroPadding } from '../plugins/utilities';
22

33
describe('Example 30 Composite Editor Modal', () => {
44
const fullPreTitles = ['', 'Common Factor', 'Analysis', 'Period', 'Item', ''];
5-
const fullTitles = ['', 'Title', 'Duration', 'Cost', '% Complete', 'Complexity', 'Start', 'Completed', 'Finish', 'Product', 'Country of Origin', 'Action'];
5+
const fullTitles = ['', ' Title ', 'Duration', 'Cost', '% Complete', 'Complexity', 'Start', 'Completed', 'Finish', 'Product', 'Country of Origin', 'Action'];
66

77
const GRID_ROW_HEIGHT = 35;
88
const EDITABLE_CELL_RGB_COLOR = 'rgba(227, 240, 251, 0.57)';
@@ -32,6 +32,23 @@ describe('Example 30 Composite Editor Modal', () => {
3232
.each(($child, index) => expect($child.text()).to.eq(fullTitles[index]));
3333
});
3434

35+
it('should display 2 different tooltips when hovering icons on "Title" column', () => {
36+
cy.get('.slick-column-name').as('title-column');
37+
cy.get('@title-column')
38+
.find('.fa-exclamation-triangle')
39+
.trigger('mouseover');
40+
41+
cy.get('.slick-custom-tooltip').should('be.visible');
42+
cy.get('.slick-custom-tooltip .tooltip-body').contains('Task must always be followed by a number');
43+
44+
cy.get('@title-column')
45+
.find('.fa-info-circle')
46+
.trigger('mouseover');
47+
48+
cy.get('.slick-custom-tooltip').should('be.visible');
49+
cy.get('.slick-custom-tooltip .tooltip-body').contains('Title is always rendered as UPPERCASE');
50+
});
51+
3552
it('should have "TASK 0" (uppercase) incremented by 1 after each row', () => {
3653
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).contains('TASK 0', { matchCase: false })
3754
.should('have.css', 'text-transform', 'uppercase');
@@ -288,7 +305,7 @@ describe('Example 30 Composite Editor Modal', () => {
288305
cy.get('.slick-editor-modal').should('not.exist');
289306
});
290307

291-
it('should have new TASK 8888 displayed on first row', () => {
308+
it('should have new TASK 8899 displayed on first row', () => {
292309
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).contains('TASK 8899', { matchCase: false });
293310
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(2)`).should('contain', '33 days');
294311
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(4)`).should('contain', '17');

test/cypress/e2e/example32.cy.ts

+9-6
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ describe('Example 32 - Regular & Custom Tooltips', () => {
3636
cy.get('.slick-custom-tooltip').should('be.visible');
3737
cy.get('.slick-custom-tooltip').contains('Task 2 - (async tooltip)');
3838

39+
cy.get('.tooltip-2cols-row:nth(0)').find('div:nth(0)').contains('Completion:');
40+
cy.get('.tooltip-2cols-row:nth(0)').find('div').should('have.class', 'percent-complete-bar-with-text');
41+
3942
cy.get('.tooltip-2cols-row:nth(1)').find('div:nth(0)').contains('Lifespan:');
4043
cy.get('.tooltip-2cols-row:nth(1)').find('div:nth(1)').contains(/\d+$/); // use regexp to make sure it's a number
4144

@@ -167,7 +170,7 @@ describe('Example 32 - Regular & Custom Tooltips', () => {
167170

168171
it('should mouse over header-row (filter) 2nd column Title and expect a tooltip to show rendered from an headerRowFormatter', () => {
169172
cy.get(`.slick-headerrow-columns .slick-headerrow-column:nth(1)`).as('checkbox0-filter');
170-
cy.get('@checkbox0-filter').trigger('mouseenter');
173+
cy.get('@checkbox0-filter').trigger('mouseover');
171174

172175
cy.get('.slick-custom-tooltip').should('be.visible');
173176
cy.get('.slick-custom-tooltip').contains('Custom Tooltip - Header Row (filter)');
@@ -180,15 +183,15 @@ describe('Example 32 - Regular & Custom Tooltips', () => {
180183

181184
it('should mouse over header-row (filter) Finish column and NOT expect any tooltip to show since it is disabled on that column', () => {
182185
cy.get(`.slick-headerrow-columns .slick-headerrow-column:nth(8)`).as('finish-filter');
183-
cy.get('@finish-filter').trigger('mouseenter');
186+
cy.get('@finish-filter').trigger('mouseover');
184187

185188
cy.get('.slick-custom-tooltip').should('not.exist');
186189
cy.get('@finish-filter').trigger('mouseout');
187190
});
188191

189192
it('should mouse over header-row (filter) Prerequisite column and expect to see tooltip of selected filter options', () => {
190193
cy.get(`.slick-headerrow-columns .slick-headerrow-column:nth(10)`).as('checkbox10-header');
191-
cy.get('@checkbox10-header').trigger('mouseenter');
194+
cy.get('@checkbox10-header').trigger('mouseover');
192195

193196
cy.get('.filter-prerequisites .ms-choice span').contains('15 of 500 selected');
194197
cy.get('.slick-custom-tooltip').should('be.visible');
@@ -199,15 +202,15 @@ describe('Example 32 - Regular & Custom Tooltips', () => {
199202

200203
it('should mouse over header title on 1st column with checkbox and NOT expect any tooltip to show since it is disabled on that column', () => {
201204
cy.get(`.slick-header-columns .slick-header-column:nth(0)`).as('checkbox-header');
202-
cy.get('@checkbox-header').trigger('mouseenter');
205+
cy.get('@checkbox-header').trigger('mouseover');
203206

204207
cy.get('.slick-custom-tooltip').should('not.exist');
205208
cy.get('@checkbox-header').trigger('mouseout');
206209
});
207210

208211
it('should mouse over header title on 2nd column with Title name and expect a tooltip to show rendered from an headerFormatter', () => {
209212
cy.get(`.slick-header-columns .slick-header-column:nth(1)`).as('checkbox0-header');
210-
cy.get('@checkbox0-header').trigger('mouseenter');
213+
cy.get('@checkbox0-header').trigger('mouseover');
211214

212215
cy.get('.slick-custom-tooltip').should('be.visible');
213216
cy.get('.slick-custom-tooltip').contains('Custom Tooltip - Header');
@@ -220,7 +223,7 @@ describe('Example 32 - Regular & Custom Tooltips', () => {
220223

221224
it('should mouse over header title on 2nd column with Finish name and NOT expect any tooltip to show since it is disabled on that column', () => {
222225
cy.get(`.slick-header-columns .slick-header-column:nth(8)`).as('finish-header');
223-
cy.get('@finish-header').trigger('mouseenter');
226+
cy.get('@finish-header').trigger('mouseover');
224227

225228
cy.get('.slick-custom-tooltip').should('not.exist');
226229
cy.get('@finish-header').trigger('mouseout');

test/cypress/e2e/example35.cy.ts

+35-30
Original file line numberDiff line numberDiff line change
@@ -22,38 +22,38 @@ describe('Example 35 - Row Based Editing', () => {
2222

2323
it('should only allow to toggle a single row into editmode on single mode', () => {
2424
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .action-btns--edit`).click();
25-
cy.get('.action-btns--edit').eq(1).click();
25+
cy.get('.action-btns--edit:nth(0)').click({ force: true });
2626

2727
cy.get('.slick-row.slick-rbe-editmode').should('have.length', 1);
2828
});
2929

3030
it('should allow to toggle a multiple rows into editmode on multiple mode', () => {
3131
cy.reload();
3232
cy.get('[data-test="single-multi-toggle"]').click();
33-
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .action-btns--edit`).click();
34-
cy.get('.action-btns--edit').eq(1).click();
35-
cy.get('.action-btns--edit').eq(2).click();
33+
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .action-btns--edit`).click({ force: true });
34+
cy.get('.action-btns--edit').eq(1).click({ force: true });
35+
cy.get('.action-btns--edit').eq(2).click({ force: true });
3636

3737
cy.get('.slick-row.slick-rbe-editmode').should('have.length', 3);
3838
});
3939

4040
it('should not display editor in rows not being in editmode', () => {
4141
cy.reload();
42-
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l2.r2`).click();
42+
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l2.r2`).click({ force: true });
4343

4444
cy.get('input').should('have.length', 0);
4545

46-
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .action-btns--edit`).click();
46+
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .action-btns--edit`).click({ force: true });
4747

48-
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l2.r2`).click();
48+
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l2.r2`).click({ force: true });
4949

5050
cy.get('input').should('have.length', 1);
5151
});
5252

5353
it('should highlight modified cells and maintain proper index on sorting', () => {
5454
cy.reload();
5555

56-
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .action-btns--edit`).click();
56+
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .action-btns--edit`).click({ force: true });
5757

5858
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l0.r0`).click().type('abc{enter}');
5959
cy.get('.slick-cell').first().should('have.class', 'slick-rbe-unsaved-cell');
@@ -66,7 +66,7 @@ describe('Example 35 - Row Based Editing', () => {
6666
it('should stay in editmode if saving failed', () => {
6767
cy.reload();
6868

69-
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .action-btns--edit`).click();
69+
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .action-btns--edit`).click({ force: true });
7070

7171
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l1.r1`).click().type('50{enter}');
7272
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l2.r2`).click().type('50');
@@ -83,12 +83,12 @@ describe('Example 35 - Row Based Editing', () => {
8383
it('should save changes on update button click', () => {
8484
cy.reload();
8585

86-
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .action-btns--edit`).click();
86+
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .action-btns--edit`).click({ force: true });
8787

8888
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l1.r1`).click().type('30{enter}');
8989
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l2.r2`).type('30');
9090

91-
cy.get('.action-btns--update').first().click();
91+
cy.get('.action-btns--update').first().click({ force: true });
9292

9393
cy.get('[data-test="fetch-result"]')
9494
.should('contain', 'success');
@@ -98,27 +98,27 @@ describe('Example 35 - Row Based Editing', () => {
9898
});
9999

100100
it('should cleanup status when starting a new edit mode', () => {
101-
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .action-btns--edit`).click();
101+
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .action-btns--edit`).click({ force: true });
102102

103103
cy.get('[data-test="fetch-result"]').should('be.empty');
104104

105-
cy.get('.action-btns--cancel').first().click();
105+
cy.get('.action-btns--cancel').first().click({ force: true });
106106
});
107107

108108
it('should revert changes on cancel click', () => {
109-
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .action-btns--edit`).click();
109+
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .action-btns--edit`).click({ force: true });
110110

111111
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l1.r1`).click().type('50{enter}');
112112
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l2.r2`).type('50{enter}');
113113

114-
cy.get('.action-btns--cancel').first().click();
114+
cy.get('.action-btns--cancel').first().click({ force: true });
115115

116116
cy.get('.slick-cell.l1.r1').first().should('contain', '30');
117117
cy.get('.slick-cell.l2.r2').first().should('contain', '30');
118118
});
119119

120120
it('should delete a row when clicking it', () => {
121-
cy.get('.action-btns--delete').first().click();
121+
cy.get('.action-btns--delete').first().click({ force: true });
122122

123123
cy.on('window:confirm', () => true);
124124

@@ -143,20 +143,25 @@ describe('Example 35 - Row Based Editing', () => {
143143
cy.get('[data-test="toggle-language"]').click();
144144
cy.get('[data-test="selected-locale"]').should('contain', 'fr.json');
145145

146-
// this seems to be a bug in Cypress, it doesn't seem to be able to click on the button
147-
// but at least it triggers a rerender, which makes it refetch the actual button instead of a cached one
148-
cy.get('.action-btns--update').first().click({ force: true });
146+
cy.get('.action-btns--edit').first().click({ force: true });
149147

150-
cy.get('.action-btns--update')
151-
.first()
152-
.should(($btn) => {
153-
expect($btn.attr('title')).to.equal('Mettre à jour la ligne actuelle');
154-
});
148+
cy.get('.action-btns--cancel').first().as('cancel-btn');
149+
cy.get('@cancel-btn').should(($btn) => {
150+
expect($btn.attr('title')).to.equal('Annuler la ligne actuelle');
151+
});
152+
cy.get('@cancel-btn').trigger('mouseover', { position: 'top' });
153+
cy.get('.slick-custom-tooltip').should('be.visible');
154+
cy.get('.slick-custom-tooltip .tooltip-body').contains('Annuler la ligne actuelle');
155155

156-
cy.get('.action-btns--cancel')
157-
.first()
158-
.should(($btn) => {
159-
expect($btn.attr('title')).to.equal('Annuler la ligne actuelle');
160-
});
156+
cy.get('.action-btns--update').first().as('update-btn');
157+
cy.get('@update-btn').should(($btn) => {
158+
expect($btn.attr('title')).to.equal('Mettre à jour la ligne actuelle');
159+
});
160+
161+
cy.get('@update-btn').trigger('mouseover', { position: 'top' });
162+
163+
cy.get('.slick-custom-tooltip').should('be.visible');
164+
cy.get('.slick-custom-tooltip .tooltip-body').contains('Mettre à jour la ligne actuelle');
165+
cy.get('@update-btn').first().click({ force: true });
161166
});
162-
});
167+
});

test/mockSlickEvent.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Handler, SlickEvent, SlickEventData } from '@slickgrid-universal/common';
1+
import type { Handler, SlickEvent, SlickEventData } from '@slickgrid-universal/common';
22
type MergeTypes<A, B> = { [key in keyof A]: key extends keyof B ? B[key] : A[key]; } & B;
33

44
// @ts-ignore

0 commit comments

Comments
 (0)