diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index 4fb9907cc..985beab78 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -79,7 +79,7 @@ jobs: wait-on: 'http://localhost:8888' config-file: test/cypress.config.ts browser: chrome - record: false + record: true env: # pass the Dashboard record key as an environment variable CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} diff --git a/packages/common/src/core/__tests__/slickCore.spec.ts b/packages/common/src/core/__tests__/slickCore.spec.ts index ef02b237e..08ab3fc9c 100644 --- a/packages/common/src/core/__tests__/slickCore.spec.ts +++ b/packages/common/src/core/__tests__/slickCore.spec.ts @@ -39,10 +39,12 @@ describe('SlickCore file', () => { it('should call isDefaultPrevented() and expect truthy when event propagation is stopped by calling preventDefault()', () => { const ed = new SlickEventData(); + expect(ed.defaultPrevented).toBeFalsy(); expect(ed.isDefaultPrevented()).toBeFalsy(); ed.preventDefault(); + expect(ed.defaultPrevented).toBeTruthy(); expect(ed.isDefaultPrevented()).toBeTruthy(); }); @@ -59,6 +61,7 @@ describe('SlickCore file', () => { const evtSpy = jest.spyOn(evt, 'preventDefault'); const ed = new SlickEventData(evt); + expect(ed.defaultPrevented).toBeFalsy(); expect(ed.isDefaultPrevented()).toBeFalsy(); ed.preventDefault(); @@ -364,7 +367,7 @@ describe('SlickCore file', () => { const elock = new SlickEditorLock(); elock.activate(ec); - expect(() => elock.activate(ec2)).toThrow(`SlickEditorLock.activate: an editController is still active, can't activate another editController`) + expect(() => elock.activate(ec2)).toThrow(`SlickEditorLock.activate: an editController is still active, can't activate another editController`); }); it('should throw when trying to call activate() with an EditController that forgot to implement commitCurrentEdit() method', () => { @@ -372,7 +375,7 @@ describe('SlickCore file', () => { const ec = { cancelCurrentEdit: cancelSpy, } as any; const elock = new SlickEditorLock(); - expect(() => elock.activate(ec)).toThrow(`SlickEditorLock.activate: editController must implement .commitCurrentEdit()`) + expect(() => elock.activate(ec)).toThrow(`SlickEditorLock.activate: editController must implement .commitCurrentEdit()`); }); it('should throw when trying to call activate() with an EditController that forgot to implement cancelCurrentEdit() method', () => { @@ -380,7 +383,7 @@ describe('SlickCore file', () => { const ec = { commitCurrentEdit: commitSpy, } as any; const elock = new SlickEditorLock(); - expect(() => elock.activate(ec)).toThrow(`SlickEditorLock.activate: editController must implement .cancelCurrentEdit()`) + expect(() => elock.activate(ec)).toThrow(`SlickEditorLock.activate: editController must implement .cancelCurrentEdit()`); }); it('should deactivate an EditController and expect isActive() to be falsy', () => { @@ -408,7 +411,7 @@ describe('SlickCore file', () => { const elock = new SlickEditorLock(); elock.activate(ec); - expect(() => elock.deactivate(ec2)).toThrow(`SlickEditorLock.deactivate: specified editController is not the currently active one`) + expect(() => elock.deactivate(ec2)).toThrow(`SlickEditorLock.deactivate: specified editController is not the currently active one`); }); it('should expect active EditController.commitCurrentEdit() being called when calling commitCurrentEdit() after it was activated', () => { diff --git a/packages/common/src/core/slickCore.ts b/packages/common/src/core/slickCore.ts index 81cca5a2a..c85edc2dc 100644 --- a/packages/common/src/core/slickCore.ts +++ b/packages/common/src/core/slickCore.ts @@ -10,7 +10,7 @@ import { MergeTypes } from '../enums/index'; import type { CSSStyleDeclarationWritable, EditController } from '../interfaces'; -export type Handler = (e: any, args: ArgType) => void; +export type Handler = (e: SlickEventData, args: ArgType) => void; export interface BasePubSub { publish(_eventName: string | any, _data?: ArgType): any; @@ -30,7 +30,32 @@ export class SlickEventData { protected _isDefaultPrevented = false; protected nativeEvent?: Event | null; protected returnValue: any = undefined; - protected target?: EventTarget | null; + protected _eventTarget?: EventTarget | null; + + // public props that can be optionally pulled from the provided Event in constructor + // they are all optional props because it really depends on the type of Event provided (KeyboardEvent, MouseEvent, ...) + readonly altKey?: boolean; + readonly ctrlKey?: boolean; + readonly metaKey?: boolean; + readonly shiftKey?: boolean; + readonly key?: string; + readonly keyCode?: number; + readonly clientX?: number; + readonly clientY?: number; + readonly offsetX?: number; + readonly offsetY?: number; + readonly pageX?: number; + readonly pageY?: number; + readonly bubbles?: boolean; + readonly target?: HTMLElement; + readonly type?: string; + readonly which?: number; + readonly x?: number; + readonly y?: number; + + get defaultPrevented() { + return this._isDefaultPrevented; + } constructor(protected event?: Event | null, protected args?: ArgType) { this.nativeEvent = event; @@ -42,10 +67,10 @@ export class SlickEventData { [ 'altKey', 'ctrlKey', 'metaKey', 'shiftKey', 'key', 'keyCode', 'clientX', 'clientY', 'offsetX', 'offsetY', 'pageX', 'pageY', - 'bubbles', 'type', 'which', 'x', 'y' + 'bubbles', 'target', 'type', 'which', 'x', 'y' ].forEach(key => (this as any)[key] = event[key as keyof Event]); } - this.target = this.nativeEvent ? this.nativeEvent.target : undefined; + this._eventTarget = this.nativeEvent ? this.nativeEvent.target : undefined; } /** @@ -186,13 +211,13 @@ export class SlickEvent { scope = scope || this; for (let i = 0; i < this._handlers.length && !(sed.isPropagationStopped() || sed.isImmediatePropagationStopped()); i++) { - const returnValue = this._handlers[i].call(scope, sed as SlickEvent | SlickEventData, args); + const returnValue = this._handlers[i].call(scope, sed, args); sed.addReturnValue(returnValue); } // user can optionally add a global PubSub Service which makes it easy to publish/subscribe to events if (typeof this._pubSubService?.publish === 'function' && this.eventName) { - const ret = this._pubSubService.publish<{ args: ArgType; eventData?: Event | SlickEventData; nativeEvent?: Event; }>(this.eventName, { args, eventData: sed }); + const ret = this._pubSubService.publish<{ args: ArgType; eventData?: SlickEventData; nativeEvent?: Event; }>(this.eventName, { args, eventData: sed }); sed.addReturnValue(ret); } return sed; diff --git a/packages/common/src/core/slickDataview.ts b/packages/common/src/core/slickDataview.ts index 721e892c9..0dc5c4046 100644 --- a/packages/common/src/core/slickDataview.ts +++ b/packages/common/src/core/slickDataview.ts @@ -71,10 +71,10 @@ export class SlickDataView implements CustomD protected items: TData[] = []; // data by index protected rows: TData[] = []; // data by row protected idxById = new Map(); // indexes by id - protected rowsById: { [id: DataIdType]: number } | undefined = undefined; // rows by id; lazy-calculated + protected rowsById: { [id: DataIdType]: number; } | undefined = undefined; // rows by id; lazy-calculated protected filter: FilterFn | null = null; // filter function protected filterCSPSafe: FilterFn | null = null; // filter function - protected updated: ({ [id: DataIdType]: boolean }) | null = null; // updated item ids + protected updated: ({ [id: DataIdType]: boolean; }) | null = null; // updated item ids protected suspend = false; // suspends the recalculation protected isBulkSuspend = false; // delays protectedious operations like the // index update and delete to efficient @@ -107,7 +107,7 @@ export class SlickDataView implements CustomD displayTotalsRow: true, lazyTotalsCalculation: false }; - protected groupingInfos: Array = []; + protected groupingInfos: Array = []; protected groups: SlickGroup[] = []; protected toggledGroupsByLevel: any[] = []; protected groupingDelimiter = ':|:'; @@ -752,7 +752,7 @@ export class SlickDataView implements CustomD // overrides for totals rows if ((item as SlickGroupTotals).__groupTotals) { - return this._options.groupItemMetadataProvider!.getTotalsRowMetadata(item as { group: GroupingFormatterItem }); + return this._options.groupItemMetadataProvider!.getTotalsRowMetadata(item as { group: GroupingFormatterItem; }); } return null; @@ -1409,7 +1409,7 @@ export class SlickDataView implements CustomD } }; - grid.onSelectedRowsChanged.subscribe((_e: Event, args: { rows: number[]; }) => { + grid.onSelectedRowsChanged.subscribe((_e: SlickEventData, args: { rows: number[]; }) => { if (!inHandler) { const newSelectedRowIds = this.mapRowsToIds(args.rows); const selectedRowsChangedArgs = { diff --git a/packages/common/src/core/slickGrid.ts b/packages/common/src/core/slickGrid.ts index 25e910526..4bf4876ca 100644 --- a/packages/common/src/core/slickGrid.ts +++ b/packages/common/src/core/slickGrid.ts @@ -87,7 +87,6 @@ import type { PagingInfo, SingleColumnSort, SlickPlugin, - SlickGridEventData, } from '../interfaces'; import type { SlickDataView } from './slickDataview'; @@ -128,13 +127,13 @@ export class SlickGrid = Column, O e // Events onActiveCellChanged: SlickEvent; - onActiveCellPositionChanged: SlickEvent; + onActiveCellPositionChanged: SlickEvent<{ grid: SlickGrid; }>; onAddNewRow: SlickEvent; onAutosizeColumns: SlickEvent; onBeforeAppendCell: SlickEvent; onBeforeCellEditorDestroy: SlickEvent; onBeforeColumnsResize: SlickEvent; - onBeforeDestroy: SlickEvent; + onBeforeDestroy: SlickEvent<{ grid: SlickGrid; }>; onBeforeEditCell: SlickEvent; onBeforeFooterRowCellDestroy: SlickEvent; onBeforeHeaderCellDestroy: SlickEvent; @@ -150,7 +149,7 @@ export class SlickGrid = Column, O e onColumnsResized: SlickEvent; onColumnsResizeDblClick: SlickEvent; onCompositeEditorChange: SlickEvent; - onContextMenu: SlickEvent; + onContextMenu: SlickEvent<{ grid: SlickGrid; }>; onDrag: SlickEvent; onDblClick: SlickEvent; onDragInit: SlickEvent; @@ -177,7 +176,7 @@ export class SlickGrid = Column, O e onActivateChangedOptions: SlickEvent; onSort: SlickEvent; onValidationError: SlickEvent; - onViewportChanged: SlickEvent; + onViewportChanged: SlickEvent<{ grid: SlickGrid; }>; // --- // protected variables @@ -485,13 +484,13 @@ export class SlickGrid = Column, O e this._pubSubService = externalPubSub; this.onActiveCellChanged = new SlickEvent('onActiveCellChanged', externalPubSub); - this.onActiveCellPositionChanged = new SlickEvent('onActiveCellPositionChanged', externalPubSub); + this.onActiveCellPositionChanged = new SlickEvent<{ grid: SlickGrid; }>('onActiveCellPositionChanged', externalPubSub); this.onAddNewRow = new SlickEvent('onAddNewRow', externalPubSub); this.onAutosizeColumns = new SlickEvent('onAutosizeColumns', externalPubSub); this.onBeforeAppendCell = new SlickEvent('onBeforeAppendCell', externalPubSub); this.onBeforeCellEditorDestroy = new SlickEvent('onBeforeCellEditorDestroy', externalPubSub); this.onBeforeColumnsResize = new SlickEvent('onBeforeColumnsResize', externalPubSub); - this.onBeforeDestroy = new SlickEvent('onBeforeDestroy', externalPubSub); + this.onBeforeDestroy = new SlickEvent<{ grid: SlickGrid; }>('onBeforeDestroy', externalPubSub); this.onBeforeEditCell = new SlickEvent('onBeforeEditCell', externalPubSub); this.onBeforeFooterRowCellDestroy = new SlickEvent('onBeforeFooterRowCellDestroy', externalPubSub); this.onBeforeHeaderCellDestroy = new SlickEvent('onBeforeHeaderCellDestroy', externalPubSub); @@ -507,7 +506,7 @@ export class SlickGrid = Column, O e this.onColumnsResized = new SlickEvent('onColumnsResized', externalPubSub); this.onColumnsResizeDblClick = new SlickEvent('onColumnsResizeDblClick', externalPubSub); this.onCompositeEditorChange = new SlickEvent('onCompositeEditorChange', externalPubSub); - this.onContextMenu = new SlickEvent('onContextMenu', externalPubSub); + this.onContextMenu = new SlickEvent<{ grid: SlickGrid; }>('onContextMenu', externalPubSub); this.onDrag = new SlickEvent('onDrag', externalPubSub); this.onDblClick = new SlickEvent('onDblClick', externalPubSub); this.onDragInit = new SlickEvent('onDragInit', externalPubSub); @@ -534,7 +533,7 @@ export class SlickGrid = Column, O e this.onActivateChangedOptions = new SlickEvent('onActivateChangedOptions', externalPubSub); this.onSort = new SlickEvent('onSort', externalPubSub); this.onValidationError = new SlickEvent('onValidationError', externalPubSub); - this.onViewportChanged = new SlickEvent('onViewportChanged', externalPubSub); + this.onViewportChanged = new SlickEvent<{ grid: SlickGrid; }>('onViewportChanged', externalPubSub); this.initialize(options); } diff --git a/packages/common/src/extensions/__tests__/slickCellMenu.plugin.spec.ts b/packages/common/src/extensions/__tests__/slickCellMenu.plugin.spec.ts index 9eb71cede..70577b52e 100644 --- a/packages/common/src/extensions/__tests__/slickCellMenu.plugin.spec.ts +++ b/packages/common/src/extensions/__tests__/slickCellMenu.plugin.spec.ts @@ -296,7 +296,7 @@ describe('CellMenu Plugin', () => { expect(commandListElm.querySelectorAll('.slick-menu-item').length).toBe(7); expect(document.body.querySelector('button.close')!.ariaLabel).toBe('Close'); // JSDOM doesn't support ariaLabel, but we can test attribute this way expect(removeExtraSpaces(document.body.innerHTML)).toBe(removeExtraSpaces( - `