mirror of https://github.com/sveltejs/svelte
implement passing CSS custom properties to components (#6237)
parent
ce55e10df5
commit
02b49a1bb4
@ -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]+</g, '><')
|
||||||
|
.trim();
|
||||||
|
return node.innerHTML.replace(/<\/?noscript\/?>/g, '');
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(`Failed to normalize HTML:\n${html}`);
|
||||||
|
}
|
||||||
|
}
|
@ -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 = `
|
||||||
|
<body>
|
||||||
|
<main></main>
|
||||||
|
<script src='/bundle.js'></script>
|
||||||
|
</body>
|
||||||
|
`;
|
||||||
|
|
||||||
|
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 ${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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,17 @@
|
|||||||
|
<script>
|
||||||
|
export let id;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div {id}>
|
||||||
|
<p>Slider</p>
|
||||||
|
<span>Track</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
p {
|
||||||
|
color: var(--rail-color);
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
color: var(--track-color);
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,41 @@
|
|||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
railColor1: 'black',
|
||||||
|
trackColor1: 'red',
|
||||||
|
railColor2: 'green',
|
||||||
|
trackColor2: 'blue'
|
||||||
|
},
|
||||||
|
html: `
|
||||||
|
<div style="display: contents; --rail-color:black; --track-color:red;">
|
||||||
|
<div id="slider-1">
|
||||||
|
<p class="svelte-17ay6rc">Slider</p>
|
||||||
|
<span class="svelte-17ay6rc">Track</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display: contents; --rail-color:green; --track-color:blue;">
|
||||||
|
<div id="slider-2">
|
||||||
|
<p class="svelte-17ay6rc">Slider</p>
|
||||||
|
<span class="svelte-17ay6rc">Track</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
test({ component, assert, target }) {
|
||||||
|
component.railColor1 = 'yellow';
|
||||||
|
component.trackColor2 = 'orange';
|
||||||
|
|
||||||
|
assert.htmlEqual(target.innerHTML, `
|
||||||
|
<div style="display: contents; --rail-color:yellow; --track-color:red;">
|
||||||
|
<div id="slider-1">
|
||||||
|
<p class="svelte-17ay6rc">Slider</p>
|
||||||
|
<span class="svelte-17ay6rc">Track</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display: contents; --rail-color:green; --track-color:orange;">
|
||||||
|
<div id="slider-2">
|
||||||
|
<p class="svelte-17ay6rc">Slider</p>
|
||||||
|
<span class="svelte-17ay6rc">Track</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,23 @@
|
|||||||
|
<script>
|
||||||
|
import Slider from './Slider.svelte';
|
||||||
|
export let railColor1;
|
||||||
|
export let railColor2;
|
||||||
|
export let trackColor1;
|
||||||
|
export let trackColor2;
|
||||||
|
|
||||||
|
function identity(color) {
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Slider
|
||||||
|
id="slider-1"
|
||||||
|
--rail-color={railColor1}
|
||||||
|
--track-color={trackColor1}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Slider
|
||||||
|
id="slider-2"
|
||||||
|
--rail-color={railColor2}
|
||||||
|
--track-color={identity(trackColor2)}
|
||||||
|
/>
|
@ -0,0 +1,17 @@
|
|||||||
|
<script>
|
||||||
|
export let id;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div {id}>
|
||||||
|
<p>Slider</p>
|
||||||
|
<span>Track</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
p {
|
||||||
|
color: var(--rail-color);
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
color: var(--track-color);
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,27 @@
|
|||||||
|
export default {
|
||||||
|
html: `
|
||||||
|
<div style="display: contents; --rail-color:rgb(0, 0, 0); --track-color:rgb(255, 0, 0);">
|
||||||
|
<div id="slider-1">
|
||||||
|
<p class="svelte-17ay6rc">Slider</p>
|
||||||
|
<span class="svelte-17ay6rc">Track</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display: contents; --rail-color:rgb(0, 255, 0); --track-color:rgb(0, 0, 255);">
|
||||||
|
<div id="slider-2">
|
||||||
|
<p class="svelte-17ay6rc">Slider</p>
|
||||||
|
<span class="svelte-17ay6rc">Track</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
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)');
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,15 @@
|
|||||||
|
<script>
|
||||||
|
import Slider from './Slider.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Slider
|
||||||
|
id="slider-1"
|
||||||
|
--rail-color="rgb(0, 0, 0)"
|
||||||
|
--track-color="rgb(255, 0, 0)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Slider
|
||||||
|
id="slider-2"
|
||||||
|
--rail-color="rgb(0, 255, 0)"
|
||||||
|
--track-color="rgb(0, 0, 255)"
|
||||||
|
/>
|
Loading…
Reference in new issue