From cc08f273bef2d9b049ecc7025aa4e1e6ab393355 Mon Sep 17 00:00:00 2001 From: Christian Bromann Date: Tue, 6 Feb 2024 17:57:39 -0800 Subject: [PATCH 01/11] fix(mock-doc): overwrite parentElement in MockHTMLElement to return null fixes #5252 STENCIL-1104 --- src/mock-doc/node.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/mock-doc/node.ts b/src/mock-doc/node.ts index 14465c36315..c06bf71b080 100644 --- a/src/mock-doc/node.ts +++ b/src/mock-doc/node.ts @@ -1101,6 +1101,13 @@ export class MockHTMLElement extends MockElement { this.nodeName = value; } + override get parentElement() { + return null; + } + override set parentElement(_value: any) { + /**/ + } + override get attributes(): MockAttributeMap { if (this.__attributeMap == null) { const attrMap = createAttributeProxy(true); From 80fa41e36393e521487c556643f1a09a1a9554ea Mon Sep 17 00:00:00 2001 From: Christian Bromann Date: Tue, 6 Feb 2024 18:03:15 -0800 Subject: [PATCH 02/11] add test --- src/mock-doc/test/element.spec.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/mock-doc/test/element.spec.ts b/src/mock-doc/test/element.spec.ts index 74c3753a91e..f8855389d54 100644 --- a/src/mock-doc/test/element.spec.ts +++ b/src/mock-doc/test/element.spec.ts @@ -444,6 +444,13 @@ describe('element', () => { }); }); + describe('parentElement', () => { + it('returns `null` for when accessing the parent element of an html node', () => { + const element = new MockHTMLElement(doc, 'myElement'); + expect(element.parentElement).toEqual(null); + }); + }); + describe('input', () => { it('list is readonly prop', () => { const input = doc.createElement('input'); From 670e0240715ab0089c3544d4155d412b7072f8bf Mon Sep 17 00:00:00 2001 From: Christian Bromann Date: Tue, 6 Feb 2024 19:06:42 -0800 Subject: [PATCH 03/11] only return null for html elements --- src/mock-doc/node.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/mock-doc/node.ts b/src/mock-doc/node.ts index c06bf71b080..dc7ed9283ba 100644 --- a/src/mock-doc/node.ts +++ b/src/mock-doc/node.ts @@ -1101,11 +1101,17 @@ export class MockHTMLElement extends MockElement { this.nodeName = value; } + /** + * A node’s parent of type Element is known as its parent element. + * If the node has a parent of a different type, its parent element + * is null. + * @returns MockElement + */ override get parentElement() { - return null; - } - override set parentElement(_value: any) { - /**/ + if (this.nodeName === 'HTML') { + return null; + } + return super.parentElement; } override get attributes(): MockAttributeMap { From 51936052f0274a747375e4f87c140bf6f5c0ff43 Mon Sep 17 00:00:00 2001 From: Christian Bromann Date: Tue, 6 Feb 2024 19:16:42 -0800 Subject: [PATCH 04/11] fix unit tests --- src/mock-doc/test/event.spec.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/mock-doc/test/event.spec.ts b/src/mock-doc/test/event.spec.ts index 5505e94d020..641e7aae21d 100644 --- a/src/mock-doc/test/event.spec.ts +++ b/src/mock-doc/test/event.spec.ts @@ -291,12 +291,10 @@ describe('event', () => { const composedPath = event.composedPath(); - expect(composedPath).toHaveLength(5); + expect(composedPath).toHaveLength(3); expect(composedPath[0]).toBe(divElm); expect(composedPath[1]).toBe(doc.body); expect(composedPath[2]).toBe(doc.documentElement); - expect(composedPath[3]).toBe(doc); - expect(composedPath[4]).toBe(win); }); it('returns the correct path for a nested element', () => { @@ -313,13 +311,11 @@ describe('event', () => { const composedPath = event.composedPath(); - expect(composedPath).toHaveLength(6); + expect(composedPath).toHaveLength(4); expect(composedPath[0]).toBe(pElm); expect(composedPath[1]).toBe(divElm); expect(composedPath[2]).toBe(doc.body); expect(composedPath[3]).toBe(doc.documentElement); - expect(composedPath[4]).toBe(doc); - expect(composedPath[5]).toBe(win); }); }); }); From 4d25edc804f0710d0f1236c4ba785dedbf94e14e Mon Sep 17 00:00:00 2001 From: Christian Bromann Date: Tue, 6 Feb 2024 19:26:49 -0800 Subject: [PATCH 05/11] tweak --- src/runtime/test/listen.spec.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/runtime/test/listen.spec.tsx b/src/runtime/test/listen.spec.tsx index 4de1e3600a9..5c5b1a3b5f2 100644 --- a/src/runtime/test/listen.spec.tsx +++ b/src/runtime/test/listen.spec.tsx @@ -89,37 +89,37 @@ describe('listen', () => { root.click(); await waitForChanges(); expect(root).toEqualHtml(` - 1,1,1,1 + 1,1,0,0 `); parent.click(); await waitForChanges(); expect(root).toEqualHtml(` - 1,2,2,2 + 1,2,0,0 `); other.click(); await waitForChanges(); expect(root).toEqualHtml(` - 1,3,3,3 + 1,3,0,0 `); body.click(); await waitForChanges(); expect(root).toEqualHtml(` - 1,4,4,4 + 1,4,0,0 `); doc.dispatchEvent(new CustomEvent('click', { bubbles: true })); await waitForChanges(); expect(root).toEqualHtml(` - 1,4,5,5 + 1,4,0,0 `); win.dispatchEvent(new CustomEvent('click', { bubbles: true })); await waitForChanges(); expect(root).toEqualHtml(` - 1,4,5,6 + 1,4,0,0 `); }); From 93a6bfa6b82eb1b02d3167e8d844ac8055730392 Mon Sep 17 00:00:00 2001 From: Christian Bromann Date: Tue, 6 Feb 2024 19:28:55 -0800 Subject: [PATCH 06/11] another tweak --- src/runtime/test/listen.spec.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/runtime/test/listen.spec.tsx b/src/runtime/test/listen.spec.tsx index 5c5b1a3b5f2..df36a3b8aa6 100644 --- a/src/runtime/test/listen.spec.tsx +++ b/src/runtime/test/listen.spec.tsx @@ -113,13 +113,13 @@ describe('listen', () => { doc.dispatchEvent(new CustomEvent('click', { bubbles: true })); await waitForChanges(); expect(root).toEqualHtml(` - 1,4,0,0 + 1,4,1,1 `); win.dispatchEvent(new CustomEvent('click', { bubbles: true })); await waitForChanges(); expect(root).toEqualHtml(` - 1,4,0,0 + 1,4,1,2 `); }); From bc02b5fba1dcb68a391974654f59b917eb7233ff Mon Sep 17 00:00:00 2001 From: Christian Bromann Date: Wed, 7 Feb 2024 16:08:35 -0800 Subject: [PATCH 07/11] revert tests and correct implementation --- src/mock-doc/event.ts | 18 +++++++++++++++++- src/mock-doc/test/event.spec.ts | 8 ++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/mock-doc/event.ts b/src/mock-doc/event.ts index 27cfb523848..8ce1cb532ba 100644 --- a/src/mock-doc/event.ts +++ b/src/mock-doc/event.ts @@ -39,6 +39,10 @@ export class MockEvent { this.cancelBubble = true; } + /** + * @ref https://developer.mozilla.org/en-US/docs/Web/API/Event/composedPath + * @returns a composed path of the event + */ composedPath(): MockElement[] { const composedPath: MockElement[] = []; @@ -54,7 +58,17 @@ export class MockEvent { break; } - currentElement = currentElement.parentElement; + /** + * bubble up the parent chain until we arrive to the HTML element. Here we continue + * with the document object instead of the parent element since the parent element + * is `null` for HTML elements. + * @ref + */ + if (currentElement.parentElement == null && currentElement.tagName === 'HTML') { + currentElement = currentElement.ownerDocument + } else { + currentElement = currentElement.parentElement; + } } return composedPath; @@ -202,6 +216,8 @@ function triggerEventListener(elm: any, ev: MockEvent) { if (elm.nodeName === NODE_NAMES.DOCUMENT_NODE) { triggerEventListener((elm as MockDocument).defaultView, ev); + } else if (elm.parentElement == null && elm.tagName === 'HTML') { + triggerEventListener(elm.ownerDocument, ev); } else { triggerEventListener(elm.parentElement, ev); } diff --git a/src/mock-doc/test/event.spec.ts b/src/mock-doc/test/event.spec.ts index 641e7aae21d..5505e94d020 100644 --- a/src/mock-doc/test/event.spec.ts +++ b/src/mock-doc/test/event.spec.ts @@ -291,10 +291,12 @@ describe('event', () => { const composedPath = event.composedPath(); - expect(composedPath).toHaveLength(3); + expect(composedPath).toHaveLength(5); expect(composedPath[0]).toBe(divElm); expect(composedPath[1]).toBe(doc.body); expect(composedPath[2]).toBe(doc.documentElement); + expect(composedPath[3]).toBe(doc); + expect(composedPath[4]).toBe(win); }); it('returns the correct path for a nested element', () => { @@ -311,11 +313,13 @@ describe('event', () => { const composedPath = event.composedPath(); - expect(composedPath).toHaveLength(4); + expect(composedPath).toHaveLength(6); expect(composedPath[0]).toBe(pElm); expect(composedPath[1]).toBe(divElm); expect(composedPath[2]).toBe(doc.body); expect(composedPath[3]).toBe(doc.documentElement); + expect(composedPath[4]).toBe(doc); + expect(composedPath[5]).toBe(win); }); }); }); From 487ead795a75cd630a279e8bd54022adc0f410d9 Mon Sep 17 00:00:00 2001 From: Christian Bromann Date: Wed, 7 Feb 2024 16:12:20 -0800 Subject: [PATCH 08/11] update tests --- src/runtime/test/listen.spec.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/runtime/test/listen.spec.tsx b/src/runtime/test/listen.spec.tsx index df36a3b8aa6..4de1e3600a9 100644 --- a/src/runtime/test/listen.spec.tsx +++ b/src/runtime/test/listen.spec.tsx @@ -89,37 +89,37 @@ describe('listen', () => { root.click(); await waitForChanges(); expect(root).toEqualHtml(` - 1,1,0,0 + 1,1,1,1 `); parent.click(); await waitForChanges(); expect(root).toEqualHtml(` - 1,2,0,0 + 1,2,2,2 `); other.click(); await waitForChanges(); expect(root).toEqualHtml(` - 1,3,0,0 + 1,3,3,3 `); body.click(); await waitForChanges(); expect(root).toEqualHtml(` - 1,4,0,0 + 1,4,4,4 `); doc.dispatchEvent(new CustomEvent('click', { bubbles: true })); await waitForChanges(); expect(root).toEqualHtml(` - 1,4,1,1 + 1,4,5,5 `); win.dispatchEvent(new CustomEvent('click', { bubbles: true })); await waitForChanges(); expect(root).toEqualHtml(` - 1,4,1,2 + 1,4,5,6 `); }); From 5ea0d7d13e0cf70a6aab54c375fc0df41ba742bb Mon Sep 17 00:00:00 2001 From: Christian Bromann Date: Thu, 8 Feb 2024 09:50:03 -0800 Subject: [PATCH 09/11] prettier --- src/mock-doc/event.ts | 2 +- src/runtime/test/style.spec.tsx | 12 ++++++++++-- test/end-to-end/src/state-cmp/state-cmp.tsx | 9 +++++++-- test/karma/test-app/attribute-host/cmp.tsx | 6 +++--- test/karma/test-app/listen-jsx/cmp.tsx | 15 ++++++++------- 5 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/mock-doc/event.ts b/src/mock-doc/event.ts index 8ce1cb532ba..4b04904bbcb 100644 --- a/src/mock-doc/event.ts +++ b/src/mock-doc/event.ts @@ -65,7 +65,7 @@ export class MockEvent { * @ref */ if (currentElement.parentElement == null && currentElement.tagName === 'HTML') { - currentElement = currentElement.ownerDocument + currentElement = currentElement.ownerDocument; } else { currentElement = currentElement.parentElement; } diff --git a/src/runtime/test/style.spec.tsx b/src/runtime/test/style.spec.tsx index e7f1a723f2f..a177b7ada12 100644 --- a/src/runtime/test/style.spec.tsx +++ b/src/runtime/test/style.spec.tsx @@ -5,7 +5,11 @@ describe('style', () => { it('get style string', async () => { @Component({ tag: 'cmp-a', - styles: `div { color: red; }`, + styles: ` + div { + color: red; + } + `, }) class CmpA { render() { @@ -26,7 +30,11 @@ describe('style', () => { it('applies the nonce value to the head style tags', async () => { @Component({ tag: 'cmp-a', - styles: `div { color: red; }`, + styles: ` + div { + color: red; + } + `, }) class CmpA { render() { diff --git a/test/end-to-end/src/state-cmp/state-cmp.tsx b/test/end-to-end/src/state-cmp/state-cmp.tsx index 7e4c1f1eb8d..bb55e2ac9e0 100644 --- a/test/end-to-end/src/state-cmp/state-cmp.tsx +++ b/test/end-to-end/src/state-cmp/state-cmp.tsx @@ -3,8 +3,13 @@ import { Component, State, h } from '@stencil/core'; @Component({ tag: 'state-cmp', styles: ` - button { color: black; } - .selected { font-weight: bold; color: blue; } + button { + color: black; + } + .selected { + font-weight: bold; + color: blue; + } `, shadow: true, }) diff --git a/test/karma/test-app/attribute-host/cmp.tsx b/test/karma/test-app/attribute-host/cmp.tsx index 8ce11c62fd4..1f63758fd69 100644 --- a/test/karma/test-app/attribute-host/cmp.tsx +++ b/test/karma/test-app/attribute-host/cmp.tsx @@ -3,19 +3,19 @@ import { Component, State, h } from '@stencil/core'; @Component({ tag: 'attribute-host', styles: ` - [color=lime] { + [color='lime'] { background: lime; } section::before { content: attr(content); } - [padding=true] { + [padding='true'] { padding: 50px; } [margin] { margin: 50px; } - [bold=true] { + [bold='true'] { font-weight: bold; } `, diff --git a/test/karma/test-app/listen-jsx/cmp.tsx b/test/karma/test-app/listen-jsx/cmp.tsx index 85601b137fc..7e98c651eed 100644 --- a/test/karma/test-app/listen-jsx/cmp.tsx +++ b/test/karma/test-app/listen-jsx/cmp.tsx @@ -4,13 +4,14 @@ import { Component, Listen, State, h } from '@stencil/core'; tag: 'listen-jsx', scoped: true, styles: ` - :host{ - background: black; - display: block; - color: white; - width: 100px; - height: 100px; - }`, + :host { + background: black; + display: block; + color: white; + width: 100px; + height: 100px; + } + `, }) export class AttributeBasic { @State() wasClicked = ''; From d81e0c9e528083d88c1c47c08eac89866e97f9fd Mon Sep 17 00:00:00 2001 From: Christian Bromann Date: Thu, 8 Feb 2024 09:51:05 -0800 Subject: [PATCH 10/11] minor comment tweak --- src/mock-doc/event.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/mock-doc/event.ts b/src/mock-doc/event.ts index 4b04904bbcb..156e47eb939 100644 --- a/src/mock-doc/event.ts +++ b/src/mock-doc/event.ts @@ -62,7 +62,6 @@ export class MockEvent { * bubble up the parent chain until we arrive to the HTML element. Here we continue * with the document object instead of the parent element since the parent element * is `null` for HTML elements. - * @ref */ if (currentElement.parentElement == null && currentElement.tagName === 'HTML') { currentElement = currentElement.ownerDocument; From b1770c871538777fe4bdf069365b88767224e5ff Mon Sep 17 00:00:00 2001 From: Christian Bromann Date: Thu, 8 Feb 2024 12:53:18 -0800 Subject: [PATCH 11/11] fix assertion --- src/runtime/test/style.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runtime/test/style.spec.tsx b/src/runtime/test/style.spec.tsx index a177b7ada12..ed85d1f5bd8 100644 --- a/src/runtime/test/style.spec.tsx +++ b/src/runtime/test/style.spec.tsx @@ -24,7 +24,7 @@ describe('style', () => { }); expect(root).toHaveClass('hydrated'); - expect(styles.get('sc-cmp-a')).toBe(`div { color: red; }`); + expect(styles.get('sc-cmp-a')).toContain(`color: red;`); }); it('applies the nonce value to the head style tags', async () => {