Skip to content

Commit

Permalink
feat: Add new replace util function
Browse files Browse the repository at this point in the history
  • Loading branch information
maxmilton committed Dec 1, 2024
1 parent bc7cbb7 commit 6aa9b34
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 4 deletions.
12 changes: 9 additions & 3 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,21 @@ export const create = <K extends keyof HTMLElementTagNameMap>(
tagName: K,
): HTMLElementTagNameMap[K] => document.createElement(tagName);
export const clone = <T extends Node>(node: T): T => node.cloneNode(true) as T;
/** Append a node to the end of the parent element. */
/** Append a node to the end of the parent node. */
export const append = <T extends Node>(node: T, parent: Node): T =>
parent.appendChild(node);
/** Prepend a node to the beginning of the parent element. */
/** Prepend a node to the beginning of the parent node. */
export const prepend = <T extends Node>(node: T, parent: Node): T =>
parent.insertBefore(node, parent.firstChild);
/** Insert a node after the target element. Target must have a parent element! */
/** Insert a node after the target node. Target must have a parent node! */
export const insert = <T extends Node>(node: T, target: Node): T =>
target.parentNode!.insertBefore(node, target.nextSibling);
/** Replace a target node with a new node. Target must have a parent node! */
export const replace = <T extends Node>(node: T, target: Node): T =>
(
// biome-ignore lint/style/noCommaOperator: save bytes on return statement
target.parentNode!.replaceChild(node, target), node // eslint-disable-line no-sequences
);

/**
* Runs callback function when a specified node is removed from the DOM.
Expand Down
2 changes: 2 additions & 0 deletions test/unit/exports.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ describe('browser', () => {
['append', Function],
['prepend', Function],
['insert', Function],
['replace', Function],
['onRemove', Function],
['store', Function],
] as const;
Expand Down Expand Up @@ -64,6 +65,7 @@ describe('index', () => {
['append', Function],
['prepend', Function],
['insert', Function],
['replace', Function],
['onRemove', Function],
['store', Function],
] as const;
Expand Down
85 changes: 84 additions & 1 deletion test/unit/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
noop,
onRemove,
prepend,
replace,
text,
} from '../../src/utils';

Expand Down Expand Up @@ -394,7 +395,7 @@ describe('insert', () => {
expect.assertions(1);
const target = ul.cloneNode() as HTMLUListElement;
const node = liA.cloneNode() as HTMLLIElement;
expect(() => insert(node, target)).toThrow(window.DOMException);
expect(() => insert(node, target)).toThrow(window.TypeError);
});

test('inserts child element after target element', () => {
Expand All @@ -408,6 +409,88 @@ describe('insert', () => {
'<ul><li class="a"></li><li class="c"></li><li class="b"></li></ul>',
);
});

// FIXME: Check DOM node is moved to new parent and is in fact the same node + removed from old parent.
// test('moves existing element to new parent', () => {
// expect.assertions(1);
// const root = ul.cloneNode() as HTMLUListElement;
// const target = liA.cloneNode() as HTMLLIElement;
// root.appendChild(target);
// const newParent = ul.cloneNode() as HTMLUListElement;
// newParent.appendChild(target);
// insert(liB.cloneNode(), target);
// insert(liC.cloneNode(), target);
// expect(root.outerHTML).toBe('<ul><li class="b"></li><li class="c"></li></ul>');
// });
});

describe('replace', () => {
test('is a function', () => {
expect.assertions(2);
expect(replace).toBeFunction();
expect(replace).not.toBeClass();
});

test('expects 2 parameters', () => {
expect.assertions(1);
expect(replace).toHaveParameters(2, 0);
});

test('throws without parameters', () => {
expect.assertions(5);
// @ts-expect-error - intentional invalid parameters
expect(() => replace()).toThrow(window.TypeError);
// @ts-expect-error - intentional invalid parameters
expect(() => replace(null)).toThrow(window.TypeError);
// @ts-expect-error - intentional invalid parameters
// eslint-disable-next-line unicorn/no-useless-undefined
expect(() => replace(undefined)).toThrow(window.TypeError);
// @ts-expect-error - intentional invalid parameters
expect(() => replace(null, null)).toThrow(window.TypeError);
// @ts-expect-error - intentional invalid parameters
// eslint-disable-next-line unicorn/no-useless-undefined
expect(() => replace(undefined, undefined)).toThrow(window.TypeError);
});

test('throws when parameters are not an element', () => {
expect.assertions(NOT_DOM_NODES.length * 2);
for (const input of NOT_DOM_NODES) {
// @ts-expect-error - intentional invalid parameters
expect(() => replace(ul.cloneNode(), input)).toThrow(window.TypeError);
// @ts-expect-error - intentional invalid parameters
expect(() => replace(input, ul.cloneNode())).toThrow(window.TypeError);
}
});

test('throws when target element has no parent', () => {
expect.assertions(1);
const target = ul.cloneNode() as HTMLUListElement;
const node = liA.cloneNode() as HTMLLIElement;
expect(() => replace(node, target)).toThrow(window.TypeError);
});

test('replaces target element with child element', () => {
expect.assertions(1);
const root = ul.cloneNode() as HTMLUListElement;
const target = liA.cloneNode() as HTMLLIElement;
root.appendChild(target);
replace(liB.cloneNode(), target);
replace(liC.cloneNode(), target);
expect(root.outerHTML).toBe('<ul><li class="c"></li><li class="b"></li></ul>');
});

// FIXME: Check DOM node is moved to new parent and is in fact the same node + removed from old parent.
// test('moves existing element to new parent', () => {
// expect.assertions(1);
// const root = ul.cloneNode() as HTMLUListElement;
// const target = liA.cloneNode() as HTMLLIElement;
// root.appendChild(target);
// const newParent = ul.cloneNode() as HTMLUListElement;
// newParent.appendChild(target);
// replace(liB.cloneNode(), target);
// replace(liC.cloneNode(), target);
// expect(root.outerHTML).toBe('<ul><li class="b"></li><li class="c"></li></ul>');
// });
});

describe('onRemove', () => {
Expand Down

0 comments on commit 6aa9b34

Please sign in to comment.