import type { Context } from '../context/grid-context' import { createGrid } from '../hooks' import { ensureCellIsVisible, ensureRowIsVisible } from './ensure-visible' describe('ensure-visible', () => { describe('ensureCellIsVisible', () => { let cell: HTMLTableCellElement, scroller: HTMLDivElement, grid: Context function configureCell({ colIndex, bounds, }: { colIndex: number bounds: Partial }) { cell.setAttribute('aria-colindex', `${colIndex + 1}`) jest.spyOn(cell, 'getBoundingClientRect').mockReturnValue( bounds as DOMRect ) } function configureScroller({ bounds, offsetWidth, offsetHeight, clientWidth, clientHeight, }: { bounds: Partial offsetWidth: number offsetHeight: number clientWidth: number clientHeight: number }) { jest.spyOn(scroller, 'offsetWidth', 'get').mockReturnValue( offsetWidth ) jest.spyOn(scroller, 'offsetHeight', 'get').mockReturnValue( offsetHeight ) jest.spyOn(scroller, 'clientWidth', 'get').mockReturnValue( clientWidth ) jest.spyOn(scroller, 'clientHeight', 'get').mockReturnValue( clientHeight ) jest.spyOn(scroller, 'getBoundingClientRect').mockReturnValue( bounds as DOMRect ) } beforeEach(() => { cell = document.createElement('td') scroller = document.createElement('div') grid = createGrid() jest.spyOn( grid.selectors, 'selectStickyColumnDetails' ).mockReturnValue({ indexes: new Set(), left: { columns: [], width: 0 }, right: { columns: [], width: 0 }, }) jest.spyOn(grid.selectors, 'selectCurrentFocus').mockReturnValue({ area: 'body', rowId: '1', columnId: '1', subFocus: 'first', }) }) describe('when there is no available width', () => { beforeEach(() => { jest.mocked( grid.selectors.selectStickyColumnDetails ).mockReturnValue({ indexes: new Set(), left: { columns: [], width: 50 }, right: { columns: [], width: 30 }, }) configureCell({ colIndex: 0, bounds: { top: 300, left: 500, bottom: 336, height: 36, right: 650, width: 150, }, }) configureScroller({ bounds: { top: 0, left: 0, right: 100, bottom: 100 }, offsetWidth: 100, clientWidth: 80, offsetHeight: 100, clientHeight: 80, }) ensureCellIsVisible(cell, scroller, grid) }) it('should scroll vertically', () => { expect(scroller.scrollTop).toBe(256) }) it('should not scroll horizontally', () => { expect(scroller.scrollLeft).toBe(0) }) }) describe('when there is no available height', () => { beforeEach(() => { jest.mocked( grid.selectors.selectStickyColumnDetails ).mockReturnValue({ indexes: new Set(), left: { columns: [], width: 0 }, right: { columns: [], width: 0 }, }) configureCell({ colIndex: 0, bounds: { top: 300, left: 500, bottom: 336, height: 36, right: 650, width: 150, }, }) configureScroller({ bounds: { top: 0, left: 0, right: 100, bottom: 56 }, offsetWidth: 100, clientWidth: 80, offsetHeight: 100, clientHeight: 80, }) ensureCellIsVisible(cell, scroller, grid) }) it('should not scroll vertically', () => { expect(scroller.scrollTop).toBe(0) }) it('should scroll horizontally', () => { expect(scroller.scrollLeft).toBe(500) }) }) describe('when there is enough available height and width', () => { describe('when column is sticky', () => { beforeEach(() => { jest.mocked( grid.selectors.selectStickyColumnDetails ).mockReturnValue({ indexes: new Set([0]), left: { columns: [], width: 36 }, right: { columns: [], width: 0 }, }) configureCell({ colIndex: 0, bounds: { top: 300, left: 500, bottom: 336, height: 36, right: 650, width: 150, }, }) configureScroller({ bounds: { top: 0, left: 0, right: 100, bottom: 100 }, offsetWidth: 100, clientWidth: 80, offsetHeight: 100, clientHeight: 80, }) ensureCellIsVisible(cell, scroller, grid) }) it('should scroll vertically', () => { expect(scroller.scrollTop).toBe(256) }) it('should not scroll horizontally', () => { expect(scroller.scrollLeft).toBe(0) }) }) describe('when column is not sticky', () => { beforeEach(() => { jest.mocked( grid.selectors.selectStickyColumnDetails ).mockReturnValue({ indexes: new Set([0, 4]), left: { columns: [], width: 100 }, right: { columns: [], width: 36 }, }) configureScroller({ bounds: { top: 0, left: 0, right: 300, bottom: 100 }, offsetWidth: 300, clientWidth: 280, offsetHeight: 100, clientHeight: 80, }) }) describe('if the cell is on screen', () => { beforeEach(() => { configureCell({ colIndex: 1, bounds: { top: 36, left: 100, bottom: 72, height: 36, right: 250, width: 150, }, }) ensureCellIsVisible(cell, scroller, grid) }) it('should not scroll at all', () => { expect(scroller.scrollTop).toBe(0) expect(scroller.scrollLeft).toBe(0) }) }) describe('if the cell is offscreen to the top', () => { beforeEach(() => { scroller.scrollTop = 300 configureCell({ colIndex: 1, bounds: { top: -200, left: 100, bottom: -164, height: 36, right: 250, width: 150, }, }) ensureCellIsVisible(cell, scroller, grid) }) it('should scroll the top of the cell into view', () => { expect(scroller.scrollTop).toBe(64) }) }) describe('if the cell is offscreen to the bottom', () => { beforeEach(() => { configureCell({ colIndex: 1, bounds: { top: 200, left: 100, bottom: 236, height: 36, right: 150, width: 50, }, }) ensureCellIsVisible(cell, scroller, grid) }) it('should scroll the bottom of the cell into view', () => { expect(scroller.scrollTop).toBe(156) }) }) describe('if the cell is offscreen to the left', () => { beforeEach(() => { scroller.scrollLeft = 500 configureCell({ colIndex: 1, bounds: { top: 36, left: 0, bottom: 72, height: 36, right: 150, width: 150, }, }) ensureCellIsVisible(cell, scroller, grid) }) it('should scroll the left of the cell into view', () => { expect(scroller.scrollLeft).toBe(400) }) }) describe('if the cell is offscreen to the right', () => { describe('when the cell width can fit into view', () => { beforeEach(() => { configureCell({ colIndex: 1, bounds: { top: 36, left: 300, bottom: 72, height: 36, right: 350, width: 50, }, }) ensureCellIsVisible(cell, scroller, grid) }) it('should scroll the right of the cell into view', () => { expect(scroller.scrollLeft).toBe(106) }) }) describe('when the cell width cannot fit into view', () => { beforeEach(() => { configureCell({ colIndex: 1, bounds: { top: 36, left: 300, bottom: 72, height: 36, right: 550, width: 250, }, }) ensureCellIsVisible(cell, scroller, grid) }) it('should scroll the left of the cell into view', () => { expect(scroller.scrollLeft).toBe(200) }) }) }) }) }) }) describe('ensureRowIsVisible', () => { let row: HTMLTableRowElement, scroller: HTMLDivElement, grid: Context function configureRow({ bounds }: { bounds: Partial }) { jest.spyOn(row, 'getBoundingClientRect').mockReturnValue( bounds as DOMRect ) } function configureScroller({ bounds, offsetWidth, offsetHeight, clientWidth, clientHeight, }: { bounds: Partial offsetWidth: number offsetHeight: number clientWidth: number clientHeight: number }) { jest.spyOn(scroller, 'offsetWidth', 'get').mockReturnValue( offsetWidth ) jest.spyOn(scroller, 'offsetHeight', 'get').mockReturnValue( offsetHeight ) jest.spyOn(scroller, 'clientWidth', 'get').mockReturnValue( clientWidth ) jest.spyOn(scroller, 'clientHeight', 'get').mockReturnValue( clientHeight ) jest.spyOn(scroller, 'getBoundingClientRect').mockReturnValue( bounds as DOMRect ) } beforeEach(() => { row = document.createElement('tr') scroller = document.createElement('div') grid = createGrid() jest.spyOn(grid.selectors, 'selectCurrentFocus').mockReturnValue({ area: 'body', rowId: '1', columnId: '1', subFocus: 'first', }) }) describe('when there is no available height', () => { beforeEach(() => { configureRow({ bounds: { top: 300, left: 500, bottom: 336, height: 36, right: 650, width: 150, }, }) configureScroller({ bounds: { top: 0, left: 0, right: 100, bottom: 56 }, offsetWidth: 100, clientWidth: 80, offsetHeight: 100, clientHeight: 80, }) ensureRowIsVisible(row, scroller, grid) }) it('should not scroll vertically', () => { expect(scroller.scrollTop).toBe(0) }) }) describe('when there is enough available height', () => { beforeEach(() => { configureScroller({ bounds: { top: 0, left: 0, right: 300, bottom: 100 }, offsetWidth: 300, clientWidth: 280, offsetHeight: 100, clientHeight: 80, }) }) describe('if the row is on screen', () => { beforeEach(() => { configureRow({ bounds: { top: 36, left: 100, bottom: 72, height: 36, right: 250, width: 150, }, }) ensureRowIsVisible(row, scroller, grid) }) it('should not scroll at all', () => { expect(scroller.scrollTop).toBe(0) }) }) describe('if the row is offscreen to the top', () => { beforeEach(() => { scroller.scrollTop = 300 configureRow({ bounds: { top: -200, left: 100, bottom: -164, height: 36, right: 250, width: 150, }, }) ensureRowIsVisible(row, scroller, grid) }) it('should scroll the top of the cell into view', () => { expect(scroller.scrollTop).toBe(64) }) }) describe('if the row is offscreen to the bottom', () => { beforeEach(() => { configureRow({ bounds: { top: 200, left: 100, bottom: 236, height: 36, right: 150, width: 50, }, }) ensureRowIsVisible(row, scroller, grid) }) it('should scroll the bottom of the cell into view', () => { expect(scroller.scrollTop).toBe(156) }) }) }) }) })