diff --git a/CHANGELOG.md b/CHANGELOG.md index bdee28d1dc..a3ce06485e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,13 +4,22 @@ * Fix `{#each}` context not shadowing outer scope when using `bind:` ([#1565](https://github.com/sveltejs/svelte/issues/1565)) * Fix edge cases in matching selectors against elements ([#1710](https://github.com/sveltejs/svelte/issues/1710)) +* Allow exiting a reactive block early with `break $` ([#2828](https://github.com/sveltejs/svelte/issues/2828)) +* Don't lose `class:` directive classes on an element with `{...spread}` attributes when updating ([#3421](https://github.com/sveltejs/svelte/issues/3421)) +* Check attributes have changed before setting them to avoid image flicker ([#3579](https://github.com/sveltejs/svelte/pull/3579)) +* Fix generating malformed code for `{@debug}` tags with no dependencies ([#3588](https://github.com/sveltejs/svelte/issue/3588)) +* Fix generated code in specific case involving compound ifs and child components ([#3595](https://github.com/sveltejs/svelte/issue/3595)) +* Fix `bind:this` binding to a store ([#3591](https://github.com/sveltejs/svelte/issue/3591)) * Use safer `HTMLElement` check before extending class ([#3608](https://github.com/sveltejs/svelte/issue/3608)) * Add `location` as a known global ([#3619](https://github.com/sveltejs/svelte/pull/3619)) +* Support `{#await}` with `{:catch}` but no `{:then}` ([#3623](https://github.com/sveltejs/svelte/issues/3623)) +* Clean up dead code emitted for ``s ([#3631](https://github.com/sveltejs/svelte/issues/3631)) * Fix tracking of dependencies of compound assignments in reactive statements ([#3634](https://github.com/sveltejs/svelte/issues/3634)) * Flush changes in newly attached block when using `{#await}` ([#3660](https://github.com/sveltejs/svelte/issues/3660)) * Throw exception immediately when calling `createEventDispatcher()` after component instantiation ([#3667](https://github.com/sveltejs/svelte/pull/3667)) * Fix globals shadowing contextual template scope ([#3674](https://github.com/sveltejs/svelte/issues/3674)) * Fix error resulting from trying to set a read-only property when spreading element attributes ([#3681](https://github.com/sveltejs/svelte/issues/3681)) +* Fix handling of boolean attributes in presence of other spread attributes ([#3764](https://github.com/sveltejs/svelte/issues/3764)) ## 3.12.1 diff --git a/package-lock.json b/package-lock.json index 99290966cc..1ca8b7cdb5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.13.0-alpha.0", + "version": "3.13.0-alpha.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -167,9 +167,9 @@ "dev": true }, "acorn": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.0.0.tgz", - "integrity": "sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", + "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==", "dev": true }, "acorn-globals": { @@ -490,14 +490,14 @@ "dev": true }, "code-red": { - "version": "0.0.17", - "resolved": "https://registry.npmjs.org/code-red/-/code-red-0.0.17.tgz", - "integrity": "sha512-RJJ48sXYOqyd0J4QelF4dRdYb+4DaLV/jHs4mNoxOdLroUGB840cMc9pMtEAbGKjFFzoTKREypFzqphBD8knMg==", + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/code-red/-/code-red-0.0.18.tgz", + "integrity": "sha512-g7W6RwRqBbQTtMaUqrNWDyyl2GK0Uulk/uZPzGdgTXpOGX/LA8bW67EKQLdQgpYfd6APhZVwoX2lrL7mnJOWkA==", "dev": true, "requires": { - "acorn": "^7.0.0", - "is-reference": "^1.1.3", - "periscopic": "^1.0.1", + "acorn": "^7.1.0", + "is-reference": "^1.1.4", + "periscopic": "^1.0.2", "sourcemap-codec": "^1.4.6" } }, @@ -1147,14 +1147,6 @@ "acorn": "^7.0.0", "acorn-jsx": "^5.0.2", "eslint-visitor-keys": "^1.1.0" - }, - "dependencies": { - "acorn": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.0.0.tgz", - "integrity": "sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ==", - "dev": true - } } }, "esprima": { @@ -1768,9 +1760,9 @@ "dev": true }, "is-reference": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.3.tgz", - "integrity": "sha512-W1iHHv/oyBb2pPxkBxtaewxa1BC58Pn5J0hogyCdefwUIvb6R+TGbAcIa4qPNYLqLhb3EnOgUf2MQkkF76BcKw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.4.tgz", + "integrity": "sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw==", "dev": true, "requires": { "@types/estree": "0.0.39" @@ -2727,17 +2719,6 @@ "dev": true, "requires": { "is-reference": "^1.1.4" - }, - "dependencies": { - "is-reference": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.4.tgz", - "integrity": "sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw==", - "dev": true, - "requires": { - "@types/estree": "0.0.39" - } - } } }, "pify": { @@ -3646,14 +3627,6 @@ "@types/istanbul-lib-coverage": "^2.0.1", "convert-source-map": "^1.6.0", "source-map": "^0.7.3" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - } } }, "validate-npm-package-license": { diff --git a/package.json b/package.json index c4f1269139..950c44d965 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.13.0-alpha.0", + "version": "3.13.0-alpha.1", "description": "Cybernetically enhanced web apps", "module": "index.mjs", "main": "index", @@ -60,17 +60,17 @@ "@types/node": "^8.10.53", "@typescript-eslint/eslint-plugin": "^1.13.0", "@typescript-eslint/parser": "^2.1.0", - "acorn": "^7.0.0", + "acorn": "^7.1.0", "agadoo": "^1.1.0", "c8": "^5.0.1", - "code-red": "0.0.17", + "code-red": "0.0.18", "codecov": "^3.5.0", "css-tree": "1.0.0-alpha22", "eslint": "^6.3.0", "eslint-plugin-import": "^2.18.2", "eslint-plugin-svelte3": "^2.7.3", "estree-walker": "^0.8.1", - "is-reference": "^1.1.3", + "is-reference": "^1.1.4", "jsdom": "^15.1.1", "kleur": "^3.0.3", "locate-character": "^2.0.5", diff --git a/site/content/docs/03-run-time.md b/site/content/docs/03-run-time.md index 551945eade..19a19c4f41 100644 --- a/site/content/docs/03-run-time.md +++ b/site/content/docs/03-run-time.md @@ -184,7 +184,7 @@ dispatch: ((name: string, detail?: any) => void) = createEventDispatcher(); --- -Creates an event dispatcher that can be used to dispatch [component events](docs#Component_events). Event dispatchers are functions that can take two arguments: `name` and `detail`. +Creates an event dispatcher that can be used to dispatch [component events](docs#on_component_event). Event dispatchers are functions that can take two arguments: `name` and `detail`. Component events created with `createEventDispatcher` create a [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent). These events do not [bubble](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#Event_bubbling_and_capture) and are not cancellable with `event.preventDefault()`. The `detail` argument corresponds to the [CustomEvent.detail](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/detail) property and can contain any type of data. @@ -1019,8 +1019,12 @@ Unlike client-side components, server-side components don't have a lifespan afte A server-side component exposes a `render` method that can be called with optional props. It returns an object with `head`, `html`, and `css` properties, where `head` contains the contents of any `` elements encountered. +You can import a Svelte component directly into Node using [`svelte/register`](docs#svelte_register). + ```js -const App = require('./App.svelte'); +require('svelte/register'); + +const App = require('./App.svelte').default; const { head, html, css } = App.render({ answer: 42 diff --git a/site/src/routes/_components/WhosUsingSvelte.svelte b/site/src/routes/_components/WhosUsingSvelte.svelte index fe9a2fcc9d..2a944fe289 100644 --- a/site/src/routes/_components/WhosUsingSvelte.svelte +++ b/site/src/routes/_components/WhosUsingSvelte.svelte @@ -55,6 +55,7 @@ Deck logo Dextra logo Entriwise logo + Entur logo From-Now-On logo FusionCharts logo GoDaddy logo diff --git a/site/src/server.js b/site/src/server.js index fb003961bc..c7cf93c3b5 100644 --- a/site/src/server.js +++ b/site/src/server.js @@ -15,7 +15,7 @@ const app = polka({ }); if (process.env.PGHOST) { - app.use(authenticate); + app.use(authenticate()); } app.use( @@ -34,4 +34,4 @@ app.use( }) ); -app.listen(PORT); \ No newline at end of file +app.listen(PORT); diff --git a/site/static/organisations/entur.svg b/site/static/organisations/entur.svg new file mode 100644 index 0000000000..98bb5cc937 --- /dev/null +++ b/site/static/organisations/entur.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index ee24fc700c..b22b351da0 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -90,15 +90,6 @@ export default class Component { file: string; locate: (c: number) => { line: number; column: number }; - // TODO this does the same as component.locate! remove one or the other - locator: ( - search: number, - startIndex?: number - ) => { - line: number; - column: number; - }; - stylesheet: Stylesheet; aliases: Map = new Map(); @@ -140,7 +131,7 @@ export default class Component { .replace(process.cwd(), '') .replace(/^[/\\]/, '') : compile_options.filename); - this.locate = getLocator(this.source); + this.locate = getLocator(this.source, { offsetLine: 1 }); // styles this.stylesheet = new Stylesheet( @@ -438,12 +429,8 @@ export default class Component { return; } - if (!this.locator) { - this.locator = getLocator(this.source, { offsetLine: 1 }); - } - - const start = this.locator(pos.start); - const end = this.locator(pos.end); + const start = this.locate(pos.start); + const end = this.locate(pos.end); const frame = get_code_frame(this.source, start.line - 1, start.column); @@ -456,7 +443,7 @@ export default class Component { pos: pos.start, filename: this.compile_options.filename, toString: () => - `${warning.message} (${start.line + 1}:${start.column})\n${frame}`, + `${warning.message} (${start.line}:${start.column})\n${frame}`, }); } @@ -582,6 +569,7 @@ export default class Component { this.add_var({ name, global: true, + hoistable: true }); } }); @@ -674,6 +662,7 @@ export default class Component { this.add_var({ name, global: true, + hoistable: true }); } }); @@ -772,12 +761,12 @@ export default class Component { invalidate(name, value?) { const variable = this.var_lookup.get(name); - if (variable && (variable.subscribable && variable.reassigned)) { + if (variable && (variable.subscribable && (variable.reassigned || variable.export_name))) { return x`${`$$subscribe_${name}`}($$invalidate('${name}', ${value || name}))`; } if (name[0] === '$' && name[1] !== '$') { - return x`${name.slice(1)}.set(${name})`; + return x`${name.slice(1)}.set(${value || name})`; } if ( diff --git a/src/compiler/compile/nodes/Attribute.ts b/src/compiler/compile/nodes/Attribute.ts index c09f9c3074..97d2fd7b2e 100644 --- a/src/compiler/compile/nodes/Attribute.ts +++ b/src/compiler/compile/nodes/Attribute.ts @@ -9,7 +9,7 @@ import TemplateScope from './shared/TemplateScope'; import { x } from 'code-red'; export default class Attribute extends Node { - type: 'Attribute'; + type: 'Attribute' | 'Spread'; start: number; end: number; scope: TemplateScope; diff --git a/src/compiler/compile/nodes/shared/Expression.ts b/src/compiler/compile/nodes/shared/Expression.ts index 4a3e96a282..ad4a1bc24d 100644 --- a/src/compiler/compile/nodes/shared/Expression.ts +++ b/src/compiler/compile/nodes/shared/Expression.ts @@ -3,7 +3,7 @@ import { walk } from 'estree-walker'; import is_reference from 'is-reference'; import flatten_reference from '../../utils/flatten_reference'; import { create_scopes, Scope, extract_names } from '../../utils/scope'; -import { globals, sanitize } from '../../../utils/names'; +import { sanitize } from '../../../utils/names'; import Wrapper from '../../render_dom/wrappers/shared/Wrapper'; import TemplateScope from './TemplateScope'; import get_object from '../../utils/get_object'; @@ -75,8 +75,6 @@ export default class Expression { if (scope.has(name)) return; - if (globals.has(name) && !(component.var_lookup.has(name) || template_scope.names.has(name))) return; - if (name[0] === '$' && template_scope.names.has(name.slice(1))) { component.error(node, { code: `contextual-store`, @@ -202,7 +200,6 @@ export default class Expression { const { name } = flatten_reference(node); if (scope.has(name)) return; - if (globals.has(name) && !(component.var_lookup.has(name) || template_scope.names.has(name))) return; if (function_expression) { if (template_scope.names.has(name)) { diff --git a/src/compiler/compile/render_dom/Block.ts b/src/compiler/compile/render_dom/Block.ts index f73212f3ba..b268954dd0 100644 --- a/src/compiler/compile/render_dom/Block.ts +++ b/src/compiler/compile/render_dom/Block.ts @@ -146,11 +146,13 @@ export default class Block { if (!wrapper.var) continue; + let suffix = ''; if (dupes.has(wrapper.var.name)) { const i = counts.get(wrapper.var.name) || 0; counts.set(wrapper.var.name, i + 1); - wrapper.var.name = this.get_unique_name(wrapper.var.name + i).name; + suffix = i; } + wrapper.var.name = this.get_unique_name(wrapper.var.name + suffix).name; } } diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index bd7b99bd8f..72f81cfb05 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -221,18 +221,15 @@ export default function dom( } }); - component.rewrite_props(({ name, reassigned }) => { + component.rewrite_props(({ name, reassigned, export_name }) => { const value = `$${name}`; + + const insert = (reassigned || export_name) + ? b`${`$$subscribe_${name}`}()` + : b`@component_subscribe($$self, ${name}, #value => $$invalidate('${value}', ${value} = #value))`; - if (reassigned) { - return b`${`$$subscribe_${name}`}()`; - } - - const callback = x`$$value => $$invalidate('${value}', ${value} = $$value)`; - - let insert = b`@component_subscribe($$self, ${name}, $${callback})`; if (component.compile_options.dev) { - insert = b`@validate_store(${name}, '${name}'); ${insert}`; + return b`@validate_store(${name}, '${name}'); ${insert}`; } return insert; @@ -304,14 +301,14 @@ export default function dom( return !variable || variable.hoistable; }) .map(({ name }) => b` - ${component.compile_options.dev && `@validate_store(${name.slice(1)}, '${name.slice(1)}');`} + ${component.compile_options.dev && b`@validate_store(${name.slice(1)}, '${name.slice(1)}');`} @component_subscribe($$self, ${name.slice(1)}, $$value => $$invalidate('${name}', ${name} = $$value)); `); const resubscribable_reactive_store_unsubscribers = reactive_stores .filter(store => { const variable = component.var_lookup.get(store.name.slice(1)); - return variable && variable.reassigned; + return variable && (variable.reassigned || variable.export_name); }) .map(({ name }) => b`$$self.$$.on_destroy.push(() => ${`$$unsubscribe_${name.slice(1)}`}());`); @@ -353,7 +350,7 @@ export default function dom( const name = $name.slice(1); const store = component.var_lookup.get(name); - if (store && store.reassigned) { + if (store && (store.reassigned || store.export_name)) { const unsubscribe = `$$unsubscribe_${name}`; const subscribe = `$$subscribe_${name}`; return b`let ${$name}, ${unsubscribe} = @noop, ${subscribe} = () => (${unsubscribe}(), ${unsubscribe} = @subscribe(${name}, $$value => $$invalidate('${$name}', ${$name} = $$value)), ${name})`; diff --git a/src/compiler/compile/render_dom/wrappers/DebugTag.ts b/src/compiler/compile/render_dom/wrappers/DebugTag.ts index 87e186f3b4..dc3a2f2857 100644 --- a/src/compiler/compile/render_dom/wrappers/DebugTag.ts +++ b/src/compiler/compile/render_dom/wrappers/DebugTag.ts @@ -30,7 +30,6 @@ export default class DebugTagWrapper extends Wrapper { const { var_lookup } = component; const start = component.locate(this.node.start + 1); - start.line += 1; const end = { line: start.line, column: start.column + 6 }; const loc = { start, end }; diff --git a/src/compiler/compile/render_dom/wrappers/EachBlock.ts b/src/compiler/compile/render_dom/wrappers/EachBlock.ts index 5c8d193f56..922679c39f 100644 --- a/src/compiler/compile/render_dom/wrappers/EachBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/EachBlock.ts @@ -108,7 +108,6 @@ export default class EachBlockWrapper extends Wrapper { let c = this.node.start + 2; while (renderer.component.source[c] !== 'e') c += 1; const start = renderer.component.locate(c); - start.line += 1; const end = { line: start.line, column: start.column + 4 }; const length = { type: 'Identifier', diff --git a/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts b/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts index 876be588d6..2cd284108c 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts @@ -44,9 +44,7 @@ export default class AttributeWrapper { const element = this.parent; const name = fix_attribute_casing(this.node.name); - let metadata = element.node.namespace ? null : attribute_lookup[name]; - if (metadata && metadata.applies_to && !~metadata.applies_to.indexOf(element.node.name)) - metadata = null; + const metadata = this.get_metadata(); const is_indirectly_bound_value = name === 'value' && @@ -95,7 +93,7 @@ export default class AttributeWrapper { const is_select_value_attribute = name === 'value' && element.node.name === 'select'; - const should_cache = (this.node.should_cache() || is_select_value_attribute); + const should_cache = is_select_value_attribute; // TODO is this necessary? const last = should_cache && block.get_unique_name( `${element.var.name}_${name.replace(/[^a-zA-Z_$]/g, '_')}_value` @@ -193,6 +191,13 @@ export default class AttributeWrapper { } } + get_metadata() { + if (this.parent.node.namespace) return null; + const metadata = attribute_lookup[fix_attribute_casing(this.node.name)]; + if (metadata && metadata.applies_to && !metadata.applies_to.includes(this.parent.node.name)) return null; + return metadata; + } + get_class_name_text() { const scoped_css = this.node.chunks.some((chunk: Text) => chunk.synthetic); const rendered = this.render_chunks(); diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 3f1b411966..8f54ebf468 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -354,7 +354,7 @@ export default class ElementWrapper extends Wrapper { if (renderer.options.dev) { const loc = renderer.locate(this.node.start); block.chunks.hydrate.push( - b`@add_location(${this.var}, ${renderer.file_var}, ${loc.line}, ${loc.column}, ${this.node.start});` + b`@add_location(${this.var}, ${renderer.file_var}, ${loc.line - 1}, ${loc.column}, ${this.node.start});` ); } } @@ -573,8 +573,7 @@ export default class ElementWrapper extends Wrapper { } }); - // @ts-ignore todo: - if (this.node.attributes.find(attr => attr.type === 'Spread')) { + if (this.node.attributes.some(attr => attr.is_spread)) { this.add_spread_attributes(block); return; } @@ -591,21 +590,24 @@ export default class ElementWrapper extends Wrapper { const initial_props = []; const updates = []; - this.node.attributes - .filter(attr => attr.type === 'Attribute' || attr.type === 'Spread') + this.attributes .forEach(attr => { - const condition = attr.dependencies.size > 0 - ? changed(Array.from(attr.dependencies)) + const condition = attr.node.dependencies.size > 0 + ? changed(Array.from(attr.node.dependencies)) : null; - if (attr.is_spread) { - const snippet = attr.expression.manipulate(block); + if (attr.node.is_spread) { + const snippet = attr.node.expression.manipulate(block); initial_props.push(snippet); updates.push(condition ? x`${condition} && ${snippet}` : snippet); } else { - const snippet = x`{ ${attr.name}: ${attr.get_value(block)} }`; + const metadata = attr.get_metadata(); + const snippet = x`{ ${ + (metadata && metadata.property_name) || + fix_attribute_casing(attr.node.name) + }: ${attr.node.get_value(block)} }`; initial_props.push(snippet); updates.push(condition ? x`${condition} && ${snippet}` : snippet); @@ -807,6 +809,7 @@ export default class ElementWrapper extends Wrapper { } add_classes(block: Block) { + const has_spread = this.node.attributes.some(attr => attr.is_spread); this.node.classes.forEach(class_directive => { const { expression, name } = class_directive; let snippet; @@ -822,7 +825,9 @@ export default class ElementWrapper extends Wrapper { block.chunks.hydrate.push(updater); - if ((dependencies && dependencies.size > 0) || this.class_dependencies.length) { + if (has_spread) { + block.chunks.update.push(updater); + } else if ((dependencies && dependencies.size > 0) || this.class_dependencies.length) { const all_dependencies = this.class_dependencies.concat(...dependencies); const condition = changed(all_dependencies); diff --git a/src/compiler/compile/render_dom/wrappers/IfBlock.ts b/src/compiler/compile/render_dom/wrappers/IfBlock.ts index c54994a36c..dff7851b5a 100644 --- a/src/compiler/compile/render_dom/wrappers/IfBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/IfBlock.ts @@ -271,8 +271,8 @@ export default class IfBlockWrapper extends Wrapper { ? b` ${snippet && ( dependencies.length > 0 - ? b`if ((${condition} == null) || ${changed(dependencies)}) ${condition} = !!(${snippet})` - : b`if (${condition} == null) ${condition} = !!(${snippet})` + ? b`if (${condition} == null || ${changed(dependencies)}) ${condition} = !!${snippet}` + : b`if (${condition} == null) ${condition} = !!${snippet}` )} if (${condition}) return ${block.name};` : b`return ${block.name};`)} @@ -388,7 +388,11 @@ export default class IfBlockWrapper extends Wrapper { function ${select_block_type}(#changed, #ctx) { ${this.branches.map(({ dependencies, condition, snippet }, i) => condition ? b` - ${snippet && b`if ((${condition} == null) || ${changed(dependencies)}) ${condition} = !!(${snippet})`} + ${snippet && ( + dependencies.length > 0 + ? b`if (${condition} == null || ${changed(dependencies)}) ${condition} = !!${snippet}` + : b`if (${condition} == null) ${condition} = !!${snippet}` + )} if (${condition}) return ${i};` : b`return ${i};`)} ${!has_else && b`return -1;`} diff --git a/src/compiler/compile/render_dom/wrappers/Slot.ts b/src/compiler/compile/render_dom/wrappers/Slot.ts index 1ab1b6abcb..fb90afab5b 100644 --- a/src/compiler/compile/render_dom/wrappers/Slot.ts +++ b/src/compiler/compile/render_dom/wrappers/Slot.ts @@ -137,12 +137,12 @@ export default class SlotWrapper extends Wrapper { block.render_listeners(`_${slot.name}`); block.event_listeners = listeners; - if (block.chunks.create) create.push(b`if (!${slot}) { ${block.chunks.create} }`); - if (block.chunks.claim) claim.push(b`if (!${slot}) { ${block.chunks.claim} }`); - if (block.chunks.hydrate) hydrate.push(b`if (!${slot}) { ${block.chunks.hydrate} }`); - if (block.chunks.mount) mount.push(b`if (!${slot}) { ${block.chunks.mount} }`); - if (block.chunks.update) update.push(b`if (!${slot}) { ${block.chunks.update} }`); - if (block.chunks.destroy) destroy.push(b`if (!${slot}) { ${block.chunks.destroy} }`); + if (block.chunks.create.length) create.push(b`if (!${slot}) { ${block.chunks.create} }`); + if (block.chunks.claim.length) claim.push(b`if (!${slot}) { ${block.chunks.claim} }`); + if (block.chunks.hydrate.length) hydrate.push(b`if (!${slot}) { ${block.chunks.hydrate} }`); + if (block.chunks.mount.length) mount.push(b`if (!${slot}) { ${block.chunks.mount} }`); + if (block.chunks.update.length) update.push(b`if (!${slot}) { ${block.chunks.update} }`); + if (block.chunks.destroy.length) destroy.push(b`if (!${slot}) { ${block.chunks.destroy} }`); block.chunks.create = create; block.chunks.claim = claim; @@ -155,9 +155,11 @@ export default class SlotWrapper extends Wrapper { b`if (${slot}) ${slot}.c();` ); - block.chunks.claim.push( - b`if (${slot}) ${slot}.l(${parent_nodes});` - ); + if (renderer.options.hydratable) { + block.chunks.claim.push( + b`if (${slot}) ${slot}.l(${parent_nodes});` + ); + } block.chunks.mount.push(b` if (${slot}) { diff --git a/src/compiler/compile/render_dom/wrappers/shared/create_debugging_comment.ts b/src/compiler/compile/render_dom/wrappers/shared/create_debugging_comment.ts index df1ae9ff8e..e2aa0c0b41 100644 --- a/src/compiler/compile/render_dom/wrappers/shared/create_debugging_comment.ts +++ b/src/compiler/compile/render_dom/wrappers/shared/create_debugging_comment.ts @@ -26,7 +26,7 @@ export default function create_debugging_comment( } const start = locate(c); - const loc = `(${start.line + 1}:${start.column})`; + const loc = `(${start.line}:${start.column})`; return `${loc} ${source.slice(c, d)}`.replace(/\s/g, ' '); } diff --git a/src/compiler/compile/render_ssr/handlers/DebugTag.ts b/src/compiler/compile/render_ssr/handlers/DebugTag.ts index 6955d1d1e6..99323a9c4d 100644 --- a/src/compiler/compile/render_ssr/handlers/DebugTag.ts +++ b/src/compiler/compile/render_ssr/handlers/DebugTag.ts @@ -12,5 +12,5 @@ export default function(node: DebugTag, renderer: Renderer, options: RenderOptio ${node.expressions.map(e => p`${e.node.name}`)} }`; - renderer.add_expression(x`@debug(${filename ? x`"${filename}"` : x`null`}, ${line}, ${column}, ${obj})`); + renderer.add_expression(x`@debug(${filename ? x`"${filename}"` : x`null`}, ${line - 1}, ${column}, ${obj})`); } diff --git a/src/compiler/compile/render_ssr/handlers/Element.ts b/src/compiler/compile/render_ssr/handlers/Element.ts index 762152f4d5..1f7c0b7b9f 100644 --- a/src/compiler/compile/render_ssr/handlers/Element.ts +++ b/src/compiler/compile/render_ssr/handlers/Element.ts @@ -1,5 +1,4 @@ import { is_void } from '../../../utils/names'; -import Attribute from '../../nodes/Attribute'; import Class from '../../nodes/Class'; import { get_attribute_value, get_class_attribute_value } from './shared/get_attribute_value'; import { get_slot_scope } from './shared/get_slot_scope'; @@ -80,62 +79,61 @@ export default function(node: Element, renderer: Renderer, options: RenderOption let add_class_attribute = class_expression ? true : false; - if (node.attributes.find(attr => attr.is_spread)) { + if (node.attributes.some(attr => attr.is_spread)) { // TODO dry this out const args = []; node.attributes.forEach(attribute => { if (attribute.is_spread) { args.push(attribute.expression.node); } else { - if (attribute.name === 'value' && node.name === 'textarea') { + const name = attribute.name.toLowerCase(); + if (name === 'value' && node.name.toLowerCase() === 'textarea') { node_contents = get_attribute_value(attribute); } else if (attribute.is_true) { args.push(x`{ ${attribute.name}: true }`); } else if ( - boolean_attributes.has(attribute.name) && + boolean_attributes.has(name) && attribute.chunks.length === 1 && attribute.chunks[0].type !== 'Text' ) { // a boolean attribute with one non-Text chunk - args.push(x`{ ${attribute.name}: ${(attribute.chunks[0] as Expression).node} }`); - } else if (attribute.name === 'class' && class_expression) { + args.push(x`{ ${attribute.name}: ${(attribute.chunks[0] as Expression).node} || null }`); + } else if (name === 'class' && class_expression) { // Add class expression args.push(x`{ ${attribute.name}: [${get_class_attribute_value(attribute)}, ${class_expression}].join(' ').trim() }`); } else { - args.push(x`{ ${attribute.name}: ${attribute.name === 'class' ? get_class_attribute_value(attribute) : get_attribute_value(attribute)} }`); + args.push(x`{ ${attribute.name}: ${(name === 'class' ? get_class_attribute_value : get_attribute_value)(attribute)} }`); } } }); renderer.add_expression(x`@spread([${args}])`); } else { - node.attributes.forEach((attribute: Attribute) => { - if (attribute.type !== 'Attribute') return; - - if (attribute.name === 'value' && node.name === 'textarea') { + 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(attribute.name) && + 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}" : ""`); - } else if (attribute.name === 'class' && class_expression) { + } else if (name === 'class' && class_expression) { add_class_attribute = false; - renderer.add_string(` class="`); + renderer.add_string(` ${attribute.name}="`); renderer.add_expression(x`[${get_class_attribute_value(attribute)}, ${class_expression}].join(' ').trim()`); renderer.add_string(`"`); } else if (attribute.chunks.length === 1 && attribute.chunks[0].type !== 'Text') { - const { name } = attribute; const snippet = (attribute.chunks[0] as Expression).node; - renderer.add_expression(x`@add_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(attribute.name === 'class' ? get_class_attribute_value(attribute) : get_attribute_value(attribute)); + renderer.add_expression((name === 'class' ? get_class_attribute_value : get_attribute_value)(attribute)); renderer.add_string(`"`); } }); diff --git a/src/compiler/compile/render_ssr/index.ts b/src/compiler/compile/render_ssr/index.ts index d193508a89..00157cc256 100644 --- a/src/compiler/compile/render_ssr/index.ts +++ b/src/compiler/compile/render_ssr/index.ts @@ -98,6 +98,8 @@ export default function ssr( : b` let ${left} = ${right}`; } + } else { // TODO do not add label if it's not referenced + statement = b`$: { ${statement} }`; } return statement; diff --git a/src/compiler/parse/acorn.ts b/src/compiler/parse/acorn.ts index a7d2703956..30ab3c398c 100644 --- a/src/compiler/parse/acorn.ts +++ b/src/compiler/parse/acorn.ts @@ -4,13 +4,11 @@ const Parser = acorn.Parser; export const parse = (source: string) => Parser.parse(source, { sourceType: 'module', - // @ts-ignore TODO pending release of fixed types ecmaVersion: 11, locations: true }); export const parse_expression_at = (source: string, index: number) => Parser.parseExpressionAt(source, index, { - // @ts-ignore TODO pending release of fixed types ecmaVersion: 11, locations: true }); \ No newline at end of file diff --git a/src/compiler/parse/read/script.ts b/src/compiler/parse/read/script.ts index efc9306677..0c416be6e8 100644 --- a/src/compiler/parse/read/script.ts +++ b/src/compiler/parse/read/script.ts @@ -45,7 +45,7 @@ export default function read_script(parser: Parser, start: number, attributes: N let ast: Program; try { - ast = acorn.parse(source); + ast = acorn.parse(source) as any as Program; } catch (err) { parser.acorn_error(err); } diff --git a/src/compiler/parse/state/mustache.ts b/src/compiler/parse/state/mustache.ts index 4a1578ada5..140e722b10 100644 --- a/src/compiler/parse/state/mustache.ts +++ b/src/compiler/parse/state/mustache.ts @@ -160,57 +160,47 @@ export default function mustache(parser: Parser) { parser.stack.push(block.else); } - } else if (parser.eat(':then')) { - // TODO DRY out this and the next section - const pending_block = parser.current(); - if (pending_block.type === 'PendingBlock') { - pending_block.end = start; - parser.stack.pop(); - const await_block = parser.current(); + } else if (parser.match(':then') || parser.match(':catch')) { + const block = parser.current(); + const is_then = parser.eat(':then') || !parser.eat(':catch'); - if (!parser.eat('}')) { - parser.require_whitespace(); - await_block.value = parser.read_identifier(); - parser.allow_whitespace(); - parser.eat('}', true); + if (is_then) { + if (block.type !== 'PendingBlock') { + parser.error({ + code: `invalid-then-placement`, + message: 'Cannot have an {:then} block outside an {#await ...} block' + }); } - - const then_block: TemplateNode = { - start, - end: null, - type: 'ThenBlock', - children: [], - skip: false - }; - - await_block.then = then_block; - parser.stack.push(then_block); - } - } else if (parser.eat(':catch')) { - const then_block = parser.current(); - if (then_block.type === 'ThenBlock') { - then_block.end = start; - parser.stack.pop(); - const await_block = parser.current(); - - if (!parser.eat('}')) { - parser.require_whitespace(); - await_block.error = parser.read_identifier(); - parser.allow_whitespace(); - parser.eat('}', true); + } else { + if (block.type !== 'ThenBlock' && block.type !== 'PendingBlock') { + parser.error({ + code: `invalid-catch-placement`, + message: 'Cannot have an {:catch} block outside an {#await ...} block' + }); } + } - const catch_block: TemplateNode = { - start, - end: null, - type: 'CatchBlock', - children: [], - skip: false - }; + block.end = start; + parser.stack.pop(); + const await_block = parser.current(); - await_block.catch = catch_block; - parser.stack.push(catch_block); + if (!parser.eat('}')) { + parser.require_whitespace(); + await_block[is_then ? 'value': 'error'] = parser.read_identifier(); + parser.allow_whitespace(); + parser.eat('}', true); } + + const new_block: TemplateNode = { + start, + end: null, + type: is_then ? 'ThenBlock': 'CatchBlock', + children: [], + skip: false + }; + + await_block[is_then ? 'then' : 'catch'] = new_block; + parser.stack.push(new_block); } else if (parser.eat('#')) { // {#if foo}, {#each foo} or {#await foo} let type; diff --git a/src/compiler/parse/state/tag.ts b/src/compiler/parse/state/tag.ts index 880c866ffe..c7e761afc2 100644 --- a/src/compiler/parse/state/tag.ts +++ b/src/compiler/parse/state/tag.ts @@ -81,7 +81,7 @@ export default function tag(parser: Parser) { parser.current().children.length ) { parser.error({ - code: `invalid-${name.slice(7)}-content`, + code: `invalid-${slug}-content`, message: `<${name}> cannot have children` }, parser.current().children[0].start); } diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index 1c904fe2a3..c60f437863 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -86,14 +86,16 @@ export function self(fn) { export function attr(node: Element, attribute: string, value?: string) { if (value == null) node.removeAttribute(attribute); - else node.setAttribute(attribute, value); + else if (node.getAttribute(attribute) !== value) node.setAttribute(attribute, value); } export function set_attributes(node: Element & ElementCSSInlineStyle, attributes: { [x: string]: string }) { // @ts-ignore const descriptors = Object.getOwnPropertyDescriptors(node.__proto__); for (const key in attributes) { - if (key === 'style') { + if (attributes[key] == null) { + node.removeAttribute(key); + } else if (key === 'style') { node.style.cssText = attributes[key]; } else if (descriptors[key] && descriptors[key].set) { node[key] = attributes[key]; diff --git a/src/runtime/internal/ssr.ts b/src/runtime/internal/ssr.ts index d8fbf15f0a..208a4637c7 100644 --- a/src/runtime/internal/ssr.ts +++ b/src/runtime/internal/ssr.ts @@ -13,7 +13,7 @@ export function spread(args) { if (invalid_attribute_name_character.test(name)) return; const value = attributes[name]; - if (value === undefined) return; + if (value == null) return; if (value === true) str += " " + name; const escaped = String(value) diff --git a/src/runtime/store/index.ts b/src/runtime/store/index.ts index 0aff706a1b..64a63d4179 100644 --- a/src/runtime/store/index.ts +++ b/src/runtime/store/index.ts @@ -122,16 +122,30 @@ type StoresValues = T extends Readable ? U : /** * Derived value store by synchronizing one or more readable stores and * applying an aggregation function over its input values. - * @param {Stores} stores input stores - * @param {function(Stores=, function(*)=):*}fn function callback that aggregates the values - * @param {*=}initial_value when used asynchronously + * + * @param stores - input stores + * @param fn - function callback that aggregates the values */ -export function derived( +export function derived( stores: S, - fn: (values: StoresValues, set: Subscriber) => T | Unsubscriber | void, - initial_value?: T, -): Readable { + fn: (values: StoresValues) => T +): Readable; +/** + * Derived value store by synchronizing one or more readable stores and + * applying an aggregation function over its input values. + * + * @param stores - input stores + * @param fn - function callback that aggregates the values + * @param initial_value - when used asynchronously + */ +export function derived( + stores: S, + fn: (values: StoresValues, set: (value: T) => void) => Unsubscriber | void, + initial_value?: T +): Readable; + +export function derived(stores: Stores, fn: Function, initial_value?: T): Readable { const single = !Array.isArray(stores); const stores_array: Array> = single ? [stores as Readable] @@ -141,7 +155,7 @@ export function derived( return readable(initial_value, (set) => { let inited = false; - const values: StoresValues = [] as StoresValues; + const values = []; let pending = 0; let cleanup = noop; diff --git a/test/css/index.js b/test/css/index.js index 61e8a0c6cb..1f90d243be 100644 --- a/test/css/index.js +++ b/test/css/index.js @@ -37,7 +37,7 @@ function create(code) { } describe('css', () => { - fs.readdirSync('test/css/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 @@ -51,7 +51,7 @@ describe('css', () => { (solo ? it.only : skip ? it.skip : it)(dir, () => { const config = try_require(`./samples/${dir}/_config.js`) || {}; const input = fs - .readFileSync(`test/css/samples/${dir}/input.svelte`, 'utf-8') + .readFileSync(`${__dirname}/samples/${dir}/input.svelte`, 'utf-8') .replace(/\s+$/, ''); const expected_warnings = (config.warnings || []).map(normalize_warning); @@ -74,10 +74,10 @@ describe('css', () => { assert.deepEqual(dom_warnings, ssr_warnings); assert.deepEqual(dom_warnings.map(normalize_warning), expected_warnings); - fs.writeFileSync(`test/css/samples/${dir}/_actual.css`, dom.css.code); + fs.writeFileSync(`${__dirname}/samples/${dir}/_actual.css`, dom.css.code); const expected = { - html: read(`test/css/samples/${dir}/expected.html`), - css: read(`test/css/samples/${dir}/expected.css`) + html: read(`${__dirname}/samples/${dir}/expected.html`), + css: read(`${__dirname}/samples/${dir}/expected.css`) }; assert.equal(dom.css.code.replace(/svelte(-ref)?-[a-z0-9]+/g, (m, $1) => $1 ? m : 'svelte-xyz'), expected.css); @@ -112,7 +112,7 @@ describe('css', () => { new ClientComponent({ target, props: config.props }); const html = target.innerHTML; - fs.writeFileSync(`test/css/samples/${dir}/_actual.html`, html); + fs.writeFileSync(`${__dirname}/samples/${dir}/_actual.html`, html); assert.equal( normalizeHtml(window, html.replace(/svelte(-ref)?-[a-z0-9]+/g, (m, $1) => $1 ? m : 'svelte-xyz')), diff --git a/test/custom-elements/index.js b/test/custom-elements/index.js index 7fe47f2ecd..5d2847bfab 100644 --- a/test/custom-elements/index.js +++ b/test/custom-elements/index.js @@ -14,7 +14,7 @@ const page = ` `; -const assert = fs.readFileSync('test/custom-elements/assert.js', 'utf-8'); +const assert = fs.readFileSync(`${__dirname}/assert.js`, 'utf-8'); describe('custom-elements', function() { this.timeout(10000); @@ -53,7 +53,7 @@ describe('custom-elements', function() { await browser.close(); }); - fs.readdirSync('test/custom-elements/samples').forEach(dir => { + fs.readdirSync(`${__dirname}/samples`).forEach(dir => { if (dir[0] === '.') return; const solo = /\.solo$/.test(dir); @@ -67,7 +67,7 @@ describe('custom-elements', function() { const expected_warnings = config.warnings || []; const bundle = await rollup({ - input: `test/custom-elements/samples/${dir}/test.js`, + input: `${__dirname}/samples/${dir}/test.js`, plugins: [ { resolveId(importee) { diff --git a/test/hydration/index.js b/test/hydration/index.js index 856052d69e..fda799ef2d 100644 --- a/test/hydration/index.js +++ b/test/hydration/index.js @@ -47,7 +47,7 @@ describe('hydration', () => { } (config.skip ? it.skip : config.solo ? it.only : it)(dir, () => { - const cwd = path.resolve(`test/hydration/samples/${dir}`); + const cwd = path.resolve(`${__dirname}/samples/${dir}`); compileOptions = config.compileOptions || {}; @@ -96,7 +96,7 @@ describe('hydration', () => { }); } - fs.readdirSync('test/hydration/samples').forEach(dir => { + fs.readdirSync(`${__dirname}/samples`).forEach(dir => { runTest(dir, null); }); }); diff --git a/test/js/index.js b/test/js/index.js index 14d73d6c65..5fd632d606 100644 --- a/test/js/index.js +++ b/test/js/index.js @@ -4,7 +4,7 @@ import * as path from "path"; import { loadConfig, svelte } from "../helpers.js"; describe("js", () => { - fs.readdirSync("test/js/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 @@ -15,7 +15,7 @@ describe("js", () => { } (solo ? it.only : it)(dir, () => { - dir = path.resolve("test/js/samples", dir); + dir = path.resolve(`${__dirname}/samples`, dir); const config = loadConfig(`${dir}/_config.js`); const input = fs.readFileSync(`${dir}/input.svelte`, "utf-8").replace(/\s+$/, ""); diff --git a/test/js/samples/component-store-access-invalidate/expected.js b/test/js/samples/component-store-access-invalidate/expected.js index 029c0a6d21..f9f7b453e6 100644 --- a/test/js/samples/component-store-access-invalidate/expected.js +++ b/test/js/samples/component-store-access-invalidate/expected.js @@ -41,7 +41,7 @@ function create_fragment(ctx) { function instance($$self, $$props, $$invalidate) { let $foo; const foo = writable(0); - component_subscribe($$self, foo, $$value => $$invalidate("$foo", $foo = $$value)); + component_subscribe($$self, foo, value => $$invalidate("$foo", $foo = value)); return { foo, $foo }; } diff --git a/test/js/samples/debug-no-dependencies/expected.js b/test/js/samples/debug-no-dependencies/expected.js index 2212f3e0bb..57a67e9ea9 100644 --- a/test/js/samples/debug-no-dependencies/expected.js +++ b/test/js/samples/debug-no-dependencies/expected.js @@ -60,7 +60,7 @@ function create_each_block(ctx) { } function create_fragment(ctx) { - let each_anchor; + let each_1_anchor; let each_value = things; let each_blocks = []; @@ -74,7 +74,7 @@ function create_fragment(ctx) { each_blocks[i].c(); } - each_anchor = empty(); + each_1_anchor = empty(); }, l: function claim(nodes) { throw new Error("options.hydrate only works if the component was compiled with the `hydratable: true` option"); @@ -84,7 +84,7 @@ function create_fragment(ctx) { each_blocks[i].m(target, anchor); } - insert_dev(target, each_anchor, anchor); + insert_dev(target, each_1_anchor, anchor); }, p: function update(changed, ctx) { if (changed.things) { @@ -99,7 +99,7 @@ function create_fragment(ctx) { } else { each_blocks[i] = create_each_block(child_ctx); each_blocks[i].c(); - each_blocks[i].m(each_anchor.parentNode, each_anchor); + each_blocks[i].m(each_1_anchor.parentNode, each_1_anchor); } } @@ -114,7 +114,7 @@ function create_fragment(ctx) { o: noop, d: function destroy(detaching) { destroy_each(each_blocks, detaching); - if (detaching) detach_dev(each_anchor); + if (detaching) detach_dev(each_1_anchor); } }; diff --git a/test/js/samples/deconflict-builtins/expected.js b/test/js/samples/deconflict-builtins/expected.js index 222d473201..194188ad4e 100644 --- a/test/js/samples/deconflict-builtins/expected.js +++ b/test/js/samples/deconflict-builtins/expected.js @@ -43,7 +43,7 @@ function create_each_block(ctx) { } function create_fragment(ctx) { - let each_anchor; + let each_1_anchor; let each_value = ctx.createElement; let each_blocks = []; @@ -57,14 +57,14 @@ function create_fragment(ctx) { each_blocks[i].c(); } - each_anchor = empty(); + each_1_anchor = empty(); }, m(target, anchor) { for (let i = 0; i < each_blocks.length; i += 1) { each_blocks[i].m(target, anchor); } - insert(target, each_anchor, anchor); + insert(target, each_1_anchor, anchor); }, p(changed, ctx) { if (changed.createElement) { @@ -79,7 +79,7 @@ function create_fragment(ctx) { } else { each_blocks[i] = create_each_block(child_ctx); each_blocks[i].c(); - each_blocks[i].m(each_anchor.parentNode, each_anchor); + each_blocks[i].m(each_1_anchor.parentNode, each_1_anchor); } } @@ -94,7 +94,7 @@ function create_fragment(ctx) { o: noop, d(detaching) { destroy_each(each_blocks, detaching); - if (detaching) detach(each_anchor); + if (detaching) detach(each_1_anchor); } }; } diff --git a/test/js/samples/each-block-array-literal/expected.js b/test/js/samples/each-block-array-literal/expected.js index 2eb1d903de..cd8e8b97c2 100644 --- a/test/js/samples/each-block-array-literal/expected.js +++ b/test/js/samples/each-block-array-literal/expected.js @@ -43,7 +43,7 @@ function create_each_block(ctx) { } function create_fragment(ctx) { - let each_anchor; + let each_1_anchor; let each_value = [ctx.a, ctx.b, ctx.c, ctx.d, ctx.e]; let each_blocks = []; @@ -57,14 +57,14 @@ function create_fragment(ctx) { each_blocks[i].c(); } - each_anchor = empty(); + each_1_anchor = empty(); }, m(target, anchor) { for (let i = 0; i < 5; i += 1) { each_blocks[i].m(target, anchor); } - insert(target, each_anchor, anchor); + insert(target, each_1_anchor, anchor); }, p(changed, ctx) { if (changed.a || changed.b || changed.c || changed.d || changed.e) { @@ -79,7 +79,7 @@ function create_fragment(ctx) { } else { each_blocks[i] = create_each_block(child_ctx); each_blocks[i].c(); - each_blocks[i].m(each_anchor.parentNode, each_anchor); + each_blocks[i].m(each_1_anchor.parentNode, each_1_anchor); } } @@ -92,7 +92,7 @@ function create_fragment(ctx) { o: noop, d(detaching) { destroy_each(each_blocks, detaching); - if (detaching) detach(each_anchor); + if (detaching) detach(each_1_anchor); } }; } diff --git a/test/js/samples/each-block-keyed-animated/expected.js b/test/js/samples/each-block-keyed-animated/expected.js index e2e8357805..02022d7d67 100644 --- a/test/js/samples/each-block-keyed-animated/expected.js +++ b/test/js/samples/each-block-keyed-animated/expected.js @@ -63,15 +63,15 @@ function create_each_block(key_1, ctx) { function create_fragment(ctx) { let each_blocks = []; - let each_lookup = new Map(); - let each_anchor; + let each_1_lookup = new Map(); + let each_1_anchor; let each_value = ctx.things; const get_key = ctx => ctx.thing.id; for (let i = 0; i < each_value.length; i += 1) { let child_ctx = get_each_context(ctx, each_value, i); let key = get_key(child_ctx); - each_lookup.set(key, each_blocks[i] = create_each_block(key, child_ctx)); + each_1_lookup.set(key, each_blocks[i] = create_each_block(key, child_ctx)); } return { @@ -80,19 +80,19 @@ function create_fragment(ctx) { each_blocks[i].c(); } - each_anchor = empty(); + each_1_anchor = empty(); }, m(target, anchor) { for (let i = 0; i < each_blocks.length; i += 1) { each_blocks[i].m(target, anchor); } - insert(target, each_anchor, anchor); + insert(target, each_1_anchor, anchor); }, p(changed, ctx) { const each_value = ctx.things; for (let i = 0; i < each_blocks.length; i += 1) each_blocks[i].r(); - each_blocks = update_keyed_each(each_blocks, changed, get_key, 1, ctx, each_value, each_lookup, each_anchor.parentNode, fix_and_destroy_block, create_each_block, each_anchor, get_each_context); + each_blocks = update_keyed_each(each_blocks, changed, get_key, 1, ctx, each_value, each_1_lookup, each_1_anchor.parentNode, fix_and_destroy_block, create_each_block, each_1_anchor, get_each_context); for (let i = 0; i < each_blocks.length; i += 1) each_blocks[i].a(); }, i: noop, @@ -102,7 +102,7 @@ function create_fragment(ctx) { each_blocks[i].d(detaching); } - if (detaching) detach(each_anchor); + if (detaching) detach(each_1_anchor); } }; } diff --git a/test/js/samples/each-block-keyed/expected.js b/test/js/samples/each-block-keyed/expected.js index 5e149826b8..050c499e01 100644 --- a/test/js/samples/each-block-keyed/expected.js +++ b/test/js/samples/each-block-keyed/expected.js @@ -48,15 +48,15 @@ function create_each_block(key_1, ctx) { function create_fragment(ctx) { let each_blocks = []; - let each_lookup = new Map(); - let each_anchor; + let each_1_lookup = new Map(); + let each_1_anchor; let each_value = ctx.things; const get_key = ctx => ctx.thing.id; for (let i = 0; i < each_value.length; i += 1) { let child_ctx = get_each_context(ctx, each_value, i); let key = get_key(child_ctx); - each_lookup.set(key, each_blocks[i] = create_each_block(key, child_ctx)); + each_1_lookup.set(key, each_blocks[i] = create_each_block(key, child_ctx)); } return { @@ -65,18 +65,18 @@ function create_fragment(ctx) { each_blocks[i].c(); } - each_anchor = empty(); + each_1_anchor = empty(); }, m(target, anchor) { for (let i = 0; i < each_blocks.length; i += 1) { each_blocks[i].m(target, anchor); } - insert(target, each_anchor, anchor); + insert(target, each_1_anchor, anchor); }, p(changed, ctx) { const each_value = ctx.things; - each_blocks = update_keyed_each(each_blocks, changed, get_key, 1, ctx, each_value, each_lookup, each_anchor.parentNode, destroy_block, create_each_block, each_anchor, get_each_context); + each_blocks = update_keyed_each(each_blocks, changed, get_key, 1, ctx, each_value, each_1_lookup, each_1_anchor.parentNode, destroy_block, create_each_block, each_1_anchor, get_each_context); }, i: noop, o: noop, @@ -85,7 +85,7 @@ function create_fragment(ctx) { each_blocks[i].d(detaching); } - if (detaching) detach(each_anchor); + if (detaching) detach(each_1_anchor); } }; } diff --git a/test/js/samples/inline-style-unoptimized/expected.js b/test/js/samples/inline-style-unoptimized/expected.js index d1a36c12ca..74ef64639f 100644 --- a/test/js/samples/inline-style-unoptimized/expected.js +++ b/test/js/samples/inline-style-unoptimized/expected.js @@ -14,7 +14,6 @@ function create_fragment(ctx) { let div0; let t; let div1; - let div1_style_value; return { c() { @@ -22,7 +21,7 @@ function create_fragment(ctx) { t = space(); div1 = element("div"); attr(div0, "style", ctx.style); - attr(div1, "style", div1_style_value = "" + (ctx.key + ": " + ctx.value)); + attr(div1, "style", "" + (ctx.key + ": " + ctx.value)); }, m(target, anchor) { insert(target, div0, anchor); @@ -34,8 +33,8 @@ function create_fragment(ctx) { attr(div0, "style", ctx.style); } - if ((changed.key || changed.value) && div1_style_value !== (div1_style_value = "" + (ctx.key + ": " + ctx.value))) { - attr(div1, "style", div1_style_value); + if (changed.key || changed.value) { + attr(div1, "style", "" + (ctx.key + ": " + ctx.value)); } }, i: noop, diff --git a/test/parser/index.js b/test/parser/index.js index 990a8751ef..0188fac431 100644 --- a/test/parser/index.js +++ b/test/parser/index.js @@ -3,7 +3,7 @@ import * as fs from 'fs'; import { svelte, tryToLoadJson } from '../helpers.js'; describe('parse', () => { - fs.readdirSync('test/parser/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 @@ -16,18 +16,18 @@ describe('parse', () => { } (solo ? it.only : it)(dir, () => { - const options = tryToLoadJson(`test/parser/samples/${dir}/options.json`) || {}; + const options = tryToLoadJson(`${__dirname}/samples/${dir}/options.json`) || {}; - const input = fs.readFileSync(`test/parser/samples/${dir}/input.svelte`, 'utf-8').replace(/\s+$/, ''); - const expectedOutput = tryToLoadJson(`test/parser/samples/${dir}/output.json`); - const expectedError = tryToLoadJson(`test/parser/samples/${dir}/error.json`); + const input = fs.readFileSync(`${__dirname}/samples/${dir}/input.svelte`, 'utf-8').replace(/\s+$/, ''); + const expectedOutput = tryToLoadJson(`${__dirname}/samples/${dir}/output.json`); + const expectedError = tryToLoadJson(`${__dirname}/samples/${dir}/error.json`); try { const { ast } = svelte.compile(input, Object.assign(options, { generate: false })); - fs.writeFileSync(`test/parser/samples/${dir}/_actual.json`, JSON.stringify(ast, null, '\t')); + fs.writeFileSync(`${__dirname}/samples/${dir}/_actual.json`, JSON.stringify(ast, null, '\t')); assert.deepEqual(ast.html, expectedOutput.html); assert.deepEqual(ast.css, expectedOutput.css); diff --git a/test/parser/samples/await-catch/input.svelte b/test/parser/samples/await-catch/input.svelte new file mode 100644 index 0000000000..207bacb5b1 --- /dev/null +++ b/test/parser/samples/await-catch/input.svelte @@ -0,0 +1,5 @@ +{#await thePromise} +

loading...

+{:catch theError} +

oh no! {theError.message}

+{/await} \ No newline at end of file diff --git a/test/parser/samples/await-catch/output.json b/test/parser/samples/await-catch/output.json new file mode 100644 index 0000000000..2461f467f2 --- /dev/null +++ b/test/parser/samples/await-catch/output.json @@ -0,0 +1,168 @@ +{ + "html": { + "start": 0, + "end": 99, + "type": "Fragment", + "children": [ + { + "start": 0, + "end": 99, + "type": "AwaitBlock", + "expression": { + "type": "Identifier", + "start": 8, + "end": 18, + "loc": { + "start": { + "line": 1, + "column": 8 + }, + "end": { + "line": 1, + "column": 18 + } + }, + "name": "thePromise" + }, + "value": null, + "error": "theError", + "pending": { + "start": 19, + "end": 39, + "type": "PendingBlock", + "children": [ + { + "start": 19, + "end": 21, + "type": "Text", + "raw": "\n\t", + "data": "\n\t" + }, + { + "start": 21, + "end": 38, + "type": "Element", + "name": "p", + "attributes": [], + "children": [ + { + "start": 24, + "end": 34, + "type": "Text", + "raw": "loading...", + "data": "loading..." + } + ] + }, + { + "start": 38, + "end": 39, + "type": "Text", + "raw": "\n", + "data": "\n" + } + ], + "skip": false + }, + "then": { + "start": null, + "end": null, + "type": "ThenBlock", + "children": [], + "skip": true + }, + "catch": { + "start": 39, + "end": 91, + "type": "CatchBlock", + "children": [ + { + "start": 56, + "end": 58, + "type": "Text", + "raw": "\n\t", + "data": "\n\t" + }, + { + "start": 58, + "end": 90, + "type": "Element", + "name": "p", + "attributes": [], + "children": [ + { + "start": 61, + "end": 68, + "type": "Text", + "raw": "oh no! ", + "data": "oh no! " + }, + { + "start": 68, + "end": 86, + "type": "MustacheTag", + "expression": { + "type": "MemberExpression", + "start": 69, + "end": 85, + "loc": { + "start": { + "line": 4, + "column": 12 + }, + "end": { + "line": 4, + "column": 28 + } + }, + "object": { + "type": "Identifier", + "start": 69, + "end": 77, + "loc": { + "start": { + "line": 4, + "column": 12 + }, + "end": { + "line": 4, + "column": 20 + } + }, + "name": "theError" + }, + "property": { + "type": "Identifier", + "start": 78, + "end": 85, + "loc": { + "start": { + "line": 4, + "column": 21 + }, + "end": { + "line": 4, + "column": 28 + } + }, + "name": "message" + }, + "computed": false + } + } + ] + }, + { + "start": 90, + "end": 91, + "type": "Text", + "raw": "\n", + "data": "\n" + } + ], + "skip": false + } + } + ] + } +} \ No newline at end of file diff --git a/test/parser/samples/error-catch-without-await/error.json b/test/parser/samples/error-catch-without-await/error.json new file mode 100644 index 0000000000..86cf64a673 --- /dev/null +++ b/test/parser/samples/error-catch-without-await/error.json @@ -0,0 +1,10 @@ +{ + "code": "invalid-catch-placement", + "message": "Cannot have an {:catch} block outside an {#await ...} block", + "start": { + "line": 1, + "column": 7, + "character": 7 + }, + "pos": 7 +} diff --git a/test/parser/samples/error-catch-without-await/input.svelte b/test/parser/samples/error-catch-without-await/input.svelte new file mode 100644 index 0000000000..ee2fab68e0 --- /dev/null +++ b/test/parser/samples/error-catch-without-await/input.svelte @@ -0,0 +1 @@ +{:catch theValue} diff --git a/test/parser/samples/error-then-without-await/error.json b/test/parser/samples/error-then-without-await/error.json new file mode 100644 index 0000000000..62d9cbc3c9 --- /dev/null +++ b/test/parser/samples/error-then-without-await/error.json @@ -0,0 +1,10 @@ +{ + "code": "invalid-then-placement", + "message": "Cannot have an {:then} block outside an {#await ...} block", + "start": { + "line": 1, + "column": 6, + "character": 6 + }, + "pos": 6 +} diff --git a/test/parser/samples/error-then-without-await/input.svelte b/test/parser/samples/error-then-without-await/input.svelte new file mode 100644 index 0000000000..4b7fb6c677 --- /dev/null +++ b/test/parser/samples/error-then-without-await/input.svelte @@ -0,0 +1 @@ +{:then theValue} diff --git a/test/preprocess/index.js b/test/preprocess/index.js index 8d114ab7b3..ea24645295 100644 --- a/test/preprocess/index.js +++ b/test/preprocess/index.js @@ -3,21 +3,21 @@ import * as assert from 'assert'; import { loadConfig, svelte } from '../helpers.js'; describe('preprocess', () => { - fs.readdirSync('test/preprocess/samples').forEach(dir => { + fs.readdirSync(`${__dirname}/samples`).forEach(dir => { if (dir[0] === '.') return; - const config = loadConfig(`./preprocess/samples/${dir}/_config.js`); + const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`); if (config.solo && process.env.CI) { throw new Error('Forgot to remove `solo: true` from test'); } (config.skip ? it.skip : config.solo ? it.only : it)(dir, async () => { - const input = fs.readFileSync(`test/preprocess/samples/${dir}/input.svelte`, 'utf-8'); - const expected = fs.readFileSync(`test/preprocess/samples/${dir}/output.svelte`, 'utf-8'); + const input = fs.readFileSync(`${__dirname}/samples/${dir}/input.svelte`, 'utf-8'); + const expected = fs.readFileSync(`${__dirname}/samples/${dir}/output.svelte`, 'utf-8'); const result = await svelte.preprocess(input, config.preprocess); - fs.writeFileSync(`test/preprocess/samples/${dir}/_actual.html`, result.code); + fs.writeFileSync(`${__dirname}/samples/${dir}/_actual.html`, result.code); assert.equal(result.code, expected); diff --git a/test/runtime/index.js b/test/runtime/index.js index 646eeb99b6..397cfef172 100644 --- a/test/runtime/index.js +++ b/test/runtime/index.js @@ -49,7 +49,7 @@ describe("runtime", () => { function runTest(dir, hydrate) { if (dir[0] === ".") return; - const config = loadConfig(`./runtime/samples/${dir}/_config.js`); + const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`); if (hydrate && config.skip_if_hydrate) return; @@ -67,7 +67,7 @@ describe("runtime", () => { compile = (config.preserveIdentifiers ? svelte : svelte$).compile; - const cwd = path.resolve(`test/runtime/samples/${dir}`); + const cwd = path.resolve(`${__dirname}/samples/${dir}`); compileOptions = config.compileOptions || {}; compileOptions.format = 'cjs'; @@ -191,10 +191,12 @@ describe("runtime", () => { assert.equal(config.error, err.message); } } else { - failed.add(dir); - showOutput(cwd, compileOptions, compile); // eslint-disable-line no-console throw err; } + }).catch(err => { + failed.add(dir); + showOutput(cwd, compileOptions, compile); // eslint-disable-line no-console + throw err; }) .catch(err => { // print a clickable link to open the directory @@ -213,7 +215,7 @@ describe("runtime", () => { }); } - fs.readdirSync("test/runtime/samples").forEach(dir => { + fs.readdirSync(`${__dirname}/samples`).forEach(dir => { runTest(dir, false); runTest(dir, true); }); diff --git a/test/runtime/samples/attribute-boolean-case-insensitive/_config.js b/test/runtime/samples/attribute-boolean-case-insensitive/_config.js new file mode 100644 index 0000000000..16e5ade6d2 --- /dev/null +++ b/test/runtime/samples/attribute-boolean-case-insensitive/_config.js @@ -0,0 +1,3 @@ +export default { + html: `` +}; diff --git a/test/runtime/samples/attribute-boolean-case-insensitive/main.svelte b/test/runtime/samples/attribute-boolean-case-insensitive/main.svelte new file mode 100644 index 0000000000..9020894086 --- /dev/null +++ b/test/runtime/samples/attribute-boolean-case-insensitive/main.svelte @@ -0,0 +1 @@ + diff --git a/test/runtime/samples/attribute-boolean-with-spread/_config.js b/test/runtime/samples/attribute-boolean-with-spread/_config.js new file mode 100644 index 0000000000..270804170d --- /dev/null +++ b/test/runtime/samples/attribute-boolean-with-spread/_config.js @@ -0,0 +1,3 @@ +export default { + html: `` +}; diff --git a/test/runtime/samples/attribute-boolean-with-spread/main.svelte b/test/runtime/samples/attribute-boolean-with-spread/main.svelte new file mode 100644 index 0000000000..d0616e3024 --- /dev/null +++ b/test/runtime/samples/attribute-boolean-with-spread/main.svelte @@ -0,0 +1 @@ + diff --git a/test/runtime/samples/binding-this-store/_config.js b/test/runtime/samples/binding-this-store/_config.js new file mode 100644 index 0000000000..f818c2e31e --- /dev/null +++ b/test/runtime/samples/binding-this-store/_config.js @@ -0,0 +1,4 @@ +export default { + skip_if_ssr: true, + html: `
object
` +}; diff --git a/test/runtime/samples/binding-this-store/main.svelte b/test/runtime/samples/binding-this-store/main.svelte new file mode 100644 index 0000000000..7958dfa2d8 --- /dev/null +++ b/test/runtime/samples/binding-this-store/main.svelte @@ -0,0 +1,6 @@ + + +
{typeof $foo}
diff --git a/test/runtime/samples/class-with-spread-and-bind/_config.js b/test/runtime/samples/class-with-spread-and-bind/_config.js new file mode 100644 index 0000000000..f3c54e8c52 --- /dev/null +++ b/test/runtime/samples/class-with-spread-and-bind/_config.js @@ -0,0 +1,18 @@ +export default { + props: { + primary: true, + }, + + html: `
`, + + test({ assert, component, target, window }) { + component.primary = true; + + assert.htmlEqual( + target.innerHTML, + ` +
+ ` + ); + }, +}; diff --git a/test/runtime/samples/class-with-spread-and-bind/main.svelte b/test/runtime/samples/class-with-spread-and-bind/main.svelte new file mode 100644 index 0000000000..c7c24a2e7b --- /dev/null +++ b/test/runtime/samples/class-with-spread-and-bind/main.svelte @@ -0,0 +1,11 @@ + + +
diff --git a/test/runtime/samples/deconflict-builtins-2/_config.js b/test/runtime/samples/deconflict-builtins-2/_config.js new file mode 100644 index 0000000000..5870ff073b --- /dev/null +++ b/test/runtime/samples/deconflict-builtins-2/_config.js @@ -0,0 +1,4 @@ +export default { + html: `hello world`, + preserveIdentifiers: true, +}; \ No newline at end of file diff --git a/test/runtime/samples/deconflict-builtins-2/main.svelte b/test/runtime/samples/deconflict-builtins-2/main.svelte new file mode 100644 index 0000000000..82f9213045 --- /dev/null +++ b/test/runtime/samples/deconflict-builtins-2/main.svelte @@ -0,0 +1,5 @@ + + +{foo} \ No newline at end of file diff --git a/test/runtime/samples/each-block-keyed-iife/_config.js b/test/runtime/samples/each-block-keyed-iife/_config.js new file mode 100644 index 0000000000..da0b0aeac1 --- /dev/null +++ b/test/runtime/samples/each-block-keyed-iife/_config.js @@ -0,0 +1,7 @@ +export default { + html: ` +
1
+
2
+
3
+ ` +}; diff --git a/test/runtime/samples/each-block-keyed-iife/main.svelte b/test/runtime/samples/each-block-keyed-iife/main.svelte new file mode 100644 index 0000000000..8753357bae --- /dev/null +++ b/test/runtime/samples/each-block-keyed-iife/main.svelte @@ -0,0 +1,9 @@ + + +{#each arr as item ((() => item)())} +
+ {item} +
+{/each} diff --git a/test/runtime/samples/if-block-compound-outro-no-dependencies/_config.js b/test/runtime/samples/if-block-compound-outro-no-dependencies/_config.js new file mode 100644 index 0000000000..58b0521022 --- /dev/null +++ b/test/runtime/samples/if-block-compound-outro-no-dependencies/_config.js @@ -0,0 +1,3 @@ +export default { + html: `blah blah blah blah` +}; diff --git a/test/runtime/samples/if-block-compound-outro-no-dependencies/main.svelte b/test/runtime/samples/if-block-compound-outro-no-dependencies/main.svelte new file mode 100644 index 0000000000..84224116da --- /dev/null +++ b/test/runtime/samples/if-block-compound-outro-no-dependencies/main.svelte @@ -0,0 +1,32 @@ + + +{#if $foo} + blah +{:else} + {#if bar()} + + {/if} +{/if} + +{#if $foo} + blah +{:else} + {#if bar} + + {/if} +{/if} + +{#if $foo} + blah +{:else if bar()} + +{/if} + +{#if $foo} + blah +{:else if bar} + +{/if} diff --git a/test/runtime/samples/reactive-block-break/_config.js b/test/runtime/samples/reactive-block-break/_config.js new file mode 100644 index 0000000000..f2d3e6a8f5 --- /dev/null +++ b/test/runtime/samples/reactive-block-break/_config.js @@ -0,0 +1,3 @@ +export default { + html: `

1 2

` +}; \ No newline at end of file diff --git a/test/runtime/samples/reactive-block-break/main.svelte b/test/runtime/samples/reactive-block-break/main.svelte new file mode 100644 index 0000000000..5b0aa005c0 --- /dev/null +++ b/test/runtime/samples/reactive-block-break/main.svelte @@ -0,0 +1,14 @@ + + +

{foo} {bar}

\ No newline at end of file diff --git a/test/runtime/samples/sigil-expression-function-body/_config.js b/test/runtime/samples/sigil-expression-function-body/_config.js new file mode 100644 index 0000000000..fccd0db2d0 --- /dev/null +++ b/test/runtime/samples/sigil-expression-function-body/_config.js @@ -0,0 +1,3 @@ +export default { + html: `@foo` +}; diff --git a/test/runtime/samples/sigil-expression-function-body/main.svelte b/test/runtime/samples/sigil-expression-function-body/main.svelte new file mode 100644 index 0000000000..074496d6f4 --- /dev/null +++ b/test/runtime/samples/sigil-expression-function-body/main.svelte @@ -0,0 +1 @@ +{(() => '@foo')()} diff --git a/test/runtime/samples/spread-element-class/_config.js b/test/runtime/samples/spread-element-class/_config.js new file mode 100644 index 0000000000..7fc3d49012 --- /dev/null +++ b/test/runtime/samples/spread-element-class/_config.js @@ -0,0 +1,7 @@ +export default { + html: `
hello
`, + test({ assert, component, target }) { + component.blah = 'goodbye'; + assert.htmlEqual(target.innerHTML, `
goodbye
`); + } +}; diff --git a/test/runtime/samples/spread-element-class/main.svelte b/test/runtime/samples/spread-element-class/main.svelte new file mode 100644 index 0000000000..a678659013 --- /dev/null +++ b/test/runtime/samples/spread-element-class/main.svelte @@ -0,0 +1,5 @@ + + +
{blah}
diff --git a/test/runtime/samples/spread-element-removal/_config.js b/test/runtime/samples/spread-element-removal/_config.js new file mode 100644 index 0000000000..270804170d --- /dev/null +++ b/test/runtime/samples/spread-element-removal/_config.js @@ -0,0 +1,3 @@ +export default { + html: `` +}; diff --git a/test/runtime/samples/spread-element-removal/main.svelte b/test/runtime/samples/spread-element-removal/main.svelte new file mode 100644 index 0000000000..f6adc82c80 --- /dev/null +++ b/test/runtime/samples/spread-element-removal/main.svelte @@ -0,0 +1 @@ + diff --git a/test/runtime/samples/store-imported/_config.js b/test/runtime/samples/store-imported/_config.js index c2d471a329..251866e5ba 100644 --- a/test/runtime/samples/store-imported/_config.js +++ b/test/runtime/samples/store-imported/_config.js @@ -1,4 +1,6 @@ export default { + compileOptions: { dev: true }, // tests `@validate_store` code generation + html: `

42

` diff --git a/test/runtime/samples/store-resubscribe-export/_config.js b/test/runtime/samples/store-resubscribe-export/_config.js new file mode 100644 index 0000000000..b6e6f11344 --- /dev/null +++ b/test/runtime/samples/store-resubscribe-export/_config.js @@ -0,0 +1,27 @@ +let subscribeCalled = false; + +const fakeStore = val => ({ + subscribe: cb => { + cb(val); + return { + unsubscribe: () => { + subscribeCalled = true; + }, + }; + }, +}); + +export default { + props: { + foo: fakeStore(1), + }, + html: ` +

1

+ `, + + async test({ assert, component, target }) { + component.foo = fakeStore(5); + + return assert.htmlEqual(target.innerHTML, `

5

`); + }, +}; diff --git a/test/runtime/samples/store-resubscribe-export/main.svelte b/test/runtime/samples/store-resubscribe-export/main.svelte new file mode 100644 index 0000000000..44b00544b7 --- /dev/null +++ b/test/runtime/samples/store-resubscribe-export/main.svelte @@ -0,0 +1,6 @@ + + +

{$foo}

diff --git a/test/server-side-rendering/index.js b/test/server-side-rendering/index.js index 1097486124..0a4b6f7496 100644 --- a/test/server-side-rendering/index.js +++ b/test/server-side-rendering/index.js @@ -30,7 +30,7 @@ describe("ssr", () => { return setupHtmlEqual(); }); - fs.readdirSync("test/server-side-rendering/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, or @@ -43,7 +43,7 @@ describe("ssr", () => { } (solo ? it.only : it)(dir, () => { - dir = path.resolve("test/server-side-rendering/samples", dir); + dir = path.resolve(`${__dirname}/samples`, dir); try { const Component = require(`${dir}/main.svelte`).default; diff --git a/test/sourcemaps/index.js b/test/sourcemaps/index.js index e5915780c6..0b0424a764 100644 --- a/test/sourcemaps/index.js +++ b/test/sourcemaps/index.js @@ -6,7 +6,7 @@ import { SourceMapConsumer } from "source-map"; import { getLocator } from "locate-character"; describe("sourcemaps", () => { - fs.readdirSync("test/sourcemaps/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,10 +19,10 @@ describe("sourcemaps", () => { (solo ? it.only : skip ? it.skip : it)(dir, async () => { const filename = path.resolve( - `test/sourcemaps/samples/${dir}/input.svelte` + `${__dirname}/samples/${dir}/input.svelte` ); const outputFilename = path.resolve( - `test/sourcemaps/samples/${dir}/output` + `${__dirname}/samples/${dir}/output` ); const input = fs.readFileSync(filename, "utf-8").replace(/\s+$/, ""); diff --git a/test/stats/index.js b/test/stats/index.js index aafc58c926..acea7a4663 100644 --- a/test/stats/index.js +++ b/test/stats/index.js @@ -3,7 +3,7 @@ import * as assert from 'assert'; import { svelte, loadConfig, tryToLoadJson } from '../helpers.js'; describe('stats', () => { - fs.readdirSync('test/stats/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 @@ -15,12 +15,12 @@ describe('stats', () => { } (solo ? it.only : skip ? it.skip : it)(dir, () => { - const config = loadConfig(`./stats/samples/${dir}/_config.js`); - const filename = `test/stats/samples/${dir}/input.svelte`; + const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`); + const filename = `${__dirname}/samples/${dir}/input.svelte`; const input = fs.readFileSync(filename, 'utf-8').replace(/\s+$/, ''); const expectedError = tryToLoadJson( - `test/stats/samples/${dir}/error.json` + `${__dirname}/samples/${dir}/error.json` ); let result; diff --git a/test/test.js b/test/test.js index 9480ae7836..6ea4bd60ab 100644 --- a/test/test.js +++ b/test/test.js @@ -8,6 +8,14 @@ require('../internal'); console.clear(); -glob('*/index.js', { cwd: 'test' }).forEach((file) => { - require('./' + file); -}); +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)); +} diff --git a/test/validator/index.js b/test/validator/index.js index 1e54cc20db..9f991df4f2 100644 --- a/test/validator/index.js +++ b/test/validator/index.js @@ -3,7 +3,7 @@ import * as assert from "assert"; import { svelte, loadConfig, tryToLoadJson } from "../helpers.js"; describe("validate", () => { - fs.readdirSync("test/validator/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 @@ -15,11 +15,11 @@ describe("validate", () => { } (solo ? it.only : skip ? it.skip : it)(dir, () => { - const config = loadConfig(`./validator/samples/${dir}/_config.js`); + const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`); - const input = fs.readFileSync(`test/validator/samples/${dir}/input.svelte`, "utf-8").replace(/\s+$/, ""); - const expected_warnings = tryToLoadJson(`test/validator/samples/${dir}/warnings.json`) || []; - const expected_errors = tryToLoadJson(`test/validator/samples/${dir}/errors.json`); + 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`); let error; diff --git a/test/vars/index.js b/test/vars/index.js index 66ffd94c70..a12ac177f2 100644 --- a/test/vars/index.js +++ b/test/vars/index.js @@ -3,7 +3,7 @@ import * as assert from 'assert'; import { svelte, loadConfig, tryToLoadJson } from '../helpers.js'; describe('vars', () => { - fs.readdirSync('test/vars/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 @@ -16,12 +16,12 @@ describe('vars', () => { for (const generate of ['dom', 'ssr', false]) { (solo ? it.only : skip ? it.skip : it)(`${dir}, generate: ${generate}`, () => { - const config = loadConfig(`./vars/samples/${dir}/_config.js`); - const filename = `test/vars/samples/${dir}/input.svelte`; + const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`); + const filename = `${__dirname}/samples/${dir}/input.svelte`; const input = fs.readFileSync(filename, 'utf-8').replace(/\s+$/, ''); const expectedError = tryToLoadJson( - `test/vars/samples/${dir}/error.json` + `${__dirname}/samples/${dir}/error.json` ); let result;