import * as assert from "assert"; import * as path from "path"; import * as fs from "fs"; import { rollup } from 'rollup'; import * as virtual from 'rollup-plugin-virtual'; import { transitionManager } from "../../internal.js"; import { showOutput, loadConfig, loadSvelte, env, setupHtmlEqual } from "../helpers.js"; const main = path.resolve('index.js'); let svelte$; let svelte; let compileOptions = null; let compile = null; function getName(filename) { const base = path.basename(filename).replace(".html", ""); return base[0].toUpperCase() + base.slice(1); } const sveltePath = process.cwd().split('\\').join('/'); const internal = `${sveltePath}/internal.js`; describe("runtime", () => { before(() => { svelte = loadSvelte(false); svelte$ = loadSvelte(true); require.extensions[".html"] = function(module, filename) { const options = Object.assign( { filename, name: getName(filename), format: 'cjs', sveltePath }, compileOptions ); const { js: { code } } = compile(fs.readFileSync(filename, "utf-8"), options); return module._compile(code, filename); }; return setupHtmlEqual(); }); const failed = new Set(); function runTest(dir, hydrate) { if (dir[0] === ".") return; const { flush } = require(internal); const config = loadConfig(`./runtime/samples/${dir}/_config.js`); if (hydrate && config.skip_if_hydrate) return; if (config.solo && process.env.CI) { throw new Error("Forgot to remove `solo: true` from test"); } (config.skip ? it.skip : config.solo ? it.only : it)(`${dir} ${hydrate ? '(with hydration)' : ''}`, () => { if (failed.has(dir)) { // this makes debugging easier, by only printing compiled output once throw new Error('skipping test, already failed'); } compile = (config.preserveIdentifiers ? svelte : svelte$).compile; const cwd = path.resolve(`test/runtime/samples/${dir}`); global.document.title = ''; compileOptions = config.compileOptions || {}; compileOptions.sveltePath = sveltePath; compileOptions.hydratable = hydrate; compileOptions.immutable = config.immutable; Object.keys(require.cache) .filter(x => x.endsWith(".html")) .forEach(file => { delete require.cache[file]; }); let mod; let SvelteComponent; let unintendedError = null; const window = env(); return Promise.resolve() .then(() => { // set of hacks to support transition tests transitionManager.running = false; transitionManager.transitions = []; const raf = { time: 0, callback: null, tick: now => { raf.time = now; if (raf.callback) raf.callback(); } }; window.performance.now = () => raf.time; global.requestAnimationFrame = cb => { let called = false; raf.callback = () => { if (!called) { called = true; cb(); } }; }; try { mod = require(`./samples/${dir}/main.html`); SvelteComponent = mod.default; } catch (err) { showOutput(cwd, { internal, hydratable: hydrate, format: 'cjs' }, svelte.compile); // eslint-disable-line no-console throw err; } global.window = window; // Put the constructor on window for testing window.SvelteComponent = SvelteComponent; const target = window.document.querySelector("main"); const warnings = []; const warn = console.warn; console.warn = warning => { warnings.push(warning); }; const options = Object.assign({}, { target, hydrate, props: config.props, intro: config.intro }, config.options || {}); const component = new SvelteComponent(options); console.warn = warn; if (config.error) { unintendedError = true; throw new Error("Expected a runtime error"); } if (config.warnings) { assert.deepEqual(warnings, config.warnings); } else if (warnings.length) { unintendedError = true; throw new Error("Received unexpected warnings"); } if (config.html) { assert.htmlEqual(target.innerHTML, config.html); } if (config.test) { return Promise.resolve(config.test({ assert, component, mod, target, window, raf })).then(() => { component.$destroy(); }); } else { component.$destroy(); assert.htmlEqual(target.innerHTML, ""); } }) .catch(err => { if (config.error && !unintendedError) { if (typeof config.error === 'function') { config.error(assert, err); } else { assert.equal(config.error, err.message); } } else { failed.add(dir); showOutput(cwd, { internal, hydratable: hydrate, dev: compileOptions.dev, format: 'cjs' }, svelte.compile); // eslint-disable-line no-console throw err; } }) .then(() => { if (config.show) { showOutput(cwd, { internal, hydratable: hydrate, format: 'cjs' }, svelte.compile); } flush(); }); }); } fs.readdirSync("test/runtime/samples").forEach(dir => { runTest(dir, false); runTest(dir, true); }); async function create_component(src = '
') { const { js } = svelte$.compile(src, { format: "esm", name: "SvelteComponent", dev: true }); const bundle = await rollup({ input: 'main.js', plugins: [ virtual({ 'main.js': js.code }), { resolveId: (importee, importer) => { if (importee.startsWith('svelte/')) { return importee.replace('svelte', process.cwd()) + '.mjs'; } } } ] }); const result = await bundle.generate({ format: 'iife', name: 'App' }); return eval( `(function () { ${result.code}; return App; }())` ); } 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/); }); });