chore: swap mocha with vitest (#8584)

Also swap out the require hook hacks with a less-hacky-but-still-somewhat-hacky loader for the Svelte files

---------

Co-authored-by: Simon Holthausen <simon.holthausen@vercel.com>
Co-authored-by: Rich Harris <richard.a.harris@gmail.com>
pull/8602/head
gtmnayan 1 year ago committed by GitHub
parent 202d119e9a
commit 783bd9899e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -36,7 +36,7 @@ jobs:
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm playwright install chromium
- run: pnpm test:integration
- run: pnpm test
env:
CI: true
Lint:
@ -50,28 +50,3 @@ jobs:
node-version: 16
cache: pnpm
- run: 'pnpm i && pnpm format:check && pnpm lint'
Unit:
runs-on: ${{ matrix.os }}
timeout-minutes: 10
strategy:
matrix:
include:
- node-version: 16
os: ubuntu-latest
- node-version: 16
os: windows-latest
- node-version: 16
os: macOS-latest
- node-version: 18
os: ubuntu-latest
- node-version: 20
os: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2.2.4
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: pnpm
- run: pnpm install
- run: pnpm test:unit

@ -1,17 +0,0 @@
const is_unit_test = process.env.UNIT_TEST;
module.exports = {
file: is_unit_test ? [] : ['test/test.js'],
require: [
'sucrase/register'
],
"node-option": [
"experimental-modules"
]
};
// add coverage options when running 'npx c8 mocha'
if (process.env.NODE_V8_COVERAGE) {
module.exports.fullTrace = true;
module.exports.require.push('source-map-support/register');
}

@ -1,15 +0,0 @@
module.exports = {
spec: [
'src/**/__test__.ts',
],
require: [
'sucrase/register'
],
recursive: true,
};
// add coverage options when running 'npx c8 mocha'
if (process.env.NODE_V8_COVERAGE) {
module.exports.fullTrace = true;
module.exports.require.push('source-map-support/register');
}

@ -10,4 +10,5 @@ src/compiler/compile/internal_exports.ts
/test/**/_expected*
/test/**/_actual*
/test/**/expected*
/test/**/_output
/types

@ -0,0 +1,47 @@
import { existsSync, fstat, readFileSync, readdirSync, writeFileSync } from 'fs';
import { resolve } from 'path';
import { parse } from 'acorn';
import { walk } from 'estree-walker';
import { inspect } from 'util';
import { p, print } from 'code-red';
const samples = resolve(`vitest/runtime/runtime/samples`);
for (const dir of readdirSync(samples)) {
const cwd = resolve(samples, dir);
const file = resolve(cwd, '_config.js');
if (!existsSync(file)) continue;
const contents = readFileSync(file, 'utf-8');
const ast = parse(contents, {
sourceType: 'module',
ecmaVersion: 'latest',
sourceFile: file,
ranges: true
});
walk(ast, {
enter(node) {
if (
node.type === 'ExportDefaultDeclaration' &&
node.declaration.type === 'ObjectExpression'
) {
this.skip();
const props = node.declaration.properties.find((prop) => prop.key.name === 'props');
if (!props) return;
const { range } = props;
const [start, end] = range;
const code =
contents.slice(0, start) +
print(p`get ${props.key}() { return ${props.value}}`).code +
contents.slice(end);
writeFileSync(file, code);
}
}
});
}

@ -83,10 +83,7 @@
"scripts": {
"format:fix": "prettier . --cache --plugin-search-dir=. --write",
"format:check": "prettier . --cache --plugin-search-dir=. --check",
"test": "npm run test:unit && npm run test:integration && echo \"manually check that there are no type errors in test/types by opening the files in there\"",
"test:integration": "mocha --exit",
"test:unit": "mocha --config .mocharc.unit.js --exit",
"quicktest": "mocha --exit",
"test": "vitest run && echo \"manually check that there are no type errors in test/types by opening the files in there\"",
"build": "rollup -c && npm run tsd",
"prepare": "npm run build",
"dev": "rollup -cw",
@ -140,12 +137,12 @@
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-svelte3": "^4.0.0",
"estree-walker": "^3.0.3",
"happy-dom": "^9.18.3",
"is-reference": "^3.0.1",
"jsdom": "^21.1.1",
"kleur": "^4.1.5",
"locate-character": "^2.0.5",
"magic-string": "^0.30.0",
"mocha": "^10.2.0",
"periscopic": "^3.1.0",
"prettier": "^2.8.8",
"prettier-plugin-svelte": "^2.10.0",
@ -155,7 +152,8 @@
"tiny-glob": "^0.2.9",
"tslib": "^2.5.0",
"typescript": "^5.0.4",
"util": "^0.12.5"
"util": "^0.12.5",
"vitest": "^0.31.0"
},
"packageManager": "pnpm@8.4.0"
}

File diff suppressed because it is too large Load Diff

