diff --git a/CHANGELOG.md b/CHANGELOG.md index f605fc14ab..1fa6ec5796 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Svelte changelog +## Unreleased + +* Return the context object in `setContext` [#7427](https://github.com/sveltejs/svelte/issues/7427) + +## 3.47.0 + +* Add support for dynamic elements through `` ([#2324](https://github.com/sveltejs/svelte/issues/2324)) +* Miscellaneous variable context fixes in `{@const}` ([#7222](https://github.com/sveltejs/svelte/pull/7222)) +* Fix `{#key}` block not being reactive when the key variable is not otherwise used ([#7408](https://github.com/sveltejs/svelte/issues/7408)) +* Add `Symbol` as a known global ([#7418](https://github.com/sveltejs/svelte/issues/7418)) + ## 3.46.6 * Actually include action TypeScript interface in published package ([#7407](https://github.com/sveltejs/svelte/pull/7407)) diff --git a/package-lock.json b/package-lock.json index 95336bd64c..0b5a130992 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,11 @@ { "name": "svelte", - "version": "3.46.6", + "version": "3.47.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "svelte", - "version": "3.46.6", + "version": "3.47.0", "license": "MIT", "devDependencies": { "@ampproject/remapping": "^0.3.0", diff --git a/package.json b/package.json index 82dc1e84c7..2237ef40f1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.46.6", + "version": "3.47.0", "description": "Cybernetically enhanced web apps", "module": "index.mjs", "main": "index", diff --git a/site/content/blog/2017-08-07-the-easiest-way-to-get-started.md b/site/content/blog/2017-08-07-the-easiest-way-to-get-started.md index 41666d0cb2..fe4a14eaeb 100644 --- a/site/content/blog/2017-08-07-the-easiest-way-to-get-started.md +++ b/site/content/blog/2017-08-07-the-easiest-way-to-get-started.md @@ -12,7 +12,9 @@ Because of that, getting started with it can be a little bit confusing at first. ## 1. Use the REPL -The [Svelte REPL](/repl) is the easiest way to begin. You can choose from a list of examples to get you started, and tweak them until they do what you want. +The [Svelte REPL](/repl) (Read-Eval-Print Loop) is the easiest way to begin. This is an interactive environment that allows you to modify code and instantly see the result. + +You can choose from a list of [examples](/examples/), click the [REPL](/repl) link, and then tweak them until they do what you want. diff --git a/site/content/docs/02-template-syntax.md b/site/content/docs/02-template-syntax.md index f80ddfee05..00070e563c 100644 --- a/site/content/docs/02-template-syntax.md +++ b/site/content/docs/02-template-syntax.md @@ -1627,6 +1627,28 @@ If `this` is falsy, no component is rendered. ``` +### `` + +```sv + +``` + +--- + +The `` element lets you render an element of a dynamically specified type. This is useful for example when rich text content from a CMS. If the tag is changed, the children will be preserved unless there's a transition attached to the element. Any properties and event listeners present will be applied to the element. + +The only supported binding is `bind:this`, since the element type specific bindings that Svelte does at build time (e.g. `bind:value` for input elements) does not work with a dynamic tag type. + +If `this` has a nullish value, a warning will be logged in development mode. + +```sv + + +Foo +``` ### `` @@ -1683,7 +1705,7 @@ All except `scrollX` and `scrollY` are readonly. Similarly to ``, this element allows you to add listeners to events on `document.body`, such as `mouseenter` and `mouseleave`, which don't fire on `window`. It also lets you use [actions](/docs#template-syntax-element-directives-use-action) on the `` element. -`` also has to appear at the top level of your component. +As with ``, this element may only appear the top level of your component and must never be inside a block or element. ```sv `, this element allows you to add listeners to even This element makes it possible to insert elements into `document.head`. During server-side rendering, `head` content is exposed separately to the main `html` content. -As with `` and ``, this element has to appear at the top level of your component and cannot be inside a block or other element. +As with `` and ``, this element may only appear at the top level of your component and must never be inside a block or element. ```sv diff --git a/site/content/docs/03-run-time.md b/site/content/docs/03-run-time.md index 636a98834d..a0d3f4148c 100644 --- a/site/content/docs/03-run-time.md +++ b/site/content/docs/03-run-time.md @@ -148,7 +148,7 @@ setContext(key: any, context: any) --- -Associates an arbitrary `context` object with the current component and the specified `key`. The context is then available to children of the component (including slotted content) with `getContext`. +Associates an arbitrary `context` object with the current component and the specified `key` and returns that object. The context is then available to children of the component (including slotted content) with `getContext`. Like lifecycle functions, this must be called during component initialisation. diff --git a/site/content/docs/04-compile-time.md b/site/content/docs/04-compile-time.md index 1ee41eeb93..35a57ced85 100644 --- a/site/content/docs/04-compile-time.md +++ b/site/content/docs/04-compile-time.md @@ -2,11 +2,7 @@ title: Compile time --- -Typically, you won't interact with the Svelte compiler directly, but will instead integrate it into your build system using a bundler plugin: - -* [rollup-plugin-svelte](https://github.com/sveltejs/rollup-plugin-svelte) for users of [Rollup](https://rollupjs.org) -* [svelte-loader](https://github.com/sveltejs/svelte-loader) for users of [webpack](https://webpack.js.org) -* or one of the [community-maintained plugins](https://sveltesociety.dev/tools) +Typically, you won't interact with the Svelte compiler directly, but will instead integrate it into your build system using a bundler plugin. The bundler plugin that the Svelte team most recommends and invests in is [vite-plugin-svelte](https://github.com/sveltejs/vite-plugin-svelte). The [SvelteKit](https://kit.svelte.dev/) framework provides a setup leveraging `vite-plugin-svelte` to build applications as well as a [tool for packaging Svelte component libraries](https://kit.svelte.dev/docs/packaging). Svelte Society maintains a list of [other bundler plugins](https://sveltesociety.dev/tools/#bundling) for additional tools like Rollup and Webpack. Nonetheless, it's useful to understand how to use the compiler, since bundler plugins generally expose compiler options to you. @@ -77,7 +73,7 @@ The following options can be passed to the compiler. None are required: | `accessors` | `false` | If `true`, getters and setters will be created for the component's props. If `false`, they will only be created for readonly exported values (i.e. those declared with `const`, `class` and `function`). If compiling with `customElement: true` this option defaults to `true`. | `customElement` | `false` | If `true`, tells the compiler to generate a custom element constructor instead of a regular Svelte component. | `tag` | `null` | A `string` that tells Svelte what tag name to register the custom element with. It must be a lowercase alphanumeric string with at least one hyphen, e.g. `"my-element"`. -| `css` | `true` | If `true`, styles will be included in the JavaScript class and injected at runtime. It's recommended that you set this to `false` and use the CSS that is statically generated, as it will result in smaller JavaScript bundles and better performance. +| `css` | `true` | If `true`, styles will be included in the JavaScript class and injected at runtime for the components actually rendered. If `false`, the CSS will be returned in the `css` field of the compilation result. Most Svelte bundler plugins will set this to `false` and use the CSS that is statically generated for better performance, as it will result in smaller JavaScript bundles and the output can be served as cacheable `.css` files. | `cssHash` | See right | A function that takes a `{ hash, css, name, filename }` argument and returns the string that is used as a classname for scoped CSS. It defaults to returning `svelte-${hash(css)}` | `loopGuardTimeout` | 0 | A `number` that tells Svelte to break the loop if it blocks the thread for more than `loopGuardTimeout` ms. This is useful to prevent infinite loops. **Only available when `dev: true`** | `preserveComments` | `false` | If `true`, your HTML comments will be preserved during server-side rendering. By default, they are stripped out. diff --git a/site/content/examples/17-special-elements/02-svelte-element/App.svelte b/site/content/examples/17-special-elements/02-svelte-element/App.svelte new file mode 100644 index 0000000000..068f7b3c4f --- /dev/null +++ b/site/content/examples/17-special-elements/02-svelte-element/App.svelte @@ -0,0 +1,12 @@ + + + + +I'm a {selected} tag diff --git a/site/content/examples/17-special-elements/02-svelte-element/meta.json b/site/content/examples/17-special-elements/02-svelte-element/meta.json new file mode 100644 index 0000000000..4ff15b716f --- /dev/null +++ b/site/content/examples/17-special-elements/02-svelte-element/meta.json @@ -0,0 +1,3 @@ +{ + "title": "" +} \ No newline at end of file diff --git a/site/content/examples/17-special-elements/02-svelte-window/App.svelte b/site/content/examples/17-special-elements/03-svelte-window/App.svelte similarity index 100% rename from site/content/examples/17-special-elements/02-svelte-window/App.svelte rename to site/content/examples/17-special-elements/03-svelte-window/App.svelte diff --git a/site/content/examples/17-special-elements/02-svelte-window/meta.json b/site/content/examples/17-special-elements/03-svelte-window/meta.json similarity index 100% rename from site/content/examples/17-special-elements/02-svelte-window/meta.json rename to site/content/examples/17-special-elements/03-svelte-window/meta.json diff --git a/site/content/examples/17-special-elements/03-svelte-window-bindings/App.svelte b/site/content/examples/17-special-elements/04-svelte-window-bindings/App.svelte similarity index 100% rename from site/content/examples/17-special-elements/03-svelte-window-bindings/App.svelte rename to site/content/examples/17-special-elements/04-svelte-window-bindings/App.svelte diff --git a/site/content/examples/17-special-elements/03-svelte-window-bindings/meta.json b/site/content/examples/17-special-elements/04-svelte-window-bindings/meta.json similarity index 100% rename from site/content/examples/17-special-elements/03-svelte-window-bindings/meta.json rename to site/content/examples/17-special-elements/04-svelte-window-bindings/meta.json diff --git a/site/content/examples/17-special-elements/04-svelte-body/App.svelte b/site/content/examples/17-special-elements/05-svelte-body/App.svelte similarity index 100% rename from site/content/examples/17-special-elements/04-svelte-body/App.svelte rename to site/content/examples/17-special-elements/05-svelte-body/App.svelte diff --git a/site/content/examples/17-special-elements/04-svelte-body/meta.json b/site/content/examples/17-special-elements/05-svelte-body/meta.json similarity index 100% rename from site/content/examples/17-special-elements/04-svelte-body/meta.json rename to site/content/examples/17-special-elements/05-svelte-body/meta.json diff --git a/site/content/examples/17-special-elements/05-svelte-head/App.svelte b/site/content/examples/17-special-elements/06-svelte-head/App.svelte similarity index 100% rename from site/content/examples/17-special-elements/05-svelte-head/App.svelte rename to site/content/examples/17-special-elements/06-svelte-head/App.svelte diff --git a/site/content/examples/17-special-elements/05-svelte-head/meta.json b/site/content/examples/17-special-elements/06-svelte-head/meta.json similarity index 100% rename from site/content/examples/17-special-elements/05-svelte-head/meta.json rename to site/content/examples/17-special-elements/06-svelte-head/meta.json diff --git a/site/content/faq/800-how-do-i-test-svelte-apps.md b/site/content/faq/800-how-do-i-test-svelte-apps.md index 5e59d5812d..1481b6ba59 100644 --- a/site/content/faq/800-how-do-i-test-svelte-apps.md +++ b/site/content/faq/800-how-do-i-test-svelte-apps.md @@ -8,4 +8,5 @@ There are a few approaches that people take when testing, but it generally invol Some resources for getting started with unit testing: - [Svelte Testing Library](https://testing-library.com/docs/svelte-testing-library/example/) +- [Example using vitest](https://github.com/vitest-dev/vitest/tree/main/examples/svelte) - [Example using uvu test runner with JSDOM](https://github.com/lukeed/uvu/tree/master/examples/svelte) diff --git a/site/content/tutorial/04-logic/05-keyed-each-blocks/text.md b/site/content/tutorial/04-logic/05-keyed-each-blocks/text.md index b990dead95..76d8322fb0 100644 --- a/site/content/tutorial/04-logic/05-keyed-each-blocks/text.md +++ b/site/content/tutorial/04-logic/05-keyed-each-blocks/text.md @@ -4,7 +4,7 @@ title: Keyed each blocks By default, when you modify the value of an `each` block, it will add and remove items at the *end* of the block, and update any values that have changed. That might not be what you want. -It's easier to show why than to explain. Click the 'Remove first thing' button a few times, and notice what happens: It removes the first `` component, but the *last* DOM node. Then it updates the `name` value in the remaining DOM nodes, but not the emoji. +It's easier to show why than to explain. Click the 'Remove first thing' button a few times, and notice what happens: it does not remove the first `` component, but rather the *last* DOM node. Then it updates the `name` value in the remaining DOM nodes, but not the emoji. Instead, we'd like to remove only the first `` component and its DOM node, and leave the others unaffected. diff --git a/site/content/tutorial/06-bindings/06-select-bindings/text.md b/site/content/tutorial/06-bindings/06-select-bindings/text.md index b9cf2de6ec..d0104f7f1a 100644 --- a/site/content/tutorial/06-bindings/06-select-bindings/text.md +++ b/site/content/tutorial/06-bindings/06-select-bindings/text.md @@ -10,4 +10,4 @@ We can also use `bind:value` with ` + {#each options as option} + + {/each} + + +{#if selected === 'h1'} +

