Skip to content

Commit 6070199

Browse files
authored
fix: [#1939] Fixes issue where HTMLSelectElement previousSibling and nextSibling didnt work (#1939)
1 parent 7852969 commit 6070199

7 files changed

Lines changed: 88 additions & 4 deletions

File tree

packages/@happy-dom/jest-environment/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"watch": "npm run compile && tsc -w --preserveWatchOutput",
3131
"test": "jest",
3232
"test:watch": "jest --watch",
33-
"test:debug": "node --inspect-brk ../../node_modules/.bin/jest --runInBand --testTimeout 60000"
33+
"test:debug": "node --inspect-brk ../../../node_modules/.bin/jest --runInBand --testTimeout 60000"
3434
},
3535
"jest": {
3636
"transform": {

packages/happy-dom/src/html-parser/HTMLParser.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ interface IHTMLDocumentStructure {
117117
export default class HTMLParser {
118118
private window: BrowserWindow;
119119
private evaluateScripts: boolean = false;
120+
private isTemplateDocumentFragment: boolean = false;
120121
private rootNode: Element | DocumentFragment | Document | null = null;
121122
private rootDocument: Document | null = null;
122123
private nodeStack: Node[] = [];
@@ -134,18 +135,24 @@ export default class HTMLParser {
134135
* @param window Window.
135136
* @param [options] Options.
136137
* @param [options.evaluateScripts] Set to "true" to enable script execution
138+
* @param [options.isTemplateDocumentFragment] Set to "true" if parsing a template content fragment.
137139
*/
138140
constructor(
139141
window: BrowserWindow,
140142
options?: {
141143
evaluateScripts?: boolean;
144+
isTemplateDocumentFragment?: boolean;
142145
}
143146
) {
144147
this.window = window;
145148

146149
if (options?.evaluateScripts) {
147150
this.evaluateScripts = true;
148151
}
152+
153+
if (options?.isTemplateDocumentFragment) {
154+
this.isTemplateDocumentFragment = true;
155+
}
149156
}
150157
/**
151158
* Parses HTML a root element containing nodes found.
@@ -545,6 +552,7 @@ export default class HTMLParser {
545552
this.currentNode = this.nodeStack[this.nodeStack.length - 1] || this.rootNode;
546553
}
547554
} else if (
555+
!this.isTemplateDocumentFragment &&
548556
config?.permittedParents &&
549557
!config.permittedParents.includes(parentLowerTagName!)
550558
) {

packages/happy-dom/src/nodes/html-template-element/HTMLTemplateElement.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ export default class HTMLTemplateElement extends HTMLElement {
4747
content.removeChild(childNodes[0]);
4848
}
4949

50-
new HTMLParser(this[PropertySymbol.window]).parse(html, this[PropertySymbol.content]);
50+
new HTMLParser(this[PropertySymbol.window], { isTemplateDocumentFragment: true }).parse(
51+
html,
52+
this[PropertySymbol.content]
53+
);
5154
}
5255

5356
/**

packages/happy-dom/src/nodes/node/Node.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ export default class Node extends EventTarget {
241241
public get previousSibling(): Node | null {
242242
if (this[PropertySymbol.parentNode]) {
243243
const nodeArray = this[PropertySymbol.parentNode][PropertySymbol.nodeArray];
244-
const index = nodeArray.indexOf(this);
244+
const index = nodeArray.indexOf(this[PropertySymbol.proxy] || this);
245245
if (index > 0) {
246246
return nodeArray[index - 1];
247247
}
@@ -257,7 +257,7 @@ export default class Node extends EventTarget {
257257
public get nextSibling(): Node | null {
258258
if (this[PropertySymbol.parentNode]) {
259259
const nodeArray = this[PropertySymbol.parentNode][PropertySymbol.nodeArray];
260-
const index = nodeArray.indexOf(this);
260+
const index = nodeArray.indexOf(this[PropertySymbol.proxy] || this);
261261
if (index > -1 && index + 1 < nodeArray.length) {
262262
return nodeArray[index + 1];
263263
}

packages/happy-dom/test/html-parser/HTMLParser.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2191,5 +2191,22 @@ describe('HTMLParser', () => {
21912191

21922192
expect(element).toBe(result.children[0].children[1]);
21932193
});
2194+
2195+
it('Ignores rules for tables when inside a <template> element', () => {
2196+
const template = document.createElement('template');
2197+
template.innerHTML = `
2198+
<tr>
2199+
<td>Test 1</td>
2200+
<td>Test 2</td>
2201+
</tr>
2202+
`;
2203+
2204+
expect(new HTMLSerializer().serializeToString(template.content)).toBe(`
2205+
<tr>
2206+
<td>Test 1</td>
2207+
<td>Test 2</td>
2208+
</tr>
2209+
`);
2210+
});
21942211
});
21952212
});

packages/happy-dom/test/nodes/html-form-element/HTMLFormElement.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,34 @@ describe('HTMLFormElement', () => {
542542
});
543543
});
544544

545+
describe('get previousSibling()', () => {
546+
it('Returns the previous sibling.', () => {
547+
const form = document.createElement('form');
548+
const span1 = document.createElement('span');
549+
const span2 = document.createElement('span');
550+
551+
document.body.appendChild(span1);
552+
document.body.appendChild(form);
553+
document.body.appendChild(span2);
554+
555+
expect(form.previousSibling).toBe(span1);
556+
});
557+
});
558+
559+
describe('get nextSibling()', () => {
560+
it('Returns the next sibling.', () => {
561+
const form = document.createElement('form');
562+
const span1 = document.createElement('span');
563+
const span2 = document.createElement('span');
564+
565+
document.body.appendChild(span1);
566+
document.body.appendChild(form);
567+
document.body.appendChild(span2);
568+
569+
expect(form.nextSibling).toBe(span2);
570+
});
571+
});
572+
545573
describe('submit()', () => {
546574
it('Fallbacks to set location URL when in the main frame of a detached Window.', () => {
547575
vi.spyOn(Fetch.prototype, 'send').mockImplementation(function (): Promise<Response> {

packages/happy-dom/test/nodes/html-select-element/HTMLSelectElement.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,34 @@ describe('HTMLSelectElement', () => {
395395
});
396396
});
397397

398+
describe('get previousSibling()', () => {
399+
it('Returns the previous sibling.', () => {
400+
const select1 = document.createElement('select');
401+
const span1 = document.createElement('span');
402+
const span2 = document.createElement('span');
403+
404+
document.body.appendChild(span1);
405+
document.body.appendChild(select1);
406+
document.body.appendChild(span2);
407+
408+
expect(select1.previousSibling).toBe(span1);
409+
});
410+
});
411+
412+
describe('get nextSibling()', () => {
413+
it('Returns the next sibling.', () => {
414+
const select1 = document.createElement('select');
415+
const span1 = document.createElement('span');
416+
const span2 = document.createElement('span');
417+
418+
document.body.appendChild(span1);
419+
document.body.appendChild(select1);
420+
document.body.appendChild(span2);
421+
422+
expect(select1.nextSibling).toBe(span2);
423+
});
424+
});
425+
398426
describe(`add()`, () => {
399427
it('Appends options.', () => {
400428
const option1 = <HTMLOptionElement>document.createElement('option');

0 commit comments

Comments
 (0)