This repository has been archived by the owner on Jun 27, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(render): dynamic head content (#213)
* update the JSX render class to not explicitly inject HTML inside of the body tag, allowing for head meta tags and such to be injected and handled by the browser. * fix last pull request for HTML render changes as it didn't do as I had expected initially. * add new render test to check that empty style tag is not injected, slightly change render body assignment to not inject empty style tag if twind isn't utilised. * update render tests to include 3 steps (1 with Twind styling, 1 without Twind styling to test for empty style tag injection, and 1 to test for meta tag injection in head.) * move Cheetah app creation to the outside of the test to clean things up a bit. * remove unnecessary console.log in render function, use </head> instead of slicing the HEAD_TAG constant. (changes requested by not-ivy) * optimize the 'no styles' case in the render function (check for empty css from Twind straight away, and return the html as is without any changes if that is the case) * feat(render): add new render test case for JSX without the html, head or body tags to ensure that the browser and document is still generated appropriately, remove the head manipulation in the render function as the browser will usually know where to put specific tags upon request.
- Loading branch information
1 parent
0184688
commit d70410f
Showing
2 changed files
with
153 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,40 +1,170 @@ | ||
// Copyright 2023 Samuel Kopp. All rights reserved. Apache-2.0 license. | ||
/** @jsx h */ | ||
import { Fragment, VNode } from 'https://esm.sh/preact@10.17.1' | ||
import cheetah, { h, Renderer } from '../mod.ts' | ||
import { assert, assertEquals, DOMParser } from './deps.ts' | ||
import { assert, assertEquals, DOMParser, z } from './deps.ts' | ||
|
||
Deno.test('render', async () => { | ||
const app = new cheetah() | ||
|
||
const { render } = new Renderer() | ||
const Document = ({ children }: { children: VNode }) => { | ||
return ( | ||
<html> | ||
<head> | ||
<title>This is a document!</title> | ||
</head> | ||
<body> | ||
{children} | ||
</body> | ||
</html> | ||
) | ||
} | ||
|
||
function Styled() { | ||
return ( | ||
const Styled = () => { | ||
return ( | ||
<Document> | ||
<h3 class='text-sm italic' id='styled'> | ||
styled <code class='font-mono'>h3</code> component | ||
</h3> | ||
) | ||
} | ||
</Document> | ||
) | ||
} | ||
|
||
app.get('/a', (c) => render(c, <Styled />)) | ||
const Unstyled = () => { | ||
return ( | ||
<Document> | ||
<h3 id='unstyled'> | ||
unstyled <code>h3</code> component | ||
</h3> | ||
</Document> | ||
) | ||
} | ||
|
||
const MetaTagsWithoutWrappers = () => { | ||
return ( | ||
<> | ||
<title>This is a document!</title> | ||
<meta charSet='utf-8' /> | ||
<h1>Hello world!</h1> | ||
</> | ||
) | ||
} | ||
|
||
const a = await app.fetch(new Request('http://localhost/a')) | ||
const app = new cheetah() | ||
|
||
const tx = await a.text() | ||
const { render } = new Renderer() | ||
|
||
app.get('/render', { | ||
query: z.object({ | ||
type: z.union([ | ||
z.literal('styled'), | ||
z.literal('unstyled'), | ||
z.literal('meta-tags-without-wrappers'), | ||
]), | ||
}), | ||
}, (ctx) => { | ||
const type = ctx.req.query.type | ||
if (type === 'meta-tags-without-wrappers') { | ||
return render(ctx, <MetaTagsWithoutWrappers />) | ||
} | ||
render( | ||
ctx, | ||
type === 'styled' ? <Styled /> : <Unstyled />, | ||
) | ||
}) | ||
|
||
Deno.test('render', async (test) => { | ||
await test.step('Twind styles are applied to the resulting HTML correctly.', async () => { | ||
const renderResponse = await app.fetch( | ||
new Request('http://localhost/render?type=styled'), | ||
) | ||
const htmlText = await renderResponse.text() | ||
const document = new DOMParser().parseFromString( | ||
htmlText, | ||
'text/html', | ||
) | ||
assert(document) | ||
assert([...document.getElementsByTagName('style')].length === 1) | ||
assertEquals( | ||
document.getElementById('styled')?.innerText, | ||
'styled h3 component', | ||
) | ||
assertEquals( | ||
renderResponse.headers.get('content-type'), | ||
'text/html; charset=utf-8', | ||
) | ||
}) | ||
|
||
const renderResponse = await app.fetch( | ||
new Request('http://localhost/render?type=unstyled'), | ||
) | ||
const htmlText = await renderResponse.text() | ||
const document = new DOMParser().parseFromString( | ||
tx, | ||
htmlText, | ||
'text/html', | ||
) | ||
|
||
assert(document) | ||
assert([...document.getElementsByTagName('style')].length) | ||
assertEquals( | ||
document.getElementById('styled')?.innerText, | ||
'styled h3 component', | ||
) | ||
assertEquals( | ||
a.headers.get('content-type'), | ||
'text/html; charset=utf-8', | ||
) | ||
await test.step('No empty style tag is injected if no Twind styles are utilised.', () => { | ||
assertEquals( | ||
[...document.getElementsByTagName('style')].length, | ||
0, | ||
) | ||
assertEquals( | ||
document.getElementById('unstyled')?.innerText, | ||
'unstyled h3 component', | ||
) | ||
assertEquals( | ||
renderResponse.headers.get('content-type'), | ||
'text/html; charset=utf-8', | ||
) | ||
}) | ||
|
||
await test.step('Head meta tags are able to be injected into the HTML output properly.', () => { | ||
const headElementsInDocument = [...document.getElementsByTagName('head')] | ||
assert(headElementsInDocument.length === 1) | ||
const headElementInDocument = headElementsInDocument.at(0) | ||
assert(headElementInDocument) | ||
assert( | ||
[...headElementInDocument.children].find((childNode) => | ||
childNode.tagName === 'TITLE' && | ||
childNode.textContent === 'This is a document!' | ||
), | ||
) | ||
}) | ||
|
||
await test.step('Even without the essential html, head and body tags in the JSX input, the JSX input is injected correctly into the appropriate places.', async () => { | ||
const renderResponse = await app.fetch( | ||
new Request('http://localhost/render?type=meta-tags-without-wrappers'), | ||
) | ||
const htmlText = await renderResponse.text() | ||
const document = new DOMParser().parseFromString( | ||
htmlText, | ||
'text/html', | ||
) | ||
assert(document) | ||
const { documentElement } = document | ||
assert(documentElement) | ||
const headElementsInDocument = documentElement.getElementsByTagName('head') | ||
const [headElement] = headElementsInDocument | ||
assert(headElement) | ||
const titleElementInDocument = headElement.getElementsByTagName('title').at( | ||
0, | ||
) | ||
assert( | ||
titleElementInDocument && | ||
titleElementInDocument.textContent === 'This is a document!', | ||
) | ||
const metaTagsInDocument = documentElement.getElementsByTagName('meta') | ||
assert( | ||
metaTagsInDocument.find((metaTag) => | ||
metaTag.getAttribute('charset') === 'utf-8' | ||
), | ||
) | ||
const [bodyElement] = documentElement.getElementsByTagName('body') | ||
assert(bodyElement) | ||
const [headingElementInBody] = [...bodyElement.children] | ||
assert( | ||
headingElementInBody !== undefined && | ||
headingElementInBody.tagName === 'H1' && | ||
headingElementInBody.textContent === 'Hello world!', | ||
) | ||
}) | ||
}) |