fix bugs + convert tests to typescript

pull/4742/head
pushkine 5 years ago
parent ff6144a3a5
commit e73b258c70

@ -24,7 +24,6 @@ module.exports = {
'@typescript-eslint/indent': 'off',
'@typescript-eslint/camelcase': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/array-type': ['error', 'array-simple'],
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/explicit-member-accessibility': 'off',

2
.gitignore vendored

@ -9,7 +9,7 @@ node_modules
/animate
/easing
/environment/
/environment
/internal
/interpolate
/motion

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

3360
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -23,9 +23,10 @@
],
"types": "types/runtime/index.d.ts",
"scripts": {
"test": "mocha --opts mocha.opts",
"test:unit": "mocha --require sucrase/register --recursive src/**/__test__.ts",
"quicktest": "mocha --opts mocha.opts",
"test": "ts-mocha",
"test:unit": "ts-mocha --require sucrase/register --recursive src/**/__test__.ts",
"quicktest": "ts-mocha",
"update-expected": "node -r esm ./test/js/update.ts && node -r esm ./test/parser/update.ts",
"precoverage": "c8 mocha --opts mocha.coverage.opts",
"coverage": "c8 report --reporter=text-lcov > coverage.lcov && c8 report --reporter=html",
"codecov": "codecov",
@ -39,6 +40,12 @@
"tsd": "tsc -p src/compiler --emitDeclarationOnly && tsc -p src/runtime --emitDeclarationOnly",
"lint": "eslint \"{src,test}/**/*.{ts,js}\""
},
"mocha": {
"file": "./test/test.ts",
"require": "esm",
"bail": true,
"timeout": "4000"
},
"repository": {
"type": "git",
"url": "https://github.com/sveltejs/svelte.git"
@ -60,13 +67,15 @@
"@rollup/plugin-json": "^4.0.3",
"@rollup/plugin-node-resolve": "^7.1.3",
"@rollup/plugin-replace": "^2.3.2",
"@rollup/plugin-sucrase": "^3.0.0",
"@rollup/plugin-sucrase": "^3.0.1",
"@rollup/plugin-typescript": "^4.1.1",
"@rollup/plugin-virtual": "^2.0.1",
"@rollup/plugin-virtual": "^2.0.2",
"@types/jsdom": "^16.2.2",
"@types/mocha": "^7.0.2",
"@types/node": "^13.13.5",
"@typescript-eslint/eslint-plugin": "^2.31.0",
"@typescript-eslint/parser": "^2.31.0",
"@types/node": "^14.0.1",
"@types/puppeteer": "^2.1.0",
"@typescript-eslint/eslint-plugin": "^2.33.0",
"@typescript-eslint/parser": "^2.33.0",
"acorn": "^7.2.0",
"agadoo": "^2.0.0",
"c8": "^7.1.2",
@ -76,6 +85,7 @@
"eslint": "^7.0.0",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-svelte3": "^2.7.3",
"esm": "^3.2.25",
"estree-walker": "^2.0.1",
"is-reference": "^1.1.4",
"jsdom": "^16.2.2",
@ -85,12 +95,12 @@
"mocha": "^7.1.2",
"periscopic": "^2.0.1",
"puppeteer": "^3.0.4",
"rollup": "^2.8.2",
"rollup": "^2.10.2",
"source-map": "^0.7.3",
"source-map-support": "^0.5.19",
"tiny-glob": "^0.2.6",
"tslib": "^1.11.2",
"typescript": "^3.8.3"
"ts-mocha": "^7.0.0",
"tslib": "^2.0.0",
"typescript": "^3.9.2"
},
"nyc": {
"include": [

@ -181,11 +181,7 @@ export default class Component {
injected: true,
referenced: true,
});
} else {
if (!name) {
console.log(name);
}
if (name[0] === '$') {
} else if (name[0] === '$') {
this.add_var({
name,
injected: true,
@ -205,7 +201,6 @@ export default class Component {
this.used_names.add(name);
}
}
}
alias(name: string) {
if (!this.aliases.has(name)) {

@ -128,7 +128,6 @@ export default class Expression {
if (names) {
names.forEach((name) => {
if (!name) return;
if (template_scope.names.has(name)) {
template_scope.dependencies_for_name.get(name).forEach((name) => {
const variable = component.var_lookup.get(name);

@ -422,17 +422,17 @@ export default class Block {
}
has_content(): boolean {
return !!(
this.first ||
this.event_listeners.length ||
this.chunks.intro.length ||
this.chunks.outro.length ||
this.chunks.create.length ||
this.chunks.hydrate.length ||
this.chunks.claim.length ||
this.chunks.mount.length ||
this.chunks.update.length ||
this.chunks.destroy.length ||
return (
!!this.first ||
this.event_listeners.length > 0 ||
this.chunks.intro.length > 0 ||
this.chunks.outro.length > 0 ||
this.chunks.create.length > 0 ||
this.chunks.hydrate.length > 0 ||
this.chunks.claim.length > 0 ||
this.chunks.mount.length > 0 ||
this.chunks.update.length > 0 ||
this.chunks.destroy.length > 0 ||
this.has_animation
);
}
@ -458,7 +458,7 @@ export default class Block {
if (this.event_listeners.length > 0) {
const dispose: Identifier = {
type: 'Identifier',
name: `#dispose${chunk}`,
name: `dispose${chunk}`,
};
this.add_variable(dispose);
@ -474,13 +474,13 @@ export default class Block {
this.chunks.destroy.push(b`${dispose}();`);
} else {
this.chunks.mount.push(b`
if (#remount) for(let #i=0;#i<${dispose}.length;#i++){ ${dispose}[#i](); }
if (#remount) for(let i=0;i<${dispose}.length;i++){ ${dispose}[i](); }
${dispose} = [
${this.event_listeners}
];
`);
this.chunks.destroy.push(b`for(let #i=0;#i<${dispose}.length;#i++){ ${dispose}[#i](); }`);
this.chunks.destroy.push(b`for(let i=0;i<${dispose}.length;i++){ ${dispose}[i](); }`);
}
}
}

@ -73,13 +73,16 @@ export default function dom(component: Component, options: CompileOptions): { js
const props = component.vars.filter((variable) => !variable.module && variable.export_name);
const writable_props = props.filter((variable) => variable.writable);
const compute_rest = b`for (#k in $$props){ if (!#keys.has(#k) && $$props[#k][0] !== '$'){ $$restProps[#k] = $$props[#k];}}`;
const rest = uses_rest
? b`
let #k;
const #keys = new Set([${props.map((prop) => `"${prop.export_name}"`).join(',')}]);
let $$restProps = {};
${compute_rest};`
const $$restProps = {};
for (#k in $$props) {
if (!#keys.has(#k) && #k[0] !== '$') {
$$restProps[#k] = $$props[#k];
}
};`
: null;
const set =
@ -88,10 +91,23 @@ export default function dom(component: Component, options: CompileOptions): { js
${
uses_any &&
b`
${!uses_rest && `let #k`}
for (#k in $$new_props) if ($$new_props[#k][0] !== '$') $$props[k] = $$new_props[k];
${uses_rest && compute_rest}
${renderer.invalidate(uses_props ? '$$props' : '$$restProps')}
${!uses_rest && b`let #k;`}
for (#k in $$new_props) {
if (#k[0] !== '$') {
${
uses_rest
? b`
if (!#keys.has(#k)) {
$$props[#k] = $$restProps[#k] = $$new_props[#k];
} else {
$$props[#k] = $$new_props[#k];
}`
: b`$$props[#k] = $$new_props[#k];`
}
}
}
${uses_props && renderer.invalidate('$$props')}
${uses_rest && renderer.invalidate('$$restProps')}
`
}
${writable_props.map(
@ -264,7 +280,7 @@ export default function dom(component: Component, options: CompileOptions): { js
const insert =
reassigned || export_name
? b`${`$$subscribe_${name}`}();`
: b`$$self.$$.on_destroy.push(@subscribe(${name}, (#value) => $$invalidate(${i}, ${value} = #value)));`;
: b`$$self.$$.on_destroy.push(@subscribe(${name}, (value) => {$$invalidate(${i}, (${value} = value));}));`;
if (component.compile_options.dev) {
return b`@validate_store_dev(${name}, '${name}'); ${insert}`;
@ -343,9 +359,9 @@ export default function dom(component: Component, options: CompileOptions): { js
.map(
({ name }) => b`
${component.compile_options.dev && b`@validate_store_dev(${name.slice(1)}, '${name.slice(1)}');`}
$$self.$$.on_destroy.push(@subscribe(${name.slice(1)}, (#value) => {
$$invalidate(${renderer.context_lookup.get(name).index}, (${name} = #value));
});
$$self.$$.on_destroy.push(@subscribe(${name.slice(1)}, #value => {
$$invalidate(${renderer.context_lookup.get(name).index}, ${name} = #value);
}));
`
);
@ -397,7 +413,7 @@ export default function dom(component: Component, options: CompileOptions): { js
const subscribe = `$$subscribe_${name}`;
const i = renderer.context_lookup.get($name).index;
return b`let ${$name}, ${unsubscribe} = @noop, ${subscribe} = () => (${unsubscribe}(), ${unsubscribe} = @subscribe(${name}, $$value => $$invalidate(${i}, ${$name} = $$value)), ${name})`;
return b`let ${$name}, ${unsubscribe} = @noop, ${subscribe} = () => (${unsubscribe}(), ${unsubscribe} = @subscribe(${name}, (value) => {$$invalidate(${i}, (${$name} = value));}), ${name})`;
}
return b`let ${$name};`;
@ -448,7 +464,7 @@ export default function dom(component: Component, options: CompileOptions): { js
.join(',')}]);`
}
${renderer.binding_groups.length && b`const $$binding_groups = [${renderer.binding_groups.map((_) => x`[]`)}];`}
${renderer.binding_groups.length ? b`const $$binding_groups = [${renderer.binding_groups.map((_) => x`[]`)}];` : null}
${component.partly_hoisted}
@ -462,15 +478,15 @@ export default function dom(component: Component, options: CompileOptions): { js
${/* before reactive declarations */ props_inject}
${reactive_declarations.length && b`$$self.$$.update = () => {${reactive_declarations}};`}
${reactive_declarations.length ? b`$$self.$$.update = () => {${reactive_declarations}};` : null}
${fixed_reactive_declarations}
${
uses_props &&
b`
let #k;
for (#k in $$props) if ($$props[#k][0] === '$') delete $$props[#k];`
${!rest ? b`let #k;` : null}
for (#k in $$props) if (#k[0] === '$') delete $$props[#k];`
}
return ${return_value};
@ -517,12 +533,13 @@ export default function dom(component: Component, options: CompileOptions): { js
}
${
(props.length > 0 || uses_props || uses_rest) &&
b`
props.length > 0 || uses_props || uses_rest
? b`
if (options.props) {
this.$set(options.props);
@flush();
}`
: null
}
}
}

@ -241,8 +241,8 @@ export default class AwaitBlockWrapper extends Wrapper {
if (this.pending.block.has_outro_method) {
block.chunks.outro.push(b`
for (let #i = 0; #i < 3; #i += 1) {
@transition_out(${info}.blocks[#i]);
for (let i = 0; i < 3; i++) {
@transition_out(${info}.blocks[i]);
}
`);
}

@ -262,7 +262,7 @@ export default class EachBlockWrapper extends Wrapper {
$if({
if: each_block_else,
true: $if({
if: no_each_else,
if: this.else.is_dynamic && no_each_else,
true: update_else,
false: destroy_else,
}),
@ -315,6 +315,8 @@ export default class EachBlockWrapper extends Wrapper {
update_anchor_node: Identifier;
update_mount_node: Identifier;
}) {
this.block.maintain_context = true;
const __DEV__ = this.renderer.options.dev;
const {
create_each_block,
@ -431,20 +433,21 @@ export default class EachBlockWrapper extends Wrapper {
const has_transitions = !!(this.block.has_intro_method || this.block.has_outro_method);
const { has_update_method } = this.block;
const start = has_update_method ? 0 : `#old_length`;
const start = has_update_method ? 0 : x`old_length`;
// We declare `i` as block scoped here, as the `remove_old_blocks` code
// may rely on continuing where this iteration stopped.
this.updates.push(b`
${!has_update_method && b`const #old_length = ${each_block}.length;`}
${!has_update_method && b`const old_length = ${each_block}.length;`}
${each_block_value} = ${snippet};
${__DEV__ && b`@is_array_like_dev(${each_block_value});`}
let #i = ${start}, #block;
let i = ${start};
let #block;
${for_loop(
each_block_value,
(_, index) => b`
#block = ${each_block}[${index}]
const #child_ctx = ${each_context_getter}(#ctx, ${each_block_value}, ${index});
(_) => b`
#block = ${each_block}[i]
const #child_ctx = ${each_context_getter}(#ctx, ${each_block_value}, i);
${$if({
if: (has_update_method || has_transitions) && x`#block`,
true: b`
@ -452,7 +455,7 @@ export default class EachBlockWrapper extends Wrapper {
${has_transitions && b`@transition_in(#block, 1);`}
`,
false: b`
#block = ${each_block}[${index}] = ${create_each_block}(#child_ctx);
#block = ${each_block}[i] = ${create_each_block}(#child_ctx);
#block.c();
${has_transitions && b`@transition_in(#block, 1);`}
#block.m(${update_mount_node}, ${update_anchor_node});
@ -465,8 +468,8 @@ export default class EachBlockWrapper extends Wrapper {
(block) =>
transition_out ? b`${transition_out}(${block}, () => { ${block} = null; });` : b`${block}.d(1);`,
{
i: has_update_method && !transition_out ? null : x`#i = ${each_block_value}.length`,
length: has_update_method || transition_out ? x`${each_block}.length` : x`#old_length`,
i: has_update_method && !transition_out ? null : x`i = ${each_block_value}.length`,
length: has_update_method || transition_out ? x`${each_block}.length` : x`old_length`,
}
)
)}
@ -487,12 +490,12 @@ const bit_state = (arr) => arr.reduce((state, bool, index) => (bool ? (state |=
const for_loop = <T>(
arr: T,
callback: (item: Node, index: Node, array: T) => Node[],
callback: (item: Node, index: string, array: T) => Node[],
{ length = x`${arr}.length`, i = undefined } = {}
) =>
i !== undefined
? b`for (${i}; #i < ${length}; #i++) { ${callback(x`${arr}[#i]`, x`#i`, arr)} }`
: b`for (let #i = 0; #i < ${length}; #i++) { ${callback(x`${arr}[#i]`, x`#i`, arr)} }`;
? b`for (${i}; i < ${length}; i++) { ${callback(x`${arr}[i]`, `i`, arr)} }`
: b`for (let i = 0; i < ${length}; i++) { ${callback(x`${arr}[i]`, `i`, arr)} }`;
const $if = ({ if: condition, true: success, false: failure = null }) => {
if (condition) {

@ -655,9 +655,9 @@ export default class ElementWrapper extends Wrapper {
block.chunks.init.push(b`
const ${levels} = [${initial_props}];
const ${data} = ${levels}[0]||{};
for (let #i = 1; #i < ${levels}.length; #i += 1) {
${data} = { ...${data}, ...${levels}[#i] };
let ${data} = ${levels}[0] || {};
for (let i = 1; i < ${levels}.length; i++) {
${data} = { ...${data}, ...${levels}[i] };
}
`);
@ -707,8 +707,10 @@ export default class ElementWrapper extends Wrapper {
const [intro_var, node, transitionFn, params] = run_transition(this, block, intro, `intro`);
block.add_variable(intro_var, x`@noop`);
let start_intro = b`${intro_var} = @run_transition(${node}, ${transitionFn}, 1, ${params});`;
if (intro.is_local) start_intro = b`if (#local) ${start_intro};`;
let start_intro;
if (intro.is_local)
start_intro = b`if (#local) ${intro_var} = @run_transition(${node}, ${transitionFn}, 1, ${params});`;
else start_intro = b`${intro_var} = @run_transition(${node}, ${transitionFn}, 1, ${params});`;
block.chunks.intro.push(start_intro);
}
// TODO
@ -724,8 +726,9 @@ export default class ElementWrapper extends Wrapper {
const [outro_var, node, transitionFn, params] = run_transition(this, block, outro, `outro`);
block.add_variable(outro_var, x`@noop`);
let start_outro = b`${outro_var} = @run_transition(${node}, ${transitionFn}, 2, ${params});`;
if (outro.is_local) start_outro = b`if (#local) ${start_outro};`;
let start_outro;
if (outro.is_local) start_outro = b`if (#local) @run_transition(${node}, ${transitionFn}, 2, ${params});`;
else start_outro = b`${outro_var} = @run_transition(${node}, ${transitionFn}, 2, ${params});`;
block.chunks.outro.push(start_outro);
block.chunks.destroy.push(b`if (detaching) ${outro_var}();`);

@ -30,7 +30,7 @@ export default class HeadWrapper extends Wrapper {
if (this.renderer.options.hydratable && this.fragment.nodes.length) {
nodes = block.get_unique_name('head_nodes');
block.chunks.claim.push(
b`const ${nodes} = Array.from((@_document.head||document.body).querySelectorAll('[data-svelte="${this.node.id}"]'));`
b`const ${nodes} = Array.from((@_document.head||@_document.body).querySelectorAll('[data-svelte="${this.node.id}"]'));`
);
}

@ -242,7 +242,7 @@ export default class InlineComponentWrapper extends Wrapper {
initial_props.push(value);
change_object =
attr.expression.node.type !== 'ObjectExpression'
? b`(typeof ${value} === 'object' && ${value} !== null ? ${value} : {})`
? x`typeof ${value} === 'object' && ${value} !== null ? ${value} : {}`
: value;
} else {
const obj = x`{ ${name}: ${attr.get_value(block)} }`;
@ -258,8 +258,8 @@ export default class InlineComponentWrapper extends Wrapper {
block.chunks.init.push(b`const ${levels} = [${initial_props}];`);
statements.push(b`
for (let #i = 0; #i < ${levels}.length; #i += 1) {
${props} = Object.assign(${props}, ${levels}[#i]);
for (let i = 0; i < ${levels}.length; i += 1) {
${props} = Object.assign(${props}, ${levels}[i]);
}
`);
@ -418,7 +418,7 @@ export default class InlineComponentWrapper extends Wrapper {
const old_component = ${name};
${block.group_transition_out(
(transition_out) =>
b`${transition_out}(old_component.$$.fragment, () => { @destroy_component(old_component, 1); }, 0)`
b`${transition_out}(old_component.$$.fragment, () => { @destroy_component(old_component, 1); }, 0);`
)}
}

@ -8,20 +8,22 @@ import { x } from 'code-red';
import Expression from '../../nodes/shared/Expression';
import remove_whitespace_children from './utils/remove_whitespace_children';
export default function(node: Element, renderer: Renderer, options: RenderOptions & {
export default function (
node: Element,
renderer: Renderer,
options: RenderOptions & {
slot_scopes: Map<any, any>;
}) {
}
) {
const children = remove_whitespace_children(node.children, node.next);
// awkward special case
let node_contents;
const contenteditable = (
const contenteditable =
node.name !== 'textarea' &&
node.name !== 'input' &&
node.attributes.some((attribute) => attribute.name === 'contenteditable')
);
node.attributes.some((attribute) => attribute.name === 'contenteditable');
const slot = node.get_static_attribute_value('slot');
const nearest_inline_component = node.find_nearest(/InlineComponent/);
@ -32,7 +34,7 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
renderer.add_string(`<${node.name}`);
const class_expression_list = node.classes.map(class_directive => {
const class_expression_list = node.classes.map((class_directive) => {
const { expression, name } = class_directive;
const snippet = expression ? expression.node : x`#ctx.${name}`; // TODO is this right?
return x`${snippet} ? "${name}" : ""`;
@ -41,13 +43,12 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
class_expression_list.push(x`"${node.component.stylesheet.id}"`);
}
const class_expression =
class_expression_list.length > 0 &&
class_expression_list.reduce((lhs, rhs) => x`${lhs} + ' ' + ${rhs}`);
class_expression_list.length > 0 && class_expression_list.reduce((lhs, rhs) => x`${lhs} + ' ' + ${rhs}`);
if (node.attributes.some(attr => attr.is_spread)) {
if (node.attributes.some((attr) => attr.is_spread)) {
// TODO dry this out
const args = [];
node.attributes.forEach(attribute => {
node.attributes.forEach((attribute) => {
if (attribute.is_spread) {
args.push(attribute.expression.node);
} else {
@ -72,17 +73,13 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
renderer.add_expression(x`@spread([${args}], ${class_expression})`);
} else {
let add_class_attribute = !!class_expression;
node.attributes.forEach(attribute => {
node.attributes.forEach((attribute) => {
const name = attribute.name.toLowerCase();
if (name === 'value' && node.name.toLowerCase() === 'textarea') {
node_contents = get_attribute_value(attribute);
} else if (attribute.is_true) {
renderer.add_string(` ${attribute.name}`);
} else if (
boolean_attributes.has(name) &&
attribute.chunks.length === 1 &&
attribute.chunks[0].type !== 'Text'
) {
} else if (boolean_attributes.has(name) && attribute.chunks.length === 1 && attribute.chunks[0].type !== 'Text') {
// a boolean attribute with one non-Text chunk
renderer.add_string(` `);
renderer.add_expression(x`${(attribute.chunks[0] as Expression).node} ? "${attribute.name}" : ""`);
@ -93,7 +90,9 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
renderer.add_string(`"`);
} else if (attribute.chunks.length === 1 && attribute.chunks[0].type !== 'Text') {
const snippet = (attribute.chunks[0] as Expression).node;
renderer.add_expression(x`@add_attribute("${attribute.name}", ${snippet}, ${boolean_attributes.has(name) ? 1 : 0})`);
renderer.add_expression(
x`@add_attribute("${attribute.name}", ${snippet}, ${boolean_attributes.has(name) ? 1 : 0})`
);
} else {
renderer.add_string(` ${attribute.name}="`);
renderer.add_expression((name === 'class' ? get_class_attribute_value : get_attribute_value)(attribute));
@ -105,7 +104,7 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
}
}
node.bindings.forEach(binding => {
node.bindings.forEach((binding) => {
const { name, expression } = binding;
if (binding.is_readonly) {
@ -156,15 +155,15 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
}
const lets = node.lets;
const seen = new Set(lets.map(l => l.name.name));
const seen = new Set(lets.map((l) => l.name.name));
nearest_inline_component.lets.forEach(l => {
nearest_inline_component.lets.forEach((l) => {
if (!seen.has(l.name.name)) lets.push(l);
});
options.slot_scopes.set(slot, {
input: get_slot_scope(node.lets),
output: renderer.pop()
output: renderer.pop(),
});
} else {
renderer.render(children, options);
@ -174,4 +173,3 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
}
}
}

@ -9,7 +9,7 @@ export function get_class_attribute_value(attribute: Attribute): ESTreeExpressio
// handle special case — `class={possiblyUndefined}` with scoped CSS
if (attribute.chunks.length === 2 && (attribute.chunks[1] as Text).synthetic) {
const value = (attribute.chunks[0] as Expression).node;
return x`(${x`${value} != null && @escape(${value}) || ""`}) + "${(attribute.chunks[1] as Text).data}"`;
return x`(${x`${value} != null ? @escape(${value}) : ""`}) + "${(attribute.chunks[1] as Text).data}"`;
}
return get_attribute_value(attribute);

@ -31,7 +31,7 @@ export default function ssr(component: Component, options: CompileOptions): { js
let #k;
const #keys = new Set([${props.map((prop) => `"${prop.export_name}"`).join(',')}]);
let $$restProps = {};
for (#k in $$props){ if (!#keys.has(#k) && $$props[#k][0] !== '$'){ $$restProps[#k] = $$props[#k];}}`
for (#k in $$props){ if (!#keys.has(#k) && #k[0] !== '$'){ $$restProps[#k] = $$props[#k];}}`
: null;
const reactive_stores = component.vars.filter((variable) => variable.name[0] === '$' && variable.name[1] !== '$');

@ -2,7 +2,6 @@ import { isIdentifierStart, isIdentifierChar } from 'acorn';
import full_char_code_at from './full_char_code_at';
/** source : rollup */
export const globals = new Set([
'global',
'__proto__',
'AbortController',
'AbortSignal',
@ -162,6 +161,7 @@ export const globals = new Set([
'GamepadEvent',
'getComputedStyle',
'getSelection',
'global',
'globalThis',
'HashChangeEvent',
'hasOwnProperty',
@ -272,6 +272,7 @@ export const globals = new Set([
'Int16Array',
'Int32Array',
'Int8Array',
'InternalError',
'IntersectionObserver',
'IntersectionObserverEntry',
'Intl',
@ -400,6 +401,7 @@ export const globals = new Set([
'PresentationReceiver',
'PresentationRequest',
'print',
'process',
'ProcessingInstruction',
'ProgressEvent',
'Promise',
@ -614,6 +616,7 @@ export const globals = new Set([
'Uint32Array',
'Uint8Array',
'Uint8ClampedArray',
'undefined',
'unescape',
'URIError',
'URL',

@ -1,15 +0,0 @@
import { whitespace } from './patterns';
export function trim_start(str: string) {
let i = 0;
while (whitespace.test(str[i])) i += 1;
return str.slice(i);
}
export function trim_end(str: string) {
let i = str.length;
while (whitespace.test(str[i - 1])) i -= 1;
return str.slice(0, i);
}

@ -1,4 +1,4 @@
import { noop } from '../internal/utils';
export function noop() {}
export const is_browser = typeof window !== 'undefined';
export const is_iframe = is_browser && window.self !== window.top;
export const is_cors =
@ -17,7 +17,7 @@ declare var global: any;
export const globals = is_browser ? window : typeof globalThis !== 'undefined' ? globalThis : global;
export const resolved_promise = Promise.resolve();
export let now = is_browser ? () => performance.now() : Date.now.bind(Date);
export let now = is_browser ? window.performance.now.bind(window.performance) : Date.now.bind(Date);
export let raf = is_browser ? requestAnimationFrame : noop;
export let framerate = 1000 / 60;
/*#__PURE__*/ raf((t1) => {
@ -29,5 +29,6 @@ export let framerate = 1000 / 60;
});
/* tests only */
export const set_now = (fn) => void (now = fn);
export const set_now = (v) => void (now = v);
export const set_raf = (fn) => void (raf = fn);
export const set_framerate = (v) => void (framerate = v);

@ -1,8 +1,8 @@
import { add_render_callback, flush, schedule_update } from './scheduler';
import { current_component, set_current_component } from './lifecycle';
import { noop } from './utils';
import { children, detach } from './dom';
import { transition_in } from './transitions';
import { noop } from 'svelte/environment';
type binary = 0 | 1;
export interface Fragment {
key: string | null;

@ -1,5 +1,5 @@
import { noop } from './utils';
import { run_transition } from './transitions';
import { noop } from 'svelte/environment';
export interface AnimationConfig {
delay?: number;

@ -127,6 +127,14 @@ export const check_duplicate_keys_dev = (ctx, list, get_context, get_key) => {
}
};
export function validate_slots_dev(name, slot, keys) {
for (const slot_key of Object.keys(slot)) {
if (!~keys.indexOf(slot_key)) {
console.warn(`<${name}> received an unexpected slot "${slot_key}".`);
}
}
}
type Props = Record<string, any>;
export interface SvelteComponentDev {
$set(props?: Props): void;

@ -125,7 +125,7 @@ export function xlink_attr(node: Element, name, value) {
export function get_binding_group_value(group) {
const value = [];
for (let i = 0, value = []; i < group.length; i += 1) {
for (let i = 0; i < group.length; i += 1) {
if (group[i].checked) value.push(group[i].__value);
}
return value;

@ -1,5 +1,4 @@
import { now, raf, framerate } from 'svelte/environment';
import { noop } from './utils';
import { now, raf, framerate, noop } from 'svelte/environment';
type TaskCallback = (t: number) => boolean;
type TaskCanceller = () => void;
@ -55,7 +54,7 @@ export const loop = (fn) => {
next_frame[n++] = (t) => !running || fn(t);
return () => void (running = false);
};
export const setFrameTimeout = (callback: () => void, timestamp: number): TaskCanceller => {
export const setFrameTimeout = (callback: (t: number) => void, timestamp: number): TaskCanceller => {
const task: TimeoutTask = { callback, timestamp };
if (running_timed) {
pending_inserts = !!pending_insert_timed.push(task);
@ -78,9 +77,9 @@ export const setTweenTimeout = (
let running = true;
unsafe_loop((t) => {
if (!running) return false;
t = 1 - (end_time - t) / duration;
if (t >= 1) return run(1), stop(t), false;
if (t >= 0) run(t);
t = 1.0 - (end_time - t) / duration;
if (t >= 1.0) return run(1), stop(t), false;
if (t >= 0.0) run(t);
return running;
});
return () => void (running = false);
@ -89,7 +88,7 @@ export const setTweenTimeout = (
* Calls function every frame with the amount of elapsed frames
*/
export const onEachFrame = (
each_frame: (seconds_elapsed: number) => boolean,
callback: (seconds_elapsed: number) => boolean,
on_stop?,
max_skipped_frames = 4
): TaskCanceller => {
@ -100,7 +99,7 @@ export const onEachFrame = (
unsafe_loop((t: number) => {
if (!running) return cancel(t);
if (t > lastTime + max_skipped_frames) t = lastTime + max_skipped_frames;
return each_frame((-lastTime + (lastTime = t)) / 1000) ? true : cancel(t);
return callback((-lastTime + (lastTime = t)) / 1000) ? true : cancel(t);
});
return () => void (running = false);
};

@ -1,5 +1,6 @@
import { safe_not_equal, noop, subscribe } from './utils';
import { safe_not_equal, subscribe } from './utils';
import { onEachFrame, loop } from './loop';
import { noop } from 'svelte/environment';
type Setter<T> = (value: T) => void;
type StopCallback = () => void;
export type StartStopNotifier<T> = (set: Setter<T>) => StopCallback | void;
@ -70,7 +71,7 @@ class StartStopWritable<T> extends Store<T> {
super(initial);
this.start = startStopNotifier || noop;
}
subscribe(run, invalidate) {
subscribe(run, invalidate?) {
// *must* run *after* first subscription ?
if (!super.has_subscribers) this.stop = this.start(this.set.bind(this)) || noop;
return super.subscribe(run, invalidate);
@ -132,7 +133,8 @@ export class Derived<S extends Obs, D extends Deriver<T>, T> extends StartStopWr
deriver.length < 2
? // deriver returned value is store value
(v) => void super.set(deriver(v) as T)
: // deriver returned value is cleanup | void, store value is set manually within deriver
: // deriver returned value is cleanup | void
// store value is set manually within deriver
(v) =>
void (this.cleanup(),
typeof (this.cleanup = deriver(v, super.set.bind(this)) as () => void) !== 'function' &&

@ -28,7 +28,7 @@ export const animate_css = /*#__PURE__*/ Function.prototype.call.bind(function a
let i = rule.length,
hash = 5381;
while (i--) hash = ((hash << 5) - hash) ^ rule.charCodeAt(i);
const name = '_' + (hash >>> 0) + document_uid.get(this.ownerDocument);
const name = `__svelte_${hash >>> 0}${document_uid.get(this.ownerDocument)}`;
if (!current_rules.has(name)) {
current_rules.add(name);

@ -1,14 +1,13 @@
import { TransitionConfig } from '../transition';
import { Fragment } from './Component';
import { custom_event } from './dom';
import { now } from 'svelte/environment';
import { now, noop } from 'svelte/environment';
import { setFrameTimeout, setTweenTimeout } from './loop';
import { add_measure_callback } from './scheduler';
import { animate_css } from './style_manager';
import { noop } from './utils';
type TransitionFn = (node: HTMLElement, params: any) => TransitionConfig;
export type StopResetReverse = (t?: number | -1) => StopResetReverse | void;
export type StopReverse = (t?: number | -1) => StopReverse | void;
export const transition_in = (block: Fragment, local?) => {
if (!block || !block.i) return;
@ -16,21 +15,23 @@ export const transition_in = (block: Fragment, local?) => {
block.i(local);
};
export const transition_out = (block: Fragment, local) => {
export const transition_out = (block: Fragment, local?) => {
if (!block || !block.o || outroing.has(block)) return;
outroing.add(block);
block.o(local);
};
let transition_group;
const outroing = new Set();
const check_transition_group = (group, decrement = true) => {
if (decrement) group.r--;
if (!group.r) for (let i = 0; i < group.c.length; i++) group.c[i]();
type TransitionGroup = {
/* parent group */ p: TransitionGroup;
/* callbacks */ c: (() => void)[];
/* running outros */ r: number;
/* stop callbacks */ s: ((t: number) => void)[];
/* outro timeout */ t: number;
};
let transition_group: TransitionGroup;
const outroing = new Set();
export const group_transition_out = (fn) => {
const c = [];
const current_group = (transition_group = { p: transition_group, c, r: 0 });
const current_group = (transition_group = { p: transition_group, c, r: 0, s: [], t: 0 });
fn((block, callback, detach = true) => {
if (!block || !block.o || outroing.has(block)) return;
outroing.add(block);
@ -44,7 +45,7 @@ export const group_transition_out = (fn) => {
});
block.o(1);
});
check_transition_group(current_group, false);
if (!current_group.r) for (let i = 0; i < current_group.c.length; i++) current_group.c[i]();
transition_group = transition_group.p;
};
@ -63,9 +64,8 @@ const reversed = (fn, rx, easing, start = 0, end = 1) => {
export enum tx {
intro = 1,
outro = 2,
reverse = 3,
bidirectional = 4,
bidirectional_intro = 5,
bidirectional_outro = 6,
animation = 8,
}
export const run_transition = /*#__PURE__*/ Function.prototype.call.bind(function transition(
@ -92,9 +92,8 @@ export const run_transition = /*#__PURE__*/ Function.prototype.call.bind(functio
add_measure_callback(() => {
if (null === (config = fn(this, params))) return noop;
return (t) => {
return (current_frame_time) => {
if (false === running) return;
if ('then' in config) return void config.then(stop);
let { delay = 0, duration = 300, easing, tick, css, strategy = 'reverse' }: TransitionConfig =
'function' === typeof config ? (config = config()) : config;
@ -108,57 +107,67 @@ export const run_transition = /*#__PURE__*/ Function.prototype.call.bind(functio
else if (solver === mirrored) delay -= elapsed_duration;
}
end_time = (start_time = t + delay) + duration;
end_time = (start_time = current_frame_time + delay) + duration;
if (0 === (rx & tx.animation)) {
this.dispatchEvent(custom_event(`${rx & tx.intro ? 'in' : 'ou'}trostart`));
}
if (css) cancel_css = animate_css(this, runner(css), duration, delay);
if (rx & tx.outro && css) {
if (current_group.s.push(stop) === current_group.r) {
setFrameTimeout((t) => {
for (let i = 0; i < current_group.s.length; i++) current_group.s[i](t);
}, Math.max(end_time, current_group.t));
} else {
current_group.t = Math.max(end_time, current_group.t);
}
if (tick) {
cancel_raf = setTweenTimeout(noop, end_time, runner(tick), duration);
}
} else {
cancel_raf = tick ? setTweenTimeout(stop, end_time, runner(tick), duration) : setFrameTimeout(stop, end_time);
}
};
});
const stop: StopResetReverse = (t?: number | 1 | -1) => {
if (!running) return;
const stop: StopReverse = (t?: number | -1) => {
if (rx & tx.outro && 0 === (rx & tx.bidirectional) && void 0 === t && 'tick' in config) config.tick(1, 0);
if (false === running) return;
else running = false;
if (cancel_css) cancel_css();
if (cancel_raf) cancel_raf();
if (0 === (rx & tx.animation)) {
if (t >= end_time) {
this.dispatchEvent(custom_event(`${rx & tx.intro ? 'in' : 'ou'}troend`));
}
if (rx & tx.outro) {
check_transition_group(current_group);
}
}
if (rx & tx.animation) return;
if (rx & tx.bidirectional) {
if (-1 === t) {
if (t >= end_time) this.dispatchEvent(custom_event(`${rx & tx.intro ? 'in' : 'ou'}troend`));
if (rx & tx.outro && !--current_group.r) for (let i = 0; i < current_group.c.length; i++) current_group.c[i]();
if (0 === (rx & tx.bidirectional)) return;
if (-1 === t)
return (
(t = now()) < end_time &&
run_transition(
this,
() => config,
rx ^ 1,
rx ^ tx.reverse,
params,
end_time - t,
start_time > t ? start_time - t : 0,
(1 - elapsed_ratio) * (1 - config.easing(1 - (end_time - t) / (end_time - start_time)))
(1 - elapsed_ratio) * (1 - (config.easing || ((v) => v))(1 - (end_time - t) / (end_time - start_time)))
)
);
} else {
running_bidi.delete(this);
}
}
else running_bidi.delete(this);
};
return stop;
});
const running_bidi: Map<HTMLElement, StopResetReverse> = new Map();
const running_bidi: Map<HTMLElement, StopReverse> = new Map();
export const run_bidirectional_transition = /*#__PURE__*/ Function.prototype.call.bind(function bidirectional(
this: HTMLElement,
fn: TransitionFn,

@ -1,4 +1,5 @@
export function noop() {}
import { noop } from 'svelte/environment';
export const is_promise = <T = any>(value: any): value is PromiseLike<T> =>
value && typeof value === 'object' && typeof value.then === 'function';

@ -36,7 +36,7 @@ function solve_spring(
}
export function spring(
value,
value?,
{ mass = 1.0, damping = 10.0, stiffness = 100.0, precision = 0.001, soft = false }: SpringParams = {}
) {
const store = new SpringMotion(value, (set) => {
@ -74,13 +74,13 @@ export function spring(
return obj;
}
export function tweened<T>(
value: T,
value?: T,
{
delay: default_delay = 0,
duration: default_duration = 400,
easing: default_easing = (v) => v,
interpolate: default_interpolate = numbers,
}: TweenParams<T>
}: TweenParams<T> = {}
) {
let delay = default_delay,
duration = default_duration,

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

@ -1,10 +1,10 @@
import * as fs from 'fs';
import * as path from 'path';
import * as http from 'http';
import { rollup } from 'rollup';
import * as virtual from '@rollup/plugin-virtual';
import * as puppeteer from 'puppeteer';
import { addLineNumbers, loadConfig, loadSvelte } from "../helpers.js";
const { rollup } = require('rollup');
const virtual = require('@rollup/plugin-virtual');
const puppeteer = require('puppeteer');
import { addLineNumbers, loadConfig, loadSvelte } from '../helpers';
import { deepEqual } from 'assert';
const page = `
@ -53,12 +53,13 @@ describe('custom-elements', function() {
await browser.close();
});
fs.readdirSync(`${__dirname}/samples`).forEach(dir => {
fs.readdirSync(`${__dirname}/samples`).forEach((dir) => {
if (dir[0] === '.') return;
const solo = /\.solo$/.test(dir);
const skip = /\.skip$/.test(dir);
const internal = path.resolve('internal/index.mjs');
const environment = path.resolve('environment/index.mjs');
const index = path.resolve('index.mjs');
const warnings = [];
@ -78,26 +79,28 @@ describe('custom-elements', function() {
if (importee === 'svelte') {
return index;
}
if (importee === 'svelte/environment' || importee === '../environment') {
return environment;
}
},
transform(code, id) {
if (id.endsWith('.svelte')) {
const compiled = svelte.compile(code, {
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;
}
}
},
},
virtual({
assert
})
]
assert,
}),
],
});
const result = await bundle.generate({ format: 'iife', name: 'test' });
@ -109,7 +112,7 @@ describe('custom-elements', function() {
console[type](...args);
});
page.on('error', error => {
page.on('error', (error) => {
console.log('>>> an error happened');
console.error(error);
});
@ -124,13 +127,16 @@ describe('custom-elements', function() {
throw err;
} finally {
if (expected_warnings) {
deepEqual(warnings.map(w => ({
deepEqual(
warnings.map((w) => ({
code: w.code,
message: w.message,
pos: w.pos,
start: w.start,
end: w.end
})), expected_warnings);
end: w.end,
})),
expected_warnings
);
}
}
});

@ -1,13 +1,13 @@
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 fs from 'fs';
import * as colors from 'kleur';
import { glob } from './tiny-glob';
import { assert } from './test';
// for coverage purposes, we need to test source files,
// but for sanity purposes, we need to test dist files
export function loadSvelte(test) {
export function loadSvelte(test?) {
process.env.TEST = test ? 'true' : '';
const resolved = require.resolve('../compiler.js');
@ -16,7 +16,7 @@ export function loadSvelte(test) {
return require(resolved);
}
export const svelte = loadSvelte();
export const svelte = loadSvelte(false);
export function exists(path) {
try {
@ -54,7 +54,8 @@ export function cleanRequireCache() {
const virtualConsole = new jsdom.VirtualConsole();
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.navigator = window.navigator;
global.getComputedStyle = window.getComputedStyle;
@ -63,14 +64,14 @@ global.window = window;
// add missing ecmascript globals to window
for (const key of Object.getOwnPropertyNames(global)) {
window[key] = window[key] || global[key];
if (!(key in window)) window[key] = global[key];
}
// implement mock scroll
window.scrollTo = function (pageXOffset, pageYOffset) {
window.pageXOffset = pageXOffset;
window.pageYOffset = pageYOffset;
};
// window.scrollTo = function (pageXOffset, pageYOffset) {
// window.pageXOffset = pageXOffset;
// window.pageYOffset = pageYOffset;
// };
export function env() {
window.document.title = '';
@ -79,8 +80,8 @@ export function env() {
return window;
}
function cleanChildren(node) {
const is_TextNode = (n: any): n is Text => n.nodeType === 3;
function cleanChildren(node: Element) {
let previous = null;
// sort attributes
@ -97,9 +98,8 @@ function cleanChildren(node) {
});
// recurse
[...node.childNodes].forEach((child) => {
if (child.nodeType === 3) {
// text
Array.from(node.childNodes).forEach((child) => {
if (is_TextNode(child)) {
if (node.namespaceURI === 'http://www.w3.org/2000/svg' && node.tagName !== 'text' && node.tagName !== 'tspan') {
node.removeChild(child);
}
@ -114,19 +114,19 @@ function cleanChildren(node) {
child = previous;
}
} else {
cleanChildren(child);
cleanChildren(child as Element);
}
previous = child;
});
// collapse whitespace
if (node.firstChild && node.firstChild.nodeType === 3) {
if (node.firstChild && is_TextNode(node.firstChild)) {
node.firstChild.data = node.firstChild.data.replace(/^\s+/, '');
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+$/, '');
if (!node.lastChild.data) node.removeChild(node.lastChild);
}
@ -145,11 +145,10 @@ export function normalizeHtml(window, html) {
throw new Error(`Failed to normalize HTML:\n${html}`);
}
}
export function setupHtmlEqual() {
const window = env();
assert.htmlEqual = (actual, expected, message) => {
assert.htmlEqual = function (actual, expected, message) {
assert.deepEqual(normalizeHtml(window, actual), normalizeHtml(window, expected), message);
};
}
@ -176,7 +175,6 @@ export function addLineNumbers(code) {
.map((line, i) => {
i = String(i + 1);
while (i.length < 3) i = ` ${i}`;
return colors.gray(` ${i}: `) + line.replace(/^\t+/, (match) => match.split('\t').join(' '));
})
.join('\n');
@ -208,12 +206,6 @@ export function shouldUpdateExpected() {
return process.argv.includes('--update');
}
export function spaces(i) {
let result = '';
while (i--) result += ' ';
return result;
}
// fake timers
const original_set_timeout = global.setTimeout;
@ -222,7 +214,7 @@ export function useFakeTimers() {
global.setTimeout = function (fn) {
callbacks.push(fn);
};
} as (callback: (...args: any[]) => void, ms: number, ...args: any[]) => any;
return {
flush() {

@ -1,15 +1,8 @@
import * as assert from 'assert';
import * as path from 'path';
import * as fs from 'fs';
import {
showOutput,
loadConfig,
loadSvelte,
env,
setupHtmlEqual,
shouldUpdateExpected
} from '../helpers.js';
import { showOutput, loadConfig, loadSvelte, env, setupHtmlEqual, shouldUpdateExpected } from '../helpers';
import { assert } from '../test';
let compileOptions = null;
@ -25,7 +18,7 @@ describe('hydration', () => {
filename,
hydratable: true,
format: 'cjs',
sveltePath
sveltePath,
},
compileOptions
);
@ -82,7 +75,7 @@ describe('hydration', () => {
const component = new SvelteComponent({
target,
hydrate: true,
props: config.props
props: config.props,
});
try {
@ -117,18 +110,19 @@ describe('hydration', () => {
}
} catch (err) {
showOutput(cwd, {
hydratable: true
hydratable: true,
});
throw err;
}
if (config.show) showOutput(cwd, {
hydratable: true
if (config.show)
showOutput(cwd, {
hydratable: true,
});
});
}
fs.readdirSync(`${__dirname}/samples`).forEach(dir => {
fs.readdirSync(`${__dirname}/samples`).forEach((dir) => {
runTest(dir, null);
});
});

@ -1,38 +1,46 @@
import * as assert from "assert";
import * as fs from "fs";
import * as path from "path";
import * as colors from "kleur";
import { loadConfig, svelte, shouldUpdateExpected } from "../helpers.js";
import * as fs from 'fs';
import * as path from 'path';
import * as colors from 'kleur';
import { loadConfig, svelte, shouldUpdateExpected } from '../helpers';
import { assert } from '../test';
describe("js", () => {
fs.readdirSync(`${__dirname}/samples`).forEach(dir => {
if (dir[0] === ".") return;
describe('js', () => {
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);
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);
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;
}
(solo ? it.only : it)(dir, () => {
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;
try {
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) {
console.log(err.frame);
throw err;
@ -45,7 +53,7 @@ describe("js", () => {
let expected = '';
try {
expected = fs.readFileSync(expectedPath, "utf-8");
expected = fs.readFileSync(expectedPath, 'utf-8');
} catch (error) {
console.log(error);
if (error.code === 'ENOENT') {
@ -55,10 +63,7 @@ describe("js", () => {
}
try {
assert.equal(
actual.trim().replace(/^[ \t]+$/gm, ""),
expected.trim().replace(/^[ \t]+$/gm, "")
);
assert.equal(actual.trim().replace(/^[ \t]+$/gm, ''), expected.trim().replace(/^[ \t]+$/gm, ''));
} catch (error) {
if (shouldUpdateExpected()) {
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 { spring, tweened } from '../../motion';

@ -1,18 +1,16 @@
import * as assert from 'assert';
import * as fs from 'fs';
import { svelte, tryToLoadJson, shouldUpdateExpected } from '../helpers.js';
import { svelte, tryToLoadJson } from '../helpers';
import { assert } from '../test';
describe('parse', () => {
fs.readdirSync(`${__dirname}/samples`).forEach(dir => {
describe('parser', () => {
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);
if (solo && process.env.CI) {
throw new Error(
`Forgot to remove '.solo' from test parser/samples/${dir}`
);
throw new Error(`Forgot to remove '.solo' from test parser/samples/${dir}`);
}
const skip = !fs.existsSync(`${__dirname}/samples/${dir}/input.svelte`);
@ -25,9 +23,12 @@ describe('parse', () => {
const expectedError = tryToLoadJson(`${__dirname}/samples/${dir}/error.json`);
try {
const { ast } = svelte.compile(input, Object.assign(options, {
generate: false
}));
const { ast } = svelte.compile(
input,
Object.assign(options, {
generate: false,
})
);
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.module, expectedOutput.module);
} catch (err) {
if (err.name !== 'ParseError') throw err;
if (!expectedError) throw err;
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;
}
if (err.name !== 'ParseError' || !expectedError) throw err;
assert.deepEqual(JSON.parse(JSON.stringify({ ...err, message: err.message })), expectedError);
}
});
});

@ -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 assert from 'assert';
import { loadConfig, svelte } from '../helpers.js';
import { loadConfig, svelte } from '../helpers';
import { assert } from '../test';
describe('preprocess', () => {
fs.readdirSync(`${__dirname}/samples`).forEach(dir => {
fs.readdirSync(`${__dirname}/samples`).forEach((dir) => {
if (dir[0] === '.') return;
const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`);

@ -1,14 +1,14 @@
import * as assert from 'assert';
import { assert } from '../../../test';
export default {
preprocess: {
style: ({ attributes }) => {
assert.deepEqual(attributes, {
type: 'text/scss',
'type': 'text/scss',
'data-foo': 'bar',
bool: true
'bool': true,
});
return { code: 'PROCESSED' };
}
}
},
},
};

@ -1,4 +1,8 @@
import * as assert from 'assert';
type TestSetup = {
test: ({ assert: module.assert, component, mod, target, window, raf, compileOptions }) => void | Promise<void>;
type Test = {
test({ assert, component, mod, target, window, raf, compileOptions }): void | Promise<void>;
html: string;
skip: boolean;
};
declare module 'samples/*/_config.js' {
export default Test;
}

@ -1,13 +1,13 @@
import * as assert from 'assert';
import * as path from 'path';
import * as fs from 'fs';
import { relative, resolve } from 'path';
import { readFileSync, existsSync, unlinkSync, writeFileSync, readdirSync } from 'fs';
import { rollup } from 'rollup';
import * as virtual from '@rollup/plugin-virtual';
import * as glob from 'tiny-glob/sync.js';
import { clear_loops, flush } from '../../internal';
import { set_now, set_raf } from '../../environment';
import { showOutput, loadConfig, loadSvelte, cleanRequireCache, env, setupHtmlEqual, mkdirp } from '../helpers.js';
import virtual from '@rollup/plugin-virtual';
import { clear_loops, flush, SvelteComponent } from '../../internal';
import { set_now, set_raf, set_framerate } from '../../environment';
import './ambient';
import { showOutput, loadConfig, loadSvelte, cleanRequireCache, env, setupHtmlEqual, mkdirp } from '../helpers';
import { glob } from '../tiny-glob';
import { assert } from '../test';
let svelte$;
let svelte;
@ -17,8 +17,8 @@ let compile = null;
const sveltePath = process.cwd().split('\\').join('/');
let unhandled_rejection = false;
process.on('unhandledRejection', (err) => {
let unhandled_rejection: Error | false = false;
process.on('unhandledRejection', (err: Error) => {
unhandled_rejection = err;
});
@ -28,18 +28,10 @@ describe('runtime', () => {
svelte$ = loadSvelte(true);
require.extensions['.svelte'] = function (module, filename) {
const options = Object.assign(
{
filename,
},
compileOptions
return module._compile(
compile(readFileSync(filename, 'utf-8'), { filename, ...compileOptions }).js.code,
filename
);
const {
js: { code },
} = compile(fs.readFileSync(filename, 'utf-8'), options);
return module._compile(code, filename);
};
return setupHtmlEqual();
@ -47,19 +39,19 @@ describe('runtime', () => {
const failed = new Set();
function runTest(dir, hydrate) {
function runTest(dir, hydratable) {
if (dir[0] === '.') return;
const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`);
const solo = config.solo || /\.solo/.test(dir);
if (hydrate && config.skip_if_hydrate) return;
if (hydratable && config.skip_if_hydrate) return;
if (solo && process.env.CI) {
throw new Error('Forgot to remove `solo: true` from test');
}
(config.skip ? it.skip : solo ? it.only : it)(`${dir} ${hydrate ? '(with hydration)' : ''}`, () => {
(config.skip ? it.skip : solo ? it.only : it)(`${dir} ${hydratable ? '(with hydration)' : ''}`, () => {
if (failed.has(dir)) {
// this makes debugging easier, by only printing compiled output once
throw new Error('skipping test, already failed');
@ -67,16 +59,18 @@ describe('runtime', () => {
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.format = 'cjs';
compileOptions.sveltePath = sveltePath;
compileOptions.hydratable = hydrate;
compileOptions.immutable = config.immutable;
compileOptions.accessors = 'accessors' in config ? config.accessors : true;
compileOptions = {
...(config.compileOptions || {}),
format: 'cjs',
sveltePath,
hydratable,
immutable: config.immutable,
accessors: 'accessors' in config ? config.accessors : true,
};
cleanRequireCache();
@ -87,35 +81,33 @@ describe('runtime', () => {
const window = env();
glob('**/*.svelte', { cwd }).forEach((file) => {
if (file[0] === '_') return;
glob('**/*.svelte', { cwd }).forEach((filename) => {
if (filename[0] === '_') return;
const dir = `${cwd}/_output/${hydrate ? 'hydratable' : 'normal'}`;
const out = `${dir}/${file.replace(/\.svelte$/, '.js')}`;
if (fs.existsSync(out)) {
fs.unlinkSync(out);
}
const dir = `${cwd}/_output/${hydratable ? 'hydratable' : 'normal'}`;
const out = `${dir}/${filename.replace(/\.svelte$/, '.js')}`;
if (existsSync(out)) unlinkSync(out);
mkdirp(dir);
try {
const { js } = compile(fs.readFileSync(`${cwd}/${file}`, 'utf-8'), {
writeFileSync(
out,
compile(readFileSync(`${cwd}/${filename}`, 'utf-8'), {
...compileOptions,
filename: file,
});
fs.writeFileSync(out, js.code);
filename,
}).js.code
);
} catch (err) {
// do nothing
}
});
// set framerate to 1 frame per millisecond
set_framerate(1);
return Promise.resolve()
.then(() => {
// hack to support transition tests
clear_loops();
const raf = {
time: 0,
callback: null,
@ -134,8 +126,7 @@ describe('runtime', () => {
});
try {
mod = require(`./samples/${dir}/main.svelte`);
SvelteComponent = mod.default;
SvelteComponent = require(`./samples/${dir}/main.svelte`).default;
} catch (err) {
showOutput(cwd, compileOptions, compile); // eslint-disable-line no-console
throw err;
@ -154,18 +145,12 @@ describe('runtime', () => {
warnings.push(warning);
};
const options = Object.assign(
{},
{
target,
hydrate,
props: config.props,
intro: config.intro,
},
config.options || {}
);
const options = {
...{ target, hydrate: hydratable, props: config.props, intro: config.intro },
...(config.options || {}),
};
const component = new SvelteComponent(options);
const component: SvelteComponent = new SvelteComponent(options);
console.warn = warn;
@ -197,6 +182,7 @@ describe('runtime', () => {
compileOptions,
})
).then(() => {
raf.tick(Infinity);
component.$destroy();
if (unhandled_rejection) {
@ -230,7 +216,7 @@ describe('runtime', () => {
})
.catch((err) => {
// 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;
})
.then(() => {
@ -245,7 +231,7 @@ describe('runtime', () => {
});
}
fs.readdirSync(`${__dirname}/samples`).forEach((dir) => {
readdirSync(`${__dirname}/samples`).forEach((dir) => {
runTest(dir, false);
runTest(dir, true);
});

@ -1,7 +1,5 @@
import * as assert from "assert";
import * as fs from "fs";
import * as path from "path";
import * as glob from 'tiny-glob/sync.js';
import * as fs from 'fs';
import * as path from 'path';
import {
showOutput,
@ -11,14 +9,16 @@ import {
tryToLoadJson,
cleanRequireCache,
shouldUpdateExpected,
mkdirp
} from "../helpers.js";
mkdirp,
} from '../helpers.ts';
import { glob } from '../tiny-glob.ts';
import { assert } from '../test';
function tryToReadFile(file) {
try {
return fs.readFileSync(file, "utf-8");
return fs.readFileSync(file, 'utf-8');
} catch (err) {
if (err.code !== "ENOENT") throw err;
if (err.code !== 'ENOENT') throw err;
return null;
}
}
@ -26,15 +26,15 @@ function tryToReadFile(file) {
const sveltePath = process.cwd().split('\\').join('/');
let compile = null;
describe("ssr", () => {
describe('ssr', () => {
before(() => {
compile = loadSvelte(true).compile;
return setupHtmlEqual();
});
fs.readdirSync(`${__dirname}/samples`).forEach(dir => {
if (dir[0] === ".") return;
fs.readdirSync(`${__dirname}/samples`).forEach((dir) => {
if (dir[0] === '.') return;
const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`);
@ -44,7 +44,7 @@ describe("ssr", () => {
const show = /\.show/.test(dir);
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, () => {
@ -56,16 +56,16 @@ describe("ssr", () => {
sveltePath,
...config.compileOptions,
generate: 'ssr',
format: 'cjs'
format: 'cjs',
};
require("../../register")(compileOptions);
require('../../register')(compileOptions);
try {
const Component = require(`${dir}/main.svelte`).default;
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;
@ -87,10 +87,7 @@ describe("ssr", () => {
}
try {
assert.equal(
css.code.replace(/^\s+/gm, ""),
expectedCss.replace(/^\s+/gm, "")
);
assert.equal(css.code.replace(/^\s+/gm, ''), expectedCss.replace(/^\s+/gm, ''));
} catch (error) {
if (shouldUpdateExpected()) {
fs.writeFileSync(`${dir}/_expected.css`, css.code);
@ -104,10 +101,7 @@ describe("ssr", () => {
fs.writeFileSync(`${dir}/_actual-head.html`, head);
try {
assert.htmlEqual(
head,
fs.readFileSync(`${dir}/_expected-head.html`, 'utf-8')
);
assert.htmlEqual(head, fs.readFileSync(`${dir}/_expected-head.html`, 'utf-8'));
} catch (error) {
if (shouldUpdateExpected()) {
fs.writeFileSync(`${dir}/_expected-head.html`, head);
@ -128,20 +122,20 @@ describe("ssr", () => {
});
// duplicate client-side tests, as far as possible
fs.readdirSync("test/runtime/samples").forEach(dir => {
if (dir[0] === ".") return;
fs.readdirSync('test/runtime/samples').forEach((dir) => {
if (dir[0] === '.') return;
const config = loadConfig(`./runtime/samples/${dir}/_config.js`);
const solo = config.solo || /\.solo/.test(dir);
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;
(config.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();
@ -151,12 +145,12 @@ describe("ssr", () => {
sveltePath,
...config.compileOptions,
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;
const dir = `${cwd}/_output/ssr`;
@ -169,13 +163,10 @@ describe("ssr", () => {
mkdirp(dir);
try {
const { js } = compile(
fs.readFileSync(`${cwd}/${file}`, 'utf-8'),
{
const { js } = compile(fs.readFileSync(`${cwd}/${file}`, 'utf-8'), {
...compileOptions,
filename: file
}
);
filename: file,
});
fs.writeFileSync(out, js.code);
} catch (err) {
@ -188,7 +179,7 @@ describe("ssr", () => {
const Component = require(`../runtime/samples/${dir}/main.svelte`).default;
const { html } = Component.render(config.props, {
store: (config.store !== true) && config.store
store: config.store !== true && config.store,
});
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 assert from 'assert';
import { svelte, loadConfig, tryToLoadJson } from '../helpers.js';
import { svelte, loadConfig, tryToLoadJson } from '../helpers.ts';
import { assert } from '../test';
describe('stats', () => {
fs.readdirSync(`${__dirname}/samples`).forEach(dir => {
fs.readdirSync(`${__dirname}/samples`).forEach((dir) => {
if (dir[0] === '.') return;
// 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 input = fs.readFileSync(filename, 'utf-8').replace(/\s+$/, '');
const expectedError = tryToLoadJson(
`${__dirname}/samples/${dir}/error.json`
);
const expectedError = tryToLoadJson(`${__dirname}/samples/${dir}/error.json`);
let result;
let error;
@ -52,7 +50,7 @@ describe('stats', () => {
it('returns a stats object when options.generate is false', () => {
const { stats } = svelte.compile('', {
generate: false
generate: false,
});
assert.equal(typeof stats.timings.total, 'number');

@ -1,5 +1,5 @@
import * as assert from 'assert';
import { readable, writable, derived, get } from '../../store';
import { assert } from '../test';
describe('store', () => {
describe('writable', () => {
@ -7,17 +7,17 @@ describe('store', () => {
const count = writable(0);
const values = [];
const unsubscribe = count.subscribe(value => {
const unsubscribe = count.subscribe((value) => {
values.push(value);
});
count.set(1);
count.update(n => n + 1);
count.update((n) => n + 1);
unsubscribe();
count.set(3);
count.update(n => n + 1);
count.update((n) => n + 1);
assert.deepEqual(values, [0, 1, 2]);
});
@ -27,7 +27,7 @@ describe('store', () => {
const store = writable(0, () => {
called += 1;
return () => called -= 1;
return () => (called -= 1);
});
const unsubscribe1 = store.subscribe(() => {});
@ -56,21 +56,21 @@ describe('store', () => {
store.set(obj);
assert.equal(called, 2);
store.update(obj => obj);
store.update((obj) => obj);
assert.equal(called, 3);
});
it('only calls subscriber once initially, including on resubscriptions', () => {
let num = 0;
const store = writable(num, set => set(num += 1));
const store = writable(num, (set) => set((num += 1)));
let count1 = 0;
let count2 = 0;
store.subscribe(() => count1 += 1)();
store.subscribe(() => (count1 += 1))();
assert.equal(count1, 1);
const unsubscribe = store.subscribe(() => count2 += 1);
const unsubscribe = store.subscribe(() => (count2 += 1));
assert.equal(count2, 1);
unsubscribe();
@ -82,7 +82,7 @@ describe('store', () => {
let running;
let tick;
const store = readable(undefined, set => {
const store = readable(undefined, (set) => {
tick = set;
running = true;
@ -98,7 +98,7 @@ describe('store', () => {
const values = [];
const unsubscribe = store.subscribe(value => {
const unsubscribe = store.subscribe((value) => {
values.push(value);
});
@ -120,19 +120,19 @@ describe('store', () => {
subscribe(fn) {
fn(42);
return {
unsubscribe: () => {}
unsubscribe: () => {},
};
}
},
};
describe('derived', () => {
it('maps a single store', () => {
const a = writable(1);
const b = derived(a, n => n * 2);
const b = derived(a, (n) => n * 2);
const values = [];
const unsubscribe = b.subscribe(value => {
const unsubscribe = b.subscribe((value) => {
values.push(value);
});
@ -148,11 +148,11 @@ describe('store', () => {
it('maps multiple stores', () => {
const a = writable(2);
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 unsubscribe = c.subscribe(value => {
const unsubscribe = c.subscribe((value) => {
values.push(value);
});
@ -168,13 +168,17 @@ describe('store', () => {
it('passes optional set function', () => {
const number = writable(1);
const evens = derived(number, (n, set) => {
const evens = derived(
number,
(n, set) => {
if (n % 2 === 0) set(n);
}, 0);
},
0
);
const values = [];
const unsubscribe = evens.subscribe(value => {
const unsubscribe = evens.subscribe((value) => {
values.push(value);
});
@ -194,22 +198,19 @@ describe('store', () => {
it('prevents glitches', () => {
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 unsubscribe = fullname.subscribe(value => {
const unsubscribe = fullname.subscribe((value) => {
values.push(value);
});
lastname.set('Hyde');
assert.deepEqual(values, [
'Henry Jekyll',
'Edward Hyde'
]);
assert.deepEqual(values, ['Henry Jekyll', 'Edward Hyde']);
unsubscribe();
});
@ -218,11 +219,11 @@ describe('store', () => {
const count = writable(0);
const values = [];
const a = derived(count, $count => {
const a = derived(count, ($count) => {
return 'a' + $count;
});
const b = derived(count, $count => {
const b = derived(count, ($count) => {
return 'b' + $count;
});
@ -230,7 +231,7 @@ describe('store', () => {
return a + b;
});
const unsubscribe = combined.subscribe(v => {
const unsubscribe = combined.subscribe((v) => {
values.push(v);
});
@ -246,7 +247,7 @@ describe('store', () => {
const root = writable({ a: 0, b: 0 });
const values = [];
const a = derived(root, $root => {
const a = derived(root, ($root) => {
return 'a' + $root.a;
});
@ -254,7 +255,7 @@ describe('store', () => {
return 'b' + $root.b + $a;
});
const unsubscribe = b.subscribe(v => {
const unsubscribe = b.subscribe((v) => {
values.push(v);
});
@ -270,14 +271,14 @@ describe('store', () => {
const arr = [0];
const number = writable(1);
const numbers = derived(number, $number => {
const numbers = derived(number, ($number) => {
arr[0] = $number;
return arr;
});
const concatenated = [];
const unsubscribe = numbers.subscribe(value => {
const unsubscribe = numbers.subscribe((value) => {
concatenated.push(...value);
});
@ -305,7 +306,7 @@ describe('store', () => {
num.set(2);
const unsubscribe = d.subscribe(value => {
const unsubscribe = d.subscribe((value) => {
values.push(value);
});
@ -332,7 +333,7 @@ describe('store', () => {
num.set(2);
const unsubscribe = d.subscribe(value => {
const unsubscribe = d.subscribe((value) => {
values.push(value);
});
@ -357,7 +358,7 @@ describe('store', () => {
});
it('works with RxJS-style observables', () => {
const d = derived(fake_observable, _ => _);
const d = derived(fake_observable, (_) => _);
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,66 @@
import './ambient';
import * as assert$1 from 'assert';
import { readFileSync } from 'fs';
export const assert = (assert$1 as unknown) as typeof assert$1 & { htmlEqual: (actual, expected, message?) => void };
const { glob } = require('./tiny-glob.ts');
// require('./setup.ts');
// // bind internal to jsdom
// require('./helpers.ts');
// require('../internal');
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,269 @@
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,
rgx = strict ? STRICT : RELAXED;
while ((match = rgx.exec(str))) {
if (match[2]) return true;
let idx = match.index + match[0].length;
let open = match[1];
let 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);
let 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 = '';
let 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);
let 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;
}
let prevChar = glob[i - 1];
let starCount = 1;
while (glob[i + 1] === '*') {
starCount++;
i++;
}
let nextChar = glob[i + 1];
if (!globstar) add('.*');
else {
let 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,
len = files.length,
file;
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 [];
let glob = globalyzer(str);
if (!glob.isGlob) {
try {
let resolved = resolve(cwd, str);
let dirent = statSync(resolved);
if (filesOnly && !dirent.isFile()) return [];
return absolute ? [resolved] : [str];
} catch (err) {
if (err.code != 'ENOENT') throw err;
return [];
}
}
if (flush) CACHE = {};
let 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": ["."],
"compilerOptions": {
"lib": ["DOM", "es2020"],
"allowJs": true,
"checkJs": true,
"noEmit": true

@ -1,23 +1,23 @@
import * as fs from "fs";
import * as assert from "assert";
import { svelte, loadConfig, tryToLoadJson } from "../helpers.js";
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;
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");
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 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`);
@ -33,13 +33,16 @@ describe("validate", () => {
...options,
});
assert.deepEqual(warnings.map(w => ({
assert.deepEqual(
warnings.map((w) => ({
code: w.code,
message: w.message,
pos: w.pos,
start: w.start,
end: w.end
})), expected_warnings);
end: w.end,
})),
expected_warnings
);
} catch (e) {
error = e;
}
@ -69,34 +72,39 @@ describe("validate", () => {
});
});
it("errors if options.name is illegal", () => {
it('errors if options.name is illegal', () => {
assert.throws(() => {
svelte.compile("<div></div>", {
name: "not.valid",
generate: false
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
it('warns if options.name is not capitalised', () => {
const { warnings } = svelte.compile('<div></div>', {
name: 'lowercase',
generate: false,
});
assert.deepEqual(warnings.map(w => ({
assert.deepEqual(
warnings.map((w) => ({
code: w.code,
message: w.message
})), [{
message: w.message,
})),
[
{
code: `options-lowercase-name`,
message: "options.name should be capitalised"
}]);
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
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 assert from 'assert';
import { svelte, loadConfig, tryToLoadJson } from '../helpers.js';
import { svelte, loadConfig, tryToLoadJson } from '../helpers';
import { assert } from '../test';
describe('vars', () => {
fs.readdirSync(`${__dirname}/samples`).forEach(dir => {
fs.readdirSync(`${__dirname}/samples`).forEach((dir) => {
if (dir[0] === '.') return;
// 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 input = fs.readFileSync(filename, 'utf-8').replace(/\s+$/, '');
const expectedError = tryToLoadJson(
`${__dirname}/samples/${dir}/error.json`
);
const expectedError = tryToLoadJson(`${__dirname}/samples/${dir}/error.json`);
let result;
let error;

@ -2,13 +2,12 @@
"include": [],
"compilerOptions": {
"rootDir": "src",
"rootDirs": ["src", "test"],
// target node v8+ (https://node.green/)
// the only missing feature is Array.prototype.values
"lib": ["ESNext"],
"target": "ESNext",
"lib": ["ES2020", "DOM"],
"target": "ES2020",
"skipLibCheck": true,
"declaration": true,
"declarationDir": "types",
"strictBindCallApply": true,

Loading…
Cancel
Save