• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import * as assert from 'node:assert';
2import { parseFragment, parse } from 'parse5';
3import { jest } from '@jest/globals';
4import { generateParsingTests } from 'parse5-test-utils/utils/generate-parsing-tests.js';
5import { treeAdapters } from 'parse5-test-utils/utils/common.js';
6
7generateParsingTests(
8    'parser',
9    'Parser',
10    {
11        expectErrors: [
12            //TODO(GH-448): Foreign content behaviour was updated in the HTML spec.
13            //The old test suite still tests the old behaviour.
14            '269.foreign-fragment',
15            '270.foreign-fragment',
16            '307.foreign-fragment',
17            '309.foreign-fragment',
18            '316.foreign-fragment',
19            '317.foreign-fragment',
20        ],
21    },
22    (test, opts) => ({
23        node: test.fragmentContext ? parseFragment(test.fragmentContext, test.input, opts) : parse(test.input, opts),
24    })
25);
26
27generateParsingTests(
28    'parser upstream',
29    'Parser',
30    {
31        withoutErrors: true,
32        suitePath: new URL('../../../../test/data/html5lib-tests/tree-construction', import.meta.url),
33        expectErrors: ['505.search-element', '506.search-element'],
34    },
35    (test, opts) => ({
36        node: test.fragmentContext ? parseFragment(test.fragmentContext, test.input, opts) : parse(test.input, opts),
37    })
38);
39
40describe('parser', () => {
41    it('Regression - HTML5 Legacy Doctype Misparsed with htmlparser2 tree adapter (GH-45)', () => {
42        const html = '<!DOCTYPE html SYSTEM "about:legacy-compat"><html><head></head><body>Hi there!</body></html>';
43        const document = parse(html, { treeAdapter: treeAdapters.htmlparser2 });
44
45        assert.ok(treeAdapters.htmlparser2.isDocumentTypeNode(document.childNodes[0]));
46        assert.strictEqual(document.childNodes[0].data, '!DOCTYPE html SYSTEM "about:legacy-compat"');
47    });
48
49    describe("Regression - Don't inherit from Object when creating collections (GH-119)", () => {
50        beforeEach(() => {
51            /*eslint-disable no-extend-native*/
52            // @ts-expect-error Adding unknown prototype method
53            Object.prototype.heyYo = 123;
54            /*eslint-enable no-extend-native*/
55        });
56
57        afterEach(() => {
58            // @ts-expect-error Deleting unknown prototype property
59            delete Object.prototype.heyYo;
60        });
61
62        it('parses correctly', () => {
63            const fragment = parseFragment('<div id="123">', {
64                treeAdapter: treeAdapters.htmlparser2,
65            });
66
67            assert.ok(treeAdapters.htmlparser2.isElementNode(fragment.childNodes[0]));
68            assert.strictEqual(treeAdapters.htmlparser2.getAttrList(fragment.childNodes[0]).length, 1);
69        });
70    });
71
72    it('Regression - DOCTYPE empty fields (GH-236)', () => {
73        const document = parse('<!DOCTYPE>');
74        const doctype = document.childNodes[0];
75
76        expect(doctype).toHaveProperty('name', '');
77        expect(doctype).toHaveProperty('publicId', '');
78        expect(doctype).toHaveProperty('systemId', '');
79    });
80
81    describe('Tree adapters', () => {
82        it('should support onItemPush and onItemPop', () => {
83            const onItemPush = jest.fn();
84            const onItemPop = jest.fn();
85            const document = parse('<p><p>', {
86                treeAdapter: {
87                    ...treeAdapters.default,
88                    onItemPush,
89                    onItemPop,
90                },
91            });
92
93            const htmlElement = document.childNodes[0];
94            assert.ok(treeAdapters.default.isElementNode(htmlElement));
95            const bodyElement = htmlElement.childNodes[1];
96            assert.ok(treeAdapters.default.isElementNode(bodyElement));
97            // Expect 5 opened elements; in order: html, head, body, and 2x p
98            expect(onItemPush).toHaveBeenCalledTimes(5);
99            expect(onItemPush).toHaveBeenNthCalledWith(1, htmlElement);
100            expect(onItemPush).toHaveBeenNthCalledWith(3, bodyElement);
101            // The last opened element is the second p
102            expect(onItemPush).toHaveBeenLastCalledWith(bodyElement.childNodes[1]);
103            // The second p isn't closed, plus we never pop body and html. Alas, only 2 pop events (head and p).
104            expect(onItemPop).toHaveBeenCalledTimes(2);
105            // The last pop event should be the first p.
106            expect(onItemPop).toHaveBeenLastCalledWith(bodyElement.childNodes[0], bodyElement);
107        });
108    });
109});
110