pull/4886/head
pushkine 5 years ago
parent 25488772e2
commit a4f7624b9a

@ -1 +0,0 @@
test/test.js

@ -13,19 +13,18 @@
"store", "store",
"animate", "animate",
"transition", "transition",
"interpolate",
"easing", "easing",
"motion", "motion",
"svelte", "svelte",
"README.md" "README.md"
], ],
"engines": {
"node": ">= 8"
},
"types": "types/runtime/index.d.ts", "types": "types/runtime/index.d.ts",
"scripts": { "scripts": {
"test": "mocha --opts mocha.opts", "test": "mocha",
"test:unit": "mocha --require sucrase/register --recursive src/**/__test__.ts", "test:unit": "mocha -r sucrase/register --recursive src/**/__test__.ts",
"quicktest": "mocha --opts mocha.opts", "quicktest": "mocha",
"update-expected": "node -r sucrase/register ./test/js/update.ts && node -r sucrase/register ./test/parser/update.ts",
"precoverage": "c8 mocha --opts mocha.coverage.opts", "precoverage": "c8 mocha --opts mocha.coverage.opts",
"coverage": "c8 report --reporter=text-lcov > coverage.lcov && c8 report --reporter=html", "coverage": "c8 report --reporter=text-lcov > coverage.lcov && c8 report --reporter=html",
"codecov": "codecov", "codecov": "codecov",
@ -39,6 +38,12 @@
"tsd": "tsc -p src/compiler --emitDeclarationOnly && tsc -p src/runtime --emitDeclarationOnly", "tsd": "tsc -p src/compiler --emitDeclarationOnly && tsc -p src/runtime --emitDeclarationOnly",
"lint": "eslint \"{src,test}/**/*.{ts,js}\"" "lint": "eslint \"{src,test}/**/*.{ts,js}\""
}, },
"mocha": {
"file": "./test/test.ts",
"require": "sucrase/register",
"bail": true,
"timeout": "10000"
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/sveltejs/svelte.git" "url": "https://github.com/sveltejs/svelte.git"
@ -56,41 +61,44 @@
}, },
"homepage": "https://github.com/sveltejs/svelte#README", "homepage": "https://github.com/sveltejs/svelte#README",
"devDependencies": { "devDependencies": {
"@rollup/plugin-commonjs": "^11.0.0", "@rollup/plugin-commonjs": "^11.1.0",
"@rollup/plugin-json": "^4.0.1", "@rollup/plugin-json": "^4.0.3",
"@rollup/plugin-node-resolve": "^6.0.0", "@rollup/plugin-node-resolve": "^7.1.3",
"@rollup/plugin-replace": "^2.3.0", "@rollup/plugin-replace": "^2.3.2",
"@rollup/plugin-sucrase": "^3.0.0", "@rollup/plugin-sucrase": "^3.0.1",
"@rollup/plugin-typescript": "^2.0.1", "@rollup/plugin-typescript": "^4.1.1",
"@rollup/plugin-virtual": "^2.0.0", "@rollup/plugin-virtual": "^2.0.2",
"@types/mocha": "^5.2.7", "@types/jsdom": "^16.2.2",
"@types/node": "^8.10.53", "@types/mocha": "^7.0.2",
"@typescript-eslint/eslint-plugin": "^1.13.0", "@types/node": "^14.0.1",
"@typescript-eslint/parser": "^2.1.0", "@types/puppeteer": "^2.1.0",
"acorn": "^7.1.0", "@typescript-eslint/eslint-plugin": "^2.33.0",
"agadoo": "^1.1.0", "@typescript-eslint/parser": "^2.33.0",
"c8": "^5.0.1", "acorn": "^7.2.0",
"agadoo": "^2.0.0",
"c8": "^7.1.2",
"code-red": "0.1.1", "code-red": "0.1.1",
"codecov": "^3.5.0", "codecov": "^3.6.5",
"css-tree": "1.0.0-alpha22", "css-tree": "1.0.0-alpha22",
"eslint": "^6.3.0", "eslint-plugin-import": "^2.20.2",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-svelte3": "^2.7.3", "eslint-plugin-svelte3": "^2.7.3",
"estree-walker": "^1.0.0", "eslint": "^7.0.0",
"esm": "^3.2.25",
"estree-walker": "^2.0.1",
"is-reference": "^1.1.4", "is-reference": "^1.1.4",
"jsdom": "^15.1.1", "jsdom": "^16.2.2",
"kleur": "^3.0.3", "kleur": "^3.0.3",
"locate-character": "^2.0.5", "locate-character": "^2.0.5",
"magic-string": "^0.25.3", "magic-string": "^0.25.3",
"mocha": "^6.2.0", "mocha": "^7.1.2",
"periscopic": "^2.0.1", "periscopic": "^2.0.1",
"puppeteer": "^1.19.0", "puppeteer": "^3.1.0",
"rollup": "^1.27.14", "rollup": "^2.10.5",
"source-map-support": "^0.5.19",
"source-map": "^0.7.3", "source-map": "^0.7.3",
"source-map-support": "^0.5.13", "ts-mocha": "^7.0.0",
"tiny-glob": "^0.2.6", "tslib": "^2.0.0",
"tslib": "^1.10.0", "typescript": "^3.9.2"
"typescript": "^3.5.3"
}, },
"nyc": { "nyc": {
"include": [ "include": [

@ -65,7 +65,7 @@ export default [
}), }),
ts_plugin, ts_plugin,
{ {
writeBundle(bundle) { writeBundle(_options, bundle) {
if (dir === 'internal') { if (dir === 'internal') {
const mod = bundle['index.mjs']; const mod = bundle['index.mjs'];
if (mod) { if (mod) {

@ -1,5 +1,7 @@
{ {
"rules": { "rules": {
"no-console": "off" "no-console": "off",
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/no-namespace": "off"
} }
} }

@ -0,0 +1,13 @@
import jsdom from 'jsdom';
export {};
declare global {
namespace NodeJS {
interface Global {
document: Document;
window: jsdom.DOMWindow;
navigator: Navigator;
getComputedStyle: jsdom.DOMWindow['getComputedStyle'];
requestAnimationFrame: any;
}
}
}

@ -1,6 +1,6 @@
import * as assert from 'assert'; import { assert } from '../test';
import * as fs from 'fs'; import { readFileSync, writeFileSync, readdirSync } from 'fs';
import { env, svelte, setupHtmlEqual, shouldUpdateExpected } from '../helpers.js'; import { env, svelte, setupHtmlEqual, shouldUpdateExpected } from '../helpers';
function try_require(file) { function try_require(file) {
try { try {
@ -13,12 +13,12 @@ function try_require(file) {
} }
function normalize_warning(warning) { function normalize_warning(warning) {
warning.frame = warning.frame warning.frame = warning.frame.replace(/^\n/, '').replace(/^\t+/gm, '').replace(/\s+$/gm, '');
.replace(/^\n/, '')
.replace(/^\t+/gm, '')
.replace(/\s+$/gm, '');
delete warning.filename; delete warning.filename;
delete warning.toString; delete warning.toString;
delete warning.start;
delete warning.end;
delete warning.pos;
return warning; return warning;
} }
@ -26,7 +26,7 @@ function create(code) {
const fn = new Function('module', 'exports', 'require', code); const fn = new Function('module', 'exports', 'require', code);
const module = { exports: {} }; const module = { exports: {} };
fn(module, module.exports, id => { fn(module, module.exports, (id) => {
if (id === 'svelte') return require('../../index.js'); if (id === 'svelte') return require('../../index.js');
if (id.startsWith('svelte/')) return require(id.replace('svelte', '../../')); if (id.startsWith('svelte/')) return require(id.replace('svelte', '../../'));
@ -41,7 +41,7 @@ describe('css', () => {
setupHtmlEqual(); setupHtmlEqual();
}); });
fs.readdirSync(`${__dirname}/samples`).forEach(dir => { readdirSync(`${__dirname}/samples`).forEach((dir) => {
if (dir[0] === '.') return; if (dir[0] === '.') return;
// add .solo to a sample directory name to only run that test // add .solo to a sample directory name to only run that test
@ -54,21 +54,13 @@ describe('css', () => {
(solo ? it.only : skip ? it.skip : it)(dir, () => { (solo ? it.only : skip ? it.skip : it)(dir, () => {
const config = try_require(`./samples/${dir}/_config.js`) || {}; const config = try_require(`./samples/${dir}/_config.js`) || {};
const input = fs const input = readFileSync(`${__dirname}/samples/${dir}/input.svelte`, 'utf-8').replace(/\s+$/, '');
.readFileSync(`${__dirname}/samples/${dir}/input.svelte`, 'utf-8')
.replace(/\s+$/, '');
const expected_warnings = (config.warnings || []).map(normalize_warning); const expected_warnings = (config.warnings || []).map(normalize_warning);
const dom = svelte.compile( const dom = svelte.compile(input, Object.assign(config.compileOptions || {}, { format: 'cjs' }));
input,
Object.assign(config.compileOptions || {}, { format: 'cjs' })
);
const ssr = svelte.compile( const ssr = svelte.compile(input, Object.assign(config.compileOptions || {}, { format: 'cjs', generate: 'ssr' }));
input,
Object.assign(config.compileOptions || {}, { format: 'cjs', generate: 'ssr' })
);
assert.equal(dom.css.code, ssr.css.code); assert.equal(dom.css.code, ssr.css.code);
@ -78,18 +70,18 @@ describe('css', () => {
assert.deepEqual(dom_warnings, ssr_warnings); assert.deepEqual(dom_warnings, ssr_warnings);
assert.deepEqual(dom_warnings.map(normalize_warning), expected_warnings); assert.deepEqual(dom_warnings.map(normalize_warning), expected_warnings);
fs.writeFileSync(`${__dirname}/samples/${dir}/_actual.css`, dom.css.code); writeFileSync(`${__dirname}/samples/${dir}/_actual.css`, dom.css.code);
const expected = { const expected = {
html: read(`${__dirname}/samples/${dir}/expected.html`), html: read(`${__dirname}/samples/${dir}/expected.html`),
css: read(`${__dirname}/samples/${dir}/expected.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 = dom.css.code.replace(/svelte(-ref)?-[a-z0-9]+/g, (m, $1) => ($1 ? m : 'svelte-xyz'));
try { try {
assert.equal(actual_css, expected.css); assert.equal(actual_css, expected.css);
} catch (error) { } catch (error) {
if (shouldUpdateExpected()) { if (shouldUpdateExpected()) {
fs.writeFileSync(`${__dirname}/samples/${dir}/expected.css`, actual_css); writeFileSync(`${__dirname}/samples/${dir}/expected.css`, actual_css);
console.log(`Updated ${dir}/expected.css.`); console.log(`Updated ${dir}/expected.css.`);
} else { } else {
throw error; throw error;
@ -126,9 +118,9 @@ describe('css', () => {
new ClientComponent({ target, props: config.props }); new ClientComponent({ target, props: config.props });
const html = target.innerHTML; const html = target.innerHTML;
fs.writeFileSync(`${__dirname}/samples/${dir}/_actual.html`, html); writeFileSync(`${__dirname}/samples/${dir}/_actual.html`, html);
const actual_html = html.replace(/svelte(-ref)?-[a-z0-9]+/g, (m, $1) => $1 ? m : 'svelte-xyz'); const actual_html = html.replace(/svelte(-ref)?-[a-z0-9]+/g, (m, $1) => ($1 ? m : 'svelte-xyz'));
assert.htmlEqual(actual_html, expected.html); assert.htmlEqual(actual_html, expected.html);
window.document.head.innerHTML = ''; // remove added styles window.document.head.innerHTML = ''; // remove added styles
@ -139,7 +131,9 @@ describe('css', () => {
// ssr // ssr
try { try {
const actual_ssr = ServerComponent.render(config.props).html.replace(/svelte(-ref)?-[a-z0-9]+/g, (m, $1) => $1 ? m : 'svelte-xyz'); 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); assert.htmlEqual(actual_ssr, expected.html);
} catch (err) { } catch (err) {
console.log(ssr.js.code); console.log(ssr.js.code);
@ -152,7 +146,7 @@ describe('css', () => {
function read(file) { function read(file) {
try { try {
return fs.readFileSync(file, 'utf-8'); return readFileSync(file, 'utf-8');
} catch (err) { } catch (err) {
return null; return null;
} }

@ -1,10 +1,10 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as http from 'http'; import * as http from 'http';
import { rollup } from 'rollup'; const { rollup } = require('rollup');
import * as virtual from '@rollup/plugin-virtual'; const virtual = require('@rollup/plugin-virtual');
import * as puppeteer from 'puppeteer'; const puppeteer = require('puppeteer');
import { addLineNumbers, loadConfig, loadSvelte } from "../helpers.js"; import { addLineNumbers, loadConfig, loadSvelte } from '../helpers';
import { deepEqual } from 'assert'; import { deepEqual } from 'assert';
const page = ` const page = `
@ -53,7 +53,7 @@ describe('custom-elements', function() {
await browser.close(); await browser.close();
}); });
fs.readdirSync(`${__dirname}/samples`).forEach(dir => { fs.readdirSync(`${__dirname}/samples`).forEach((dir) => {
if (dir[0] === '.') return; if (dir[0] === '.') return;
const solo = /\.solo$/.test(dir); const solo = /\.solo$/.test(dir);
@ -84,20 +84,19 @@ describe('custom-elements', function() {
if (id.endsWith('.svelte')) { if (id.endsWith('.svelte')) {
const compiled = svelte.compile(code, { const compiled = svelte.compile(code, {
customElement: true, customElement: true,
dev: config.dev dev: config.dev,
}); });
compiled.warnings.forEach(w => warnings.push(w)); compiled.warnings.forEach((w) => warnings.push(w));
return compiled.js; return compiled.js;
} }
}
}, },
},
virtual({ virtual({
assert assert,
}) }),
] ],
}); });
const result = await bundle.generate({ format: 'iife', name: 'test' }); const result = await bundle.generate({ format: 'iife', name: 'test' });
@ -109,7 +108,7 @@ describe('custom-elements', function() {
console[type](...args); console[type](...args);
}); });
page.on('error', error => { page.on('error', (error) => {
console.log('>>> an error happened'); console.log('>>> an error happened');
console.error(error); console.error(error);
}); });
@ -124,13 +123,16 @@ describe('custom-elements', function() {
throw err; throw err;
} finally { } finally {
if (expected_warnings) { if (expected_warnings) {
deepEqual(warnings.map(w => ({ deepEqual(
warnings.map((w) => ({
code: w.code, code: w.code,
message: w.message, message: w.message,
pos: w.pos, pos: w.pos,
start: w.start, start: w.start,
end: w.end end: w.end,
})), expected_warnings); })),
expected_warnings
);
} }
} }
}); });

@ -1,13 +1,13 @@
import * as jsdom from 'jsdom'; import * as jsdom from 'jsdom';
import * as assert from 'assert';
import * as glob from 'tiny-glob/sync.js';
import * as path from 'path'; import * as path from 'path';
import * as fs from 'fs'; import * as fs from 'fs';
import * as colors from 'kleur'; import * as colors from 'kleur';
import { glob } from './tiny-glob';
import { assert } from './test';
// for coverage purposes, we need to test source files, // for coverage purposes, we need to test source files,
// but for sanity purposes, we need to test dist files // but for sanity purposes, we need to test dist files
export function loadSvelte(test) { export function loadSvelte(test?) {
process.env.TEST = test ? 'true' : ''; process.env.TEST = test ? 'true' : '';
const resolved = require.resolve('../compiler.js'); const resolved = require.resolve('../compiler.js');
@ -16,7 +16,7 @@ export function loadSvelte(test) {
return require(resolved); return require(resolved);
} }
export const svelte = loadSvelte(); export const svelte = loadSvelte(false);
export function exists(path) { export function exists(path) {
try { try {
@ -47,14 +47,15 @@ export function tryToReadFile(file) {
export function cleanRequireCache() { export function cleanRequireCache() {
Object.keys(require.cache) Object.keys(require.cache)
.filter(x => x.endsWith('.svelte')) .filter((x) => x.endsWith('.svelte'))
.forEach(file => delete require.cache[file]); .forEach((file) => delete require.cache[file]);
} }
const virtualConsole = new jsdom.VirtualConsole(); const virtualConsole = new jsdom.VirtualConsole();
virtualConsole.sendTo(console); virtualConsole.sendTo(console);
const window = new jsdom.JSDOM('<main></main>', {virtualConsole}).window; const { window } = new jsdom.JSDOM('<main></main>', { virtualConsole });
global.document = window.document; global.document = window.document;
global.navigator = window.navigator; global.navigator = window.navigator;
global.getComputedStyle = window.getComputedStyle; global.getComputedStyle = window.getComputedStyle;
@ -63,7 +64,7 @@ global.window = window;
// add missing ecmascript globals to window // add missing ecmascript globals to window
for (const key of Object.getOwnPropertyNames(global)) { for (const key of Object.getOwnPropertyNames(global)) {
window[key] = window[key] || global[key]; if (!(key in window)) window[key] = global[key];
} }
// implement mock scroll // implement mock scroll
@ -79,8 +80,8 @@ export function env() {
return window; return window;
} }
const is_TextNode = (n: any): n is Text => n.nodeType === 3;
function cleanChildren(node) { function cleanChildren(node: Element) {
let previous = null; let previous = null;
// sort attributes // sort attributes
@ -88,23 +89,18 @@ function cleanChildren(node) {
return a.name < b.name ? -1 : 1; return a.name < b.name ? -1 : 1;
}); });
attributes.forEach(attr => { attributes.forEach((attr) => {
node.removeAttribute(attr.name); node.removeAttribute(attr.name);
}); });
attributes.forEach(attr => { attributes.forEach((attr) => {
node.setAttribute(attr.name, attr.value); node.setAttribute(attr.name, attr.value);
}); });
// recurse // recurse
[...node.childNodes].forEach(child => { Array.from(node.childNodes).forEach((child) => {
if (child.nodeType === 3) { if (is_TextNode(child)) {
// text if (node.namespaceURI === 'http://www.w3.org/2000/svg' && node.tagName !== 'text' && node.tagName !== 'tspan') {
if (
node.namespaceURI === 'http://www.w3.org/2000/svg' &&
node.tagName !== 'text' &&
node.tagName !== 'tspan'
) {
node.removeChild(child); node.removeChild(child);
} }
@ -118,19 +114,19 @@ function cleanChildren(node) {
child = previous; child = previous;
} }
} else { } else {
cleanChildren(child); cleanChildren(child as Element);
} }
previous = child; previous = child;
}); });
// collapse whitespace // collapse whitespace
if (node.firstChild && node.firstChild.nodeType === 3) { if (node.firstChild && is_TextNode(node.firstChild)) {
node.firstChild.data = node.firstChild.data.replace(/^\s+/, ''); node.firstChild.data = node.firstChild.data.replace(/^\s+/, '');
if (!node.firstChild.data) node.removeChild(node.firstChild); if (!node.firstChild.data) node.removeChild(node.firstChild);
} }
if (node.lastChild && node.lastChild.nodeType === 3) { if (node.lastChild && is_TextNode(node.lastChild)) {
node.lastChild.data = node.lastChild.data.replace(/\s+$/, ''); node.lastChild.data = node.lastChild.data.replace(/\s+$/, '');
if (!node.lastChild.data) node.removeChild(node.lastChild); if (!node.lastChild.data) node.removeChild(node.lastChild);
} }
@ -149,16 +145,11 @@ export function normalizeHtml(window, html) {
throw new Error(`Failed to normalize HTML:\n${html}`); throw new Error(`Failed to normalize HTML:\n${html}`);
} }
} }
export function setupHtmlEqual() { export function setupHtmlEqual() {
const window = env(); const window = env();
assert.htmlEqual = (actual, expected, message) => { assert.htmlEqual = function (actual, expected, message) {
assert.deepEqual( assert.deepEqual(normalizeHtml(window, actual), normalizeHtml(window, expected), message);
normalizeHtml(window, actual),
normalizeHtml(window, expected),
message
);
}; };
} }
@ -184,28 +175,25 @@ export function addLineNumbers(code) {
.map((line, i) => { .map((line, i) => {
i = String(i + 1); i = String(i + 1);
while (i.length < 3) i = ` ${i}`; while (i.length < 3) i = ` ${i}`;
return colors.gray(` ${i}: `) + line.replace(/^\t+/, (match) => match.split('\t').join(' '));
return (
colors.gray(` ${i}: `) +
line.replace(/^\t+/, match => match.split('\t').join(' '))
);
}) })
.join('\n'); .join('\n');
} }
export function showOutput(cwd, options = {}, compile = svelte.compile) { export function showOutput(cwd, options = {}, compile = svelte.compile) {
glob('**/*.svelte', { cwd }).forEach(file => { glob('**/*.svelte', { cwd }).forEach((file) => {
if (file[0] === '_') return; if (file[0] === '_') return;
try { try {
const { js } = compile( const { js } = compile(
fs.readFileSync(`${cwd}/${file}`, 'utf-8'), fs.readFileSync(`${cwd}/${file}`, 'utf-8'),
Object.assign(options, { Object.assign(options, {
filename: file filename: file,
}) })
); );
console.log( // eslint-disable-line no-console console.log(
// eslint-disable-line no-console
`\n>> ${colors.cyan().bold(file)}\n${addLineNumbers(js.code)}\n<< ${colors.cyan().bold(file)}` `\n>> ${colors.cyan().bold(file)}\n${addLineNumbers(js.code)}\n<< ${colors.cyan().bold(file)}`
); );
} catch (err) { } catch (err) {
@ -218,12 +206,6 @@ export function shouldUpdateExpected() {
return process.argv.includes('--update'); return process.argv.includes('--update');
} }
export function spaces(i) {
let result = '';
while (i--) result += ' ';
return result;
}
// fake timers // fake timers
const original_set_timeout = global.setTimeout; const original_set_timeout = global.setTimeout;
@ -232,17 +214,17 @@ export function useFakeTimers() {
global.setTimeout = function (fn) { global.setTimeout = function (fn) {
callbacks.push(fn); callbacks.push(fn);
}; } as (callback: (...args: any[]) => void, ms: number, ...args: any[]) => any;
return { return {
flush() { flush() {
callbacks.forEach(fn => fn()); callbacks.forEach((fn) => fn());
callbacks.splice(0, callbacks.length); callbacks.splice(0, callbacks.length);
}, },
removeFakeTimers() { removeFakeTimers() {
callbacks.splice(0, callbacks.length); callbacks.splice(0, callbacks.length);
global.setTimeout = original_set_timeout; global.setTimeout = original_set_timeout;
} },
}; };
} }

@ -1,15 +1,8 @@
import * as assert from 'assert';
import * as path from 'path'; import * as path from 'path';
import * as fs from 'fs'; import * as fs from 'fs';
import { import { showOutput, loadConfig, loadSvelte, env, setupHtmlEqual, shouldUpdateExpected } from '../helpers';
showOutput, import { assert } from '../test';
loadConfig,
loadSvelte,
env,
setupHtmlEqual,
shouldUpdateExpected
} from '../helpers.js';
let compileOptions = null; let compileOptions = null;
@ -25,7 +18,7 @@ describe('hydration', () => {
filename, filename,
hydratable: true, hydratable: true,
format: 'cjs', format: 'cjs',
sveltePath sveltePath,
}, },
compileOptions compileOptions
); );
@ -58,13 +51,11 @@ describe('hydration', () => {
try { try {
global.window = window; global.window = window;
let SvelteComponent; // try {
const SvelteComponent = require(`${cwd}/main.svelte`).default;
try { // } catch (err) {
SvelteComponent = require(`${cwd}/main.svelte`).default; // throw err;
} catch (err) { // }
throw err;
}
const target = window.document.body; const target = window.document.body;
const head = window.document.head; const head = window.document.head;
@ -75,14 +66,16 @@ describe('hydration', () => {
try { try {
before_head = fs.readFileSync(`${cwd}/_before_head.html`, 'utf-8'); before_head = fs.readFileSync(`${cwd}/_before_head.html`, 'utf-8');
head.innerHTML = before_head; head.innerHTML = before_head;
} catch (err) {} } catch (_err) {
//
}
const snapshot = config.snapshot ? config.snapshot(target) : {}; const snapshot = config.snapshot ? config.snapshot(target) : {};
const component = new SvelteComponent({ const component = new SvelteComponent({
target, target,
hydrate: true, hydrate: true,
props: config.props props: config.props,
}); });
try { try {
@ -117,18 +110,19 @@ describe('hydration', () => {
} }
} catch (err) { } catch (err) {
showOutput(cwd, { showOutput(cwd, {
hydratable: true hydratable: true,
}); });
throw err; throw err;
} }
if (config.show) showOutput(cwd, { if (config.show)
hydratable: true showOutput(cwd, {
hydratable: true,
}); });
}); });
} }
fs.readdirSync(`${__dirname}/samples`).forEach(dir => { fs.readdirSync(`${__dirname}/samples`).forEach((dir) => {
runTest(dir, null); runTest(dir, null);
}); });
}); });

@ -1,38 +1,46 @@
import * as assert from "assert"; import * as fs from 'fs';
import * as fs from "fs"; import * as path from 'path';
import * as path from "path"; import * as colors from 'kleur';
import * as colors from "kleur"; import { loadConfig, svelte, shouldUpdateExpected } from '../helpers';
import { loadConfig, svelte, shouldUpdateExpected } from "../helpers.js"; import { assert } from '../test';
describe("js", () => { describe('js', () => {
fs.readdirSync(`${__dirname}/samples`).forEach(dir => { fs.readdirSync(`${__dirname}/samples`).forEach((dir) => {
if (dir[0] === ".") return; if (dir[0] === '.') return;
// add .solo to a sample directory name to only run that test // add .solo to a sample directory name to only run that test
const solo = /\.solo/.test(dir); const solo = /\.solo/.test(dir);
if (solo && process.env.CI) { if (solo && process.env.CI) {
throw new Error("Forgot to remove `solo: true` from test"); throw new Error('Forgot to remove `solo: true` from test');
} }
const resolved = path.resolve(`${__dirname}/samples`, dir); const resolved = path.resolve(`${__dirname}/samples`, dir);
if (!fs.existsSync(`${resolved}/input.svelte`)) { 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`)); console.log(
colors
.red()
.bold(
`Missing file ${dir}/input.svelte. If you recently switched branches you may need to delete this directory`
)
);
return; return;
} }
(solo ? it.only : it)(dir, () => { (solo ? it.only : it)(dir, () => {
const config = loadConfig(`${resolved}/_config.js`); const config = loadConfig(`${resolved}/_config.js`);
const input = fs.readFileSync(`${resolved}/input.svelte`, "utf-8").replace(/\s+$/, ""); const input = fs.readFileSync(`${resolved}/input.svelte`, 'utf-8').replace(/\s+$/, '');
let actual; let actual;
try { try {
const options = Object.assign(config.options || {}); 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'); 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) { } catch (err) {
console.log(err.frame); console.log(err.frame);
throw err; throw err;
@ -45,7 +53,7 @@ describe("js", () => {
let expected = ''; let expected = '';
try { try {
expected = fs.readFileSync(expectedPath, "utf-8"); expected = fs.readFileSync(expectedPath, 'utf-8');
} catch (error) { } catch (error) {
console.log(error); console.log(error);
if (error.code === 'ENOENT') { if (error.code === 'ENOENT') {
@ -55,10 +63,7 @@ describe("js", () => {
} }
try { try {
assert.equal( assert.equal(actual.trim().replace(/^[ \t]+$/gm, ''), expected.trim().replace(/^[ \t]+$/gm, ''));
actual.trim().replace(/^[ \t]+$/gm, ""),
expected.trim().replace(/^[ \t]+$/gm, "")
);
} catch (error) { } catch (error) {
if (shouldUpdateExpected()) { if (shouldUpdateExpected()) {
fs.writeFileSync(expectedPath, actual); fs.writeFileSync(expectedPath, actual);

@ -1,13 +0,0 @@
// this file will replace all the expected.js files with their _actual
// equivalents. Only use it when you're sure that you haven't
// broken anything!
const fs = require("fs");
const glob = require("tiny-glob/sync.js");
glob("samples/*/_actual.js", { cwd: __dirname }).forEach(file => {
const actual = fs.readFileSync(`${__dirname}/${file}`, "utf-8");
fs.writeFileSync(
`${__dirname}/${file.replace("_actual.js", "expected.js")}`,
actual
);
});

@ -0,0 +1,39 @@
import { readFileSync, writeFileSync } from 'fs';
import { resolve } from 'path';
// this file will replace all the expected.js files with their _actual
// equivalents. Only use it when you're sure that you haven't
// broken anything!
const svelte = (function loadSvelte(test) {
process.env.TEST = test ? 'true' : '';
const resolved = require.resolve('../../compiler.js');
delete require.cache[resolved];
return require(resolved);
})(false);
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;
}
}
require(resolve(__dirname, '../tiny-glob.ts'))
.glob('samples/*/_actual.js', { cwd: __dirname })
.forEach((file) => {
writeFileSync(
`${__dirname}/${file.replace('_actual.js', 'expected.js')}`,
svelte
.compile(
readFileSync(`${__dirname}/${file.replace('_actual.js', 'input.svelte')}`, 'utf-8').replace(/\s+$/, ''),
loadConfig(`${__dirname}/${file.replace('_actual.js', '_config.js')}`).options
)
.js.code.replace(/generated by Svelte v\d+\.\d+\.\d+(-\w+\.\d+)?/, 'generated by Svelte vX.Y.Z')
);
});

@ -1,4 +1,4 @@
import * as assert from 'assert'; import { assert } from '../test';
import { get } from '../../store'; import { get } from '../../store';
import { spring, tweened } from '../../motion'; import { spring, tweened } from '../../motion';

@ -1,18 +1,16 @@
import * as assert from 'assert';
import * as fs from 'fs'; import * as fs from 'fs';
import { svelte, tryToLoadJson, shouldUpdateExpected } from '../helpers.js'; import { svelte, tryToLoadJson } from '../helpers';
import { assert } from '../test';
describe('parse', () => { describe('parser', () => {
fs.readdirSync(`${__dirname}/samples`).forEach(dir => { fs.readdirSync(`${__dirname}/samples`).forEach((dir) => {
if (dir[0] === '.') return; if (dir[0] === '.') return;
// add .solo to a sample directory name to only run that test // add .solo to a sample directory name to only run that test
const solo = /\.solo$/.test(dir); const solo = /\.solo$/.test(dir);
if (solo && process.env.CI) { if (solo && process.env.CI) {
throw new Error( throw new Error(`Forgot to remove '.solo' from test parser/samples/${dir}`);
`Forgot to remove '.solo' from test parser/samples/${dir}`
);
} }
const skip = !fs.existsSync(`${__dirname}/samples/${dir}/input.svelte`); const skip = !fs.existsSync(`${__dirname}/samples/${dir}/input.svelte`);
@ -25,9 +23,12 @@ describe('parse', () => {
const expectedError = tryToLoadJson(`${__dirname}/samples/${dir}/error.json`); const expectedError = tryToLoadJson(`${__dirname}/samples/${dir}/error.json`);
try { try {
const { ast } = svelte.compile(input, Object.assign(options, { const { ast } = svelte.compile(
generate: false input,
})); Object.assign(options, {
generate: false,
})
);
fs.writeFileSync(`${__dirname}/samples/${dir}/_actual.json`, JSON.stringify(ast, null, '\t')); fs.writeFileSync(`${__dirname}/samples/${dir}/_actual.json`, JSON.stringify(ast, null, '\t'));
@ -36,19 +37,8 @@ describe('parse', () => {
assert.deepEqual(ast.instance, expectedOutput.instance); assert.deepEqual(ast.instance, expectedOutput.instance);
assert.deepEqual(ast.module, expectedOutput.module); assert.deepEqual(ast.module, expectedOutput.module);
} catch (err) { } catch (err) {
if (err.name !== 'ParseError') throw err; if (err.name !== 'ParseError' || !expectedError) throw err;
if (!expectedError) throw err; assert.deepEqual(JSON.parse(JSON.stringify({ ...err, message: err.message })), expectedError);
try {
assert.equal(err.code, expectedError.code);
assert.equal(err.message, expectedError.message);
assert.deepEqual(err.start, expectedError.start);
assert.equal(err.pos, expectedError.pos);
assert.equal(err.toString().split('\n')[0], `${expectedError.message} (${expectedError.start.line}:${expectedError.start.column})`);
} catch (err2) {
const e = err2.code === 'MODULE_NOT_FOUND' ? err : err2;
throw e;
}
} }
}); });
}); });

@ -1,13 +0,0 @@
// this file will replace all the output.json files with their _actual.json
// equivalents. Only use it when you're sure that you haven't
// broken anything!
const fs = require("fs");
const glob = require("tiny-glob/sync.js");
glob("samples/*/_actual.json", { cwd: __dirname }).forEach(file => {
const actual = fs.readFileSync(`${__dirname}/${file}`, "utf-8");
fs.writeFileSync(
`${__dirname}/${file.replace("_actual.json", "output.json")}`,
actual
);
});

@ -0,0 +1,31 @@
import { readFileSync, writeFileSync } from 'fs';
import { resolve } from 'path';
// this file will replace all the expected.js files with their _actual
// equivalents. Only use it when you're sure that you haven't
// broken anything!
const svelte = (function loadSvelte(test) {
process.env.TEST = test ? 'true' : '';
const resolved = require.resolve('../../compiler.js');
delete require.cache[resolved];
return require(resolved);
})(false);
require(resolve(__dirname, '../tiny-glob.ts'))
.glob('samples/*/input.svelte', { cwd: __dirname })
.forEach((file) => {
try {
writeFileSync(
`${__dirname}/${file.replace('input.svelte', 'output.json')}`,
JSON.stringify(
svelte.compile(readFileSync(`${__dirname}/${file}`, 'utf-8').replace(/\s+$/, ''), { generate: false }).ast,
null,
'\t'
)
);
} catch (e) {
if (e.name !== 'ParseError') throw e;
writeFileSync(
`${__dirname}/${file.replace('input.svelte', 'error.json')}`,
JSON.stringify({ ...e, message: e.message }, null, '\t')
);
}
});

@ -1,9 +1,9 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as assert from 'assert'; import { loadConfig, svelte } from '../helpers';
import { loadConfig, svelte } from '../helpers.js'; import { assert } from '../test';
describe('preprocess', () => { describe('preprocess', () => {
fs.readdirSync(`${__dirname}/samples`).forEach(dir => { fs.readdirSync(`${__dirname}/samples`).forEach((dir) => {
if (dir[0] === '.') return; if (dir[0] === '.') return;
const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`); const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`);

@ -1,20 +1,11 @@
import * as assert from "assert"; import { relative, resolve } from 'path';
import * as path from "path"; import { readFileSync, existsSync, unlinkSync, writeFileSync, readdirSync } from 'fs';
import * as fs from "fs";
import { rollup } from 'rollup'; import { rollup } from 'rollup';
import * as virtual from '@rollup/plugin-virtual'; import virtual from '@rollup/plugin-virtual';
import * as glob from 'tiny-glob/sync.js'; import { clear_loops, flush, SvelteComponent,set_now, set_raf } from '../../internal';
import { clear_loops, flush, set_now, set_raf } from "../../internal"; import { showOutput, loadConfig, loadSvelte, cleanRequireCache, env, setupHtmlEqual, mkdirp } from '../helpers';
import { glob } from '../tiny-glob';
import { import { assert } from '../test';
showOutput,
loadConfig,
loadSvelte,
cleanRequireCache,
env,
setupHtmlEqual,
mkdirp
} from "../helpers.js";
let svelte$; let svelte$;
let svelte; let svelte;
@ -24,24 +15,21 @@ let compile = null;
const sveltePath = process.cwd().split('\\').join('/'); const sveltePath = process.cwd().split('\\').join('/');
let unhandled_rejection = false; let unhandled_rejection: Error | false = false;
process.on('unhandledRejection', err => { process.on('unhandledRejection', (err: Error) => {
unhandled_rejection = err; unhandled_rejection = err;
}); });
describe("runtime", () => { describe('runtime', () => {
before(() => { before(() => {
svelte = loadSvelte(false); svelte = loadSvelte(false);
svelte$ = loadSvelte(true); svelte$ = loadSvelte(true);
require.extensions[".svelte"] = function(module, filename) { require.extensions['.svelte'] = function (module, filename) {
const options = Object.assign({ return module._compile(
compile(readFileSync(filename, 'utf-8'), { filename, ...compileOptions }).js.code,
filename filename
}, compileOptions); );
const { js: { code } } = compile(fs.readFileSync(filename, "utf-8"), options);
return module._compile(code, filename);
}; };
return setupHtmlEqual(); return setupHtmlEqual();
@ -49,19 +37,20 @@ describe("runtime", () => {
const failed = new Set(); const failed = new Set();
function runTest(dir, hydrate) { function runTest(dir, hydratable) {
if (dir[0] === ".") return; if (dir[0] === '.') return;
const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`); const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`);
const solo = config.solo || /\.solo/.test(dir); const solo = config.solo || /\.solo/.test(dir);
const skip = config.skip || /\.skip/.test(dir);
if (hydrate && config.skip_if_hydrate) return; if (hydratable && config.skip_if_hydrate) return;
if (solo && process.env.CI) { if (solo && process.env.CI) {
throw new Error("Forgot to remove `solo: true` from test"); throw new Error('Forgot to remove `solo: true` from test');
} }
(config.skip ? it.skip : solo ? it.only : it)(`${dir} ${hydrate ? '(with hydration)' : ''}`, () => { (skip ? it.skip : solo ? it.only : it)(`${dir} ${hydratable ? '(with hydration)' : ''}`, () => {
if (failed.has(dir)) { if (failed.has(dir)) {
// this makes debugging easier, by only printing compiled output once // this makes debugging easier, by only printing compiled output once
throw new Error('skipping test, already failed'); throw new Error('skipping test, already failed');
@ -69,16 +58,18 @@ describe("runtime", () => {
unhandled_rejection = null; unhandled_rejection = null;
compile = (config.preserveIdentifiers ? svelte : svelte$).compile; ({ compile } = config.preserveIdentifiers ? svelte : svelte$);
const cwd = path.resolve(`${__dirname}/samples/${dir}`); const cwd = resolve(`${__dirname}/samples/${dir}`);
compileOptions = config.compileOptions || {}; compileOptions = {
compileOptions.format = 'cjs'; ...(config.compileOptions || {}),
compileOptions.sveltePath = sveltePath; format: 'cjs',
compileOptions.hydratable = hydrate; sveltePath,
compileOptions.immutable = config.immutable; hydratable,
compileOptions.accessors = 'accessors' in config ? config.accessors : true; immutable: config.immutable,
accessors: 'accessors' in config ? config.accessors : true,
};
cleanRequireCache(); cleanRequireCache();
@ -89,28 +80,23 @@ describe("runtime", () => {
const window = env(); const window = env();
glob('**/*.svelte', { cwd }).forEach(file => { glob('**/*.svelte', { cwd }).forEach((filename) => {
if (file[0] === '_') return; if (filename[0] === '_') return;
const dir = `${cwd}/_output/${hydrate ? 'hydratable' : 'normal'}`; const dir = `${cwd}/_output/${hydratable ? 'hydratable' : 'normal'}`;
const out = `${dir}/${file.replace(/\.svelte$/, '.js')}`; const out = `${dir}/${filename.replace(/\.svelte$/, '.js')}`;
if (fs.existsSync(out)) {
fs.unlinkSync(out);
}
if (existsSync(out)) unlinkSync(out);
mkdirp(dir); mkdirp(dir);
try { try {
const { js } = compile( writeFileSync(
fs.readFileSync(`${cwd}/${file}`, 'utf-8'), out,
{ compile(readFileSync(`${cwd}/${filename}`, 'utf-8'), {
...compileOptions, ...compileOptions,
filename: file filename,
} }).js.code
); );
fs.writeFileSync(out, js.code);
} catch (err) { } catch (err) {
// do nothing // do nothing
} }
@ -118,19 +104,17 @@ describe("runtime", () => {
return Promise.resolve() return Promise.resolve()
.then(() => { .then(() => {
// hack to support transition tests
clear_loops(); clear_loops();
const raf = { const raf = {
time: 0, time: 0,
callback: null, callback: null,
tick: now => { tick: (now) => {
raf.time = now; raf.time = now;
if (raf.callback) raf.callback(); if (raf.callback) raf.callback();
} },
}; };
set_now(() => raf.time); set_now(() => raf.time);
set_raf(cb => { set_raf((cb) => {
raf.callback = () => { raf.callback = () => {
raf.callback = null; raf.callback = null;
cb(raf.time); cb(raf.time);
@ -139,8 +123,7 @@ describe("runtime", () => {
}); });
try { try {
mod = require(`./samples/${dir}/main.svelte`); SvelteComponent = (mod = require(`./samples/${dir}/main.svelte`)).default;
SvelteComponent = mod.default;
} catch (err) { } catch (err) {
showOutput(cwd, compileOptions, compile); // eslint-disable-line no-console showOutput(cwd, compileOptions, compile); // eslint-disable-line no-console
throw err; throw err;
@ -151,35 +134,33 @@ describe("runtime", () => {
// Put things we need on window for testing // Put things we need on window for testing
window.SvelteComponent = SvelteComponent; window.SvelteComponent = SvelteComponent;
const target = window.document.querySelector("main"); const target = window.document.querySelector('main');
const warnings = []; const warnings = [];
const warn = console.warn; const warn = console.warn;
console.warn = warning => { console.warn = (warning) => {
warnings.push(warning); warnings.push(warning);
}; };
const options = Object.assign({}, { const options = {
target, ...{ target, hydrate: hydratable, props: config.props, intro: config.intro },
hydrate, ...(config.options || {}),
props: config.props, };
intro: config.intro
}, config.options || {});
const component = new SvelteComponent(options); const component: SvelteComponent = new SvelteComponent(options);
console.warn = warn; console.warn = warn;
if (config.error) { if (config.error) {
unintendedError = true; unintendedError = true;
throw new Error("Expected a runtime error"); throw new Error('Expected a runtime error');
} }
if (config.warnings) { if (config.warnings) {
assert.deepEqual(warnings, config.warnings); assert.deepEqual(warnings, config.warnings);
} else if (warnings.length) { } else if (warnings.length) {
unintendedError = true; unintendedError = true;
throw new Error("Received unexpected warnings"); throw new Error('Received unexpected warnings');
} }
if (config.html) { if (config.html) {
@ -187,15 +168,18 @@ describe("runtime", () => {
} }
if (config.test) { if (config.test) {
return Promise.resolve(config.test({ return Promise.resolve(
config.test({
assert, assert,
component, component,
mod, mod,
target, target,
window, window,
raf, raf,
compileOptions compileOptions,
})).then(() => { })
).then(() => {
raf.tick(Infinity);
component.$destroy(); component.$destroy();
if (unhandled_rejection) { if (unhandled_rejection) {
@ -204,14 +188,14 @@ describe("runtime", () => {
}); });
} else { } else {
component.$destroy(); component.$destroy();
assert.htmlEqual(target.innerHTML, ""); assert.htmlEqual(target.innerHTML, '');
if (unhandled_rejection) { if (unhandled_rejection) {
throw unhandled_rejection; throw unhandled_rejection;
} }
} }
}) })
.catch(err => { .catch((err) => {
if (config.error && !unintendedError) { if (config.error && !unintendedError) {
if (typeof config.error === 'function') { if (typeof config.error === 'function') {
config.error(assert, err); config.error(assert, err);
@ -221,14 +205,15 @@ describe("runtime", () => {
} else { } else {
throw err; throw err;
} }
}).catch(err => { })
.catch((err) => {
failed.add(dir); failed.add(dir);
showOutput(cwd, compileOptions, compile); // eslint-disable-line no-console showOutput(cwd, compileOptions, compile); // eslint-disable-line no-console
throw err; throw err;
}) })
.catch(err => { .catch((err) => {
// print a clickable link to open the directory // print a clickable link to open the directory
err.stack += `\n\ncmd-click: ${path.relative(process.cwd(), cwd)}/main.svelte`; err.stack += `\n\ncmd-click: ${relative(process.cwd(), cwd)}/main.svelte`;
throw err; throw err;
}) })
.then(() => { .then(() => {
@ -243,23 +228,23 @@ describe("runtime", () => {
}); });
} }
fs.readdirSync(`${__dirname}/samples`).forEach(dir => { readdirSync(`${__dirname}/samples`).forEach((dir) => {
runTest(dir, false); runTest(dir, false);
runTest(dir, true); runTest(dir, true);
}); });
async function create_component(src = '<div></div>') { async function create_component(src = '<div></div>') {
const { js } = svelte$.compile(src, { const { js } = svelte$.compile(src, {
format: "esm", format: 'esm',
name: "SvelteComponent", name: 'SvelteComponent',
dev: true dev: true,
}); });
const bundle = await rollup({ const bundle = await rollup({
input: 'main.js', input: 'main.js',
plugins: [ plugins: [
virtual({ virtual({
'main.js': js.code 'main.js': js.code,
}), }),
{ {
name: 'svelte-packages', name: 'svelte-packages',
@ -267,22 +252,20 @@ describe("runtime", () => {
if (importee.startsWith('svelte/')) { if (importee.startsWith('svelte/')) {
return importee.replace('svelte', process.cwd()) + '/index.mjs'; return importee.replace('svelte', process.cwd()) + '/index.mjs';
} }
} },
} },
] ],
}); });
const result = await bundle.generate({ const result = await bundle.generate({
format: 'iife', format: 'iife',
name: 'App' name: 'App',
}); });
return eval( return eval(`(function () { ${result.output[0].code}; return App; }())`);
`(function () { ${result.output[0].code}; return App; }())`
);
} }
it("fails if options.target is missing in dev mode", async () => { it('fails if options.target is missing in dev mode', async () => {
const App = await create_component(); const App = await create_component();
assert.throws(() => { assert.throws(() => {
@ -290,13 +273,13 @@ describe("runtime", () => {
}, /'target' is a required option/); }, /'target' is a required option/);
}); });
it("fails if options.hydrate is true but the component is non-hydratable", async () => { it('fails if options.hydrate is true but the component is non-hydratable', async () => {
const App = await create_component(); const App = await create_component();
assert.throws(() => { assert.throws(() => {
new App({ new App({
target: { childNodes: [] }, target: { childNodes: [] },
hydrate: true 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,7 +1,5 @@
import * as assert from "assert"; import * as fs from 'fs';
import * as fs from "fs"; import * as path from 'path';
import * as path from "path";
import * as glob from 'tiny-glob/sync.js';
import { import {
showOutput, showOutput,
@ -11,14 +9,16 @@ import {
tryToLoadJson, tryToLoadJson,
cleanRequireCache, cleanRequireCache,
shouldUpdateExpected, shouldUpdateExpected,
mkdirp mkdirp,
} from "../helpers.js"; } from '../helpers';
import { glob } from '../tiny-glob';
import { assert } from '../test';
function tryToReadFile(file) { function tryToReadFile(file) {
try { try {
return fs.readFileSync(file, "utf-8"); return fs.readFileSync(file, 'utf-8');
} catch (err) { } catch (err) {
if (err.code !== "ENOENT") throw err; if (err.code !== 'ENOENT') throw err;
return null; return null;
} }
} }
@ -26,28 +26,29 @@ function tryToReadFile(file) {
const sveltePath = process.cwd().split('\\').join('/'); const sveltePath = process.cwd().split('\\').join('/');
let compile = null; let compile = null;
describe("ssr", () => { describe('ssr', () => {
before(() => { before(() => {
compile = loadSvelte(true).compile; compile = loadSvelte(true).compile;
return setupHtmlEqual(); return setupHtmlEqual();
}); });
fs.readdirSync(`${__dirname}/samples`).forEach(dir => { fs.readdirSync(`${__dirname}/samples`).forEach((dir) => {
if (dir[0] === ".") return; if (dir[0] === '.') return;
const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`); const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`);
// add .solo to a sample directory name to only run that test, or // add .solo to a sample directory name to only run that test, or
// .show to always show the output. or both // .show to always show the output. or both
const solo = config.solo || /\.solo/.test(dir); const solo = config.solo || /\.solo/.test(dir);
const skip = config.skip || /\.skip/.test(dir);
const show = /\.show/.test(dir); const show = /\.show/.test(dir);
if (solo && process.env.CI) { if (solo && process.env.CI) {
throw new Error("Forgot to remove `solo: true` from test"); throw new Error('Forgot to remove `solo: true` from test');
} }
(solo ? it.only : it)(dir, () => { (skip ? it.skip : solo ? it.only : it)(dir, () => {
dir = path.resolve(`${__dirname}/samples`, dir); dir = path.resolve(`${__dirname}/samples`, dir);
cleanRequireCache(); cleanRequireCache();
@ -56,16 +57,16 @@ describe("ssr", () => {
sveltePath, sveltePath,
...config.compileOptions, ...config.compileOptions,
generate: 'ssr', generate: 'ssr',
format: 'cjs' format: 'cjs',
}; };
require("../../register")(compileOptions); require('../../register')(compileOptions);
try { try {
const Component = require(`${dir}/main.svelte`).default; const Component = require(`${dir}/main.svelte`).default;
const expectedHtml = tryToReadFile(`${dir}/_expected.html`); const expectedHtml = tryToReadFile(`${dir}/_expected.html`);
const expectedCss = tryToReadFile(`${dir}/_expected.css`) || ""; const expectedCss = tryToReadFile(`${dir}/_expected.css`) || '';
const props = tryToLoadJson(`${dir}/data.json`) || undefined; const props = tryToLoadJson(`${dir}/data.json`) || undefined;
@ -87,10 +88,7 @@ describe("ssr", () => {
} }
try { try {
assert.equal( assert.equal(css.code.replace(/^\s+/gm, ''), expectedCss.replace(/^\s+/gm, ''));
css.code.replace(/^\s+/gm, ""),
expectedCss.replace(/^\s+/gm, "")
);
} catch (error) { } catch (error) {
if (shouldUpdateExpected()) { if (shouldUpdateExpected()) {
fs.writeFileSync(`${dir}/_expected.css`, css.code); fs.writeFileSync(`${dir}/_expected.css`, css.code);
@ -104,10 +102,7 @@ describe("ssr", () => {
fs.writeFileSync(`${dir}/_actual-head.html`, head); fs.writeFileSync(`${dir}/_actual-head.html`, head);
try { try {
assert.htmlEqual( assert.htmlEqual(head, fs.readFileSync(`${dir}/_expected-head.html`, 'utf-8'));
head,
fs.readFileSync(`${dir}/_expected-head.html`, 'utf-8')
);
} catch (error) { } catch (error) {
if (shouldUpdateExpected()) { if (shouldUpdateExpected()) {
fs.writeFileSync(`${dir}/_expected-head.html`, head); fs.writeFileSync(`${dir}/_expected-head.html`, head);
@ -128,20 +123,21 @@ describe("ssr", () => {
}); });
// duplicate client-side tests, as far as possible // duplicate client-side tests, as far as possible
fs.readdirSync("test/runtime/samples").forEach(dir => { fs.readdirSync('test/runtime/samples').forEach((dir) => {
if (dir[0] === ".") return; if (dir[0] === '.') return;
const config = loadConfig(`./runtime/samples/${dir}/_config.js`); const config = loadConfig(`./runtime/samples/${dir}/_config.js`);
const solo = config.solo || /\.solo/.test(dir); const solo = config.solo || /\.solo/.test(dir);
const skip = config.skip || /\.skip/.test(dir);
if (solo && process.env.CI) { if (solo && process.env.CI) {
throw new Error("Forgot to remove `solo: true` from test"); throw new Error('Forgot to remove `solo: true` from test');
} }
if (config.skip_if_ssr) return; if (config.skip_if_ssr) return;
(config.skip ? it.skip : solo ? it.only : it)(dir, () => { (skip ? it.skip : solo ? it.only : it)(dir, () => {
const cwd = path.resolve("test/runtime/samples", dir); const cwd = path.resolve('test/runtime/samples', dir);
cleanRequireCache(); cleanRequireCache();
@ -151,12 +147,12 @@ describe("ssr", () => {
sveltePath, sveltePath,
...config.compileOptions, ...config.compileOptions,
generate: 'ssr', generate: 'ssr',
format: 'cjs' format: 'cjs',
}; };
require("../../register")(compileOptions); require('../../register')(compileOptions);
glob('**/*.svelte', { cwd }).forEach(file => { glob('**/*.svelte', { cwd }).forEach((file) => {
if (file[0] === '_') return; if (file[0] === '_') return;
const dir = `${cwd}/_output/ssr`; const dir = `${cwd}/_output/ssr`;
@ -169,13 +165,10 @@ describe("ssr", () => {
mkdirp(dir); mkdirp(dir);
try { try {
const { js } = compile( const { js } = compile(fs.readFileSync(`${cwd}/${file}`, 'utf-8'), {
fs.readFileSync(`${cwd}/${file}`, 'utf-8'),
{
...compileOptions, ...compileOptions,
filename: file filename: file,
} });
);
fs.writeFileSync(out, js.code); fs.writeFileSync(out, js.code);
} catch (err) { } catch (err) {
@ -188,7 +181,7 @@ describe("ssr", () => {
const Component = require(`../runtime/samples/${dir}/main.svelte`).default; const Component = require(`../runtime/samples/${dir}/main.svelte`).default;
const { html } = Component.render(config.props, { const { html } = Component.render(config.props, {
store: (config.store !== true) && config.store store: config.store !== true && config.store,
}); });
if (config.ssrHtml) { if (config.ssrHtml) {

@ -1,38 +0,0 @@
const fs = require('fs');
require('source-map-support').install();
process.env.TEST = true;
require.extensions['.js'] = function(module, filename) {
const exports = [];
let code = fs.readFileSync(filename, 'utf-8')
.replace(/^import \* as (\w+) from ['"]([^'"]+)['"];?/gm, 'var $1 = require("$2");')
.replace(/^import (\w+) from ['"]([^'"]+)['"];?/gm, 'var {default: $1} = require("$2");')
.replace(/^import {([^}]+)} from ['"](.+)['"];?/gm, 'var {$1} = require("$2");')
.replace(/^export default /gm, 'exports.default = ')
.replace(/^export (const|let|var|class|function) (\w+)/gm, (match, type, name) => {
exports.push(name);
return `${type} ${name}`;
})
.replace(/^export \{([^}]+)\}(?: from ['"]([^'"]+)['"];?)?/gm, (match, names, source) => {
names.split(',').filter(Boolean).forEach(name => {
exports.push(name);
});
return source ? `const { ${names} } = require("${source}");` : '';
})
.replace(/^export function (\w+)/gm, 'exports.$1 = function $1');
exports.forEach(name => {
code += `\nexports.${name} = ${name};`;
});
try {
return module._compile(code, filename);
} catch (err) {
console.log(code); // eslint-disable-line no-console
throw err;
}
};

@ -1,73 +0,0 @@
import * as fs from "fs";
import * as path from "path";
import * as assert from "assert";
import { svelte } from "../helpers.js";
import { SourceMapConsumer } from "source-map";
import { getLocator } from "locate-character";
describe("sourcemaps", () => {
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 skip = /\.skip/.test(dir);
if (solo && process.env.CI) {
throw new Error("Forgot to remove `solo: true` from test");
}
(solo ? it.only : skip ? it.skip : it)(dir, async () => {
const filename = path.resolve(
`${__dirname}/samples/${dir}/input.svelte`
);
const outputFilename = path.resolve(
`${__dirname}/samples/${dir}/output`
);
const input = fs.readFileSync(filename, "utf-8").replace(/\s+$/, "");
const { js, css } = svelte.compile(input, {
filename,
outputFilename: `${outputFilename}.js`,
cssOutputFilename: `${outputFilename}.css`
});
const _code = js.code.replace(/Svelte v\d+\.\d+\.\d+/, match => match.replace(/\d/g, 'x'));
fs.writeFileSync(
`${outputFilename}.js`,
`${_code}\n//# sourceMappingURL=output.js.map`
);
fs.writeFileSync(
`${outputFilename}.js.map`,
JSON.stringify(js.map, null, " ")
);
if (css.code) {
fs.writeFileSync(
`${outputFilename}.css`,
`${css.code}\n/*# sourceMappingURL=output.css.map */`
);
fs.writeFileSync(
`${outputFilename}.css.map`,
JSON.stringify(css.map, null, " ")
);
}
assert.deepEqual(js.map.sources, ["input.svelte"]);
if (css.map) assert.deepEqual(css.map.sources, ["input.svelte"]);
const { test } = require(`./samples/${dir}/test.js`);
const locateInSource = getLocator(input);
const smc = await new SourceMapConsumer(js.map);
const locateInGenerated = getLocator(_code);
const smcCss = css.map && await new SourceMapConsumer(css.map);
const locateInGeneratedCss = getLocator(css.code || '');
test({ assert, code: _code, map: js.map, smc, smcCss, locateInSource, locateInGenerated, locateInGeneratedCss });
});
});
});

@ -0,0 +1,57 @@
import * as fs from 'fs';
import * as path from 'path';
import * as assert from 'assert';
import { svelte } from '../helpers';
import { SourceMapConsumer } from 'source-map';
import { getLocator } from 'locate-character';
describe('sourcemaps', () => {
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 skip = /\.skip/.test(dir);
if (solo && process.env.CI) {
throw new Error('Forgot to remove `solo: true` from test');
}
(solo ? it.only : skip ? it.skip : it)(dir, async () => {
const filename = path.resolve(`${__dirname}/samples/${dir}/input.svelte`);
const outputFilename = path.resolve(`${__dirname}/samples/${dir}/output`);
const input = fs.readFileSync(filename, 'utf-8').replace(/\s+$/, '');
const { js, css } = svelte.compile(input, {
filename,
outputFilename: `${outputFilename}.js`,
cssOutputFilename: `${outputFilename}.css`,
});
const _code = js.code.replace(/Svelte v\d+\.\d+\.\d+/, (match) => match.replace(/\d/g, 'x'));
fs.writeFileSync(`${outputFilename}.js`, `${_code}\n//# sourceMappingURL=output.js.map`);
fs.writeFileSync(`${outputFilename}.js.map`, JSON.stringify(js.map, null, ' '));
if (css.code) {
fs.writeFileSync(`${outputFilename}.css`, `${css.code}\n/*# sourceMappingURL=output.css.map */`);
fs.writeFileSync(`${outputFilename}.css.map`, JSON.stringify(css.map, null, ' '));
}
assert.deepEqual(js.map.sources, ['input.svelte']);
if (css.map) assert.deepEqual(css.map.sources, ['input.svelte']);
const { test } = require(`./samples/${dir}/test.js`);
const locateInSource = getLocator(input);
const smc = await new SourceMapConsumer(js.map);
const locateInGenerated = getLocator(_code);
const smcCss = css.map && (await new SourceMapConsumer(css.map));
const locateInGeneratedCss = getLocator(css.code || '');
test({ assert, code: _code, map: js.map, smc, smcCss, locateInSource, locateInGenerated, locateInGeneratedCss });
});
});
});

@ -1,9 +1,9 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as assert from 'assert'; import { svelte, loadConfig, tryToLoadJson } from '../helpers';
import { svelte, loadConfig, tryToLoadJson } from '../helpers.js'; import { assert } from '../test';
describe('stats', () => { describe('stats', () => {
fs.readdirSync(`${__dirname}/samples`).forEach(dir => { fs.readdirSync(`${__dirname}/samples`).forEach((dir) => {
if (dir[0] === '.') return; if (dir[0] === '.') return;
// add .solo to a sample directory name to only run that test // add .solo to a sample directory name to only run that test
@ -19,9 +19,7 @@ describe('stats', () => {
const filename = `${__dirname}/samples/${dir}/input.svelte`; const filename = `${__dirname}/samples/${dir}/input.svelte`;
const input = fs.readFileSync(filename, 'utf-8').replace(/\s+$/, ''); const input = fs.readFileSync(filename, 'utf-8').replace(/\s+$/, '');
const expectedError = tryToLoadJson( const expectedError = tryToLoadJson(`${__dirname}/samples/${dir}/error.json`);
`${__dirname}/samples/${dir}/error.json`
);
let result; let result;
let error; let error;
@ -52,7 +50,7 @@ describe('stats', () => {
it('returns a stats object when options.generate is false', () => { it('returns a stats object when options.generate is false', () => {
const { stats } = svelte.compile('', { const { stats } = svelte.compile('', {
generate: false generate: false,
}); });
assert.equal(typeof stats.timings.total, 'number'); assert.equal(typeof stats.timings.total, 'number');

@ -1,5 +1,5 @@
import * as assert from 'assert';
import { readable, writable, derived, get } from '../../store'; import { readable, writable, derived, get } from '../../store';
import { assert } from '../test';
describe('store', () => { describe('store', () => {
describe('writable', () => { describe('writable', () => {
@ -7,17 +7,17 @@ describe('store', () => {
const count = writable(0); const count = writable(0);
const values = []; const values = [];
const unsubscribe = count.subscribe(value => { const unsubscribe = count.subscribe((value) => {
values.push(value); values.push(value);
}); });
count.set(1); count.set(1);
count.update(n => n + 1); count.update((n) => n + 1);
unsubscribe(); unsubscribe();
count.set(3); count.set(3);
count.update(n => n + 1); count.update((n) => n + 1);
assert.deepEqual(values, [0, 1, 2]); assert.deepEqual(values, [0, 1, 2]);
}); });
@ -27,7 +27,7 @@ describe('store', () => {
const store = writable(0, () => { const store = writable(0, () => {
called += 1; called += 1;
return () => called -= 1; return () => (called -= 1);
}); });
const unsubscribe1 = store.subscribe(() => {}); const unsubscribe1 = store.subscribe(() => {});
@ -56,21 +56,21 @@ describe('store', () => {
store.set(obj); store.set(obj);
assert.equal(called, 2); assert.equal(called, 2);
store.update(obj => obj); store.update((obj) => obj);
assert.equal(called, 3); assert.equal(called, 3);
}); });
it('only calls subscriber once initially, including on resubscriptions', () => { it('only calls subscriber once initially, including on resubscriptions', () => {
let num = 0; let num = 0;
const store = writable(num, set => set(num += 1)); const store = writable(num, (set) => set((num += 1)));
let count1 = 0; let count1 = 0;
let count2 = 0; let count2 = 0;
store.subscribe(() => count1 += 1)(); store.subscribe(() => (count1 += 1))();
assert.equal(count1, 1); assert.equal(count1, 1);
const unsubscribe = store.subscribe(() => count2 += 1); const unsubscribe = store.subscribe(() => (count2 += 1));
assert.equal(count2, 1); assert.equal(count2, 1);
unsubscribe(); unsubscribe();
@ -82,7 +82,7 @@ describe('store', () => {
let running; let running;
let tick; let tick;
const store = readable(undefined, set => { const store = readable(undefined, (set) => {
tick = set; tick = set;
running = true; running = true;
@ -98,7 +98,7 @@ describe('store', () => {
const values = []; const values = [];
const unsubscribe = store.subscribe(value => { const unsubscribe = store.subscribe((value) => {
values.push(value); values.push(value);
}); });
@ -120,19 +120,19 @@ describe('store', () => {
subscribe(fn) { subscribe(fn) {
fn(42); fn(42);
return { return {
unsubscribe: () => {} unsubscribe: () => {},
}; };
} },
}; };
describe('derived', () => { describe('derived', () => {
it('maps a single store', () => { it('maps a single store', () => {
const a = writable(1); const a = writable(1);
const b = derived(a, n => n * 2); const b = derived(a, (n) => n * 2);
const values = []; const values = [];
const unsubscribe = b.subscribe(value => { const unsubscribe = b.subscribe((value) => {
values.push(value); values.push(value);
}); });
@ -148,11 +148,11 @@ describe('store', () => {
it('maps multiple stores', () => { it('maps multiple stores', () => {
const a = writable(2); const a = writable(2);
const b = writable(3); const b = writable(3);
const c = derived(([a, b]), ([a, b]) => a * b); const c = derived([a, b], ([a, b]) => a * b);
const values = []; const values = [];
const unsubscribe = c.subscribe(value => { const unsubscribe = c.subscribe((value) => {
values.push(value); values.push(value);
}); });
@ -168,13 +168,17 @@ describe('store', () => {
it('passes optional set function', () => { it('passes optional set function', () => {
const number = writable(1); const number = writable(1);
const evens = derived(number, (n, set) => { const evens = derived(
number,
(n, set) => {
if (n % 2 === 0) set(n); if (n % 2 === 0) set(n);
}, 0); },
0
);
const values = []; const values = [];
const unsubscribe = evens.subscribe(value => { const unsubscribe = evens.subscribe((value) => {
values.push(value); values.push(value);
}); });
@ -194,22 +198,19 @@ describe('store', () => {
it('prevents glitches', () => { it('prevents glitches', () => {
const lastname = writable('Jekyll'); const lastname = writable('Jekyll');
const firstname = derived(lastname, n => n === 'Jekyll' ? 'Henry' : 'Edward'); const firstname = derived(lastname, (n) => (n === 'Jekyll' ? 'Henry' : 'Edward'));
const fullname = derived([firstname, lastname], names => names.join(' ')); const fullname = derived([firstname, lastname], (names) => names.join(' '));
const values = []; const values = [];
const unsubscribe = fullname.subscribe(value => { const unsubscribe = fullname.subscribe((value) => {
values.push(value); values.push(value);
}); });
lastname.set('Hyde'); lastname.set('Hyde');
assert.deepEqual(values, [ assert.deepEqual(values, ['Henry Jekyll', 'Edward Hyde']);
'Henry Jekyll',
'Edward Hyde'
]);
unsubscribe(); unsubscribe();
}); });
@ -218,11 +219,11 @@ describe('store', () => {
const count = writable(0); const count = writable(0);
const values = []; const values = [];
const a = derived(count, $count => { const a = derived(count, ($count) => {
return 'a' + $count; return 'a' + $count;
}); });
const b = derived(count, $count => { const b = derived(count, ($count) => {
return 'b' + $count; return 'b' + $count;
}); });
@ -230,7 +231,7 @@ describe('store', () => {
return a + b; return a + b;
}); });
const unsubscribe = combined.subscribe(v => { const unsubscribe = combined.subscribe((v) => {
values.push(v); values.push(v);
}); });
@ -246,7 +247,7 @@ describe('store', () => {
const root = writable({ a: 0, b: 0 }); const root = writable({ a: 0, b: 0 });
const values = []; const values = [];
const a = derived(root, $root => { const a = derived(root, ($root) => {
return 'a' + $root.a; return 'a' + $root.a;
}); });
@ -254,7 +255,7 @@ describe('store', () => {
return 'b' + $root.b + $a; return 'b' + $root.b + $a;
}); });
const unsubscribe = b.subscribe(v => { const unsubscribe = b.subscribe((v) => {
values.push(v); values.push(v);
}); });
@ -270,14 +271,14 @@ describe('store', () => {
const arr = [0]; const arr = [0];
const number = writable(1); const number = writable(1);
const numbers = derived(number, $number => { const numbers = derived(number, ($number) => {
arr[0] = $number; arr[0] = $number;
return arr; return arr;
}); });
const concatenated = []; const concatenated = [];
const unsubscribe = numbers.subscribe(value => { const unsubscribe = numbers.subscribe((value) => {
concatenated.push(...value); concatenated.push(...value);
}); });
@ -305,7 +306,7 @@ describe('store', () => {
num.set(2); num.set(2);
const unsubscribe = d.subscribe(value => { const unsubscribe = d.subscribe((value) => {
values.push(value); values.push(value);
}); });
@ -332,7 +333,7 @@ describe('store', () => {
num.set(2); num.set(2);
const unsubscribe = d.subscribe(value => { const unsubscribe = d.subscribe((value) => {
values.push(value); values.push(value);
}); });
@ -357,7 +358,7 @@ describe('store', () => {
}); });
it('works with RxJS-style observables', () => { it('works with RxJS-style observables', () => {
const d = derived(fake_observable, _ => _); const d = derived(fake_observable, (_) => _);
assert.equal(get(d), 42); assert.equal(get(d), 42);
}); });
}); });

@ -1,21 +0,0 @@
const glob = require('tiny-glob/sync.js');
require('./setup');
// bind internal to jsdom
require('./helpers');
require('../internal');
console.clear();
const test_folders = glob('*/index.js', { cwd: 'test' });
const solo_folders = test_folders.filter(folder => /\.solo/.test(folder));
if (solo_folders.length) {
if (process.env.CI) {
throw new Error('Forgot to remove `.solo` from test');
}
solo_folders.forEach(name => require('./' + name));
} else {
test_folders.forEach(name => require('./' + name));
}

@ -0,0 +1,59 @@
import './ambient';
import * as assert$1 from 'assert';
export const assert = (assert$1 as unknown) as typeof assert$1 & { htmlEqual: (actual, expected, message?) => void };
import { glob } from './tiny-glob';
require('source-map-support').install();
process.env.TEST = true;
// require.extensions['.js'] = function (module, filename) {
// const exports = [];
// let code = readFileSync(filename, 'utf-8')
// .replace(/^import \* as (\w+) from ['"]([^'"]+)['"];?/gm, 'var $1 = require("$2");')
// .replace(/^import (\w+) from ['"]([^'"]+)['"];?/gm, 'var {default: $1} = require("$2");')
// .replace(/^import {([^}]+)} from ['"](.+)['"];?/gm, 'var {$1} = require("$2");')
// .replace(/^export default /gm, 'exports.default = ')
// .replace(/^export (const|let|var|class|function) (\w+)/gm, (_match, type, name) => {
// exports.push(name);
// return `${type} ${name}`;
// })
// .replace(/^export \{([^}]+)\}(?: from ['"]([^'"]+)['"];?)?/gm, (_match, names, source) => {
// names
// .split(',')
// .filter(Boolean)
// .forEach((name) => {
// exports.push(name);
// });
// return source ? `const { ${names} } = require("${source}");` : '';
// })
// .replace(/^export function (\w+)/gm, 'exports.$1 = function $1');
// exports.forEach((name) => {
// code += `\nexports.${name} = ${name};`;
// });
// try {
// return module._compile(code, filename);
// } catch (err) {
// console.log(code);
// throw err;
// }
// };
import './helpers';
import '../internal';
console.clear();
const test_folders = glob('*/index.ts', { cwd: 'test' });
const solo_folders = test_folders.filter((folder) => /\.solo/.test(folder));
if (solo_folders.length) {
if (process.env.CI) {
throw new Error('Forgot to remove `.solo` from test');
}
solo_folders.forEach((name) => require('./' + name));
} else {
test_folders.forEach((name) => require('./' + name));
}

@ -0,0 +1,270 @@
import { readdirSync, lstatSync, statSync } from 'fs';
import { normalize, dirname, join, resolve, relative } from 'path';
// MIT
// tiny-glob, globrex and globalyzer by Terkel Gjervig
const CHARS = { '{': '}', '(': ')', '[': ']' };
const STRICT = /\\(.)|(^!|\*|[\].+)]\?|\[[^\\\]]+\]|\{[^\\}]+\}|\(\?[:!=][^\\)]+\)|\([^|]+\|[^\\)]+\)|(\\).|([@?!+*]\(.*\)))/;
const RELAXED = /\\(.)|(^!|[*?{}()[\]]|\(\?)/;
const isWin = process.platform === 'win32';
const SEP = isWin ? `\\\\+` : `\\/`;
const SEP_ESC = isWin ? `\\\\` : `/`;
const GLOBSTAR = `((?:[^/]*(?:/|$))*)`;
const WILDCARD = `([^/]*)`;
const GLOBSTAR_SEGMENT = `((?:[^${SEP_ESC}]*(?:${SEP_ESC}|$))*)`;
const WILDCARD_SEGMENT = `([^${SEP_ESC}]*)`;
const isHidden = /(^|[\\/])\.[^\\/.]/g;
let CACHE = {};
function isglob(str, { strict = true } = {}) {
if (str === '') return false;
let match;
const rgx = strict ? STRICT : RELAXED;
while ((match = rgx.exec(str))) {
if (match[2]) return true;
let idx = match.index + match[0].length;
const open = match[1];
const close = open ? CHARS[open] : null;
let n;
if (open && close) if ((n = str.indexOf(close, idx)) !== -1) idx = n + 1;
str = str.slice(idx);
}
return false;
}
function parent(str, { strict = false } = {}) {
str = normalize(str).replace(/\/|\\/, '/');
if (/[{[].*[/]*.*[}]]$/.test(str)) str += '/';
str += 'a';
do str = dirname(str);
while (isglob(str, { strict }) || /(^|[^\\])([{[]|\([^)]+$)/.test(str));
return str.replace(/\\([*?|[\](){}])/g, '$1');
}
function globalyzer(pattern, opts = {}) {
let base = parent(pattern, opts);
const isGlob = isglob(pattern, opts);
let glob;
if (base != '.') {
if ((glob = pattern.substr(base.length)).startsWith('/')) glob = glob.substr(1);
} else glob = pattern;
if (!isGlob) glob = (base = dirname(pattern)) !== '.' ? pattern.substr(base.length) : pattern;
if (glob.startsWith('./')) glob = glob.substr(2);
if (glob.startsWith('/')) glob = glob.substr(1);
return { base, glob, isGlob };
}
function globrex(glob, { extended = false, globstar = false, strict = false, filepath = false, flags = '' } = {}) {
let regex = '';
let segment = '';
const path = {
regex: '',
segments: [],
globstar: undefined,
};
let inGroup = false;
let inRange = false;
const ext = [];
function add(str, { split = false, last = false, only = '' } = {}) {
if (only !== 'path') regex += str;
if (filepath && only !== 'regex') {
path.regex += str === '\\/' ? SEP : str;
if (split) {
if (last) segment += str;
if (segment !== '') {
if (!flags.includes('g')) segment = `^${segment}$`; // change it 'includes'
path.segments.push(new RegExp(segment, flags));
}
segment = '';
} else {
segment += str;
}
}
}
const escaped = (condition, str = c) => add(condition ? str : `//${c}`);
let c, n;
for (let i = 0; i < glob.length; i++) {
c = glob[i];
n = glob[i + 1];
if (['\\', '$', '^', '.', '='].includes(c)) {
add(`\\${c}`);
continue;
}
switch (c) {
case '/': {
add(`\\${c}`, { split: true });
if (n === '/' && !strict) regex += '?';
break;
}
case '|':
case '(': {
escaped(ext.length);
break;
}
case ')': {
if (ext.length) {
add(c);
const type = ext.pop();
if (type === '@') {
add('{1}');
} else if (type === '!') {
add('([^/]*)');
} else {
add(type);
}
} else add(`\\${c}`);
break;
}
case '+': {
if (n === '(' && extended) {
ext.push(c);
} else add(`\\${c}`);
break;
}
case '!': {
if (extended) {
if (inRange) {
add('^');
break;
} else if (n === '(') {
ext.push(c);
i++;
}
}
escaped(extended && n === '(', '(?!');
break;
}
case '?': {
if (extended && n === '(') {
ext.push(c);
} else {
escaped(extended, '.');
}
break;
}
case '[': {
if (inRange && n === ':') {
i++; // skip [
let value = '';
while (glob[++i] !== ':') value += glob[i];
if (value === 'alnum') add('(\\w|\\d)');
else if (value === 'space') add('\\s');
else if (value === 'digit') add('\\d');
i++; // skip last ]
break;
} else if (extended) inRange = true;
escaped(extended);
break;
}
case ']': {
if (extended) inRange = false;
escaped(extended);
break;
}
case '{': {
if (extended) inGroup = true;
escaped(extended, '(');
break;
}
case '}': {
if (extended) inGroup = false;
escaped(extended, ')');
break;
}
case ',': {
escaped(inGroup, '|');
break;
}
case '*': {
if (n === '(' && extended) {
ext.push(c);
break;
}
const prevChar = glob[i - 1];
let starCount = 1;
while (glob[i + 1] === '*') {
starCount++;
i++;
}
const nextChar = glob[i + 1];
if (!globstar) add('.*');
else {
const isGlobstar =
starCount > 1 && (prevChar === '/' || prevChar === void 0) && (nextChar === '/' || nextChar === void 0);
if (isGlobstar) {
add(GLOBSTAR, { only: 'regex' });
add(GLOBSTAR_SEGMENT, { only: 'path', last: true, split: true });
i++;
} else {
add(WILDCARD, { only: 'regex' });
add(WILDCARD_SEGMENT, { only: 'path' });
}
}
break;
}
case '@': {
if (extended && n === '(') ext.push(c);
else add(c);
break;
}
default:
add(c);
}
}
const g = flags.includes('g');
return {
regex: new RegExp(g ? regex : `^${regex}$`, flags),
path: filepath
? {
segments: [...path.segments, new RegExp(g ? segment : `^${segment}$`, flags)],
regex: new RegExp(g ? path.regex : `^${path.regex}$`, flags),
globstar: new RegExp(!g ? `^${GLOBSTAR_SEGMENT}$` : GLOBSTAR_SEGMENT, flags),
}
: undefined,
};
}
function walk(output, prefix, lexer, filesOnly, dot, cwd, dirname = '', level = 0) {
const rgx = lexer.segments[level];
const dir = join(cwd, prefix, dirname);
const files = readdirSync(dir);
let i = 0,
file;
const len = files.length;
let fullpath, relpath, stats, isMatch;
for (; i < len; i++) {
fullpath = join(dir, (file = files[i]));
relpath = dirname ? join(dirname, file) : file;
if (!dot && isHidden.test(relpath)) continue;
isMatch = lexer.regex.test(relpath);
if ((stats = CACHE[relpath]) === void 0) CACHE[relpath] = stats = lstatSync(fullpath);
if (!stats.isDirectory()) {
isMatch && output.push(relative(cwd, fullpath));
continue;
}
if (rgx && !rgx.test(file)) continue;
if (!filesOnly && isMatch) output.push(join(prefix, relpath));
walk(output, prefix, lexer, filesOnly, dot, cwd, relpath, rgx && rgx.toString() !== lexer.globstar && ++level);
}
}
export function glob(str: string, { cwd = '.', absolute = false, filesOnly = false, dot = false, flush = false }) {
if (!str) return [];
const glob = globalyzer(str);
if (!glob.isGlob) {
try {
const resolved = resolve(cwd, str);
const dirent = statSync(resolved);
if (filesOnly && !dirent.isFile()) return [];
return absolute ? [resolved] : [str];
} catch (err) {
if (err.code != 'ENOENT') throw err;
return [];
}
}
if (flush) CACHE = {};
const matches = [];
const { path } = globrex(glob.glob, { filepath: true, globstar: true, extended: true });
//@ts-ignore
path.globstar = path.globstar.toString();
walk(matches, glob.base, path, filesOnly, dot, cwd, '.', 0);
return absolute ? matches.map((x) => resolve(cwd, x)) : matches;
}

@ -3,6 +3,7 @@
"include": ["."], "include": ["."],
"compilerOptions": { "compilerOptions": {
"lib": ["DOM", "es2020"],
"allowJs": true, "allowJs": true,
"checkJs": true, "checkJs": true,
"noEmit": true "noEmit": true

@ -1,104 +0,0 @@
import * as fs from "fs";
import * as assert from "assert";
import { svelte, loadConfig, tryToLoadJson } from "../helpers.js";
describe("validate", () => {
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 skip = /\.skip/.test(dir);
if (solo && process.env.CI) {
throw new Error("Forgot to remove `solo: true` from test");
}
(solo ? it.only : skip ? it.skip : it)(dir, () => {
const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`);
const input = fs.readFileSync(`${__dirname}/samples/${dir}/input.svelte`, "utf-8").replace(/\s+$/, "");
const expected_warnings = tryToLoadJson(`${__dirname}/samples/${dir}/warnings.json`) || [];
const expected_errors = tryToLoadJson(`${__dirname}/samples/${dir}/errors.json`);
const options = tryToLoadJson(`${__dirname}/samples/${dir}/options.json`);
let error;
try {
const { warnings } = svelte.compile(input, {
dev: config.dev,
legacy: config.legacy,
generate: false,
customElement: config.customElement,
...options,
});
assert.deepEqual(warnings.map(w => ({
code: w.code,
message: w.message,
pos: w.pos,
start: w.start,
end: w.end
})), expected_warnings);
} catch (e) {
error = e;
}
const expected = expected_errors && expected_errors[0];
if (error || expected) {
if (error && !expected) {
throw error;
}
if (expected && !error) {
throw new Error(`Expected an error: ${expected.message}`);
}
try {
assert.equal(error.code, expected.code);
assert.equal(error.message, expected.message);
assert.deepEqual(error.start, expected.start);
assert.deepEqual(error.end, expected.end);
assert.equal(error.pos, expected.pos);
} catch (e) {
console.error(error); // eslint-disable-line no-console
throw e;
}
}
});
});
it("errors if options.name is illegal", () => {
assert.throws(() => {
svelte.compile("<div></div>", {
name: "not.valid",
generate: false
});
}, /options\.name must be a valid identifier/);
});
it("warns if options.name is not capitalised", () => {
const { warnings } = svelte.compile("<div></div>", {
name: "lowercase",
generate: false
});
assert.deepEqual(warnings.map(w => ({
code: w.code,
message: w.message
})), [{
code: `options-lowercase-name`,
message: "options.name should be capitalised"
}]);
});
it("does not warn if options.name begins with non-alphabetic character", () => {
const { warnings } = svelte.compile("<div></div>", {
name: "_",
generate: false
});
assert.deepEqual(warnings, []);
});
});

@ -0,0 +1,118 @@
import * as fs from 'fs';
import { svelte, loadConfig, tryToLoadJson } from '../helpers';
import { assert } from '../test';
describe('validate', () => {
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 skip = /\.skip/.test(dir);
if (solo && process.env.CI) {
throw new Error('Forgot to remove `solo: true` from test');
}
(solo ? it.only : skip ? it.skip : it)(dir, () => {
const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`);
const input = fs.readFileSync(`${__dirname}/samples/${dir}/input.svelte`, 'utf-8').replace(/\s+$/, '');
const expected_warnings = tryToLoadJson(`${__dirname}/samples/${dir}/warnings.json`) || [];
const expected_errors = tryToLoadJson(`${__dirname}/samples/${dir}/errors.json`);
const options = tryToLoadJson(`${__dirname}/samples/${dir}/options.json`);
let error;
try {
const { warnings } = svelte.compile(input, {
dev: config.dev,
legacy: config.legacy,
generate: false,
customElement: config.customElement,
...options,
});
assert.deepEqual(
warnings.map((w) => ({
code: w.code,
message: w.message,
// pos: w.pos,
// start: w.start,
// end: w.end,
})),
expected_warnings.map((w) => ({
code: w.code,
message: w.message,
// pos: w.pos,
// start: w.start,
// end: w.end,
}))
);
} catch (e) {
error = e;
}
const expected = expected_errors && expected_errors[0];
if (error || expected) {
if (error && !expected) {
throw error;
}
if (expected && !error) {
throw new Error(`Expected an error: ${expected.message}`);
}
try {
assert.equal(error.code, expected.code);
assert.equal(error.message, expected.message);
// assert.deepEqual(error.start, expected.start);
// assert.deepEqual(error.end, expected.end);
// assert.equal(error.pos, expected.pos);
} catch (e) {
console.error(error); // eslint-disable-line no-console
throw e;
}
}
});
});
it('errors if options.name is illegal', () => {
assert.throws(() => {
svelte.compile('<div></div>', {
name: 'not.valid',
generate: false,
});
}, /options\.name must be a valid identifier/);
});
it('warns if options.name is not capitalised', () => {
const { warnings } = svelte.compile('<div></div>', {
name: 'lowercase',
generate: false,
});
assert.deepEqual(
warnings.map((w) => ({
code: w.code,
message: w.message,
})),
[
{
code: `options-lowercase-name`,
message: 'options.name should be capitalised',
},
]
);
});
it('does not warn if options.name begins with non-alphabetic character', () => {
const { warnings } = svelte.compile('<div></div>', {
name: '_',
generate: false,
});
assert.deepEqual(warnings, []);
});
});

@ -1,9 +1,9 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as assert from 'assert'; import { svelte, loadConfig, tryToLoadJson } from '../helpers';
import { svelte, loadConfig, tryToLoadJson } from '../helpers.js'; import { assert } from '../test';
describe('vars', () => { describe('vars', () => {
fs.readdirSync(`${__dirname}/samples`).forEach(dir => { fs.readdirSync(`${__dirname}/samples`).forEach((dir) => {
if (dir[0] === '.') return; if (dir[0] === '.') return;
// add .solo to a sample directory name to only run that test // add .solo to a sample directory name to only run that test
@ -20,9 +20,7 @@ describe('vars', () => {
const filename = `${__dirname}/samples/${dir}/input.svelte`; const filename = `${__dirname}/samples/${dir}/input.svelte`;
const input = fs.readFileSync(filename, 'utf-8').replace(/\s+$/, ''); const input = fs.readFileSync(filename, 'utf-8').replace(/\s+$/, '');
const expectedError = tryToLoadJson( const expectedError = tryToLoadJson(`${__dirname}/samples/${dir}/error.json`);
`${__dirname}/samples/${dir}/error.json`
);
let result; let result;
let error; let error;

@ -2,13 +2,14 @@
"include": [], "include": [],
"compilerOptions": { "compilerOptions": {
"rootDir": "src", "rootDirs": ["src", "test"],
// target node v8+ (https://node.green/) // target node v8+ (https://node.green/)
// the only missing feature is Array.prototype.values // the only missing feature is Array.prototype.values
"lib": ["es2017"], "lib": ["ES2017"],
"target": "es2017", "target": "ES2017",
"skipLibCheck": true,
"declaration": true, "declaration": true,
"declarationDir": "types", "declarationDir": "types",
@ -16,7 +17,7 @@
"noErrorTruncation": true, "noErrorTruncation": true,
// rollup takes care of these // rollup takes care of these
"module": "esnext", "module": "ESNext",
"moduleResolution": "node", "moduleResolution": "node",
"resolveJsonModule": true, "resolveJsonModule": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,

Loading…
Cancel
Save