@ -8,7 +8,6 @@ import { namespaces, valid_namespaces } from '../utils/namespaces';
import create_module from './create_module';
import { create_scopes, extract_names, Scope, extract_identifiers } from './utils/scope';
import Stylesheet from './css/Stylesheet';
import { test } from '../config';
import Fragment from './nodes/Fragment';
import internal_exports from './internal_exports';
import { Ast, CompileOptions, Var, Warning, CssResult, Attribute } from '../interfaces';
@ -413,7 +412,6 @@ export default class Component {
}
get_unique_name(name: string, scope?: Scope): Identifier {
if (test) name = `${name}$`;
let alias = name;
for (
let i = 1;
@ -422,8 +420,10 @@ export default class Component {
this.used_names.has(alias) ||
this.globally_used_names.has(alias) ||
(scope && scope.has(alias));
alias = `${name}_${i++}`
);
) {
alias = `${name}_${i++}`;
}
this.used_names.add(alias);
return { type: 'Identifier', name: alias };
}
@ -440,7 +440,6 @@ export default class Component {
this.var_lookup.forEach((_value, key) => add(key));
return (name: string): Identifier => {
if (test) name = `${name}$`;
let alias = name;
for (
let i = 1;

@ -1,88 +0,0 @@
import * as assert from 'assert';
import get_name_from_filename from './get_name_from_filename';
import {
is_contenteditable,
has_contenteditable_attr,
is_name_contenteditable,
get_contenteditable_attr,
CONTENTEDITABLE_BINDINGS
} from './contenteditable';
import Element from '../nodes/Element';
import Attribute from '../nodes/Attribute';
describe('get_name_from_filename', () => {
it('uses the basename', () => {
assert.equal(get_name_from_filename('path/to/Widget.svelte'), 'Widget');
});
it('uses the directory name, if basename is index', () => {
assert.equal(get_name_from_filename('path/to/Widget/index.svelte'), 'Widget');
});
it('handles Windows filenames', () => {
assert.equal(get_name_from_filename('path\\to\\Widget.svelte'), 'Widget');
});
it('handles special characters in filenames', () => {
assert.equal(get_name_from_filename('@.svelte'), '_');
assert.equal(get_name_from_filename('&.svelte'), '_');
assert.equal(get_name_from_filename('~.svelte'), '_');
});
});
describe('contenteditable', () => {
describe('is_contenteditable', () => {
it('returns false if node is input', () => {
const node = { name: 'input' } as Element;
assert.equal(is_contenteditable(node), false);
});
it('returns false if node is textarea', () => {
const node = { name: 'textarea' } as Element;
assert.equal(is_contenteditable(node), false);
});
it('returns false if node is not input or textarea AND it is not contenteditable', () => {
const attr = { name: 'href' } as Attribute;
const node = { name: 'a', attributes: [attr] } as Element;
assert.equal(is_contenteditable(node), false);
});
it('returns true if node is not input or textarea AND it is contenteditable', () => {
const attr = { name: 'contenteditable' } as Attribute;
const node = { name: 'a', attributes: [attr] } as Element;
assert.equal(is_contenteditable(node), true);
});
});
describe('has_contenteditable_attr', () => {
it('returns true if attribute is contenteditable', () => {
const attr = { name: 'contenteditable' } as Attribute;
const node = { attributes: [attr] } as Element;
assert.equal(has_contenteditable_attr(node), true);
});
it('returns false if attribute is not contenteditable', () => {
const attr = { name: 'href' } as Attribute;
const node = { attributes: [attr] } as Element;
assert.equal(has_contenteditable_attr(node), false);
});
});
describe('is_name_contenteditable', () => {
it('returns true if name is a contenteditable type', () => {
assert.equal(is_name_contenteditable(CONTENTEDITABLE_BINDINGS[0]), true);
});
it('returns false if name is not contenteditable type', () => {
assert.equal(is_name_contenteditable('value'), false);
});
});
describe('get_contenteditable_attr', () => {
it('returns the contenteditable Attribute if it exists', () => {
const attr = { name: 'contenteditable' } as Attribute;
const node = { name: 'div', attributes: [attr] } as Element;
assert.equal(get_contenteditable_attr(node), attr);
});
it('returns undefined if contenteditable attribute cannot be found', () => {
const node = { name: 'div', attributes: [] } as Element;
assert.equal(get_contenteditable_attr(node), undefined);
});
});
});

@ -1 +0,0 @@
export const test = typeof process !== 'undefined' && process.env.TEST;

@ -1,15 +1,10 @@
import * as fs from 'fs';
import { assert, env, svelte, setupHtmlEqual, shouldUpdateExpected } from '../helpers';
// @vitest-environment happy-dom
function try_require(file) {
try {
const mod = require(file);
return mod.default || mod;
} catch (err) {
if (err.code !== 'MODULE_NOT_FOUND') throw err;
return null;
}
}
import * as fs from 'fs';
import { assert, describe, it } from 'vitest';
import * as svelte from '../../compiler.mjs';
import { create_loader, should_update_expected, try_load_config } from '../helpers.js';
import { assert_html_equal } from '../html_equal.js';
function normalize_warning(warning) {
warning.frame = warning.frame.replace(/^\n/, '').replace(/^\t+/gm, '').replace(/\s+$/gm, '');
@ -18,25 +13,7 @@ function normalize_warning(warning) {
return warning;
}
function create(code) {
const fn = new Function('module', 'exports', 'require', code);
const module = { exports: {} };
fn(module, module.exports, (id) => {
if (id === 'svelte') return require('../../index.js');
if (id.startsWith('svelte/')) return require(id.replace('svelte', '../../'));
return require(id);
});
return module.exports.default;
}
describe('css', () => {
before(() => {
setupHtmlEqual();
});
fs.readdirSync(`${__dirname}/samples`).forEach((dir) => {
if (dir[0] === '.') return;
@ -44,12 +21,11 @@ describe('css', () => {
const solo = /\.solo/.test(dir);
const skip = /\.skip/.test(dir);
if (solo && process.env.CI) {
throw new Error('Forgot to remove `solo: true` from test');
}
const it_fn = solo ? it.only : skip ? it.skip : it;
it_fn(dir, async () => {
const config = await try_load_config(`${__dirname}/samples/${dir}/_config.js`);
(solo ? it.only : skip ? it.skip : it)(dir, () => {
const config = try_require(`./samples/${dir}/_config.js`) || {};
const input = fs
.readFileSync(`${__dirname}/samples/${dir}/input.svelte`, 'utf-8')
.replace(/\s+$/, '')
@ -81,13 +57,11 @@ describe('css', () => {
css: read(`${__dirname}/samples/${dir}/expected.css`)
};
const actual_css = dom.css.code.replace(/svelte(-ref)?-[a-z0-9]+/g, (m, $1) =>
$1 ? m : 'svelte-xyz'
);
const actual_css = replace_css_hash(dom.css.code);
try {
assert.equal(actual_css, expected.css);
} catch (error) {
if (shouldUpdateExpected()) {
if (should_update_expected()) {
fs.writeFileSync(`${__dirname}/samples/${dir}/expected.css`, actual_css);
console.log(`Updated ${dir}/expected.css.`);
} else {
@ -95,20 +69,27 @@ describe('css', () => {
}
}
const cwd = `${__dirname}/samples/${dir}`;
let ClientComponent;
let ServerComponent;
// we do this here, rather than in the expected.html !== null
// block, to verify that valid code was generated
const load = create_loader({ ...(config.compileOptions || {}), format: 'cjs' }, cwd);
try {
ClientComponent = create(dom.js.code);
ClientComponent = (await load('input.svelte')).default;
} catch (err) {
console.log(dom.js.code);
throw err;
}
const load_ssr = create_loader(
{ ...(config.compileOptions || {}), generate: 'ssr', format: 'cjs' },
cwd
);
try {
ServerComponent = create(ssr.js.code);
ServerComponent = (await load_ssr('input.svelte')).default;
} catch (err) {
console.log(dom.js.code);
throw err;
@ -116,44 +97,29 @@ describe('css', () => {
// verify that the right elements have scoping selectors
if (expected.html !== null) {
const window = env();
// dom
try {
const target = window.document.querySelector('main');
const target = window.document.createElement('main');
new ClientComponent({ target, props: config.props });
const html = target.innerHTML;
new ClientComponent({ target, props: config.props });
const html = target.innerHTML;
fs.writeFileSync(`${__dirname}/samples/${dir}/_actual.html`, html);
fs.writeFileSync(`${__dirname}/samples/${dir}/_actual.html`, html);
const actual_html = html.replace(/svelte(-ref)?-[a-z0-9]+/g, (m, $1) =>
$1 ? m : 'svelte-xyz'
);
assert.htmlEqual(actual_html, expected.html);
const actual_html = replace_css_hash(html);
assert_html_equal(actual_html, expected.html);
window.document.head.innerHTML = ''; // remove added styles
} catch (err) {
console.log(dom.js.code);
throw err;
}
window.document.head.innerHTML = ''; // remove added styles
// ssr
try {
const actual_ssr = ServerComponent.render(config.props).html.replace(
/svelte(-ref)?-[a-z0-9]+/g,
(m, $1) => ($1 ? m : 'svelte-xyz')
);
assert.htmlEqual(actual_ssr, expected.html);
} catch (err) {
console.log(ssr.js.code);
throw err;
}
const actual_ssr = replace_css_hash(ServerComponent.render(config.props).html);
assert_html_equal(actual_ssr, expected.html);
}
});
});
});
function replace_css_hash(str) {
return str.replace(/svelte(-ref)?-[a-z0-9]+/g, (m, $1) => ($1 ? m : 'svelte-xyz'));
}
function read(file) {
try {
return fs.readFileSync(file, 'utf-8');

@ -0,0 +1,118 @@
import { chromium } from '@playwright/test';
import * as fs from 'fs';
import * as path from 'path';
import { rollup } from 'rollup';
import { try_load_config } from '../helpers.js';
import * as svelte from '../../compiler.mjs';
import { beforeAll, describe, afterAll, assert, it } from 'vitest';
const internal = path.resolve('internal/index.mjs');
const index = path.resolve('index.mjs');
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') {
return 'assert';
}
},
load(id) {
if (id === '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('<main></main>');
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 }
);

@ -1,107 +0,0 @@
import { chromium } from '@playwright/test';
import virtual from '@rollup/plugin-virtual';
import { deepStrictEqual } from 'assert';
import * as fs from 'fs';
import * as path from 'path';
import { rollup } from 'rollup';
import { loadConfig, loadSvelte } from '../helpers';
const assert = fs.readFileSync(`${__dirname}/assert.js`, 'utf-8');
describe('custom-elements', function () {
this.timeout(20000);
let svelte;
/** @type {import('@playwright/test').Browser} */
let browser;
before(async function () {
svelte = loadSvelte();
console.log('[custom-elements] Loaded Svelte');
browser = await chromium.launch();
console.log('[custom-elements] Launched browser');
});
after(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 internal = path.resolve('internal/index.mjs');
const index = path.resolve('index.mjs');
const warnings = [];
(solo ? it.only : skip ? it.skip : it)(dir, async () => {
const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`);
const expected_warnings = config.warnings || [];
const bundle = await rollup({
input: `${__dirname}/samples/${dir}/test.js`,
plugins: [
// @ts-ignore -- TODO: fix this
{
resolveId(importee) {
if (importee === 'svelte/internal' || importee === './internal') {
return internal;
}
if (importee === 'svelte') {
return index;
}
},
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;
}
}
},
virtual({
assert
})
]
});
const generated_bundle = await bundle.generate({ format: 'iife', name: 'test' });
function assertWarnings() {
if (expected_warnings) {
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('<main></main>');
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();
page.close();
});
});
});

@ -1,38 +1,12 @@
import * as assert$1 from 'assert';
import * as jsdom from 'jsdom';
import glob from 'tiny-glob/sync';
import * as path from 'path';
import * as fs from 'fs';
import * as colors from 'kleur';
/**
* @type {typeof assert$1 & { htmlEqual: (actual: string, expected: string, message?: string) => void, htmlEqualWithOptions: (actual: string, expected: string, options: { preserveComments?: boolean, withoutNormalizeHtml?: boolean }, message?: string) => void }}
*/
export const assert = /** @type {any} */ (assert$1);
// for coverage purposes, we need to test source files,
// but for sanity purposes, we need to test dist files
export function loadSvelte(test = false) {
process.env.TEST = test ? 'true' : '';
const resolved = require.resolve('../compiler.js');
delete require.cache[resolved];
return require(resolved);
}
export const svelte = loadSvelte();
export function exists(path) {
try {
fs.statSync(path);
return true;
} catch (err) {
return false;
}
}
import * as path from 'path';
import glob from 'tiny-glob/sync';
import colors from 'kleur';
import { assert } from 'vitest';
import { compile } from '../compiler.js';
import { fileURLToPath } from 'url';
export function tryToLoadJson(file) {
export function try_load_json(file) {
try {
return JSON.parse(fs.readFileSync(file, 'utf-8'));
} catch (err) {
@ -41,7 +15,7 @@ export function tryToLoadJson(file) {
}
}
export function tryToReadFile(file) {
export function try_read_file(file) {
try {
return fs.readFileSync(file, 'utf-8');
} catch (err) {
@ -50,39 +24,138 @@ export function tryToReadFile(file) {
}
}
export function cleanRequireCache() {
Object.keys(require.cache)
.filter((x) => x.endsWith('.svelte'))
.forEach((file) => delete require.cache[file]);
export async function try_load_config(path) {
if (!fs.existsSync(path)) return {};
// a whole
// bunch
const _ = 1;
// of lines
// cause
const result = await import(path);
// source
// maps
// are
// stupid
return result.default;
}
const virtualConsole = new jsdom.VirtualConsole();
virtualConsole.sendTo(console);
export function should_update_expected() {
return process.env.SHOULD_UPDATE_EXPECTED === 'true';
}
const window = new jsdom.JSDOM('<main></main>', { virtualConsole }).window;
global.document = window.document;
global.navigator = window.navigator;
global.getComputedStyle = window.getComputedStyle;
global.requestAnimationFrame = null; // placeholder, filled in using set_raf
global.window = window;
export function pretty_print_browser_assertion(message) {
const match = /Error: Expected "(.+)" to equal "(.+)"/.exec(message);
// add missing ecmascript globals to window
for (const key of Object.getOwnPropertyNames(global)) {
if (!(key in window)) window[key] = global[key];
if (match) {
assert.equal(match[1], match[2]);
}
}
// implement mock scroll
window.scrollTo = function (pageXOffset, pageYOffset) {
window.pageXOffset = pageXOffset;
window.pageYOffset = pageYOffset;
};
export function mkdirp(path) {
if (!fs.existsSync(path)) {
fs.mkdirSync(path, { recursive: true });
}
}
export function env() {
window.document.title = '';
window.document.head.innerHTML = '';
window.document.body.innerHTML = '<main></main>';
export function add_line_numbers(code) {
return code
.split('\n')
.map((line, i) => {
i = String(i + 1);
while (i.length < 3) i = ` ${i}`;
return window;
return (
colors.gray(` ${i}: `) + line.replace(/^\t+/, (match) => match.split('\t').join(' '))
);
})
.join('\n');
}
export function show_output(cwd, options = {}) {
glob('**/*.svelte', { cwd }).forEach((file) => {
if (file[0] === '_') return;
try {
const { js } = compile(
fs.readFileSync(`${cwd}/${file}`, 'utf-8'),
Object.assign(options, {
filename: file
})
);
console.log(
// eslint-disable-line no-console
`\n>> ${colors.cyan().bold(file)}\n${add_line_numbers(js.code)}\n<< ${colors
.cyan()
.bold(file)}`
);
} catch (err) {
console.log(`failed to generate output: ${err.message}`);
}
});
}
const svelte_path = fileURLToPath(new URL('..', import.meta.url));
export function create_loader(compileOptions, cwd) {
const cache = new Map();
async function load(file) {
if (cache.has(file)) return cache.get(file);
if (file.endsWith('.svelte')) {
const compiled = compile(
// Windows/Linux newline conversion
fs.readFileSync(file, 'utf-8').replace(/\r\n/g, '\n'),
{
...compileOptions,
filename: file
}
);
const imports = new Map();
for (const match of compiled.js.code.matchAll(/require\("(.+?)"\)/g)) {
const source = match[1];
let resolved = source;
if (source.startsWith('.')) {
resolved = path.resolve(path.dirname(file), source);
}
if (source === 'svelte') {
resolved = `${svelte_path}/index.mjs`;
}
if (source.startsWith('svelte/')) {
resolved = `${svelte_path}/${source.slice(7)}/index.mjs`;
}
imports.set(source, await load(resolved));
}
function require(id) {
return imports.get(id);
}
const fn = new Function('require', 'exports', 'module', compiled.js.code);
const module = { exports: {} };
fn(require, module.exports, module);
cache.set(file, module.exports);
return module.exports;
} else {
return import(file);
}
}
return (file) => load(path.resolve(cwd, file));
}
function cleanChildren(node) {
@ -181,8 +254,6 @@ export function normalizeNewline(html) {
* @param {{ removeDataSvelte?: boolean }} options
*/
export function setupHtmlEqual(options = {}) {
const window = env();
// eslint-disable-next-line no-import-assign
assert.htmlEqual = (actual, expected, message) => {
assert.deepEqual(
@ -223,110 +294,16 @@ export function setupHtmlEqual(options = {}) {
};
}
export function loadConfig(file) {
try {
const resolved = require.resolve(file);
delete require.cache[resolved];
const config = require(resolved);
return config.default || config;
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
return {};
}
throw err;
}
}
export function addLineNumbers(code) {
return code
.split('\n')
.map((line, i) => {
i = String(i + 1);
while (i.length < 3) i = ` ${i}`;
return (
colors.gray(` ${i}: `) + line.replace(/^\t+/, (match) => match.split('\t').join(' '))
);
})
.join('\n');
}
export function showOutput(cwd, options = {}, compile = svelte.compile) {
glob('**/*.svelte', { cwd }).forEach((file) => {
if (file[0] === '_') return;
try {
const { js } = compile(
fs.readFileSync(`${cwd}/${file}`, 'utf-8'),
Object.assign(options, {
filename: file
})
);
export function create_deferred() {
/** @type {(value: any) => void} */
let resolve;
/** @type {(reason: any) => void} */
let reject;
console.log(
// eslint-disable-line no-console
`\n>> ${colors.cyan().bold(file)}\n${addLineNumbers(js.code)}\n<< ${colors
.cyan()
.bold(file)}`
);
} catch (err) {
console.log(`failed to generate output: ${err.message}`);
}
const promise = new Promise((f, r) => {
resolve = f;
reject = r;
});
}
export function shouldUpdateExpected() {
return process.argv.includes('--update');
}
export function spaces(i) {
let result = '';
while (i--) result += ' ';
return result;
}
// fake timers
const original_set_timeout = global.setTimeout;
export function useFakeTimers() {
const callbacks = [];
// @ts-ignore
global.setTimeout = function (fn) {
callbacks.push(fn);
};
return {
flush() {
callbacks.forEach((fn) => fn());
callbacks.splice(0, callbacks.length);
},
removeFakeTimers() {
callbacks.splice(0, callbacks.length);
global.setTimeout = original_set_timeout;
}
};
}
export function mkdirp(dir) {
const parent = path.dirname(dir);
if (parent === dir) return;
mkdirp(parent);
try {
fs.mkdirSync(dir);
} catch (err) {
// do nothing
}
}
export function prettyPrintBrowserAssertionError(message) {
const match = /Error: Expected "(.+)" to equal "(.+)"/.exec(message);
if (match) {
assert.equal(match[1], match[2]);
}
return { promise, resolve, reject };
}

@ -0,0 +1,127 @@
import { assert } from 'vitest';
/** @type {HTMLDivElement} */
let _container;
/**
* @param {string} html
* @param {{
* removeDataSvelte?: boolean,
* preserveComments?: boolean,
* }} options
*/
export function normalize_html(html, options = {}) {
const container = (_container ??= document.createElement('div'));
if (!options.preserveComments) {
html = html.replace(/(<!--.*?-->)/g, '');
}
if (options.removeDataSvelte) {
html = html.replace(/(data-svelte-h="[^"]+")/g, '');
}
html = html.replace(/>[ \t\n\r\f]+</g, '><').trim();
container.innerHTML = html;
clean_children(container);
return container.innerHTML.replace(/<\/?noscript\/?>/g, '');
}
/** @param {any} node */
function clean_children(node) {
// sort attributes
const attributes = Array.from(node.attributes).sort((a, b) => (a.name < b.name ? -1 : 1));
attributes.forEach((attr) => {
node.removeAttribute(attr.name);
});
attributes.forEach((attr) => {
node.setAttribute(attr.name, attr.value);
});
let previous = null;
// recurse
[...node.childNodes].forEach((child) => {
if (child.nodeType === 3) {
// text
if (
node.namespaceURI === 'http://www.w3.org/2000/svg' &&
node.tagName !== 'text' &&
node.tagName !== 'tspan'
) {
node.removeChild(child);
}
child.data = child.data.replace(/[ \t\n\r\f]+/g, '\n');
if (previous && previous.nodeType === 3) {
previous.data += child.data;
previous.data = previous.data.replace(/[ \t\n\r\f]+/g, '\n');
node.removeChild(child);
child = previous;
}
} else if (child.nodeType === 8) {
// comment
// do nothing
} else {
clean_children(child);
}
previous = child;
});
// collapse whitespace
if (node.firstChild && node.firstChild.nodeType === 3) {
node.firstChild.data = node.firstChild.data.replace(/^[ \t\n\r\f]+/, '');
if (!node.firstChild.data.length) node.removeChild(node.firstChild);
}
if (node.lastChild && node.lastChild.nodeType === 3) {
node.lastChild.data = node.lastChild.data.replace(/[ \t\n\r\f]+$/, '');
if (!node.lastChild.data.length) node.removeChild(node.lastChild);
}
}
/**
*
* @param {string} actual
* @param {string} expected
* @param {{
* message?: string,
* normalize_html?: {
* removeDataSvelte?: boolean,
* preserveComments?: boolean,
* },
* without_normalize?: boolean,
* }} options
*/
export function assert_html_equal(actual, expected, options = {}) {
if (options.without_normalize) {
actual = actual.replace(/\r\n/g, '\n');
expected = expected.replace(/\r\n/g, '\n');
if (options.normalize_html.removeDataSvelte) {
actual = actual.replace(/(\sdata-svelte-h="[^"]+")/g, '');
expected = expected.replace(/(\sdata-svelte-h="[^"]+")/g, '');
}
} else {
actual = normalize_html(actual, options.normalize_html);
expected = normalize_html(expected, options.normalize_html);
}
try {
assert.equal(actual, expected, options.message);
} catch (err) {
// Remove this function from the stack trace so that the error is shown in the test file
if (Error.captureStackTrace) {
Error.captureStackTrace(err, assert_html_equal);
}
throw err;
}
}

@ -0,0 +1,107 @@
// @vitest-environment jsdom
// TODO: https://github.com/capricorn86/happy-dom/issues/916
import * as fs from 'fs';
import * as path from 'path';
import { assert, describe, it } from 'vitest';
import { create_loader, should_update_expected, try_load_config } from '../helpers.js';
import { assert_html_equal } from '../html_equal.js';
describe('hydration', async () => {
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(dir, async () => {
const cwd = path.resolve(`${__dirname}/samples/${dir}`);
let compileOptions = Object.assign({}, config.compileOptions, {
accessors: 'accessors' in config ? config.accessors : true,
format: 'cjs',
hydratable: true
});
const { default: SvelteComponent } = await create_loader(compileOptions, cwd)('main.svelte');
const target = window.document.body;
const head = window.document.head;
target.innerHTML = fs.readFileSync(`${cwd}/_before.html`, 'utf-8');
let before_head;
try {
before_head = fs.readFileSync(`${cwd}/_before_head.html`, 'utf-8');
head.innerHTML = before_head;
} catch (err) {
// continue regardless of error
}
const snapshot = config.snapshot ? config.snapshot(target) : {};
const component = new SvelteComponent({
target,
hydrate: true,
props: config.props
});
try {
assert_html_equal(target.innerHTML, fs.readFileSync(`${cwd}/_after.html`, 'utf-8'));
} catch (error) {
if (should_update_expected()) {
fs.writeFileSync(`${cwd}/_after.html`, target.innerHTML);
console.log(`Updated ${cwd}/_after.html.`);
} else {
throw error;
}
}
if (before_head) {
try {
const after_head = fs.readFileSync(`${cwd}/_after_head.html`, 'utf-8');
assert_html_equal(head.innerHTML, after_head);
} catch (error) {
if (should_update_expected()) {
fs.writeFileSync(`${cwd}/_after_head.html`, head.innerHTML);
console.log(`Updated ${cwd}/_after_head.html.`);
} else {
throw error;
}
}
}
if (config.snapshot) {
const snapshot_after = config.snapshot(target);
for (const s in snapshot_after) {
assert.ok(
// Error logger borks because of circular references so use this instead
snapshot_after[s] === snapshot[s],
`Expected snapshot key "${s}" to have same value/reference`
);
}
}
if (config.test) {
await config.test(
{
...assert,
htmlEqual: assert_html_equal
},
target,
snapshot,
component,
window
);
}
component.$destroy();
assert.equal(target.innerHTML, '');
});
}
await Promise.all(fs.readdirSync(`${__dirname}/samples`).map((dir) => run_test(dir)));
});

@ -1,144 +0,0 @@
import * as path from 'path';
import * as fs from 'fs';
import {
assert,
showOutput,
loadConfig,
loadSvelte,
env,
setupHtmlEqual,
shouldUpdateExpected
} from '../helpers';
let compileOptions = null;
const sveltePath = process.cwd();
describe('hydration', () => {
before(() => {
const svelte = loadSvelte();
require.extensions['.svelte'] = function (module, filename) {
const options = Object.assign(
{
filename,
hydratable: true,
format: 'cjs',
sveltePath
},
compileOptions
);
const { js } = svelte.compile(fs.readFileSync(filename, 'utf-8'), options);
return module._compile(js.code, filename);
};
return setupHtmlEqual();
});
function runTest(dir) {
if (dir[0] === '.') return;
const config = loadConfig(`./hydration/samples/${dir}/_config.js`);
const solo = config.solo || /\.solo/.test(dir);
if (solo && process.env.CI) {
throw new Error('Forgot to remove `solo: true` from test');
}
(config.skip ? it.skip : solo ? it.only : it)(dir, () => {
const cwd = path.resolve(`${__dirname}/samples/${dir}`);
compileOptions = config.compileOptions || {};
compileOptions.accessors = 'accessors' in config ? config.accessors : true;
const window = env();
try {
global.window = window;
const SvelteComponent = require(`${cwd}/main.svelte`).default;
const target = window.document.body;
const head = window.document.head;
target.innerHTML = fs.readFileSync(`${cwd}/_before.html`, 'utf-8');
let before_head;
try {
before_head = fs.readFileSync(`${cwd}/_before_head.html`, 'utf-8');
head.innerHTML = before_head;
} catch (err) {
// continue regardless of error
}
const snapshot = config.snapshot ? config.snapshot(target) : {};
const component = new SvelteComponent({
target,
hydrate: true,
props: config.props
});
try {
assert.htmlEqual(target.innerHTML, fs.readFileSync(`${cwd}/_after.html`, 'utf-8'));
} catch (error) {
if (shouldUpdateExpected()) {
fs.writeFileSync(`${cwd}/_after.html`, target.innerHTML);
console.log(`Updated ${cwd}/_after.html.`);
} else {
throw error;
}
}
if (before_head) {
try {
assert.htmlEqual(head.innerHTML, fs.readFileSync(`${cwd}/_after_head.html`, 'utf-8'));
} catch (error) {
if (shouldUpdateExpected()) {
fs.writeFileSync(`${cwd}/_after_head.html`, head.innerHTML);
console.log(`Updated ${cwd}/_after_head.html.`);
} else {
throw error;
}
}
}
if (config.snapshot) {
const snapshot_after = config.snapshot(target);
for (const s in snapshot_after) {
assert.equal(
snapshot_after[s],
snapshot[s],
`Expected snapshot key "${s}" to have same value/reference`
);
}
}
if (config.test) {
config.test(assert, target, snapshot, component, window);
} else {
component.$destroy();
assert.equal(target.innerHTML, '');
}
} catch (err) {
showOutput(cwd, {
hydratable: true
});
throw err;
}
if (config.show) {
showOutput(cwd, {
hydratable: true
});
}
});
}
fs.readdirSync(`${__dirname}/samples`).forEach((dir) => {
runTest(dir);
});
});

@ -1,92 +0,0 @@
import * as assert from 'assert';
import * as fs from 'fs';
import * as path from 'path';
import * as colors from 'kleur';
import { loadConfig, svelte, shouldUpdateExpected } from '../helpers';
describe('js', () => {
fs.readdirSync(`${__dirname}/samples`).forEach((dir) => {
if (dir[0] === '.') return;
// add .solo to a sample directory name to only run that test
const solo = /\.solo/.test(dir);
if (solo && process.env.CI) {
throw new Error('Forgot to remove `solo: true` from test');
}
const resolved = path.resolve(`${__dirname}/samples`, dir);
if (!fs.existsSync(`${resolved}/input.svelte`)) {
console.log(
colors
.red()
.bold(
`Missing file ${dir}/input.svelte. If you recently switched branches you may need to delete this directory`
)
);
return;
}
(solo ? it.only : it)(dir, () => {
const config = loadConfig(`${resolved}/_config.js`);
const input = fs
.readFileSync(`${resolved}/input.svelte`, 'utf-8')
.replace(/\s+$/, '')
.replace(/\r/g, '');
let actual;
try {
const options = Object.assign(config.options || {});
actual = svelte
.compile(input, options)
.js.code.replace(
/generated by Svelte v\d+\.\d+\.\d+(-\w+\.\d+)?/,
'generated by Svelte vX.Y.Z'
);
} catch (err) {
console.log(err.frame);
throw err;
}
const output = `${resolved}/_actual.js`;
fs.writeFileSync(output, actual);
const expectedPath = `${resolved}/expected.js`;
let expected = '';
try {
expected = fs.readFileSync(expectedPath, 'utf-8');
} catch (error) {
console.log(error);
if (error.code === 'ENOENT') {
// missing expected.js
fs.writeFileSync(expectedPath, actual);
}
}
try {
assert.equal(
actual
.trim()
.replace(/^[ \t]+$/gm, '')
.replace(/\r/g, ''),
expected
.trim()
.replace(/^[ \t]+$/gm, '')
.replace(/\r/g, '')
);
} catch (error) {
if (shouldUpdateExpected()) {
fs.writeFileSync(expectedPath, actual);
console.log(`Updated ${expectedPath}.`);
} else {
throw error;
}
}
});
});
});

@ -0,0 +1,84 @@
import * as fs from 'fs';
import * as path from 'path';
import { describe, it, assert } from 'vitest';
import { try_load_config, should_update_expected } from '../helpers';
import * as svelte from '../../compiler';
describe('js-output', () => {
fs.readdirSync(`${__dirname}/samples`).forEach((dir) => {
if (dir[0] === '.') return;
// add .solo to a sample directory name to only run that test
const solo = /\.solo/.test(dir);
const resolved = path.resolve(`${__dirname}/samples`, dir);
const skip = !fs.existsSync(`${resolved}/input.svelte`);
if (skip) {
console.warn(
`Missing file ${dir}/input.svelte. If you recently switched branches you may need to delete this directory`
);
}
const it_fn = solo ? it.only : skip ? it.skip : it;
it_fn(dir, async () => {
const config = await try_load_config(`${resolved}/_config.js`);
const input = fs
.readFileSync(`${resolved}/input.svelte`, 'utf-8')
.trimEnd()
.replace(/\r/g, '');
let actual;
try {
const options = Object.assign(config.options || {});
actual = svelte
.compile(input, options)
.js.code.replace(
/generated by Svelte v\d+\.\d+\.\d+(-\w+\.\d+)?/,
'generated by Svelte vX.Y.Z'
);
} catch (err) {
console.log(err.frame);
throw err;
}
const output = `${resolved}/_actual.js`;
fs.writeFileSync(output, actual);
const expected_path = `${resolved}/expected.js`;
let expected = '';
try {
expected = fs.readFileSync(expected_path, 'utf-8');
} catch (error) {
console.log(error);
if (error.code === 'ENOENT') {
// missing expected.js
fs.writeFileSync(expected_path, actual);
}
}
try {
assert.equal(normalize_output(actual), normalize_output(expected));
} catch (error) {
if (should_update_expected()) {
fs.writeFileSync(expected_path, actual);
console.log(`Updated ${expected_path}.`);
} else {
throw error;
}
}
});
});
});
function normalize_output(str) {
return str
.trim()
.replace(/^[ \t]+$/gm, '')
.replace(/\r/g, '');
}

@ -1,3 +1,3 @@
import { writable } from '../../../../store';
import { writable } from 'svelte/store';
export const count = writable(0);

@ -1,4 +1,4 @@
import { writable } from '../../../../store';
import { writable } from 'svelte/store';
export const reactiveStoreVal = writable(0);
export const unreactiveExport = true;

@ -1,6 +1,6 @@
import * as assert from 'assert';
import { get } from '../../store';
import { spring, tweened } from '../../motion';
import { describe, it, assert } from 'vitest';
import { get } from 'svelte/store';
import { spring, tweened } from 'svelte/motion';
describe('motion', () => {
describe('spring', () => {

@ -1,6 +1,7 @@
import * as assert from 'assert';
import * as fs from 'fs';
import { svelte, tryToLoadJson } from '../helpers';
import { assert, describe, it } from 'vitest';
import * as svelte from '../../compiler';
import { try_load_json } from '../helpers';
describe('parse', () => {
fs.readdirSync(`${__dirname}/samples`).forEach((dir) => {
@ -8,22 +9,25 @@ describe('parse', () => {
// add .solo to a sample directory name to only run that test
const solo = /\.solo$/.test(dir);
if (solo && process.env.CI) {
throw new Error(`Forgot to remove '.solo' from test parser/samples/${dir}`);
const skip = !fs.existsSync(`${__dirname}/samples/${dir}/input.svelte`);
if (skip) {
console.warn(
`skipping ${dir} because no input.svelte exists. This could be a leftover folder from a different branch.`
);
}
const skip = !fs.existsSync(`${__dirname}/samples/${dir}/input.svelte`);
const it_fn = skip ? it.skip : solo ? it.only : it;
(skip ? it.skip : solo ? it.only : it)(dir, () => {
const options = tryToLoadJson(`${__dirname}/samples/${dir}/options.json`) || {};
it_fn(dir, () => {
const options = try_load_json(`${__dirname}/samples/${dir}/options.json`) || {};
const input = fs
.readFileSync(`${__dirname}/samples/${dir}/input.svelte`, 'utf-8')
.replace(/\s+$/, '')
.trimEnd()
.replace(/\r/g, '');
const expectedOutput = tryToLoadJson(`${__dirname}/samples/${dir}/output.json`);
const expectedError = tryToLoadJson(`${__dirname}/samples/${dir}/error.json`);
const expectedOutput = try_load_json(`${__dirname}/samples/${dir}/output.json`);
const expectedError = try_load_json(`${__dirname}/samples/${dir}/error.json`);
try {
const { ast } = svelte.compile(
@ -46,12 +50,8 @@ describe('parse', () => {
if (err.name !== 'ParseError') throw err;
if (!expectedError) throw err;
const { code, message, pos, start } = err;
try {
assert.deepEqual({ code, message, pos, start }, expectedError);
} catch (err2) {
const e = err2.code === 'MODULE_NOT_FOUND' ? err : err2;
throw e;
}
assert.deepEqual({ code, message, pos, start }, expectedError);
}
});
});

@ -1,39 +0,0 @@
import * as fs from 'fs';
import * as assert from 'assert';
import { loadConfig, svelte } from '../helpers';
describe('preprocess', () => {
fs.readdirSync(`${__dirname}/samples`).forEach((dir) => {
if (dir[0] === '.') return;
const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`);
const solo = config.solo || /\.solo/.test(dir);
const skip = config.skip || /\.skip/.test(dir);
if (solo && process.env.CI) {
throw new Error('Forgot to remove `solo: true` from test');
}
(skip ? it.skip : solo ? it.only : it)(dir, async () => {
const input = fs.readFileSync(`${__dirname}/samples/${dir}/input.svelte`, 'utf-8');
const expected = fs.readFileSync(`${__dirname}/samples/${dir}/output.svelte`, 'utf-8');
const result = await svelte.preprocess(
input,
config.preprocess || {},
config.options || { filename: 'input.svelte' }
);
fs.writeFileSync(`${__dirname}/samples/${dir}/_actual.html`, result.code);
if (result.map) {
fs.writeFileSync(
`${__dirname}/samples/${dir}/_actual.html.map`,
JSON.stringify(result.map, null, 2)
);
}
assert.equal(result.code, expected);
assert.deepEqual(result.dependencies, config.dependencies || []);
});
});
});

@ -0,0 +1,42 @@
import * as fs from 'fs';
import * as svelte from '../../compiler';
import { try_load_config } from '../helpers';
import { describe, it } from 'vitest';
const samples = fs.readdirSync(`${__dirname}/samples`);
describe('preprocess', async () => {
await Promise.all(samples.map((dir) => run(dir)));
async function run(dir) {
if (dir[0] === '.') return;
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);
const it_fn = skip ? it.skip : solo ? it.only : it;
it_fn(dir, async ({ expect }) => {
const input = fs.readFileSync(`${__dirname}/samples/${dir}/input.svelte`, 'utf-8');
const result = await svelte.preprocess(
input,
config.preprocess || {},
config.options || { filename: 'input.svelte' }
);
fs.writeFileSync(`${__dirname}/samples/${dir}/_actual.html`, result.code);
if (result.map) {
fs.writeFileSync(
`${__dirname}/samples/${dir}/_actual.html.map`,
JSON.stringify(result.map, null, 2)
);
}
expect(result.code).toMatchFileSnapshot(`${__dirname}/samples/${dir}/output.svelte`);
expect(result.dependencies).toEqual(config.dependencies || []);
});
}
});

@ -0,0 +1,173 @@
import { chromium } from '@playwright/test';
import * as fs from 'fs';
import * as path from 'path';
import { rollup } from 'rollup';
import { pretty_print_browser_assertion, try_load_config } from '../helpers.js';
import * as svelte from '../../compiler.mjs';
import { beforeAll, describe, afterAll, assert } from 'vitest';
const internal = path.resolve('internal/index.mjs');
const index = path.resolve('index.mjs');
const main = fs.readFileSync(`${__dirname}/driver.js`, 'utf-8');
const browser_assert = fs.readFileSync(`${__dirname}/assert.js`, 'utf-8');
describe(
'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 'main';
}
if (importee === 'assert') {
return '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 === 'assert') return browser_assert;
if (id === '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('<main></main>');
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;
}
});
}
await Promise.all(
fs.readdirSync(`${__dirname}/samples`).map(async (dir) => {
await runTest(dir, false);
await runTest(dir, true);
})
);
},
// Browser tests are brittle and slow on CI
{ timeout: 20000, retry: process.env.CI ? 1 : 0 }
);

@ -0,0 +1,73 @@
import SvelteComponent from '__MAIN_DOT_SVELTE__';
import config from '__CONFIG__';
import * as assert from 'assert';
export default async function (target) {
let unhandled_rejection = false;
function unhandled_rejection_handler(event) {
unhandled_rejection = event.reason;
}
window.addEventListener('unhandledrejection', unhandled_rejection_handler);
try {
if (config.before_test) config.before_test();
const options = Object.assign(
{},
{
target,
hydrate: __HYDRATE__,
props: config.props,
intro: config.intro
},
config.options || {}
);
const component = new SvelteComponent(options);
const waitUntil = async (fn, ms = 500) => {
const start = new Date().getTime();
do {
if (fn()) return;
await new Promise((resolve) => window.setTimeout(resolve, 1));
} while (new Date().getTime() <= start + ms);
};
if (config.html) {
assert.htmlEqual(target.innerHTML, config.html);
}
if (config.test) {
await config.test({
assert,
component,
target,
window,
waitUntil
});
component.$destroy();
if (unhandled_rejection) {
throw unhandled_rejection;
}
} else {
component.$destroy();
assert.htmlEqual(target.innerHTML, '');
if (unhandled_rejection) {
throw unhandled_rejection;
}
}
if (config.after_test) config.after_test();
} catch (error) {
if (config.error) {
assert.equal(err.message, config.error);
} else {
throw error;
}
} finally {
window.removeEventListener('unhandledrejection', unhandled_rejection_handler);
}
}

@ -1,230 +0,0 @@
import virtual from '@rollup/plugin-virtual';
import * as fs from 'fs';
import * as path from 'path';
import { rollup } from 'rollup';
import { chromium } from '@playwright/test';
import { deepStrictEqual } from 'assert';
import { loadConfig, loadSvelte, mkdirp, prettyPrintBrowserAssertionError } from '../helpers';
const internal = path.resolve('internal/index.mjs');
const index = path.resolve('index.mjs');
const assert = fs.readFileSync(`${__dirname}/assert.js`, 'utf-8');
describe('runtime (browser)', function () {
this.timeout(20000);
let svelte;
let browser;
before(async () => {
svelte = loadSvelte(false);
console.log('[runtime-browser] Loaded Svelte');
browser = await chromium.launch();
console.log('[runtime-browser] Launched browser');
});
after(async () => {
if (browser) await browser.close();
});
const failed = new Set();
function runTest(dir, hydrate) {
if (dir[0] === '.') return;
const config = loadConfig(`${__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;
if (solo && process.env.CI) {
throw new Error('Forgot to remove `solo: true` from test');
}
(skip ? it.skip : solo ? it.only : it)(
`${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 'main';
}
},
load(id) {
if (id === 'main') {
return `
import SvelteComponent from ${JSON.stringify(path.join(__dirname, 'samples', dir, 'main.svelte'))};
import config from ${JSON.stringify(path.join(__dirname, 'samples', dir, '_config.js'))};
import * as assert from 'assert';
export default async function (target) {
let unhandled_rejection = false;
function unhandled_rejection_handler(event) {
unhandled_rejection = event.reason;
}
window.addEventListener('unhandledrejection', unhandled_rejection_handler);
try {
if (config.before_test) config.before_test();
const options = Object.assign({}, {
target,
hydrate: ${String(!!hydrate)},
props: config.props,
intro: config.intro
}, config.options || {});
const component = new SvelteComponent(options);
const waitUntil = async (fn, ms = 500) => {
const start = new Date().getTime();
do {
if (fn()) return;
await new Promise(resolve => window.setTimeout(resolve, 1));
} while (new Date().getTime() <= start + ms);
};
if (config.html) {
assert.htmlEqual(target.innerHTML, config.html);
}
if (config.test) {
await config.test({
assert,
component,
target,
window,
waitUntil,
});
component.$destroy();
if (unhandled_rejection) {
throw unhandled_rejection;
}
} else {
component.$destroy();
assert.htmlEqual(target.innerHTML, '');
if (unhandled_rejection) {
throw unhandled_rejection;
}
}
if (config.after_test) config.after_test();
} catch (error) {
if (config.error) {
assert.equal(err.message, config.error);
} else {
throw error;
}
} finally {
window.removeEventListener('unhandledrejection', unhandled_rejection_handler);
}
}
`;
}
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);
}
mkdirp(out_dir);
fs.writeFileSync(out, compiled.js.code, 'utf8');
compiled.warnings.forEach((w) => warnings.push(w));
return compiled.js;
}
}
},
virtual({ assert })
]
});
const generated_bundle = await bundle.generate({ format: 'iife', name: 'test' });
function assertWarnings() {
if (config.warnings) {
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('<main></main>');
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);
prettyPrintBrowserAssertionError(err.message);
assertWarnings();
throw err;
}
}
);
}
fs.readdirSync(`${__dirname}/samples`).forEach((dir) => {
runTest(dir, false);
runTest(dir, true);
});
});

@ -1,105 +1,73 @@
import * as path from 'path';
// @vitest-environment jsdom
import * as fs from 'fs';
import { rollup } from 'rollup';
import virtual from '@rollup/plugin-virtual';
import * as path from 'path';
import glob from 'tiny-glob/sync.js';
import { clear_loops, flush, set_now, set_raf } from '../../internal';
import {
assert,
showOutput,
loadConfig,
loadSvelte,
cleanRequireCache,
env,
setupHtmlEqual,
mkdirp
} from '../helpers';
let svelte$;
let svelte;
let compileOptions = null;
let compile = null;
const sveltePath = process.cwd().split('\\').join('/');
import { beforeAll, afterAll, describe, it, assert } from 'vitest';
import { compile } from '../../compiler.mjs';
import { clear_loops, flush, set_now, set_raf } from 'svelte/internal';
import { show_output, try_load_config, mkdirp, create_loader, setupHtmlEqual } from '../helpers.js';
import { setTimeout } from 'timers/promises';
let unhandled_rejection = false;
function unhandledRejection_handler(err) {
unhandled_rejection = err;
}
describe('runtime', () => {
before(() => {
process.on('unhandledRejection', unhandledRejection_handler);
svelte = loadSvelte(false);
svelte$ = loadSvelte(true);
require.extensions['.svelte'] = function (module, filename) {
const options = Object.assign(
{
filename
},
compileOptions
);
const {
js: { code }
} = compile(fs.readFileSync(filename, 'utf-8').replace(/\r/g, ''), options);
return module._compile(code, filename);
};
let listeners = process.rawListeners('unhandledRejection');
describe('runtime', async () => {
beforeAll(() => {
process.prependListener('unhandledRejection', unhandledRejection_handler);
return setupHtmlEqual({ removeDataSvelte: true });
});
after(() => process.removeListener('unhandledRejection', unhandledRejection_handler));
afterAll(() => {
process.removeListener('unhandledRejection', unhandledRejection_handler);
});
const failed = new Set();
function runTest(dir, hydrate, from_ssr_html) {
async function run_test(dir) {
if (dir[0] === '.') return;
const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`);
const config = await try_load_config(`${__dirname}/samples/${dir}/_config.js`);
const solo = config.solo || /\.solo/.test(dir);
if (hydrate && config.skip_if_hydrate) return;
if (hydrate && from_ssr_html && config.skip_if_hydrate_from_ssr) return;
const it_fn = config.skip ? it.skip : solo ? it.only : it;
if (solo && process.env.CI) {
throw new Error('Forgot to remove `solo: true` from test');
}
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;
const testName = `${dir} ${
hydrate ? `(with hydration${from_ssr_html ? ' from ssr rendered html' : ''})` : ''
}`;
(config.skip ? it.skip : solo ? it.only : it)(testName, (done) => {
if (failed.has(dir)) {
// this makes debugging easier, by only printing compiled output once
throw new Error('skipping test, already failed');
assert.fail(`skipping ${dir}, already failed`);
}
unhandled_rejection = null;
compile = (config.preserveIdentifiers ? svelte : svelte$).compile;
const cwd = path.resolve(`${__dirname}/samples/${dir}`);
compileOptions = config.compileOptions || {};
compileOptions.format = 'cjs';
compileOptions.sveltePath = sveltePath;
compileOptions.hydratable = hydrate;
compileOptions.immutable = config.immutable;
compileOptions.accessors = 'accessors' in config ? config.accessors : true;
const compileOptions = Object.assign(config.compileOptions || {}, {
format: 'cjs',
hydratable: hydrate,
immutable: config.immutable,
accessors: 'accessors' in config ? config.accessors : true
});
cleanRequireCache();
const load = create_loader(compileOptions, cwd);
let mod;
let SvelteComponent;
let unintendedError = null;
const window = env();
glob('**/*.svelte', { cwd }).forEach((file) => {
if (file[0] === '_') return;
@ -124,8 +92,14 @@ describe('runtime', () => {
}
});
Promise.resolve()
.then(() => {
if (config.expect_unhandled_rejections) {
listeners.forEach((listener) => {
process.removeListener('unhandledRejection', listener);
});
}
await Promise.resolve()
.then(async () => {
// hack to support transition tests
clear_loops();
@ -147,25 +121,29 @@ describe('runtime', () => {
});
try {
mod = require(`./samples/${dir}/main.svelte`);
mod = await load(`./main.svelte`);
SvelteComponent = mod.default;
} catch (err) {
showOutput(cwd, compileOptions, compile); // eslint-disable-line no-console
show_output(cwd, compileOptions); // eslint-disable-line no-console
throw err;
}
// Put things we need on window for testing
window.SvelteComponent = SvelteComponent;
window.location.href = '';
window.document.title = '';
window.document.head.innerHTML = '';
window.document.body.innerHTML = '<main></main>';
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
compileOptions.generate = 'ssr';
cleanRequireCache();
if (config.before_test) config.before_test();
const SsrSvelteComponent = require(`./samples/${dir}/main.svelte`).default;
const SsrSvelteComponent = (await load_ssr(`./main.svelte`)).default;
const { html } = SsrSvelteComponent.render(config.props);
target.innerHTML = html;
@ -173,7 +151,6 @@ describe('runtime', () => {
snapshot = config.snapshot(target);
}
delete compileOptions.generate;
if (config.after_test) config.after_test();
} else {
target.innerHTML = '';
@ -204,14 +181,14 @@ describe('runtime', () => {
if (config.error) {
unintendedError = true;
throw new Error('Expected a runtime error');
assert.fail('Expected a runtime error');
}
if (config.warnings) {
assert.deepEqual(warnings, config.warnings);
} else if (warnings.length) {
unintendedError = true;
throw new Error('Received unexpected warnings');
assert.fail('Received unexpected warnings');
}
if (config.html) {
@ -220,9 +197,9 @@ describe('runtime', () => {
});
}
if (config.test) {
return Promise.resolve(
config.test({
try {
if (config.test) {
await config.test({
assert,
component,
mod,
@ -230,19 +207,16 @@ describe('runtime', () => {
snapshot,
window,
raf,
compileOptions
})
).then(() => {
component.$destroy();
if (unhandled_rejection) {
throw unhandled_rejection;
}
});
} else {
compileOptions,
load
});
}
} finally {
component.$destroy();
assert.htmlEqual(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;
}
@ -261,82 +235,48 @@ describe('runtime', () => {
})
.catch((err) => {
failed.add(dir);
showOutput(cwd, compileOptions, compile); // eslint-disable-line no-console
throw err;
})
.catch((err) => {
// print a clickable link to open the directory
err.stack += `\n\ncmd-click: ${path.relative(process.cwd(), cwd)}/main.svelte`;
done(err);
throw err;
})
.then(() => {
if (config.show) {
showOutput(cwd, compileOptions, compile);
}
.finally(async () => {
flush();
if (config.after_test) config.after_test();
done();
});
});
}
fs.readdirSync(`${__dirname}/samples`).forEach((dir) => {
runTest(dir, false);
runTest(dir, true, false);
runTest(dir, true, true);
});
async function create_component(src = '<div></div>') {
const { js } = svelte$.compile(src, {
format: 'esm',
name: 'SvelteComponent',
dev: true
});
// 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();
const bundle = await rollup({
input: 'main.js',
plugins: [
virtual({
'main.js': js.code
}),
{
name: 'svelte-packages',
resolveId: (importee) => {
if (importee.startsWith('svelte/')) {
return importee.replace('svelte', process.cwd()) + '/index.mjs';
}
if (config.expect_unhandled_rejections) {
listeners.forEach((listener) => {
process.on('unhandledRejection', listener);
});
}
}
]
});
});
}
const result = await bundle.generate({
format: 'iife',
name: 'App'
});
const samples = fs.readdirSync(`${__dirname}/samples`);
await Promise.all(samples.map((sample) => run_test(sample)));
return eval(`(function () { ${result.output[0].code}; return App; }())`);
}
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 () => {
const App = await create_component();
assert.throws(() => {
new App();
}, /'target' is a required option/);
});
it('fails if options.hydrate is true but the component is non-hydratable', async () => {
const App = await create_component();
assert.throws(() => {
new App({
target: { childNodes: [] },
hydrate: true
});
}, /options.hydrate only works if the component was compiled with the `hydratable: true` option/);
}, /options\.hydrate only works if the component was compiled with the `hydratable: true` option/);
});
});

@ -1,9 +1,6 @@
export default {
props: {
a: 3,
b: 4,
c: 5,
d: 6
get props() {
return { a: 3, b: 4, c: 5, d: 6 };
},
html: `
<div>Length: 3</div>

@ -1,9 +1,6 @@
export default {
props: {
a: 3,
b: 4,
c: 5,
d: 6
get props() {
return { a: 3, b: 4, c: 5, d: 6 };
},
html: `
<div>Length: 3</div>

@ -1,7 +1,9 @@
const result = {};
export default {
props: { result },
get props() {
return { result };
},
async test({ assert }) {
assert.notEqual(result.parentElement, null);
}

@ -1,7 +1,6 @@
export default {
props: {
target: 'World!',
display: true
get props() {
return { target: 'World!', display: true };
},
html: `

@ -1,8 +1,8 @@
export default {
skip_if_ssr: true,
props: {
value: 'hello!'
get props() {
return { value: 'hello!' };
},
html: `

@ -1,8 +1,8 @@
export default {
skip_if_ssr: true,
props: {
value: 'hello!'
get props() {
return { value: 'hello!' };
},
html: `

@ -1,12 +1,14 @@
export default {
props: {
things: [
{ id: 1, name: 'a' },
{ id: 2, name: 'b' },
{ id: 3, name: 'c' },
{ id: 4, name: 'd' },
{ id: 5, name: 'e' }
]
get props() {
return {
things: [
{ id: 1, name: 'a' },
{ id: 2, name: 'b' },
{ id: 3, name: 'c' },
{ id: 4, name: 'd' },
{ id: 5, name: 'e' }
]
};
},
html: `

@ -1,12 +1,14 @@
export default {
props: {
things: [
{ id: 1, name: 'a' },
{ id: 2, name: 'b' },
{ id: 3, name: 'c' },
{ id: 4, name: 'd' },
{ id: 5, name: 'e' }
]
get props() {
return {
things: [
{ id: 1, name: 'a' },
{ id: 2, name: 'b' },
{ id: 3, name: 'c' },
{ id: 4, name: 'd' },
{ id: 5, name: 'e' }
]
};
},
html: `

@ -1,12 +1,14 @@
export default {
props: {
things: [
{ id: 1, name: 'a' },
{ id: 2, name: 'b' },
{ id: 3, name: 'c' },
{ id: 4, name: 'd' },
{ id: 5, name: 'e' }
]
get props() {
return {
things: [
{ id: 1, name: 'a' },
{ id: 2, name: 'b' },
{ id: 3, name: 'c' },
{ id: 4, name: 'd' },
{ id: 5, name: 'e' }
]
};
},
html: `

@ -1,3 +0,0 @@
export function linear(t) {
return t;
}

@ -1,7 +1,10 @@
<script>
import { linear } from './easing.js';
export let things;
export function linear(t) {
return t;
}
function flip(node, animation, params) {
const dx = animation.from.left - animation.to.left;
const dy = animation.from.top - animation.to.top;

@ -1,12 +1,14 @@
export default {
props: {
things: [
{ id: 1, name: 'a' },
{ id: 2, name: 'b' },
{ id: 3, name: 'c' },
{ id: 4, name: 'd' },
{ id: 5, name: 'e' }
]
get props() {
return {
things: [
{ id: 1, name: 'a' },
{ id: 2, name: 'b' },
{ id: 3, name: 'c' },
{ id: 4, name: 'd' },
{ id: 5, name: 'e' }
]
};
},
html: `

@ -1,7 +1,8 @@
const value = [];
let value = [];
export default {
props: {
value
get props() {
value = [];
return { value };
},
async test({ assert, target, window }) {

@ -1,6 +1,6 @@
export default {
props: {
value: ''
get props() {
return { value: '' };
},
html: `

@ -1,6 +1,6 @@
export default {
props: {
hidden: true
get props() {
return { hidden: true };
},
html: '<div hidden />',
test({ assert, component, target }) {

@ -3,8 +3,8 @@ export default {
// so it can't be server-rendered
skip_if_ssr: true,
props: {
indeterminate: true
get props() {
return { indeterminate: true };
},
html: "<input type='checkbox'>",

@ -1,6 +1,6 @@
export default {
props: {
inert: true
get props() {
return { inert: true };
},
test({ assert, target, component }) {
const div = target.querySelector('div');

@ -1,10 +1,12 @@
export default {
props: {
items: [
{ foo: true, bar: false },
{ foo: false, bar: true },
{ foo: true, bar: true }
]
get props() {
return {
items: [
{ foo: true, bar: false },
{ foo: false, bar: true },
{ foo: true, bar: true }
]
};
},
html: `

@ -1,7 +1,6 @@
export default {
props: {
inputType: 'text',
inputValue: 42
get props() {
return { inputType: 'text', inputValue: 42 };
},
html: '<input type="text">',

@ -1,5 +1,7 @@
export default {
props: { foo: 'bar' },
get props() {
return { foo: 'bar' };
},
html: `
<svg>

@ -1,6 +1,6 @@
export default {
props: {
testName: 'testClassName'
get props() {
return { testName: 'testClassName' };
},
html: '<div class="testClassName"></div>',

@ -1,7 +1,6 @@
export default {
props: {
testName1: 'test1',
testName2: 'test2'
get props() {
return { testName1: 'test1', testName2: 'test2' };
},
html: '<div class="test1test2"></div>',

@ -1,7 +1,6 @@
export default {
props: {
testName1: 'test1',
testName2: 'test2'
get props() {
return { testName1: 'test1', testName2: 'test2' };
},
html: '<div class="test1test2 svelte-x1o6ra"></div>',

@ -1,6 +1,6 @@
export default {
props: {
testName: 'testClassName'
get props() {
return { testName: 'testClassName' };
},
html: '<div class="testClassName"></div>',

@ -1,6 +1,6 @@
export default {
props: {
testName: 'testClassName'
get props() {
return { testName: 'testClassName' };
},
html: '<div class="testClassName svelte-x1o6ra"></div>',

@ -1,7 +1,6 @@
export default {
props: {
testName1: 'test1',
testName2: 'test2'
get props() {
return { testName1: 'test1', testName2: 'test2' };
},
html: '<div class="test1test2"></div>',

@ -1,7 +1,6 @@
export default {
props: {
testName1: 'test1',
testName2: 'test2'
get props() {
return { testName1: 'test1', testName2: 'test2' };
},
html: '<div class="test1test2 svelte-x1o6ra"></div>',

@ -1,6 +1,6 @@
export default {
props: {
foo: false
get props() {
return { foo: false };
},
test({ assert, component, target }) {

@ -1,12 +1,14 @@
let fulfil;
import { create_deferred } from '../../../helpers.js';
let thePromise = new Promise((f) => {
fulfil = f;
});
let deferred;
export default {
props: {
thePromise
before_test() {
deferred = create_deferred();
},
get props() {
return { thePromise: deferred.promise };
},
html: `
@ -15,39 +17,30 @@ export default {
`,
async test({ assert, component, target }) {
fulfil(42);
deferred.resolve(42);
await thePromise;
await deferred.promise;
assert.htmlEqual(target.innerHTML, '<br />');
let reject;
thePromise = new Promise((f, r) => {
reject = r;
});
component.thePromise = thePromise;
deferred = create_deferred();
component.thePromise = deferred.promise;
assert.htmlEqual(
target.innerHTML,
`
<br />
<p>the promise is pending</p>
`
);
assert.htmlEqual(target.innerHTML, `<br /><p>the promise is pending</p>`);
reject(new Error());
const rejection = deferred.promise
.catch(() => {})
.finally(() => {
assert.htmlEqual(
target.innerHTML,
`<p>oh no! Something broke!</p>
<br />
<p>oh no! Something broke!</p>`
);
});
await thePromise.catch(() => {});
deferred.reject(new Error());
assert.htmlEqual(
target.innerHTML,
`
<p>oh no! Something broke!</p>
<br />
<p>oh no! Something broke!</p>
`
);
await rejection;
}
};

@ -1,44 +1,37 @@
let fulfil;
import { create_deferred } from '../../../helpers.js';
let thePromise = new Promise((f) => {
fulfil = f;
});
let deferred;
export default {
props: {
thePromise
before_test() {
deferred = create_deferred();
},
get props() {
return { thePromise: deferred.promise };
},
html: '',
test({ assert, component, target }) {
fulfil(42);
deferred.resolve(42);
return thePromise
return deferred.promise
.then(() => {
assert.htmlEqual(target.innerHTML, '');
let reject;
thePromise = new Promise((f, r) => {
reject = r;
});
deferred = create_deferred();
component.thePromise = thePromise;
component.thePromise = deferred.promise;
assert.htmlEqual(target.innerHTML, '');
reject(new Error('something broke'));
deferred.reject(new Error('something broke'));
return thePromise.catch(() => {});
return deferred.promise.catch(() => {});
})
.then(() => {
assert.htmlEqual(
target.innerHTML,
`
<p>oh no! something broke</p>
`
);
assert.htmlEqual(target.innerHTML, `<p>oh no! something broke</p>`);
});
}
};

@ -1,8 +1,8 @@
const promise = Promise.resolve(42);
export default {
props: {
promise
get props() {
return { promise };
},
test({ assert, target }) {

@ -1,13 +1,14 @@
let fulfil;
import { create_deferred } from '../../../helpers.js';
const thePromise = new Promise((f) => {
fulfil = f;
});
let deferred;
export default {
props: {
thePromise,
show: true
before_test() {
deferred = create_deferred();
},
get props() {
return { thePromise: deferred.promise, show: true };
},
html: `
@ -15,9 +16,9 @@ export default {
`,
test({ assert, component, target }) {
fulfil(42);
deferred.resolve(42);
return thePromise.then(() => {
return deferred.promise.then(() => {
assert.htmlEqual(
target.innerHTML,
`

@ -5,8 +5,8 @@ promise.then = realPromise.then.bind(realPromise);
promise.catch = realPromise.catch.bind(realPromise);
export default {
props: {
promise
get props() {
return { promise };
},
test({ assert, target }) {

@ -12,8 +12,8 @@ const items = [
];
export default {
props: {
items
get props() {
return { items };
},
html: `

@ -5,8 +5,8 @@ const promise = new Promise((f) => {
});
export default {
props: {
promise
get props() {
return { promise };
},
html: '',

@ -1,12 +1,14 @@
let fulfil;
import { create_deferred } from '../../../helpers.js';
let thePromise = new Promise((f) => {
fulfil = f;
});
let deferred;
export default {
props: {
thePromise
before_test() {
deferred = create_deferred();
},
get props() {
return { thePromise: deferred.promise };
},
html: `
@ -14,9 +16,9 @@ export default {
`,
test({ assert, component, target }) {
fulfil(42);
deferred.resolve(42);
return thePromise
return deferred.promise
.then(() => {
assert.htmlEqual(
target.innerHTML,
@ -25,32 +27,18 @@ export default {
`
);
let reject;
deferred = create_deferred();
thePromise = new Promise((f, r) => {
reject = r;
});
component.thePromise = deferred.promise;
component.thePromise = thePromise;
assert.htmlEqual(target.innerHTML, `<div><p>loading...</p></div>`);
assert.htmlEqual(
target.innerHTML,
`
<div><p>loading...</p></div>
`
);
deferred.reject(new Error('something broke'));
reject(new Error('something broke'));
return thePromise.catch(() => {});
return deferred.promise.catch(() => {});
})
.then(() => {
assert.htmlEqual(
target.innerHTML,
`
<div><p>oh no! something broke</p></div>
`
);
assert.htmlEqual(target.innerHTML, `<div><p>oh no! something broke</p></div>`);
});
}
};

@ -1,11 +1,13 @@
let fulfil;
let thePromise = new Promise((f) => {
fulfil = f;
});
import { create_deferred } from '../../../helpers.js';
let deferred;
export default {
props: {
thePromise
before_test() {
deferred = create_deferred();
},
get props() {
return { thePromise: deferred.promise };
},
html: `
@ -13,16 +15,11 @@ export default {
`,
test({ assert, component, target, window }) {
fulfil(42);
deferred.resolve(42);
return thePromise
return deferred.promise
.then(async () => {
assert.htmlEqual(
target.innerHTML,
`
<button>click me</button>
`
);
assert.htmlEqual(target.innerHTML, `<button>click me</button>`);
const { button } = component;
@ -31,7 +28,7 @@ export default {
assert.equal(component.clicked, 42);
thePromise = Promise.resolve(43);
const thePromise = Promise.resolve(43);
component.thePromise = thePromise;
return thePromise;

@ -5,9 +5,8 @@ const thePromise = new Promise((f) => {
});
export default {
props: {
show: true,
thePromise
get props() {
return { show: true, thePromise };
},
html: `

@ -1,56 +1,36 @@
let fulfil;
import { create_deferred } from '../../../helpers.js';
let thePromise = new Promise((f) => {
fulfil = f;
});
let deferred;
export default {
props: {
thePromise
before_test() {
deferred = create_deferred();
},
get props() {
return { thePromise: deferred.promise };
},
html: `
<p>loading...</p>
`,
test({ assert, component, target }) {
fulfil(42);
return thePromise
.then(() => {
assert.htmlEqual(
target.innerHTML,
`
<p>the value is 42</p>
`
);
let reject;
thePromise = new Promise((f, r) => {
reject = r;
});
component.thePromise = thePromise;
assert.htmlEqual(
target.innerHTML,
`
<p>loading...</p>
`
);
reject(new Error('something broke'));
return thePromise.catch(() => {});
})
.then(() => {
assert.htmlEqual(
target.innerHTML,
`
<p>oh no! something broke</p>
`
);
});
async test({ assert, component, target }) {
deferred.resolve(42);
await deferred.promise;
assert.htmlEqual(target.innerHTML, `<p>the value is 42</p>`);
deferred = create_deferred();
component.thePromise = deferred.promise;
assert.htmlEqual(target.innerHTML, `<p>loading...</p>`);
deferred.reject(new Error('something broke'));
try {
await deferred.promise;
} catch {}
assert.htmlEqual(target.innerHTML, `<p>oh no! something broke</p>`);
}
};

@ -1,12 +1,14 @@
let fulfil;
import { create_deferred } from '../../../helpers.js';
let thePromise = new Promise((f) => {
fulfil = f;
});
let deferred;
export default {
props: {
thePromise
before_test() {
deferred = create_deferred();
},
get props() {
return { thePromise: deferred.promise };
},
html: `
@ -15,9 +17,9 @@ export default {
`,
test({ assert, component, target }) {
fulfil(42);
deferred.resolve(42);
return thePromise
return deferred.promise
.then(() => {
assert.htmlEqual(
target.innerHTML,
@ -27,13 +29,9 @@ export default {
`
);
let reject;
thePromise = new Promise((f, r) => {
reject = r;
});
deferred = create_deferred();
component.thePromise = thePromise;
component.thePromise = deferred.promise;
assert.htmlEqual(
target.innerHTML,
@ -43,9 +41,9 @@ export default {
`
);
reject(new Error('something broke'));
deferred.reject(new Error('something broke'));
return thePromise.catch(() => {});
return deferred.promise.catch(() => {});
})
.then(() => {
assert.htmlEqual(

@ -1,36 +1,34 @@
let fulfil;
import { create_deferred } from '../../../helpers.js';
let thePromise = new Promise((f) => {
fulfil = f;
});
let deferred;
export default {
props: {
thePromise
before_test() {
deferred = create_deferred();
},
get props() {
return { thePromise: deferred.promise };
},
html: 'waiting',
test({ assert, component, target }) {
fulfil(9000);
deferred.resolve(9000);
return thePromise
return deferred.promise
.then(() => {
assert.htmlEqual(target.innerHTML, 'resolved');
let reject;
thePromise = new Promise((f, r) => {
reject = r;
});
deferred = create_deferred();
component.thePromise = thePromise;
component.thePromise = deferred.promise;
assert.htmlEqual(target.innerHTML, 'waiting');
reject(new Error('something broke'));
deferred.reject(new Error('something broke'));
return thePromise.catch(() => {});
return deferred.promise.catch(() => {});
})
.then(() => {
assert.htmlEqual(target.innerHTML, 'rejected');

@ -1,6 +1,6 @@
export default {
props: {
thePromise: 'not actually a promise'
get props() {
return { thePromise: 'not actually a promise' };
},
html: `

@ -5,8 +5,8 @@ const thePromise = new Promise((f) => {
});
export default {
props: {
thePromise
get props() {
return { thePromise };
},
html: `

@ -5,8 +5,8 @@ let promise = new Promise((f) => {
});
export default {
props: {
promise
get props() {
return { promise };
},
html: `

@ -1,12 +1,13 @@
let fulfil;
let thePromise = new Promise((f) => {
fulfil = f;
});
import { create_deferred } from '../../../helpers.js';
let deferred;
export default {
props: {
thePromise
before_test() {
deferred = create_deferred();
},
get props() {
return { thePromise: deferred.promise };
},
html: `
@ -14,9 +15,9 @@ export default {
`,
test({ assert, component, target }) {
fulfil(42);
deferred.resolve(42);
return thePromise
return deferred.promise
.then(() => {
assert.htmlEqual(
target.innerHTML,
@ -25,32 +26,18 @@ export default {
`
);
let reject;
thePromise = new Promise((f, r) => {
reject = r;
});
deferred = create_deferred();
component.thePromise = thePromise;
component.thePromise = deferred.promise;
assert.htmlEqual(
target.innerHTML,
`
<p>loading...</p>
`
);
assert.htmlEqual(target.innerHTML, `<p>loading...</p>`);
reject(new Error('something broke'));
deferred.reject(new Error('something broke'));
return thePromise.catch(() => {});
return deferred.promise.catch(() => {});
})
.then(() => {
assert.htmlEqual(
target.innerHTML,
`
<p>oh no! something broke</p>
`
);
assert.htmlEqual(target.innerHTML, `<p>oh no! something broke</p>`);
});
}
};

@ -1,6 +1,8 @@
export default {
props: {
thePromise: new Promise((_) => {})
get props() {
return {
thePromise: new Promise((_) => {})
};
},
html: `

@ -1,6 +1,8 @@
export default {
props: {
thePromise: new Promise((_) => {})
get props() {
return {
thePromise: new Promise((_) => {})
};
},
html: `

@ -1,6 +1,8 @@
export default {
props: {
thePromise: new Promise((_) => {})
get props() {
return {
thePromise: new Promise((_) => {})
};
},
html: `

@ -1,6 +1,8 @@
export default {
props: {
thePromise: Promise.resolve({ result: 1 })
get props() {
return {
thePromise: Promise.resolve({ result: 1 })
};
},
html: '',

@ -1,6 +1,8 @@
export default {
props: {
thePromise: new Promise((_) => {})
get props() {
return {
thePromise: new Promise((_) => {})
};
},
html: `

@ -5,8 +5,8 @@ const thePromise = new Promise((f) => {
});
export default {
props: {
thePromise
get props() {
return { thePromise };
},
html: `

@ -1,12 +1,14 @@
let fulfil;
import { create_deferred } from '../../../helpers.js';
let thePromise = new Promise((f) => {
fulfil = f;
});
let deferred;
export default {
props: {
thePromise
before_test() {
deferred = create_deferred();
},
get props() {
return { thePromise: deferred.promise };
},
html: `
@ -15,10 +17,11 @@ export default {
<p>the promise is pending</p>
`,
expect_unhandled_rejections: true,
async test({ assert, component, target }) {
fulfil();
deferred.resolve();
await thePromise;
await deferred.promise;
assert.htmlEqual(
target.innerHTML,
@ -31,13 +34,9 @@ export default {
`
);
let reject;
const local = (deferred = create_deferred());
thePromise = new Promise((f, r) => {
reject = r;
});
component.thePromise = thePromise;
component.thePromise = local.promise;
assert.htmlEqual(
target.innerHTML,
@ -48,17 +47,17 @@ export default {
`
);
reject(new Error('something broke'));
local.reject(new Error('something broke'));
await thePromise.catch(() => {});
try {
await local.promise;
} catch {}
assert.htmlEqual(
target.innerHTML,
`
<p>oh no! something broke</p>
<br>
<br>
`
`<p>oh no! something broke</p>
<br />
<br />`
);
}
};

@ -1,20 +1,22 @@
let fulfil;
import { create_deferred } from '../../../helpers.js';
let thePromise = new Promise((f) => {
fulfil = f;
});
let deferred;
export default {
props: {
thePromise
before_test() {
deferred = create_deferred();
},
get props() {
return { thePromise: deferred.promise };
},
html: '',
test({ assert, component, target }) {
fulfil(42);
deferred.resolve(42);
return thePromise
return deferred.promise
.then(() => {
assert.htmlEqual(
target.innerHTML,
@ -23,19 +25,15 @@ export default {
`
);
let reject;
thePromise = new Promise((f, r) => {
reject = r;
});
deferred = create_deferred();
component.thePromise = thePromise;
component.thePromise = deferred.promise;
assert.htmlEqual(target.innerHTML, '');
reject(new Error('something broke'));
deferred.reject(new Error('something broke'));
return thePromise.catch(() => {});
return deferred.promise.catch(() => {});
})
.then(() => {
assert.htmlEqual(

@ -1,7 +1,9 @@
export default {
props: {
thePromise: new Promise((_) => {}),
count: 0
get props() {
return {
thePromise: new Promise((_) => {}),
count: 0
};
},
html: `

@ -1,6 +1,8 @@
export default {
props: {
thePromise: new Promise((_) => {})
get props() {
return {
thePromise: new Promise((_) => {})
};
},
html: `

@ -1,7 +1,9 @@
export default {
props: {
thePromise: new Promise((_) => {}),
count: 0
get props() {
return {
thePromise: new Promise((_) => {}),
count: 0
};
},
html: `

@ -1,47 +1,37 @@
let fulfil;
import { create_deferred } from '../../../helpers.js';
let promise = new Promise((f) => {
fulfil = f;
});
let deferred;
export default {
props: {
promise
before_test() {
deferred = create_deferred();
},
get props() {
return { promise: deferred.promise };
},
html: `
<p>loading...</p>
`,
expect_unhandled_rejections: true,
test({ assert, component, target }) {
fulfil(42);
deferred.resolve(42);
return promise
return deferred.promise
.then(() => {
assert.htmlEqual(
target.innerHTML,
`
<p>loaded</p>
`
);
let reject;
promise = new Promise((f, r) => {
reject = r;
});
component.promise = promise;
assert.htmlEqual(
target.innerHTML,
`
<p>loading...</p>
`
);
reject(new Error('this error should be thrown'));
return promise;
assert.htmlEqual(target.innerHTML, `<p>loaded</p>`);
deferred = create_deferred();
component.promise = deferred.promise;
assert.htmlEqual(target.innerHTML, `<p>loading...</p>`);
deferred.reject(new Error('this error should be thrown'));
return deferred.promise;
})
.catch((err) => {
assert.equal(err.message, 'this error should be thrown');

@ -1,8 +1,8 @@
export default {
skip_if_ssr: true,
props: {
value: 'hello!'
get props() {
return { value: 'hello!' };
},
html: `

@ -1,6 +1,6 @@
export default {
props: {
name: '<b>world</b>'
get props() {
return { name: '<b>world</b>' };
},
html: `

@ -1,6 +1,6 @@
export default {
props: {
name: 'world'
get props() {
return { name: 'world' };
},
ssrHtml: `

@ -1,6 +1,6 @@
export default {
props: {
name: 'world'
get props() {
return { name: 'world' };
},
html: `

@ -1,16 +1,16 @@
const tasks = [
{ description: 'put your left leg in', done: false },
{ description: 'your left leg out', done: false },
{ description: 'in, out, in, out', done: false },
{ description: 'shake it all about', done: false }
];
let tasks = [];
export default {
skip_if_ssr: true,
props: {
tasks,
selected: tasks[0]
get props() {
tasks = [
{ description: 'put your left leg in', done: false },
{ description: 'your left leg out', done: false },
{ description: 'in, out, in, out', done: false },
{ description: 'shake it all about', done: false }
];
return { tasks, selected: tasks[0] };
},
html: `

@ -1,10 +1,12 @@
export default {
props: {
items: [
{ description: 'one', completed: true },
{ description: 'two', completed: false },
{ description: 'three', completed: false }
]
get props() {
return {
items: [
{ description: 'one', completed: true },
{ description: 'two', completed: false },
{ description: 'three', completed: false }
]
};
},
html: `

@ -1,9 +1,8 @@
const values = [{ name: 'Alpha' }, { name: 'Beta' }, { name: 'Gamma' }];
export default {
props: {
values,
selected: [values[1]]
get props() {
return { values, selected: [values[1]] };
},
html: `

@ -1,9 +1,8 @@
const values = [{ name: 'Alpha' }, { name: 'Beta' }, { name: 'Gamma' }];
export default {
props: {
values,
selected: [values[1]]
get props() {
return { values, selected: [values[1]] };
},
html: `

@ -1,8 +1,8 @@
export default {
skip_if_ssr: true,
props: {
indeterminate: true
get props() {
return { indeterminate: true };
},
html: `

@ -1,15 +1,11 @@
export default {
props: {
cats: [
{
name: 'cat 0',
checked: false
},
{
name: 'cat 1',
checked: false
}
]
get props() {
return {
cats: [
{ name: 'cat 0', checked: false },
{ name: 'cat 1', checked: false }
]
};
},
html: `

@ -1,6 +1,6 @@
export default {
props: {
foo: true
get props() {
return { foo: true };
},
html: `

@ -1,11 +1,14 @@
const values = [{ name: 'Alpha' }, { name: 'Beta' }, { name: 'Gamma' }];
const selected_array = [[values[1]], [], [values[2]]];
let values = [];
let selected_array = [];
export default {
props: {
values,
selected_array
before_test() {
values = [{ name: 'Alpha' }, { name: 'Beta' }, { name: 'Gamma' }];
selected_array = [[values[1]], [], [values[2]]];
},
get props() {
return { values, selected_array };
},
html: `

@ -1,11 +1,15 @@
const values = [{ name: 'Alpha' }, { name: 'Beta' }, { name: 'Gamma' }];
let values = [];
const selected_array = [[values[1]], [], [values[2]]];
let selected_array = [];
export default {
props: {
values,
selected_array
before_test() {
values = [{ name: 'Alpha' }, { name: 'Beta' }, { name: 'Gamma' }];
selected_array = [[values[1]], [], [values[2]]];
},
get props() {
return { values, selected_array };
},
html: `

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save