From 2268e8c03c5c533248b56e81661646b420ef7bec Mon Sep 17 00:00:00 2001 From: tanhauhau Date: Sat, 24 Apr 2021 14:24:56 +0800 Subject: [PATCH] add test --- test/helpers.ts | 8 + test/runtime-puppeteer/assert.js | 54 ++++ test/runtime-puppeteer/index.ts | 250 ++++++++++++++++++ .../Slider.svelte | 17 ++ .../_config.js | 41 +++ .../main.svelte | 23 ++ .../Slider.svelte | 17 ++ .../_config.js | 27 ++ .../main.svelte | 15 ++ test/server-side-rendering/index.ts | 147 +++++----- 10 files changed, 528 insertions(+), 71 deletions(-) create mode 100644 test/runtime-puppeteer/assert.js create mode 100644 test/runtime-puppeteer/index.ts create mode 100644 test/runtime-puppeteer/samples/component-css-custom-properties-dynamic/Slider.svelte create mode 100644 test/runtime-puppeteer/samples/component-css-custom-properties-dynamic/_config.js create mode 100644 test/runtime-puppeteer/samples/component-css-custom-properties-dynamic/main.svelte create mode 100644 test/runtime-puppeteer/samples/component-css-custom-properties/Slider.svelte create mode 100644 test/runtime-puppeteer/samples/component-css-custom-properties/_config.js create mode 100644 test/runtime-puppeteer/samples/component-css-custom-properties/main.svelte diff --git a/test/helpers.ts b/test/helpers.ts index 78f000c274..4de53e6cf9 100644 --- a/test/helpers.ts +++ b/test/helpers.ts @@ -260,3 +260,11 @@ export function mkdirp(dir) { // do nothing } } + +export function prettyPrintPuppeteerAssertionError(message) { + const match = /Error: Expected "(.+)" to equal "(.+)"/.exec(message); + + if (match) { + assert.equal(match[1], match[2]); + } +} diff --git a/test/runtime-puppeteer/assert.js b/test/runtime-puppeteer/assert.js new file mode 100644 index 0000000000..4ee8d9dda0 --- /dev/null +++ b/test/runtime-puppeteer/assert.js @@ -0,0 +1,54 @@ +export function deepEqual(a, b, message) { + if (!is_equal(a, b)) { + throw new Error(message || `Expected ${JSON.stringify(a)} to equal ${JSON.stringify(b)}`); + } +} + +function is_equal(a, b) { + if (a && typeof a === 'object') { + const is_array = Array.isArray(a); + if (Array.isArray(b) !== is_array) return false; + + if (is_array) { + if (a.length !== b.length) return false; + return a.every((value, i) => is_equal(value, b[i])); + } + + const a_keys = Object.keys(a).sort(); + const b_keys = Object.keys(b).sort(); + if (a_keys.join(',') !== b_keys.join(',')) return false; + + return a_keys.every(key => is_equal(a[key], b[key])); + } + + return a === b; +} + +export function equal(a, b, message) { + if (a != b) throw new Error(message || `Expected ${a} to equal ${b}`); +} + +export function ok(condition, message) { + if (!condition) throw new Error(message || `Expected ${condition} to be truthy`); +} + +export function htmlEqual(actual, expected, message) { + return deepEqual( + normalizeHtml(window, actual), + normalizeHtml(window, expected), + message + ); +} + +function normalizeHtml(window, html) { + try { + const node = window.document.createElement('div'); + node.innerHTML = html + .replace(//g, '') + .replace(/>[\s\r\n]+<') + .trim(); + return node.innerHTML.replace(/<\/?noscript\/?>/g, ''); + } catch (err) { + throw new Error(`Failed to normalize HTML:\n${html}`); + } +} diff --git a/test/runtime-puppeteer/index.ts b/test/runtime-puppeteer/index.ts new file mode 100644 index 0000000000..5b330f2f51 --- /dev/null +++ b/test/runtime-puppeteer/index.ts @@ -0,0 +1,250 @@ +import * as path from 'path'; +import * as fs from 'fs'; +import * as http from 'http'; +import { rollup } from 'rollup'; +import virtual from '@rollup/plugin-virtual'; +import puppeteer from 'puppeteer'; + +import { + loadConfig, + loadSvelte, + mkdirp, + prettyPrintPuppeteerAssertionError +} from '../helpers'; +import { deepEqual } from 'assert'; + +const page = ` + +
+ + +`; + +let svelte; +let server; +let code; +let browser; + +const internal = path.resolve('internal/index.mjs'); +const index = path.resolve('index.mjs'); + +function create_server() { + return new Promise((fulfil, reject) => { + const server = http.createServer((req, res) => { + if (req.url === '/') { + res.end(page); + } + + if (req.url === '/bundle.js') { + res.end(code); + } + }); + + server.on('error', reject); + + server.listen('6789', () => { + fulfil(server); + }); + }); +} + +const assert = fs.readFileSync(`${__dirname}/assert.js`, 'utf-8'); + +describe('runtime (puppeteer)', () => { + before(async () => { + svelte = loadSvelte(false); + console.log('[runtime-puppeteer] Loaded Svelte'); + server = await create_server(); + console.log('[runtime-puppeteer] Started server'); + browser = await puppeteer.launch(); + console.log('[runtime-puppeteer] Launched puppeteer browser'); + }); + + after(async () => { + if (server) server.close(); + 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-puppeteer', + 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 '${__dirname}/samples/${dir}/main.svelte'; + import config from '${__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); + + if (config.html) { + assert.htmlEqual(target.innerHTML, config.html); + } + + if (config.test) { + await config.test({ + assert, + component, + target, + window, + }); + + 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 result = await bundle.generate({ format: 'iife', name: 'test' }); + code = result.output[0].code; + + const page = await browser.newPage(); + + page.on('console', (type) => { + console[type._type](type._text); + }); + + page.on('error', error => { + console.log('>>> an error happened'); + console.error(error); + }); + + try { + await page.goto('http://localhost:6789'); + + const result = await page.evaluate(() => test(document.querySelector('main'))); + if (result) console.log(result); + } catch (err) { + failed.add(dir); + prettyPrintPuppeteerAssertionError(err.message); + throw err; + } finally { + if (config.warnings) { + deepEqual(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'); + } + } + }); + } + + fs.readdirSync(`${__dirname}/samples`).forEach(dir => { + runTest(dir, false); + runTest(dir, true); + }); +}); diff --git a/test/runtime-puppeteer/samples/component-css-custom-properties-dynamic/Slider.svelte b/test/runtime-puppeteer/samples/component-css-custom-properties-dynamic/Slider.svelte new file mode 100644 index 0000000000..4863966f85 --- /dev/null +++ b/test/runtime-puppeteer/samples/component-css-custom-properties-dynamic/Slider.svelte @@ -0,0 +1,17 @@ + + +
+

Slider

+ Track +
+ + \ No newline at end of file diff --git a/test/runtime-puppeteer/samples/component-css-custom-properties-dynamic/_config.js b/test/runtime-puppeteer/samples/component-css-custom-properties-dynamic/_config.js new file mode 100644 index 0000000000..96965c223b --- /dev/null +++ b/test/runtime-puppeteer/samples/component-css-custom-properties-dynamic/_config.js @@ -0,0 +1,41 @@ +export default { + props: { + railColor1: 'black', + trackColor1: 'red', + railColor2: 'green', + trackColor2: 'blue' + }, + html: ` +
+
+

Slider

+ Track +
+
+
+
+

Slider

+ Track +
+
+ `, + test({ component, assert, target }) { + component.railColor1 = 'yellow'; + component.trackColor2 = 'orange'; + + assert.htmlEqual(target.innerHTML, ` +
+
+

Slider

+ Track +
+
+
+
+

Slider

+ Track +
+
+ `); + } +}; diff --git a/test/runtime-puppeteer/samples/component-css-custom-properties-dynamic/main.svelte b/test/runtime-puppeteer/samples/component-css-custom-properties-dynamic/main.svelte new file mode 100644 index 0000000000..94e68e38a2 --- /dev/null +++ b/test/runtime-puppeteer/samples/component-css-custom-properties-dynamic/main.svelte @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/test/runtime-puppeteer/samples/component-css-custom-properties/Slider.svelte b/test/runtime-puppeteer/samples/component-css-custom-properties/Slider.svelte new file mode 100644 index 0000000000..4863966f85 --- /dev/null +++ b/test/runtime-puppeteer/samples/component-css-custom-properties/Slider.svelte @@ -0,0 +1,17 @@ + + +
+

Slider

+ Track +
+ + \ No newline at end of file diff --git a/test/runtime-puppeteer/samples/component-css-custom-properties/_config.js b/test/runtime-puppeteer/samples/component-css-custom-properties/_config.js new file mode 100644 index 0000000000..9c3b7cbdea --- /dev/null +++ b/test/runtime-puppeteer/samples/component-css-custom-properties/_config.js @@ -0,0 +1,27 @@ +export default { + html: ` +
+
+

Slider

+ Track +
+
+
+
+

Slider

+ Track +
+
+ `, + test({ target, window, assert }) { + const railColor1 = target.querySelector('#slider-1 p'); + const trackColor1 = target.querySelector('#slider-1 span'); + const railColor2 = target.querySelector('#slider-2 p'); + const trackColor2 = target.querySelector('#slider-2 span'); + + assert.htmlEqual(window.getComputedStyle(railColor1).color, 'rgb(0, 0, 0)'); + assert.htmlEqual(window.getComputedStyle(trackColor1).color, 'rgb(255, 0, 0)'); + assert.htmlEqual(window.getComputedStyle(railColor2).color, 'rgb(0, 255, 0)'); + assert.htmlEqual(window.getComputedStyle(trackColor2).color, 'rgb(0, 0, 255)'); + } +}; diff --git a/test/runtime-puppeteer/samples/component-css-custom-properties/main.svelte b/test/runtime-puppeteer/samples/component-css-custom-properties/main.svelte new file mode 100644 index 0000000000..0d92b7ae87 --- /dev/null +++ b/test/runtime-puppeteer/samples/component-css-custom-properties/main.svelte @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/test/server-side-rendering/index.ts b/test/server-side-rendering/index.ts index e81058181d..f455cecc72 100644 --- a/test/server-side-rendering/index.ts +++ b/test/server-side-rendering/index.ts @@ -135,100 +135,105 @@ describe('ssr', () => { }); // duplicate client-side tests, as far as possible - fs.readdirSync('test/runtime/samples').forEach(dir => { - if (dir[0] === '.') return; + runRuntimeSamples('runtime'); + runRuntimeSamples('runtime-puppeteer'); - const config = loadConfig(`./runtime/samples/${dir}/_config.js`); - const solo = config.solo || /\.solo/.test(dir); + function runRuntimeSamples(suite) { + fs.readdirSync(`test/${suite}/samples`).forEach(dir => { + if (dir[0] === '.') return; - if (solo && process.env.CI) { - throw new Error('Forgot to remove `solo: true` from test'); - } + const config = loadConfig(`./${suite}/samples/${dir}/_config.js`); + const solo = config.solo || /\.solo/.test(dir); - if (config.skip_if_ssr) return; + 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('test/runtime/samples', dir); + if (config.skip_if_ssr) return; - cleanRequireCache(); + (config.skip ? it.skip : solo ? it.only : it)(dir, () => { + const cwd = path.resolve(`test/${suite}/samples`, dir); - delete global.window; + cleanRequireCache(); - const compileOptions = { - sveltePath, - ...config.compileOptions, - generate: 'ssr', - format: 'cjs' - }; - - require('../../register')(compileOptions); + delete global.window; - glob('**/*.svelte', { cwd }).forEach(file => { - if (file[0] === '_') return; + const compileOptions = { + sveltePath, + ...config.compileOptions, + generate: 'ssr', + format: 'cjs' + }; - const dir = `${cwd}/_output/ssr`; - const out = `${dir}/${file.replace(/\.svelte$/, '.js')}`; + require('../../register')(compileOptions); - if (fs.existsSync(out)) { - fs.unlinkSync(out); - } + glob('**/*.svelte', { cwd }).forEach(file => { + if (file[0] === '_') return; - mkdirp(dir); + const dir = `${cwd}/_output/ssr`; + const out = `${dir}/${file.replace(/\.svelte$/, '.js')}`; - try { - const { js } = compile( - fs.readFileSync(`${cwd}/${file}`, 'utf-8'), - { - ...compileOptions, - filename: file - } - ); + if (fs.existsSync(out)) { + fs.unlinkSync(out); + } - fs.writeFileSync(out, js.code); - } catch (err) { - // do nothing - } - }); + mkdirp(dir); - try { - if (config.before_test) config.before_test(); + try { + const { js } = compile( + fs.readFileSync(`${cwd}/${file}`, 'utf-8'), + { + ...compileOptions, + filename: file + } + ); - const Component = require(`../runtime/samples/${dir}/main.svelte`).default; - const { html } = Component.render(config.props, { - store: (config.store !== true) && config.store + fs.writeFileSync(out, js.code); + } catch (err) { + // do nothing + } }); - if (config.ssrHtml) { - assert.htmlEqual(html, config.ssrHtml); - } else if (config.html) { - assert.htmlEqual(html, config.html); - } + try { + if (config.before_test) config.before_test(); - if (config.test_ssr) { - config.test_ssr({ assert }); - } + const Component = require(`../${suite}/samples/${dir}/main.svelte`).default; + const { html } = Component.render(config.props, { + store: (config.store !== true) && config.store + }); - if (config.after_test) config.after_test(); + if (config.ssrHtml) { + assert.htmlEqual(html, config.ssrHtml); + } else if (config.html) { + assert.htmlEqual(html, config.html); + } - if (config.show) { - showOutput(cwd, compileOptions); - } - } catch (err) { - err.stack += `\n\ncmd-click: ${path.relative(process.cwd(), cwd)}/main.svelte`; + if (config.test_ssr) { + config.test_ssr({ assert }); + } + + if (config.after_test) config.after_test(); - if (config.error) { - if (typeof config.error === 'function') { - config.error(assert, err); + if (config.show) { + showOutput(cwd, compileOptions); + } + } catch (err) { + err.stack += `\n\ncmd-click: ${path.relative(process.cwd(), cwd)}/main.svelte`; + + if (config.error) { + if (typeof config.error === 'function') { + config.error(assert, err); + } else { + assert.equal(err.message, config.error); + } } else { - assert.equal(err.message, config.error); + showOutput(cwd, compileOptions); + throw err; } - } else { - showOutput(cwd, compileOptions); - throw err; + } finally { + set_current_component(null); } - } finally { - set_current_component(null); - } + }); }); - }); + } });