Skip to content

Commit

Permalink
refactor: Have load export a function (#1869)
Browse files Browse the repository at this point in the history
Instead of the constructor.

__BREAKING CHANGES__

Low potential for breakages:
- Removed the internal `_originalRoot` property. Some methods, such as `appendTo`, can now have their roots overwritten by passing the `root` property to `CheerioAPI`.
- The `default` export now has a surrounding `Document`, without any contents.

Co-authored-by: 5saviahv <49443574+5saviahv@users.noreply.github.com>
  • Loading branch information
fb55 and 5saviahv committed May 17, 2021
1 parent b59b6b1 commit c370f4e
Show file tree
Hide file tree
Showing 14 changed files with 209 additions and 214 deletions.
2 changes: 1 addition & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ Cheerio's selector implementation is nearly identical to jQuery's, so the API is

`selector` searches within the `context` scope which searches within the `root` scope. `selector` and `context` can be a string expression, DOM Element, array of DOM elements, or cheerio object. `root` is typically the HTML document string.

This selector method is the starting point for traversing and manipulating the document. Like jQuery, it's the primary method for selecting elements in the document, but unlike jQuery it's built on top of the CSSSelect library, which implements most of the Sizzle selectors.
This selector method is the starting point for traversing and manipulating the document. Like jQuery, it's the primary method for selecting elements in the document.

```js
$('.apple', '#fruits').text();
Expand Down
5 changes: 3 additions & 2 deletions src/__tests__/deprecated.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ describe('deprecated APIs', () => {

// #1674 - merge, wont accept Cheerio object
it('should be a able merge array and cheerio object', () => {
const ret = cheerio.merge(new cheerio(), ['elem1', 'elem2'] as any);
const ret = cheerio.merge(cheerio(), ['elem1', 'elem2'] as any);
expect(typeof ret).toBe('object');
expect(ret).toHaveLength(2);
});
Expand Down Expand Up @@ -202,7 +202,8 @@ describe('deprecated APIs', () => {
describe('.root', () => {
it('returns an empty selection', () => {
const $empty = cheerio.root();
expect($empty).toHaveLength(0);
expect($empty).toHaveLength(1);
expect($empty[0].children).toHaveLength(0);
});
});
});
Expand Down
2 changes: 1 addition & 1 deletion src/api/forms.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import cheerio from '../../src';
import type { CheerioAPI } from '../cheerio';
import type { CheerioAPI } from '../load';
import { forms } from '../__fixtures__/fixtures';

describe('$(...)', () => {
Expand Down
7 changes: 3 additions & 4 deletions src/api/forms.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Node } from 'domhandler';
import type { Cheerio, CheerioAPI } from '../cheerio';
import type { Cheerio } from '../cheerio';
import { isTag } from '../utils';

/*
Expand Down Expand Up @@ -54,9 +54,8 @@ export function serializeArray<T extends Node>(
this: Cheerio<T>
): SerializedField[] {
// Resolve all form elements from either forms or collections of form elements
const Cheerio = this.constructor as CheerioAPI;
return this.map((_, elem) => {
const $elem = Cheerio(elem);
const $elem = this._make(elem);
if (isTag(elem) && elem.name === 'form') {
return $elem.find(submittableSelector).toArray();
}
Expand All @@ -72,7 +71,7 @@ export function serializeArray<T extends Node>(
// Convert each of the elements to its value(s)
)
.map<Node, SerializedField>((_, elem) => {
const $elem = Cheerio(elem);
const $elem = this._make(elem);
const name = $elem.attr('name') as string; // We have filtered for elements with a name before.
// If there is no value set (e.g. `undefined`, `null`), then default value to empty
const value = $elem.val() ?? '';
Expand Down
2 changes: 1 addition & 1 deletion src/api/manipulation.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { load } from '../../src';
import type { CheerioAPI, Cheerio } from '../cheerio';
import type { CheerioAPI, Cheerio } from '..';
import { fruits, divcontainers, mixedText } from '../__fixtures__/fixtures';
import type { Node, Element } from 'domhandler';

Expand Down
12 changes: 4 additions & 8 deletions src/api/manipulation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,7 @@ export function appendTo<T extends Node>(
this: Cheerio<T>,
target: BasicAcceptedElems<Node>
): Cheerio<T> {
const appendTarget = isCheerio(target)
? target
: this._make(target, null, this._originalRoot);
const appendTarget = isCheerio(target) ? target : this._make(target);

appendTarget.append(this);

Expand Down Expand Up @@ -202,9 +200,7 @@ export function prependTo<T extends Node>(
this: Cheerio<T>,
target: BasicAcceptedElems<Node>
): Cheerio<T> {
const prependTarget = isCheerio(target)
? target
: this._make(target, null, this._originalRoot);
const prependTarget = isCheerio(target) ? target : this._make(target);

prependTarget.prepend(this);

Expand Down Expand Up @@ -642,7 +638,7 @@ export function insertAfter<T extends Node>(
target: BasicAcceptedElems<Node>
): Cheerio<T> {
if (typeof target === 'string') {
target = this._make<Node>(target, null, this._originalRoot);
target = this._make<Node>(target);
}

this.remove();
Expand Down Expand Up @@ -755,7 +751,7 @@ export function insertBefore<T extends Node>(
this: Cheerio<T>,
target: BasicAcceptedElems<Node>
): Cheerio<T> {
const targetArr = this._make<Node>(target, null, this._originalRoot);
const targetArr = this._make<Node>(target);

this.remove();

Expand Down
12 changes: 10 additions & 2 deletions src/api/traversing.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import cheerio from '../../src';
import type { CheerioAPI, Cheerio } from '../cheerio';
import { Cheerio } from '../cheerio';
import type { CheerioAPI } from '../load';
import { Node, Element, isText } from 'domhandler';
import {
food,
Expand Down Expand Up @@ -689,7 +690,7 @@ describe('$(...)', () => {
it('() : should return an empty array', () => {
const result = $('.orange').closest();
expect(result).toHaveLength(0);
expect(result).toBeInstanceOf(cheerio);
expect(result).toBeInstanceOf(Cheerio);
});

it('(selector) : should find the closest element that matches the selector, searching through its ancestors and itself', () => {
Expand Down Expand Up @@ -1237,6 +1238,13 @@ describe('$(...)', () => {
expect($selection[0]).toBe($fruits[0]);
expect($selection[1]).toBe($orange[0]);
});
it('is root object preserved', () => {
const $selection = $('<div></div>').add('#fruits');

expect($selection).toHaveLength(2);
expect($selection.eq(0).is('div')).toBe(true);
expect($selection.eq(1).is($fruits.eq(0))).toBe(true);
});
});
describe('(selector) matched elements :', () => {
it('occur before the current selection', () => {
Expand Down
20 changes: 18 additions & 2 deletions src/api/traversing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Node, Element, hasChildren } from 'domhandler';
import type { Cheerio } from '../cheerio';
import * as select from 'cheerio-select';
import { domEach, isTag, isCheerio } from '../utils';
import { contains } from '../static';
import { DomUtils } from 'htmlparser2';
import type { FilterFunction, AcceptedFilters } from '../types';
const { uniqueSort } = DomUtils;
Expand Down Expand Up @@ -42,7 +43,6 @@ export function find<T extends Node>(
const context: Node[] = this.toArray();

if (typeof selectorOrHaystack !== 'string') {
const { contains } = this.constructor as typeof Cheerio;
const haystack = isCheerio(selectorOrHaystack)
? selectorOrHaystack.get()
: [selectorOrHaystack];
Expand Down Expand Up @@ -941,11 +941,27 @@ export function get<T>(this: Cheerio<T>, i: number): T;
export function get<T>(this: Cheerio<T>): T[];
export function get<T>(this: Cheerio<T>, i?: number): T | T[] {
if (i == null) {
return Array.prototype.slice.call(this);
return this.toArray();
}
return this[i < 0 ? this.length + i : i];
}

/**
* Retrieve all the DOM elements contained in the jQuery set as an array.
*
* @example
*
* ```js
* $('li').toArray();
* //=> [ {...}, {...}, {...} ]
* ```
*
* @returns The contained items.
*/
export function toArray<T>(this: Cheerio<T>): T[] {
return Array.prototype.slice.call(this);
}

/**
* Search for a given element from among the matched elements.
*
Expand Down
14 changes: 9 additions & 5 deletions src/cheerio.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,11 +215,6 @@ describe('cheerio', () => {
expect($elem.eq(1).attr('class')).toBe('orange');
});

it('should gracefully degrade on complex, unmatched queries', () => {
const $elem = cheerio('Eastern States Cup #8-fin&nbsp;<1br>Downhill&nbsp;');
expect($elem).toHaveLength(0);
});

it('(extended Array) should not interfere with prototype methods (issue #119)', () => {
const extended: any = [];
extended.find = extended.children = extended.each = function () {
Expand Down Expand Up @@ -275,6 +270,15 @@ describe('cheerio', () => {
expect(lis).toHaveLength(3);
});

it('should preserve root content', () => {
const $ = cheerio.load(fruits);
// Root should not be overwritten
const el = $('<div></div>');
expect(Object.is(el, el._root)).toBe(false);
// Query has to have results
expect($('li', 'ul')).toHaveLength(3);
});

it('should allow loading a pre-parsed DOM', () => {
const dom = parseDOM(food);
const $ = cheerio.load(dom);
Expand Down

0 comments on commit c370f4e

Please sign in to comment.