Skip to content

Commit

Permalink
Don't reset <head> when using injectHTML (#631)
Browse files Browse the repository at this point in the history
  • Loading branch information
calebeby committed Dec 19, 2022
1 parent c2b6faa commit f124a5a
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 15 deletions.
7 changes: 7 additions & 0 deletions .changeset/empty-cooks-roll.md
@@ -0,0 +1,7 @@
---
'pleasantest': patch
---

[bugfix] Don't override `<head>` when using `utils.injectHTML`

This was a bug introduced in `3.0.0`. This change fixes that behavior to match what is documented. The documented behavior is that `injectHTML` replaces the content of the body _only_. The behavior introduced in `3.0.0` also resets the `<head>` to the default; that was unintended behavior that is now removed.
21 changes: 14 additions & 7 deletions src/index.ts
Expand Up @@ -15,7 +15,6 @@ import { createBuildStatusTracker } from './module-server/build-status-tracker.j
import { cleanupClientRuntimeServer } from './module-server/client-runtime-server.js';
import type { ModuleServerOpts } from './module-server/index.js';
import { createModuleServer } from './module-server/index.js';
import { defaultHTML } from './module-server/middleware/index-html.js';
import type { BoundQueries, WaitForOptions } from './pptr-testing-library.js';
import {
getQueriesForElement,
Expand Down Expand Up @@ -417,15 +416,23 @@ const createTab = async ({
() =>
page.evaluate(
(html, executeScriptTags) => {
document.body.innerHTML = html;
if (executeScriptTags) {
document.open();
document.write(html);
document.close();
} else {
document.documentElement.innerHTML = html;
// Scripts injected with innerHTML are not executed by default;
// To get the browser to execute them we must manually create the script tags
// and replace them
const scripts = document.body.querySelectorAll('script');
for (const script of scripts) {
const newScript = document.createElement('script');
newScript.text = script.innerHTML;
for (const attribute of script.attributes) {
newScript.setAttribute(attribute.name, attribute.value);
}
script.replaceWith(newScript);
}
}
},
defaultHTML(html),
html,
executeScriptTags,
),
injectHTML,
Expand Down
88 changes: 80 additions & 8 deletions tests/utils/injectHTML.test.ts
Expand Up @@ -3,36 +3,108 @@ import { withBrowser } from 'pleasantest';
test(
'injectHTML',
withBrowser(async ({ utils, page }) => {
const getHTML = () => page.evaluate(() => document.body.innerHTML.trim());
const getHTML = () =>
page.evaluate(() => document.documentElement.innerHTML.trim());

await utils.injectHTML('<div>Hi</div>');
expect(await getHTML()).toEqual('<div>Hi</div>');
expect(await getHTML()).toMatchInlineSnapshot(`
"<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="data:;base64,=">
<title>pleasantest</title>
</head>
<body><div>Hi</div></body>"
`);

// It should fully override existing content
await utils.injectHTML('<div>Hiya</div>');
expect(await getHTML()).toEqual('<div>Hiya</div>');
expect(await getHTML()).toMatchInlineSnapshot(`
"<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="data:;base64,=">
<title>pleasantest</title>
</head>
<body><div>Hiya</div></body>"
`);

// Executes scripts by default
await utils.injectHTML(`
<div>hello</div>
<script>document.querySelector('div').textContent = 'changed'</script>
`);
expect(await getHTML()).toMatchInlineSnapshot(`
"<div>changed</div>
<script>document.querySelector('div').textContent = 'changed'</script>"
"<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="data:;base64,=">
<title>pleasantest</title>
</head>
<body>
<div>changed</div>
<script>document.querySelector('div').textContent = 'changed'</script>
</body>"
`);

// Can pass option to not execute
await utils.injectHTML(
`
<div>hello</div>
<script>document.querySelector('div').textContent = 'changed'</script>
<script foo="bar" asdf>document.querySelector('div').textContent = 'changed'</script>
`,
{ executeScriptTags: false },
);
expect(await getHTML()).toMatchInlineSnapshot(`
"<div>hello</div>
<script>document.querySelector('div').textContent = 'changed'</script>"
"<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="data:;base64,=">
<title>pleasantest</title>
</head>
<body>
<div>hello</div>
<script foo="bar" asdf="">document.querySelector('div').textContent = 'changed'</script>
</body>"
`);

// Stuff in <head> should be left as-is and not re-executed after injectHTML is called again below
await page.evaluate(() => {
const script = document.createElement('script');
script.text =
'document.body.querySelector("div").innerHTML = "changed from script in head"';
document.head.append(script);
});

expect(await getHTML()).toMatchInlineSnapshot(`
"<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="data:;base64,=">
<title>pleasantest</title>
<script>document.body.querySelector("div").innerHTML = "changed from script in head"</script></head>
<body>
<div>changed from script in head</div>
<script foo="bar" asdf="">document.querySelector('div').textContent = 'changed'</script>
</body>"
`);

await utils.injectHTML(
`
<div>injected HTML</div>
`,
);

expect(await getHTML()).toMatchInlineSnapshot(`
"<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="data:;base64,=">
<title>pleasantest</title>
<script>document.body.querySelector("div").innerHTML = "changed from script in head"</script></head>
<body>
<div>injected HTML</div>
</body>"
`);
}),
);

0 comments on commit f124a5a

Please sign in to comment.