Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: migrate from jest to mocha #1268

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .c8rc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"reporter": ["lcov"],
"include": ["packages/**/*.ts"],
"exclude": ["**/*.test.ts", "packages/*/dist"]
}
6 changes: 3 additions & 3 deletions .github/workflows/nodejs-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,12 @@ jobs:
- run: npm ci
- run: npm run build --if-present

- name: Run Jest
- name: Run unit tests
run: npm run unit-tests
if: matrix.node != env.NODE_COV

- name: Run Jest with coverage
run: npm run unit-tests -- --coverage
- name: Run unit tests with coverage
run: npm run unit-tests-coverage
if: matrix.node == env.NODE_COV

- name: Run Coveralls
Expand Down
7 changes: 7 additions & 0 deletions .mocharc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"require": "ts-node/register",
"loader": "ts-node/esm",
"extensions": ["ts"],
"spec": ["**/*.test.ts"],
"watch-files": ["packages/*/lib"]
}
8 changes: 8 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ export default [
'arrow-body-style': ['error', 'as-needed'],
},
},
{
files: testFiles,
languageOptions: {
globals: {
...globals.mocha,
},
},
},
{
files: ['**/*.ts'],
rules: {
Expand Down
7,516 changes: 1,759 additions & 5,757 deletions package-lock.json

Large diffs are not rendered by default.

26 changes: 6 additions & 20 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
],
"devDependencies": {
"@eslint/js": "^9.16.0",
"@types/jest": "^29.5.14",
"eslint": "^9.14.0",
"@types/mocha": "^10.0.8",
"c8": "^10.1.2",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-unicorn": "^55.0.0",
"globals": "^15.13.0",
Expand All @@ -20,6 +21,8 @@
"outdent": "^0.8.0",
"prettier": "^3.4.2",
"ts-jest": "^29.2.5",
"mocha": "^10.7.3",
"tinyspy": "^3.0.2",
"ts-node": "^10.9.2",
"typedoc": "^0.27.4",
"typescript": "^5.7.2",
Expand All @@ -36,7 +39,8 @@
"lint": "npm run lint:es && npm run lint:prettier",
"lint:es": "eslint .",
"lint:prettier": "npm run prettier -- --check",
"unit-tests": "NODE_OPTIONS=--experimental-vm-modules jest",
"unit-tests": "mocha",
"unit-tests-coverage": "c8 mocha",
"test": "npm run lint && npm run unit-tests",
"generate-feedback-tests": "node --loader ts-node/esm scripts/generate-parser-feedback-test/index.ts test/data/html5lib-tests/tree-construction/*.dat",
"bench-perf": "npm run build && node bench/perf/index.js",
Expand All @@ -55,23 +59,5 @@
"*.{md,json,yml}": [
"prettier --write"
]
},
"jest": {
"preset": "ts-jest/presets/default-esm",
"testEnvironment": "node",
"coverageProvider": "v8",
"moduleNameMapper": {
"^(parse5[^/]*)/dist/(.*?)(?:\\.js)?$": "<rootDir>/packages/$1/lib/$2",
"^(parse5[^/]*)$": "<rootDir>/packages/$1/lib/index.ts",
"^(.*)\\.js$": [
"$1",
"$1.js"
]
},
"coveragePathIgnorePatterns": [
"node_modules",
"bench",
"test"
]
}
}
2 changes: 1 addition & 1 deletion packages/parse5-parser-stream/test/location-info.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ generateLocationInfoParserTests('location-info', (input, opts) =>
);

generateTestsForEachTreeAdapter('location-info', (treeAdapter) => {
test('Regression - location info for the implicitly generated <body>, <html> and <head> (GH-44)', () => {
it('Regression - location info for the implicitly generated <body>, <html> and <head> (GH-44)', () => {
const html = '</head><div class="test"></div></body></html>';

const opts = {
Expand Down
4 changes: 2 additions & 2 deletions packages/parse5-parser-stream/test/scripting.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ generateParsingTests(
);

generateTestsForEachTreeAdapter('ParserStream', (treeAdapter) => {
test('Regression - Synchronously calling resume() leads to crash (GH-98)', (done) => {
it('Regression - Synchronously calling resume() leads to crash (GH-98)', (done) => {
const parser = new ParserStream({ treeAdapter });

parser.on('script', (_el, _docWrite, resume) => resume());
Expand All @@ -65,7 +65,7 @@ generateTestsForEachTreeAdapter('ParserStream', (treeAdapter) => {
process.nextTick(done);
});

test('Regression - Parsing loop lock causes accidental hang ups (GH-101)', () => {
it('Regression - Parsing loop lock causes accidental hang ups (GH-101)', () => {
const parser = new ParserStream({ treeAdapter });

parser.on('script', (_scriptElement, _documentWrite, resume) => process.nextTick(resume));
Expand Down
66 changes: 41 additions & 25 deletions packages/parse5/lib/parser/formatting-element-list.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as assert from 'node:assert';
import { TAG_NAMES as $, NS, getTagID } from '../common/html.js';
import { type TagToken, TokenType } from '../common/token.js';
import { FormattingElementList, EntryType } from './formatting-element-list.js';
import { type DefaultTreeAdapterMap } from '../tree-adapters/default.js';
import { FormattingElementList, EntryType, type ElementEntry } from './formatting-element-list.js';
import { generateTestsForEachTreeAdapter } from 'parse5-test-utils/utils/common.js';

function createToken(name: $): TagToken {
Expand All @@ -17,7 +18,7 @@ function createToken(name: $): TagToken {
}

generateTestsForEachTreeAdapter('FormattingElementList', (treeAdapter) => {
test('Insert marker', () => {
it('Insert marker', () => {
const list = new FormattingElementList(treeAdapter);

list.insertMarker();
Expand All @@ -29,7 +30,7 @@ generateTestsForEachTreeAdapter('FormattingElementList', (treeAdapter) => {
assert.strictEqual(list.entries[0].type, EntryType.Marker);
});

test('Push element', () => {
it('Push element', () => {
const list = new FormattingElementList(treeAdapter);
const element1Token = createToken($.DIV);
const element2Token = createToken($.P);
Expand All @@ -49,7 +50,7 @@ generateTestsForEachTreeAdapter('FormattingElementList', (treeAdapter) => {
assert.strictEqual(list.entries[0].token, element2Token);
});

test('Insert element after bookmark', () => {
it('Insert element after bookmark', () => {
const list = new FormattingElementList(treeAdapter);
const element1 = treeAdapter.createElement($.DIV, NS.HTML, []);
const element2 = treeAdapter.createElement($.P, NS.HTML, []);
Expand All @@ -65,10 +66,11 @@ generateTestsForEachTreeAdapter('FormattingElementList', (treeAdapter) => {
list.insertElementAfterBookmark(element4, createToken($.TITLE));

assert.strictEqual(list.entries.length, 4);
expect(list.entries[2]).toHaveProperty('element', element4);
assert.ok(Object.prototype.hasOwnProperty.call(list.entries[2], 'element'));
assert.equal((list.entries[2] as ElementEntry<DefaultTreeAdapterMap>).element, element4);
});

test('Push element - Noah Ark condition', () => {
it('Push element - Noah Ark condition', () => {
const list = new FormattingElementList(treeAdapter);
const token1 = createToken($.DIV);
const token2 = createToken($.DIV);
Expand All @@ -93,32 +95,46 @@ generateTestsForEachTreeAdapter('FormattingElementList', (treeAdapter) => {
list.pushElement(element1, token4);

assert.strictEqual(list.entries.length, 4);
expect(list.entries[3]).toHaveProperty('token', token1);
expect(list.entries[2]).toHaveProperty('token', token2);
expect(list.entries[1]).toHaveProperty('token', token3);
expect(list.entries[0]).toHaveProperty('token', token4);
assert.ok(Object.prototype.hasOwnProperty.call(list.entries[3], 'token'));
assert.equal((list.entries[3] as ElementEntry<DefaultTreeAdapterMap>).token, token1);
assert.ok(Object.prototype.hasOwnProperty.call(list.entries[2], 'token'));
assert.equal((list.entries[2] as ElementEntry<DefaultTreeAdapterMap>).token, token2);
assert.ok(Object.prototype.hasOwnProperty.call(list.entries[1], 'token'));
assert.equal((list.entries[1] as ElementEntry<DefaultTreeAdapterMap>).token, token3);
assert.ok(Object.prototype.hasOwnProperty.call(list.entries[0], 'token'));
assert.equal((list.entries[0] as ElementEntry<DefaultTreeAdapterMap>).token, token4);

list.pushElement(element1, token5);

assert.strictEqual(list.entries.length, 4);
expect(list.entries[3]).toHaveProperty('token', token2);
expect(list.entries[2]).toHaveProperty('token', token3);
expect(list.entries[1]).toHaveProperty('token', token4);
expect(list.entries[0]).toHaveProperty('token', token5);
assert.ok(Object.prototype.hasOwnProperty.call(list.entries[3], 'token'));
assert.equal((list.entries[3] as ElementEntry<DefaultTreeAdapterMap>).token, token2);
assert.ok(Object.prototype.hasOwnProperty.call(list.entries[2], 'token'));
assert.equal((list.entries[2] as ElementEntry<DefaultTreeAdapterMap>).token, token3);
assert.ok(Object.prototype.hasOwnProperty.call(list.entries[1], 'token'));
assert.equal((list.entries[1] as ElementEntry<DefaultTreeAdapterMap>).token, token4);
assert.ok(Object.prototype.hasOwnProperty.call(list.entries[0], 'token'));
assert.equal((list.entries[0] as ElementEntry<DefaultTreeAdapterMap>).token, token5);

list.insertMarker();
list.pushElement(element1, token6);

assert.strictEqual(list.entries.length, 6);
expect(list.entries[5]).toHaveProperty('token', token2);
expect(list.entries[4]).toHaveProperty('token', token3);
expect(list.entries[3]).toHaveProperty('token', token4);
expect(list.entries[2]).toHaveProperty('token', token5);
expect(list.entries[1]).toHaveProperty('type', EntryType.Marker);
expect(list.entries[0]).toHaveProperty('token', token6);
assert.ok(Object.prototype.hasOwnProperty.call(list.entries[5], 'token'));
assert.equal((list.entries[5] as ElementEntry<DefaultTreeAdapterMap>).token, token2);
assert.ok(Object.prototype.hasOwnProperty.call(list.entries[4], 'token'));
assert.equal((list.entries[4] as ElementEntry<DefaultTreeAdapterMap>).token, token3);
assert.ok(Object.prototype.hasOwnProperty.call(list.entries[3], 'token'));
assert.equal((list.entries[3] as ElementEntry<DefaultTreeAdapterMap>).token, token4);
assert.ok(Object.prototype.hasOwnProperty.call(list.entries[2], 'token'));
assert.equal((list.entries[2] as ElementEntry<DefaultTreeAdapterMap>).token, token5);
assert.ok(Object.prototype.hasOwnProperty.call(list.entries[1], 'type'));
assert.equal(list.entries[1].type, EntryType.Marker);
assert.ok(Object.prototype.hasOwnProperty.call(list.entries[0], 'token'));
assert.equal((list.entries[0] as ElementEntry<DefaultTreeAdapterMap>).token, token6);
});

test('Clear to the last marker', () => {
it('Clear to the last marker', () => {
const list = new FormattingElementList(treeAdapter);
const token = createToken($.DIV);

Expand Down Expand Up @@ -148,7 +164,7 @@ generateTestsForEachTreeAdapter('FormattingElementList', (treeAdapter) => {
assert.strictEqual(list.entries.length, 0);
});

test('Remove entry', () => {
it('Remove entry', () => {
const list = new FormattingElementList(treeAdapter);
const token = createToken($.DIV);

Expand All @@ -171,11 +187,11 @@ generateTestsForEachTreeAdapter('FormattingElementList', (treeAdapter) => {
assert.strictEqual(list.entries.length, 2);

for (let i = 0; i < list.entries.length; i++) {
expect(list.entries[i]).not.toHaveProperty('element', element1);
assert.notEqual((list.entries[i] as ElementEntry<DefaultTreeAdapterMap>).element, element1);
}
});

test('Get entry in scope with given tag name', () => {
it('Get entry in scope with given tag name', () => {
const list = new FormattingElementList(treeAdapter);
const token = createToken($.DIV);
const element = treeAdapter.createElement($.DIV, NS.HTML, []);
Expand All @@ -193,7 +209,7 @@ generateTestsForEachTreeAdapter('FormattingElementList', (treeAdapter) => {
assert.strictEqual(list.getElementEntryInScopeWithTagName($.DIV), list.entries[0]);
});

test('Get element entry', () => {
it('Get element entry', () => {
const list = new FormattingElementList(treeAdapter);
const token = createToken($.DIV);
const element1 = treeAdapter.createElement($.DIV, NS.HTML, []);
Expand Down
84 changes: 44 additions & 40 deletions packages/parse5/lib/parser/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as assert from 'node:assert';
import * as assert from 'node:assert/strict';
import { parseFragment, parse } from 'parse5';
import { jest } from '@jest/globals';
import { generateParsingTests } from 'parse5-test-utils/utils/generate-parsing-tests.js';
import { treeAdapters } from 'parse5-test-utils/utils/common.js';
import type { Element, TextNode } from '../tree-adapters/default.js';
import type { Element, TextNode, DocumentType } from '../tree-adapters/default.js';
import { spy } from 'tinyspy';

generateParsingTests(
'parser',
Expand Down Expand Up @@ -69,24 +69,27 @@ describe('parser', () => {

it('Regression - DOCTYPE empty fields (GH-236)', () => {
const document = parse('<!DOCTYPE>');
const doctype = document.childNodes[0];

expect(doctype).toHaveProperty('name', '');
expect(doctype).toHaveProperty('publicId', '');
expect(doctype).toHaveProperty('systemId', '');
const doctype = document.childNodes[0] as DocumentType;

assert.ok(Object.prototype.hasOwnProperty.call(doctype, 'name'));
assert.equal(doctype.name, '');
assert.ok(Object.prototype.hasOwnProperty.call(doctype, 'publicId'));
assert.equal(doctype.publicId, '');
assert.ok(Object.prototype.hasOwnProperty.call(doctype, 'systemId'));
assert.equal(doctype.systemId, '');
});

it('Regression - CRLF inside </noscript> (GH-710)', () => {
const onParseError = jest.fn();
const onParseError = spy();
parse('<!doctype html><noscript>foo</noscript\r\n>', { onParseError });

expect(onParseError).not.toHaveBeenCalled();
assert.equal(onParseError.called, false);
});

describe('Tree adapters', () => {
it('should support onItemPush and onItemPop', () => {
const onItemPush = jest.fn();
const onItemPop = jest.fn();
const onItemPush = spy();
const onItemPop = spy();
const document = parse('<p><p>', {
treeAdapter: {
...treeAdapters.default,
Expand All @@ -100,48 +103,49 @@ describe('parser', () => {
const bodyElement = htmlElement.childNodes[1];
assert.ok(treeAdapters.default.isElementNode(bodyElement));
// Expect 5 opened elements; in order: html, head, body, and 2x p
expect(onItemPush).toHaveBeenCalledTimes(5);
expect(onItemPush).toHaveBeenNthCalledWith(1, htmlElement);
expect(onItemPush).toHaveBeenNthCalledWith(3, bodyElement);
assert.equal(onItemPush.callCount, 5);
assert.deepEqual(onItemPush.calls[0], [htmlElement]);
assert.deepEqual(onItemPush.calls[2], [bodyElement]);
// The last opened element is the second p
expect(onItemPush).toHaveBeenLastCalledWith(bodyElement.childNodes[1]);
assert.deepEqual(onItemPush.calls[onItemPush.calls.length - 1], [bodyElement.childNodes[1]]);
// The second p isn't closed, plus we never pop body and html. Alas, only 2 pop events (head and p).
expect(onItemPop).toHaveBeenCalledTimes(2);
assert.equal(onItemPop.callCount, 2);
// The last pop event should be the first p.
expect(onItemPop).toHaveBeenLastCalledWith(bodyElement.childNodes[0], bodyElement);
assert.deepEqual(onItemPop.calls[onItemPop.calls.length - 1], [bodyElement.childNodes[0], bodyElement]);
});
});

describe('rawtext parsing', () => {
it.each([
['iframe'],
['noembed'],
['noframes'],
['noscript'],
['script'],
['style'],
['textarea'],
['title'],
['xmp'],
])('<%s>', (tagName) => {
const tagNames = [
'iframe',
'noembed',
'noframes',
'noscript',
'script',
'style',
'textarea',
'title',
'xmp',
];
for (const tagName of tagNames) {
const html = `<r><${tagName}><math id="</${tagName}><b>should be outside</b>">`;
const fragment = parseFragment(html);

expect(fragment.childNodes.length).toBe(1);
assert.equal(fragment.childNodes.length, 1);
const r = fragment.childNodes[0] as Element;
expect(r.nodeName).toBe('r');
expect(r.childNodes).toHaveLength(3);
expect(r.childNodes.map((_) => _.nodeName)).toEqual([tagName, 'b', '#text']);
assert.equal(r.nodeName, 'r');
assert.equal(r.childNodes.length, 3);
assert.deepEqual(r.childNodes.map((_) => _.nodeName), [tagName, 'b', '#text']);

const target = r.childNodes[0] as Element;
expect(target.childNodes).toHaveLength(1);
expect(target.childNodes[0].nodeName).toBe('#text');
expect((target.childNodes[0] as TextNode).value).toBe('<math id="');
assert.equal(target.childNodes.length, 1);
assert.equal(target.childNodes[0].nodeName, '#text');
assert.equal((target.childNodes[0] as TextNode).value, '<math id="');

const b = r.childNodes[1] as Element;
expect(b.childNodes).toHaveLength(1);
expect(b.childNodes[0].nodeName).toBe('#text');
expect((b.childNodes[0] as TextNode).value).toBe('should be outside');
});
assert.equal(b.childNodes.length, 1);
assert.equal(b.childNodes[0].nodeName, '#text');
assert.equal((b.childNodes[0] as TextNode).value, 'should be outside');
}
});
});
Loading
Loading