'));
+
+ const $apple = $fruits.eq(0);
+ const $orange = $fruits.eq(1);
+ const $pear = $fruits.eq(2);
+
+ expect($apple.find('.second')[0]).toBe($apple.contents()[0]);
+ expect($orange.find('.second')[0]).toBe($orange.contents()[0]);
+ expect($pear.find('.second')[0]).toBe($pear.contents()[0]);
+ });
+
+ it('(fn) : should add returned Node as first child', () => {
+ const $fruits = $('#fruits').children();
+
+ $fruits.prepend(() => $('
')[0]);
+
+ const $apple = $fruits.eq(0);
+ const $orange = $fruits.eq(1);
+ const $pear = $fruits.eq(2);
+
+ expect($apple.find('.third')[0]).toBe($apple.contents()[0]);
+ expect($orange.find('.third')[0]).toBe($orange.contents()[0]);
+ expect($pear.find('.third')[0]).toBe($pear.contents()[0]);
+ });
+
+ it('($(...)) : should remove from root element', () => {
+ const $plum = $('
Plum');
+ const root = $plum[0].parent;
+ expect(root?.type).toBe('root');
+
+ $fruits.prepend($plum);
+ expect($plum[0].parent?.type).not.toBe('root');
+ expect(root?.childNodes).not.toContain($plum[0]);
+ });
+ });
+
+ describe('.appendTo', () => {
+ it('(html) : should add element as last child', () => {
+ const $plum = $('
Plum').appendTo(fruits);
+ expect($plum.parent().children().eq(3).hasClass('plum')).toBe(true);
+ });
+
+ it('($(...)) : should add element as last child', () => {
+ $('
Plum').appendTo($fruits);
+ expect($fruits.children().eq(3).hasClass('plum')).toBe(true);
+ });
+
+ it('(Node) : should add element as last child', () => {
+ $('
Plum').appendTo($fruits[0]);
+ expect($fruits.children().eq(3).hasClass('plum')).toBe(true);
+ });
+
+ it('(selector) : should add element as last child', () => {
+ $('
Plum').appendTo('#fruits');
+ expect($fruits.children().eq(3).hasClass('plum')).toBe(true);
+ });
+
+ it('(Array) : should add element as last child of all elements in the array', () => {
+ const $multiple = $('
');
+ $('
Plum').appendTo($multiple.get());
+ expect($multiple.first().children().eq(1).hasClass('plum')).toBe(true);
+ expect($multiple.last().children().eq(1).hasClass('plum')).toBe(true);
+ });
+ });
+
+ describe('.prependTo', () => {
+ it('(html) : should add element as first child', () => {
+ const $plum = $('
Plum').prependTo(fruits);
+ expect($plum.parent().children().eq(0).hasClass('plum')).toBe(true);
+ });
+
+ it('($(...)) : should add element as first child', () => {
+ $('
Plum').prependTo($fruits);
+ expect($fruits.children().eq(0).hasClass('plum')).toBe(true);
+ });
+
+ it('(Node) : should add node as first child', () => {
+ $('
Plum').prependTo($fruits[0]);
+ expect($fruits.children().eq(0).hasClass('plum')).toBe(true);
+ });
+
+ it('(selector) : should add element as first child', () => {
+ $('
Plum').prependTo('#fruits');
+ expect($fruits.children().eq(0).hasClass('plum')).toBe(true);
+ });
+
+ it('(Array) : should add element as first child of all elements in the array', () => {
+ const $multiple = $('
');
+ $('
Plum').prependTo($multiple.get());
+ expect($multiple.first().children().eq(0).hasClass('plum')).toBe(true);
+ expect($multiple.last().children().eq(0).hasClass('plum')).toBe(true);
+ });
+ });
+
+ describe('.after', () => {
+ it('() : should do nothing', () => {
+ expect($fruits.after()[0].tagName).toBe('ul');
+ });
+
+ it('(html) : should add element as next sibling', () => {
+ const grape = '
Grape';
+ $('.apple').after(grape);
+ expect($('.apple').next().hasClass('grape')).toBe(true);
+ });
+
+ it('(Array) : should add all elements in the array as next sibling', () => {
+ const more = $(
+ '
PlumGrape',
+ ).get();
+ $('.apple').after(more);
+ expect($fruits.children().eq(1).hasClass('plum')).toBe(true);
+ expect($fruits.children().eq(2).hasClass('grape')).toBe(true);
+ });
+
+ it('($(...)) : should add element as next sibling', () => {
+ const $plum = $('
Plum');
+ $('.apple').after($plum);
+ expect($('.apple').next().hasClass('plum')).toBe(true);
+ });
+
+ it('(Node) : should add element as next sibling', () => {
+ const plum = $('
Plum')[0];
+ $('.apple').after(plum);
+ expect($('.apple').next().hasClass('plum')).toBe(true);
+ });
+
+ it('(existing Node) : should remove existing nodes from previous locations', () => {
+ const pear = $fruits.children()[2];
+
+ $('.apple').after(pear);
+
+ const $children = $fruits.children();
+ expect($children).toHaveLength(3);
+ expect($children[1]).toBe(pear);
+ });
+
+ it('(existing Node) : should update original direct siblings', () => {
+ $('.pear').after($('.orange'));
+ expect($('.apple').next()[0]).toBe($('.pear')[0]);
+ expect($('.pear').prev()[0]).toBe($('.apple')[0]);
+ });
+
+ it('(existing Node) : should clone all but the last occurrence', () => {
+ const $originalApple = $('.apple');
+ $('.orange, .pear').after($originalApple);
+
+ expect($('.apple')).toHaveLength(2);
+ expect($('.apple').eq(0).prev()[0]).toBe($('.orange')[0]);
+ expect($('.apple').eq(0).next()[0]).toBe($('.pear')[0]);
+ expect($('.apple').eq(1).prev()[0]).toBe($('.pear')[0]);
+ expect($('.apple').eq(1).next()).toHaveLength(0);
+ expect($('.apple')[0]).not.toStrictEqual($originalApple[0]);
+ expect($('.apple')[1]).toStrictEqual($originalApple[0]);
+ });
+
+ it('(elem) : should handle if removed', () => {
+ const $apple = $('.apple');
+ const $plum = $('
Plum');
+
+ $apple.remove();
+ $apple.after($plum);
+ expect($plum.prev()).toHaveLength(0);
+ });
+
+ it('($(...), html) : should add multiple elements as next siblings', () => {
+ const $plum = $('
Plum');
+ const grape = '
Grape';
+ $('.apple').after($plum, grape);
+ expect($('.apple').next().hasClass('plum')).toBe(true);
+ expect($('.plum').next().hasClass('grape')).toBe(true);
+ });
+
+ it('(fn) : should invoke the callback with the correct arguments and context', () => {
+ const args: [number, string][] = [];
+ const thisValues: AnyNode[] = [];
+ const $fruits = $('#fruits').children();
+
+ $fruits.after(function (...myArgs) {
+ args.push(myArgs);
+ thisValues.push(this);
+ return this;
+ });
+
+ expect(args).toStrictEqual([
+ [0, 'Apple'],
+ [1, 'Orange'],
+ [2, 'Pear'],
+ ]);
+ expect(thisValues).toStrictEqual([$fruits[0], $fruits[1], $fruits[2]]);
+ });
+
+ it('(fn) : should add returned string as next sibling', () => {
+ const $fruits = $('#fruits').children();
+
+ $fruits.after(() => '
');
+
+ expect($('.first')[0]).toBe($('#fruits').contents()[1]);
+ expect($('.first')[1]).toBe($('#fruits').contents()[3]);
+ expect($('.first')[2]).toBe($('#fruits').contents()[5]);
+ });
+
+ it('(fn) : should add returned Cheerio object as next sibling', () => {
+ const $fruits = $('#fruits').children();
+
+ $fruits.after(() => $(''));
+
+ expect($('.second')[0]).toBe($('#fruits').contents()[1]);
+ expect($('.second')[1]).toBe($('#fruits').contents()[3]);
+ expect($('.second')[2]).toBe($('#fruits').contents()[5]);
+ });
+
+ it('(fn) : should add returned element as next sibling', () => {
+ const $fruits = $('#fruits').children();
+
+ $fruits.after(() => $('')[0]);
+
+ expect($('.third')[0]).toBe($('#fruits').contents()[1]);
+ expect($('.third')[1]).toBe($('#fruits').contents()[3]);
+ expect($('.third')[2]).toBe($('#fruits').contents()[5]);
+ });
+
+ it('(fn) : should support text nodes', () => {
+ const $text = load(mixedText);
+
+ $text($text('body')[0].children).after(
+ (_, content) => `${content}added`,
+ );
+
+ expect($text('body').html()).toBe(
+ '11addedTEXT22added',
+ );
+ });
+
+ it('($(...)) : should remove from root element', () => {
+ const $plum = $('Plum');
+ const root = $plum[0].parent;
+ expect(root?.type).toBe('root');
+
+ $fruits.after($plum);
+ expect($plum[0].parent?.type).not.toBe('root');
+ expect(root?.childNodes).not.toContain($plum[0]);
+ });
+ });
+
+ describe('.insertAfter', () => {
+ it('(selector) : should create element and add as next sibling', () => {
+ const grape = $('
Grape');
+ grape.insertAfter('.apple');
+ expect($('.apple').next().hasClass('grape')).toBe(true);
+ });
+
+ it('(selector) : should create element and add as next sibling of multiple elements', () => {
+ const grape = $('
Grape');
+ grape.insertAfter('.apple, .pear');
+ expect($('.apple').next().hasClass('grape')).toBe(true);
+ expect($('.pear').next().hasClass('grape')).toBe(true);
+ });
+
+ it('($(...)) : should create element and add as next sibling', () => {
+ const grape = $('
Grape');
+ grape.insertAfter($('.apple'));
+ expect($('.apple').next().hasClass('grape')).toBe(true);
+ });
+
+ it('($(...)) : should create element and add as next sibling of multiple elements', () => {
+ const grape = $('
Grape');
+ grape.insertAfter($('.apple, .pear'));
+ expect($('.apple').next().hasClass('grape')).toBe(true);
+ expect($('.pear').next().hasClass('grape')).toBe(true);
+ });
+
+ it('($(...)) : should create all elements in the array and add as next siblings', () => {
+ const more = $('
PlumGrape');
+ more.insertAfter($('.apple'));
+ expect($fruits.children().eq(0).hasClass('apple')).toBe(true);
+ expect($fruits.children().eq(1).hasClass('plum')).toBe(true);
+ expect($fruits.children().eq(2).hasClass('grape')).toBe(true);
+ });
+
+ it('(existing Node) : should remove existing nodes from previous locations', () => {
+ $('.orange').insertAfter('.pear');
+ expect($fruits.children().eq(1).hasClass('orange')).toBe(false);
+ expect($fruits.children().length).toBe(3);
+ expect($('.orange').length).toBe(1);
+ });
+
+ it('(existing Node) : should update original direct siblings', () => {
+ $('.orange').insertAfter('.pear');
+ expect($('.apple').next().hasClass('pear')).toBe(true);
+ expect($('.pear').prev().hasClass('apple')).toBe(true);
+ expect($('.pear').next().hasClass('orange')).toBe(true);
+ expect($('.orange').next()).toHaveLength(0);
+ });
+
+ it('(existing Node) : should update original direct siblings of multiple elements', () => {
+ $('.apple').insertAfter('.orange, .pear');
+ expect($('.orange').prev()).toHaveLength(0);
+ expect($('.orange').next().hasClass('apple')).toBe(true);
+ expect($('.pear').next().hasClass('apple')).toBe(true);
+ expect($('.pear').prev().hasClass('apple')).toBe(true);
+ expect($fruits.children().length).toBe(4);
+ const apples = $('.apple');
+ expect(apples.length).toBe(2);
+ expect(apples.eq(0).prev().hasClass('orange')).toBe(true);
+ expect(apples.eq(1).prev().hasClass('pear')).toBe(true);
+ });
+
+ it('(elem) : should handle if removed', () => {
+ const $apple = $('.apple');
+ const $plum = $('
Plum');
+ $apple.remove();
+ $plum.insertAfter($apple);
+ expect($plum.prev()).toHaveLength(0);
+ });
+
+ it('(single) should return the new element for chaining', () => {
+ const $grape = $('
Grape').insertAfter('.apple');
+ expect($grape.cheerio).toBeTruthy();
+ expect($grape.each).toBeTruthy();
+ expect($grape.length).toBe(1);
+ expect($grape.hasClass('grape')).toBe(true);
+ });
+
+ it('(single) should return the new elements for chaining', () => {
+ const $purple = $(
+ '
GrapePlum',
+ ).insertAfter('.apple');
+ expect($purple.cheerio).toBeTruthy();
+ expect($purple.each).toBeTruthy();
+ expect($purple.length).toBe(2);
+ expect($purple.eq(0).hasClass('grape')).toBe(true);
+ expect($purple.eq(1).hasClass('plum')).toBe(true);
+ });
+
+ it('(multiple) should return the new elements for chaining', () => {
+ const $purple = $(
+ '
GrapePlum',
+ ).insertAfter('.apple, .pear');
+ expect($purple.cheerio).toBeTruthy();
+ expect($purple.each).toBeTruthy();
+ expect($purple.length).toBe(4);
+ expect($purple.eq(0).hasClass('grape')).toBe(true);
+ expect($purple.eq(1).hasClass('plum')).toBe(true);
+ expect($purple.eq(2).hasClass('grape')).toBe(true);
+ expect($purple.eq(3).hasClass('plum')).toBe(true);
+ });
+
+ it('(single) should return the existing element for chaining', () => {
+ const $pear = $('.pear').insertAfter('.apple');
+ expect($pear.cheerio).toBeTruthy();
+ expect($pear.each).toBeTruthy();
+ expect($pear.length).toBe(1);
+ expect($pear.hasClass('pear')).toBe(true);
+ });
+
+ it('(single) should return the existing elements for chaining', () => {
+ const $things = $('.orange, .apple').insertAfter('.pear');
+ expect($things.cheerio).toBeTruthy();
+ expect($things.each).toBeTruthy();
+ expect($things.length).toBe(2);
+ expect($things.eq(0).hasClass('apple')).toBe(true);
+ expect($things.eq(1).hasClass('orange')).toBe(true);
+ });
+
+ it('(multiple) should return the existing elements for chaining', () => {
+ $('
Grape').insertAfter('.apple');
+ const $things = $('.orange, .apple').insertAfter('.pear, .grape');
+ expect($things.cheerio).toBeTruthy();
+ expect($things.each).toBeTruthy();
+ expect($things.length).toBe(4);
+ expect($things.eq(0).hasClass('apple')).toBe(true);
+ expect($things.eq(1).hasClass('orange')).toBe(true);
+ expect($things.eq(2).hasClass('apple')).toBe(true);
+ expect($things.eq(3).hasClass('orange')).toBe(true);
+ });
+ });
+
+ describe('.before', () => {
+ it('() : should do nothing', () => {
+ expect($('#fruits').before()[0].tagName).toBe('ul');
+ });
+
+ it('(html) : should add element as previous sibling', () => {
+ const grape = '
Grape';
+ $('.apple').before(grape);
+ expect($('.apple').prev().hasClass('grape')).toBe(true);
+ });
+
+ it('($(...)) : should add element as previous sibling', () => {
+ const $plum = $('
Plum');
+ $('.apple').before($plum);
+ expect($('.apple').prev().hasClass('plum')).toBe(true);
+ });
+
+ it('(Node) : should add element as previous sibling', () => {
+ const plum = $('
Plum')[0];
+ $('.apple').before(plum);
+ expect($('.apple').prev().hasClass('plum')).toBe(true);
+ });
+
+ it('(existing Node) : should remove existing nodes from previous locations', () => {
+ const pear = $fruits.children()[2];
+
+ $('.apple').before(pear);
+
+ const $children = $fruits.children();
+ expect($children).toHaveLength(3);
+ expect($children[0]).toBe(pear);
+ });
+
+ it('(existing Node) : should update original direct siblings', () => {
+ $('.apple').before($('.orange'));
+ expect($('.apple').next()[0]).toBe($('.pear')[0]);
+ expect($('.pear').prev()[0]).toBe($('.apple')[0]);
+ });
+
+ it('(existing Node) : should clone all but the last occurrence', () => {
+ const $originalPear = $('.pear');
+ $('.apple, .orange').before($originalPear);
+
+ expect($('.pear')).toHaveLength(2);
+ expect($('.pear').eq(0).prev()).toHaveLength(0);
+ expect($('.pear').eq(0).next()[0]).toBe($('.apple')[0]);
+ expect($('.pear').eq(1).prev()[0]).toBe($('.apple')[0]);
+ expect($('.pear').eq(1).next()[0]).toBe($('.orange')[0]);
+ expect($('.pear')[0]).not.toStrictEqual($originalPear[0]);
+ expect($('.pear')[1]).toStrictEqual($originalPear[0]);
+ });
+
+ it('(elem) : should handle if removed', () => {
+ const $apple = $('.apple');
+ const $plum = $('
Plum');
+
+ $apple.remove();
+ $apple.before($plum);
+ expect($plum.next()).toHaveLength(0);
+ });
+
+ it('(Array) : should add all elements in the array as previous sibling', () => {
+ const more = $(
+ '
PlumGrape',
+ ).get();
+ $('.apple').before(more);
+ expect($fruits.children().eq(0).hasClass('plum')).toBe(true);
+ expect($fruits.children().eq(1).hasClass('grape')).toBe(true);
+ });
+
+ it('($(...), html) : should add multiple elements as previous siblings', () => {
+ const $plum = $('
Plum');
+ const grape = '
Grape';
+ $('.apple').before($plum, grape);
+ expect($('.apple').prev().hasClass('grape')).toBe(true);
+ expect($('.grape').prev().hasClass('plum')).toBe(true);
+ });
+
+ it('(fn) : should invoke the callback with the correct arguments and context', () => {
+ const args: [number, string][] = [];
+ const thisValues: AnyNode[] = [];
+ const $fruits = $('#fruits').children();
+
+ $fruits.before(function (...myArgs) {
+ args.push(myArgs);
+ thisValues.push(this);
+ return this;
+ });
+
+ expect(args).toStrictEqual([
+ [0, 'Apple'],
+ [1, 'Orange'],
+ [2, 'Pear'],
+ ]);
+ expect(thisValues).toStrictEqual([$fruits[0], $fruits[1], $fruits[2]]);
+ });
+
+ it('(fn) : should add returned string as previous sibling', () => {
+ const $fruits = $('#fruits').children();
+
+ $fruits.before(() => '
');
+
+ expect($('.first')[0]).toBe($('#fruits').contents()[0]);
+ expect($('.first')[1]).toBe($('#fruits').contents()[2]);
+ expect($('.first')[2]).toBe($('#fruits').contents()[4]);
+ });
+
+ it('(fn) : should add returned Cheerio object as previous sibling', () => {
+ const $fruits = $('#fruits').children();
+
+ $fruits.before(() => $(''));
+
+ expect($('.second')[0]).toBe($('#fruits').contents()[0]);
+ expect($('.second')[1]).toBe($('#fruits').contents()[2]);
+ expect($('.second')[2]).toBe($('#fruits').contents()[4]);
+ });
+
+ it('(fn) : should add returned Node as previous sibling', () => {
+ const $fruits = $('#fruits').children();
+
+ $fruits.before(() => $('')[0]);
+
+ expect($('.third')[0]).toBe($('#fruits').contents()[0]);
+ expect($('.third')[1]).toBe($('#fruits').contents()[2]);
+ expect($('.third')[2]).toBe($('#fruits').contents()[4]);
+ });
+
+ it('(fn) : should support text nodes', () => {
+ const $text = load(mixedText);
+
+ $text($text('body')[0].children).before(
+ (_, content) => `${content}added`,
+ );
+
+ expect($text('body').html()).toBe(
+ '1added1TEXT2added2',
+ );
+ });
+
+ it('($(...)) : should remove from root element', () => {
+ const $plum = $('Plum');
+ const root = $plum[0].parent;
+ expect(root?.type).toBe('root');
+
+ $fruits.before($plum);
+ expect($plum[0].parent?.type).not.toBe('root');
+ expect(root?.childNodes).not.toContain($plum[0]);
+ });
+ });
+
+ describe('.insertBefore', () => {
+ it('(selector) : should create element and add as prev sibling', () => {
+ const grape = $('
Grape');
+ grape.insertBefore('.apple');
+ expect($('.apple').prev().hasClass('grape')).toBe(true);
+ });
+
+ it('(selector) : should create element and add as prev sibling of multiple elements', () => {
+ const grape = $('
Grape');
+ grape.insertBefore('.apple, .pear');
+ expect($('.apple').prev().hasClass('grape')).toBe(true);
+ expect($('.pear').prev().hasClass('grape')).toBe(true);
+ });
+
+ it('($(...)) : should create element and add as prev sibling', () => {
+ const grape = $('
Grape');
+ grape.insertBefore($('.apple'));
+ expect($('.apple').prev().hasClass('grape')).toBe(true);
+ });
+
+ it('($(...)) : should create element and add as next sibling of multiple elements', () => {
+ const grape = $('
Grape');
+ grape.insertBefore($('.apple, .pear'));
+ expect($('.apple').prev().hasClass('grape')).toBe(true);
+ expect($('.pear').prev().hasClass('grape')).toBe(true);
+ });
+
+ it('($(...)) : should create all elements in the array and add as prev siblings', () => {
+ const more = $('
PlumGrape');
+ more.insertBefore($('.apple'));
+ expect($fruits.children().eq(0).hasClass('plum')).toBe(true);
+ expect($fruits.children().eq(1).hasClass('grape')).toBe(true);
+ expect($fruits.children().eq(2).hasClass('apple')).toBe(true);
+ });
+
+ it('(existing Node) : should remove existing nodes from previous locations', () => {
+ $('.pear').insertBefore('.apple');
+ expect($fruits.children().eq(2).hasClass('pear')).toBe(false);
+ expect($fruits.children().length).toBe(3);
+ expect($('.pear').length).toBe(1);
+ });
+
+ it('(existing Node) : should update original direct siblings', () => {
+ $('.pear').insertBefore('.apple');
+ expect($('.apple').prev().hasClass('pear')).toBe(true);
+ expect($('.apple').next().hasClass('orange')).toBe(true);
+ expect($('.pear').next().hasClass('apple')).toBe(true);
+ expect($('.pear').prev()).toHaveLength(0);
+ });
+
+ it('(existing Node) : should update original direct siblings of multiple elements', () => {
+ $('.pear').insertBefore('.apple, .orange');
+ expect($('.apple').prev().hasClass('pear')).toBe(true);
+ expect($('.apple').next().hasClass('pear')).toBe(true);
+ expect($('.orange').prev().hasClass('pear')).toBe(true);
+ expect($('.orange').next()).toHaveLength(0);
+ expect($fruits.children().length).toBe(4);
+ const pears = $('.pear');
+ expect(pears.length).toBe(2);
+ expect(pears.eq(0).next().hasClass('apple')).toBe(true);
+ expect(pears.eq(1).next().hasClass('orange')).toBe(true);
+ });
+
+ it('(elem) : should handle if removed', () => {
+ const $apple = $('.apple');
+ const $plum = $('
Plum');
+
+ $apple.remove();
+ $plum.insertBefore($apple);
+ expect($plum.next()).toHaveLength(0);
+ });
+
+ it('(single) should return the new element for chaining', () => {
+ const $grape = $('
Grape').insertBefore('.apple');
+ expect($grape.cheerio).toBeTruthy();
+ expect($grape.each).toBeTruthy();
+ expect($grape.length).toBe(1);
+ expect($grape.hasClass('grape')).toBe(true);
+ });
+
+ it('(single) should return the new elements for chaining', () => {
+ const $purple = $(
+ '
GrapePlum',
+ ).insertBefore('.apple');
+ expect($purple.cheerio).toBeTruthy();
+ expect($purple.each).toBeTruthy();
+ expect($purple.length).toBe(2);
+ expect($purple.eq(0).hasClass('grape')).toBe(true);
+ expect($purple.eq(1).hasClass('plum')).toBe(true);
+ });
+
+ it('(multiple) should return the new elements for chaining', () => {
+ const $purple = $(
+ '
GrapePlum',
+ ).insertBefore('.apple, .pear');
+ expect($purple.cheerio).toBeTruthy();
+ expect($purple.each).toBeTruthy();
+ expect($purple.length).toBe(4);
+ expect($purple.eq(0).hasClass('grape')).toBe(true);
+ expect($purple.eq(1).hasClass('plum')).toBe(true);
+ expect($purple.eq(2).hasClass('grape')).toBe(true);
+ expect($purple.eq(3).hasClass('plum')).toBe(true);
+ });
+
+ it('(single) should return the existing element for chaining', () => {
+ const $orange = $('.orange').insertBefore('.apple');
+ expect($orange.cheerio).toBeTruthy();
+ expect($orange.each).toBeTruthy();
+ expect($orange.length).toBe(1);
+ expect($orange.hasClass('orange')).toBe(true);
+ });
+
+ it('(single) should return the existing elements for chaining', () => {
+ const $things = $('.orange, .pear').insertBefore('.apple');
+ expect($things.cheerio).toBeTruthy();
+ expect($things.each).toBeTruthy();
+ expect($things.length).toBe(2);
+ expect($things.eq(0).hasClass('orange')).toBe(true);
+ expect($things.eq(1).hasClass('pear')).toBe(true);
+ });
+
+ it('(multiple) should return the existing elements for chaining', () => {
+ $('
Grape').insertBefore('.apple');
+ const $things = $('.orange, .apple').insertBefore('.pear, .grape');
+ expect($things.cheerio).toBeTruthy();
+ expect($things.each).toBeTruthy();
+ expect($things.length).toBe(4);
+ expect($things.eq(0).hasClass('apple')).toBe(true);
+ expect($things.eq(1).hasClass('orange')).toBe(true);
+ expect($things.eq(2).hasClass('apple')).toBe(true);
+ expect($things.eq(3).hasClass('orange')).toBe(true);
+ });
+ });
+
+ describe('.remove', () => {
+ it('() : should remove selected elements', () => {
+ $('.apple').remove();
+ expect($fruits.find('.apple')).toHaveLength(0);
+ });
+
+ it('() : should be reentrant', () => {
+ const $apple = $('.apple');
+ $apple.remove();
+ $apple.remove();
+ expect($fruits.find('.apple')).toHaveLength(0);
+ });
+
+ it('(selector) : should remove matching selected elements', () => {
+ $('li').remove('.apple');
+ expect($fruits.find('.apple')).toHaveLength(0);
+ });
+
+ it('($(...)) : should remove from root element', () => {
+ const $plum = $('
Plum');
+ const root = $plum[0].parent;
+ expect(root?.type).toBe('root');
+
+ $plum.remove();
+ expect($plum[0].parent).toBe(null);
+ expect(root?.childNodes).not.toContain($plum[0]);
+ });
+ });
+
+ describe('.replaceWith', () => {
+ it('(elem) : should replace one
tag with another', () => {
+ const $plum = $('Plum');
+ $('.orange').replaceWith($plum);
+ expect($('.apple').next().hasClass('plum')).toBe(true);
+ expect($('.apple').next().html()).toBe('Plum');
+ expect($('.pear').prev().hasClass('plum')).toBe(true);
+ expect($('.pear').prev().html()).toBe('Plum');
+ });
+
+ it('(Array) : should replace one
tag with the elements in the array', () => {
+ const more = $(
+ 'PlumGrape',
+ ).get();
+ $('.orange').replaceWith(more);
+
+ expect($fruits.children().eq(1).hasClass('plum')).toBe(true);
+ expect($fruits.children().eq(2).hasClass('grape')).toBe(true);
+ expect($('.apple').next().hasClass('plum')).toBe(true);
+ expect($('.pear').prev().hasClass('grape')).toBe(true);
+ expect($fruits.children()).toHaveLength(4);
+ });
+
+ it('(Node) : should replace the selected element with given node', () => {
+ const $src = $('
hi there
');
+ const $new = $('
');
+ const $replaced = $src.find('span').replaceWith($new[0]);
+ expect($new[0].parentNode).toBe($src[0]);
+ expect($replaced[0].parentNode).toBe(null);
+ expect($.html($src)).toBe('
hi
');
+ });
+
+ it('(existing element) : should remove element from its previous location', () => {
+ $('.pear').replaceWith($('.apple'));
+ expect($fruits.children()).toHaveLength(2);
+ expect($fruits.children()[0]).toBe($('.orange')[0]);
+ expect($fruits.children()[1]).toBe($('.apple')[0]);
+ });
+
+ it('(elem) : should NOP if removed', () => {
+ const $pear = $('.pear');
+ const $plum = $('
Plum');
+
+ $pear.remove();
+ $pear.replaceWith($plum);
+ expect($('.orange').next().hasClass('plum')).toBe(false);
+ });
+
+ it('(elem) : should replace the single selected element with given element', () => {
+ const $src = $('
hi there
');
+ const $new = $('
here
');
+ const $replaced = $src.find('span').replaceWith($new);
+ expect($new[0].parentNode).toBe($src[0]);
+ expect($replaced[0].parentNode).toBe(null);
+ expect($.html($src)).toBe('
hi
here
');
+ });
+
+ it('(self) : should be replaced after replacing it with itself', () => {
+ const $a = load('
foo', null, false);
+ const replacement = '
bar';
+ $a('a').replaceWith((_, el: AnyNode) => el);
+ $a('a').replaceWith(replacement);
+ expect($a.html()).toBe(replacement);
+ });
+
+ it('(str) : should accept strings', () => {
+ const $src = $('
hi there
');
+ const newStr = '
here
';
+ const $replaced = $src.find('span').replaceWith(newStr);
+ expect($replaced[0].parentNode).toBe(null);
+ expect($.html($src)).toBe('
hi
here
');
+ });
+
+ it('(str) : should replace all selected elements', () => {
+ const $src = $('
a
b
c
d');
+ const $replaced = $src.find('br').replaceWith(' ');
+ expect($replaced[0].parentNode).toBe(null);
+ expect($.html($src)).toBe('
a b c d');
+ });
+
+ it('(fn) : should invoke the callback with the correct argument and context', () => {
+ const origChildren = $fruits.children().get();
+ const args: [number, AnyNode][] = [];
+ const thisValues: AnyNode[] = [];
+
+ $fruits.children().replaceWith(function (...myArgs) {
+ args.push(myArgs);
+ thisValues.push(this);
+ return '
';
+ });
+
+ expect(args).toStrictEqual([
+ [0, origChildren[0]],
+ [1, origChildren[1]],
+ [2, origChildren[2]],
+ ]);
+ expect(thisValues).toStrictEqual([
+ origChildren[0],
+ origChildren[1],
+ origChildren[2],
+ ]);
+ });
+
+ it('(fn) : should replace the selected element with the returned string', () => {
+ $fruits.children().replaceWith(() => '');
+
+ expect($fruits.find('.first')).toHaveLength(3);
+ });
+
+ it('(fn) : should replace the selected element with the returned Cheerio object', () => {
+ $fruits.children().replaceWith(() => $(''));
+
+ expect($fruits.find('.second')).toHaveLength(3);
+ });
+
+ it('(fn) : should replace the selected element with the returned node', () => {
+ $fruits.children().replaceWith(() => $('')[0]);
+
+ expect($fruits.find('.third')).toHaveLength(3);
+ });
+
+ it('($(...)) : should remove from root element', () => {
+ const $plum = $('Plum');
+ const root = $plum[0].parent;
+ expect(root?.type).toBe('root');
+
+ $fruits.children().replaceWith($plum);
+ expect($plum[0].parent?.type).not.toBe('root');
+ expect(root?.childNodes).not.toContain($plum[0]);
+ });
+ });
+
+ describe('.empty', () => {
+ it('() : should remove all children from selected elements', () => {
+ expect($fruits.children()).toHaveLength(3);
+
+ $fruits.empty();
+ expect($fruits.children()).toHaveLength(0);
+ });
+
+ it('() : should allow element reinsertion', () => {
+ const $children = $fruits.children();
+
+ $fruits.empty();
+ expect($fruits.children()).toHaveLength(0);
+ expect($children).toHaveLength(3);
+
+ $fruits.append($('
'));
+ const $remove = $fruits.children().eq(0);
+
+ $remove.replaceWith($children);
+ expect($fruits.children()).toHaveLength(4);
+ });
+
+ it("() : should destroy children's references to the parent", () => {
+ const $children = $fruits.children();
+
+ $fruits.empty();
+
+ expect($children.eq(0).parent()).toHaveLength(0);
+ expect($children.eq(0).next()).toHaveLength(0);
+ expect($children.eq(0).prev()).toHaveLength(0);
+ expect($children.eq(1).parent()).toHaveLength(0);
+ expect($children.eq(1).next()).toHaveLength(0);
+ expect($children.eq(1).prev()).toHaveLength(0);
+ expect($children.eq(2).parent()).toHaveLength(0);
+ expect($children.eq(2).next()).toHaveLength(0);
+ expect($children.eq(2).prev()).toHaveLength(0);
+ });
+
+ it('() : should skip text nodes', () => {
+ const $text = load(mixedText);
+ const $body = $text($text('body')[0].children);
+
+ $body.empty();
+
+ expect($text('body').html()).toBe('
TEXT
');
+ });
+
+ it('() : should skip comment nodes', () => {
+ const $comment = load('
1TEXT
2');
+ const $body = $comment($comment('body')[0].children);
+
+ $body.empty();
+
+ expect($comment('body').html()).toBe('
TEXT
');
+ });
+ });
+
+ describe('.html', () => {
+ it('() : should get the innerHTML for an element', () => {
+ expect($fruits.html()).toBe(
+ [
+ '
Apple',
+ '
Orange',
+ '
Pear',
+ ].join(''),
+ );
+ });
+
+ it('() : should get innerHTML even if its just text', () => {
+ expect($('.pear', '
Pear').html()).toBe('Pear');
+ });
+
+ it('() : should return empty string if nothing inside', () => {
+ expect($('li', '
').html()).toBe('');
+ });
+
+ it('(html) : should set the html for its children', () => {
+ $fruits.html('
Durian');
+ const html = $fruits.html();
+ expect(html).toBe('
Durian');
+ });
+
+ it('(html) : should add new elements for each element in selection', () => {
+ const $fruits = $('li');
+ $fruits.html('
Durian');
+ let tested = 0;
+ $fruits.each(function () {
+ expect($(this).children().parent().get(0)).toBe(this);
+ tested++;
+ });
+ expect(tested).toBe(3);
+ });
+
+ it('(html) : should skip text nodes', () => {
+ const $text = load(mixedText);
+ const $body = $text($text('body')[0].children);
+
+ $body.html('test');
+
+ expect($text('body').html()).toBe('
testTEXT
test');
+ });
+
+ it('(elem) : should set the html for its children with element', () => {
+ $fruits.html($('
Durian'));
+ const html = $fruits.html();
+ expect(html).toBe('
Durian');
+ });
+
+ it('(elem) : should move the passed element (#940)', () => {
+ $('.apple').html($('.orange'));
+ expect($fruits.html()).toBe(
+ '
OrangePear',
+ );
+ });
+
+ it('() : should allow element reinsertion', () => {
+ const $children = $fruits.children();
+
+ $fruits.html('
');
+ expect($fruits.children()).toHaveLength(2);
+
+ const $remove = $fruits.children().eq(0);
+
+ $remove.replaceWith($children);
+ expect($fruits.children()).toHaveLength(4);
+ });
+
+ it('(script value) : should add content as text', () => {
+ const $data = '
';
+ const $script = $(' blah');
+ expect($apple[0].childNodes[0]).toHaveProperty(
+ 'data',
+ 'blah blah',
+ );
+ expect($apple.text()).toBe('blah blah');
+
+ $apple.text('blah blah');
+ expect($apple.html()).not.toContain('');
+ });
+ });
+
+ describe('.clone', () => {
+ it('() : should return a copy', () => {
+ const $src = $(
+ 'foobarbaz
',
+ ).children();
+ const $elem = $src.clone();
+ expect($elem.length).toBe(3);
+ expect($elem.parent()).toHaveLength(0);
+ expect($elem.text()).toBe($src.text());
+ $src.text('rofl');
+ expect($elem.text()).not.toBe($src.text());
+ });
+
+ it('() : should return a copy of document', () => {
+ const $src = load('foo
bar')
+ .root()
+ .children();
+ const $elem = $src.clone();
+ expect($elem.length).toBe(1);
+ expect($elem.parent()).toHaveLength(0);
+ expect($elem.text()).toBe($src.text());
+ $src.text('rofl');
+ expect($elem.text()).not.toBe($src.text());
+ });
+
+ it('() : should preserve parsing options', () => {
+ const $ = load('π
', { xml: { decodeEntities: false } });
+ const $div = $('div');
+
+ expect($div.text()).toBe($div.clone().text());
+ });
+ });
+});
diff --git a/frontend/node_modules/cheerio/src/api/manipulation.ts b/frontend/node_modules/cheerio/src/api/manipulation.ts
new file mode 100644
index 0000000..7c76360
--- /dev/null
+++ b/frontend/node_modules/cheerio/src/api/manipulation.ts
@@ -0,0 +1,1111 @@
+/**
+ * Methods for modifying the DOM structure.
+ *
+ * @module cheerio/manipulation
+ */
+
+import {
+ isTag,
+ Text,
+ hasChildren,
+ cloneNode,
+ Document,
+ type ParentNode,
+ type AnyNode,
+ type Element,
+} from 'domhandler';
+import { update as updateDOM } from '../parse.js';
+import { text as staticText } from '../static.js';
+import { domEach, isHtml, isCheerio } from '../utils.js';
+import { removeElement } from 'domutils';
+import type { Cheerio } from '../cheerio.js';
+import type { BasicAcceptedElems, AcceptedElems } from '../types.js';
+
+/**
+ * Create an array of nodes, recursing into arrays and parsing strings if
+ * necessary.
+ *
+ * @private
+ * @category Manipulation
+ * @param elem - Elements to make an array of.
+ * @param clone - Optionally clone nodes.
+ * @returns The array of nodes.
+ */
+export function _makeDomArray(
+ this: Cheerio,
+ elem?: BasicAcceptedElems | BasicAcceptedElems[],
+ clone?: boolean,
+): AnyNode[] {
+ if (elem == null) {
+ return [];
+ }
+
+ if (typeof elem === 'string') {
+ return this._parse(elem, this.options, false, null).children.slice(0);
+ }
+
+ if ('length' in elem) {
+ if (elem.length === 1) {
+ return this._makeDomArray(elem[0], clone);
+ }
+
+ const result: AnyNode[] = [];
+
+ for (let i = 0; i < elem.length; i++) {
+ const el = elem[i];
+
+ if (typeof el === 'object') {
+ if (el == null) {
+ continue;
+ }
+
+ if (!('length' in el)) {
+ result.push(clone ? cloneNode(el, true) : el);
+ continue;
+ }
+ }
+
+ result.push(...this._makeDomArray(el, clone));
+ }
+
+ return result;
+ }
+
+ return [clone ? cloneNode(elem, true) : elem];
+}
+
+function _insert(
+ concatenator: (
+ dom: AnyNode[],
+ children: AnyNode[],
+ parent: ParentNode,
+ ) => void,
+) {
+ return function (
+ this: Cheerio,
+ ...elems:
+ | [
+ (
+ this: AnyNode,
+ i: number,
+ html: string,
+ ) => BasicAcceptedElems,
+ ]
+ | BasicAcceptedElems[]
+ ) {
+ const lastIdx = this.length - 1;
+
+ return domEach(this, (el, i) => {
+ if (!hasChildren(el)) return;
+
+ const domSrc =
+ typeof elems[0] === 'function'
+ ? elems[0].call(el, i, this._render(el.children))
+ : (elems as BasicAcceptedElems[]);
+
+ const dom = this._makeDomArray(domSrc, i < lastIdx);
+ concatenator(dom, el.children, el);
+ });
+ };
+}
+
+/**
+ * Modify an array in-place, removing some number of elements and adding new
+ * elements directly following them.
+ *
+ * @private
+ * @category Manipulation
+ * @param array - Target array to splice.
+ * @param spliceIdx - Index at which to begin changing the array.
+ * @param spliceCount - Number of elements to remove from the array.
+ * @param newElems - Elements to insert into the array.
+ * @param parent - The parent of the node.
+ * @returns The spliced array.
+ */
+function uniqueSplice(
+ array: AnyNode[],
+ spliceIdx: number,
+ spliceCount: number,
+ newElems: AnyNode[],
+ parent: ParentNode,
+): AnyNode[] {
+ const spliceArgs: Parameters = [
+ spliceIdx,
+ spliceCount,
+ ...newElems,
+ ];
+ const prev = spliceIdx === 0 ? null : array[spliceIdx - 1];
+ const next =
+ spliceIdx + spliceCount >= array.length
+ ? null
+ : array[spliceIdx + spliceCount];
+
+ /*
+ * Before splicing in new elements, ensure they do not already appear in the
+ * current array.
+ */
+ for (let idx = 0; idx < newElems.length; ++idx) {
+ const node = newElems[idx];
+ const oldParent = node.parent;
+
+ if (oldParent) {
+ const oldSiblings: AnyNode[] = oldParent.children;
+ const prevIdx = oldSiblings.indexOf(node);
+
+ if (prevIdx > -1) {
+ oldParent.children.splice(prevIdx, 1);
+ if (parent === oldParent && spliceIdx > prevIdx) {
+ spliceArgs[0]--;
+ }
+ }
+ }
+
+ node.parent = parent;
+
+ if (node.prev) {
+ node.prev.next = node.next ?? null;
+ }
+
+ if (node.next) {
+ node.next.prev = node.prev ?? null;
+ }
+
+ node.prev = idx === 0 ? prev : newElems[idx - 1];
+ node.next = idx === newElems.length - 1 ? next : newElems[idx + 1];
+ }
+
+ if (prev) {
+ prev.next = newElems[0];
+ }
+ if (next) {
+ next.prev = newElems[newElems.length - 1];
+ }
+ return array.splice(...spliceArgs);
+}
+
+/**
+ * Insert every element in the set of matched elements to the end of the target.
+ *
+ * @category Manipulation
+ * @example
+ *
+ * ```js
+ * $('Plum').appendTo('#fruits');
+ * $.html();
+ * //=>
+ * // - Apple
+ * // - Orange
+ * // - Pear
+ * // - Plum
+ * //
+ * ```
+ *
+ * @param target - Element to append elements to.
+ * @returns The instance itself.
+ * @see {@link https://api.jquery.com/appendTo/}
+ */
+export function appendTo(
+ this: Cheerio,
+ target: BasicAcceptedElems,
+): Cheerio {
+ const appendTarget = isCheerio(target) ? target : this._make(target);
+
+ appendTarget.append(this);
+
+ return this;
+}
+
+/**
+ * Insert every element in the set of matched elements to the beginning of the
+ * target.
+ *
+ * @category Manipulation
+ * @example
+ *
+ * ```js
+ * $('Plum').prependTo('#fruits');
+ * $.html();
+ * //=>
+ * // - Plum
+ * // - Apple
+ * // - Orange
+ * // - Pear
+ * //
+ * ```
+ *
+ * @param target - Element to prepend elements to.
+ * @returns The instance itself.
+ * @see {@link https://api.jquery.com/prependTo/}
+ */
+export function prependTo(
+ this: Cheerio,
+ target: BasicAcceptedElems,
+): Cheerio {
+ const prependTarget = isCheerio(target) ? target : this._make(target);
+
+ prependTarget.prepend(this);
+
+ return this;
+}
+
+/**
+ * Inserts content as the _last_ child of each of the selected elements.
+ *
+ * @category Manipulation
+ * @example
+ *
+ * ```js
+ * $('ul').append('Plum');
+ * $.html();
+ * //=>
+ * // - Apple
+ * // - Orange
+ * // - Pear
+ * // - Plum
+ * //
+ * ```
+ *
+ * @see {@link https://api.jquery.com/append/}
+ */
+export const append: (
+ this: Cheerio,
+ ...elems:
+ | [(this: AnyNode, i: number, html: string) => BasicAcceptedElems]
+ | BasicAcceptedElems[]
+) => Cheerio = _insert((dom, children, parent) => {
+ uniqueSplice(children, children.length, 0, dom, parent);
+});
+
+/**
+ * Inserts content as the _first_ child of each of the selected elements.
+ *
+ * @category Manipulation
+ * @example
+ *
+ * ```js
+ * $('ul').prepend('Plum');
+ * $.html();
+ * //=>
+ * // - Plum
+ * // - Apple
+ * // - Orange
+ * // - Pear
+ * //
+ * ```
+ *
+ * @see {@link https://api.jquery.com/prepend/}
+ */
+export const prepend: (
+ this: Cheerio,
+ ...elems:
+ | [(this: AnyNode, i: number, html: string) => BasicAcceptedElems]
+ | BasicAcceptedElems[]
+) => Cheerio = _insert((dom, children, parent) => {
+ uniqueSplice(children, 0, 0, dom, parent);
+});
+
+function _wrap(
+ insert: (
+ el: AnyNode,
+ elInsertLocation: ParentNode,
+ wrapperDom: ParentNode[],
+ ) => void,
+) {
+ return function (
+ this: Cheerio,
+ wrapper: AcceptedElems,
+ ) {
+ const lastIdx = this.length - 1;
+ const lastParent = this.parents().last();
+
+ for (let i = 0; i < this.length; i++) {
+ const el = this[i];
+
+ const wrap =
+ typeof wrapper === 'function'
+ ? wrapper.call(el, i, el)
+ : typeof wrapper === 'string' && !isHtml(wrapper)
+ ? lastParent.find(wrapper).clone()
+ : wrapper;
+
+ const [wrapperDom] = this._makeDomArray(wrap, i < lastIdx);
+
+ if (!wrapperDom || !hasChildren(wrapperDom)) continue;
+
+ let elInsertLocation = wrapperDom;
+
+ /*
+ * Find the deepest child. Only consider the first tag child of each node
+ * (ignore text); stop if no children are found.
+ */
+ let j = 0;
+
+ while (j < elInsertLocation.children.length) {
+ const child = elInsertLocation.children[j];
+ if (isTag(child)) {
+ elInsertLocation = child;
+ j = 0;
+ } else {
+ j++;
+ }
+ }
+
+ insert(el, elInsertLocation, [wrapperDom]);
+ }
+
+ return this;
+ };
+}
+
+/**
+ * The .wrap() function can take any string or object that could be passed to
+ * the $() factory function to specify a DOM structure. This structure may be
+ * nested several levels deep, but should contain only one inmost element. A
+ * copy of this structure will be wrapped around each of the elements in the set
+ * of matched elements. This method returns the original set of elements for
+ * chaining purposes.
+ *
+ * @category Manipulation
+ * @example
+ *
+ * ```js
+ * const redFruit = $('');
+ * $('.apple').wrap(redFruit);
+ *
+ * //=>
+ * //
+ * //
- Apple
+ * //
+ * // - Orange
+ * // - Plum
+ * //
+ *
+ * const healthy = $('');
+ * $('li').wrap(healthy);
+ *
+ * //=>
+ * //
+ * //
- Apple
+ * //
+ * //
+ * //
- Orange
+ * //
+ * //
+ * //
- Plum
+ * //
+ * //
+ * ```
+ *
+ * @param wrapper - The DOM structure to wrap around each element in the
+ * selection.
+ * @see {@link https://api.jquery.com/wrap/}
+ */
+export const wrap: (
+ this: Cheerio,
+ wrapper: AcceptedElems,
+) => Cheerio = _wrap((el, elInsertLocation, wrapperDom) => {
+ const { parent } = el;
+
+ if (!parent) return;
+
+ const siblings: AnyNode[] = parent.children;
+ const index = siblings.indexOf(el);
+
+ updateDOM([el], elInsertLocation);
+ /*
+ * The previous operation removed the current element from the `siblings`
+ * array, so the `dom` array can be inserted without removing any
+ * additional elements.
+ */
+ uniqueSplice(siblings, index, 0, wrapperDom, parent);
+});
+
+/**
+ * The .wrapInner() function can take any string or object that could be passed
+ * to the $() factory function to specify a DOM structure. This structure may be
+ * nested several levels deep, but should contain only one inmost element. The
+ * structure will be wrapped around the content of each of the elements in the
+ * set of matched elements.
+ *
+ * @category Manipulation
+ * @example
+ *
+ * ```js
+ * const redFruit = $('');
+ * $('.apple').wrapInner(redFruit);
+ *
+ * //=>
+ * // -
+ * //
Apple
+ * //
+ * // - Orange
+ * // - Pear
+ * //
+ *
+ * const healthy = $('');
+ * $('li').wrapInner(healthy);
+ *
+ * //=>
+ * // -
+ * //
Apple
+ * //
+ * // -
+ * //
Orange
+ * //
+ * // -
+ * //
Pear
+ * //
+ * //
+ * ```
+ *
+ * @param wrapper - The DOM structure to wrap around the content of each element
+ * in the selection.
+ * @returns The instance itself, for chaining.
+ * @see {@link https://api.jquery.com/wrapInner/}
+ */
+export const wrapInner: (
+ this: Cheerio,
+ wrapper: AcceptedElems,
+) => Cheerio = _wrap((el, elInsertLocation, wrapperDom) => {
+ if (!hasChildren(el)) return;
+ updateDOM(el.children, elInsertLocation);
+ updateDOM(wrapperDom, el);
+});
+
+/**
+ * The .unwrap() function, removes the parents of the set of matched elements
+ * from the DOM, leaving the matched elements in their place.
+ *
+ * @category Manipulation
+ * @example without selector
+ *
+ * ```js
+ * const $ = cheerio.load(
+ * '',
+ * );
+ * $('#test p').unwrap();
+ *
+ * //=>
+ * //
Hello
+ * //
World
+ * //
+ * ```
+ *
+ * @example with selector
+ *
+ * ```js
+ * const $ = cheerio.load(
+ * '',
+ * );
+ * $('#test p').unwrap('b');
+ *
+ * //=>
+ * //
Hello
+ * //
World
+ * //
+ * ```
+ *
+ * @param selector - A selector to check the parent element against. If an
+ * element's parent does not match the selector, the element won't be
+ * unwrapped.
+ * @returns The instance itself, for chaining.
+ * @see {@link https://api.jquery.com/unwrap/}
+ */
+export function unwrap(
+ this: Cheerio,
+ selector?: string,
+): Cheerio {
+ this.parent(selector)
+ .not('body')
+ .each((_, el) => {
+ this._make(el).replaceWith(el.children);
+ });
+ return this;
+}
+
+/**
+ * The .wrapAll() function can take any string or object that could be passed to
+ * the $() function to specify a DOM structure. This structure may be nested
+ * several levels deep, but should contain only one inmost element. The
+ * structure will be wrapped around all of the elements in the set of matched
+ * elements, as a single group.
+ *
+ * @category Manipulation
+ * @example With markup passed to `wrapAll`
+ *
+ * ```js
+ * const $ = cheerio.load(
+ * '',
+ * );
+ * $('.inner').wrapAll("");
+ *
+ * //=>
+ * //
+ * //
First
+ * //
Second
+ * //
+ * //
+ * ```
+ *
+ * @example With an existing cheerio instance
+ *
+ * ```js
+ * const $ = cheerio.load(
+ * 'Span 1StrongSpan 2',
+ * );
+ * const wrap = $('');
+ * $('span').wrapAll(wrap);
+ *
+ * //=>
+ * //
+ * //
+ * //
+ * // Span 1
+ * // Span 2
+ * //
+ * //
+ * //
+ * //
+ * // Strong
+ * ```
+ *
+ * @param wrapper - The DOM structure to wrap around all matched elements in the
+ * selection.
+ * @returns The instance itself.
+ * @see {@link https://api.jquery.com/wrapAll/}
+ */
+export function wrapAll(
+ this: Cheerio,
+ wrapper: AcceptedElems,
+): Cheerio {
+ const el = this[0];
+ if (el) {
+ const wrap: Cheerio = this._make(
+ typeof wrapper === 'function' ? wrapper.call(el, 0, el) : wrapper,
+ ).insertBefore(el);
+
+ // If html is given as wrapper, wrap may contain text elements
+ let elInsertLocation: Element | undefined;
+
+ for (let i = 0; i < wrap.length; i++) {
+ if (wrap[i].type === 'tag') elInsertLocation = wrap[i] as Element;
+ }
+
+ let j = 0;
+
+ /*
+ * Find the deepest child. Only consider the first tag child of each node
+ * (ignore text); stop if no children are found.
+ */
+ while (elInsertLocation && j < elInsertLocation.children.length) {
+ const child = elInsertLocation.children[j];
+ if (child.type === 'tag') {
+ elInsertLocation = child as Element;
+ j = 0;
+ } else {
+ j++;
+ }
+ }
+
+ if (elInsertLocation) this._make(elInsertLocation).append(this);
+ }
+ return this;
+}
+
+/**
+ * Insert content next to each element in the set of matched elements.
+ *
+ * @category Manipulation
+ * @example
+ *
+ * ```js
+ * $('.apple').after('Plum');
+ * $.html();
+ * //=>
+ * // - Apple
+ * // - Plum
+ * // - Orange
+ * // - Pear
+ * //
+ * ```
+ *
+ * @param elems - HTML string, DOM element, array of DOM elements or Cheerio to
+ * insert after each element in the set of matched elements.
+ * @returns The instance itself.
+ * @see {@link https://api.jquery.com/after/}
+ */
+export function after(
+ this: Cheerio,
+ ...elems:
+ | [(this: AnyNode, i: number, html: string) => BasicAcceptedElems]
+ | BasicAcceptedElems[]
+): Cheerio {
+ const lastIdx = this.length - 1;
+
+ return domEach(this, (el, i) => {
+ if (!hasChildren(el) || !el.parent) {
+ return;
+ }
+
+ const siblings: AnyNode[] = el.parent.children;
+ const index = siblings.indexOf(el);
+
+ // If not found, move on
+ /* istanbul ignore next */
+ if (index < 0) return;
+
+ const domSrc =
+ typeof elems[0] === 'function'
+ ? elems[0].call(el, i, this._render(el.children))
+ : (elems as BasicAcceptedElems[]);
+
+ const dom = this._makeDomArray(domSrc, i < lastIdx);
+
+ // Add element after `this` element
+ uniqueSplice(siblings, index + 1, 0, dom, el.parent);
+ });
+}
+
+/**
+ * Insert every element in the set of matched elements after the target.
+ *
+ * @category Manipulation
+ * @example
+ *
+ * ```js
+ * $('Plum').insertAfter('.apple');
+ * $.html();
+ * //=>
+ * // - Apple
+ * // - Plum
+ * // - Orange
+ * // - Pear
+ * //
+ * ```
+ *
+ * @param target - Element to insert elements after.
+ * @returns The set of newly inserted elements.
+ * @see {@link https://api.jquery.com/insertAfter/}
+ */
+export function insertAfter(
+ this: Cheerio,
+ target: BasicAcceptedElems,
+): Cheerio {
+ if (typeof target === 'string') {
+ target = this._make(target);
+ }
+
+ this.remove();
+
+ const clones: T[] = [];
+
+ for (const el of this._makeDomArray(target)) {
+ const clonedSelf = this.clone().toArray();
+ const { parent } = el;
+ if (!parent) {
+ continue;
+ }
+
+ const siblings: AnyNode[] = parent.children;
+ const index = siblings.indexOf(el);
+
+ // If not found, move on
+ /* istanbul ignore next */
+ if (index < 0) continue;
+
+ // Add cloned `this` element(s) after target element
+ uniqueSplice(siblings, index + 1, 0, clonedSelf, parent);
+ clones.push(...clonedSelf);
+ }
+
+ return this._make(clones);
+}
+
+/**
+ * Insert content previous to each element in the set of matched elements.
+ *
+ * @category Manipulation
+ * @example
+ *
+ * ```js
+ * $('.apple').before('Plum');
+ * $.html();
+ * //=>
+ * // - Plum
+ * // - Apple
+ * // - Orange
+ * // - Pear
+ * //
+ * ```
+ *
+ * @param elems - HTML string, DOM element, array of DOM elements or Cheerio to
+ * insert before each element in the set of matched elements.
+ * @returns The instance itself.
+ * @see {@link https://api.jquery.com/before/}
+ */
+export function before(
+ this: Cheerio,
+ ...elems:
+ | [(this: AnyNode, i: number, html: string) => BasicAcceptedElems]
+ | BasicAcceptedElems[]
+): Cheerio {
+ const lastIdx = this.length - 1;
+
+ return domEach(this, (el, i) => {
+ if (!hasChildren(el) || !el.parent) {
+ return;
+ }
+
+ const siblings: AnyNode[] = el.parent.children;
+ const index = siblings.indexOf(el);
+
+ // If not found, move on
+ /* istanbul ignore next */
+ if (index < 0) return;
+
+ const domSrc =
+ typeof elems[0] === 'function'
+ ? elems[0].call(el, i, this._render(el.children))
+ : (elems as BasicAcceptedElems[]);
+
+ const dom = this._makeDomArray(domSrc, i < lastIdx);
+
+ // Add element before `el` element
+ uniqueSplice(siblings, index, 0, dom, el.parent);
+ });
+}
+
+/**
+ * Insert every element in the set of matched elements before the target.
+ *
+ * @category Manipulation
+ * @example
+ *
+ * ```js
+ * $('Plum').insertBefore('.apple');
+ * $.html();
+ * //=>
+ * // - Plum
+ * // - Apple
+ * // - Orange
+ * // - Pear
+ * //
+ * ```
+ *
+ * @param target - Element to insert elements before.
+ * @returns The set of newly inserted elements.
+ * @see {@link https://api.jquery.com/insertBefore/}
+ */
+export function insertBefore(
+ this: Cheerio,
+ target: BasicAcceptedElems,
+): Cheerio {
+ const targetArr = this._make(target);
+
+ this.remove();
+
+ const clones: T[] = [];
+
+ domEach(targetArr, (el) => {
+ const clonedSelf = this.clone().toArray();
+ const { parent } = el;
+ if (!parent) {
+ return;
+ }
+
+ const siblings: AnyNode[] = parent.children;
+ const index = siblings.indexOf(el);
+
+ // If not found, move on
+ /* istanbul ignore next */
+ if (index < 0) return;
+
+ // Add cloned `this` element(s) after target element
+ uniqueSplice(siblings, index, 0, clonedSelf, parent);
+ clones.push(...clonedSelf);
+ });
+
+ return this._make(clones);
+}
+
+/**
+ * Removes the set of matched elements from the DOM and all their children.
+ * `selector` filters the set of matched elements to be removed.
+ *
+ * @category Manipulation
+ * @example
+ *
+ * ```js
+ * $('.pear').remove();
+ * $.html();
+ * //=>
+ * // - Apple
+ * // - Orange
+ * //
+ * ```
+ *
+ * @param selector - Optional selector for elements to remove.
+ * @returns The instance itself.
+ * @see {@link https://api.jquery.com/remove/}
+ */
+export function remove(
+ this: Cheerio,
+ selector?: string,
+): Cheerio {
+ // Filter if we have selector
+ const elems = selector ? this.filter(selector) : this;
+
+ domEach(elems, (el) => {
+ removeElement(el);
+ el.prev = el.next = el.parent = null;
+ });
+
+ return this;
+}
+
+/**
+ * Replaces matched elements with `content`.
+ *
+ * @category Manipulation
+ * @example
+ *
+ * ```js
+ * const plum = $('Plum');
+ * $('.pear').replaceWith(plum);
+ * $.html();
+ * //=>
+ * // - Apple
+ * // - Orange
+ * // - Plum
+ * //
+ * ```
+ *
+ * @param content - Replacement for matched elements.
+ * @returns The instance itself.
+ * @see {@link https://api.jquery.com/replaceWith/}
+ */
+export function replaceWith(
+ this: Cheerio,
+ content: AcceptedElems,
+): Cheerio {
+ return domEach(this, (el, i) => {
+ const { parent } = el;
+ if (!parent) {
+ return;
+ }
+
+ const siblings: AnyNode[] = parent.children;
+ const cont =
+ typeof content === 'function' ? content.call(el, i, el) : content;
+ const dom = this._makeDomArray(cont);
+
+ /*
+ * In the case that `dom` contains nodes that already exist in other
+ * structures, ensure those nodes are properly removed.
+ */
+ updateDOM(dom, null);
+
+ const index = siblings.indexOf(el);
+
+ // Completely remove old element
+ uniqueSplice(siblings, index, 1, dom, parent);
+
+ if (!dom.includes(el)) {
+ el.parent = el.prev = el.next = null;
+ }
+ });
+}
+
+/**
+ * Removes all children from each item in the selection. Text nodes and comment
+ * nodes are left as is.
+ *
+ * @category Manipulation
+ * @example
+ *
+ * ```js
+ * $('ul').empty();
+ * $.html();
+ * //=>
+ * ```
+ *
+ * @returns The instance itself.
+ * @see {@link https://api.jquery.com/empty/}
+ */
+export function empty(this: Cheerio): Cheerio {
+ return domEach(this, (el) => {
+ if (!hasChildren(el)) return;
+ for (const child of el.children) {
+ child.next = child.prev = child.parent = null;
+ }
+
+ el.children.length = 0;
+ });
+}
+
+/**
+ * Gets an HTML content string from the first selected element.
+ *
+ * @category Manipulation
+ * @example
+ *
+ * ```js
+ * $('.orange').html();
+ * //=> Orange
+ *
+ * $('#fruits').html('Mango').html();
+ * //=> Mango
+ * ```
+ *
+ * @returns The HTML content string.
+ * @see {@link https://api.jquery.com/html/}
+ */
+export function html(this: Cheerio): string | null;
+/**
+ * Replaces each selected element's content with the specified content.
+ *
+ * @category Manipulation
+ * @example
+ *
+ * ```js
+ * $('.orange').html('Mango').html();
+ * //=> Mango
+ * ```
+ *
+ * @param str - The content to replace selection's contents with.
+ * @returns The instance itself.
+ * @see {@link https://api.jquery.com/html/}
+ */
+export function html(
+ this: Cheerio,
+ str: string | Cheerio,
+): Cheerio;
+export function html(
+ this: Cheerio