remove test duplication

pull/15538/head
Rich Harris 4 months ago
parent 07aed41b7b
commit 4039f9ef24

@ -58,17 +58,15 @@ export function create_deferred() {
* @param {Partial<CompileOptions>} compileOptions * @param {Partial<CompileOptions>} compileOptions
* @param {boolean} [output_map] * @param {boolean} [output_map]
* @param {any} [preprocessor] * @param {any} [preprocessor]
* @param {import('./suite').TemplatingMode} [templating_mode]
*/ */
export async function compile_directory( export async function compile_directory(
cwd, cwd,
generate, generate,
compileOptions = {}, compileOptions = {},
output_map = false, output_map = false,
preprocessor, preprocessor
templating_mode
) { ) {
const output_dir = `${cwd}/_output/${generate}${templating_mode === 'functional' ? `-functional` : ''}`; const output_dir = `${cwd}/_output/${generate}`;
fs.rmSync(output_dir, { recursive: true, force: true }); fs.rmSync(output_dir, { recursive: true, force: true });
@ -79,8 +77,7 @@ export async function compile_directory(
let opts = { let opts = {
filename: path.join(cwd, file), filename: path.join(cwd, file),
...compileOptions, ...compileOptions,
generate, generate
templatingMode: templating_mode
}; };
if (file.endsWith('.js')) { if (file.endsWith('.js')) {
@ -193,3 +190,6 @@ if (typeof window !== 'undefined') {
}; };
}); });
} }
export const templatingMode =
/** @type {'string' | 'functional'} */ (process.env.TEMPLATING_MODE) ?? 'string';

