chore: squelch warnings, robustify (#13034)

This also flagged a few places where we were doing things wrongly (createRawSnippet validation of render function was buggy, we had an unnecessary console.trace which duplicated the existing trace in the warning, and the parent/child SSR element state was getting corrupted when unrelated errors happened). The fact that the test output now looks a lot cleaner is just a nice bonus
pull/13038/head
Rich Harris 4 weeks ago committed by GitHub
parent 8cda791d5a
commit 93ffb4dbeb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -92,7 +92,7 @@ export function createRawSnippet(fn) {
var fragment = create_fragment_from_html(html); var fragment = create_fragment_from_html(html);
element = /** @type {Element} */ (get_first_child(fragment)); element = /** @type {Element} */ (get_first_child(fragment));
if (DEV && (get_next_sibling(element) !== null || element.nodeType !== 3)) { if (DEV && (get_next_sibling(element) !== null || element.nodeType !== 1)) {
w.invalid_raw_snippet_render(); w.invalid_raw_snippet_render();
} }

@ -290,10 +290,10 @@ let mounted_components = new WeakMap();
*/ */
export function unmount(component) { export function unmount(component) {
const fn = mounted_components.get(component); const fn = mounted_components.get(component);
if (DEV && !fn) {
if (fn) {
fn();
} else if (DEV) {
w.lifecycle_double_unmount(); w.lifecycle_double_unmount();
// eslint-disable-next-line no-console
console.trace('stack trace');
} }
fn?.();
} }

@ -50,6 +50,10 @@ function print_error(payload, parent, child) {
payload.head.out += `<script>console.error(${JSON.stringify(message)})</script>`; payload.head.out += `<script>console.error(${JSON.stringify(message)})</script>`;
} }
export function reset_elements() {
parent = null;
}
/** /**
* @param {Payload} payload * @param {Payload} payload
* @param {string} tag * @param {string} tag

@ -16,6 +16,7 @@ import { current_component, pop, push } from './context.js';
import { EMPTY_COMMENT, BLOCK_CLOSE, BLOCK_OPEN } from './hydration.js'; import { EMPTY_COMMENT, BLOCK_CLOSE, BLOCK_OPEN } from './hydration.js';
import { validate_store } from '../shared/validate.js'; import { validate_store } from '../shared/validate.js';
import { is_boolean_attribute, is_void } from '../../utils.js'; import { is_boolean_attribute, is_void } from '../../utils.js';
import { reset_elements } from './dev.js';
// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
// https://infra.spec.whatwg.org/#noncharacter // https://infra.spec.whatwg.org/#noncharacter
@ -100,6 +101,11 @@ export function render(component, options = {}) {
on_destroy = []; on_destroy = [];
payload.out += BLOCK_OPEN; payload.out += BLOCK_OPEN;
if (DEV) {
// prevent parent/child element state being corrupted by a bad render
reset_elements();
}
if (options.context) { if (options.context) {
push(); push();
/** @type {Component} */ (current_component).c = options.context; /** @type {Component} */ (current_component).c = options.context;

@ -2,4 +2,4 @@
export let count; export let count;
</script> </script>
<button on:click="{() => count.update(n => n + 1)}">count {$count}</button> <button on:click={() => count.update((n) => n + 1)}>count {$count}</button>

@ -15,5 +15,7 @@ export default test({
component.$destroy(); component.$destroy();
raf.tick(100); raf.tick(100);
} },
warnings: ['Tried to unmount a component that was not mounted']
}); });

