Files
OverlayScrollbars/packages/overlayscrollbars/test/jest-jsdom/setups/structureSetup/structureSetup.elements.test.ts
T
2022-10-30 20:58:14 +01:00

1390 lines
51 KiB
TypeScript

import { hasClass, is, isHTMLElement } from '~/support';
import { resolveInitialization } from '~/initialization';
import {
dataAttributeHost,
classNamePadding,
classNameViewport,
classNameContent,
} from '~/classnames';
import { getEnvironment } from '~/environment';
import { createStructureSetupElements } from '~/setups/structureSetup/structureSetup.elements';
import { addPlugin, ScrollbarsHidingPlugin } from '~/plugins';
import type { StructureSetupElementsObj } from '~/setups/structureSetup/structureSetup.elements';
import type { InternalEnvironment } from '~/environment';
import type {
Initialization,
InitializationTarget,
InitializationTargetObject,
} from '~/initialization';
jest.mock('~/environment', () => ({
getEnvironment: jest.fn(),
}));
addPlugin(ScrollbarsHidingPlugin);
interface StructureSetupElementsProxy {
input: InitializationTarget;
elements: StructureSetupElementsObj;
destroy: () => void;
}
type TargetType = 'element' | 'textarea' | 'body';
type StructureStaticInitializationElement = Initialization['elements']['viewport'];
type StructureDynamicInitializationElement = Initialization['elements']['content'];
const textareaId = 'textarea';
const textareaHostId = 'host';
const elementId = 'target';
const dynamicContentId = 'dynamicContent';
const dynamicContent = `text<p id="${dynamicContentId}">paragraph</p>`;
const textareaContent = `<textarea id="${textareaId}">text</textarea>`;
const getSnapshot = () => document.documentElement.outerHTML;
const getTarget = (targetType: TargetType) => {
switch (targetType) {
case 'element':
return document.getElementById(elementId)!;
case 'textarea':
return document.getElementById(textareaId)!;
case 'body':
return document.body;
default:
throw new Error('Invalid Target');
}
};
const fillBody = (
targetType: TargetType,
customDOM?: (content: string, hostId: string) => string
) => {
const textarea = targetType === 'textarea';
const hostId = textarea ? textareaHostId : elementId;
const customDomResult =
customDOM && customDOM(textarea ? textareaContent : dynamicContent, hostId);
const normalDom = textarea ? textareaContent : `<div id="${elementId}">${dynamicContent}</div>`;
document.body.innerHTML = `
<nav></nav>
${customDomResult || normalDom}
<footer></footer>
`;
// unwrap host if target is body
if (targetType === 'body') {
const target = document.querySelector(`#${hostId}`);
if (target) {
const { parentElement } = target;
parentElement!.append(...target.childNodes);
target.remove();
}
}
return getSnapshot();
};
const clearBody = () => {
document.body.innerHTML = '';
};
const getElements = (targetType: TargetType) => {
const target = getTarget(targetType);
const host = document.querySelector(`[${dataAttributeHost}]`)!;
const padding = document.querySelector(`.${classNamePadding}`)!;
const viewport = document.querySelector(`.${classNameViewport}`)!;
const content = document.querySelector(`.${classNameContent}`)!;
const children =
targetType === 'textarea'
? document.querySelector(`#${textareaId}`)
: document.querySelector(`#${dynamicContentId}`);
return {
target,
host,
padding,
viewport,
content,
children,
};
};
const assertCorrectDOMStructure = (
targetType: TargetType,
env: InternalEnvironment,
elements: StructureSetupElementsObj
) => {
const { target, host, padding, viewport, content, children } = getElements(targetType);
const { _getDefaultInitialization } = env;
const { _viewportIsTarget, _viewportIsContent, _viewport, _content } = elements;
expect(children).toBeDefined();
expect((_content || _viewport).contains(children)).toBe(true);
if (_viewportIsTarget) {
expect(host.getAttribute(dataAttributeHost)).toBe('viewport');
expect(_viewportIsContent).toBe(false);
expect(target).toBe(host);
expect(host).toBeTruthy();
expect(padding).toBeFalsy();
expect(viewport).toBeFalsy();
expect(content).toBeFalsy();
} else {
expect(host.getAttribute(dataAttributeHost)).toBe('host');
expect(host).toBeTruthy();
expect(viewport).toBeTruthy();
expect(viewport.parentElement).toBe(padding || host);
if (content) {
expect(content.parentElement).toBe(viewport);
}
if (padding) {
expect(padding.parentElement).toBe(host);
}
if (targetType !== 'body') {
expect(host.parentElement).toBe(document.body);
expect(host.previousElementSibling).toBe(document.querySelector('nav'));
expect(host.nextElementSibling).toBe(document.querySelector('footer'));
}
const contentElm = content || viewport;
if (targetType === 'textarea') {
expect(target.parentElement).toBe(contentElm);
expect(contentElm.innerHTML).toBe(textareaContent);
} else {
expect(target).toBe(host);
expect(contentElm.innerHTML).toContain(dynamicContent);
}
}
if (_viewportIsContent) {
const { _target } = elements;
const { elements: defaultInitElements } = _getDefaultInitialization();
const { content: defaultContentInit } = defaultInitElements;
const resolvedDefaultContentInit = resolveInitialization([_target], defaultContentInit);
if (resolvedDefaultContentInit) {
expect(children!.parentElement).toBe(_content || _viewport);
} else {
expect(children!.parentElement).toBe(_viewport);
}
}
};
const createStructureSetupElementsProxy = (
target: InitializationTarget,
{ tabindex = false, autoAppend = true } = {}
): StructureSetupElementsProxy => {
const [elements, appendElements, destroy] = createStructureSetupElements(target);
// simulate tabindex inheritance from host via mutation observer
if (tabindex) {
elements._viewport.setAttribute('tabindex', elements._target.getAttribute('tabindex')!);
}
if (autoAppend) {
appendElements();
}
return {
input: target,
elements,
destroy,
};
};
const assertCorrectSetupElements = (
targetType: TargetType,
setupElementsProxy: StructureSetupElementsProxy,
environment: InternalEnvironment
): [StructureSetupElementsObj, () => void] => {
const { input, elements, destroy } = setupElementsProxy;
const {
_target,
_host,
_padding,
_viewport,
_content,
_viewportIsTarget,
_viewportIsContent,
_viewportHasClass,
_viewportAddRemoveClass,
} = elements;
const { target, host, padding, viewport, content } = getElements(targetType);
const isTextarea = target.matches('textarea');
const isBody = target.matches('body');
if (targetType !== 'element') {
expect(target.matches(targetType)).toBe(true);
}
expect(_target).toBe(target);
expect(_host).toBe(host);
if (_viewportIsTarget) {
expect(padding).toBeFalsy();
expect(_padding).toBeFalsy();
} else if (padding || _padding) {
expect(_padding).toBe(padding);
} else {
expect(padding).toBeFalsy();
expect(_padding).toBeFalsy();
}
if (_viewportIsTarget) {
expect(_viewport).toBe(_target);
} else if (viewport || _viewport) {
expect(_viewport).toBe(viewport);
} else {
expect(viewport).toBeFalsy();
expect(_viewport).toBeFalsy();
}
if (_viewportIsTarget) {
expect(content).toBeFalsy();
expect(_content).toBeFalsy();
} else if (content || _content) {
expect(_content).toBe(content);
} else {
expect(content).toBeFalsy();
expect(_content).toBeFalsy();
}
const { _isTextarea, _isBody, _documentElm, _windowElm } = elements;
expect(_isTextarea).toBe(isTextarea);
expect(_isBody).toBe(isBody);
expect(_windowElm).toBe(document.defaultView);
expect(_documentElm).toBe(document);
expect(typeof destroy).toBe('function');
const { _nativeScrollbarsHiding, _cssCustomProperties, _getDefaultInitialization } = environment;
const { elements: defaultInitElements } = _getDefaultInitialization();
const {
host: defaultHostInitStrategy,
padding: defaultPaddingInitStrategy,
viewport: defaultViewportInitStrategy,
content: defaultContentInitStrategy,
} = defaultInitElements;
const inputIsElement = isHTMLElement(input);
const inputAsObj = input as InitializationTargetObject;
const styleElm = document.querySelector('style');
const checkStrategyDependendElements = (
elm: Element | null,
initialization:
| StructureStaticInitializationElement
| StructureDynamicInitializationElement
| undefined,
isStaticInitialization: boolean,
defaultInitialization:
| StructureStaticInitializationElement
| StructureDynamicInitializationElement,
kind: 'padding' | 'viewport' | 'content' | 'host'
) => {
const resolvedInitialization = resolveInitialization([target], initialization);
const resolvedDefaultInitialization = resolveInitialization([target], defaultInitialization);
if (resolvedInitialization) {
if (!_viewportIsTarget && !_viewportIsContent) {
expect(elm).toBeTruthy();
}
if (_viewportIsContent) {
if (kind === 'content') {
if (resolvedDefaultInitialization) {
expect(elm).toBeTruthy();
} else {
expect(elm).toBeFalsy();
}
} else {
expect(elm).toBeTruthy();
}
}
} else {
if (resolvedInitialization === false) {
expect(elm).toBeFalsy();
}
if (resolvedInitialization === undefined) {
if (isStaticInitialization) {
defaultInitialization = defaultInitialization as StructureStaticInitializationElement;
if (_viewportIsTarget) {
if (kind === 'host') {
expect(elm).toBeTruthy();
} else {
expect(elm).toBeFalsy();
}
} else if (resolvedDefaultInitialization && !isTextarea) {
expect(resolvedDefaultInitialization).toBe(elm);
} else {
expect(elm).toBeTruthy();
}
} else {
defaultInitialization = defaultInitialization as StructureDynamicInitializationElement;
const resultIsBoolean = typeof resolvedDefaultInitialization === 'boolean';
if (_viewportIsTarget) {
if (kind === 'host') {
expect(elm).toBeTruthy();
} else {
expect(elm).toBeFalsy();
}
} else if (resultIsBoolean) {
if (resolvedDefaultInitialization) {
expect(elm).toBeTruthy();
} else {
expect(elm).toBeFalsy();
}
} else if (resolvedDefaultInitialization) {
expect(elm).toBe(resolvedDefaultInitialization);
}
}
}
}
};
if (_nativeScrollbarsHiding || _cssCustomProperties || _viewportIsTarget) {
expect(styleElm).toBeFalsy();
} else {
expect(styleElm).toBeTruthy();
}
if (_viewportIsContent) {
const { content: defaultContentInit } = defaultInitElements;
const resolvedDefaultContentInit = resolveInitialization([_target], defaultContentInit);
if (resolvedDefaultContentInit) {
expect(_content).toBeTruthy();
} else {
expect(_content).toBeFalsy();
}
}
if (inputIsElement) {
checkStrategyDependendElements(
padding,
undefined,
false,
defaultPaddingInitStrategy,
'padding'
);
checkStrategyDependendElements(
content,
undefined,
false,
defaultContentInitStrategy,
'content'
);
checkStrategyDependendElements(
viewport,
undefined,
true,
defaultViewportInitStrategy,
'viewport'
);
checkStrategyDependendElements(host, undefined, true, defaultHostInitStrategy, 'host');
} else {
const { elements: inputElements } = inputAsObj;
const {
host: hostInitialization,
padding: paddingInitialization,
viewport: viewportInitialization,
content: contentInitialization,
} = inputElements || {};
checkStrategyDependendElements(
padding,
paddingInitialization,
false,
defaultPaddingInitStrategy,
'padding'
);
checkStrategyDependendElements(
content,
contentInitialization,
false,
defaultContentInitStrategy,
'content'
);
checkStrategyDependendElements(
viewport,
viewportInitialization,
true,
defaultViewportInitStrategy,
'viewport'
);
checkStrategyDependendElements(host, hostInitialization, true, defaultHostInitStrategy, 'host');
}
const className = 'clazz';
const attrName = 'attr';
_viewportAddRemoveClass(className, attrName, true);
if (_viewportIsTarget) {
expect(_host.getAttribute(dataAttributeHost)!.indexOf(attrName) >= 0).toBe(true);
expect(_viewportHasClass('', attrName)).toBe(true);
} else {
expect(hasClass(_viewport, className)).toBe(true);
expect(_viewportHasClass(className, '')).toBe(true);
}
_viewportAddRemoveClass(className, attrName);
if (_viewportIsTarget) {
expect(_host.getAttribute(dataAttributeHost)!.indexOf(attrName) >= 0).toBe(false);
expect(_viewportHasClass('', attrName)).toBe(false);
} else {
expect(hasClass(_viewport, className)).toBe(false);
expect(_viewportHasClass(className, '')).toBe(false);
}
return [elements, destroy];
};
const assertCorrectDestroy = (snapshot: string, destroy: () => void) => {
destroy();
// remove empty class attr
const elms = document.querySelectorAll('*');
Array.from(elms).forEach((elm) => {
const classAttr = elm.getAttribute('class');
if (classAttr === '') {
elm.removeAttribute('class');
}
});
expect(snapshot).toBe(getSnapshot());
};
const env: InternalEnvironment = jest.requireActual('~/environment').getEnvironment();
const envDefault = {
name: 'default',
env,
};
const envNativeScrollbarStyling = {
name: 'native scrollbar styling',
env: {
...env,
_nativeScrollbarsHiding: true,
},
};
const envCssCustomProperties = {
name: 'custom css properties',
env: {
...env,
_cssCustomProperties: true,
},
};
const envInitStrategyMin = {
name: 'initialization strategy min',
env: {
...env,
_getDefaultInitialization: () => ({
...env._staticDefaultInitialization,
elements: {
...env._staticDefaultInitialization.elements,
host: null,
viewport: () => null,
content: () => false,
padding: false,
},
}),
},
};
const envInitStrategyMax = {
name: 'initialization strategy max',
env: {
...env,
_getDefaultInitialization: () => ({
...env._staticDefaultInitialization,
elements: {
...env._staticDefaultInitialization.elements,
host: null,
viewport: null,
content: true,
padding: () => true,
},
}),
},
};
const envInitStrategyAssigned = {
name: 'initialization strategy assigned',
env: {
...env,
_getDefaultInitialization: () => ({
...env._staticDefaultInitialization,
elements: {
...env._staticDefaultInitialization.elements,
host: () => document.querySelector('#host1') as HTMLElement,
viewport: (target: HTMLElement) => target.querySelector('#viewport') as HTMLElement,
content: (target: HTMLElement) => target.querySelector<HTMLElement>('#content'),
padding: (target: HTMLElement) => target.querySelector<HTMLElement>('#padding'),
},
}),
},
};
const envInitStrategyViewportIsTarget = {
name: 'initialization strategy viewport is target',
env: {
...env,
_nativeScrollbarsHiding: true,
_getDefaultInitialization: () => ({
...env._staticDefaultInitialization,
elements: {
...env._staticDefaultInitialization.elements,
viewport: (target: HTMLElement) => !is(target, 'textarea') && target,
},
}),
},
};
describe('structureSetup.elements', () => {
afterEach(() => clearBody());
beforeEach(() => {
(getEnvironment as jest.Mock).mockImplementation(() =>
jest.requireActual('~/environment').getEnvironment()
);
});
[
envDefault,
envNativeScrollbarStyling,
envCssCustomProperties,
envInitStrategyMin,
envInitStrategyMax,
envInitStrategyAssigned,
envInitStrategyViewportIsTarget,
].forEach((envWithName) => {
const { env: currEnv, name } = envWithName;
describe(`Environment: ${name}`, () => {
beforeEach(() => {
(getEnvironment as jest.Mock).mockImplementation(() => currEnv);
});
(['element', 'textarea', 'body'] as TargetType[]).forEach((targetType) => {
describe(targetType, () => {
describe('basic', () => {
test('Element', () => {
const snapshot = fillBody(targetType);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy(getTarget(targetType)),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
test('Object', () => {
const snapshot = fillBody(targetType);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({ target: getTarget(targetType) }),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
});
describe('complex', () => {
describe('single assigned', () => {
test('padding', () => {
const snapshot = fillBody(
targetType,
(content, hostId) =>
`<div id="${hostId}"><div id="padding">${content}</div></div>`
);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: {
host: document.querySelector<HTMLElement>(`#${textareaHostId}`),
padding: () => document.querySelector<HTMLElement>('#padding'),
},
}),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
test('viewport', () => {
const snapshot = fillBody(
targetType,
(content, hostId) =>
`<div id="${hostId}"><div id="viewport">${content}</div></div>`
);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: {
host: () => document.querySelector<HTMLElement>(`#${textareaHostId}`),
viewport: document.querySelector<HTMLElement>('#viewport'),
},
}),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
test('content', () => {
const snapshot = fillBody(
targetType,
(content, hostId) =>
`<div id="${hostId}"><div id="content">${content}</div></div>`
);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: {
host: document.querySelector<HTMLElement>(`#${textareaHostId}`),
content: document.querySelector<HTMLElement>('#content'),
},
}),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
});
describe('multiple assigned', () => {
test('padding viewport content', () => {
const snapshot = fillBody(
targetType,
(content, hostId) =>
`<div id="${hostId}"><div id="padding"><div id="viewport"><div id="content">${content}</div></div></div></div>`
);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: {
host: document.querySelector<HTMLElement>(`#${textareaHostId}`),
padding: document.querySelector<HTMLElement>('#padding'),
viewport: document.querySelector<HTMLElement>('#viewport'),
content: () => document.querySelector<HTMLElement>('#content'),
},
}),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
test('padding viewport', () => {
const snapshot = fillBody(
targetType,
(content, hostId) =>
`<div id="${hostId}"><div id="padding"><div id="viewport">${content}</div></div></div>`
);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: {
host: () => document.querySelector<HTMLElement>(`#${textareaHostId}`),
padding: document.querySelector<HTMLElement>('#padding'),
viewport: document.querySelector<HTMLElement>('#viewport'),
},
}),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
test('padding content', () => {
const snapshot = fillBody(
targetType,
(content, hostId) =>
`<div id="${hostId}"><div id="padding"><div id="content">${content}</div></div></div>`
);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: {
host: document.querySelector<HTMLElement>(`#${textareaHostId}`),
padding: () => document.querySelector<HTMLElement>('#padding'),
content: document.querySelector<HTMLElement>('#content'),
},
}),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
test('viewport content', () => {
const snapshot = fillBody(
targetType,
(content, hostId) =>
`<div id="${hostId}"><div id="viewport"><div id="content">${content}</div></div></div>`
);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: {
host: document.querySelector<HTMLElement>(`#${textareaHostId}`),
viewport: document.querySelector<HTMLElement>('#viewport'),
content: () => document.querySelector<HTMLElement>('#content'),
},
}),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
});
describe('single false', () => {
test('padding', () => {
const snapshot = fillBody(targetType);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: {
padding: false,
},
}),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
test('content', () => {
const snapshot = fillBody(targetType);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: {
content: () => false,
},
}),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
});
describe('single true', () => {
test('padding', () => {
const snapshot = fillBody(targetType);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: {
padding: () => true,
},
}),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
test('content', () => {
const snapshot = fillBody(targetType);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: {
content: true,
},
}),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
});
describe('multiple false', () => {
test('padding & content', () => {
const snapshot = fillBody(targetType);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: {
padding: false,
content: false,
},
}),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
});
describe('multiple true', () => {
test('padding & content', () => {
const snapshot = fillBody(targetType);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: {
padding: true,
content: true,
},
}),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
});
describe('mixed', () => {
test('false: padding & content | assigned: viewport', () => {
const snapshot = fillBody(
targetType,
(content, hostId) =>
`<div id="${hostId}"><div id="viewport">${content}</div></div>`
);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: {
host: document.querySelector<HTMLElement>(`#${textareaHostId}`),
padding: false,
viewport: document.querySelector<HTMLElement>('#viewport'),
content: false,
},
}),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
test('true: padding & content | assigned: viewport', () => {
const snapshot = fillBody(
targetType,
(content, hostId) =>
`<div id="${hostId}"><div id="viewport">${content}</div></div>`
);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: {
host: document.querySelector<HTMLElement>(`#${textareaHostId}`),
padding: true,
viewport: document.querySelector<HTMLElement>('#viewport'),
content: true,
},
}),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
test('true: content | false: padding | assigned: viewport', () => {
const snapshot = fillBody(
targetType,
(content, hostId) =>
`<div id="${hostId}"><div id="viewport">${content}</div></div>`
);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: {
host: document.querySelector<HTMLElement>(`#${textareaHostId}`),
padding: () => false,
viewport: document.querySelector<HTMLElement>('#viewport'),
content: () => true,
},
}),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
test('true: padding | false: content | assigned: viewport', () => {
const snapshot = fillBody(
targetType,
(content, hostId) =>
`<div id="${hostId}"><div id="viewport">${content}</div></div>`
);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: {
host: document.querySelector<HTMLElement>(`#${textareaHostId}`),
padding: true,
viewport: () => document.querySelector<HTMLElement>('#viewport'),
content: false,
},
}),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
test('false: padding | assigned: content', () => {
const snapshot = fillBody(
targetType,
(content, hostId) =>
`<div id="${hostId}"><div id="content">${content}</div></div>`
);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: {
host: document.querySelector<HTMLElement>(`#${textareaHostId}`),
padding: false,
content: document.querySelector<HTMLElement>('#content'),
},
}),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
test('true: padding | assigned: content', () => {
const snapshot = fillBody(
targetType,
(content, hostId) =>
`<div id="${hostId}"><div id="content">${content}</div></div>`
);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: {
host: document.querySelector<HTMLElement>(`#${textareaHostId}`),
padding: true,
content: document.querySelector<HTMLElement>('#content'),
},
}),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
test('false: padding | assigned: viewport', () => {
const snapshot = fillBody(
targetType,
(content, hostId) =>
`<div id="${hostId}"><div id="viewport">${content}</div></div>`
);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: {
host: document.querySelector<HTMLElement>(`#${textareaHostId}`),
padding: () => false,
viewport: document.querySelector<HTMLElement>('#viewport'),
},
}),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
test('true: padding | assigned: viewport', () => {
const snapshot = fillBody(
targetType,
(content, hostId) =>
`<div id="${hostId}"><div id="viewport">${content}</div></div>`
);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: {
host: document.querySelector<HTMLElement>(`#${textareaHostId}`),
padding: true,
viewport: document.querySelector<HTMLElement>('#viewport'),
},
}),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
test('false: padding | assigned: viewport & content', () => {
const snapshot = fillBody(
targetType,
(content, hostId) =>
`<div id="${hostId}"><div id="viewport"><div id="content">${content}</div></div></div>`
);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: {
host: document.querySelector<HTMLElement>(`#${textareaHostId}`),
viewport: document.querySelector<HTMLElement>('#viewport'),
padding: false,
content: () => document.querySelector<HTMLElement>('#content'),
},
}),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
test('true: padding | assigned: viewport & content', () => {
const snapshot = fillBody(
targetType,
(content, hostId) =>
`<div id="${hostId}"><div id="viewport"><div id="content">${content}</div></div></div>`
);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: {
host: document.querySelector<HTMLElement>(`#${textareaHostId}`),
viewport: () => document.querySelector<HTMLElement>('#viewport'),
padding: true,
content: document.querySelector<HTMLElement>('#content'),
},
}),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
test('false: content | assigned: padding', () => {
const snapshot = fillBody(
targetType,
(content, hostId) =>
`<div id="${hostId}"><div id="padding">${content}</div></div>`
);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: {
host: document.querySelector<HTMLElement>(`#${textareaHostId}`),
padding: document.querySelector<HTMLElement>('#padding'),
content: false,
},
}),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
test('true: content | assigned: padding', () => {
const snapshot = fillBody(
targetType,
(content, hostId) =>
`<div id="${hostId}"><div id="padding">${content}</div></div>`
);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: {
host: () => document.querySelector<HTMLElement>(`#${textareaHostId}`),
padding: document.querySelector<HTMLElement>('#padding'),
content: true,
},
}),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
test('false: content | assigned: viewport', () => {
const snapshot = fillBody(
targetType,
(content, hostId) =>
`<div id="${hostId}"><div id="viewport">${content}</div></div>`
);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: {
host: document.querySelector<HTMLElement>(`#${textareaHostId}`),
viewport: () => document.querySelector<HTMLElement>('#viewport'),
content: false,
},
}),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
test('true: content | assigned: viewport', () => {
const snapshot = fillBody(
targetType,
(content, hostId) =>
`<div id="${hostId}"><div id="viewport">${content}</div></div>`
);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: {
host: document.querySelector<HTMLElement>(`#${textareaHostId}`),
viewport: document.querySelector<HTMLElement>('#viewport'),
content: true,
},
}),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
test('false: content | assigned: padding & viewport', () => {
const snapshot = fillBody(
targetType,
(content, hostId) =>
`<div id="${hostId}"><div id="padding"><div id="viewport">${content}</div></div></div>`
);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: {
host: document.querySelector<HTMLElement>(`#${textareaHostId}`),
padding: () => document.querySelector<HTMLElement>('#padding'),
viewport: document.querySelector<HTMLElement>('#viewport'),
content: () => false,
},
}),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
test('true: content | assigned: padding & viewport', () => {
const snapshot = fillBody(
targetType,
(content, hostId) =>
`<div id="${hostId}"><div id="padding"><div id="viewport">${content}</div></div></div>`
);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: {
host: () => document.querySelector<HTMLElement>(`#${textareaHostId}`),
padding: document.querySelector<HTMLElement>('#padding'),
viewport: () => document.querySelector<HTMLElement>('#viewport'),
content: true,
},
}),
currEnv
);
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
});
});
describe('viewport is content', () => {
[
{
testName: 'HTMLElement',
getInitializationElements: () => ({
host: () => document.querySelector<HTMLElement>(`#${textareaHostId}`),
viewport: document.querySelector<HTMLElement>('#viewportOrContent'),
content: document.querySelector<HTMLElement>('#viewportOrContent'),
}),
},
{
testName: 'function',
getInitializationElements: () => ({
host: document.querySelector<HTMLElement>(`#${textareaHostId}`),
viewport: () => document.querySelector<HTMLElement>('#viewportOrContent'),
content: () => document.querySelector<HTMLElement>('#viewportOrContent'),
}),
},
{
testName: 'mixed',
getInitializationElements: () => ({
host: () => document.querySelector<HTMLElement>(`#${textareaHostId}`),
viewport: document.querySelector<HTMLElement>('#viewportOrContent'),
content: () => document.querySelector<HTMLElement>('#viewportOrContent'),
}),
},
].forEach(({ testName, getInitializationElements }) => {
test(testName, () => {
const snapshot = fillBody(
targetType,
(content, hostId) =>
`<div id="${hostId}"><div id="viewportOrContent">${content}</div></div>`
);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: getInitializationElements(),
}),
currEnv
);
if (!elements._viewportIsTarget) {
expect(elements._viewportIsContent).toBe(true);
}
if (elements._viewportIsContent) {
expect(getElements(targetType).children!.parentElement).toBe(
document.querySelector(`#viewportOrContent`)
);
}
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
});
});
// textarea can't ever be the viewport
if (targetType !== 'textarea') {
describe('viewport is target', () => {
[
{
testName: 'HTMLElement',
getInitializationElements: (target: HTMLElement) => ({
host: () => document.querySelector<HTMLElement>(`#${textareaHostId}`),
viewport: target,
}),
},
{
testName: 'function',
getInitializationElements: (target: HTMLElement) => ({
host: document.querySelector<HTMLElement>(`#${textareaHostId}`),
viewport: () => target,
}),
},
].forEach(({ testName, getInitializationElements }) => {
test(testName, () => {
const snapshot = fillBody(
targetType,
(content, hostId) => `<div id="${hostId}">${content}</div>`
);
const [elements, destroy] = assertCorrectSetupElements(
targetType,
createStructureSetupElementsProxy({
target: getTarget(targetType),
elements: getInitializationElements(getTarget(targetType)),
}),
currEnv
);
expect(elements._viewportIsTarget).toBe(true);
expect(elements._host).toBe(elements._target);
expect(elements._padding).toBeFalsy();
expect(elements._viewport).toBe(elements._target);
expect(elements._content).toBeFalsy();
assertCorrectDOMStructure(targetType, currEnv, elements);
assertCorrectDestroy(snapshot, destroy);
});
});
});
}
});
});
});
});
describe('focus', () => {
describe('shift tabindex to viewport', () => {
test('with pointerdown on body', () => {
const { elements } = createStructureSetupElementsProxy(document.body);
expect(elements._viewport.getAttribute('tabindex')).toBe('-1');
expect(document.activeElement).toBe(elements._viewport);
elements._documentElm.dispatchEvent(new Event('pointerdown'));
expect(elements._viewport.getAttribute('tabindex')).toBe(null);
});
test('with keydown on element', () => {
document.body.innerHTML = '<div tabindex="123"></div>';
const target = document.body.firstElementChild as HTMLElement;
target.focus();
const { elements } = createStructureSetupElementsProxy(target, { tabindex: true });
expect(elements._viewport.getAttribute('tabindex')).toBe('-1');
expect(document.activeElement).toBe(elements._viewport);
elements._documentElm.dispatchEvent(new Event('keydown'));
expect(elements._viewport.getAttribute('tabindex')).toBe('123');
});
});
test('shift tabindex back to original activeElement', () => {
document.body.innerHTML = '<input type="text" value="hi"></input>';
const input = document.querySelector('input') as HTMLInputElement;
const target = document.body;
input.focus();
const preInitFocus = document.activeElement;
const { elements } = createStructureSetupElementsProxy(target);
expect(preInitFocus).toBe(document.activeElement);
elements._documentElm.dispatchEvent(new Event('pointerdown'));
elements._documentElm.dispatchEvent(new Event('keydown'));
expect(preInitFocus).toBe(document.activeElement);
});
});
describe('data-overlayscrollbars attribute', () => {
test('already set data-overlayscrollbars attribute is removed', () => {
const target = document.body;
target.setAttribute(dataAttributeHost, '');
const { destroy } = createStructureSetupElementsProxy(target);
destroy();
expect(document.body.getAttribute(dataAttributeHost)).toBe(null);
});
test('already set data-overlayscrollbars attribute is removed even if initialization gets canceled', () => {
const target = document.body;
target.setAttribute(dataAttributeHost, '');
const { destroy } = createStructureSetupElementsProxy(target, { autoAppend: false });
destroy();
expect(document.body.getAttribute(dataAttributeHost)).toBe(null);
});
test('already set data-overlayscrollbars attribute on html element is removed if target is body', () => {
document.documentElement.setAttribute(dataAttributeHost, '');
const { destroy } = createStructureSetupElementsProxy(document.body);
destroy();
expect(document.documentElement.getAttribute(dataAttributeHost)).toBe(null);
});
});
});