I'm a h1 tag

+{:else if selected === 'h3'} +

I'm a h3 tag

+{:else if selected === 'p'} +

I'm a p tag

+{/if} diff --git a/site/content/tutorial/16-special-elements/03-svelte-element/app-b/App.svelte b/site/content/tutorial/16-special-elements/03-svelte-element/app-b/App.svelte new file mode 100644 index 0000000000..068f7b3c4f --- /dev/null +++ b/site/content/tutorial/16-special-elements/03-svelte-element/app-b/App.svelte @@ -0,0 +1,12 @@ + + + + +I'm a {selected} tag diff --git a/site/content/tutorial/16-special-elements/03-svelte-element/text.md b/site/content/tutorial/16-special-elements/03-svelte-element/text.md new file mode 100644 index 0000000000..ace344db42 --- /dev/null +++ b/site/content/tutorial/16-special-elements/03-svelte-element/text.md @@ -0,0 +1,23 @@ +--- +title: +--- + +Sometimes we don't know in advance what kind of DOM element to render. `` comes in handy here. Instead of a sequence of `if` blocks... + +```html +{#if selected === 'h1'} +

I'm a h1 tag

+{:else if selected === 'h3'} +

I'm a h3 tag

+{:else if selected === 'p'} +

I'm a p tag

+{/if} +``` + +...we can have a single dynamic component: + +```html +I'm a {selected} tag +``` + +The `this` value can be any string, or a falsy value — if it's falsy, no element is rendered. \ No newline at end of file diff --git a/site/content/tutorial/16-special-elements/03-svelte-window/app-a/App.svelte b/site/content/tutorial/16-special-elements/04-svelte-window/app-a/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/03-svelte-window/app-a/App.svelte rename to site/content/tutorial/16-special-elements/04-svelte-window/app-a/App.svelte diff --git a/site/content/tutorial/16-special-elements/03-svelte-window/app-b/App.svelte b/site/content/tutorial/16-special-elements/04-svelte-window/app-b/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/03-svelte-window/app-b/App.svelte rename to site/content/tutorial/16-special-elements/04-svelte-window/app-b/App.svelte diff --git a/site/content/tutorial/16-special-elements/03-svelte-window/text.md b/site/content/tutorial/16-special-elements/04-svelte-window/text.md similarity index 100% rename from site/content/tutorial/16-special-elements/03-svelte-window/text.md rename to site/content/tutorial/16-special-elements/04-svelte-window/text.md diff --git a/site/content/tutorial/16-special-elements/04-svelte-window-bindings/app-a/App.svelte b/site/content/tutorial/16-special-elements/05-svelte-window-bindings/app-a/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/04-svelte-window-bindings/app-a/App.svelte rename to site/content/tutorial/16-special-elements/05-svelte-window-bindings/app-a/App.svelte diff --git a/site/content/tutorial/16-special-elements/04-svelte-window-bindings/app-b/App.svelte b/site/content/tutorial/16-special-elements/05-svelte-window-bindings/app-b/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/04-svelte-window-bindings/app-b/App.svelte rename to site/content/tutorial/16-special-elements/05-svelte-window-bindings/app-b/App.svelte diff --git a/site/content/tutorial/16-special-elements/04-svelte-window-bindings/text.md b/site/content/tutorial/16-special-elements/05-svelte-window-bindings/text.md similarity index 100% rename from site/content/tutorial/16-special-elements/04-svelte-window-bindings/text.md rename to site/content/tutorial/16-special-elements/05-svelte-window-bindings/text.md diff --git a/site/content/tutorial/16-special-elements/05-svelte-body/app-a/App.svelte b/site/content/tutorial/16-special-elements/06-svelte-body/app-a/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/05-svelte-body/app-a/App.svelte rename to site/content/tutorial/16-special-elements/06-svelte-body/app-a/App.svelte diff --git a/site/content/tutorial/16-special-elements/05-svelte-body/app-b/App.svelte b/site/content/tutorial/16-special-elements/06-svelte-body/app-b/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/05-svelte-body/app-b/App.svelte rename to site/content/tutorial/16-special-elements/06-svelte-body/app-b/App.svelte diff --git a/site/content/tutorial/16-special-elements/05-svelte-body/text.md b/site/content/tutorial/16-special-elements/06-svelte-body/text.md similarity index 100% rename from site/content/tutorial/16-special-elements/05-svelte-body/text.md rename to site/content/tutorial/16-special-elements/06-svelte-body/text.md diff --git a/site/content/tutorial/16-special-elements/06-svelte-head/app-a/App.svelte b/site/content/tutorial/16-special-elements/07-svelte-head/app-a/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/06-svelte-head/app-a/App.svelte rename to site/content/tutorial/16-special-elements/07-svelte-head/app-a/App.svelte diff --git a/site/content/tutorial/16-special-elements/06-svelte-head/app-b/App.svelte b/site/content/tutorial/16-special-elements/07-svelte-head/app-b/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/06-svelte-head/app-b/App.svelte rename to site/content/tutorial/16-special-elements/07-svelte-head/app-b/App.svelte diff --git a/site/content/tutorial/16-special-elements/06-svelte-head/text.md b/site/content/tutorial/16-special-elements/07-svelte-head/text.md similarity index 100% rename from site/content/tutorial/16-special-elements/06-svelte-head/text.md rename to site/content/tutorial/16-special-elements/07-svelte-head/text.md diff --git a/site/content/tutorial/16-special-elements/07-svelte-options/app-a/App.svelte b/site/content/tutorial/16-special-elements/08-svelte-options/app-a/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/07-svelte-options/app-a/App.svelte rename to site/content/tutorial/16-special-elements/08-svelte-options/app-a/App.svelte diff --git a/site/content/tutorial/16-special-elements/07-svelte-options/app-a/Todo.svelte b/site/content/tutorial/16-special-elements/08-svelte-options/app-a/Todo.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/07-svelte-options/app-a/Todo.svelte rename to site/content/tutorial/16-special-elements/08-svelte-options/app-a/Todo.svelte diff --git a/site/content/tutorial/16-special-elements/07-svelte-options/app-a/flash.js b/site/content/tutorial/16-special-elements/08-svelte-options/app-a/flash.js similarity index 100% rename from site/content/tutorial/16-special-elements/07-svelte-options/app-a/flash.js rename to site/content/tutorial/16-special-elements/08-svelte-options/app-a/flash.js diff --git a/site/content/tutorial/16-special-elements/07-svelte-options/app-b/App.svelte b/site/content/tutorial/16-special-elements/08-svelte-options/app-b/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/07-svelte-options/app-b/App.svelte rename to site/content/tutorial/16-special-elements/08-svelte-options/app-b/App.svelte diff --git a/site/content/tutorial/16-special-elements/07-svelte-options/app-b/Todo.svelte b/site/content/tutorial/16-special-elements/08-svelte-options/app-b/Todo.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/07-svelte-options/app-b/Todo.svelte rename to site/content/tutorial/16-special-elements/08-svelte-options/app-b/Todo.svelte diff --git a/site/content/tutorial/16-special-elements/07-svelte-options/app-b/flash.js b/site/content/tutorial/16-special-elements/08-svelte-options/app-b/flash.js similarity index 100% rename from site/content/tutorial/16-special-elements/07-svelte-options/app-b/flash.js rename to site/content/tutorial/16-special-elements/08-svelte-options/app-b/flash.js diff --git a/site/content/tutorial/16-special-elements/07-svelte-options/text.md b/site/content/tutorial/16-special-elements/08-svelte-options/text.md similarity index 100% rename from site/content/tutorial/16-special-elements/07-svelte-options/text.md rename to site/content/tutorial/16-special-elements/08-svelte-options/text.md diff --git a/site/content/tutorial/16-special-elements/08-svelte-fragment/app-a/App.svelte b/site/content/tutorial/16-special-elements/09-svelte-fragment/app-a/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/08-svelte-fragment/app-a/App.svelte rename to site/content/tutorial/16-special-elements/09-svelte-fragment/app-a/App.svelte diff --git a/site/content/tutorial/16-special-elements/08-svelte-fragment/app-a/Box.svelte b/site/content/tutorial/16-special-elements/09-svelte-fragment/app-a/Box.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/08-svelte-fragment/app-a/Box.svelte rename to site/content/tutorial/16-special-elements/09-svelte-fragment/app-a/Box.svelte diff --git a/site/content/tutorial/16-special-elements/08-svelte-fragment/app-b/App.svelte b/site/content/tutorial/16-special-elements/09-svelte-fragment/app-b/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/08-svelte-fragment/app-b/App.svelte rename to site/content/tutorial/16-special-elements/09-svelte-fragment/app-b/App.svelte diff --git a/site/content/tutorial/16-special-elements/08-svelte-fragment/app-b/Box.svelte b/site/content/tutorial/16-special-elements/09-svelte-fragment/app-b/Box.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/08-svelte-fragment/app-b/Box.svelte rename to site/content/tutorial/16-special-elements/09-svelte-fragment/app-b/Box.svelte diff --git a/site/content/tutorial/16-special-elements/08-svelte-fragment/text.md b/site/content/tutorial/16-special-elements/09-svelte-fragment/text.md similarity index 100% rename from site/content/tutorial/16-special-elements/08-svelte-fragment/text.md rename to site/content/tutorial/16-special-elements/09-svelte-fragment/text.md diff --git a/src/compiler/compile/compiler_errors.ts b/src/compiler/compile/compiler_errors.ts index df832373fc..a04780e375 100644 --- a/src/compiler/compile/compiler_errors.ts +++ b/src/compiler/compile/compiler_errors.ts @@ -246,6 +246,10 @@ export default { code: 'invalid-animation', message: 'An element that uses the animate directive must be the sole child of a keyed each block' }, + invalid_animation_dynamic_element: { + code: 'invalid-animation', + message: ' cannot have a animate directive' + }, invalid_directive_value: { code: 'invalid-directive-value', message: 'Can only bind to an identifier (e.g. `foo`) or a member expression (e.g. `foo.bar` or `foo[baz]`)' diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 78d6c9188f..00a9b6b22f 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -18,6 +18,9 @@ import Let from './Let'; import TemplateScope from './shared/TemplateScope'; import { INode } from './interfaces'; import Component from '../Component'; +import Expression from './shared/Expression'; +import { string_literal } from '../utils/stringify'; +import { Literal } from 'estree'; import compiler_warnings from '../compiler_warnings'; import compiler_errors from '../compiler_errors'; @@ -190,11 +193,26 @@ export default class Element extends Node { children: INode[]; namespace: string; needs_manual_style_scoping: boolean; + tag_expr: Expression; + + get is_dynamic_element() { + return this.name === 'svelte:element'; + } constructor(component: Component, parent: Node, scope: TemplateScope, info: any) { super(component, parent, scope, info); this.name = info.name; + if (info.name === 'svelte:element') { + if (typeof info.tag !== 'string') { + this.tag_expr = new Expression(component, this, scope, info.tag); + } else { + this.tag_expr = new Expression(component, this, scope, string_literal(info.tag) as Literal); + } + } else { + this.tag_expr = new Expression(component, this, scope, string_literal(this.name) as Literal); + } + this.namespace = get_namespace(parent as Element, this, component.namespace); if (this.namespace !== namespaces.foreign) { diff --git a/src/compiler/compile/nodes/shared/Expression.ts b/src/compiler/compile/nodes/shared/Expression.ts index 98fb2f1e3a..751b739564 100644 --- a/src/compiler/compile/nodes/shared/Expression.ts +++ b/src/compiler/compile/nodes/shared/Expression.ts @@ -254,11 +254,17 @@ export default class Expression { const declaration = b`const ${id} = ${node}`; if (owner.type === 'ConstTag') { + let child_scope = scope; walk(node, { - enter(node: Node) { - if (node.type === 'Identifier') { + enter(node: Node, parent: any) { + if (map.has(node)) child_scope = map.get(node); + if (node.type === 'Identifier' && is_reference(node, parent)) { + if (child_scope.has(node.name)) return; this.replace(block.renderer.reference(node, ctx)); } + }, + leave(node: Node) { + if (map.has(node)) child_scope = child_scope.parent; } }); } else if (dependencies.size === 0 && contextual_dependencies.size === 0) { diff --git a/src/compiler/compile/render_dom/Block.ts b/src/compiler/compile/render_dom/Block.ts index cad7e22170..34c4774804 100644 --- a/src/compiler/compile/render_dom/Block.ts +++ b/src/compiler/compile/render_dom/Block.ts @@ -48,6 +48,7 @@ export default class Block { hydrate: Array; mount: Array; measure: Array; + restore_measurements: Array; fix: Array; animate: Array; intro: Array; @@ -96,6 +97,7 @@ export default class Block { hydrate: [], mount: [], measure: [], + restore_measurements: [], fix: [], animate: [], intro: [], @@ -326,6 +328,12 @@ export default class Block { ${this.chunks.measure} }`; + if (this.chunks.restore_measurements.length) { + properties.restore_measurements = x`function #restore_measurements(#measurement) { + ${this.chunks.restore_measurements} + }`; + } + properties.fix = x`function #fix() { ${this.chunks.fix} }`; @@ -379,6 +387,7 @@ export default class Block { m: ${properties.mount}, p: ${properties.update}, r: ${properties.measure}, + s: ${properties.restore_measurements}, f: ${properties.fix}, a: ${properties.animate}, i: ${properties.intro}, diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 989e7cb82b..b66c8938fc 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -26,6 +26,7 @@ import Action from '../../../nodes/Action'; import MustacheTagWrapper from '../MustacheTag'; import RawMustacheTagWrapper from '../RawMustacheTag'; import is_dynamic from '../shared/is_dynamic'; +import create_debugging_comment from '../shared/create_debugging_comment'; import { push_array } from '../../../../utils/push_array'; interface BindingGroup { @@ -134,6 +135,8 @@ const events = [ } ]; +const CHILD_DYNAMIC_ELEMENT_BLOCK = 'child_dynamic_element'; + export default class ElementWrapper extends Wrapper { node: Element; fragment: FragmentWrapper; @@ -147,6 +150,9 @@ export default class ElementWrapper extends Wrapper { var: any; void: boolean; + child_dynamic_element_block?: Block = null; + child_dynamic_element?: ElementWrapper = null; + constructor( renderer: Renderer, block: Block, @@ -156,6 +162,24 @@ export default class ElementWrapper extends Wrapper { next_sibling: Wrapper ) { super(renderer, block, parent, node); + + if (node.is_dynamic_element && block.type !== CHILD_DYNAMIC_ELEMENT_BLOCK) { + this.child_dynamic_element_block = block.child({ + comment: create_debugging_comment(node, renderer.component), + name: renderer.component.get_unique_name('create_dynamic_element'), + type: CHILD_DYNAMIC_ELEMENT_BLOCK + }); + renderer.blocks.push(this.child_dynamic_element_block); + this.child_dynamic_element = new ElementWrapper( + renderer, + this.child_dynamic_element_block, + parent, + node, + strip_whitespace, + next_sibling + ); + } + this.var = { type: 'Identifier', name: node.name.replace(/[^a-zA-Z0-9_$]/g, '_') @@ -199,6 +223,8 @@ export default class ElementWrapper extends Wrapper { block.add_animation(); } + block.add_dependencies(node.tag_expr.dependencies); + // add directive and handler dependencies [node.animation, node.outro, ...node.actions, ...node.classes, ...node.styles].forEach(directive => { if (directive && directive.expression) { @@ -221,6 +247,7 @@ export default class ElementWrapper extends Wrapper { node.handlers.length > 0 || node.styles.length > 0 || this.node.name === 'option' || + node.tag_expr.dynamic_dependencies().length || renderer.options.dev ) { this.parent.cannot_use_innerhtml(); // need to use add_location @@ -232,6 +259,110 @@ export default class ElementWrapper extends Wrapper { } render(block: Block, parent_node: Identifier, parent_nodes: Identifier) { + if (this.child_dynamic_element) { + this.render_dynamic_element(block, parent_node, parent_nodes); + } else { + this.render_element(block, parent_node, parent_nodes); + } + } + + render_dynamic_element(block: Block, parent_node: Identifier, parent_nodes: Identifier) { + this.child_dynamic_element.render( + this.child_dynamic_element_block, + null, + (x`#nodes` as unknown) as Identifier + ); + + const previous_tag = block.get_unique_name('previous_tag'); + const tag = this.node.tag_expr.manipulate(block); + block.add_variable(previous_tag, tag); + + block.chunks.init.push(b` + ${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`} + let ${this.var} = ${tag} && ${this.child_dynamic_element_block.name}(#ctx); + `); + + block.chunks.create.push(b` + if (${this.var}) ${this.var}.c(); + `); + + if (this.renderer.options.hydratable) { + block.chunks.claim.push(b` + if (${this.var}) ${this.var}.l(${parent_nodes}); + `); + } + + block.chunks.mount.push(b` + if (${this.var}) ${this.var}.m(${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor'}); + `); + + const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); + const has_transitions = !!(this.node.intro || this.node.outro); + const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`; + + block.chunks.update.push(b` + if (${tag}) { + if (!${previous_tag}) { + ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); + ${this.var}.c(); + ${has_transitions && b`@transition_in(${this.var})`} + ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); + } else if (${not_equal}(${previous_tag}, ${tag})) { + ${this.var}.d(1); + ${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`} + ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); + ${this.var}.c(); + ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); + } else { + ${this.var}.p(#ctx, #dirty); + } + } else if (${previous_tag}) { + ${ + has_transitions + ? b` + @group_outros(); + @transition_out(${this.var}, 1, 1, () => { + ${this.var} = null; + }); + @check_outros(); + ` + : b` + ${this.var}.d(1); + ${this.var} = null; + ` + } + } + ${previous_tag} = ${tag}; + `); + + if (this.child_dynamic_element_block.has_intros) { + block.chunks.intro.push(b`@transition_in(${this.var});`); + } + + if (this.child_dynamic_element_block.has_outros) { + block.chunks.outro.push(b`@transition_out(${this.var});`); + } + + block.chunks.destroy.push(b`if (${this.var}) ${this.var}.d(detaching)`); + + if (this.node.animation) { + const measurements = block.get_unique_name('measurements'); + block.add_variable(measurements); + block.chunks.measure.push(b`${measurements} = ${this.var}.r()`); + block.chunks.fix.push(b`${this.var}.f();`); + block.chunks.animate.push(b` + ${this.var}.s(${measurements}); + ${this.var}.a() + `); + } + } + + is_dom_node() { + return super.is_dom_node() && !this.child_dynamic_element; + } + + render_element(block: Block, parent_node: Identifier, parent_nodes: Identifier) { + const { renderer } = this; if (this.node.name === 'noscript') return; @@ -249,7 +380,7 @@ export default class ElementWrapper extends Wrapper { if (renderer.options.hydratable) { if (parent_nodes) { block.chunks.claim.push(b` - ${node} = ${this.get_claim_statement(parent_nodes)}; + ${node} = ${this.get_claim_statement(block, parent_nodes)}; `); if (!this.void && this.node.children.length > 0) { @@ -357,6 +488,8 @@ export default class ElementWrapper extends Wrapper { b`@add_location(${this.var}, ${renderer.file_var}, ${loc.line - 1}, ${loc.column}, ${this.node.start});` ); } + + block.renderer.dirty(this.node.tag_expr.dynamic_dependencies()); } can_use_textcontent() { @@ -364,7 +497,7 @@ export default class ElementWrapper extends Wrapper { } get_render_statement(block: Block) { - const { name, namespace } = this.node; + const { name, namespace, tag_expr } = this.node; if (namespace === namespaces.svg) { return x`@svg_element("${name}")`; @@ -379,22 +512,32 @@ export default class ElementWrapper extends Wrapper { return x`@element_is("${name}", ${is.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`)})`; } - return x`@element("${name}")`; + const reference = tag_expr.manipulate(block); + return x`@element(${reference})`; } - get_claim_statement(nodes: Identifier) { + get_claim_statement(block: Block, nodes: Identifier) { const attributes = this.attributes .filter((attr) => !(attr instanceof SpreadAttributeWrapper) && !attr.property_name) .map((attr) => p`${(attr as StyleAttributeWrapper | AttributeWrapper).name}: true`); - const name = this.node.namespace - ? this.node.name - : this.node.name.toUpperCase(); + let reference; + if (this.node.tag_expr.node.type === 'Literal') { + if (this.node.namespace) { + reference = `"${this.node.tag_expr.node.value}"`; + } else { + reference = `"${(this.node.tag_expr.node.value as String || '').toUpperCase()}"`; + } + } else if (this.node.namespace) { + reference = x`${this.node.tag_expr.manipulate(block)}`; + } else { + reference = x`(${this.node.tag_expr.manipulate(block)} || 'null').toUpperCase()`; + } if (this.node.namespace === namespaces.svg) { - return x`@claim_svg_element(${nodes}, "${name}", { ${attributes} })`; + return x`@claim_svg_element(${nodes}, ${reference}, { ${attributes} })`; } else { - return x`@claim_element(${nodes}, "${name}", { ${attributes} })`; + return x`@claim_element(${nodes}, ${reference}, { ${attributes} })`; } } @@ -847,6 +990,11 @@ export default class ElementWrapper extends Wrapper { ${rect} = ${this.var}.getBoundingClientRect(); `); + if (block.type === CHILD_DYNAMIC_ELEMENT_BLOCK) { + block.chunks.measure.push(b`return ${rect}`); + block.chunks.restore_measurements.push(b`${rect} = #measurement;`); + } + block.chunks.fix.push(b` @fix_position(${this.var}); ${stop_animation}(); @@ -940,7 +1088,7 @@ export default class ElementWrapper extends Wrapper { if (should_cache) { block.chunks.update.push(b` if (${block.renderer.dirty(dependencies)} && (${cached_snippet} !== (${cached_snippet} = ${snippet}))) { - ${updater} + ${updater} } `); } else { diff --git a/src/compiler/compile/render_dom/wrappers/KeyBlock.ts b/src/compiler/compile/render_dom/wrappers/KeyBlock.ts index cbd021ff6c..55402b9ff0 100644 --- a/src/compiler/compile/render_dom/wrappers/KeyBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/KeyBlock.ts @@ -36,6 +36,7 @@ export default class KeyBlockWrapper extends Wrapper { name: renderer.component.get_unique_name('create_key_block'), type: 'key' }); + block.add_dependencies(node.expression.dependencies); renderer.blocks.push(block); } diff --git a/src/compiler/compile/render_ssr/handlers/Element.ts b/src/compiler/compile/render_ssr/handlers/Element.ts index dd096ae79b..7c4c5ba765 100644 --- a/src/compiler/compile/render_ssr/handlers/Element.ts +++ b/src/compiler/compile/render_ssr/handlers/Element.ts @@ -8,8 +8,9 @@ import Expression from '../../nodes/shared/Expression'; import remove_whitespace_children from './utils/remove_whitespace_children'; import fix_attribute_casing from '../../render_dom/wrappers/Element/fix_attribute_casing'; import { namespaces } from '../../../utils/namespaces'; +import { Expression as ESExpression } from 'estree'; -export default function(node: Element, renderer: Renderer, options: RenderOptions) { +export default function (node: Element, renderer: Renderer, options: RenderOptions) { const children = remove_whitespace_children(node.children, node.next); @@ -22,7 +23,8 @@ export default function(node: Element, renderer: Renderer, options: RenderOption node.attributes.some((attribute) => attribute.name === 'contenteditable') ); - renderer.add_string(`<${node.name}`); + renderer.add_string('<'); + add_tag_name(); const class_expression_list = node.classes.map(class_directive => { const { expression, name } = class_directive; @@ -167,14 +169,25 @@ export default function(node: Element, renderer: Renderer, options: RenderOption renderer.add_expression(node_contents); } - if (!is_void(node.name)) { - renderer.add_string(``); - } + add_close_tag(); } else { renderer.render(children, options); + add_close_tag(); + } + function add_close_tag() { if (!is_void(node.name)) { - renderer.add_string(``); + renderer.add_string(''); + } + } + + function add_tag_name() { + if (node.tag_expr.node.type === 'Literal') { + renderer.add_string(node.tag_expr.node.value as string); + } else { + renderer.add_expression(node.tag_expr.node as ESExpression); } } } diff --git a/src/compiler/parse/errors.ts b/src/compiler/parse/errors.ts index ef1f72a8be..63bd5b0919 100644 --- a/src/compiler/parse/errors.ts +++ b/src/compiler/parse/errors.ts @@ -99,6 +99,10 @@ export default { code: `invalid-${slug}-content`, message: `<${name}> cannot have children` }), + invalid_element_definition: { + code: 'invalid-element-definition', + message: 'Invalid element definition' + }, invalid_element_placement: (slug: string, name: string) => ({ code: `invalid-${slug}-placement`, message: `<${name}> tags cannot be inside elements or blocks` @@ -161,6 +165,10 @@ export default { code: 'missing-attribute-value', message: 'Expected value for the attribute' }, + missing_element_definition: { + code: 'missing-element-definition', + message: ' must have a \'this\' attribute' + }, unclosed_script: { code: 'unclosed-script', message: ' + +

{foo}

+{#each nums as num, index} + {@const bar = nums.map((num) => { + const func = (foos, num) => { + return [...foos.map((foo) => foo), num]; + } + return func(foos[index].nums, num); + })} +

bar: {bar}, num: {num}

+{/each} diff --git a/test/runtime/samples/const-tag-each-duplicated-variable1/_config.js b/test/runtime/samples/const-tag-each-duplicated-variable1/_config.js new file mode 100644 index 0000000000..1f3e5856f2 --- /dev/null +++ b/test/runtime/samples/const-tag-each-duplicated-variable1/_config.js @@ -0,0 +1,29 @@ +export default { + html: ` +

bar: 1,2,3,0,2,4,-100,0,100, num: 1

+

bar: 1,2,3,0,2,4,-100,0,100, num: 2

+

bar: 1,2,3,0,2,4,-100,0,100, num: 3

+ `, + async test({ component, target, assert }) { + assert.htmlEqual( + target.innerHTML, + ` +

bar: 1,2,3,0,2,4,-100,0,100, num: 1

+

bar: 1,2,3,0,2,4,-100,0,100, num: 2

+

bar: 1,2,3,0,2,4,-100,0,100, num: 3

+ ` + ); + + component.nums = [1, 2, 3, 4]; + + assert.htmlEqual( + target.innerHTML, + ` +

bar: 1,2,3,0,2,4,-100,0,100, num: 1

+

bar: 1,2,3,0,2,4,-100,0,100, num: 2

+

bar: 1,2,3,0,2,4,-100,0,100, num: 3

+

bar: 1,2,3,0,2,4,-100,0,100, num: 4

+ ` + ); + } +}; diff --git a/test/runtime/samples/const-tag-each-duplicated-variable1/main.svelte b/test/runtime/samples/const-tag-each-duplicated-variable1/main.svelte new file mode 100644 index 0000000000..00c53dd4ff --- /dev/null +++ b/test/runtime/samples/const-tag-each-duplicated-variable1/main.svelte @@ -0,0 +1,19 @@ + + +{#each nums as num} + {@const bar = foos.map((foos) => foos.nums)} +

bar: {bar}, num: {num}

+{/each} diff --git a/test/runtime/samples/const-tag-each-duplicated-variable2/_config.js b/test/runtime/samples/const-tag-each-duplicated-variable2/_config.js new file mode 100644 index 0000000000..16f48218f1 --- /dev/null +++ b/test/runtime/samples/const-tag-each-duplicated-variable2/_config.js @@ -0,0 +1,32 @@ +export default { + html: ` +

foo: dummy-foo, num: dummy-num

+

bar: 1,2,3,2,, num: 1

+

bar: 1,2,3,2,, num: 2

+

bar: 1,2,3,2,, num: 3

+ `, + async test({ component, target, assert }) { + assert.htmlEqual( + target.innerHTML, + ` +

foo: dummy-foo, num: dummy-num

+

bar: 1,2,3,2,, num: 1

+

bar: 1,2,3,2,, num: 2

+

bar: 1,2,3,2,, num: 3

+ ` + ); + + component.nums = [1, 2, 3, 4]; + + assert.htmlEqual( + target.innerHTML, + ` +

foo: dummy-foo, num: dummy-num

+

bar: 1,2,3,2,4,, num: 1

+

bar: 1,2,3,2,4,, num: 2

+

bar: 1,2,3,2,4,, num: 3

+

bar: 1,2,3,2,4,, num: 4

+ ` + ); + } +}; diff --git a/test/runtime/samples/const-tag-each-duplicated-variable2/main.svelte b/test/runtime/samples/const-tag-each-duplicated-variable2/main.svelte new file mode 100644 index 0000000000..1f7afd92fd --- /dev/null +++ b/test/runtime/samples/const-tag-each-duplicated-variable2/main.svelte @@ -0,0 +1,33 @@ + + +

foo: {foo}, num: {num}

+{#each nums as num} + {@const bar = foos.map((foo) => + foo.nums.filter((num) => { + if (Object.keys($$slots).length) { + return false; + } else if (Object.keys(foo).length) { + return nums.includes(num) || default_nums.includes(num); + } else { + return false; + } + }) || num + )} +

bar: {bar}, num: {num}

+{/each} diff --git a/test/runtime/samples/const-tag-each-duplicated-variable3/_config.js b/test/runtime/samples/const-tag-each-duplicated-variable3/_config.js new file mode 100644 index 0000000000..cde4226a25 --- /dev/null +++ b/test/runtime/samples/const-tag-each-duplicated-variable3/_config.js @@ -0,0 +1,29 @@ +export default { + html: ` +

0

+

bar: 1,2,3,1,1,2,3,2, num: 1

+

bar: 0,2,4,1,0,2,4,2, num: 2

+ `, + async test({ component, target, assert }) { + assert.htmlEqual( + target.innerHTML, + ` +

0

+

bar: 1,2,3,1,1,2,3,2, num: 1

+

bar: 0,2,4,1,0,2,4,2, num: 2

+ ` + ); + + component.nums = [1, 2, 3]; + + assert.htmlEqual( + target.innerHTML, + ` +

0

+

bar: 1,2,3,1,1,2,3,2,1,2,3,3, num: 1

+

bar: 0,2,4,1,0,2,4,2,0,2,4,3, num: 2

+

bar: -100,0,100,1,-100,0,100,2,-100,0,100,3, num: 3

+ ` + ); + } +}; diff --git a/test/runtime/samples/const-tag-each-duplicated-variable3/main.svelte b/test/runtime/samples/const-tag-each-duplicated-variable3/main.svelte new file mode 100644 index 0000000000..f559627d23 --- /dev/null +++ b/test/runtime/samples/const-tag-each-duplicated-variable3/main.svelte @@ -0,0 +1,25 @@ + + +

{foo}

+{#each nums as num, index} + {@const bar = nums.map((num) => { + return (function (foos, num) { + return [...foos.map((foo) => foo), num]; + })(foos[index].nums, num); + })} +

bar: {bar}, num: {num}

+{/each} diff --git a/test/runtime/samples/const-tag-each-function/_config.js b/test/runtime/samples/const-tag-each-function/_config.js new file mode 100644 index 0000000000..cde4226a25 --- /dev/null +++ b/test/runtime/samples/const-tag-each-function/_config.js @@ -0,0 +1,29 @@ +export default { + html: ` +

0

+

bar: 1,2,3,1,1,2,3,2, num: 1

+

bar: 0,2,4,1,0,2,4,2, num: 2

+ `, + async test({ component, target, assert }) { + assert.htmlEqual( + target.innerHTML, + ` +

0

+

bar: 1,2,3,1,1,2,3,2, num: 1

+

bar: 0,2,4,1,0,2,4,2, num: 2

+ ` + ); + + component.nums = [1, 2, 3]; + + assert.htmlEqual( + target.innerHTML, + ` +

0

+

bar: 1,2,3,1,1,2,3,2,1,2,3,3, num: 1

+

bar: 0,2,4,1,0,2,4,2,0,2,4,3, num: 2

+

bar: -100,0,100,1,-100,0,100,2,-100,0,100,3, num: 3

+ ` + ); + } +}; diff --git a/test/runtime/samples/const-tag-each-function/main.svelte b/test/runtime/samples/const-tag-each-function/main.svelte new file mode 100644 index 0000000000..2e027c6620 --- /dev/null +++ b/test/runtime/samples/const-tag-each-function/main.svelte @@ -0,0 +1,26 @@ + + +

{foo}

+{#each nums as num, index} + {@const bar = nums.map((num) => { + function func(foos, num) { + return [...foos.map((foo) => foo), num]; + } + return func(foos[index].nums, num); + })} +

bar: {bar}, num: {num}

+{/each} diff --git a/test/runtime/samples/const-tag-shadow-2/_config.js b/test/runtime/samples/const-tag-shadow-2/_config.js new file mode 100644 index 0000000000..9ad4aed48c --- /dev/null +++ b/test/runtime/samples/const-tag-shadow-2/_config.js @@ -0,0 +1,37 @@ +export default { + html: ` +

1

+

3,6,9

+

2

+

3,6,9

+

3

+

3,6,9

+ `, + test({ component, target, assert }) { + component.baz = 5; + assert.htmlEqual( + target.innerHTML, + ` +

1

+

5,10,15

+

2

+

5,10,15

+

3

+

5,10,15

+ ` + ); + + component.array = [3, 4, 5]; + assert.htmlEqual( + target.innerHTML, + ` +

3

+

15,20,25

+

4

+

15,20,25

+

5

+

15,20,25

+ ` + ); + } +}; diff --git a/test/runtime/samples/const-tag-shadow-2/main.svelte b/test/runtime/samples/const-tag-shadow-2/main.svelte new file mode 100644 index 0000000000..c3bcb2f605 --- /dev/null +++ b/test/runtime/samples/const-tag-shadow-2/main.svelte @@ -0,0 +1,15 @@ + + +{#each array as item} +

{foo(item)}

+ {@const bar = array.map((item) => { + const bar = baz; + const foo = (item) => item * bar; + return foo(item); + })} +

{bar}

+{/each} diff --git a/test/runtime/samples/context-setcontext-return/_config.js b/test/runtime/samples/context-setcontext-return/_config.js new file mode 100644 index 0000000000..28a78f1359 --- /dev/null +++ b/test/runtime/samples/context-setcontext-return/_config.js @@ -0,0 +1,5 @@ +export default { + html: ` +
true
+ ` +}; diff --git a/test/runtime/samples/context-setcontext-return/main.svelte b/test/runtime/samples/context-setcontext-return/main.svelte new file mode 100644 index 0000000000..87153ee846 --- /dev/null +++ b/test/runtime/samples/context-setcontext-return/main.svelte @@ -0,0 +1,7 @@ + + +
{a === b}
diff --git a/test/runtime/samples/dynamic-element-action-update/_config.js b/test/runtime/samples/dynamic-element-action-update/_config.js new file mode 100644 index 0000000000..ebbe6f05a9 --- /dev/null +++ b/test/runtime/samples/dynamic-element-action-update/_config.js @@ -0,0 +1,45 @@ +let logs = []; + +export default { + html: ` +

tag is h1.

+ `, + props: { + logs + }, + after_test() { + logs = []; + }, + + async test({ assert, component, target }) { + assert.equal(component.tag, 'h1'); + + assert.deepEqual(logs, ['create: h1,opt1']); + component.opt = 'opt2'; + + assert.equal(component.tag, 'h1'); + assert.deepEqual(logs, ['create: h1,opt1', 'update: h1,opt2']); + + component.tag = 'h2'; + + assert.equal(component.tag, 'h2'); + assert.deepEqual(logs, [ + 'create: h1,opt1', + 'update: h1,opt2', + 'destroy', + 'create: h2,opt2' + ]); + assert.htmlEqual(target.innerHTML, '

tag is h2.

'); + + component.tag = false; + assert.deepEqual(logs, [ + 'create: h1,opt1', + 'update: h1,opt2', + 'destroy', + 'create: h2,opt2', + 'destroy' + ]); + + assert.htmlEqual(target.innerHTML, ''); + } +}; diff --git a/test/runtime/samples/dynamic-element-action-update/main.svelte b/test/runtime/samples/dynamic-element-action-update/main.svelte new file mode 100644 index 0000000000..da269e115e --- /dev/null +++ b/test/runtime/samples/dynamic-element-action-update/main.svelte @@ -0,0 +1,14 @@ + + +tag is {tag}. diff --git a/test/runtime/samples/dynamic-element-animation-2/_config.js b/test/runtime/samples/dynamic-element-animation-2/_config.js new file mode 100644 index 0000000000..27b28bb349 --- /dev/null +++ b/test/runtime/samples/dynamic-element-animation-2/_config.js @@ -0,0 +1,105 @@ +let originalDivGetBoundingClientRect; +let originalSpanGetBoundingClientRect; +let originalParagraphGetBoundingClientRect; + +export default { + skip_if_ssr: true, + props: { + things: [ + { id: 1, name: 'a' }, + { id: 2, name: 'b' }, + { id: 3, name: 'c' }, + { id: 4, name: 'd' }, + { id: 5, name: 'e' } + ], + tag: 'div' + }, + + html: ` +
a
+
b
+
c
+
d
+
e
+ `, + + before_test() { + originalDivGetBoundingClientRect = + window.HTMLDivElement.prototype.getBoundingClientRect; + originalSpanGetBoundingClientRect = + window.HTMLSpanElement.prototype.getBoundingClientRect; + originalParagraphGetBoundingClientRect = + window.HTMLParagraphElement.prototype.getBoundingClientRect; + + window.HTMLDivElement.prototype.getBoundingClientRect = + fakeGetBoundingClientRect; + window.HTMLSpanElement.prototype.getBoundingClientRect = + fakeGetBoundingClientRect; + window.HTMLParagraphElement.prototype.getBoundingClientRect = + fakeGetBoundingClientRect; + + function fakeGetBoundingClientRect() { + const index = [...this.parentNode.children].indexOf(this); + const top = index * 30; + + return { + left: 0, + right: 100, + top, + bottom: top + 20 + }; + } + }, + after_test() { + window.HTMLDivElement.prototype.getBoundingClientRect = + originalDivGetBoundingClientRect; + window.HTMLSpanElement.prototype.getBoundingClientRect = + originalSpanGetBoundingClientRect; + window.HTMLParagraphElement.prototype.getBoundingClientRect = + originalParagraphGetBoundingClientRect; + }, + + async test({ assert, component, target, raf }) { + // switch tag and things at the same time + await component.update('p', [ + { id: 5, name: 'e' }, + { id: 2, name: 'b' }, + { id: 3, name: 'c' }, + { id: 4, name: 'd' }, + { id: 1, name: 'a' } + ]); + + const ps = document.querySelectorAll('p'); + assert.equal(ps[0].dy, 120); + assert.equal(ps[4].dy, -120); + + raf.tick(50); + assert.equal(ps[0].dy, 60); + assert.equal(ps[4].dy, -60); + + raf.tick(100); + assert.equal(ps[0].dy, 0); + assert.equal(ps[4].dy, 0); + + await component.update('span', [ + { id: 1, name: 'a' }, + { id: 2, name: 'b' }, + { id: 3, name: 'c' }, + { id: 4, name: 'd' }, + { id: 5, name: 'e' } + ]); + + const spans = document.querySelectorAll('span'); + + assert.equal(spans[0].dy, 120); + assert.equal(spans[4].dy, -120); + + raf.tick(150); + assert.equal(spans[0].dy, 60); + assert.equal(spans[4].dy, -60); + + raf.tick(200); + assert.equal(spans[0].dy, 0); + assert.equal(spans[4].dy, 0); + } +}; diff --git a/test/runtime/samples/dynamic-element-animation-2/main.svelte b/test/runtime/samples/dynamic-element-animation-2/main.svelte new file mode 100644 index 0000000000..f655a40af9 --- /dev/null +++ b/test/runtime/samples/dynamic-element-animation-2/main.svelte @@ -0,0 +1,26 @@ + + +{#each things as thing (thing.id)} + {thing.name} +{/each} \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-animation/_config.js b/test/runtime/samples/dynamic-element-animation/_config.js new file mode 100644 index 0000000000..e3c57a868e --- /dev/null +++ b/test/runtime/samples/dynamic-element-animation/_config.js @@ -0,0 +1,62 @@ +export default { + props: { + things: [ + { id: 1, name: 'a' }, + { id: 2, name: 'b' }, + { id: 3, name: 'c' }, + { id: 4, name: 'd' }, + { id: 5, name: 'e' } + ], + tag: 'div' + }, + + html: ` +
a
+
b
+
c
+
d
+
e
+ `, + + test({ assert, component, target, raf }) { + component.tag = 'p'; + assert.equal(target.querySelectorAll('p').length, 5); + + component.tag = 'div'; + let divs = target.querySelectorAll('div'); + divs.forEach(div => { + div.getBoundingClientRect = function() { + const index = [...this.parentNode.children].indexOf(this); + const top = index * 30; + + return { + left: 0, + right: 100, + top, + bottom: top + 20 + }; + }; + }); + + component.things = [ + { id: 5, name: 'e' }, + { id: 2, name: 'b' }, + { id: 3, name: 'c' }, + { id: 4, name: 'd' }, + { id: 1, name: 'a' } + ]; + + divs = target.querySelectorAll('div'); + assert.ok(~divs[0].style.animation.indexOf('__svelte')); + assert.equal(divs[1].style.animation, ''); + assert.equal(divs[2].style.animation, ''); + assert.equal(divs[3].style.animation, ''); + assert.ok(~divs[4].style.animation.indexOf('__svelte')); + + raf.tick(100); + assert.deepEqual([ + divs[0].style.animation, + divs[4].style.animation + ], ['', '']); + } +}; diff --git a/test/runtime/samples/dynamic-element-animation/main.svelte b/test/runtime/samples/dynamic-element-animation/main.svelte new file mode 100644 index 0000000000..596d12c77a --- /dev/null +++ b/test/runtime/samples/dynamic-element-animation/main.svelte @@ -0,0 +1,18 @@ + + +{#each things as thing (thing.id)} + {thing.name} +{/each} \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-attribute/_config.js b/test/runtime/samples/dynamic-element-attribute/_config.js new file mode 100644 index 0000000000..6e7c340437 --- /dev/null +++ b/test/runtime/samples/dynamic-element-attribute/_config.js @@ -0,0 +1,17 @@ +export default { + props: { + tag: 'div' + }, + html: '
Foo
', + + test({ assert, component, target }) { + component.tag = 'h1'; + + assert.htmlEqual( + target.innerHTML, + ` +

Foo

+ ` + ); + } +}; diff --git a/test/runtime/samples/dynamic-element-attribute/main.svelte b/test/runtime/samples/dynamic-element-attribute/main.svelte new file mode 100644 index 0000000000..2498e06de9 --- /dev/null +++ b/test/runtime/samples/dynamic-element-attribute/main.svelte @@ -0,0 +1,5 @@ + + +Foo diff --git a/test/runtime/samples/dynamic-element-binding-invalid/_config.js b/test/runtime/samples/dynamic-element-binding-invalid/_config.js new file mode 100644 index 0000000000..14c6d775dc --- /dev/null +++ b/test/runtime/samples/dynamic-element-binding-invalid/_config.js @@ -0,0 +1,3 @@ +export default { + error: "'value' is not a valid binding on elements" +}; diff --git a/test/runtime/samples/dynamic-element-binding-invalid/main.svelte b/test/runtime/samples/dynamic-element-binding-invalid/main.svelte new file mode 100644 index 0000000000..45f8f96061 --- /dev/null +++ b/test/runtime/samples/dynamic-element-binding-invalid/main.svelte @@ -0,0 +1,6 @@ + + + diff --git a/test/runtime/samples/dynamic-element-binding-this/_config.js b/test/runtime/samples/dynamic-element-binding-this/_config.js new file mode 100644 index 0000000000..e0722c9375 --- /dev/null +++ b/test/runtime/samples/dynamic-element-binding-this/_config.js @@ -0,0 +1,8 @@ +export default { + html: '
', + + test({ assert, component, target }) { + const div = target.querySelector('div'); + assert.equal(div, component.foo); + } +}; diff --git a/test/runtime/samples/dynamic-element-binding-this/main.svelte b/test/runtime/samples/dynamic-element-binding-this/main.svelte new file mode 100644 index 0000000000..75e8b02ce1 --- /dev/null +++ b/test/runtime/samples/dynamic-element-binding-this/main.svelte @@ -0,0 +1,6 @@ + + + diff --git a/test/runtime/samples/dynamic-element-change-tag/_config.js b/test/runtime/samples/dynamic-element-change-tag/_config.js new file mode 100644 index 0000000000..9e4bf6fd32 --- /dev/null +++ b/test/runtime/samples/dynamic-element-change-tag/_config.js @@ -0,0 +1,17 @@ +export default { + props: { + tag: 'div' + }, + html: '
Foo
', + + test({ assert, component, target }) { + component.tag = 'h1'; + + assert.htmlEqual( + target.innerHTML, + ` +

Foo

+ ` + ); + } +}; diff --git a/test/runtime/samples/dynamic-element-change-tag/main.svelte b/test/runtime/samples/dynamic-element-change-tag/main.svelte new file mode 100644 index 0000000000..a9c4d5c00c --- /dev/null +++ b/test/runtime/samples/dynamic-element-change-tag/main.svelte @@ -0,0 +1,5 @@ + + +Foo \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-empty-tag/_config.js b/test/runtime/samples/dynamic-element-empty-tag/_config.js new file mode 100644 index 0000000000..22dc25b41e --- /dev/null +++ b/test/runtime/samples/dynamic-element-empty-tag/_config.js @@ -0,0 +1,3 @@ +export default { + html: '' +}; diff --git a/test/runtime/samples/dynamic-element-empty-tag/main.svelte b/test/runtime/samples/dynamic-element-empty-tag/main.svelte new file mode 100644 index 0000000000..e3889ce0f5 --- /dev/null +++ b/test/runtime/samples/dynamic-element-empty-tag/main.svelte @@ -0,0 +1,5 @@ + + +Foo diff --git a/test/runtime/samples/dynamic-element-event-handler1/_config.js b/test/runtime/samples/dynamic-element-event-handler1/_config.js new file mode 100644 index 0000000000..03b8f7879d --- /dev/null +++ b/test/runtime/samples/dynamic-element-event-handler1/_config.js @@ -0,0 +1,21 @@ +let clicked = false; +function handler() { + clicked = true; +} + +export default { + props: { + handler + }, + html: '', + + test({ assert, target }) { + assert.equal(clicked, false); + + const button = target.querySelector('button'); + const click = new window.MouseEvent('click'); + button.dispatchEvent(click); + + assert.equal(clicked, true); + } +}; diff --git a/test/runtime/samples/dynamic-element-event-handler1/main.svelte b/test/runtime/samples/dynamic-element-event-handler1/main.svelte new file mode 100644 index 0000000000..7a7fef9c22 --- /dev/null +++ b/test/runtime/samples/dynamic-element-event-handler1/main.svelte @@ -0,0 +1,6 @@ + + +Foo \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-event-handler2/_config.js b/test/runtime/samples/dynamic-element-event-handler2/_config.js new file mode 100644 index 0000000000..22cbe735a7 --- /dev/null +++ b/test/runtime/samples/dynamic-element-event-handler2/_config.js @@ -0,0 +1,23 @@ +let clicked = false; +function handler() { + clicked = true; +} + +export default { + props: { + tag: 'div', + handler + }, + html: '
Foo
', + + test({ assert, component, target }) { + assert.equal(clicked, false); + + component.tag = 'button'; + const button = target.querySelector('button'); + const click = new window.MouseEvent('click'); + button.dispatchEvent(click); + + assert.equal(clicked, true); + } +}; diff --git a/test/runtime/samples/dynamic-element-event-handler2/main.svelte b/test/runtime/samples/dynamic-element-event-handler2/main.svelte new file mode 100644 index 0000000000..f2534c1f62 --- /dev/null +++ b/test/runtime/samples/dynamic-element-event-handler2/main.svelte @@ -0,0 +1,6 @@ + + +Foo diff --git a/test/runtime/samples/dynamic-element-expression/_config.js b/test/runtime/samples/dynamic-element-expression/_config.js new file mode 100644 index 0000000000..acad91c901 --- /dev/null +++ b/test/runtime/samples/dynamic-element-expression/_config.js @@ -0,0 +1,3 @@ +export default { + html: '
Foo
' +}; diff --git a/test/runtime/samples/dynamic-element-expression/main.svelte b/test/runtime/samples/dynamic-element-expression/main.svelte new file mode 100644 index 0000000000..7ec11e4ef6 --- /dev/null +++ b/test/runtime/samples/dynamic-element-expression/main.svelte @@ -0,0 +1 @@ +Foo \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-invalid-this/_config.js b/test/runtime/samples/dynamic-element-invalid-this/_config.js new file mode 100644 index 0000000000..3aaa554991 --- /dev/null +++ b/test/runtime/samples/dynamic-element-invalid-this/_config.js @@ -0,0 +1,9 @@ +export default { + compileOptions: { + dev: true + }, + props: { + tag: 123 + }, + error: ' expects "this" attribute to be a string.' +}; diff --git a/test/runtime/samples/dynamic-element-invalid-this/main.svelte b/test/runtime/samples/dynamic-element-invalid-this/main.svelte new file mode 100644 index 0000000000..bcc2c293bb --- /dev/null +++ b/test/runtime/samples/dynamic-element-invalid-this/main.svelte @@ -0,0 +1,5 @@ + + + diff --git a/test/runtime/samples/dynamic-element-null-tag/_config.js b/test/runtime/samples/dynamic-element-null-tag/_config.js new file mode 100644 index 0000000000..22dc25b41e --- /dev/null +++ b/test/runtime/samples/dynamic-element-null-tag/_config.js @@ -0,0 +1,3 @@ +export default { + html: '' +}; diff --git a/test/runtime/samples/dynamic-element-null-tag/main.svelte b/test/runtime/samples/dynamic-element-null-tag/main.svelte new file mode 100644 index 0000000000..58dc96ff2a --- /dev/null +++ b/test/runtime/samples/dynamic-element-null-tag/main.svelte @@ -0,0 +1,5 @@ + + +Foo diff --git a/test/runtime/samples/dynamic-element-pass-props/_config.js b/test/runtime/samples/dynamic-element-pass-props/_config.js new file mode 100644 index 0000000000..35f8b7abdf --- /dev/null +++ b/test/runtime/samples/dynamic-element-pass-props/_config.js @@ -0,0 +1,16 @@ +let clicked = false; + +export default { + props: { + tag: 'div', + onClick: () => clicked = true + }, + html: '
Foo
', + + async test({ assert, target, window }) { + const div = target.querySelector('div'); + await div.dispatchEvent(new window.MouseEvent('click')); + + assert.equal(clicked, true); + } +}; diff --git a/test/runtime/samples/dynamic-element-pass-props/main.svelte b/test/runtime/samples/dynamic-element-pass-props/main.svelte new file mode 100644 index 0000000000..6a54a93f27 --- /dev/null +++ b/test/runtime/samples/dynamic-element-pass-props/main.svelte @@ -0,0 +1,6 @@ + + +Foo \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-slot/Foo.svelte b/test/runtime/samples/dynamic-element-slot/Foo.svelte new file mode 100644 index 0000000000..51dda4b7e3 --- /dev/null +++ b/test/runtime/samples/dynamic-element-slot/Foo.svelte @@ -0,0 +1,7 @@ +

Foo

+
+ +
+
+ +
diff --git a/test/runtime/samples/dynamic-element-slot/_config.js b/test/runtime/samples/dynamic-element-slot/_config.js new file mode 100644 index 0000000000..aa9da522a3 --- /dev/null +++ b/test/runtime/samples/dynamic-element-slot/_config.js @@ -0,0 +1,29 @@ +export default { + props: { + x: true + }, + + html: ` +

Foo

+
+

This is default slot

+
+
+

This is other slot

+
+ `, + + test({ assert, component, target }) { + component.tag = 'h2'; + + assert.htmlEqual(target.innerHTML, ` +

Foo

+
+

This is default slot

+
+
+

This is other slot

+
+ `); + } +}; diff --git a/test/runtime/samples/dynamic-element-slot/main.svelte b/test/runtime/samples/dynamic-element-slot/main.svelte new file mode 100644 index 0000000000..4b1cd81969 --- /dev/null +++ b/test/runtime/samples/dynamic-element-slot/main.svelte @@ -0,0 +1,10 @@ + + + + This is default slot + This is other slot + + diff --git a/test/runtime/samples/dynamic-element-store/_config.js b/test/runtime/samples/dynamic-element-store/_config.js new file mode 100644 index 0000000000..ded19eef79 --- /dev/null +++ b/test/runtime/samples/dynamic-element-store/_config.js @@ -0,0 +1,3 @@ +export default { + html: '
' +}; diff --git a/test/runtime/samples/dynamic-element-store/main.svelte b/test/runtime/samples/dynamic-element-store/main.svelte new file mode 100644 index 0000000000..84a577ecee --- /dev/null +++ b/test/runtime/samples/dynamic-element-store/main.svelte @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-string/_config.js b/test/runtime/samples/dynamic-element-string/_config.js new file mode 100644 index 0000000000..acad91c901 --- /dev/null +++ b/test/runtime/samples/dynamic-element-string/_config.js @@ -0,0 +1,3 @@ +export default { + html: '
Foo
' +}; diff --git a/test/runtime/samples/dynamic-element-string/main.svelte b/test/runtime/samples/dynamic-element-string/main.svelte new file mode 100644 index 0000000000..62d65d5f20 --- /dev/null +++ b/test/runtime/samples/dynamic-element-string/main.svelte @@ -0,0 +1 @@ +Foo \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-template-literals/_config.js b/test/runtime/samples/dynamic-element-template-literals/_config.js new file mode 100644 index 0000000000..d37164b4d8 --- /dev/null +++ b/test/runtime/samples/dynamic-element-template-literals/_config.js @@ -0,0 +1,21 @@ +export default { + props: { + size: 1 + }, + html: '

This is h1 tag

', + + test({ assert, component, target }) { + const h1 = target.firstChild; + component.size = 2; + + assert.htmlEqual( + target.innerHTML, + ` +

This is h2 tag

+ ` + ); + + const h2 = target.firstChild; + assert.notEqual(h1, h2); + } +}; diff --git a/test/runtime/samples/dynamic-element-template-literals/main.svelte b/test/runtime/samples/dynamic-element-template-literals/main.svelte new file mode 100644 index 0000000000..84b63dcca7 --- /dev/null +++ b/test/runtime/samples/dynamic-element-template-literals/main.svelte @@ -0,0 +1,5 @@ + + +This is h{size} tag diff --git a/test/runtime/samples/dynamic-element-transition/_config.js b/test/runtime/samples/dynamic-element-transition/_config.js new file mode 100644 index 0000000000..cb8474afc1 --- /dev/null +++ b/test/runtime/samples/dynamic-element-transition/_config.js @@ -0,0 +1,17 @@ +export default { + test({ assert, component, target, raf }) { + component.visible = true; + const h1 = target.querySelector('h1'); + assert.equal(h1.style.animation, '__svelte_3809512021_0 100ms linear 0ms 1 both'); + + raf.tick(150); + component.tag = 'h2'; + const h2 = target.querySelector('h2'); + assert.equal(h1.style.animation, ''); + assert.equal(h2.style.animation, ''); + + raf.tick(50); + component.visible = false; + assert.equal(h2.style.animation, '__svelte_3750847757_0 100ms linear 0ms 1 both'); + } +}; diff --git a/test/runtime/samples/dynamic-element-transition/main.svelte b/test/runtime/samples/dynamic-element-transition/main.svelte new file mode 100644 index 0000000000..b8c0eff0bd --- /dev/null +++ b/test/runtime/samples/dynamic-element-transition/main.svelte @@ -0,0 +1,17 @@ + + +{#if visible} + +{/if} diff --git a/test/runtime/samples/dynamic-element-undefined-tag/_config.js b/test/runtime/samples/dynamic-element-undefined-tag/_config.js new file mode 100644 index 0000000000..d0bd665d3d --- /dev/null +++ b/test/runtime/samples/dynamic-element-undefined-tag/_config.js @@ -0,0 +1,19 @@ +export default { + html: '', + test({ component, target, assert }) { + component.tag = 'h1'; + assert.htmlEqual(target.innerHTML, '

Foo

'); + + component.tag = null; + assert.htmlEqual(target.innerHTML, ''); + + component.tag = 'div'; + assert.htmlEqual(target.innerHTML, '
Foo
'); + + component.tag = false; + assert.htmlEqual(target.innerHTML, ''); + + component.tag = 'span'; + assert.htmlEqual(target.innerHTML, 'Foo'); + } +}; diff --git a/test/runtime/samples/dynamic-element-undefined-tag/main.svelte b/test/runtime/samples/dynamic-element-undefined-tag/main.svelte new file mode 100644 index 0000000000..6aca93ca13 --- /dev/null +++ b/test/runtime/samples/dynamic-element-undefined-tag/main.svelte @@ -0,0 +1,5 @@ + + +Foo diff --git a/test/runtime/samples/dynamic-element-variable/_config.js b/test/runtime/samples/dynamic-element-variable/_config.js new file mode 100644 index 0000000000..20e0fa9418 --- /dev/null +++ b/test/runtime/samples/dynamic-element-variable/_config.js @@ -0,0 +1,20 @@ +export default { + props: { + tag: 'div', + text: 'Foo' + }, + html: '
Foo
', + + test({ assert, component, target }) { + const div = target.firstChild; + component.tag = 'nav'; + component.text = 'Bar'; + + assert.htmlEqual(target.innerHTML, ` + + `); + + const h1 = target.firstChild; + assert.notEqual(div, h1); + } +}; diff --git a/test/runtime/samples/dynamic-element-variable/main.svelte b/test/runtime/samples/dynamic-element-variable/main.svelte new file mode 100644 index 0000000000..d60953bba5 --- /dev/null +++ b/test/runtime/samples/dynamic-element-variable/main.svelte @@ -0,0 +1,6 @@ + + +{text} \ No newline at end of file diff --git a/test/runtime/samples/key-block-component-slot/Component1.svelte b/test/runtime/samples/key-block-component-slot/Component1.svelte new file mode 100644 index 0000000000..d0ea817d54 --- /dev/null +++ b/test/runtime/samples/key-block-component-slot/Component1.svelte @@ -0,0 +1 @@ + diff --git a/test/runtime/samples/key-block-component-slot/Component2.svelte b/test/runtime/samples/key-block-component-slot/Component2.svelte new file mode 100644 index 0000000000..ca04744936 --- /dev/null +++ b/test/runtime/samples/key-block-component-slot/Component2.svelte @@ -0,0 +1,12 @@ + diff --git a/test/runtime/samples/key-block-component-slot/_config.js b/test/runtime/samples/key-block-component-slot/_config.js new file mode 100644 index 0000000000..8fbbeaec64 --- /dev/null +++ b/test/runtime/samples/key-block-component-slot/_config.js @@ -0,0 +1,18 @@ +const logs = []; + +export default { + html: '', + props: { + logs + }, + async test({ assert, component, target, raf }) { + assert.deepEqual(logs, ['mount']); + + const button = target.querySelector('button'); + + const click = new window.MouseEvent('click'); + await button.dispatchEvent(click); + + assert.deepEqual(logs, ['mount', 'unmount', 'mount']); + } +}; diff --git a/test/runtime/samples/key-block-component-slot/main.svelte b/test/runtime/samples/key-block-component-slot/main.svelte new file mode 100644 index 0000000000..77fde32d9c --- /dev/null +++ b/test/runtime/samples/key-block-component-slot/main.svelte @@ -0,0 +1,17 @@ + + + + {#key reset} + + {/key} + + + \ No newline at end of file diff --git a/test/server-side-rendering/samples/dynamic-element-string/_expected.html b/test/server-side-rendering/samples/dynamic-element-string/_expected.html new file mode 100644 index 0000000000..cb98432e14 --- /dev/null +++ b/test/server-side-rendering/samples/dynamic-element-string/_expected.html @@ -0,0 +1 @@ +
Foo
diff --git a/test/server-side-rendering/samples/dynamic-element-string/main.svelte b/test/server-side-rendering/samples/dynamic-element-string/main.svelte new file mode 100644 index 0000000000..62d65d5f20 --- /dev/null +++ b/test/server-side-rendering/samples/dynamic-element-string/main.svelte @@ -0,0 +1 @@ +Foo \ No newline at end of file diff --git a/test/server-side-rendering/samples/dynamic-element-variable/_expected.html b/test/server-side-rendering/samples/dynamic-element-variable/_expected.html new file mode 100644 index 0000000000..3ae445f54c --- /dev/null +++ b/test/server-side-rendering/samples/dynamic-element-variable/_expected.html @@ -0,0 +1,2 @@ +

Foo

+
Bar
\ No newline at end of file diff --git a/test/server-side-rendering/samples/dynamic-element-variable/main.svelte b/test/server-side-rendering/samples/dynamic-element-variable/main.svelte new file mode 100644 index 0000000000..9aaba18bf1 --- /dev/null +++ b/test/server-side-rendering/samples/dynamic-element-variable/main.svelte @@ -0,0 +1,7 @@ + + +Foo +Bar \ No newline at end of file diff --git a/test/validator/samples/dynamic-element-invalid-tag/errors.json b/test/validator/samples/dynamic-element-invalid-tag/errors.json new file mode 100644 index 0000000000..c8d3c52490 --- /dev/null +++ b/test/validator/samples/dynamic-element-invalid-tag/errors.json @@ -0,0 +1,17 @@ +[ + { + "message": "Invalid element definition", + "code": "invalid-element-definition", + "start": { + "line": 2, + "column": 17, + "character": 23 + }, + "end": { + "line": 2, + "column": 17, + "character": 23 + }, + "pos": 23 + } +] diff --git a/test/validator/samples/dynamic-element-invalid-tag/input.svelte b/test/validator/samples/dynamic-element-invalid-tag/input.svelte new file mode 100644 index 0000000000..bc6b8be822 --- /dev/null +++ b/test/validator/samples/dynamic-element-invalid-tag/input.svelte @@ -0,0 +1,3 @@ +
+ foo +
diff --git a/test/validator/samples/dynamic-element-missing-tag/errors.json b/test/validator/samples/dynamic-element-missing-tag/errors.json new file mode 100644 index 0000000000..8243deeb80 --- /dev/null +++ b/test/validator/samples/dynamic-element-missing-tag/errors.json @@ -0,0 +1,15 @@ +[{ + "code": "missing-element-definition", + "message": " must have a 'this' attribute", + "start": { + "line": 2, + "column": 1, + "character": 7 + }, + "end": { + "line": 2, + "column": 1, + "character": 7 + }, + "pos": 7 +}] diff --git a/test/validator/samples/dynamic-element-missing-tag/input.svelte b/test/validator/samples/dynamic-element-missing-tag/input.svelte new file mode 100644 index 0000000000..4b645d25a1 --- /dev/null +++ b/test/validator/samples/dynamic-element-missing-tag/input.svelte @@ -0,0 +1,3 @@ +
+ foo +
diff --git a/test/validator/samples/dynamic-element-this/errors.json b/test/validator/samples/dynamic-element-this/errors.json new file mode 100644 index 0000000000..7d61c20ee2 --- /dev/null +++ b/test/validator/samples/dynamic-element-this/errors.json @@ -0,0 +1,15 @@ +[{ + "code": "unexpected-reserved-word", + "message": "'this' is a reserved word in JavaScript and cannot be used here", + "start": { + "line": 2, + "column": 18, + "character": 24 + }, + "end": { + "line": 2, + "column": 18, + "character": 24 + }, + "pos": 24 + }] diff --git a/test/validator/samples/dynamic-element-this/input.svelte b/test/validator/samples/dynamic-element-this/input.svelte new file mode 100644 index 0000000000..1a5079b924 --- /dev/null +++ b/test/validator/samples/dynamic-element-this/input.svelte @@ -0,0 +1,3 @@ +
+ foo +