fix: provide more hydration mismatch coverage (#12755)

* fix: provide more hydration mismatch coverage

* tweak

* add test for safari borking stuff

* fix

* fix windows test

* failing test

* oops

* revert playground changes

* simplify

* template content hydration logic should really be separate from reset logic

* actually the test is incorrect, and now i cant seem to recreate what i saw before... hmm

* update comment to no longer mention templates

* failing test

* delete test for now

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
pull/12789/head
Dominic Gannaway 5 months ago committed by GitHub
parent c32a91891f
commit 19819d0477
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: provide more hydration mismatch coverage

@ -327,9 +327,8 @@ export function RegularElement(node, context) {
// set the value of `hydrate_node` to `node.content`
if (node.name === 'template') {
needs_reset = true;
child_state.init.push(b.stmt(b.call('$.hydrate_template', arg)));
arg = b.member(arg, b.id('content'));
child_state.init.push(b.stmt(b.call('$.reset', arg)));
}
process_children(trimmed, () => b.call('$.child', arg), true, {

@ -30,21 +30,38 @@ export let hydrate_node;
/** @param {TemplateNode} node */
export function set_hydrate_node(node) {
if (node === null) {
w.hydration_mismatch();
throw HYDRATION_ERROR;
}
return (hydrate_node = node);
}
export function hydrate_next() {
if (hydrate_node === null) {
return set_hydrate_node(/** @type {TemplateNode} */ (hydrate_node.nextSibling));
}
/** @param {TemplateNode} node */
export function reset(node) {
if (!hydrating) return;
// If the node has remaining siblings, something has gone wrong
if (hydrate_node.nextSibling !== null) {
w.hydration_mismatch();
throw HYDRATION_ERROR;
}
return (hydrate_node = /** @type {TemplateNode} */ (hydrate_node.nextSibling));
hydrate_node = node;
}
/** @param {TemplateNode} node */
export function reset(node) {
/**
* @param {HTMLTemplateElement} template
*/
export function hydrate_template(template) {
if (hydrating) {
hydrate_node = node;
// @ts-expect-error TemplateNode doesn't include DocumentFragment, but it's actually fine
hydrate_node = template.content;
}
}

@ -66,7 +66,7 @@ export {
bind_focused
} from './dom/elements/bindings/universal.js';
export { bind_window_scroll, bind_window_size } from './dom/elements/bindings/window.js';
export { next, reset } from './dom/hydration.js';
export { hydrate_template, next, reset } from './dom/hydration.js';
export {
once,
preventDefault,

@ -0,0 +1,5 @@
import { test } from '../../test';
export default test({
expect_hydration_error: true
});

@ -0,0 +1 @@
<!--[--><h1>call <a href="tel:+636-555-3226">+636-555-3226</a> now</h1><!--]-->

@ -0,0 +1,5 @@
<script>
const message = `call +636-555-3226 now`;
</script>
<h1>{message}</h1>

@ -0,0 +1,2 @@
<!-- unrelated comment -->
<!--[--><!--[-->hello<!--]--><!--]-->

@ -113,15 +113,16 @@ const { test, run } = suite<HydrationTest>(async (config, cwd) => {
throw new Error(`Unexpected errors: ${errors.join('\n')}`);
}
if (!override) {
const expected = read(`${cwd}/_expected.html`) ?? rendered.html;
flushSync();
assert.equal(target.innerHTML.trim(), expected.trim());
}
flushSync();
const normalize = (string: string) => string.trim().replace(/\r\n/g, '\n');
const expected = read(`${cwd}/_expected.html`) ?? rendered.html;
assert.equal(normalize(target.innerHTML), normalize(expected));
if (rendered.head) {
const expected = read(`${cwd}/_expected_head.html`) ?? rendered.head;
assert.equal(head.innerHTML.trim(), expected.trim());
assert.equal(normalize(head.innerHTML), normalize(expected));
}
if (config.snapshot) {

@ -27,7 +27,9 @@ polka()
const html = transformed_template
.replace(`<!--ssr-head-->`, head)
.replace(`<!--ssr-body-->`, body);
.replace(`<!--ssr-body-->`, body)
// check that Safari doesn't break hydration
.replaceAll('+636-555-3226', '<a href="tel:+636-555-3226">+636-555-3226</a>');
res.writeHead(200, { 'Content-Type': 'text/html' }).end(html);
})

Loading…
Cancel
Save