Skip to content

Commit f124a5a

Browse files
authored
Don't reset <head> when using injectHTML (#631)
1 parent c2b6faa commit f124a5a

File tree

3 files changed

+101
-15
lines changed

3 files changed

+101
-15
lines changed

.changeset/empty-cooks-roll.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'pleasantest': patch
3+
---
4+
5+
[bugfix] Don't override `<head>` when using `utils.injectHTML`
6+
7+
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.

src/index.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import { createBuildStatusTracker } from './module-server/build-status-tracker.j
1515
import { cleanupClientRuntimeServer } from './module-server/client-runtime-server.js';
1616
import type { ModuleServerOpts } from './module-server/index.js';
1717
import { createModuleServer } from './module-server/index.js';
18-
import { defaultHTML } from './module-server/middleware/index-html.js';
1918
import type { BoundQueries, WaitForOptions } from './pptr-testing-library.js';
2019
import {
2120
getQueriesForElement,
@@ -417,15 +416,23 @@ const createTab = async ({
417416
() =>
418417
page.evaluate(
419418
(html, executeScriptTags) => {
419+
document.body.innerHTML = html;
420420
if (executeScriptTags) {
421-
document.open();
422-
document.write(html);
423-
document.close();
424-
} else {
425-
document.documentElement.innerHTML = html;
421+
// Scripts injected with innerHTML are not executed by default;
422+
// To get the browser to execute them we must manually create the script tags
423+
// and replace them
424+
const scripts = document.body.querySelectorAll('script');
425+
for (const script of scripts) {
426+
const newScript = document.createElement('script');
427+
newScript.text = script.innerHTML;
428+
for (const attribute of script.attributes) {
429+
newScript.setAttribute(attribute.name, attribute.value);
430+
}
431+
script.replaceWith(newScript);
432+
}
426433
}
427434
},
428-
defaultHTML(html),
435+
html,
429436
executeScriptTags,
430437
),
431438
injectHTML,

tests/utils/injectHTML.test.ts

Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,36 +3,108 @@ import { withBrowser } from 'pleasantest';
33
test(
44
'injectHTML',
55
withBrowser(async ({ utils, page }) => {
6-
const getHTML = () => page.evaluate(() => document.body.innerHTML.trim());
6+
const getHTML = () =>
7+
page.evaluate(() => document.documentElement.innerHTML.trim());
78

89
await utils.injectHTML('<div>Hi</div>');
9-
expect(await getHTML()).toEqual('<div>Hi</div>');
10+
expect(await getHTML()).toMatchInlineSnapshot(`
11+
"<head>
12+
<meta charset="UTF-8">
13+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
14+
<link rel="icon" href="data:;base64,=">
15+
<title>pleasantest</title>
16+
</head>
17+
<body><div>Hi</div></body>"
18+
`);
1019

1120
// It should fully override existing content
1221
await utils.injectHTML('<div>Hiya</div>');
13-
expect(await getHTML()).toEqual('<div>Hiya</div>');
22+
expect(await getHTML()).toMatchInlineSnapshot(`
23+
"<head>
24+
<meta charset="UTF-8">
25+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
26+
<link rel="icon" href="data:;base64,=">
27+
<title>pleasantest</title>
28+
</head>
29+
<body><div>Hiya</div></body>"
30+
`);
1431

1532
// Executes scripts by default
1633
await utils.injectHTML(`
1734
<div>hello</div>
1835
<script>document.querySelector('div').textContent = 'changed'</script>
1936
`);
2037
expect(await getHTML()).toMatchInlineSnapshot(`
21-
"<div>changed</div>
22-
<script>document.querySelector('div').textContent = 'changed'</script>"
38+
"<head>
39+
<meta charset="UTF-8">
40+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
41+
<link rel="icon" href="data:;base64,=">
42+
<title>pleasantest</title>
43+
</head>
44+
<body>
45+
<div>changed</div>
46+
<script>document.querySelector('div').textContent = 'changed'</script>
47+
</body>"
2348
`);
2449

2550
// Can pass option to not execute
2651
await utils.injectHTML(
2752
`
2853
<div>hello</div>
29-
<script>document.querySelector('div').textContent = 'changed'</script>
54+
<script foo="bar" asdf>document.querySelector('div').textContent = 'changed'</script>
3055
`,
3156
{ executeScriptTags: false },
3257
);
3358
expect(await getHTML()).toMatchInlineSnapshot(`
34-
"<div>hello</div>
35-
<script>document.querySelector('div').textContent = 'changed'</script>"
59+
"<head>
60+
<meta charset="UTF-8">
61+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
62+
<link rel="icon" href="data:;base64,=">
63+
<title>pleasantest</title>
64+
</head>
65+
<body>
66+
<div>hello</div>
67+
<script foo="bar" asdf="">document.querySelector('div').textContent = 'changed'</script>
68+
</body>"
69+
`);
70+
71+
// Stuff in <head> should be left as-is and not re-executed after injectHTML is called again below
72+
await page.evaluate(() => {
73+
const script = document.createElement('script');
74+
script.text =
75+
'document.body.querySelector("div").innerHTML = "changed from script in head"';
76+
document.head.append(script);
77+
});
78+
79+
expect(await getHTML()).toMatchInlineSnapshot(`
80+
"<head>
81+
<meta charset="UTF-8">
82+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
83+
<link rel="icon" href="data:;base64,=">
84+
<title>pleasantest</title>
85+
<script>document.body.querySelector("div").innerHTML = "changed from script in head"</script></head>
86+
<body>
87+
<div>changed from script in head</div>
88+
<script foo="bar" asdf="">document.querySelector('div').textContent = 'changed'</script>
89+
</body>"
90+
`);
91+
92+
await utils.injectHTML(
93+
`
94+
<div>injected HTML</div>
95+
`,
96+
);
97+
98+
expect(await getHTML()).toMatchInlineSnapshot(`
99+
"<head>
100+
<meta charset="UTF-8">
101+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
102+
<link rel="icon" href="data:;base64,=">
103+
<title>pleasantest</title>
104+
<script>document.body.querySelector("div").innerHTML = "changed from script in head"</script></head>
105+
<body>
106+
<div>injected HTML</div>
107+
</body>"
36108
`);
37109
}),
38110
);

0 commit comments

Comments
 (0)