diff --git a/jest.config.js b/jest.config.js index 4986aa9a7..006e8c354 100644 --- a/jest.config.js +++ b/jest.config.js @@ -42,7 +42,7 @@ module.exports = { ], reporters: ['default', 'jest-junit'], setupFiles: ['/jest-pretest.ts'], - setupTestFrameworkScriptFile: '/setup-jest.ts', + setupFilesAfterEnv: ['/setup-jest.ts'], transform: { '^.+\\.(ts|html)$': '/node_modules/jest-preset-angular/preprocessor.js', }, diff --git a/package.json b/package.json index 40bb87ca2..b59161eeb 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "vinyl-paths": "^2.1.0" }, "devDependencies": { - "@angular-builders/jest": "^7.3.1", + "@angular-builders/jest": "^7.4.2", "@angular-devkit/build-angular": "~0.13.1", "@angular/animations": "^7.2.8", "@angular/cli": "^7.3.1", @@ -105,12 +105,12 @@ "@angular/router": "^7.2.8", "@ng-select/ng-select": "^2.15.3", "@types/flatpickr": "^3.1.2", - "@types/jest": "^23.3.9", + "@types/jest": "^24.0.12", "@types/jquery": "^3.3.29", "@types/moment": "^2.13.0", "@types/node": "^10.12.15", "@types/text-encoding-utf-8": "^1.0.1", - "babel-jest": "^23.6.0", + "babel-jest": "^24.8.0", "bootstrap": "3.4.1", "codecov": "^3.3.0", "codelyzer": "~4.5.0", @@ -124,15 +124,14 @@ "gulp-bump": "^3.1.3", "gulp-sass": "^4.0.2", "gulp-yuidoc": "^0.1.2", - "jest": "^23.6.0", - "jest-junit": "^6.3.0", + "jest": "^24.8.0", + "jest-junit": "^6.4.0", "jest-preset-angular": "^6.0.1", "mocha": "^5.2.0", "mochawesome": "^3.1.2", "mochawesome-merge": "^1.0.7", "mochawesome-report-generator": "^3.1.5", "ng-packagr": "^4.7.0", - "ngx-wallaby-jest": "^0.0.2", "node-sass": "^4.11.0", "npm-run-all": "^4.1.5", "postcss-cli": "^6.0.1", diff --git a/src/app/modules/angular-slickgrid/components/angular-slickgrid.component.ts b/src/app/modules/angular-slickgrid/components/angular-slickgrid.component.ts index dbeb45110..831574828 100644 --- a/src/app/modules/angular-slickgrid/components/angular-slickgrid.component.ts +++ b/src/app/modules/angular-slickgrid/components/angular-slickgrid.component.ts @@ -589,7 +589,7 @@ export class AngularSlickgridComponent implements AfterViewInit, OnDestroy, OnIn this.resizer.init(grid); } if (options.enableAutoResize) { - this.resizer.attachAutoResizeDataGrid(); + this.resizer.bindAutoResizeDataGrid(); if (grid && options.autoFitColumnsOnFirstLoad && options.enableAutoSizeColumns) { grid.autosizeColumns(); } diff --git a/src/app/modules/angular-slickgrid/global-grid-options.ts b/src/app/modules/angular-slickgrid/global-grid-options.ts index 478891ddd..b30c469f1 100644 --- a/src/app/modules/angular-slickgrid/global-grid-options.ts +++ b/src/app/modules/angular-slickgrid/global-grid-options.ts @@ -10,6 +10,7 @@ export const GlobalGridOptions: GridOption = { asyncEditorLoading: false, autoFitColumnsOnFirstLoad: true, autoResize: { + calculateAvailableSizeBy: 'window', bottomPadding: 20, minHeight: 180, minWidth: 300, diff --git a/src/app/modules/angular-slickgrid/models/autoResizeOption.interface.ts b/src/app/modules/angular-slickgrid/models/autoResizeOption.interface.ts index 235e14e4a..183b72ec1 100644 --- a/src/app/modules/angular-slickgrid/models/autoResizeOption.interface.ts +++ b/src/app/modules/angular-slickgrid/models/autoResizeOption.interface.ts @@ -1,4 +1,7 @@ export interface AutoResizeOption { + /** Defaults to 'window', which DOM element are we using to calculate the available size for the grid? */ + calculateAvailableSizeBy?: 'container' | 'window'; + /** bottom padding of the grid in pixels */ bottomPadding?: number; diff --git a/src/app/modules/angular-slickgrid/services/resizer.service.spec.ts b/src/app/modules/angular-slickgrid/services/resizer.service.spec.ts new file mode 100644 index 000000000..4c116daa5 --- /dev/null +++ b/src/app/modules/angular-slickgrid/services/resizer.service.spec.ts @@ -0,0 +1,190 @@ +import { GridOption } from './../models/gridOption.interface'; +import { ResizerService } from './resizer.service'; + +const DATAGRID_MIN_HEIGHT = 180; +const DATAGRID_MIN_WIDTH = 300; +const DATAGRID_BOTTOM_PADDING = 20; +const DATAGRID_PAGINATION_HEIGHT = 35; +const gridId = 'grid1'; +const gridUid = 'slickgrid_124343'; +const containerId = 'demo-container'; + +const gridOptionMock = { + gridId, + gridContainerId: `slickGridContainer-${gridId}`, + autoResize: { containerId }, + enableAutoResize: true +} as GridOption; + +const gridStub = { + autosizeColumns: jest.fn(), + getScrollbarDimensions: jest.fn(), + getOptions: () => gridOptionMock, + getUID: () => gridUid, +}; + +// define a
container to simulate the grid container +const template = + `
+
+
+
+
`; + +// --- NOTE --- +// with JSDOM our container or element height/width will always be 0 (JSDOM does not render like a real browser) +// we can only mock the window height/width, we cannot mock an element height/width +// I tried various hack but nothing worked, this one for example https://github.com/jsdom/jsdom/issues/135#issuecomment-68191941 + +describe('Resizer Service', () => { + let service: ResizerService; + + beforeEach(() => { + const div = document.createElement('div'); + div.innerHTML = template; + document.body.appendChild(div); + + service = new ResizerService(); + service.init(gridStub); + }); + + afterEach(() => { + service.dispose(); + }); + + it('should create the service', () => { + expect(service).toBeTruthy(); + }); + + it('should throw an error when there is no grid object defined', () => { + service = new ResizerService(); + service.init(null); + expect(() => service.resizeGrid()).toThrowError('Angular-Slickgrid resizer requires a valid Grid object and Grid Options defined'); + }); + + it('should throw an error when there is no grid options defined', () => { + service = new ResizerService(); + service.init({ getOptions: () => null }); + expect(() => service.resizeGrid()).toThrowError('Angular-Slickgrid resizer requires a valid Grid object and Grid Options defined'); + }); + + it('should trigger a grid resize when a window resize event occurs', () => { + // arrange + const newHeight = 500; + const previousHeight = window.innerHeight; + const subjectBeforeSpy = jest.spyOn(service.onGridBeforeResize, 'next'); + const subjectAfterSpy = jest.spyOn(service.onGridAfterResize, 'next'); + const gridSpy = jest.spyOn(gridStub, 'getOptions'); + const serviceCalculateSpy = jest.spyOn(service, 'calculateGridNewDimensions'); + const serviceResizeSpy = jest.spyOn(service, 'resizeGrid'); + + // act + // bind window resize & call a viewport resize + service.bindAutoResizeDataGrid(); + Object.defineProperty(window, 'innerHeight', { writable: true, configurable: true, value: newHeight }); + window.dispatchEvent(new Event('resize')); + const lastDimensions = service.getLastResizeDimensions(); + + // so the height dimension will work because calculateGridNewDimensions() uses "window.innerHeight" while the width it uses the container width + // for that reason, we can only verify the height, while the width should be set as the minimum width from the constant because 0 is override by the constant + const dimensionResult = { height: newHeight - DATAGRID_BOTTOM_PADDING, width: DATAGRID_MIN_WIDTH }; + + // assert + expect(gridSpy).toHaveBeenCalled(); + expect(serviceResizeSpy).toHaveBeenCalled(); + expect(window.innerHeight).not.toEqual(previousHeight); + expect(serviceCalculateSpy).toReturnWith(dimensionResult); + expect(lastDimensions).toEqual(dimensionResult); + expect(subjectBeforeSpy).toHaveBeenCalledWith(true); + expect(subjectAfterSpy).toHaveBeenCalledWith(dimensionResult); + }); + + it('should resize grid to a defined height and width when fixed dimensions are provided to the init method', () => { + const fixedHeight = 330; + const fixedWidth = 412; + const windowHeight = 840; + service.init(gridStub, { height: fixedHeight, width: fixedWidth }); + const serviceCalculateSpy = jest.spyOn(service, 'calculateGridNewDimensions'); + + Object.defineProperty(window, 'innerHeight', { writable: true, configurable: true, value: windowHeight }); + window.dispatchEvent(new Event('resize')); + service.calculateGridNewDimensions(gridOptionMock); + + // same comment as previous test, the height dimension will work because calculateGridNewDimensions() uses "window.innerHeight" + expect(serviceCalculateSpy).toReturnWith({ height: fixedHeight, width: fixedWidth }); + }); + + it('should calculate new dimensions when calculateGridNewDimensions is called', () => { + const newHeight = 440; + const serviceCalculateSpy = jest.spyOn(service, 'calculateGridNewDimensions'); + + Object.defineProperty(window, 'innerHeight', { writable: true, configurable: true, value: newHeight }); + window.dispatchEvent(new Event('resize')); + service.calculateGridNewDimensions(gridOptionMock); + + // same comment as previous test, the height dimension will work because calculateGridNewDimensions() uses "window.innerHeight" + expect(serviceCalculateSpy).toReturnWith({ height: (newHeight - DATAGRID_BOTTOM_PADDING), width: DATAGRID_MIN_WIDTH }); + }); + + it('should calculate new dimensions minus a padding when "bottomPadding" is defined in "autoResize" and calculateGridNewDimensions is called', () => { + const newHeight = 422; + const inputBottomPadding = 13; + const serviceCalculateSpy = jest.spyOn(service, 'calculateGridNewDimensions'); + + Object.defineProperty(window, 'innerHeight', { writable: true, configurable: true, value: newHeight }); + window.dispatchEvent(new Event('resize')); + service.calculateGridNewDimensions({ ...gridOptionMock, autoResize: { bottomPadding: inputBottomPadding } }); + + // same comment as previous test, the height dimension will work because calculateGridNewDimensions() uses "window.innerHeight" + expect(serviceCalculateSpy).toReturnWith({ height: (newHeight - inputBottomPadding), width: DATAGRID_MIN_WIDTH }); + }); + + it('should calculate new dimensions minus the pagination height when pagination is enabled and resizeGrid is called with a delay', (done) => { + const newHeight = 440; + const newOptions = { ...gridOptionMock, enablePagination: true }; + const newGridStub = { ...gridStub, getOptions: () => newOptions }; + service.init(newGridStub); + const subjectAfterSpy = jest.spyOn(service.onGridAfterResize, 'next'); + const serviceCalculateSpy = jest.spyOn(service, 'calculateGridNewDimensions'); + + Object.defineProperty(window, 'innerHeight', { writable: true, configurable: true, value: newHeight }); + window.dispatchEvent(new Event('resize')); + service.resizeGrid(2).then((newGridDimensions) => { + // same comment as previous test, the height dimension will work because calculateGridNewDimensions() uses "window.innerHeight" + const calculatedDimensions = { height: (newHeight - DATAGRID_BOTTOM_PADDING - DATAGRID_PAGINATION_HEIGHT), width: DATAGRID_MIN_WIDTH }; + expect(serviceCalculateSpy).toReturnWith(calculatedDimensions); + expect(newGridDimensions).toEqual({ ...calculatedDimensions, heightWithPagination: (calculatedDimensions.height + DATAGRID_PAGINATION_HEIGHT) }); + expect(subjectAfterSpy).toHaveBeenCalledWith(newGridDimensions); + done(); + }); + }); + + it('should calculate new dimensions by using the container dimensions (instead of the window dimensions) when calculateAvailableSizeBy is set to container', () => { + const newHeight = 500; + const spy = jest.spyOn(service, 'calculateGridNewDimensions'); + + Object.defineProperty(window, 'innerHeight', { writable: true, configurable: true, value: newHeight }); + window.dispatchEvent(new Event('resize')); + service.calculateGridNewDimensions({ ...gridOptionMock, autoResize: { calculateAvailableSizeBy: 'container' } }); + + // with JSDOM the height is always 0 so we can assume that the height will be the minimum height (without the padding) + expect(spy).toReturnWith({ height: DATAGRID_MIN_HEIGHT, width: DATAGRID_MIN_WIDTH }); + }); + + it('should call the autosizeColumns from the core lib when "enableAutoSizeColumns" is set and the new width is wider than prior width', () => { + const newHeight = 500; + const newOptions = { ...gridOptionMock, enableAutoSizeColumns: true }; + const newGridStub = { ...gridStub, getOptions: () => newOptions }; + service.init(newGridStub); + const serviceCalculateSpy = jest.spyOn(service, 'calculateGridNewDimensions'); + const gridAutosizeSpy = jest.spyOn(newGridStub, 'autosizeColumns'); + + service.bindAutoResizeDataGrid(); + Object.defineProperty(window, 'innerHeight', { writable: true, configurable: true, value: newHeight }); + window.dispatchEvent(new Event('resize')); + + // with JSDOM the height is always 0 so we can assume that the height will be the minimum height (without the padding) + expect(serviceCalculateSpy).toHaveBeenCalled(); + expect(gridAutosizeSpy).toHaveBeenCalled(); + }); +}); diff --git a/src/app/modules/angular-slickgrid/services/resizer.service.ts b/src/app/modules/angular-slickgrid/services/resizer.service.ts index 0483b7ce5..85e5facca 100644 --- a/src/app/modules/angular-slickgrid/services/resizer.service.ts +++ b/src/app/modules/angular-slickgrid/services/resizer.service.ts @@ -23,6 +23,7 @@ export class ResizerService { private _grid: any; private _lastDimensions: GridDimension; private _timer: any; + onGridAfterResize = new Subject(); onGridBeforeResize = new Subject(); /** Getter for the Grid Options pulled through the Grid Object */ @@ -31,7 +32,7 @@ export class ResizerService { } private get _gridUid(): string { - return (this._grid && this._grid.getUID) ? this._grid.getUID() : this._gridOptions.gridId; + return (this._grid && this._grid.getUID) ? this._grid.getUID() : this._gridOptions && this._gridOptions.gridId; } init(grid: any, fixedDimensions?: GridDimension): void { @@ -45,7 +46,7 @@ export class ResizerService { /** Attach an auto resize trigger on the datagrid, if that is enable then it will resize itself to the available space * Options: we could also provide a % factor to resize on each height/width independently */ - attachAutoResizeDataGrid(newSizes?: GridDimension) { + bindAutoResizeDataGrid(newSizes?: GridDimension) { // if we can't find the grid to resize, return without attaching anything const gridDomElm = $(`#${this._gridOptions && this._gridOptions.gridId ? this._gridOptions.gridId : 'grid1'}`); if (gridDomElm === undefined || gridDomElm.offset() === undefined) { @@ -70,10 +71,9 @@ export class ResizerService { */ calculateGridNewDimensions(gridOptions: GridOption): GridDimension | null { const gridDomElm = $(`#${gridOptions.gridId}`); - const autoResizeOptions = gridOptions && gridOptions.autoResize; + const autoResizeOptions = gridOptions && gridOptions.autoResize || {}; const containerElm = (autoResizeOptions && autoResizeOptions.containerId) ? $(`#${autoResizeOptions.containerId}`) : $(`#${gridOptions.gridContainerId}`); - const windowElm = $(window); - if (windowElm === undefined || containerElm === undefined || gridDomElm === undefined) { + if (!window || containerElm === undefined || gridDomElm === undefined) { return null; } @@ -84,9 +84,20 @@ export class ResizerService { bottomPadding += DATAGRID_PAGINATION_HEIGHT; } - const gridHeight = windowElm.height() || 0; - const coordOffsetTop = gridDomElm.offset(); - const gridOffsetTop = (coordOffsetTop !== undefined) ? coordOffsetTop.top : 0; + let gridHeight = 0; + let gridOffsetTop = 0; + + // which DOM element are we using to calculate the available size for the grid? + if (autoResizeOptions.calculateAvailableSizeBy === 'container') { + // uses the container's height to calculate grid height without any top offset + gridHeight = containerElm.height() || 0; + } else { + // uses the browser's window height with its top offset to calculate grid height + gridHeight = window.innerHeight || 0; + const coordOffsetTop = gridDomElm.offset(); + gridOffsetTop = (coordOffsetTop !== undefined) ? coordOffsetTop.top : 0; + } + const availableHeight = gridHeight - gridOffsetTop - bottomPadding; const availableWidth = containerElm.width() || 0; const maxHeight = autoResizeOptions && autoResizeOptions.maxHeight || undefined; @@ -158,7 +169,7 @@ export class ResizerService { if (!this._grid || !this._gridOptions) { throw new Error(` Angular-Slickgrid resizer requires a valid Grid object and Grid Options defined. - You can fix this by setting your gridOption to use "enableAutoResize" or create an instance of the ResizerService by calling attachAutoResizeDataGrid()`); + You can fix this by setting your gridOption to use "enableAutoResize" or create an instance of the ResizerService by calling bindAutoResizeDataGrid()`); } return new Promise((resolve) => { @@ -167,17 +178,19 @@ export class ResizerService { if (delay > 0) { clearTimeout(this._timer); - this._timer = setTimeout(() => { - this.resizeGridWithDimensions(newSizes); - resolve(this._lastDimensions); - }, delay); + this._timer = setTimeout(() => resolve(this.resizeGridCallback(newSizes)), delay); } else { - this.resizeGridWithDimensions(newSizes); - resolve(this._lastDimensions); + resolve(this.resizeGridCallback(newSizes)); } }); } + resizeGridCallback(newSizes: GridDimension) { + const lastDimensions = this.resizeGridWithDimensions(newSizes); + this.onGridAfterResize.next(lastDimensions); + return lastDimensions; + } + resizeGridWithDimensions(newSizes?: GridDimension): GridDimension { // calculate the available sizes with minimum height defined as a constant const availableDimensions = this.calculateGridNewDimensions(this._gridOptions);