Skip to content

Commit

Permalink
Merge branch 'main' into cb/fix-windows-build
Browse files Browse the repository at this point in the history
  • Loading branch information
christian-bromann authored Aug 20, 2024
2 parents 7e1545a + 958c826 commit d0c34d0
Show file tree
Hide file tree
Showing 16 changed files with 200 additions and 22 deletions.
22 changes: 12 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/app-data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export const BUILD: BuildConditionals = {
devTools: false,
shadowDelegatesFocus: true,
initializeNextTick: false,
asyncLoading: false,
asyncLoading: true,
asyncQueue: false,
transformTagName: false,
attachStyles: true,
Expand Down
1 change: 1 addition & 0 deletions src/compiler/transformers/static-to-meta/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ const validateComponentMembers = (node: ts.Node) => {
/**
* the parent node is a class declaration
*/
node.parent &&
ts.isClassDeclaration(node.parent)
) {
const propName = node.name.escapedText;
Expand Down
4 changes: 3 additions & 1 deletion src/compiler/types/generate-app-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,9 @@ const generateComponentTypesFile = (
return `{ ${typeData
.sort(sortImportNames)
.map((td) => {
if (td.originalName === td.importName) {
if (td.originalName === '') {
return `${td.localName}`;
} else if (td.originalName === td.importName) {
return `${td.originalName}`;
} else {
return `${td.originalName} as ${td.importName}`;
Expand Down
7 changes: 7 additions & 0 deletions src/compiler/types/tests/generate-app-types.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1747,6 +1747,11 @@ declare module "@stencil/core" {
location: 'import',
path: '@utils',
},
Fragment: {
location: 'import',
path: '@stencil/core',
id: '',
},
},
},
}),
Expand All @@ -1767,7 +1772,9 @@ declare module "@stencil/core" {
*/
import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
import { MyType as UserImplementedPropType } from "@utils";
import { Fragment } from "@stencil/core";
export { MyType as UserImplementedPropType } from "@utils";
export { Fragment } from "@stencil/core";
export namespace Components {
/**
* docs
Expand Down
2 changes: 2 additions & 0 deletions src/declarations/stencil-public-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,8 @@ export declare namespace h {
export function h(sel: any, data: VNodeData | null, children: VNode): VNode;

export namespace JSX {
interface Element extends LocalJSX.Element {}

interface IntrinsicElements extends LocalJSX.IntrinsicElements, JSXBase.IntrinsicElements {
[tagName: string]: any;
}
Expand Down
6 changes: 6 additions & 0 deletions src/mock-doc/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,12 @@ patchPropAttributes(
},
);

Object.defineProperty(MockButtonElement.prototype, 'form', {
get(this: MockElement) {
return this.hasAttribute('form') ? this.getAttribute('form') : null;
},
});

export class MockImageElement extends MockHTMLElement {
constructor(ownerDocument: any) {
super(ownerDocument, 'img');
Expand Down
23 changes: 21 additions & 2 deletions src/runtime/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,29 @@ export const addStyle = (styleContainerNode: any, cmpMeta: d.ComponentRuntimeMet
}

/**
* attach styles at the end of the head tag if we render shadow components
* attach styles at the end of the head tag if we render scoped components
*/
if (!(cmpMeta.$flags$ & CMP_FLAGS.shadowDomEncapsulation)) {
styleContainerNode.append(styleElm);
if (styleContainerNode.nodeName === 'HEAD') {
/**
* if the page contains preconnect links, we want to insert the styles
* after the last preconnect link to ensure the styles are preloaded
*/
const preconnectLinks = styleContainerNode.querySelectorAll('link[rel=preconnect]');
const referenceNode =
preconnectLinks.length > 0
? preconnectLinks[preconnectLinks.length - 1].nextSibling
: document.querySelector('style');
(styleContainerNode as HTMLElement).insertBefore(styleElm, referenceNode);
} else if ('host' in styleContainerNode) {
/**
* if a scoped component is used within a shadow root, we want to insert the styles
* at the beginning of the shadow root node
*/
(styleContainerNode as HTMLElement).prepend(styleElm);
} else {
styleContainerNode.append(styleElm);
}
}

/**
Expand Down
6 changes: 5 additions & 1 deletion src/runtime/vdom/set-accessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,11 @@ export const setAccessor = (
if (memberName === 'list') {
isProp = false;
} else if (oldValue == null || (elm as any)[memberName] != n) {
(elm as any)[memberName] = n;
if (typeof (elm as any).__lookupSetter__(memberName) === 'function') {
(elm as any)[memberName] = n;
} else {
elm.setAttribute(memberName, n);
}
}
} else {
(elm as any)[memberName] = newValue;
Expand Down
12 changes: 12 additions & 0 deletions src/runtime/vdom/test/set-accessor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -886,4 +886,16 @@ describe('setAccessor for standard html elements', () => {
expect(elm.style.cssText).toEqual('margin: 30px; color: orange;');
});
});

it('uses setAttribute if element has not setter', () => {
const elm = document.createElement('button');
const spy = jest.spyOn(elm, 'setAttribute');
setAccessor(elm, 'form', undefined, 'some-form', false, 0);
expect(spy.mock.calls).toEqual([['form', 'some-form']]);

const elm2 = document.createElement('button');
const spy2 = jest.spyOn(elm2, 'setAttribute');
setAccessor(elm2, 'textContent', undefined, 'some-content', false, 0);
expect(spy2.mock.calls).toEqual([]);
});
});
58 changes: 52 additions & 6 deletions test/end-to-end/src/miscellaneous/renderToString.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,16 @@ describe('renderToString', () => {
);
});

it('puts style last in the head tag', async () => {
it('puts style after preconnect links in the head tag', async () => {
const { html } = await renderToString(
`<html>
<head>
<link rel="preconnect" href="https://some-url.com" />
<style>
.myComponent {
display: none;
}
</style>
</head>
<body>
Expand All @@ -72,11 +77,52 @@ describe('renderToString', () => {
{ fullDocument: true, serializeShadowRoot: false },
);

/**
* expect the scoped component styles to be injected after the preconnect link
*/
expect(html).toContain(
'<link rel="preconnect" href="https://some-url.com"> <style sty-id="sc-scoped-car-list">.sc-scoped-car-list-h{',
'<link rel="preconnect" href="https://some-url.com"><style sty-id="sc-scoped-car-list">.sc-scoped-car-list-h',
);
expect(html).toContain(
'.selected.sc-scoped-car-list{font-weight:bold;background:rgb(255, 255, 210)}</style></head> <body> <div class="__next"> <main> <scoped-car-list cars',
/**
* expect the custom style tag to be last in the head tag
*/
expect(html.replaceAll(/\n[ ]*/g, '')).toContain(
`.selected.sc-scoped-car-list{font-weight:bold;background:rgb(255, 255, 210)}</style> <style>.myComponent {display: none;}</style> </head> <body>`,
);
});

it('puts styles before any custom styles', async () => {
const { html } = await renderToString(
`<html>
<head>
<style>
.myComponent {
display: none;
}
</style>
</head>
<body>
<div class="__next">
<main>
<scoped-car-list cars=${JSON.stringify([vento, beetle])}></scoped-car-list>
</main>
</div>
<script type="module">
import { defineCustomElements } from "./static/loader/index.js";
defineCustomElements().catch(console.error);
</script>
</body>
</html>`,
{ fullDocument: true, serializeShadowRoot: false },
);

/**
* expect the scoped component styles to be injected before custom styles
*/
expect(html.replaceAll(/\n[ ]*/g, '')).toContain(
'.selected.sc-scoped-car-list{font-weight:bold;background:rgb(255, 255, 210)}</style><style class="vjs-styles-defaults">.video-js {width: 300px;height: 150px;}.vjs-fluid {padding-top: 56.25%}</style> <style>.myComponent {display: none;}</style> </head>',
);
});

Expand Down Expand Up @@ -109,8 +155,8 @@ describe('renderToString', () => {
/**
* renders hydration styles and custom link tag within the head tag
*/
expect(html).toContain(
'<link rel="stylesheet" href="whatever.css"> <style sty-id="sc-scoped-car-list">.sc-scoped-car-list-h{display:block;margin:10px;padding:10px;border:1px solid blue}ul.sc-scoped-car-list{display:block;margin:0;padding:0}li.sc-scoped-car-list{list-style:none;margin:0;padding:20px}.selected.sc-scoped-car-list{font-weight:bold;background:rgb(255, 255, 210)}</style></head> <body> <div class="__next"> <main> <scoped-car-list cars=',
expect(html.replaceAll(/\n[ ]*/g, '')).toContain(
'<head><meta charset="utf-8"><style sty-id="sc-scoped-car-list">.sc-scoped-car-list-h{display:block;margin:10px;padding:10px;border:1px solid blue}ul.sc-scoped-car-list{display:block;margin:0;padding:0}li.sc-scoped-car-list{list-style:none;margin:0;padding:20px}.selected.sc-scoped-car-list{font-weight:bold;background:rgb(255, 255, 210)}</style><style class="vjs-styles-defaults">.video-js {width: 300px;height: 150px;}.vjs-fluid {padding-top: 56.25%}</style> <link rel="stylesheet" href="whatever.css"> </head>',
);
});
});
2 changes: 1 addition & 1 deletion test/end-to-end/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"jsxFragmentFactory": "Fragment",
"lib": [
"dom",
"es2017"
"es2021"
],
"module": "esnext",
"moduleResolution": "node",
Expand Down
18 changes: 18 additions & 0 deletions test/wdio/custom-elements-hierarchy-lifecycle/cmp-child.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Component, h } from '@stencil/core';

import { createAndAppendElement } from './cmp-util.js';

@Component({
tag: 'custom-elements-hierarchy-lifecycle-child',
shadow: true,
})
export class CustomElementsHierarchyLifecycleChild {
async componentDidLoad(): Promise<void> {
createAndAppendElement('DID LOAD CHILD');
return Promise.resolve();
}

render() {
return <p>CHILD CONTENT</p>;
}
}
18 changes: 18 additions & 0 deletions test/wdio/custom-elements-hierarchy-lifecycle/cmp-parent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Component, h } from '@stencil/core';

