0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2024-12-16 21:46:22 -05:00

Fix set:html behavior with null (#2790)

* feat: improve set:html behavior for null/undefined

* chore: add changeset

* refactor: improve set:html and set:text documentation

* test: improve set:html tests

* refactor: better types for server API
This commit is contained in:
Nate Moore 2022-03-14 18:19:53 -05:00 committed by GitHub
parent de2b246237
commit 6b34840d3d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 57 additions and 6 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Improve `set:html` behavior for `null` and `undefined` values

View file

@ -1,6 +1,7 @@
const entities = { '"': 'quot', '&': 'amp', "'": 'apos', '<': 'lt', '>': 'gt' } as const;
export const escapeHTML = (string: any) => string.replace(/["'&<>]/g, (char: keyof typeof entities) => '&' + entities[char] + ';');
// This util is only ever run on expression values that we already know are of type `string`
export const escapeHTML = (str: string) => str.replace(/["'&<>]/g, (char: string) => '&' + entities[char as keyof typeof entities] + ';');
/**
* RawString is a "blessed" version of String
@ -11,7 +12,14 @@ export class UnescapedString extends String {}
/**
* unescapeHTML marks a string as raw, unescaped HTML.
* This should only be generated internally, not a public API.
*
* Need to cast the return value `as unknown as string` so TS doesn't yell at us.
*/
export const unescapeHTML = (str: any) => new UnescapedString(str) as unknown as string;
export const unescapeHTML = (value: any) => {
// Cast any `string` values to `UnescapedString` to mark them as ignored
// The `as unknown as string` is necessary for TypeScript to treat this as `string`
if (typeof value === 'string') {
return new UnescapedString(value) as unknown as string;
}
// Just return values that are `number`, `null`, `undefined` etc
// The compiler will recursively stringify these correctly
return value
}

View file

@ -418,7 +418,7 @@ export async function renderEndpoint(mod: EndpointHandler, params: any) {
}
// Calls a component and renders it into a string of HTML
export async function renderToString(result: SSRResult, componentFactory: AstroComponentFactory, props: any, children: any) {
export async function renderToString(result: SSRResult, componentFactory: AstroComponentFactory, props: any, children: any): Promise<string> {
const Component = await componentFactory(result, props, children);
let template = await renderAstroComponent(Component);
@ -439,7 +439,7 @@ const uniqueElements = (item: any, index: number, all: any[]) => {
// Renders a page to completion by first calling the factory callback, waiting for its result, and then appending
// styles and scripts into the head.
export async function renderHead(result: SSRResult) {
export async function renderHead(result: SSRResult): Promise<string> {
const styles = Array.from(result.styles)
.filter(uniqueElements)
.map((style) => {

View file

@ -17,4 +17,30 @@ describe('Directives', async () => {
expect($('script#inline')).to.have.lengthOf(1);
expect($('script#inline').toString()).to.include('let foo = "bar"');
});
it('set:html', async () => {
const html = await fixture.readFile('/set-html/index.html');
const $ = cheerio.load(html);
expect($('#text')).to.have.lengthOf(1);
expect($('#text').text()).to.equal('a');
expect($('#zero')).to.have.lengthOf(1);
expect($('#zero').text()).to.equal('0');
expect($('#number')).to.have.lengthOf(1);
expect($('#number').text()).to.equal('1');
expect($('#undefined')).to.have.lengthOf(1);
expect($('#undefined').text()).to.equal('');
expect($('#null')).to.have.lengthOf(1);
expect($('#null').text()).to.equal('');
expect($('#false')).to.have.lengthOf(1);
expect($('#false').text()).to.equal('');
expect($('#true')).to.have.lengthOf(1);
expect($('#true').text()).to.equal('true');
});
});

View file

@ -0,0 +1,12 @@
<html>
<head></head>
<body>
<div id="text" set:html={"a"} />
<div id="zero" set:html={0} />
<div id="number" set:html={1} />
<div id="false" set:html={false} />
<div id="true" set:html={true} />
<div id="undefined" set:html={undefined} />
<div id="null" set:html={null} />
</body>
</html>