From bba48eaf3ecaa2c9a1639cae07e6c6490384d94e Mon Sep 17 00:00:00 2001 From: Rene Haas Date: Thu, 25 Aug 2022 14:50:03 +0200 Subject: [PATCH] improve domObserver and add domObserver unit tests --- .../src/observers/domObserver.ts | 105 ++-- .../overlayscrollbars/src/observers/index.ts | 3 + .../structureSetup.observers.ts | 11 +- .../jest-jsdom/observers/domObserver.test.ts | 478 ++++++++++++++++++ .../structureSetup.elements.test.ts | 2 +- .../observers/domObserver/index.browser.ts | 3 +- .../observers/sizeObserver/index.browser.ts | 2 +- .../trinsicObserver/index.browser.ts | 2 +- 8 files changed, 553 insertions(+), 53 deletions(-) create mode 100644 packages/overlayscrollbars/src/observers/index.ts create mode 100644 packages/overlayscrollbars/tests/jest-jsdom/observers/domObserver.test.ts diff --git a/packages/overlayscrollbars/src/observers/domObserver.ts b/packages/overlayscrollbars/src/observers/domObserver.ts index 0b95221..ad3f842 100644 --- a/packages/overlayscrollbars/src/observers/domObserver.ts +++ b/packages/overlayscrollbars/src/observers/domObserver.ts @@ -11,6 +11,8 @@ import { is, find, push, + from, + runEachAndClear, } from 'support'; type DOMContentObserverCallback = (contentChangedTroughEvent: boolean) => any; @@ -19,7 +21,6 @@ type DOMTargetObserverCallback = (targetChangedAttrs: string[], targetStyleChang interface DOMObserverOptionsBase { _attributes?: string[]; - _styleChangingAttributes?: string[]; /** * A function which can ignore a changed attribute if it returns true. * for DOMTargetObserver this applies to the changes to the observed target @@ -34,7 +35,13 @@ interface DOMContentObserverOptions extends DOMObserverOptionsBase { _ignoreContentChange?: DOMObserverIgnoreContentChange; // function which will prevent marking certain dom changes as content change if it returns true } -type DOMTargetObserverOptions = DOMObserverOptionsBase; +interface DOMTargetObserverOptions extends DOMObserverOptionsBase { + /** + * Marks certain attributes as style changing, should be a subset of the _attributes prop. + * Used to set the "targetStyleChanged" param in the DOMTargetObserverCallback. + */ + _styleChangingAttributes?: string[]; +} type ContentChangeArrayItem = [selector?: string, eventNames?: string] | null | undefined; @@ -71,7 +78,10 @@ export type DOMObserver = [ update: () => void | false | Parameters> ]; -type EventContentChangeUpdateElement = (getElements?: (selector: string) => Node[]) => void; +type EventContentChangeUpdateElement = ( + getElements?: (selector: string) => Node[], + removed?: boolean +) => void; type EventContentChange = [destroy: () => void, updateElements: EventContentChangeUpdateElement]; /** @@ -82,21 +92,20 @@ type EventContentChange = [destroy: () => void, updateElements: EventContentChan * @returns A object which contains a set of helper functions to destroy and update the observation of elements. */ const createEventContentChange = ( - target: Element, + target: HTMLElement, callback: (...args: any) => any, eventContentChange?: DOMObserverEventContentChange ): EventContentChange => { - let map: WeakMap any]> | undefined; // weak map to prevent memory leak for detached elements + let map: WeakMap any)[]> | undefined; // weak map to prevent memory leak for detached elements let destroyed = false; const destroy = () => { destroyed = true; }; - const updateElements: EventContentChangeUpdateElement = (getElements?) => { + const updateElements: EventContentChangeUpdateElement = (getElements) => { if (eventContentChange) { const eventElmList = eventContentChange.reduce>((arr, item) => { if (item) { - const selector = item[0]; - const eventNames = item[1]; + const [selector, eventNames] = item; const elements = eventNames && selector && @@ -112,27 +121,23 @@ const createEventContentChange = ( each(eventElmList, (item) => each(item[0], (elm) => { const eventNames = item[1]; - const entry = map!.get(elm); + const entries = map!.get(elm) || []; + const isTargetChild = target.contains(elm); - if (entry) { - const entryEventNames = entry[0]; - const entryOff = entry[1]; - - // in case an already registered element is registered again, unregister the previous events - if (entryEventNames === eventNames) { - entryOff(); - } + if (isTargetChild) { + const off = on(elm, eventNames, (event: Event) => { + if (destroyed) { + off(); + map!.delete(elm); + } else { + callback(event); + } + }); + map!.set(elm, push(entries, off)); + } else { + runEachAndClear(entries); + map!.delete(elm); } - - const off = on(elm, eventNames, (event: Event) => { - if (destroyed) { - off(); - map!.delete(elm); - } else { - callback(event); - } - }); - map!.set(elm, [eventNames, off]); }) ); } @@ -193,13 +198,21 @@ export const createDOMObserver = ( ): void | Parameters> => { const ignoreTargetChange = _ignoreTargetChange || noop; const ignoreContentChange = _ignoreContentChange || noop; - const targetChangedAttrs: string[] = []; - const totalAddedNodes: Node[] = []; + const totalChangedNodes: Set = new Set(); + const targetChangedAttrs: Set = new Set(); let targetStyleChanged = false; let contentChanged = false; let childListChanged = false; + each(mutations, (mutation) => { - const { attributeName, target: mutationTarget, type, oldValue, addedNodes } = mutation; + const { + attributeName, + target: mutationTarget, + type, + oldValue, + addedNodes, + removedNodes, + } = mutation; const isAttributesType = type === 'attributes'; const isChildListType = type === 'childList'; const targetIsMutationTarget = target === mutationTarget; @@ -212,9 +225,9 @@ export const createDOMObserver = ( indexOf(finalStyleChangingAttributes, attributeName) > -1 && attributeChanged; // if is content observer and something changed in children - if (isContentObserver && !targetIsMutationTarget) { + if (isContentObserver && (isChildListType || !targetIsMutationTarget)) { const notOnlyAttrChanged = !isAttributesType; - const contentAttrChanged = isAttributesType && styleChangingAttrChanged; + const contentAttrChanged = isAttributesType && attributeChanged; const isNestedTarget = contentAttrChanged && _nestedTargetSelector && is(mutationTarget, _nestedTargetSelector); const baseAssertion = isNestedTarget @@ -223,7 +236,8 @@ export const createDOMObserver = ( const contentFinalChanged = baseAssertion && !ignoreContentChange(mutation, !!isNestedTarget, target, options); - push(totalAddedNodes, addedNodes); + each(addedNodes, (node) => totalChangedNodes.add(node)); + each(removedNodes, (node) => totalChangedNodes.add(node)); contentChanged = contentChanged || contentFinalChanged; childListChanged = childListChanged || isChildListType; @@ -235,15 +249,15 @@ export const createDOMObserver = ( attributeChanged && !ignoreTargetChange(mutationTarget, attributeName!, oldValue, attributeValue) ) { - push(targetChangedAttrs, attributeName!); + targetChangedAttrs.add(attributeName!); targetStyleChanged = targetStyleChanged || styleChangingAttrChanged; } }); - if (childListChanged && !isEmptyArray(totalAddedNodes)) { - // adds / removes the new elements from the event content change - updateEventContentChangeElements((selector) => - totalAddedNodes.reduce((arr, node) => { + // adds / removes the new elements from the event content change + if (totalChangedNodes.size > 0) { + updateEventContentChangeElements((selector: string) => + from(totalChangedNodes).reduce((arr, node) => { push(arr, find(selector, node)); return is(node, selector) ? push(arr, node) : arr; }, []) @@ -254,12 +268,15 @@ export const createDOMObserver = ( !fromRecords && contentChanged && (callback as DOMContentObserverCallback)(false); return [false] as Parameters>; } - if (!isEmptyArray(targetChangedAttrs) || targetStyleChanged) { - !fromRecords && - (callback as DOMTargetObserverCallback)(targetChangedAttrs, targetStyleChanged); - return [targetChangedAttrs, targetStyleChanged] as Parameters< - DOMObserverCallback - >; + + if (targetChangedAttrs.size > 0 || targetStyleChanged) { + const args: Parameters = [ + from(targetChangedAttrs), + targetStyleChanged, + ]; + !fromRecords && (callback as DOMTargetObserverCallback).apply(0, args); + + return args as Parameters>; } }; const mutationObserver: MutationObserver = new MutationObserverConstructor!((mutations) => diff --git a/packages/overlayscrollbars/src/observers/index.ts b/packages/overlayscrollbars/src/observers/index.ts new file mode 100644 index 0000000..923ff35 --- /dev/null +++ b/packages/overlayscrollbars/src/observers/index.ts @@ -0,0 +1,3 @@ +export * from 'observers/domObserver'; +export * from 'observers/sizeObserver'; +export * from 'observers/trinsicObserver'; diff --git a/packages/overlayscrollbars/src/setups/structureSetup/structureSetup.observers.ts b/packages/overlayscrollbars/src/setups/structureSetup/structureSetup.observers.ts index dc5e951..e8e518f 100644 --- a/packages/overlayscrollbars/src/setups/structureSetup/structureSetup.observers.ts +++ b/packages/overlayscrollbars/src/setups/structureSetup/structureSetup.observers.ts @@ -34,9 +34,13 @@ import { classNameScrollbar, classNameViewportArrange, } from 'classnames'; -import { createSizeObserver, SizeObserverCallbackParams } from 'observers/sizeObserver'; -import { createTrinsicObserver } from 'observers/trinsicObserver'; -import { createDOMObserver, DOMObserver } from 'observers/domObserver'; +import { + createSizeObserver, + createTrinsicObserver, + createDOMObserver, + DOMObserver, + SizeObserverCallbackParams, +} from 'observers'; import type { SetupState, SetupUpdateCheckOption } from 'setups'; import type { StructureSetupState } from 'setups/structureSetup'; import type { StructureSetupElementsObj } from 'setups/structureSetup/structureSetup.elements'; @@ -318,7 +322,6 @@ export const createStructureSetupObservers = ( true, onContentMutation, { - _styleChangingAttributes: contentMutationObserverAttr.concat(attributes || []), _attributes: contentMutationObserverAttr.concat(attributes || []), _eventContentChange: elementEvents, _nestedTargetSelector: hostSelector, diff --git a/packages/overlayscrollbars/tests/jest-jsdom/observers/domObserver.test.ts b/packages/overlayscrollbars/tests/jest-jsdom/observers/domObserver.test.ts new file mode 100644 index 0000000..58258d2 --- /dev/null +++ b/packages/overlayscrollbars/tests/jest-jsdom/observers/domObserver.test.ts @@ -0,0 +1,478 @@ +import { createDOMObserver } from 'observers'; + +jest.useFakeTimers(); + +jest.mock('support/compatibility/apis', () => { + const originalModule = jest.requireActual('support/compatibility/apis'); + const mockRAF = (arg: any) => setTimeout(arg, 0); + return { + ...originalModule, + // @ts-ignore + rAF: jest.fn().mockImplementation((...args) => mockRAF(...args)), + cAF: jest.fn().mockImplementation((...args) => clearTimeout(...args)), + // @ts-ignore + setT: jest.fn().mockImplementation((...args) => setTimeout(...args)), + clearT: jest.fn().mockImplementation((...args) => clearTimeout(...args)), + }; +}); + +describe('createDOMObserver', () => { + beforeEach(() => { + document.body.outerHTML = ''; + }); + + describe('target observer', () => { + test('basic functionality', async () => { + document.body.innerHTML = '
'; + const callback = jest.fn(); + const div = document.body.firstElementChild as HTMLElement; + const [destroy, update] = createDOMObserver(document.body, false, callback, { + _attributes: ['style', 'class', 'id'], + _styleChangingAttributes: ['id'], + }); + + expect(destroy).toEqual(expect.any(Function)); + expect(update).toEqual(expect.any(Function)); + + expect(callback).not.toHaveBeenCalled(); + + document.body.style.width = '100px'; + await Promise.resolve(); + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenLastCalledWith(['style'], false); + + document.body.classList.add('test'); + await Promise.resolve(); + expect(callback).toHaveBeenCalledTimes(2); + expect(callback).toHaveBeenLastCalledWith(['class'], false); + + document.body.id = 'test'; + await Promise.resolve(); + expect(callback).toHaveBeenCalledTimes(3); + expect(callback).toHaveBeenLastCalledWith(['id'], true); + + document.body.style.width = ''; + document.body.classList.remove('test'); + document.body.id = ''; + await Promise.resolve(); + expect(callback).toHaveBeenCalledTimes(4); + expect(callback).toHaveBeenLastCalledWith(['style', 'class', 'id'], true); + + document.body.setAttribute('data-something', 'hi'); + await Promise.resolve(); + expect(callback).toHaveBeenCalledTimes(4); + + div.style.width = '100px'; + div.classList.add('test'); + div.id = 'test'; + await Promise.resolve(); + expect(callback).toHaveBeenCalledTimes(4); + + div.append(document.createElement('div')); + await Promise.resolve(); + expect(callback).toHaveBeenCalledTimes(4); + + div.remove(); + await Promise.resolve(); + expect(callback).toHaveBeenCalledTimes(4); + }); + + test('update', async () => { + document.body.innerHTML = '
'; + const callback = jest.fn(); + const [destroy, update] = createDOMObserver(document.body, false, callback, { + _attributes: ['style', 'class', 'id'], + _styleChangingAttributes: ['data-stylechanged'], + }); + + expect(destroy).toEqual(expect.any(Function)); + expect(update).toEqual(expect.any(Function)); + + expect(callback).not.toHaveBeenCalled(); + + document.body.style.width = '100px'; + document.body.classList.add('test'); + document.body.id = 'test'; + document.body.classList.add('test2'); + const [changedAttrs, styleChanged] = update() as any; + + expect(changedAttrs).toEqual(['style', 'class', 'id']); + expect(styleChanged).toEqual(false); + + document.body.setAttribute('data-stylechanged', 'true'); + document.body.id = ''; + + const [changedAttrs2, styleChanged2] = update() as any; + + expect(changedAttrs2).toEqual(['data-stylechanged', 'id']); + expect(styleChanged2).toEqual(true); + + document.body.removeAttribute('data-stylechanged'); + document.body.id = 'something'; + await Promise.resolve(); + + expect(callback).toHaveBeenCalledTimes(1); + const changed = update(); + expect(changed).toBeFalsy(); + }); + + test('destroy', async () => { + document.body.innerHTML = '
'; + const callback = jest.fn(); + const div = document.body.firstElementChild as HTMLElement; + const [destroy, update] = createDOMObserver(document.body, false, callback, { + _attributes: ['style', 'class', 'id'], + }); + + expect(destroy).toEqual(expect.any(Function)); + expect(update).toEqual(expect.any(Function)); + + expect(callback).not.toHaveBeenCalled(); + + document.body.style.width = '100px'; + document.body.classList.add('test'); + document.body.id = 'test'; + document.body.classList.add('test2'); + await Promise.resolve(); + + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenLastCalledWith(['style', 'class', 'id'], false); + + destroy(); + + document.body.style.width = ''; + document.body.classList.remove('test'); + document.body.id = ''; + document.body.setAttribute('data-something', 'hi'); + div.append(document.createElement('div')); + div.remove(); + await Promise.resolve(); + + expect(callback).toHaveBeenCalledTimes(1); + expect(update()).toBeFalsy(); + expect(destroy()).toBeUndefined(); + }); + }); + + describe('content observer', () => { + test('basic functionality', async () => { + document.body.innerHTML = '
'; + const callback = jest.fn(); + const div = document.body.firstElementChild as HTMLElement; + const [destroy, update] = createDOMObserver(document.body, true, callback, { + _attributes: ['style', 'class', 'id'], + }); + + expect(destroy).toEqual(expect.any(Function)); + expect(update).toEqual(expect.any(Function)); + + expect(callback).not.toHaveBeenCalled(); + + document.body.style.width = '100px'; + await Promise.resolve(); + expect(callback).not.toHaveBeenCalled(); + + document.body.classList.add('test'); + await Promise.resolve(); + expect(callback).not.toHaveBeenCalled(); + + document.body.id = 'test'; + await Promise.resolve(); + expect(callback).not.toHaveBeenCalled(); + + document.body.setAttribute('data-something', 'hi'); + await Promise.resolve(); + expect(callback).not.toHaveBeenCalled(); + + div.style.width = '100px'; + div.classList.add('test'); + div.id = 'test'; + await Promise.resolve(); + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledWith(false); + + div.append(document.createElement('div')); + await Promise.resolve(); + expect(callback).toHaveBeenCalledTimes(2); + expect(callback).toHaveBeenCalledWith(false); + + document.body.append(document.createElement('div')); + await Promise.resolve(); + expect(callback).toHaveBeenCalledTimes(3); + expect(callback).toHaveBeenCalledWith(false); + + div.remove(); + await Promise.resolve(); + expect(callback).toHaveBeenCalledTimes(4); + expect(callback).toHaveBeenCalledWith(false); + }); + + test('ignoreContentChange', async () => { + document.body.innerHTML = '
'; + const callback = jest.fn(); + const ignoreContentChange = jest.fn(() => true); + const div = document.body.firstElementChild as HTMLElement; + const [destroy, update] = createDOMObserver(document.body, true, callback, { + _attributes: ['style', 'class', 'id'], + _ignoreContentChange: ignoreContentChange, + }); + + expect(destroy).toEqual(expect.any(Function)); + expect(update).toEqual(expect.any(Function)); + + expect(callback).not.toHaveBeenCalled(); + expect(ignoreContentChange).not.toHaveBeenCalled(); + + div.style.width = '100px'; + await Promise.resolve(); + expect(ignoreContentChange).toHaveBeenCalledTimes(1); + expect(callback).not.toHaveBeenCalled(); + + div.classList.add('test'); + div.id = 'test'; + await Promise.resolve(); + expect(ignoreContentChange).toHaveBeenCalledTimes(3); + expect(callback).not.toHaveBeenCalled(); + + div.append(document.createElement('div')); + await Promise.resolve(); + expect(ignoreContentChange).toHaveBeenCalledTimes(4); + expect(callback).not.toHaveBeenCalled(); + + document.body.append(document.createElement('div')); + await Promise.resolve(); + expect(ignoreContentChange).toHaveBeenCalledTimes(5); + expect(callback).not.toHaveBeenCalled(); + + div.remove(); + await Promise.resolve(); + expect(ignoreContentChange).toHaveBeenCalledTimes(6); + expect(callback).not.toHaveBeenCalled(); + }); + + test('eventContentChange', async () => { + document.body.innerHTML = '

'; + const callback = jest.fn(); + const div = document.body.firstElementChild as HTMLElement; + const [destroy, update] = createDOMObserver(document.body, true, callback, { + _attributes: ['style', 'class', 'id'], + _eventContentChange: [ + ['*', 'click'], + ['*', 'keydown'], + ['span', 'transitionend animationend'], + ], + }); + + expect(destroy).toEqual(expect.any(Function)); + expect(update).toEqual(expect.any(Function)); + + expect(callback).not.toHaveBeenCalled(); + + const paragraph = document.createElement('p'); + const span = document.createElement('span'); + const appendedDiv = document.createElement('div'); + appendedDiv.append(span); + div.append(appendedDiv); + div.append(paragraph); + await Promise.resolve(); + + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenLastCalledWith(false); + + div.dispatchEvent(new Event('click')); + jest.runAllTimers(); + + expect(callback).toHaveBeenCalledTimes(2); + expect(callback).toHaveBeenLastCalledWith(true); + + div.dispatchEvent(new Event('keydown')); + jest.runAllTimers(); + + expect(callback).toHaveBeenCalledTimes(3); + expect(callback).toHaveBeenLastCalledWith(true); + + paragraph.dispatchEvent(new Event('click')); + jest.runAllTimers(); + + expect(callback).toHaveBeenCalledTimes(4); + expect(callback).toHaveBeenLastCalledWith(true); + + paragraph.dispatchEvent(new Event('keydown')); + jest.runAllTimers(); + + expect(callback).toHaveBeenCalledTimes(5); + expect(callback).toHaveBeenLastCalledWith(true); + + // debounced to one update + div.dispatchEvent(new Event('click')); + div.dispatchEvent(new Event('keydown')); + paragraph.dispatchEvent(new Event('click')); + paragraph.dispatchEvent(new Event('keydown')); + jest.runAllTimers(); + + expect(callback).toHaveBeenCalledTimes(6); + expect(callback).toHaveBeenLastCalledWith(true); + + span.dispatchEvent(new Event('click')); + jest.runAllTimers(); + + expect(callback).toHaveBeenCalledTimes(7); + expect(callback).toHaveBeenLastCalledWith(true); + + span.dispatchEvent(new Event('transitionend')); + jest.runAllTimers(); + + expect(callback).toHaveBeenCalledTimes(8); + expect(callback).toHaveBeenLastCalledWith(true); + + span.dispatchEvent(new Event('animationend')); + jest.runAllTimers(); + + expect(callback).toHaveBeenCalledTimes(9); + expect(callback).toHaveBeenLastCalledWith(true); + + span.dispatchEvent(new Event('transitionend')); + span.dispatchEvent(new Event('animationend')); + jest.runAllTimers(); + + expect(callback).toHaveBeenCalledTimes(10); + expect(callback).toHaveBeenLastCalledWith(true); + + appendedDiv.dispatchEvent(new Event('click')); + jest.runAllTimers(); + + expect(callback).toHaveBeenCalledTimes(11); + expect(callback).toHaveBeenLastCalledWith(true); + + // remove from target and trigger events from new location + document.body.parentElement!.append(appendedDiv); + await Promise.resolve(); + + expect(callback).toHaveBeenCalledTimes(12); + expect(callback).toHaveBeenLastCalledWith(false); + + span.dispatchEvent(new Event('transitionend')); + jest.runAllTimers(); + + expect(callback).toHaveBeenCalledTimes(12); + + appendedDiv.dispatchEvent(new Event('click')); + jest.runAllTimers(); + + expect(callback).toHaveBeenCalledTimes(12); + }); + + test('update', async () => { + document.body.innerHTML = '
'; + const callback = jest.fn(); + const div = document.body.firstElementChild as HTMLElement; + const [destroy, update] = createDOMObserver(document.body, true, callback, { + _attributes: ['style', 'class', 'id'], + _eventContentChange: [ + ['*', 'click'], + ['*', 'keydown'], + ['span', 'transitionend animationend'], + ], + }); + + expect(destroy).toEqual(expect.any(Function)); + expect(update).toEqual(expect.any(Function)); + + expect(callback).not.toHaveBeenCalled(); + + div.style.width = '100px'; + div.classList.add('test'); + div.id = 'test'; + + const [contentChangedThroughEvent] = update() as any; + expect(contentChangedThroughEvent).toBe(false); + + const paragraph = document.createElement('p'); + const span = document.createElement('span'); + const appendedDiv = document.createElement('div'); + appendedDiv.append(span); + div.append(appendedDiv); + div.append(paragraph); + + const [contentChangedThroughEvent2] = update() as any; + expect(contentChangedThroughEvent2).toBe(false); + + expect(callback).not.toHaveBeenCalled(); + + div.dispatchEvent(new Event('click')); + div.dispatchEvent(new Event('keydown')); + + expect(callback).not.toHaveBeenCalled(); + + const change = update(); + expect(change).toBeFalsy(); + expect(callback).toHaveBeenCalledTimes(1); + }); + + test('destroy', async () => { + document.body.innerHTML = '

'; + const callback = jest.fn(); + const div = document.body.firstElementChild as HTMLElement; + const [destroy, update] = createDOMObserver(document.body, true, callback, { + _attributes: ['style', 'class', 'id'], + _eventContentChange: [ + ['*', 'click'], + ['*', 'keydown'], + ['span', 'transitionend animationend'], + ], + }); + + expect(destroy).toEqual(expect.any(Function)); + expect(update).toEqual(expect.any(Function)); + + expect(callback).not.toHaveBeenCalled(); + + div.style.width = '100px'; + div.classList.add('test'); + div.id = 'test'; + await Promise.resolve(); + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledWith(false); + + const paragraph = document.createElement('p'); + const span = document.createElement('span'); + const appendedDiv = document.createElement('div'); + appendedDiv.append(span); + div.append(appendedDiv); + div.append(paragraph); + await Promise.resolve(); + + expect(callback).toHaveBeenCalledTimes(2); + expect(callback).toHaveBeenLastCalledWith(false); + + destroy(); + + div.dispatchEvent(new Event('click')); + div.dispatchEvent(new Event('keydown')); + paragraph.dispatchEvent(new Event('click')); + paragraph.dispatchEvent(new Event('keydown')); + span.dispatchEvent(new Event('click')); + span.dispatchEvent(new Event('transitionend')); + span.dispatchEvent(new Event('animationend')); + appendedDiv.dispatchEvent(new Event('click')); + document.body.parentElement!.append(appendedDiv); + + jest.runAllTimers(); + await Promise.resolve(); + + expect(callback).toHaveBeenCalledTimes(2); + + span.dispatchEvent(new Event('transitionend')); + appendedDiv.dispatchEvent(new Event('click')); + div.style.width = '100px'; + div.classList.add('test'); + div.id = 'test'; + await Promise.resolve(); + jest.runAllTimers(); + + expect(callback).toHaveBeenCalledTimes(2); + expect(update()).toBeFalsy(); + expect(destroy()).toBeUndefined(); + }); + }); +}); diff --git a/packages/overlayscrollbars/tests/jest-jsdom/setups/structureSetup/structureSetup.elements.test.ts b/packages/overlayscrollbars/tests/jest-jsdom/setups/structureSetup/structureSetup.elements.test.ts index 67c592e..6d03d77 100644 --- a/packages/overlayscrollbars/tests/jest-jsdom/setups/structureSetup/structureSetup.elements.test.ts +++ b/packages/overlayscrollbars/tests/jest-jsdom/setups/structureSetup/structureSetup.elements.test.ts @@ -80,7 +80,7 @@ const fillBody = ( return getSnapshot(); }; const clearBody = () => { - document.body.innerHTML = ''; + document.body.outerHTML = ''; }; const getElements = (targetType: TargetType) => { diff --git a/packages/overlayscrollbars/tests/playwright/observers/domObserver/index.browser.ts b/packages/overlayscrollbars/tests/playwright/observers/domObserver/index.browser.ts index 31663a6..d5adcfc 100644 --- a/packages/overlayscrollbars/tests/playwright/observers/domObserver/index.browser.ts +++ b/packages/overlayscrollbars/tests/playwright/observers/domObserver/index.browser.ts @@ -22,7 +22,7 @@ import { on, } from 'support'; -import { createDOMObserver } from 'observers/domObserver'; +import { createDOMObserver } from 'observers'; type DOMContentObserverResult = { contentChange: boolean; @@ -168,7 +168,6 @@ const createContentDomOserver = ( }); }, { - _styleChangingAttributes: attrs, _attributes: attrs, _eventContentChange: eventContentChange, _nestedTargetSelector: hostSelector, diff --git a/packages/overlayscrollbars/tests/playwright/observers/sizeObserver/index.browser.ts b/packages/overlayscrollbars/tests/playwright/observers/sizeObserver/index.browser.ts index 64322b9..9dc6273 100644 --- a/packages/overlayscrollbars/tests/playwright/observers/sizeObserver/index.browser.ts +++ b/packages/overlayscrollbars/tests/playwright/observers/sizeObserver/index.browser.ts @@ -12,7 +12,7 @@ import { } from '@~local/browser-testing'; import { hasDimensions, offsetSize, WH, style } from 'support'; import { addPlugin, sizeObserverPlugin } from 'plugins'; -import { createSizeObserver } from 'observers/sizeObserver'; +import { createSizeObserver } from 'observers'; if (!window.ResizeObserver) { addPlugin(sizeObserverPlugin); diff --git a/packages/overlayscrollbars/tests/playwright/observers/trinsicObserver/index.browser.ts b/packages/overlayscrollbars/tests/playwright/observers/trinsicObserver/index.browser.ts index 91b9f20..94aa641 100644 --- a/packages/overlayscrollbars/tests/playwright/observers/trinsicObserver/index.browser.ts +++ b/packages/overlayscrollbars/tests/playwright/observers/trinsicObserver/index.browser.ts @@ -11,7 +11,7 @@ import { waitForOrFailTest, } from '@~local/browser-testing'; import { offsetSize } from 'support'; -import { createTrinsicObserver } from 'observers/trinsicObserver'; +import { createTrinsicObserver } from 'observers'; import { addPlugin, sizeObserverPlugin } from 'plugins'; if (!window.ResizeObserver) {