From 3df9d8466f712be14c99542f6a696f96db087b4d Mon Sep 17 00:00:00 2001
From: gtmnayan <50981692+gtm-nayan@users.noreply.github.com>
Date: Mon, 22 May 2023 19:31:27 +0545
Subject: [PATCH] chore: speed up test execution (#8598)
- shard runtime tests for better use of Vite's test parallelization
- merge custom element and other browser tests to run in one test suite and use esbuild in it
- on some setups only generate code output when test fails
---
.gitignore | 1 +
.prettierignore | 1 +
package.json | 1 +
pnpm-lock.yaml | 3 +
test/css/css.test.js | 4 +-
test/custom-elements/assert.js | 50 ---
test/custom-elements/custom-elements.test.js | 118 ------
test/helpers.js | 2 +-
test/js/js-output.test.js | 2 +-
test/parser/parser.test.js | 2 +-
test/preprocess/preprocess.test.js | 4 +-
test/runtime-browser/browser.test.js | 364 ++++++++++--------
.../$$props/main.svelte | 0
.../custom-elements-samples}/$$props/test.js | 0
.../$$slot-dynamic-content/main.svelte | 0
.../$$slot-dynamic-content/my-widget.svelte | 0
.../$$slot-dynamic-content/test.js | 0
.../$$slot/main.svelte | 0
.../custom-elements-samples}/$$slot/test.js | 0
.../action/main.svelte | 0
.../custom-elements-samples}/action/test.js | 0
.../camel-case-attribute/main.svelte | 0
.../camel-case-attribute/test.js | 0
.../ce-options-valid/main.svelte | 0
.../ce-options-valid/test.js | 0
.../custom-method/main.svelte | 0
.../custom-method/test.js | 0
.../escaped-css/main.svelte | 0
.../escaped-css/test.js | 0
.../events/main.svelte | 0
.../custom-elements-samples}/events/test.js | 0
.../extended-builtin/_config.js | 0
.../extended-builtin/custom-button.js | 0
.../extended-builtin/main.svelte | 0
.../extended-builtin/test.js | 0
.../html-slots/main.svelte | 0
.../html-slots/test.js | 0
.../custom-elements-samples}/html/main.svelte | 0
.../custom-elements-samples}/html/test.js | 0
.../nested/Counter.svelte | 0
.../nested/main.svelte | 0
.../custom-elements-samples}/nested/test.js | 0
.../new-styled/main.svelte | 0
.../new-styled/test.js | 0
.../no-missing-prop-warnings/_config.js | 0
.../no-missing-prop-warnings/main.svelte | 0
.../no-missing-prop-warnings/test.js | 0
.../no-shadow-dom/main.svelte | 0
.../no-shadow-dom/test.js | 0
.../no-tag/_config.js | 0
.../no-tag/main.svelte | 0
.../custom-elements-samples}/no-tag/test.js | 0
.../oncreate/main.svelte | 0
.../custom-elements-samples}/oncreate/test.js | 0
.../ondestroy/main.svelte | 0
.../ondestroy/test.js | 0
.../props/main.svelte | 0
.../props/my-widget.svelte | 0
.../custom-elements-samples}/props/test.js | 0
.../reflect-attributes/main.svelte | 0
.../reflect-attributes/my-widget.svelte | 0
.../reflect-attributes/test.js | 0
test/runtime/runtime.shared.js | 272 +++++++++++++
test/runtime/runtime.test.js | 292 --------------
test/runtime/runtime_base.test.js | 22 ++
test/server-side-rendering/ssr-2.test.js | 50 ++-
test/sourcemaps/sourcemaps.test.js | 4 +-
test/stats/stats.test.js | 2 +-
test/validator/validator.test.js | 2 +-
test/vars/vars.test.js | 2 +-
test/vitest-global-setup.js | 27 ++
vitest.config.js | 10 +-
72 files changed, 579 insertions(+), 656 deletions(-)
delete mode 100644 test/custom-elements/assert.js
delete mode 100644 test/custom-elements/custom-elements.test.js
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/$$props/main.svelte (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/$$props/test.js (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/$$slot-dynamic-content/main.svelte (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/$$slot-dynamic-content/my-widget.svelte (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/$$slot-dynamic-content/test.js (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/$$slot/main.svelte (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/$$slot/test.js (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/action/main.svelte (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/action/test.js (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/camel-case-attribute/main.svelte (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/camel-case-attribute/test.js (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/ce-options-valid/main.svelte (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/ce-options-valid/test.js (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/custom-method/main.svelte (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/custom-method/test.js (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/escaped-css/main.svelte (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/escaped-css/test.js (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/events/main.svelte (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/events/test.js (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/extended-builtin/_config.js (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/extended-builtin/custom-button.js (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/extended-builtin/main.svelte (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/extended-builtin/test.js (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/html-slots/main.svelte (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/html-slots/test.js (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/html/main.svelte (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/html/test.js (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/nested/Counter.svelte (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/nested/main.svelte (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/nested/test.js (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/new-styled/main.svelte (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/new-styled/test.js (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/no-missing-prop-warnings/_config.js (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/no-missing-prop-warnings/main.svelte (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/no-missing-prop-warnings/test.js (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/no-shadow-dom/main.svelte (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/no-shadow-dom/test.js (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/no-tag/_config.js (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/no-tag/main.svelte (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/no-tag/test.js (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/oncreate/main.svelte (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/oncreate/test.js (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/ondestroy/main.svelte (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/ondestroy/test.js (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/props/main.svelte (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/props/my-widget.svelte (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/props/test.js (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/reflect-attributes/main.svelte (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/reflect-attributes/my-widget.svelte (100%)
rename test/{custom-elements/samples => runtime-browser/custom-elements-samples}/reflect-attributes/test.js (100%)
create mode 100644 test/runtime/runtime.shared.js
delete mode 100644 test/runtime/runtime.test.js
create mode 100644 test/runtime/runtime_base.test.js
create mode 100644 test/vitest-global-setup.js
diff --git a/.gitignore b/.gitignore
index 2231de9065..963ae9f26e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,6 +17,7 @@ node_modules
/animate
/scratch/
/test/*/samples/_
+/test/runtime/shards
/yarn-error.log
_actual*.*
_output
diff --git a/.prettierignore b/.prettierignore
index 15f58330d5..0b3348e52f 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -12,3 +12,4 @@ src/compiler/compile/internal_exports.js
/test/**/expected*
/test/**/_output
/types
+!vitest.config.js
diff --git a/package.json b/package.json
index b66101ed2d..b94bebb49b 100644
--- a/package.json
+++ b/package.json
@@ -132,6 +132,7 @@
"axobject-query": "^3.1.1",
"code-red": "^1.0.0",
"css-tree": "^2.3.1",
+ "esbuild": "^0.17.19",
"eslint": "^8.40.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.27.5",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a22326634d..51ce202cdf 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -67,6 +67,9 @@ devDependencies:
css-tree:
specifier: ^2.3.1
version: 2.3.1
+ esbuild:
+ specifier: ^0.17.19
+ version: 0.17.19
eslint:
specifier: ^8.40.0
version: 8.40.0
diff --git a/test/css/css.test.js b/test/css/css.test.js
index 54f3600f5a..eed4862e60 100644
--- a/test/css/css.test.js
+++ b/test/css/css.test.js
@@ -2,7 +2,7 @@
import * as fs from 'node:fs';
import { assert, describe, it } from 'vitest';
-import * as svelte from '../../src/compiler/index.js';
+import * as svelte from 'svelte/compiler';
import { create_loader, should_update_expected, try_load_config } from '../helpers.js';
import { assert_html_equal } from '../html_equal.js';
@@ -116,7 +116,7 @@ describe('css', () => {
});
function replace_css_hash(str) {
- return str.replace(/svelte(-ref)?-[a-z0-9]+/g, (m, $1) => ($1 ? m : 'svelte-xyz'));
+ return str.replace(/svelte-[a-z0-9]+/g, 'svelte-xyz');
}
function read(file) {
diff --git a/test/custom-elements/assert.js b/test/custom-elements/assert.js
deleted file mode 100644
index e4d6f02f33..0000000000
--- a/test/custom-elements/assert.js
+++ /dev/null
@@ -1,50 +0,0 @@
-export function deepEqual(a, b, message) {
- if (!is_equal(a, b)) {
- throw new Error(message || `Expected ${JSON.stringify(a)} to equal ${JSON.stringify(b)}`);
- }
-}
-
-function is_equal(a, b) {
- if (a && typeof a === 'object') {
- const is_array = Array.isArray(a);
- if (Array.isArray(b) !== is_array) return false;
-
- if (is_array) {
- if (a.length !== b.length) return false;
- return a.every((value, i) => is_equal(value, b[i]));
- }
-
- const a_keys = Object.keys(a).sort();
- const b_keys = Object.keys(b).sort();
- if (a_keys.join(',') !== b_keys.join(',')) return false;
-
- return a_keys.every((key) => is_equal(a[key], b[key]));
- }
-
- return a === b;
-}
-
-export function equal(a, b, message) {
- if (a != b) throw new Error(message || `Expected ${a} to equal ${b}`);
-}
-
-export function ok(condition, message) {
- if (!condition) throw new Error(message || `Expected ${condition} to be truthy`);
-}
-
-export function htmlEqual(actual, expected, message) {
- return deepEqual(normalizeHtml(window, actual), normalizeHtml(window, expected), message);
-}
-
-function normalizeHtml(window, html) {
- try {
- const node = window.document.createElement('div');
- node.innerHTML = html
- .replace(//g, '')
- .replace(/>[\s\r\n]+<')
- .trim();
- return node.innerHTML.replace(/<\/?noscript\/?>/g, '');
- } catch (err) {
- throw new Error(`Failed to normalize HTML:\n${html}`);
- }
-}
diff --git a/test/custom-elements/custom-elements.test.js b/test/custom-elements/custom-elements.test.js
deleted file mode 100644
index 2083a39951..0000000000
--- a/test/custom-elements/custom-elements.test.js
+++ /dev/null
@@ -1,118 +0,0 @@
-import { chromium } from '@playwright/test';
-import * as fs from 'node:fs';
-import * as path from 'node:path';
-import { rollup } from 'rollup';
-import { try_load_config } from '../helpers.js';
-import * as svelte from '../../src/compiler/index.js';
-import { beforeAll, describe, afterAll, assert, it } from 'vitest';
-
-const internal = path.resolve('src/runtime/internal/index.js');
-const index = path.resolve('src/runtime/index.js');
-
-const browser_assert = fs.readFileSync(`${__dirname}/assert.js`, 'utf-8');
-
-describe(
- 'custom-elements',
- () => {
- /** @type {import('@playwright/test').Browser} */
- let browser;
-
- beforeAll(async () => {
- browser = await chromium.launch();
- console.log('[custom-elements] Launched browser');
- }, 20000);
-
- afterAll(async () => {
- if (browser) await browser.close();
- });
-
- fs.readdirSync(`${__dirname}/samples`).forEach((dir) => {
- if (dir[0] === '.') return;
-
- const solo = /\.solo$/.test(dir);
- const skip = /\.skip$/.test(dir);
-
- const warnings = [];
- const it_fn = solo ? it.only : skip ? it.skip : it;
-
- it_fn(dir, async () => {
- // TODO: Vitest currently doesn't register a watcher because the import is hidden
- const config = await try_load_config(`${__dirname}/samples/${dir}/_config.js`);
-
- const expected_warnings = config.warnings || [];
-
- const bundle = await rollup({
- input: `${__dirname}/samples/${dir}/test.js`,
- plugins: [
- {
- name: 'plugin-resolve-svelte',
- resolveId(importee) {
- if (importee === 'svelte/internal' || importee === './internal') {
- return internal;
- }
-
- if (importee === 'svelte') {
- return index;
- }
-
- if (importee === 'assert.js') {
- return '\0virtual:assert';
- }
- },
-
- load(id) {
- if (id === '\0virtual:assert') return browser_assert;
- },
-
- transform(code, id) {
- if (id.endsWith('.svelte')) {
- const compiled = svelte.compile(code.replace(/\r/g, ''), {
- customElement: true,
- dev: config.dev
- });
-
- compiled.warnings.forEach((w) => warnings.push(w));
-
- return compiled.js;
- }
- }
- }
- ]
- });
-
- const generated_bundle = await bundle.generate({ format: 'iife', name: 'test' });
-
- function assertWarnings() {
- if (expected_warnings) {
- assert.deepStrictEqual(
- warnings.map((w) => ({
- code: w.code,
- message: w.message,
- pos: w.pos,
- start: w.start,
- end: w.end
- })),
- expected_warnings
- );
- }
- }
-
- const page = await browser.newPage();
- page.on('console', (type) => {
- console[type.type()](type.text());
- });
- await page.setContent('');
- await page.evaluate(generated_bundle.output[0].code);
- const test_result = await page.evaluate("test(document.querySelector('main'))");
-
- if (test_result) console.log(test_result);
-
- assertWarnings();
-
- await page.close();
- });
- });
- },
- // Browser tests are brittle and slow on CI
- { timeout: 20000, retry: process.env.CI ? 1 : 0 }
-);
diff --git a/test/helpers.js b/test/helpers.js
index 112c5c28bd..5dfa80e542 100644
--- a/test/helpers.js
+++ b/test/helpers.js
@@ -3,7 +3,7 @@ import * as path from 'node:path';
import glob from 'tiny-glob/sync';
import colors from 'kleur';
import { assert } from 'vitest';
-import { compile } from '../src/compiler/index.js';
+import { compile } from 'svelte/compiler';
import { fileURLToPath } from 'node:url';
export function try_load_json(file) {
diff --git a/test/js/js-output.test.js b/test/js/js-output.test.js
index 3fd2b106cb..e608d5cbdf 100644
--- a/test/js/js-output.test.js
+++ b/test/js/js-output.test.js
@@ -2,7 +2,7 @@ import * as fs from 'node:fs';
import * as path from 'node:path';
import { describe, it, assert } from 'vitest';
import { try_load_config, should_update_expected } from '../helpers.js';
-import * as svelte from '../../src/compiler/index.js';
+import * as svelte from 'svelte/compiler';
describe('js-output', () => {
fs.readdirSync(`${__dirname}/samples`).forEach((dir) => {
diff --git a/test/parser/parser.test.js b/test/parser/parser.test.js
index 0ac62ab070..67a621582a 100644
--- a/test/parser/parser.test.js
+++ b/test/parser/parser.test.js
@@ -1,6 +1,6 @@
import * as fs from 'node:fs';
import { assert, describe, it } from 'vitest';
-import * as svelte from '../../src/compiler/index.js';
+import * as svelte from 'svelte/compiler';
import { try_load_json } from '../helpers.js';
describe('parse', () => {
diff --git a/test/preprocess/preprocess.test.js b/test/preprocess/preprocess.test.js
index 8f7e368bb7..1615468ee0 100644
--- a/test/preprocess/preprocess.test.js
+++ b/test/preprocess/preprocess.test.js
@@ -1,7 +1,7 @@
import * as fs from 'node:fs';
-import * as svelte from '../../src/compiler/index.js';
-import { try_load_config } from '../helpers.js';
+import * as svelte from 'svelte/compiler';
import { describe, it } from 'vitest';
+import { try_load_config } from '../helpers.js';
const samples = fs.readdirSync(`${__dirname}/samples`);
diff --git a/test/runtime-browser/browser.test.js b/test/runtime-browser/browser.test.js
index a84ec67244..09eb43cf0b 100644
--- a/test/runtime-browser/browser.test.js
+++ b/test/runtime-browser/browser.test.js
@@ -1,173 +1,227 @@
import { chromium } from '@playwright/test';
+import { build } from 'esbuild';
import * as fs from 'node:fs';
import * as path from 'node:path';
-import { rollup } from 'rollup';
+import * as svelte from 'svelte/compiler';
+import { afterAll, assert, beforeAll, describe, it } from 'vitest';
import { pretty_print_browser_assertion, try_load_config } from '../helpers.js';
-import * as svelte from '../../src/compiler/index.js';
-import { beforeAll, describe, afterAll, assert } from 'vitest';
const internal = path.resolve('src/runtime/internal/index.js');
const index = path.resolve('src/runtime/index.js');
-const main = fs.readFileSync(`${__dirname}/driver.js`, 'utf-8');
-const browser_assert = fs.readFileSync(`${__dirname}/assert.js`, 'utf-8');
+/** @type {import('@playwright/test').Browser} */
+let browser;
-describe(
+beforeAll(async () => {
+ browser = await chromium.launch();
+ console.log('[runtime-browser] Launched browser');
+}, 20000);
+
+afterAll(async () => {
+ if (browser) await browser.close();
+});
+
+describe.concurrent(
'runtime (browser)',
- async (it) => {
- /** @type {import('@playwright/test').Browser} */
- let browser;
-
- beforeAll(async () => {
- browser = await chromium.launch();
- console.log('[runtime-browser] Launched browser');
- });
-
- afterAll(async () => {
- if (browser) await browser.close();
- });
-
- const failed = new Set();
-
- async function runTest(dir, hydrate) {
- if (dir[0] === '.') return;
-
- // TODO: Vitest currently doesn't register a watcher because the import is hidden
- const config = await try_load_config(`${__dirname}/samples/${dir}/_config.js`);
- const solo = config.solo || /\.solo/.test(dir);
- const skip = config.skip || /\.skip/.test(dir);
-
- if (hydrate && config.skip_if_hydrate) return;
-
- const it_fn = skip ? it.skip : solo ? it.only : it;
-
- it_fn(`${dir} ${hydrate ? '(with hydration)' : ''}`, async () => {
- if (failed.has(dir)) {
- // this makes debugging easier, by only printing compiled output once
- throw new Error('skipping test, already failed');
- }
-
- const warnings = [];
-
- const bundle = await rollup({
- input: 'main',
- plugins: [
- {
- name: 'testing-runtime-browser',
- resolveId(importee) {
- if (importee === 'svelte/internal' || importee === './internal') {
- return internal;
- }
-
- if (importee === 'svelte') {
- return index;
- }
-
- if (importee === 'main') {
- return '\0virtual:main';
- }
-
- if (importee === 'assert.js') {
- return '\0virtual:assert';
- }
-
- if (importee === '__MAIN_DOT_SVELTE__') {
- return path.resolve(__dirname, 'samples', dir, 'main.svelte');
- }
-
- if (importee === '__CONFIG__') {
- return path.resolve(__dirname, 'samples', dir, '_config.js');
- }
- },
- load(id) {
- if (id === '\0virtual:assert') return browser_assert;
-
- if (id === '\0virtual:main') {
- return main.replace('__HYDRATE__', hydrate ? 'true' : 'false');
- }
- return null;
- },
- transform(code, id) {
- if (id.endsWith('.svelte')) {
- const compiled = svelte.compile(code.replace(/\r/g, ''), {
- ...config.compileOptions,
- hydratable: hydrate,
- immutable: config.immutable,
- accessors: 'accessors' in config ? config.accessors : true
- });
-
- const out_dir = `${__dirname}/samples/${dir}/_output/${
- hydrate ? 'hydratable' : 'normal'
- }`;
- const out = `${out_dir}/${path.basename(id).replace(/\.svelte$/, '.js')}`;
-
- if (fs.existsSync(out)) {
- fs.unlinkSync(out);
- }
- if (!fs.existsSync(out_dir)) {
- fs.mkdirSync(out_dir, { recursive: true });
- }
-
- fs.writeFileSync(out, compiled.js.code, 'utf8');
-
- compiled.warnings.forEach((w) => warnings.push(w));
-
- return compiled.js;
- }
- }
- }
- ]
- });
-
- const generated_bundle = await bundle.generate({ format: 'iife', name: 'test' });
-
- function assertWarnings() {
- if (config.warnings) {
- assert.deepStrictEqual(
- warnings.map((w) => ({
- code: w.code,
- message: w.message,
- pos: w.pos,
- start: w.start,
- end: w.end
- })),
- config.warnings
- );
- } else if (warnings.length) {
- failed.add(dir);
- /* eslint-disable no-unsafe-finally */
- throw new Error('Received unexpected warnings');
- }
- }
-
- try {
- const page = await browser.newPage();
- page.on('console', (type) => {
- console[type.type()](type.text());
- });
- await page.setContent('');
- await page.evaluate(generated_bundle.output[0].code);
- const test_result = await page.evaluate("test(document.querySelector('main'))");
-
- if (test_result) console.log(test_result);
- assertWarnings();
- await page.close();
- } catch (err) {
- failed.add(dir);
- pretty_print_browser_assertion(err.message);
- assertWarnings();
- throw err;
- }
- });
- }
-
+ async () => {
await Promise.all(
fs.readdirSync(`${__dirname}/samples`).map(async (dir) => {
- await runTest(dir, false);
- await runTest(dir, true);
+ await run_browser_test(dir);
})
);
},
// Browser tests are brittle and slow on CI
{ timeout: 20000, retry: process.env.CI ? 1 : 0 }
);
+
+async function run_browser_test(dir) {
+ if (dir[0] === '.') return;
+
+ const cwd = `${__dirname}/samples/${dir}`;
+
+ // TODO: Vitest currently doesn't register a watcher because the import is hidden
+ const config = await try_load_config(`${cwd}/_config.js`);
+ const solo = config.solo || /\.solo/.test(dir);
+ const skip = config.skip || /\.skip/.test(dir);
+
+ const it_fn = skip ? it.skip : solo ? it.only : it;
+
+ let failed = false;
+ it_fn.each([false, true])(`${dir} hydrate: %s`, async (hydrate) => {
+ if (hydrate && config.skip_if_hydrate) return;
+ if (failed) {
+ // this makes debugging easier, by only printing compiled output once
+ assert.fail('skipping test, already failed');
+ }
+
+ const warnings = [];
+
+ const build_result = await build({
+ entryPoints: [`${__dirname}/driver.js`],
+ write: false,
+ alias: {
+ __MAIN_DOT_SVELTE__: path.resolve(__dirname, 'samples', dir, 'main.svelte'),
+ __CONFIG__: path.resolve(__dirname, 'samples', dir, '_config.js'),
+ 'assert.js': path.resolve(__dirname, 'assert.js'),
+ 'svelte/internal': internal,
+ svelte: index
+ },
+ plugins: [
+ {
+ name: 'testing-runtime-browser',
+ setup(build) {
+ build.onLoad({ filter: /\.svelte$/ }, ({ path }) => {
+ const compiled = svelte.compile(fs.readFileSync(path, 'utf-8').replace(/\r/g, ''), {
+ ...config.compileOptions,
+ hydratable: hydrate,
+ immutable: config.immutable,
+ accessors: 'accessors' in config ? config.accessors : true
+ });
+
+ compiled.warnings.forEach((warning) => warnings.push(warning));
+
+ return {
+ contents: compiled.js.code,
+ loader: 'js'
+ };
+ });
+ }
+ }
+ ],
+ define: {
+ __HYDRATE__: hydrate ? 'true' : 'false'
+ },
+ bundle: true,
+ format: 'iife',
+ globalName: 'test'
+ });
+
+ function assertWarnings() {
+ if (config.warnings) {
+ assert.deepStrictEqual(
+ warnings.map((w) => ({
+ code: w.code,
+ message: w.message,
+ pos: w.pos,
+ start: w.start,
+ end: w.end
+ })),
+ config.warnings
+ );
+ } else if (warnings.length) {
+ failed = true;
+ /* eslint-disable no-unsafe-finally */
+ throw new Error('Received unexpected warnings');
+ }
+ }
+
+ assertWarnings();
+
+ try {
+ const page = await browser.newPage();
+ page.on('console', (type) => {
+ console[type.type()](type.text());
+ });
+ await page.setContent('');
+ await page.evaluate(build_result.outputFiles[0].text);
+ const test_result = await page.evaluate("test.default(document.querySelector('main'))");
+
+ if (test_result) console.log(test_result);
+ await page.close();
+ } catch (err) {
+ failed = true;
+ pretty_print_browser_assertion(err.message);
+ throw err;
+ }
+ });
+}
+
+describe.concurrent(
+ 'custom-elements',
+ async () => {
+ await Promise.all(
+ fs
+ .readdirSync(`${__dirname}/custom-elements-samples`)
+ .map((dir) => run_custom_elements_test(dir))
+ );
+ },
+ // Browser tests are brittle and slow on CI
+ { timeout: 20000, retry: process.env.CI ? 1 : 0 }
+);
+
+async function run_custom_elements_test(dir) {
+ if (dir[0] === '.') return;
+ const cwd = `${__dirname}/custom-elements-samples/${dir}`;
+
+ const solo = /\.solo$/.test(dir);
+ const skip = /\.skip$/.test(dir);
+
+ const warnings = [];
+ const it_fn = solo ? it.only : skip ? it.skip : it;
+
+ it_fn(dir, async () => {
+ // TODO: Vitest currently doesn't register a watcher because the import is hidden
+ const config = await try_load_config(`${cwd}/_config.js`);
+
+ const expected_warnings = config.warnings || [];
+
+ const build_result = await build({
+ entryPoints: [`${cwd}/test.js`],
+ write: false,
+ alias: {
+ 'assert.js': path.resolve(__dirname, 'assert.js'),
+ 'svelte/internal': internal,
+ svelte: index
+ },
+ plugins: [
+ {
+ name: 'testing-runtime-browser',
+ setup(build) {
+ build.onLoad({ filter: /\.svelte$/ }, ({ path }) => {
+ const compiled = svelte.compile(fs.readFileSync(path, 'utf-8').replace(/\r/g, ''), {
+ customElement: true,
+ dev: config.dev
+ });
+ compiled.warnings.forEach((w) => warnings.push(w));
+ return {
+ contents: compiled.js.code,
+ loader: 'js'
+ };
+ });
+ }
+ }
+ ],
+ bundle: true,
+ format: 'iife',
+ globalName: 'test'
+ });
+
+ function assertWarnings() {
+ if (expected_warnings) {
+ assert.deepStrictEqual(
+ warnings.map((w) => ({
+ code: w.code,
+ message: w.message,
+ pos: w.pos,
+ start: w.start,
+ end: w.end
+ })),
+ expected_warnings
+ );
+ }
+ }
+ assertWarnings();
+
+ const page = await browser.newPage();
+ page.on('console', (type) => {
+ console[type.type()](type.text());
+ });
+ await page.setContent('');
+ await page.evaluate(build_result.outputFiles[0].text);
+ const test_result = await page.evaluate("test.default(document.querySelector('main'))");
+
+ if (test_result) console.log(test_result);
+
+ await page.close();
+ });
+}
diff --git a/test/custom-elements/samples/$$props/main.svelte b/test/runtime-browser/custom-elements-samples/$$props/main.svelte
similarity index 100%
rename from test/custom-elements/samples/$$props/main.svelte
rename to test/runtime-browser/custom-elements-samples/$$props/main.svelte
diff --git a/test/custom-elements/samples/$$props/test.js b/test/runtime-browser/custom-elements-samples/$$props/test.js
similarity index 100%
rename from test/custom-elements/samples/$$props/test.js
rename to test/runtime-browser/custom-elements-samples/$$props/test.js
diff --git a/test/custom-elements/samples/$$slot-dynamic-content/main.svelte b/test/runtime-browser/custom-elements-samples/$$slot-dynamic-content/main.svelte
similarity index 100%
rename from test/custom-elements/samples/$$slot-dynamic-content/main.svelte
rename to test/runtime-browser/custom-elements-samples/$$slot-dynamic-content/main.svelte
diff --git a/test/custom-elements/samples/$$slot-dynamic-content/my-widget.svelte b/test/runtime-browser/custom-elements-samples/$$slot-dynamic-content/my-widget.svelte
similarity index 100%
rename from test/custom-elements/samples/$$slot-dynamic-content/my-widget.svelte
rename to test/runtime-browser/custom-elements-samples/$$slot-dynamic-content/my-widget.svelte
diff --git a/test/custom-elements/samples/$$slot-dynamic-content/test.js b/test/runtime-browser/custom-elements-samples/$$slot-dynamic-content/test.js
similarity index 100%
rename from test/custom-elements/samples/$$slot-dynamic-content/test.js
rename to test/runtime-browser/custom-elements-samples/$$slot-dynamic-content/test.js
diff --git a/test/custom-elements/samples/$$slot/main.svelte b/test/runtime-browser/custom-elements-samples/$$slot/main.svelte
similarity index 100%
rename from test/custom-elements/samples/$$slot/main.svelte
rename to test/runtime-browser/custom-elements-samples/$$slot/main.svelte
diff --git a/test/custom-elements/samples/$$slot/test.js b/test/runtime-browser/custom-elements-samples/$$slot/test.js
similarity index 100%
rename from test/custom-elements/samples/$$slot/test.js
rename to test/runtime-browser/custom-elements-samples/$$slot/test.js
diff --git a/test/custom-elements/samples/action/main.svelte b/test/runtime-browser/custom-elements-samples/action/main.svelte
similarity index 100%
rename from test/custom-elements/samples/action/main.svelte
rename to test/runtime-browser/custom-elements-samples/action/main.svelte
diff --git a/test/custom-elements/samples/action/test.js b/test/runtime-browser/custom-elements-samples/action/test.js
similarity index 100%
rename from test/custom-elements/samples/action/test.js
rename to test/runtime-browser/custom-elements-samples/action/test.js
diff --git a/test/custom-elements/samples/camel-case-attribute/main.svelte b/test/runtime-browser/custom-elements-samples/camel-case-attribute/main.svelte
similarity index 100%
rename from test/custom-elements/samples/camel-case-attribute/main.svelte
rename to test/runtime-browser/custom-elements-samples/camel-case-attribute/main.svelte
diff --git a/test/custom-elements/samples/camel-case-attribute/test.js b/test/runtime-browser/custom-elements-samples/camel-case-attribute/test.js
similarity index 100%
rename from test/custom-elements/samples/camel-case-attribute/test.js
rename to test/runtime-browser/custom-elements-samples/camel-case-attribute/test.js
diff --git a/test/custom-elements/samples/ce-options-valid/main.svelte b/test/runtime-browser/custom-elements-samples/ce-options-valid/main.svelte
similarity index 100%
rename from test/custom-elements/samples/ce-options-valid/main.svelte
rename to test/runtime-browser/custom-elements-samples/ce-options-valid/main.svelte
diff --git a/test/custom-elements/samples/ce-options-valid/test.js b/test/runtime-browser/custom-elements-samples/ce-options-valid/test.js
similarity index 100%
rename from test/custom-elements/samples/ce-options-valid/test.js
rename to test/runtime-browser/custom-elements-samples/ce-options-valid/test.js
diff --git a/test/custom-elements/samples/custom-method/main.svelte b/test/runtime-browser/custom-elements-samples/custom-method/main.svelte
similarity index 100%
rename from test/custom-elements/samples/custom-method/main.svelte
rename to test/runtime-browser/custom-elements-samples/custom-method/main.svelte
diff --git a/test/custom-elements/samples/custom-method/test.js b/test/runtime-browser/custom-elements-samples/custom-method/test.js
similarity index 100%
rename from test/custom-elements/samples/custom-method/test.js
rename to test/runtime-browser/custom-elements-samples/custom-method/test.js
diff --git a/test/custom-elements/samples/escaped-css/main.svelte b/test/runtime-browser/custom-elements-samples/escaped-css/main.svelte
similarity index 100%
rename from test/custom-elements/samples/escaped-css/main.svelte
rename to test/runtime-browser/custom-elements-samples/escaped-css/main.svelte
diff --git a/test/custom-elements/samples/escaped-css/test.js b/test/runtime-browser/custom-elements-samples/escaped-css/test.js
similarity index 100%
rename from test/custom-elements/samples/escaped-css/test.js
rename to test/runtime-browser/custom-elements-samples/escaped-css/test.js
diff --git a/test/custom-elements/samples/events/main.svelte b/test/runtime-browser/custom-elements-samples/events/main.svelte
similarity index 100%
rename from test/custom-elements/samples/events/main.svelte
rename to test/runtime-browser/custom-elements-samples/events/main.svelte
diff --git a/test/custom-elements/samples/events/test.js b/test/runtime-browser/custom-elements-samples/events/test.js
similarity index 100%
rename from test/custom-elements/samples/events/test.js
rename to test/runtime-browser/custom-elements-samples/events/test.js
diff --git a/test/custom-elements/samples/extended-builtin/_config.js b/test/runtime-browser/custom-elements-samples/extended-builtin/_config.js
similarity index 100%
rename from test/custom-elements/samples/extended-builtin/_config.js
rename to test/runtime-browser/custom-elements-samples/extended-builtin/_config.js
diff --git a/test/custom-elements/samples/extended-builtin/custom-button.js b/test/runtime-browser/custom-elements-samples/extended-builtin/custom-button.js
similarity index 100%
rename from test/custom-elements/samples/extended-builtin/custom-button.js
rename to test/runtime-browser/custom-elements-samples/extended-builtin/custom-button.js
diff --git a/test/custom-elements/samples/extended-builtin/main.svelte b/test/runtime-browser/custom-elements-samples/extended-builtin/main.svelte
similarity index 100%
rename from test/custom-elements/samples/extended-builtin/main.svelte
rename to test/runtime-browser/custom-elements-samples/extended-builtin/main.svelte
diff --git a/test/custom-elements/samples/extended-builtin/test.js b/test/runtime-browser/custom-elements-samples/extended-builtin/test.js
similarity index 100%
rename from test/custom-elements/samples/extended-builtin/test.js
rename to test/runtime-browser/custom-elements-samples/extended-builtin/test.js
diff --git a/test/custom-elements/samples/html-slots/main.svelte b/test/runtime-browser/custom-elements-samples/html-slots/main.svelte
similarity index 100%
rename from test/custom-elements/samples/html-slots/main.svelte
rename to test/runtime-browser/custom-elements-samples/html-slots/main.svelte
diff --git a/test/custom-elements/samples/html-slots/test.js b/test/runtime-browser/custom-elements-samples/html-slots/test.js
similarity index 100%
rename from test/custom-elements/samples/html-slots/test.js
rename to test/runtime-browser/custom-elements-samples/html-slots/test.js
diff --git a/test/custom-elements/samples/html/main.svelte b/test/runtime-browser/custom-elements-samples/html/main.svelte
similarity index 100%
rename from test/custom-elements/samples/html/main.svelte
rename to test/runtime-browser/custom-elements-samples/html/main.svelte
diff --git a/test/custom-elements/samples/html/test.js b/test/runtime-browser/custom-elements-samples/html/test.js
similarity index 100%
rename from test/custom-elements/samples/html/test.js
rename to test/runtime-browser/custom-elements-samples/html/test.js
diff --git a/test/custom-elements/samples/nested/Counter.svelte b/test/runtime-browser/custom-elements-samples/nested/Counter.svelte
similarity index 100%
rename from test/custom-elements/samples/nested/Counter.svelte
rename to test/runtime-browser/custom-elements-samples/nested/Counter.svelte
diff --git a/test/custom-elements/samples/nested/main.svelte b/test/runtime-browser/custom-elements-samples/nested/main.svelte
similarity index 100%
rename from test/custom-elements/samples/nested/main.svelte
rename to test/runtime-browser/custom-elements-samples/nested/main.svelte
diff --git a/test/custom-elements/samples/nested/test.js b/test/runtime-browser/custom-elements-samples/nested/test.js
similarity index 100%
rename from test/custom-elements/samples/nested/test.js
rename to test/runtime-browser/custom-elements-samples/nested/test.js
diff --git a/test/custom-elements/samples/new-styled/main.svelte b/test/runtime-browser/custom-elements-samples/new-styled/main.svelte
similarity index 100%
rename from test/custom-elements/samples/new-styled/main.svelte
rename to test/runtime-browser/custom-elements-samples/new-styled/main.svelte
diff --git a/test/custom-elements/samples/new-styled/test.js b/test/runtime-browser/custom-elements-samples/new-styled/test.js
similarity index 100%
rename from test/custom-elements/samples/new-styled/test.js
rename to test/runtime-browser/custom-elements-samples/new-styled/test.js
diff --git a/test/custom-elements/samples/no-missing-prop-warnings/_config.js b/test/runtime-browser/custom-elements-samples/no-missing-prop-warnings/_config.js
similarity index 100%
rename from test/custom-elements/samples/no-missing-prop-warnings/_config.js
rename to test/runtime-browser/custom-elements-samples/no-missing-prop-warnings/_config.js
diff --git a/test/custom-elements/samples/no-missing-prop-warnings/main.svelte b/test/runtime-browser/custom-elements-samples/no-missing-prop-warnings/main.svelte
similarity index 100%
rename from test/custom-elements/samples/no-missing-prop-warnings/main.svelte
rename to test/runtime-browser/custom-elements-samples/no-missing-prop-warnings/main.svelte
diff --git a/test/custom-elements/samples/no-missing-prop-warnings/test.js b/test/runtime-browser/custom-elements-samples/no-missing-prop-warnings/test.js
similarity index 100%
rename from test/custom-elements/samples/no-missing-prop-warnings/test.js
rename to test/runtime-browser/custom-elements-samples/no-missing-prop-warnings/test.js
diff --git a/test/custom-elements/samples/no-shadow-dom/main.svelte b/test/runtime-browser/custom-elements-samples/no-shadow-dom/main.svelte
similarity index 100%
rename from test/custom-elements/samples/no-shadow-dom/main.svelte
rename to test/runtime-browser/custom-elements-samples/no-shadow-dom/main.svelte
diff --git a/test/custom-elements/samples/no-shadow-dom/test.js b/test/runtime-browser/custom-elements-samples/no-shadow-dom/test.js
similarity index 100%
rename from test/custom-elements/samples/no-shadow-dom/test.js
rename to test/runtime-browser/custom-elements-samples/no-shadow-dom/test.js
diff --git a/test/custom-elements/samples/no-tag/_config.js b/test/runtime-browser/custom-elements-samples/no-tag/_config.js
similarity index 100%
rename from test/custom-elements/samples/no-tag/_config.js
rename to test/runtime-browser/custom-elements-samples/no-tag/_config.js
diff --git a/test/custom-elements/samples/no-tag/main.svelte b/test/runtime-browser/custom-elements-samples/no-tag/main.svelte
similarity index 100%
rename from test/custom-elements/samples/no-tag/main.svelte
rename to test/runtime-browser/custom-elements-samples/no-tag/main.svelte
diff --git a/test/custom-elements/samples/no-tag/test.js b/test/runtime-browser/custom-elements-samples/no-tag/test.js
similarity index 100%
rename from test/custom-elements/samples/no-tag/test.js
rename to test/runtime-browser/custom-elements-samples/no-tag/test.js
diff --git a/test/custom-elements/samples/oncreate/main.svelte b/test/runtime-browser/custom-elements-samples/oncreate/main.svelte
similarity index 100%
rename from test/custom-elements/samples/oncreate/main.svelte
rename to test/runtime-browser/custom-elements-samples/oncreate/main.svelte
diff --git a/test/custom-elements/samples/oncreate/test.js b/test/runtime-browser/custom-elements-samples/oncreate/test.js
similarity index 100%
rename from test/custom-elements/samples/oncreate/test.js
rename to test/runtime-browser/custom-elements-samples/oncreate/test.js
diff --git a/test/custom-elements/samples/ondestroy/main.svelte b/test/runtime-browser/custom-elements-samples/ondestroy/main.svelte
similarity index 100%
rename from test/custom-elements/samples/ondestroy/main.svelte
rename to test/runtime-browser/custom-elements-samples/ondestroy/main.svelte
diff --git a/test/custom-elements/samples/ondestroy/test.js b/test/runtime-browser/custom-elements-samples/ondestroy/test.js
similarity index 100%
rename from test/custom-elements/samples/ondestroy/test.js
rename to test/runtime-browser/custom-elements-samples/ondestroy/test.js
diff --git a/test/custom-elements/samples/props/main.svelte b/test/runtime-browser/custom-elements-samples/props/main.svelte
similarity index 100%
rename from test/custom-elements/samples/props/main.svelte
rename to test/runtime-browser/custom-elements-samples/props/main.svelte
diff --git a/test/custom-elements/samples/props/my-widget.svelte b/test/runtime-browser/custom-elements-samples/props/my-widget.svelte
similarity index 100%
rename from test/custom-elements/samples/props/my-widget.svelte
rename to test/runtime-browser/custom-elements-samples/props/my-widget.svelte
diff --git a/test/custom-elements/samples/props/test.js b/test/runtime-browser/custom-elements-samples/props/test.js
similarity index 100%
rename from test/custom-elements/samples/props/test.js
rename to test/runtime-browser/custom-elements-samples/props/test.js
diff --git a/test/custom-elements/samples/reflect-attributes/main.svelte b/test/runtime-browser/custom-elements-samples/reflect-attributes/main.svelte
similarity index 100%
rename from test/custom-elements/samples/reflect-attributes/main.svelte
rename to test/runtime-browser/custom-elements-samples/reflect-attributes/main.svelte
diff --git a/test/custom-elements/samples/reflect-attributes/my-widget.svelte b/test/runtime-browser/custom-elements-samples/reflect-attributes/my-widget.svelte
similarity index 100%
rename from test/custom-elements/samples/reflect-attributes/my-widget.svelte
rename to test/runtime-browser/custom-elements-samples/reflect-attributes/my-widget.svelte
diff --git a/test/custom-elements/samples/reflect-attributes/test.js b/test/runtime-browser/custom-elements-samples/reflect-attributes/test.js
similarity index 100%
rename from test/custom-elements/samples/reflect-attributes/test.js
rename to test/runtime-browser/custom-elements-samples/reflect-attributes/test.js
diff --git a/test/runtime/runtime.shared.js b/test/runtime/runtime.shared.js
new file mode 100644
index 0000000000..95a648771b
--- /dev/null
+++ b/test/runtime/runtime.shared.js
@@ -0,0 +1,272 @@
+import * as fs from 'node:fs';
+import * as path from 'node:path';
+import { setImmediate } from 'node:timers/promises';
+import { compile } from 'svelte/compiler';
+import { clear_loops, flush, set_now, set_raf } from 'svelte/internal';
+import glob from 'tiny-glob/sync.js';
+import { afterAll, assert, beforeAll, describe, it } from 'vitest';
+import { create_loader, mkdirp, try_load_config } from '../helpers.js';
+import { setup_html_equal } from '../html_equal.js';
+
+let unhandled_rejection = false;
+function unhandled_rejection_handler(err) {
+ unhandled_rejection = err;
+}
+
+const listeners = process.rawListeners('unhandledRejection');
+
+const { assert_html_equal, assert_html_equal_with_options } = setup_html_equal({
+ removeDataSvelte: true
+});
+
+beforeAll(() => {
+ process.prependListener('unhandledRejection', unhandled_rejection_handler);
+});
+
+afterAll(() => {
+ process.removeListener('unhandledRejection', unhandled_rejection_handler);
+});
+
+const failed = new Set();
+
+async function run_test(dir) {
+ if (dir[0] === '.') return;
+
+ const config = await try_load_config(`${__dirname}/samples/${dir}/_config.js`);
+ const solo = config.solo || /\.solo/.test(dir);
+
+ const it_fn = config.skip ? it.skip : solo ? it.only : it;
+
+ it_fn.each`
+ hydrate | from_ssr_html
+ ${false} | ${false}
+ ${true} | ${false}
+ ${true} | ${true}
+ `(`${dir} hydrate: $hydrate, from_ssr: $from_ssr_html`, async ({ hydrate, from_ssr_html }) => {
+ if (hydrate && config.skip_if_hydrate) return;
+ if (hydrate && from_ssr_html && config.skip_if_hydrate_from_ssr) return;
+
+ if (failed.has(dir)) {
+ // this makes debugging easier, by only printing compiled output once
+ assert.fail(`skipping ${dir}, already failed`);
+ }
+
+ unhandled_rejection = null;
+
+ const cwd = path.resolve(`${__dirname}/samples/${dir}`);
+
+ const compileOptions = Object.assign({}, config.compileOptions || {}, {
+ format: 'cjs',
+ hydratable: hydrate,
+ immutable: config.immutable,
+ accessors: 'accessors' in config ? config.accessors : true
+ });
+
+ const load = create_loader(compileOptions, cwd);
+
+ let mod;
+ let SvelteComponent;
+
+ let unintendedError = null;
+
+ if (config.expect_unhandled_rejections) {
+ listeners.forEach((listener) => {
+ // @ts-expect-error
+ process.removeListener('unhandledRejection', listener);
+ });
+ }
+
+ async function test() {
+ // hack to support transition tests
+ clear_loops();
+
+ const raf = {
+ time: 0,
+ callback: null,
+ tick: (now) => {
+ raf.time = now;
+ if (raf.callback) raf.callback();
+ }
+ };
+ set_now(() => raf.time);
+ set_raf((cb) => {
+ raf.callback = () => {
+ raf.callback = null;
+ cb(raf.time);
+ flush();
+ };
+ });
+
+ mod = await load('./main.svelte');
+ SvelteComponent = mod.default;
+
+ // Put things we need on window for testing
+ // @ts-expect-error
+ window.SvelteComponent = SvelteComponent;
+ window.location.href = '';
+ window.document.title = '';
+ window.document.head.innerHTML = '';
+ window.document.body.innerHTML = '';
+
+ const target = window.document.querySelector('main');
+ let snapshot = undefined;
+
+ if (hydrate && from_ssr_html) {
+ const load_ssr = create_loader({ ...compileOptions, generate: 'ssr' }, cwd);
+
+ // ssr into target
+ if (config.before_test) config.before_test();
+ const SsrSvelteComponent = (await load_ssr('./main.svelte')).default;
+ const { html } = SsrSvelteComponent.render(config.props);
+ target.innerHTML = html;
+
+ if (config.snapshot) {
+ snapshot = config.snapshot(target);
+ }
+
+ if (config.after_test) config.after_test();
+ } else {
+ target.innerHTML = '';
+ }
+
+ if (config.before_test) config.before_test();
+
+ const warnings = [];
+ const warn = console.warn;
+ console.warn = (warning) => {
+ warnings.push(warning);
+ };
+
+ const options = Object.assign(
+ {},
+ {
+ target,
+ hydrate,
+ props: config.props,
+ intro: config.intro
+ },
+ config.options || {}
+ );
+
+ const component = new SvelteComponent(options);
+
+ console.warn = warn;
+
+ if (config.error) {
+ unintendedError = true;
+ assert.fail('Expected a runtime error');
+ }
+
+ if (config.warnings) {
+ assert.deepEqual(warnings, config.warnings);
+ } else if (warnings.length) {
+ unintendedError = true;
+ assert.fail('Received unexpected warnings');
+ }
+
+ if (config.html) {
+ assert_html_equal_with_options(target.innerHTML, config.html, {
+ withoutNormalizeHtml: config.withoutNormalizeHtml
+ });
+ }
+
+ try {
+ if (config.test) {
+ await config.test({
+ assert: {
+ ...assert,
+ htmlEqual: assert_html_equal,
+ htmlEqualWithOptions: assert_html_equal_with_options
+ },
+ component,
+ mod,
+ target,
+ snapshot,
+ window,
+ raf,
+ compileOptions,
+ load
+ });
+ }
+ } finally {
+ component.$destroy();
+ assert_html_equal(target.innerHTML, '');
+
+ // TODO: This seems useless, unhandledRejection is only triggered on the next task
+ // by which time the test has already finished and the next test resets it to null above
+ if (unhandled_rejection) {
+ throw unhandled_rejection; // eslint-disable-line no-unsafe-finally
+ }
+ }
+ }
+
+ await test()
+ .catch((err) => {
+ if (config.error && !unintendedError) {
+ if (typeof config.error === 'function') {
+ config.error(assert, err);
+ } else {
+ assert.equal(err.message, config.error);
+ }
+ } else {
+ for (const file of glob('**/*.svelte', { cwd })) {
+ if (file[0] === '_') continue;
+
+ const dir = `${cwd}/_output/${hydrate ? 'hydratable' : 'normal'}`;
+ const out = `${dir}/${file.replace(/\.svelte$/, '.js')}`;
+
+ mkdirp(dir);
+
+ const { js } = compile(fs.readFileSync(`${cwd}/${file}`, 'utf-8').replace(/\r/g, ''), {
+ ...compileOptions,
+ filename: file
+ });
+ fs.writeFileSync(out, js.code);
+ }
+
+ throw err;
+ }
+ })
+ .catch((err) => {
+ failed.add(dir);
+ // print a clickable link to open the directory
+ err.stack += `\n\ncmd-click: ${path.relative(process.cwd(), cwd)}/main.svelte`;
+
+ throw err;
+ })
+ .finally(async () => {
+ flush();
+
+ if (config.after_test) config.after_test();
+
+ // Free up the microtask queue
+ // 1. Vitest's test runner which uses setInterval can log progress
+ // 2. Any expected unhandled rejections are ran before we reattach the listeners
+ await setImmediate();
+
+ if (config.expect_unhandled_rejections) {
+ listeners.forEach((listener) => {
+ // @ts-expect-error
+ process.on('unhandledRejection', listener);
+ });
+ }
+ });
+ });
+}
+
+// There are a lot of tests in this suite, which take up a lot of time.
+// Split them into groups so that they can run in parallel and finish faster.
+export function run_shard(id, total_shards) {
+ assert.isAtMost(id, total_shards);
+
+ const samples = fs.readdirSync(`${__dirname}/samples`);
+ const shard_size = Math.ceil(samples.length / total_shards);
+
+ const start = (id - 1) * shard_size;
+ const end = id * shard_size;
+ const to_run = samples.slice(start, end);
+
+ describe(`runtime_${id}`, async () => {
+ await Promise.all(to_run.map((dir) => run_test(dir)));
+ });
+}
diff --git a/test/runtime/runtime.test.js b/test/runtime/runtime.test.js
deleted file mode 100644
index 004045db3f..0000000000
--- a/test/runtime/runtime.test.js
+++ /dev/null
@@ -1,292 +0,0 @@
-// @vitest-environment jsdom
-
-import * as fs from 'node:fs';
-import * as path from 'node:path';
-import glob from 'tiny-glob/sync.js';
-import { beforeAll, afterAll, describe, it, assert } from 'vitest';
-import { compile } from '../../src/compiler/index.js';
-import { clear_loops, flush, set_now, set_raf } from 'svelte/internal';
-import { show_output, try_load_config, mkdirp, create_loader } from '../helpers.js';
-import { setTimeout } from 'node:timers/promises';
-import { setup_html_equal } from '../html_equal.js';
-
-let unhandled_rejection = false;
-function unhandledRejection_handler(err) {
- unhandled_rejection = err;
-}
-
-const listeners = process.rawListeners('unhandledRejection');
-
-const { assert_html_equal, assert_html_equal_with_options } = setup_html_equal({
- removeDataSvelte: true
-});
-
-describe('runtime', async () => {
- beforeAll(() => {
- process.prependListener('unhandledRejection', unhandledRejection_handler);
- });
-
- afterAll(() => {
- process.removeListener('unhandledRejection', unhandledRejection_handler);
- });
-
- const failed = new Set();
-
- async function run_test(dir) {
- if (dir[0] === '.') return;
-
- const config = await try_load_config(`${__dirname}/samples/${dir}/_config.js`);
- const solo = config.solo || /\.solo/.test(dir);
-
- const it_fn = config.skip ? it.skip : solo ? it.only : it;
-
- it_fn.each`
- hydrate | from_ssr_html
- ${false} | ${false}
- ${true} | ${false}
- ${true} | ${true}
- `(`${dir} hydrate: $hydrate, from_ssr: $from_ssr_html`, async ({ hydrate, from_ssr_html }) => {
- if (hydrate && config.skip_if_hydrate) return;
- if (hydrate && from_ssr_html && config.skip_if_hydrate_from_ssr) return;
-
- if (failed.has(dir)) {
- // this makes debugging easier, by only printing compiled output once
- assert.fail(`skipping ${dir}, already failed`);
- }
-
- unhandled_rejection = null;
-
- const cwd = path.resolve(`${__dirname}/samples/${dir}`);
-
- const compileOptions = Object.assign({}, config.compileOptions || {}, {
- format: 'cjs',
- hydratable: hydrate,
- immutable: config.immutable,
- accessors: 'accessors' in config ? config.accessors : true
- });
-
- const load = create_loader(compileOptions, cwd);
-
- let mod;
- let SvelteComponent;
-
- let unintendedError = null;
-
- glob('**/*.svelte', { cwd }).forEach((file) => {
- if (file[0] === '_') return;
-
- const dir = `${cwd}/_output/${hydrate ? 'hydratable' : 'normal'}`;
- const out = `${dir}/${file.replace(/\.svelte$/, '.js')}`;
-
- if (fs.existsSync(out)) {
- fs.unlinkSync(out);
- }
-
- mkdirp(dir);
-
- try {
- const { js } = compile(fs.readFileSync(`${cwd}/${file}`, 'utf-8').replace(/\r/g, ''), {
- ...compileOptions,
- filename: file
- });
-
- fs.writeFileSync(out, js.code);
- } catch (err) {
- // do nothing
- }
- });
-
- if (config.expect_unhandled_rejections) {
- listeners.forEach((listener) => {
- process.removeListener('unhandledRejection', listener);
- });
- }
-
- await Promise.resolve()
- .then(async () => {
- // hack to support transition tests
- clear_loops();
-
- const raf = {
- time: 0,
- callback: null,
- tick: (now) => {
- raf.time = now;
- if (raf.callback) raf.callback();
- }
- };
- set_now(() => raf.time);
- set_raf((cb) => {
- raf.callback = () => {
- raf.callback = null;
- cb(raf.time);
- flush();
- };
- });
-
- try {
- mod = await load('./main.svelte');
- SvelteComponent = mod.default;
- } catch (err) {
- show_output(cwd, compileOptions); // eslint-disable-line no-console
- throw err;
- }
-
- // Put things we need on window for testing
- // @ts-ignore
- window.SvelteComponent = SvelteComponent;
- window.location.href = '';
- window.document.title = '';
- window.document.head.innerHTML = '';
- window.document.body.innerHTML = '';
-
- const target = window.document.querySelector('main');
- let snapshot = undefined;
-
- if (hydrate && from_ssr_html) {
- const load_ssr = create_loader({ ...compileOptions, generate: 'ssr' }, cwd);
-
- // ssr into target
- if (config.before_test) config.before_test();
- const SsrSvelteComponent = (await load_ssr('./main.svelte')).default;
- const { html } = SsrSvelteComponent.render(config.props);
- target.innerHTML = html;
-
- if (config.snapshot) {
- snapshot = config.snapshot(target);
- }
-
- if (config.after_test) config.after_test();
- } else {
- target.innerHTML = '';
- }
-
- if (config.before_test) config.before_test();
-
- const warnings = [];
- const warn = console.warn;
- console.warn = (warning) => {
- warnings.push(warning);
- };
-
- const options = Object.assign(
- {},
- {
- target,
- hydrate,
- props: config.props,
- intro: config.intro
- },
- config.options || {}
- );
-
- const component = new SvelteComponent(options);
-
- console.warn = warn;
-
- if (config.error) {
- unintendedError = true;
- assert.fail('Expected a runtime error');
- }
-
- if (config.warnings) {
- assert.deepEqual(warnings, config.warnings);
- } else if (warnings.length) {
- unintendedError = true;
- assert.fail('Received unexpected warnings');
- }
-
- if (config.html) {
- assert_html_equal_with_options(target.innerHTML, config.html, {
- withoutNormalizeHtml: config.withoutNormalizeHtml
- });
- }
-
- try {
- if (config.test) {
- await config.test({
- assert: {
- ...assert,
- htmlEqual: assert_html_equal,
- htmlEqualWithOptions: assert_html_equal_with_options
- },
- component,
- mod,
- target,
- snapshot,
- window,
- raf,
- compileOptions,
- load
- });
- }
- } finally {
- component.$destroy();
- assert_html_equal(target.innerHTML, '');
-
- // TODO: This seems useless, unhandledRejection is only triggered on the next task
- // by which time the test has already finished and the next test resets it to null above
- if (unhandled_rejection) {
- // eslint-disable-next-line no-unsafe-finally
- throw unhandled_rejection;
- }
- }
- })
- .catch((err) => {
- if (config.error && !unintendedError) {
- if (typeof config.error === 'function') {
- config.error(assert, err);
- } else {
- assert.equal(err.message, config.error);
- }
- } else {
- throw err;
- }
- })
- .catch((err) => {
- failed.add(dir);
- // print a clickable link to open the directory
- err.stack += `\n\ncmd-click: ${path.relative(process.cwd(), cwd)}/main.svelte`;
-
- throw err;
- })
- .finally(async () => {
- flush();
-
- if (config.after_test) config.after_test();
-
- // Free up the microtask queue, so that
- // 1. Vitest's test runner which uses setInterval can log progress
- // 2. Any expected unhandled rejections are ran before we reattach the listeners
- await setTimeout(0);
-
- if (config.expect_unhandled_rejections) {
- listeners.forEach((listener) => {
- process.on('unhandledRejection', listener);
- });
- }
- });
- });
- }
-
- const samples = fs.readdirSync(`${__dirname}/samples`);
- await Promise.all(samples.map((sample) => run_test(sample)));
-
- const load = create_loader({ generate: 'dom', dev: true, format: 'cjs' }, __dirname);
- const { default: App } = await load('App.svelte');
-
- it('fails if options.target is missing in dev mode', async () => {
- assert.throws(() => {
- new App();
- }, /'target' is a required option/);
- });
-
- it('fails if options.hydrate is true but the component is non-hydratable', async () => {
- assert.throws(() => {
- new App({
- target: { childNodes: [] },
- hydrate: true
- });
- }, /options\.hydrate only works if the component was compiled with the `hydratable: true` option/);
- });
-});
diff --git a/test/runtime/runtime_base.test.js b/test/runtime/runtime_base.test.js
new file mode 100644
index 0000000000..756c63c187
--- /dev/null
+++ b/test/runtime/runtime_base.test.js
@@ -0,0 +1,22 @@
+// @vitest-environment jsdom
+
+import { create_loader } from '../helpers';
+import { assert, it } from 'vitest';
+
+const load = create_loader({ generate: 'dom', dev: true, format: 'cjs' }, __dirname);
+const { default: App } = await load('App.svelte');
+
+it('fails if options.target is missing in dev mode', async () => {
+ assert.throws(() => {
+ new App();
+ }, /'target' is a required option/);
+});
+
+it('fails if options.hydrate is true but the component is non-hydratable', async () => {
+ assert.throws(() => {
+ new App({
+ target: { childNodes: [] },
+ hydrate: true
+ });
+ }, /options\.hydrate only works if the component was compiled with the `hydratable: true` option/);
+});
diff --git a/test/server-side-rendering/ssr-2.test.js b/test/server-side-rendering/ssr-2.test.js
index a0780f04f6..acf823d0b0 100644
--- a/test/server-side-rendering/ssr-2.test.js
+++ b/test/server-side-rendering/ssr-2.test.js
@@ -2,10 +2,10 @@
import * as fs from 'node:fs';
import * as path from 'node:path';
-import { setTimeout } from 'node:timers/promises';
+import { setImmediate } from 'node:timers/promises';
import glob from 'tiny-glob/sync';
import { assert, describe, it } from 'vitest';
-import { compile } from '../../src/compiler/index.js';
+import { compile } from 'svelte/compiler';
import { create_loader, mkdirp, try_load_config } from '../helpers.js';
import { assert_html_equal, assert_html_equal_with_options } from '../html_equal.js';
@@ -39,30 +39,6 @@ function run_runtime_samples(suite) {
const load = create_loader(compileOptions, cwd);
- glob('**/*.svelte', { cwd }).forEach((file) => {
- if (file[0] === '_') return;
-
- const dir = `${cwd}/_output/ssr`;
- const out = `${dir}/${file.replace(/\.svelte$/, '.js')}`;
-
- if (fs.existsSync(out)) {
- fs.unlinkSync(out);
- }
-
- mkdirp(dir);
-
- try {
- const { js } = compile(fs.readFileSync(`${cwd}/${file}`, 'utf-8'), {
- ...compileOptions,
- filename: file
- });
-
- fs.writeFileSync(out, js.code);
- } catch (err) {
- // do nothing
- }
- });
-
try {
if (config.before_test) config.before_test();
@@ -105,12 +81,32 @@ function run_runtime_samples(suite) {
assert.equal(err.message, config.error);
}
} else {
+ glob('**/*.svelte', { cwd }).forEach((file) => {
+ if (file[0] === '_') return;
+
+ const dir = `${cwd}/_output/ssr`;
+ const out = `${dir}/${file.replace(/\.svelte$/, '.js')}`;
+
+ if (fs.existsSync(out)) {
+ fs.unlinkSync(out);
+ }
+
+ mkdirp(dir);
+
+ const { js } = compile(fs.readFileSync(`${cwd}/${file}`, 'utf-8'), {
+ ...compileOptions,
+ filename: file
+ });
+
+ fs.writeFileSync(out, js.code);
+ });
+
throw err;
}
}
// wait for vitest to report progress
- await setTimeout(0);
+ await setImmediate();
});
}
}
diff --git a/test/sourcemaps/sourcemaps.test.js b/test/sourcemaps/sourcemaps.test.js
index 7ae7c1336e..d00ba0fc2d 100644
--- a/test/sourcemaps/sourcemaps.test.js
+++ b/test/sourcemaps/sourcemaps.test.js
@@ -1,8 +1,8 @@
import * as fs from 'node:fs';
import * as path from 'node:path';
-import * as svelte from '../../src/compiler/index.js';
+import * as svelte from 'svelte/compiler';
+import { assert, describe, it } from 'vitest';
import { try_load_config } from '../helpers.js';
-import { describe, assert, it } from 'vitest';
// keep source-map at version 0.7.x
// https://github.com/mozilla/source-map/issues/400
import { getLocator } from 'locate-character';
diff --git a/test/stats/stats.test.js b/test/stats/stats.test.js
index b5875a89ff..4abb771c4c 100644
--- a/test/stats/stats.test.js
+++ b/test/stats/stats.test.js
@@ -1,6 +1,6 @@
import * as fs from 'node:fs';
import { describe, it, assert } from 'vitest';
-import * as svelte from '../../src/compiler/index.js';
+import * as svelte from 'svelte/compiler';
import { try_load_config, try_load_json } from '../helpers.js';
describe('stats', () => {
diff --git a/test/validator/validator.test.js b/test/validator/validator.test.js
index ca15ca23ce..e23cf4d45a 100644
--- a/test/validator/validator.test.js
+++ b/test/validator/validator.test.js
@@ -1,6 +1,6 @@
import * as fs from 'node:fs';
import { describe, it, assert } from 'vitest';
-import * as svelte from '../../src/compiler/index.js';
+import * as svelte from 'svelte/compiler';
import { try_load_json, try_load_config } from '../helpers.js';
describe('validate', () => {
diff --git a/test/vars/vars.test.js b/test/vars/vars.test.js
index eaee475b03..9e786377ac 100644
--- a/test/vars/vars.test.js
+++ b/test/vars/vars.test.js
@@ -1,6 +1,6 @@
import * as fs from 'node:fs';
import { assert, describe, it } from 'vitest';
-import { compile } from '../../src/compiler/index.js';
+import { compile } from 'svelte/compiler';
import { try_load_json } from '../helpers.js';
describe('vars', () => {
diff --git a/test/vitest-global-setup.js b/test/vitest-global-setup.js
new file mode 100644
index 0000000000..f18cebb6af
--- /dev/null
+++ b/test/vitest-global-setup.js
@@ -0,0 +1,27 @@
+import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
+
+// There are a lot of tests in the runtime suite, which take up a lot of time.
+// Split them into groups so that they can run in parallel and finish faster.
+// Vitest parallelizes tests on a per-file basis, so this hack creates a bunch
+// of files that run a subset of the tests.
+function create_shard_files() {
+ let num_shards = +process.env.SVELTE_TEST_SUITE_SHARDS || 1;
+ num_shards = Math.max(1, num_shards);
+
+ const runtime_shards_dir = `${__dirname}/runtime/shards`;
+ rmSync(runtime_shards_dir, { recursive: true, force: true });
+ mkdirSync(runtime_shards_dir);
+
+ for (let i = 1; i <= num_shards; i += 1) {
+ writeFileSync(
+ `${runtime_shards_dir}/runtime_${i}.test.js`,
+ `// @vitest-environment jsdom
+ import { run_shard } from '../runtime.shared.js';
+ run_shard(${i}, ${num_shards});`.replaceAll('\t', '')
+ );
+ }
+}
+
+export default function () {
+ create_shard_files();
+}
diff --git a/vitest.config.js b/vitest.config.js
index eda8875f3d..4392caa0e1 100644
--- a/vitest.config.js
+++ b/vitest.config.js
@@ -1,13 +1,18 @@
-import { defineConfig, configDefaults } from 'vitest/config';
+import { configDefaults, defineConfig } from 'vitest/config';
export default defineConfig({
plugins: [
{
name: 'resolve-svelte',
resolveId(id) {
+ if (id === 'svelte/compiler') {
+ return `${__dirname}/src/compiler/index.js`;
+ }
+
if (id === 'svelte') {
return `${__dirname}/src/runtime/index.js`;
}
+
if (id.startsWith('svelte/')) {
return id.replace(/^svelte(.*)\/?$/, `${__dirname}/src/runtime/$1/index.js`);
}
@@ -17,6 +22,7 @@ export default defineConfig({
test: {
dir: 'test',
reporters: ['dot'],
- exclude: [...configDefaults.exclude, '**/samples/**']
+ exclude: [...configDefaults.exclude, '**/samples/**'],
+ globalSetup: './test/vitest-global-setup.js'
}
});