mirror of
https://github.com/withastro/astro.git
synced 2025-01-20 22:12:38 -05:00
Re-enable custom element test and fix "undefined" child (#3095)
* Re-enable custom element test and fix "undefined" child * Remove outdated comment * Adds a changeset
This commit is contained in:
parent
200a01c4f8
commit
5acf77dd22
9 changed files with 81 additions and 42 deletions
6
.changeset/neat-oranges-tan.md
Normal file
6
.changeset/neat-oranges-tan.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
'@astrojs/webapi': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fixes rendering of "undefined" on custom element children
|
|
@ -329,9 +329,9 @@ function resolveFlags(flags: Partial<Flags>): CLIFlags {
|
||||||
config: typeof flags.config === 'string' ? flags.config : undefined,
|
config: typeof flags.config === 'string' ? flags.config : undefined,
|
||||||
host:
|
host:
|
||||||
typeof flags.host === 'string' || typeof flags.host === 'boolean' ? flags.host : undefined,
|
typeof flags.host === 'string' || typeof flags.host === 'boolean' ? flags.host : undefined,
|
||||||
experimentalSsr: typeof flags.experimentalSsr === 'boolean' ? flags.experimentalSsr : false,
|
experimentalSsr: typeof flags.experimentalSsr === 'boolean' ? flags.experimentalSsr : undefined,
|
||||||
experimentalIntegrations:
|
experimentalIntegrations:
|
||||||
typeof flags.experimentalIntegrations === 'boolean' ? flags.experimentalIntegrations : false,
|
typeof flags.experimentalIntegrations === 'boolean' ? flags.experimentalIntegrations : undefined,
|
||||||
drafts: typeof flags.drafts === 'boolean' ? flags.drafts : false,
|
drafts: typeof flags.drafts === 'boolean' ? flags.drafts : false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -290,7 +290,7 @@ If you're still stuck, please open an issue on GitHub or join us at https://astr
|
||||||
await render`<${Component}${spreadAttributes(props)}${markHTMLString(
|
await render`<${Component}${spreadAttributes(props)}${markHTMLString(
|
||||||
(children == null || children == '') && voidElementNames.test(Component)
|
(children == null || children == '') && voidElementNames.test(Component)
|
||||||
? `/>`
|
? `/>`
|
||||||
: `>${children}</${Component}>`
|
: `>${children == null ? '' : children}</${Component}>`
|
||||||
)}`
|
)}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,23 @@
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import cheerio from 'cheerio';
|
import { load as cheerioLoad } from 'cheerio';
|
||||||
import { loadFixture } from './test-utils.js';
|
import { loadFixture } from './test-utils.js';
|
||||||
|
|
||||||
// TODO(fks): This seemed to test a custom renderer, but it seemed to be a copy
|
describe('Custom Elements', () => {
|
||||||
// fixture of lit. Should this be moved into a publicly published integration now,
|
|
||||||
// and then tested as an example? Or, should we just remove. Skipping now
|
|
||||||
// to tackle later, since our lit tests cover similar code paths.
|
|
||||||
describe.skip('Custom Elements', () => {
|
|
||||||
let fixture;
|
let fixture;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
fixture = await loadFixture({
|
fixture = await loadFixture({
|
||||||
root: './fixtures/custom-elements/',
|
root: './fixtures/custom-elements/',
|
||||||
intergrations: ['@test/custom-element-renderer'],
|
experimental: {
|
||||||
|
integrations: true
|
||||||
|
}
|
||||||
});
|
});
|
||||||
await fixture.build();
|
await fixture.build();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Work as constructors', async () => {
|
it('Work as constructors', async () => {
|
||||||
const html = await fixture.readFile('/ctr/index.html');
|
const html = await fixture.readFile('/ctr/index.html');
|
||||||
const $ = cheerio.load(html);
|
const $ = cheerioLoad(html);
|
||||||
|
|
||||||
// test 1: Element rendered
|
// test 1: Element rendered
|
||||||
expect($('my-element')).to.have.lengthOf(1);
|
expect($('my-element')).to.have.lengthOf(1);
|
||||||
|
@ -30,7 +28,7 @@ describe.skip('Custom Elements', () => {
|
||||||
|
|
||||||
it('Works with exported tagName', async () => {
|
it('Works with exported tagName', async () => {
|
||||||
const html = await fixture.readFile('/index.html');
|
const html = await fixture.readFile('/index.html');
|
||||||
const $ = cheerio.load(html);
|
const $ = cheerioLoad(html);
|
||||||
|
|
||||||
// test 1: Element rendered
|
// test 1: Element rendered
|
||||||
expect($('my-element')).to.have.lengthOf(1);
|
expect($('my-element')).to.have.lengthOf(1);
|
||||||
|
@ -41,7 +39,7 @@ describe.skip('Custom Elements', () => {
|
||||||
|
|
||||||
it('Hydration works with exported tagName', async () => {
|
it('Hydration works with exported tagName', async () => {
|
||||||
const html = await fixture.readFile('/load/index.html');
|
const html = await fixture.readFile('/load/index.html');
|
||||||
const $ = cheerio.load(html);
|
const $ = cheerioLoad(html);
|
||||||
|
|
||||||
// SSR
|
// SSR
|
||||||
// test 1: Element rendered
|
// test 1: Element rendered
|
||||||
|
@ -52,27 +50,22 @@ describe.skip('Custom Elements', () => {
|
||||||
|
|
||||||
// Hydration
|
// Hydration
|
||||||
// test 3: Component and polyfill scripts bundled separately
|
// test 3: Component and polyfill scripts bundled separately
|
||||||
expect($('script[type=module]')).to.have.lengthOf(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Polyfills are added even if not hydrating', async () => {
|
|
||||||
const html = await fixture.readFile('/index.html');
|
|
||||||
const $ = cheerio.load(html);
|
|
||||||
|
|
||||||
expect($('script[type=module]')).to.have.lengthOf(1);
|
expect($('script[type=module]')).to.have.lengthOf(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Custom elements not claimed by renderer are rendered as regular HTML', async () => {
|
it('Custom elements not claimed by renderer are rendered as regular HTML', async () => {
|
||||||
const html = await fixture.readFile('/nossr/index.html');
|
const html = await fixture.readFile('/nossr/index.html');
|
||||||
const $ = cheerio.load(html);
|
const $ = cheerioLoad(html);
|
||||||
|
|
||||||
// test 1: Rendered the client-only element
|
// test 1: Rendered the client-only element
|
||||||
expect($('client-element')).to.have.lengthOf(1);
|
expect($('client-element')).to.have.lengthOf(1);
|
||||||
|
// No children
|
||||||
|
expect($('client-element').text()).to.equal('');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Can import a client-only element that is nested in JSX', async () => {
|
it('Can import a client-only element that is nested in JSX', async () => {
|
||||||
const html = await fixture.readFile('/nested/index.html');
|
const html = await fixture.readFile('/nested/index.html');
|
||||||
const $ = cheerio.load(html);
|
const $ = cheerioLoad(html);
|
||||||
|
|
||||||
// test 1: Element rendered
|
// test 1: Element rendered
|
||||||
expect($('client-only-element')).to.have.lengthOf(1);
|
expect($('client-only-element')).to.have.lengthOf(1);
|
||||||
|
|
9
packages/astro/test/fixtures/custom-elements/astro.config.mjs
vendored
Normal file
9
packages/astro/test/fixtures/custom-elements/astro.config.mjs
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
import ceIntegration from '@test/custom-element-renderer';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
integrations: [ceIntegration()],
|
||||||
|
experimental: {
|
||||||
|
integrations: true
|
||||||
|
}
|
||||||
|
})
|
|
@ -1,18 +1,31 @@
|
||||||
export default {
|
function getViteConfiguration() {
|
||||||
name: '@test/custom-element-renderer',
|
|
||||||
server: './server.js',
|
|
||||||
polyfills: [
|
|
||||||
'./polyfill.js'
|
|
||||||
],
|
|
||||||
hydrationPolyfills: [
|
|
||||||
'./hydration-polyfill.js'
|
|
||||||
],
|
|
||||||
viteConfig() {
|
|
||||||
return {
|
return {
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
include: ['@test/custom-element-renderer/polyfill.js', '@test/custom-element-renderer/hydration-polyfill.js'],
|
include: ['@test/custom-element-renderer/polyfill.js', '@test/custom-element-renderer/hydration-polyfill.js'],
|
||||||
exclude: ['@test/custom-element-renderer/server.js']
|
exclude: ['@test/custom-element-renderer/server.js']
|
||||||
}
|
},
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
return {
|
||||||
|
name: '@test/custom-element-renderer',
|
||||||
|
hooks: {
|
||||||
|
'astro:config:setup': ({ updateConfig, addRenderer, injectScript }) => {
|
||||||
|
// Inject the necessary polyfills on every page
|
||||||
|
injectScript('head-inline', `import '@test/custom-element-renderer/polyfill.js';`);
|
||||||
|
// Inject the hydration code, before a component is hydrated.
|
||||||
|
injectScript('before-hydration', `import '@test/custom-element-renderer/hydration-polyfill.js';`);
|
||||||
|
// Add the lit renderer so that Astro can understand lit components.
|
||||||
|
addRenderer({
|
||||||
|
name: '@test/custom-element-renderer',
|
||||||
|
serverEntrypoint: '@test/custom-element-renderer/server.js',
|
||||||
|
});
|
||||||
|
// Update the vite configuration.
|
||||||
|
updateConfig({
|
||||||
|
vite: getViteConfiguration(),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import './shim.js';
|
|
||||||
|
|
||||||
function getConstructor(Component) {
|
function getConstructor(Component) {
|
||||||
if (typeof Component === 'string') {
|
if (typeof Component === 'string') {
|
||||||
const tagName = Component;
|
const tagName = Component;
|
||||||
|
|
|
@ -26,6 +26,11 @@ export class CustomElementRegistry {
|
||||||
if (!/-/.test(name))
|
if (!/-/.test(name))
|
||||||
throw new SyntaxError('Custom element name must contain a hyphen')
|
throw new SyntaxError('Custom element name must contain a hyphen')
|
||||||
|
|
||||||
|
_.INTERNALS.set(constructor, {
|
||||||
|
attributes: {},
|
||||||
|
localName: name,
|
||||||
|
} as any)
|
||||||
|
|
||||||
internals.constructorByName.set(name, constructor)
|
internals.constructorByName.set(name, constructor)
|
||||||
internals.nameByConstructor.set(constructor, name)
|
internals.nameByConstructor.set(constructor, name)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,21 @@
|
||||||
import * as _ from './utils'
|
import * as _ from './utils'
|
||||||
|
|
||||||
export class Element extends Node {
|
export class Element extends Node {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(_.INTERNALS.has(new.target)) {
|
||||||
|
const internals = _.internalsOf(new.target, 'Element', 'localName')
|
||||||
|
_.INTERNALS.set(this, {
|
||||||
|
attributes: {},
|
||||||
|
localName: internals.localName,
|
||||||
|
ownerDocument: this.ownerDocument,
|
||||||
|
shadowInit: null as unknown as ShadowRootInit,
|
||||||
|
shadowRoot: null as unknown as ShadowRoot,
|
||||||
|
} as ElementInternals)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
hasAttribute(name: string): boolean {
|
hasAttribute(name: string): boolean {
|
||||||
void name
|
void name
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue