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);
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();
}

@ -290,10 +290,10 @@ let mounted_components = new WeakMap();
*/
export function unmount(component) {
const fn = mounted_components.get(component);
if (DEV && !fn) {
if (fn) {
fn();
} else if (DEV) {
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>`;
}
export function reset_elements() {
parent = null;
}
/**
* @param {Payload} payload
* @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 { validate_store } from '../shared/validate.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://infra.spec.whatwg.org/#noncharacter
@ -100,6 +101,11 @@ export function render(component, options = {}) {
on_destroy = [];
payload.out += BLOCK_OPEN;
if (DEV) {
// prevent parent/child element state being corrupted by a bad render
reset_elements();
}
if (options.context) {
push();
/** @type {Component} */ (current_component).c = options.context;

@ -2,4 +2,4 @@
export let count;
</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();
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[];
warnings: any[];
errors: any[];
hydrate: Function;
}) => 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;
runtime_error?: string;
warnings?: string[];
errors?: string[];
expect_unhandled_rejections?: boolean;
withoutNormalizeHtml?: boolean | 'only-strip-comments';
recover?: boolean;
@ -96,6 +98,15 @@ afterAll(() => {
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) {
return suite_with_variants<RuntimeTest, 'hydrate' | 'ssr' | 'dom', CompileOptions>(
['dom', 'hydrate', 'ssr'],
@ -171,11 +182,9 @@ async function run_test_variant(
) {
let unintended_error = false;
// eslint-disable-next-line no-console
const { log, warn } = console;
let logs: string[] = [];
let warnings: string[] = [];
let errors: string[] = [];
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 {
@ -311,15 +327,6 @@ async function run_test_variant(
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 props: any;
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) {
unintended_error = true;
assert.fail('Expected a runtime error');
@ -395,6 +399,7 @@ async function run_test_variant(
compileOptions,
logs,
warnings,
errors,
hydrate: hydrate_fn
});
}
@ -412,12 +417,20 @@ async function run_test_variant(
if (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;
warn.apply(console, warnings);
console_warn.apply(console, 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(
target.innerHTML,
'',
@ -440,8 +453,9 @@ async function run_test_variant(
throw err;
}
} finally {
console.log = log;
console.warn = warn;
console.log = console_log;
console.warn = console_warn;
console.error = console_error;
config.after_test?.();

@ -3,11 +3,18 @@ import { test } from '../../test';
export default test({
html: '<p><p>invalid</p></p>',
mode: ['hydrate'],
recover: true,
test({ assert, target, logs }) {
target.click();
flushSync();
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';
let console_error = console.error;
/**
* @type {any[]}
*/
const log = [];
export default test({
compileOptions: {
dev: true
@ -16,27 +9,14 @@ export default test({
recover: true,
before_test() {
console.error = (x) => {
log.push(x);
};
},
mode: ['hydrate'],
after_test() {
console.error = console_error;
log.length = 0;
},
errors: [
'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.',
'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 }) {
if (variant === 'hydrate') {
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)'
);
}
}
warnings: [
'Hydration failed because the initial UI does not match what was rendered on the server'
]
});

@ -3,5 +3,9 @@ import { test } from '../../test';
export default test({
compileOptions: {
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>;
props?: Record<string, any>;
withoutNormalizeHtml?: boolean;
errors?: string[];
}
// eslint-disable-next-line no-console
let console_error = console.error;
const { test, run } = suite<SSRTest>(async (config, test_dir) => {
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 expected_html = try_read_file(`${test_dir}/_expected.html`);
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 };

Loading…
Cancel
Save