import { createAndAppendElement } from './cmp-util.js';

@Component({
tag: 'custom-elements-hierarchy-lifecycle-parent',
shadow: true,
})
export class CustomElementsHierarchyLifecycleParent {
async componentDidLoad(): Promise<void> {
createAndAppendElement('DID LOAD PARENT');
return Promise.resolve();
}

render() {
return <custom-elements-hierarchy-lifecycle-child></custom-elements-hierarchy-lifecycle-child>;
}
}
6 changes: 6 additions & 0 deletions test/wdio/custom-elements-hierarchy-lifecycle/cmp-util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const createAndAppendElement = (text: string) => {
const p = document.createElement('p');
p.textContent = text;

document.body.appendChild(p);
};
35 changes: 35 additions & 0 deletions test/wdio/custom-elements-hierarchy-lifecycle/cmp.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Fragment, h } from '@stencil/core';
import { render } from '@wdio/browser-runner/stencil';

import { defineCustomElement as defineCustomElementChildCmp } from '../test-components/custom-elements-hierarchy-lifecycle-child.js';
import { defineCustomElement as defineCustomElementParentCmp } from '../test-components/custom-elements-hierarchy-lifecycle-parent.js';

describe('custom-elements-hierarchy-lifecycle', () => {
before(() => {
defineCustomElementChildCmp();
defineCustomElementParentCmp();
});

it('should call componentDidLoad in the child before the parent', async () => {
expect(customElements.get('custom-elements-hierarchy-lifecycle-child')).toBeDefined();
expect(customElements.get('custom-elements-hierarchy-lifecycle-parent')).toBeDefined();

render({
template: () => (
<>
<custom-elements-hierarchy-lifecycle-parent></custom-elements-hierarchy-lifecycle-parent>
</>
),
});

const elm = document.querySelector('custom-elements-hierarchy-lifecycle-parent');
expect(elm.shadowRoot).toBeDefined();

await browser.waitUntil(() => Boolean(elm.shadowRoot.querySelector('custom-elements-hierarchy-lifecycle-child')));

expect(Array.from(document.querySelectorAll('p')).map((r) => r.textContent)).toEqual([
'DID LOAD CHILD',
'DID LOAD PARENT',
]);
});
});

0 comments on commit d0c34d0

Please sign in to comment.