Skip to content

Commit

Permalink
feat(parser): Add hooks for stack events (#385)
Browse files Browse the repository at this point in the history
  • Loading branch information
fb55 committed Feb 12, 2022
1 parent ae63d97 commit 68d4438
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 0 deletions.
31 changes: 31 additions & 0 deletions packages/parse5/lib/parser/index.test.ts
@@ -1,10 +1,12 @@
import * as assert from 'node:assert';
import * as parse5 from 'parse5';
import { jest } from '@jest/globals';
import { Parser, ParserOptions } from './index.js';
import type { TreeAdapterTypeMap } from './../tree-adapters/interface.js';
import { generateParsingTests } from 'parse5-test-utils/utils/generate-parsing-tests.js';
import { treeAdapters } from 'parse5-test-utils/utils/common.js';
import { NAMESPACES as NS } from '../common/html.js';
import { isElementNode } from '../tree-adapters/default.js';

const origParseFragment = Parser.prototype.parseFragment;

Expand Down Expand Up @@ -98,4 +100,33 @@ describe('parser', () => {
expect(doctype).toHaveProperty('publicId', '');
expect(doctype).toHaveProperty('systemId', '');
});

describe('Tree adapters', () => {
it('should support onItemPush and onItemPop', () => {
const onItemPush = jest.fn();
const onItemPop = jest.fn();
const document = parse5.parse('<p><p>', {
treeAdapter: {
...treeAdapters.default,
onItemPush,
onItemPop,
},
});

const htmlElement = document.childNodes[0];
assert.ok(isElementNode(htmlElement));
const bodyElement = htmlElement.childNodes[1];
assert.ok(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);
// The last opened element is the second p
expect(onItemPush).toHaveBeenLastCalledWith(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);
// The last pop event should be the first p.
expect(onItemPop).toHaveBeenLastCalledWith(bodyElement.childNodes[0], bodyElement);
});
});
});
3 changes: 3 additions & 0 deletions packages/parse5/lib/parser/index.ts
Expand Up @@ -317,6 +317,7 @@ export class Parser<T extends TreeAdapterTypeMap> {

//Text parsing
private onItemPush(node: T['parentNode'], tid: number, isTop: boolean): void {
this.treeAdapter.onItemPush?.(node);
if (isTop && this.openElements.stackTop > 0) this._setContextModes(node, tid);
}

Expand All @@ -325,6 +326,8 @@ export class Parser<T extends TreeAdapterTypeMap> {
this._setEndLocation(node, this.currentToken!);
}

this.treeAdapter.onItemPop?.(node, this.openElements.current);

if (isTop) {
let current;
let currentTagId;
Expand Down
14 changes: 14 additions & 0 deletions packages/parse5/lib/tree-adapters/interface.ts
Expand Up @@ -279,4 +279,18 @@ export interface TreeAdapter<T extends TreeAdapterTypeMap = TreeAdapterTypeMap>
* @param contentElement - Content element.
*/
setTemplateContent(templateElement: T['template'], contentElement: T['documentFragment']): void;

/**
* Optional callback for elements being pushed to the stack of open elements.
*
* @param element The element being pushed to the stack of open elements.
*/
onItemPush?: (item: T['element']) => void;

/**
* Optional callback for elements being popped from the stack of open elements.
*
* @param item The element being popped.
*/
onItemPop?: (item: T['element'], newTop: T['parentNode']) => void;
}

0 comments on commit 68d4438

Please sign in to comment.