@ -4,6 +4,7 @@ import * as fs from 'node:fs';
import { assert } from 'vitest'; import { assert } from 'vitest';
import { compile_directory } from '../helpers.js'; import { compile_directory } from '../helpers.js';
import { assert_html_equal } from '../html_equal.js'; import { assert_html_equal } from '../html_equal.js';
import { templatingMode } from '../helpers.js';
import { assert_ok, suite, type BaseTest } from '../suite.js'; import { assert_ok, suite, type BaseTest } from '../suite.js';
import { createClassComponent } from 'svelte/legacy'; import { createClassComponent } from 'svelte/legacy';
import { render } from 'svelte/server'; import { render } from 'svelte/server';
@ -41,24 +42,15 @@ function read(path: string): string | void {
return fs.existsSync(path) ? fs.readFileSync(path, 'utf-8') : undefined; return fs.existsSync(path) ? fs.readFileSync(path, 'utf-8') : undefined;
} }
const { test, run } = suite<HydrationTest>(async (config, cwd, templating_mode) => { const { test, run } = suite<HydrationTest>(async (config, cwd) => {
if (!config.load_compiled) { if (!config.load_compiled) {
await compile_directory( await compile_directory(cwd, 'client', {
cwd, accessors: true,
'client', templatingMode,
{ accessors: true, ...config.compileOptions }, ...config.compileOptions
undefined, });
undefined,
templating_mode await compile_directory(cwd, 'server', config.compileOptions, undefined, undefined);
);
await compile_directory(
cwd,
'server',
config.compileOptions,
undefined,
undefined,
templating_mode
);
} }
const target = window.document.body; const target = window.document.body;
@ -116,11 +108,7 @@ const { test, run } = suite<HydrationTest>(async (config, cwd, templating_mode)
}; };
const component = createClassComponent({ const component = createClassComponent({
component: ( component: (await import(`${cwd}/_output/client/main.svelte.js`)).default,
await import(
`${cwd}/_output/client${templating_mode === 'functional' ? '-functional' : ''}/main.svelte.js`
)
).default,
target, target,
hydrate: true, hydrate: true,
props: config.props, props: config.props,
@ -187,5 +175,4 @@ const { test, run } = suite<HydrationTest>(async (config, cwd, templating_mode)
}); });
export { test, assert_ok }; export { test, assert_ok };
await run(__dirname, 'string'); await run(__dirname);
await run(__dirname, 'functional');

@ -4,8 +4,8 @@ import * as fs from 'node:fs';
import * as path from 'node:path'; import * as path from 'node:path';
import { compile } from 'svelte/compiler'; import { compile } from 'svelte/compiler';
import { afterAll, assert, beforeAll, describe } from 'vitest'; import { afterAll, assert, beforeAll, describe } from 'vitest';
import { suite, suite_with_variants, type TemplatingMode } from '../suite'; import { suite, suite_with_variants } from '../suite';
import { write } from '../helpers'; import { write, templatingMode } from '../helpers';
import type { Warning } from '#compiler'; import type { Warning } from '#compiler';
const assert_file = path.resolve(__dirname, 'assert.js'); const assert_file = path.resolve(__dirname, 'assert.js');
@ -35,41 +35,34 @@ const { run: run_browser_tests } = suite_with_variants<
return false; return false;
}, },
() => {}, () => {},
async (config, test_dir, variant, _, templating_mode) => { async (config, test_dir, variant) => {
await run_test(test_dir, config, variant === 'hydrate', templating_mode); await run_test(test_dir, config, variant === 'hydrate');
} }
); );
describe.concurrent( describe.concurrent(
'runtime-browser', 'runtime-browser',
() => run_browser_tests(__dirname, 'string'), () => run_browser_tests(__dirname),
// Browser tests are brittle and slow on CI
{ timeout: 20000, retry: process.env.CI ? 1 : 0 }
);
describe.concurrent(
'runtime-browser-functional',
() => run_browser_tests(__dirname, 'functional'),
// Browser tests are brittle and slow on CI // Browser tests are brittle and slow on CI
{ timeout: 20000, retry: process.env.CI ? 1 : 0 } { timeout: 20000, retry: process.env.CI ? 1 : 0 }
); );
const { run: run_ce_tests } = suite<ReturnType<typeof import('./assert').test>>( const { run: run_ce_tests } = suite<ReturnType<typeof import('./assert').test>>(
async (config, test_dir, templating_mode) => { async (config, test_dir) => {
await run_test(test_dir, config, false, templating_mode); await run_test(test_dir, config, false);
} }
); );
describe.concurrent( describe.concurrent(
'custom-elements', 'custom-elements',
() => run_ce_tests(__dirname, 'string', 'custom-elements-samples'), () => run_ce_tests(__dirname, 'custom-elements-samples'),
// Browser tests are brittle and slow on CI // Browser tests are brittle and slow on CI
{ timeout: 20000, retry: process.env.CI ? 1 : 0 } { timeout: 20000, retry: process.env.CI ? 1 : 0 }
); );
describe.concurrent( describe.concurrent(
'custom-elements', 'custom-elements',
() => run_ce_tests(__dirname, 'functional', 'custom-elements-samples'), () => run_ce_tests(__dirname, 'custom-elements-samples'),
// Browser tests are brittle and slow on CI // Browser tests are brittle and slow on CI
{ timeout: 20000, retry: process.env.CI ? 1 : 0 } { timeout: 20000, retry: process.env.CI ? 1 : 0 }
); );
@ -77,8 +70,7 @@ describe.concurrent(
async function run_test( async function run_test(
test_dir: string, test_dir: string,
config: ReturnType<typeof import('./assert').test>, config: ReturnType<typeof import('./assert').test>,
hydrate: boolean, hydrate: boolean
templating_mode: TemplatingMode
) { ) {
const warnings: any[] = []; const warnings: any[] = [];
@ -102,17 +94,14 @@ async function run_test(
build.onLoad({ filter: /\.svelte$/ }, (args) => { build.onLoad({ filter: /\.svelte$/ }, (args) => {
const compiled = compile(fs.readFileSync(args.path, 'utf-8').replace(/\r/g, ''), { const compiled = compile(fs.readFileSync(args.path, 'utf-8').replace(/\r/g, ''), {
generate: 'client', generate: 'client',
templatingMode,
...config.compileOptions, ...config.compileOptions,
immutable: config.immutable, immutable: config.immutable,
customElement: test_dir.includes('custom-elements-samples'), customElement: test_dir.includes('custom-elements-samples'),
accessors: 'accessors' in config ? config.accessors : true, accessors: 'accessors' in config ? config.accessors : true
templatingMode: templating_mode
}); });
write( write(`${test_dir}/_output/client/${path.basename(args.path)}.js`, compiled.js.code);
`${test_dir}/_output/client${templating_mode === 'functional' ? '-functional' : ''}/${path.basename(args.path)}.js`,
compiled.js.code
);
compiled.warnings.forEach((warning) => { compiled.warnings.forEach((warning) => {
if (warning.code === 'options_deprecated_accessors') return; if (warning.code === 'options_deprecated_accessors') return;
@ -121,10 +110,7 @@ async function run_test(
if (compiled.css !== null) { if (compiled.css !== null) {
compiled.js.code += `document.head.innerHTML += \`<style>${compiled.css.code}</style>\``; compiled.js.code += `document.head.innerHTML += \`<style>${compiled.css.code}</style>\``;
write( write(`${test_dir}/_output/${path.basename(args.path)}.css`, compiled.css.code);
`${test_dir}/_output/${templating_mode === 'functional' ? '-functional' : ''}/${path.basename(args.path)}.css`,
compiled.css.code
);
} }
return { return {
@ -170,8 +156,7 @@ async function run_test(
...config.compileOptions, ...config.compileOptions,
immutable: config.immutable, immutable: config.immutable,
customElement: test_dir.includes('custom-elements-samples'), customElement: test_dir.includes('custom-elements-samples'),
accessors: 'accessors' in config ? config.accessors : true, accessors: 'accessors' in config ? config.accessors : true
templatingMode: templating_mode
}); });
return { return {

@ -6,11 +6,11 @@ import { proxy } from 'svelte/internal/client';
import { flushSync, hydrate, mount, unmount } from 'svelte'; import { flushSync, hydrate, mount, unmount } from 'svelte';
import { render } from 'svelte/server'; import { render } from 'svelte/server';
import { afterAll, assert, beforeAll } from 'vitest'; import { afterAll, assert, beforeAll } from 'vitest';
import { compile_directory } from '../helpers.js'; import { compile_directory, templatingMode } from '../helpers.js';
import { setup_html_equal } from '../html_equal.js'; import { setup_html_equal } from '../html_equal.js';
import { raf } from '../animation-helpers.js'; import { raf } from '../animation-helpers.js';
import type { CompileOptions } from '#compiler'; import type { CompileOptions } from '#compiler';
import { suite_with_variants, type BaseTest, type TemplatingMode } from '../suite.js'; import { suite_with_variants, type BaseTest } from '../suite.js';
import { seen } from '../../src/internal/server/dev.js'; import { seen } from '../../src/internal/server/dev.js';
type Assert = typeof import('vitest').assert & { type Assert = typeof import('vitest').assert & {
@ -142,21 +142,16 @@ export function runtime_suite(runes: boolean) {
return false; return false;
}, },
(config, cwd, templating_mode) => { (config, cwd) => {
return common_setup(cwd, runes, config, templating_mode); return common_setup(cwd, runes, config);
}, },
async (config, cwd, variant, common, templating_mode) => { async (config, cwd, variant, common) => {
await run_test_variant(cwd, config, variant, common, runes, templating_mode); await run_test_variant(cwd, config, variant, common, runes);
} }
); );
} }
async function common_setup( async function common_setup(cwd: string, runes: boolean | undefined, config: RuntimeTest) {
cwd: string,
runes: boolean | undefined,
config: RuntimeTest,
templating_mode: TemplatingMode
) {
const force_hmr = process.env.HMR && config.compileOptions?.dev !== false && !config.error; const force_hmr = process.env.HMR && config.compileOptions?.dev !== false && !config.error;
const compileOptions: CompileOptions = { const compileOptions: CompileOptions = {
@ -164,17 +159,17 @@ async function common_setup(
rootDir: cwd, rootDir: cwd,
dev: force_hmr ? true : undefined, dev: force_hmr ? true : undefined,
hmr: force_hmr ? true : undefined, hmr: force_hmr ? true : undefined,
templatingMode,
...config.compileOptions, ...config.compileOptions,
immutable: config.immutable, immutable: config.immutable,
accessors: 'accessors' in config ? config.accessors : true, accessors: 'accessors' in config ? config.accessors : true,
runes, runes
templatingMode: templating_mode
}; };
// load_compiled can be used for debugging a test. It means the compiler will not run on the input // load_compiled can be used for debugging a test. It means the compiler will not run on the input
// so you can manipulate the output manually to see what fixes it, adding console.logs etc. // so you can manipulate the output manually to see what fixes it, adding console.logs etc.
if (!config.load_compiled) { if (!config.load_compiled) {
await compile_directory(cwd, 'client', compileOptions, undefined, undefined, templating_mode); await compile_directory(cwd, 'client', compileOptions, undefined, undefined);
await compile_directory(cwd, 'server', compileOptions); await compile_directory(cwd, 'server', compileOptions);
} }
@ -186,8 +181,7 @@ async function run_test_variant(
config: RuntimeTest, config: RuntimeTest,
variant: 'dom' | 'hydrate' | 'ssr', variant: 'dom' | 'hydrate' | 'ssr',
compileOptions: CompileOptions, compileOptions: CompileOptions,
runes: boolean, runes: boolean
templating_mode: TemplatingMode
) { ) {
let unintended_error = false; let unintended_error = false;
@ -266,14 +260,9 @@ async function run_test_variant(
// Put things we need on window for testing // Put things we need on window for testing
const styles = globSync('**/*.css', { const styles = globSync('**/*.css', {
cwd: `${cwd}/_output/client${templating_mode === 'functional' ? '-functional' : ''}` cwd: `${cwd}/_output/client`
}) })
.map((file) => .map((file) => fs.readFileSync(`${cwd}/_output/client/${file}`, 'utf-8'))
fs.readFileSync(
`${cwd}/_output/client${templating_mode === 'functional' ? '-functional' : ''}/${file}`,
'utf-8'
)
)
.join('\n') .join('\n')
.replace(/\/\*<\/?style>\*\//g, ''); .replace(/\/\*<\/?style>\*\//g, '');
@ -289,9 +278,7 @@ async function run_test_variant(
globalThis.requestAnimationFrame = globalThis.setTimeout; globalThis.requestAnimationFrame = globalThis.setTimeout;
let mod = await import( let mod = await import(`${cwd}/_output/client/main.svelte.js`);
`${cwd}/_output/client${templating_mode === 'functional' ? '-functional' : ''}/main.svelte.js`
);
const target = window.document.querySelector('main') as HTMLElement; const target = window.document.querySelector('main') as HTMLElement;

@ -11,5 +11,4 @@ const { test, run } = runtime_suite(false);
export { test, ok }; export { test, ok };
await run(__dirname, 'string'); await run(__dirname);
await run(__dirname, 'functional');

@ -5,5 +5,4 @@ const { test, run } = runtime_suite(true);
export { test, ok }; export { test, ok };
await run(__dirname, 'string'); await run(__dirname);
await run(__dirname, 'functional');

@ -9,15 +9,8 @@ interface SnapshotTest extends BaseTest {
compileOptions?: Partial<import('#compiler').CompileOptions>; compileOptions?: Partial<import('#compiler').CompileOptions>;
} }
const { test, run } = suite<SnapshotTest>(async (config, cwd, templating_mode) => { const { test, run } = suite<SnapshotTest>(async (config, cwd) => {
await compile_directory( await compile_directory(cwd, 'client', config.compileOptions);
cwd,
'client',
config.compileOptions,
undefined,
undefined,
templating_mode
);
await compile_directory(cwd, 'server', config.compileOptions); await compile_directory(cwd, 'server', config.compileOptions);
// run `UPDATE_SNAPSHOTS=true pnpm test snapshot` to update snapshot tests // run `UPDATE_SNAPSHOTS=true pnpm test snapshot` to update snapshot tests
@ -25,18 +18,8 @@ const { test, run } = suite<SnapshotTest>(async (config, cwd, templating_mode) =
fs.rmSync(`${cwd}/_expected`, { recursive: true, force: true }); fs.rmSync(`${cwd}/_expected`, { recursive: true, force: true });
fs.cpSync(`${cwd}/_output`, `${cwd}/_expected`, { recursive: true, force: true }); fs.cpSync(`${cwd}/_output`, `${cwd}/_expected`, { recursive: true, force: true });
} else { } else {
const actual = globSync('**', { cwd: `${cwd}/_output`, onlyFiles: true }).filter( const actual = globSync('**', { cwd: `${cwd}/_output`, onlyFiles: true });
// filters out files that might not yet be compiled (functional is executed after string) const expected = globSync('**', { cwd: `${cwd}/_expected`, onlyFiles: true });
(expected) =>
expected.startsWith('server/') ||
expected.startsWith(`client${templating_mode === 'functional' ? '-functional' : ''}/`)
);
const expected = globSync('**', { cwd: `${cwd}/_expected`, onlyFiles: true }).filter(
// filters out files that might not yet be compiled (functional is executed after string)
(expected) =>
expected.startsWith('server/') ||
expected.startsWith(`client${templating_mode === 'functional' ? '-functional' : ''}/`)
);
assert.deepEqual(actual, expected); assert.deepEqual(actual, expected);
@ -58,5 +41,4 @@ const { test, run } = suite<SnapshotTest>(async (config, cwd, templating_mode) =
export { test }; export { test };
await run(__dirname, 'string'); await run(__dirname);
await run(__dirname, 'functional');

@ -6,8 +6,6 @@ export interface BaseTest {
solo?: boolean; solo?: boolean;
} }
export type TemplatingMode = 'string' | 'functional';
/** /**
* To filter tests, run one of these: * To filter tests, run one of these:
* *
@ -22,22 +20,14 @@ const filter = process.env.FILTER
) )
: /./; : /./;
export function suite<Test extends BaseTest>( export function suite<Test extends BaseTest>(fn: (config: Test, test_dir: string) => void) {
fn: (config: Test, test_dir: string, templating_mode: TemplatingMode) => void
) {
return { return {
test: (config: Test) => config, test: (config: Test) => config,
run: async ( run: async (cwd: string, samples_dir = 'samples') => {
cwd: string,
templating_mode: TemplatingMode = 'string',
samples_dir = 'samples'
) => {
await for_each_dir<Test>(cwd, samples_dir, (config, dir) => { await for_each_dir<Test>(cwd, samples_dir, (config, dir) => {
let it_fn = config.skip ? it.skip : config.solo ? it.only : it; let it_fn = config.skip ? it.skip : config.solo ? it.only : it;
it_fn(`${dir} (${templating_mode})`, () => it_fn(dir, () => fn(config, `${cwd}/${samples_dir}/${dir}`));
fn(config, `${cwd}/${samples_dir}/${dir}`, templating_mode)
);
}); });
} }
}; };
@ -46,26 +36,12 @@ export function suite<Test extends BaseTest>(
export function suite_with_variants<Test extends BaseTest, Variants extends string, Common>( export function suite_with_variants<Test extends BaseTest, Variants extends string, Common>(
variants: Variants[], variants: Variants[],
should_skip_variant: (variant: Variants, config: Test) => boolean | 'no-test', should_skip_variant: (variant: Variants, config: Test) => boolean | 'no-test',
common_setup: ( common_setup: (config: Test, test_dir: string) => Promise<Common> | Common,
config: Test, fn: (config: Test, test_dir: string, variant: Variants, common: Common) => void
test_dir: string,
templating_mode: TemplatingMode
) => Promise<Common> | Common,
fn: (
config: Test,
test_dir: string,
variant: Variants,
common: Common,
templating_mode: TemplatingMode
) => void
) { ) {
return { return {
test: (config: Test) => config, test: (config: Test) => config,
run: async ( run: async (cwd: string, samples_dir = 'samples') => {
cwd: string,
templating_mode: TemplatingMode = 'string',
samples_dir = 'samples'
) => {
await for_each_dir<Test>(cwd, samples_dir, (config, dir) => { await for_each_dir<Test>(cwd, samples_dir, (config, dir) => {
let called_common = false; let called_common = false;
let common: any = undefined; let common: any = undefined;
@ -78,12 +54,12 @@ export function suite_with_variants<Test extends BaseTest, Variants extends stri
const solo = config.solo; const solo = config.solo;
let it_fn = skip ? it.skip : solo ? it.only : it; let it_fn = skip ? it.skip : solo ? it.only : it;
it_fn(`${dir} (${templating_mode}-${variant})`, async () => { it_fn(dir, async () => {
if (!called_common) { if (!called_common) {
called_common = true; called_common = true;
common = await common_setup(config, `${cwd}/${samples_dir}/${dir}`, templating_mode); common = await common_setup(config, `${cwd}/${samples_dir}/${dir}`);
} }
return fn(config, `${cwd}/${samples_dir}/${dir}`, variant, common, templating_mode); return fn(config, `${cwd}/${samples_dir}/${dir}`, variant, common);
}); });
} }
}); });

Loading…
Cancel
Save