@ -60,6 +60,7 @@ export interface RuntimeTest<Props extends Record<string, any> = Record<string,
}; };
logs: any[]; logs: any[];
warnings: any[]; warnings: any[];
errors: any[];
hydrate: Function; hydrate: Function;
}) => void | Promise<void>; }) => void | Promise<void>;
test_ssr?: (args: { logs: any[]; assert: Assert }) => void | Promise<void>; test_ssr?: (args: { logs: any[]; assert: Assert }) => void | Promise<void>;
@ -70,6 +71,7 @@ export interface RuntimeTest<Props extends Record<string, any> = Record<string,
error?: string; error?: string;
runtime_error?: string; runtime_error?: string;
warnings?: string[]; warnings?: string[];
errors?: string[];
expect_unhandled_rejections?: boolean; expect_unhandled_rejections?: boolean;
withoutNormalizeHtml?: boolean | 'only-strip-comments'; withoutNormalizeHtml?: boolean | 'only-strip-comments';
recover?: boolean; recover?: boolean;
@ -96,6 +98,15 @@ afterAll(() => {
process.removeListener('unhandledRejection', unhandled_rejection_handler); process.removeListener('unhandledRejection', unhandled_rejection_handler);
}); });
// eslint-disable-next-line no-console
let console_log = console.log;
// eslint-disable-next-line no-console
let console_warn = console.warn;
// eslint-disable-next-line no-console
let console_error = console.error;
export function runtime_suite(runes: boolean) { export function runtime_suite(runes: boolean) {
return suite_with_variants<RuntimeTest, 'hydrate' | 'ssr' | 'dom', CompileOptions>( return suite_with_variants<RuntimeTest, 'hydrate' | 'ssr' | 'dom', CompileOptions>(
['dom', 'hydrate', 'ssr'], ['dom', 'hydrate', 'ssr'],
@ -171,11 +182,9 @@ async function run_test_variant(
) { ) {
let unintended_error = false; let unintended_error = false;
// eslint-disable-next-line no-console
const { log, warn } = console;
let logs: string[] = []; let logs: string[] = [];
let warnings: string[] = []; let warnings: string[] = [];
let errors: string[] = [];
let manual_hydrate = false; let manual_hydrate = false;
{ {
@ -214,6 +223,13 @@ async function run_test_variant(
} }
}; };
} }
if (str.slice(0, i).includes('errors') || config.errors) {
// eslint-disable-next-line no-console
console.error = (...args) => {
errors.push(...args);
};
}
} }
try { try {
@ -311,15 +327,6 @@ async function run_test_variant(
config.before_test?.(); config.before_test?.();
// eslint-disable-next-line no-console
const error = console.error;
// eslint-disable-next-line no-console
console.error = (error) => {
if (typeof error === 'string' && error.startsWith('Hydration failed')) {
throw new Error(error);
}
};
let instance: any; let instance: any;
let props: any; let props: any;
let hydrate_fn: Function = () => { let hydrate_fn: Function = () => {
@ -357,9 +364,6 @@ async function run_test_variant(
}); });
} }
// eslint-disable-next-line no-console
console.error = error;
if (config.error) { if (config.error) {
unintended_error = true; unintended_error = true;
assert.fail('Expected a runtime error'); assert.fail('Expected a runtime error');
@ -395,6 +399,7 @@ async function run_test_variant(
compileOptions, compileOptions,
logs, logs,
warnings, warnings,
errors,
hydrate: hydrate_fn hydrate: hydrate_fn
}); });
} }
@ -412,12 +417,20 @@ async function run_test_variant(
if (config.warnings) { if (config.warnings) {
assert.deepEqual(warnings, config.warnings); assert.deepEqual(warnings, config.warnings);
} else if (warnings.length && console.warn === warn) { } else if (warnings.length && console.warn === console_warn) {
unintended_error = true; unintended_error = true;
warn.apply(console, warnings); console_warn.apply(console, warnings);
assert.fail('Received unexpected warnings'); assert.fail('Received unexpected warnings');
} }
if (config.errors) {
assert.deepEqual(errors, config.errors);
} else if (errors.length && console.error === console_error) {
unintended_error = true;
console_error.apply(console, errors);
assert.fail('Received unexpected errors');
}
assert_html_equal( assert_html_equal(
target.innerHTML, target.innerHTML,
'', '',
@ -440,8 +453,9 @@ async function run_test_variant(
throw err; throw err;
} }
} finally { } finally {
console.log = log; console.log = console_log;
console.warn = warn; console.warn = console_warn;
console.error = console_error;
config.after_test?.(); config.after_test?.();

@ -3,11 +3,18 @@ import { test } from '../../test';
export default test({ export default test({
html: '<p><p>invalid</p></p>', html: '<p><p>invalid</p></p>',
mode: ['hydrate'], mode: ['hydrate'],
recover: true, recover: true,
test({ assert, target, logs }) { test({ assert, target, logs }) {
target.click(); target.click();
flushSync(); flushSync();
assert.deepEqual(logs, ['body', 'document', 'window']); assert.deepEqual(logs, ['body', 'document', 'window']);
} },
warnings: [
'Hydration failed because the initial UI does not match what was rendered on the server'
]
}); });

@ -1,12 +1,5 @@
import { test } from '../../test'; import { test } from '../../test';
let console_error = console.error;
/**
* @type {any[]}
*/
const log = [];
export default test({ export default test({
compileOptions: { compileOptions: {
dev: true dev: true
@ -16,27 +9,14 @@ export default test({
recover: true, recover: true,
before_test() { mode: ['hydrate'],
console.error = (x) => {
log.push(x);
};
},
after_test() { errors: [
console.error = console_error; 'node_invalid_placement_ssr: `<p>` (main.svelte:6:0) cannot contain `<h1>` (h1.svelte:1:0)\n\nThis can cause content to shift around as the browser repairs the HTML, and will likely result in a `hydration_mismatch` warning.',
log.length = 0; 'node_invalid_placement_ssr: `<form>` (main.svelte:9:0) cannot contain `<form>` (form.svelte:1:0)\n\nThis can cause content to shift around as the browser repairs the HTML, and will likely result in a `hydration_mismatch` warning.'
}, ],
async test({ assert, variant }) { warnings: [
if (variant === 'hydrate') { 'Hydration failed because the initial UI does not match what was rendered on the server'
assert.equal( ]
log[0].split('\n')[0],
'node_invalid_placement_ssr: `<p>` (main.svelte:6:0) cannot contain `<h1>` (h1.svelte:1:0)'
);
assert.equal(
log[1].split('\n')[0],
'node_invalid_placement_ssr: `<form>` (main.svelte:9:0) cannot contain `<form>` (form.svelte:1:0)'
);
}
}
}); });

@ -3,5 +3,9 @@ import { test } from '../../test';
export default test({ export default test({
compileOptions: { compileOptions: {
dev: true dev: true
} },
errors: [
'node_invalid_placement_ssr: `<p>` (packages/svelte/tests/server-side-rendering/samples/invalid-nested-svelte-element/main.svelte:1:0) cannot contain `<p>` (packages/svelte/tests/server-side-rendering/samples/invalid-nested-svelte-element/main.svelte:2:1)\n\nThis can cause content to shift around as the browser repairs the HTML, and will likely result in a `hydration_mismatch` warning.'
]
}); });

@ -16,11 +16,21 @@ interface SSRTest extends BaseTest {
compileOptions?: Partial<CompileOptions>; compileOptions?: Partial<CompileOptions>;
props?: Record<string, any>; props?: Record<string, any>;
withoutNormalizeHtml?: boolean; withoutNormalizeHtml?: boolean;
errors?: string[];
} }
// eslint-disable-next-line no-console
let console_error = console.error;
const { test, run } = suite<SSRTest>(async (config, test_dir) => { const { test, run } = suite<SSRTest>(async (config, test_dir) => {
await compile_directory(test_dir, 'server', config.compileOptions); await compile_directory(test_dir, 'server', config.compileOptions);
const errors: string[] = [];
console.error = (...args) => {
errors.push(...args);
};
const Component = (await import(`${test_dir}/_output/server/main.svelte.js`)).default; const Component = (await import(`${test_dir}/_output/server/main.svelte.js`)).default;
const expected_html = try_read_file(`${test_dir}/_expected.html`); const expected_html = try_read_file(`${test_dir}/_expected.html`);
const rendered = render(Component, { props: config.props || {} }); const rendered = render(Component, { props: config.props || {} });
@ -64,6 +74,12 @@ const { test, run } = suite<SSRTest>(async (config, test_dir) => {
} }
} }
} }
if (errors.length > 0) {
assert.deepEqual(config.errors, errors);
}
console.error = console_error;
}); });
export { test }; export { test };

Loading…
Cancel
Save