UNPKG

panelsnap

Version:

A JavaScript plugin that provides snapping functionality to a set of panels within your interface.

414 lines (355 loc) 12.8 kB
import { getScrollingElement, getTargetScrollOffset, getElementsInContainerViewport, elementFillsContainer, passiveIsSupported, } from './utilities'; const SCREEN_WIDTH = 800; const SCREEN_HEIGHT = 600; Object.defineProperty(document.documentElement, 'clientWidth', { value: SCREEN_WIDTH }); Object.defineProperty(document.documentElement, 'clientHeight', { value: SCREEN_HEIGHT }); describe('getScrolllingElement', () => { test('returns same element when container is not body', () => { const container = document.createElement('div'); expect(getScrollingElement(container)).toBe(container); }); test('returns scrollingElement when container is body on modern browser', () => { const container = document.body; const scrollContainer = document.createElement('div'); document.scrollingElement = scrollContainer; expect(getScrollingElement(container)).toBe(scrollContainer); // TODO: Jest should clean this up... delete document.scrollingElement; }); test('returns same element when container is body on WebKit browser', () => { Object.defineProperty(window.navigator, 'userAgent', { value: 'WebKit', configurable: true }); const container = document.body; expect(getScrollingElement(container)).toBe(container); }); test('returns document element when container is body on legacy browser', () => { Object.defineProperty(window.navigator, 'userAgent', { value: 'legacy browser', configurable: true }); const container = document.body; const scrollContainer = document.documentElement; expect(getScrollingElement(container)).toBe(scrollContainer); }); }); describe('getTargetScrollOffset', () => { function testElements(options) { const { scrollOffset, containerDimensions, targetDimensions, expectedResult, toBottom, toRight, } = options; const container = document.createElement('div'); container.scrollTop = scrollOffset.top; container.scrollLeft = scrollOffset.left; container.getBoundingClientRect = () => ({ ...containerDimensions, height: containerDimensions.bottom - containerDimensions.top, width: containerDimensions.right - containerDimensions.left, }); const target = document.createElement('div'); target.getBoundingClientRect = () => ({ ...targetDimensions, height: targetDimensions.bottom - targetDimensions.top, width: targetDimensions.right - targetDimensions.left, }); expect(getTargetScrollOffset(container, target, !!toBottom, !!toRight)).toEqual(expectedResult); } test('calculates scrollOffset for target element', () => { testElements({ scrollOffset: { top: 0, left: 0 }, containerDimensions: { top: 0, left: 0 }, targetDimensions: { top: 0, left: 0 }, expectedResult: { top: 0, left: 0 }, }); testElements({ scrollOffset: { top: 100, left: 100 }, containerDimensions: { top: 0, left: 0 }, targetDimensions: { top: -100, left: -100 }, expectedResult: { top: 0, left: 0 }, }); testElements({ scrollOffset: { top: 100, left: 100 }, containerDimensions: { top: 0, left: 0 }, targetDimensions: { top: 0, left: 0 }, expectedResult: { top: 100, left: 100 }, }); testElements({ scrollOffset: { top: 0, left: 0 }, containerDimensions: { top: 0, left: 0 }, targetDimensions: { top: 100, left: 100 }, expectedResult: { top: 100, left: 100 }, }); testElements({ scrollOffset: { top: 100, left: 100 }, containerDimensions: { top: 0, left: 0 }, targetDimensions: { top: 100, left: 100 }, expectedResult: { top: 200, left: 200 }, }); testElements({ scrollOffset: { top: 100, left: 100 }, containerDimensions: { top: 100, left: 100 }, targetDimensions: { top: 100, left: 100 }, expectedResult: { top: 100, left: 100 }, }); }); test('calculates scrollOffset for target element bottom', () => { testElements({ scrollOffset: { top: 0, left: 0 }, containerDimensions: { top: 0, bottom: 300, left: 0, right: 300, }, targetDimensions: { top: 0, bottom: 300, left: 0, right: 300, }, expectedResult: { top: 0, left: 0 }, toBottom: true, toRight: true, }); testElements({ scrollOffset: { top: 100, left: 100 }, containerDimensions: { top: 0, bottom: 300, left: 0, right: 300, }, targetDimensions: { top: -100, bottom: 200, left: -100, right: 200, }, expectedResult: { top: 0, left: 0 }, toBottom: true, toRight: true, }); testElements({ scrollOffset: { top: 100, left: 100 }, containerDimensions: { top: 0, bottom: 300, left: 0, right: 300, }, targetDimensions: { top: 0, bottom: 300, left: 0, right: 300, }, expectedResult: { top: 100, left: 100 }, toBottom: true, toRight: true, }); testElements({ scrollOffset: { top: 0, left: 0 }, containerDimensions: { top: 0, bottom: 300, left: 0, right: 300, }, targetDimensions: { top: 100, bottom: 400, left: 100, right: 400, }, expectedResult: { top: 100, left: 100 }, toBottom: true, toRight: true, }); testElements({ scrollOffset: { top: 100, left: 100 }, containerDimensions: { top: 0, bottom: 300, left: 0, right: 300, }, targetDimensions: { top: 100, bottom: 400, left: 100, right: 400, }, expectedResult: { top: 200, left: 200 }, toBottom: true, toRight: true, }); testElements({ scrollOffset: { top: 100, left: 100 }, containerDimensions: { top: 100, bottom: 400, left: 100, right: 400, }, targetDimensions: { top: 100, bottom: 400, left: 100, right: 400, }, expectedResult: { top: 100, left: 100 }, toBottom: true, toRight: true, }); }); function testBodyElements(options) { const { scrollOffset, targetDimensions, expectedResult, toBottom, toRight, } = options; const container = document.body; getScrollingElement(document.body).scrollTop = scrollOffset.top; getScrollingElement(document.body).scrollLeft = scrollOffset.left; const target = document.createElement('div'); target.getBoundingClientRect = () => ({ ...targetDimensions, height: targetDimensions.bottom - targetDimensions.top, width: targetDimensions.right - targetDimensions.left, }); expect(getTargetScrollOffset(container, target, !!toBottom, !!toRight)).toEqual(expectedResult); } test('calculates scrollOffset for target element in body', () => { testBodyElements({ scrollOffset: { top: 0, left: 0 }, targetDimensions: { top: 0, left: 0 }, expectedResult: { top: 0, left: 0 }, }); testBodyElements({ scrollOffset: { top: 100, left: 100 }, targetDimensions: { top: -100, left: -100 }, expectedResult: { top: 0, left: 0 }, }); testBodyElements({ scrollOffset: { top: 100, left: 100 }, targetDimensions: { top: 0, left: 0 }, expectedResult: { top: 100, left: 100 }, }); testBodyElements({ scrollOffset: { top: 0, left: 0 }, targetDimensions: { top: 100, left: 100 }, expectedResult: { top: 100, left: 100 }, }); testBodyElements({ scrollOffset: { top: 100, left: 100 }, targetDimensions: { top: 100, left: 100 }, expectedResult: { top: 200, left: 200 }, }); }); test('calculates scrollOffset for target element bottom in body', () => { testBodyElements({ scrollOffset: { top: 0, left: 0 }, targetDimensions: { top: 0, left: 0, bottom: SCREEN_HEIGHT, right: SCREEN_WIDTH, }, expectedResult: { top: 0, left: 0 }, toBottom: true, toRight: true, }); testBodyElements({ scrollOffset: { top: 100, left: 100 }, targetDimensions: { top: -100, left: -100, bottom: SCREEN_HEIGHT - 100, right: SCREEN_WIDTH - 100, }, expectedResult: { top: 0, left: 0 }, toBottom: true, toRight: true, }); testBodyElements({ scrollOffset: { top: 100, left: 100 }, targetDimensions: { top: 0, left: 0, bottom: SCREEN_HEIGHT, right: SCREEN_WIDTH, }, expectedResult: { top: 100, left: 100 }, toBottom: true, toRight: true, }); testBodyElements({ scrollOffset: { top: 0, left: 0 }, targetDimensions: { top: 100, left: 100, bottom: SCREEN_HEIGHT + 100, right: SCREEN_WIDTH + 100, }, expectedResult: { top: 100, left: 100 }, toBottom: true, toRight: true, }); testBodyElements({ scrollOffset: { top: 100, left: 100 }, targetDimensions: { top: 100, left: 100, bottom: SCREEN_HEIGHT + 100, right: SCREEN_WIDTH + 100, }, expectedResult: { top: 200, left: 200 }, toBottom: true, toRight: true, }); }); }); describe('getElementsInContainerViewport', () => { function getElements(scrollOffset, amountOfElements, elementSize, horizontal = false) { return Array.from(Array(amountOfElements), (_, i) => { const target = document.createElement('div'); const elementOffset = (i * elementSize) - scrollOffset; target.getBoundingClientRect = () => ({ top: horizontal ? 0 : elementOffset, bottom: horizontal ? SCREEN_HEIGHT : elementOffset + elementSize, left: horizontal ? elementOffset : 0, right: horizontal ? elementOffset + elementSize : SCREEN_WIDTH, }); return target; }); } test('finds vertical elements in body viewport', () => { const test1 = getElementsInContainerViewport(document.body, getElements(0, 5, 300)); expect(test1).toHaveLength(2); const test2 = getElementsInContainerViewport(document.body, getElements(200, 5, 300)); expect(test2).toHaveLength(3); const test3 = getElementsInContainerViewport(document.body, getElements(600, 3, 300)); expect(test3).toHaveLength(1); }); test('finds horizontal elements in body viewport', () => { const test1 = getElementsInContainerViewport(document.body, getElements(0, 5, 400, true)); expect(test1).toHaveLength(2); const test2 = getElementsInContainerViewport(document.body, getElements(200, 5, 400, true)); expect(test2).toHaveLength(3); const test3 = getElementsInContainerViewport(document.body, getElements(800, 3, 400, true)); expect(test3).toHaveLength(1); }); test('finds vertical elements in non-body viewport', () => { const container = getElements(0, 1, 400)[0]; const test1 = getElementsInContainerViewport(container, getElements(0, 5, 300)); expect(test1).toHaveLength(2); const test2 = getElementsInContainerViewport(container, getElements(0, 5, 400)); expect(test2).toHaveLength(1); }); test('finds horizontal elements in non-body viewport', () => { const container = getElements(0, 1, 400, true)[0]; const test1 = getElementsInContainerViewport(container, getElements(0, 5, 300, true)); expect(test1).toHaveLength(2); const test2 = getElementsInContainerViewport(container, getElements(0, 5, 400, true)); expect(test2).toHaveLength(1); }); }); describe('elementFillsContainer', () => { function getElement(top, bottom, left = 0, right = SCREEN_WIDTH) { const target = document.createElement('div'); target.getBoundingClientRect = () => ({ top, bottom, left, right, }); return target; } test('checks element in body viewport', () => { expect(elementFillsContainer(document.body, getElement(-100, SCREEN_HEIGHT + 100))).toBe(true); expect(elementFillsContainer(document.body, getElement(0, SCREEN_HEIGHT))).toBe(true); expect(elementFillsContainer(document.body, getElement(1, SCREEN_HEIGHT + 1))).toBe(false); expect(elementFillsContainer(document.body, getElement(-1, SCREEN_HEIGHT - 1))).toBe(false); }); test('checks element in non-body viewport', () => { const container = getElement(200, 500); expect(elementFillsContainer(container, getElement(100, 600))).toBe(true); expect(elementFillsContainer(container, getElement(200, 500))).toBe(true); expect(elementFillsContainer(container, getElement(201, 501))).toBe(false); expect(elementFillsContainer(container, getElement(99, 499))).toBe(false); }); }); describe('isPassiveSupported', () => { test('returns false in JSDOM', () => { expect(passiveIsSupported).toBe(false); }); });