diff --git a/site/content/tutorial/15-context/01-context-api/text.md b/site/content/tutorial/15-context/01-context-api/text.md index ef146772c5..e577b9847e 100644 --- a/site/content/tutorial/15-context/01-context-api/text.md +++ b/site/content/tutorial/15-context/01-context-api/text.md @@ -44,6 +44,6 @@ In `mapbox.js` you'll see this line: const key = {}; ``` -We can use anything as a key — we could do `setContext('mapbox', ...)` for example. The downside of using a string is that different component libraries might accidentally use the same one; using an object literal means the keys are guaranteed not to conflict in any circumstance, even when you have multiple different contexts operating across many component layers. +We can use anything as a key — we could do `setContext('mapbox', ...)` for example. The downside of using a string is that different component libraries might accidentally use the same one; using an object literal means the keys are guaranteed not to conflict in any circumstance (since an object only has referential equality to itself, i.e. `{} !== {}` whereas `"x" === "x"`), even when you have multiple different contexts operating across many component layers. > Remember that context is not inherently reactive. If you need context values to be reactive then you can pass a store into context, which *will* be reactive. diff --git a/src/compiler/compile/nodes/EachBlock.ts b/src/compiler/compile/nodes/EachBlock.ts index adaa46b8db..225f3b441e 100644 --- a/src/compiler/compile/nodes/EachBlock.ts +++ b/src/compiler/compile/nodes/EachBlock.ts @@ -83,16 +83,16 @@ export default class EachBlock extends AbstractBlock { this.scope.add(context.key.name, this.expression.dependencies, this); }); - this.key = info.key - ? new Expression(component, this, this.scope, info.key) - : null; - if (this.index) { // index can only change if this is a keyed each block - const dependencies = this.key ? this.expression.dependencies : new Set([]); + const dependencies = info.key ? this.expression.dependencies : new Set([]); this.scope.add(this.index, dependencies, this); } + this.key = info.key + ? new Expression(component, this, this.scope, info.key) + : null; + this.has_animation = false; this.children = map_children(component, this, this.scope, info.children); diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 999b4df8e1..80b5c6df00 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -703,16 +703,20 @@ export default class Element extends Node { return this.name === 'audio' || this.name === 'video'; } - add_css_class(class_name = this.component.stylesheet.id) { + add_css_class() { + const { id } = this.component.stylesheet; + const class_attribute = this.attributes.find(a => a.name === 'class'); + if (class_attribute && !class_attribute.is_true) { if (class_attribute.chunks.length === 1 && class_attribute.chunks[0].type === 'Text') { - (class_attribute.chunks[0] as Text).data += ` ${class_name}`; + (class_attribute.chunks[0] as Text).data += ` ${id}`; } else { (class_attribute.chunks as Node[]).push( new Text(this.component, this, this.scope, { type: 'Text', - data: ` ${class_name}` + data: ` ${id}`, + synthetic: true }) ); } @@ -721,7 +725,7 @@ export default class Element extends Node { new Attribute(this.component, this, this.scope, { type: 'Attribute', name: 'class', - value: [{ type: 'Text', data: class_name }] + value: [{ type: 'Text', data: id, synthetic: true }] }) ); } diff --git a/src/compiler/compile/nodes/Text.ts b/src/compiler/compile/nodes/Text.ts index eff3efe06e..bfd28a5073 100644 --- a/src/compiler/compile/nodes/Text.ts +++ b/src/compiler/compile/nodes/Text.ts @@ -6,9 +6,11 @@ import { INode } from './interfaces'; export default class Text extends Node { type: 'Text'; data: string; + synthetic: boolean; constructor(component: Component, parent: INode, scope: TemplateScope, info: any) { super(component, parent, scope, info); this.data = info.data; + this.synthetic = info.synthetic || false; } } diff --git a/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts b/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts index f55e731fdb..f83b1f2acf 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts @@ -5,6 +5,7 @@ import ElementWrapper from './index'; import { stringify } from '../../../utils/stringify'; import deindent from '../../../utils/deindent'; import Expression from '../../../nodes/shared/Expression'; +import Text from '../../../nodes/Text'; export default class AttributeWrapper { node: Attribute; @@ -84,19 +85,13 @@ export default class AttributeWrapper { value = (this.node.chunks[0] as Expression).render(block); } else { // '{foo} {bar}' — treat as string concatenation - value = - (this.node.chunks[0].type === 'Text' ? '' : `"" + `) + - this.node.chunks - .map((chunk) => { - if (chunk.type === 'Text') { - return stringify(chunk.data); - } else { - return chunk.get_precedence() <= 13 - ? `(${chunk.render()})` - : chunk.render(); - } - }) - .join(' + '); + const prefix = this.node.chunks[0].type === 'Text' ? '' : `"" + `; + + const text = this.node.name === 'class' + ? this.get_class_name_text() + : this.render_chunks().join(' + '); + + value = `${prefix}${text}`; } const is_select_value_attribute = @@ -210,6 +205,31 @@ export default class AttributeWrapper { } } + get_class_name_text() { + const scoped_css = this.node.chunks.some((chunk: Text) => chunk.synthetic); + const rendered = this.render_chunks(); + + if (scoped_css && rendered.length === 2) { + // we have a situation like class={possiblyUndefined} + rendered[0] = `@null_to_empty(${rendered[0]})`; + } + + return rendered.join(' + '); + } + + render_chunks() { + return this.node.chunks.map((chunk) => { + if (chunk.type === 'Text') { + return stringify(chunk.data); + } + + const rendered = chunk.render(); + return chunk.get_precedence() <= 13 + ? `(${rendered})` + : rendered; + }); + } + stringify() { if (this.node.is_true) return ''; diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 7c0fd62892..7480c31cc7 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -816,29 +816,4 @@ export default class ElementWrapper extends Wrapper { } }); } - - // todo: looks to be dead code copypasted from Element.add_css_class in src/compile/nodes/Element.ts - // add_css_class(class_name = this.component.stylesheet.id) { - // const class_attribute = this.attributes.find(a => a.name === 'class'); - // if (class_attribute && !class_attribute.is_true) { - // if (class_attribute.chunks.length === 1 && class_attribute.chunks[0].type === 'Text') { - // (class_attribute.chunks[0] as Text).data += ` ${class_name}`; - // } else { - // (class_attribute.chunks as Node[]).push( - // new Text(this.component, this, this.scope, { - // type: 'Text', - // data: ` ${class_name}` - // }) - // ); - // } - // } else { - // this.attributes.push( - // new Attribute(this.component, this, this.scope, { - // type: 'Attribute', - // name: 'class', - // value: [{ type: 'Text', data: class_name }] - // }) - // ); - // } - // } } diff --git a/src/compiler/compile/render_ssr/handlers/Element.ts b/src/compiler/compile/render_ssr/handlers/Element.ts index 0cd101df72..146324f2a4 100644 --- a/src/compiler/compile/render_ssr/handlers/Element.ts +++ b/src/compiler/compile/render_ssr/handlers/Element.ts @@ -2,7 +2,7 @@ import { is_void, quote_prop_if_necessary, quote_name_if_necessary } from '../.. import Attribute from '../../nodes/Attribute'; import Class from '../../nodes/Class'; import { snip } from '../../utils/snip'; -import { stringify_attribute } from '../../utils/stringify_attribute'; +import { stringify_attribute, stringify_class_attribute } from '../../utils/stringify_attribute'; import { get_slot_scope } from './shared/get_slot_scope'; import Renderer, { RenderOptions } from '../Renderer'; import Element from '../../nodes/Element'; @@ -111,9 +111,9 @@ export default function(node: Element, renderer: Renderer, options: RenderOption args.push(`{ ${quote_name_if_necessary(attribute.name)}: ${snip(attribute.chunks[0])} }`); } else if (attribute.name === 'class' && class_expression) { // Add class expression - args.push(`{ ${quote_name_if_necessary(attribute.name)}: [\`${stringify_attribute(attribute, true)}\`, \`\${${class_expression}}\`].join(' ').trim() }`); + args.push(`{ ${quote_name_if_necessary(attribute.name)}: [\`${stringify_class_attribute(attribute)}\`, \`\${${class_expression}}\`].join(' ').trim() }`); } else { - args.push(`{ ${quote_name_if_necessary(attribute.name)}: \`${stringify_attribute(attribute, true)}\` }`); + args.push(`{ ${quote_name_if_necessary(attribute.name)}: \`${attribute.name === 'class' ? stringify_class_attribute(attribute) : stringify_attribute(attribute, true)}\` }`); } } }); @@ -136,14 +136,13 @@ export default function(node: Element, renderer: Renderer, options: RenderOption opening_tag += '${' + snip(attribute.chunks[0]) + ' ? " ' + attribute.name + '" : "" }'; } else if (attribute.name === 'class' && class_expression) { add_class_attribute = false; - opening_tag += ` class="\${[\`${stringify_attribute(attribute, true)}\`, ${class_expression}].join(' ').trim() }"`; + opening_tag += ` class="\${[\`${stringify_class_attribute(attribute)}\`, ${class_expression}].join(' ').trim() }"`; } else if (attribute.chunks.length === 1 && attribute.chunks[0].type !== 'Text') { const { name } = attribute; const snippet = snip(attribute.chunks[0]); - - opening_tag += '${(v => v == null ? "" : ` ' + name + '="${@escape(' + snippet + ')}"`)(' + snippet + ')}'; + opening_tag += '${@add_attribute("' + name + '", ' + snippet + ', ' + (boolean_attributes.has(name) ? 1 : 0) + ')}'; } else { - opening_tag += ` ${attribute.name}="${stringify_attribute(attribute, true)}"`; + opening_tag += ` ${attribute.name}="${attribute.name === 'class' ? stringify_class_attribute(attribute) : stringify_attribute(attribute, true)}"`; } }); } @@ -165,7 +164,7 @@ export default function(node: Element, renderer: Renderer, options: RenderOption node_contents = '${(' + snippet + ') || ""}'; } else { const snippet = snip(expression); - opening_tag += '${@add_attribute("' + name + '", ' + snippet + ')}'; + opening_tag += '${@add_attribute("' + name + '", ' + snippet + ', 1)}'; } }); diff --git a/src/compiler/compile/utils/stringify_attribute.ts b/src/compiler/compile/utils/stringify_attribute.ts index ee3450edbb..c837659b93 100644 --- a/src/compiler/compile/utils/stringify_attribute.ts +++ b/src/compiler/compile/utils/stringify_attribute.ts @@ -1,6 +1,16 @@ import Attribute from '../nodes/Attribute'; import { escape_template, escape } from './stringify'; import { snip } from './snip'; +import Text from '../nodes/Text'; + +export function stringify_class_attribute(attribute: Attribute) { + // handle special case — `class={possiblyUndefined}` with scoped CSS + if (attribute.chunks.length === 2 && (attribute.chunks[1] as Text).synthetic) { + return '${@escape(@null_to_empty(' + snip(attribute.chunks[0]) + '))}' + (attribute.chunks[1] as Text).data; + } + + return stringify_attribute(attribute, true); +} export function stringify_attribute(attribute: Attribute, is_ssr: boolean) { return attribute.chunks diff --git a/src/runtime/internal/ssr.ts b/src/runtime/internal/ssr.ts index d84efc7314..d8fbf15f0a 100644 --- a/src/runtime/internal/ssr.ts +++ b/src/runtime/internal/ssr.ts @@ -120,9 +120,9 @@ export function create_ssr_component(fn) { }; } -export function add_attribute(name, value) { - if (!value) return ''; - return ` ${name}${value === true ? '' : `=${typeof value === 'string' ? JSON.stringify(value) : `"${value}"`}`}`; +export function add_attribute(name, value, boolean) { + if (value == null || (boolean && !value)) return ''; + return ` ${name}${value === true ? '' : `=${typeof value === 'string' ? JSON.stringify(escape(value)) : `"${value}"`}`}`; } export function add_classes(classes) { diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts index 41a2ee890b..e457eb0505 100644 --- a/src/runtime/internal/utils.ts +++ b/src/runtime/internal/utils.ts @@ -96,3 +96,7 @@ export function once(fn) { fn.call(this, ...args); }; } + +export function null_to_empty(value) { + return value == null ? '' : value; +} diff --git a/test/runtime/samples/attribute-false/_config.js b/test/runtime/samples/attribute-false/_config.js new file mode 100644 index 0000000000..9fd08a2a48 --- /dev/null +++ b/test/runtime/samples/attribute-false/_config.js @@ -0,0 +1,3 @@ +export default { + html: `
`, +}; diff --git a/test/runtime/samples/attribute-false/main.svelte b/test/runtime/samples/attribute-false/main.svelte new file mode 100644 index 0000000000..ca78020a39 --- /dev/null +++ b/test/runtime/samples/attribute-false/main.svelte @@ -0,0 +1 @@ + diff --git a/test/runtime/samples/attribute-null-classname-no-style/_config.js b/test/runtime/samples/attribute-null-classname-no-style/_config.js new file mode 100644 index 0000000000..4a78b680ef --- /dev/null +++ b/test/runtime/samples/attribute-null-classname-no-style/_config.js @@ -0,0 +1,42 @@ +export default { + props: { + testName: "testClassName" + }, + + html: ``, + + test({ assert, component, target }) { + const div = target.querySelector('div'); + assert.equal(div.className, 'testClassName'); + + component.testName = null; + assert.equal(div.className, ''); + + component.testName = undefined; + assert.equal(div.className, ''); + + component.testName = undefined + ''; + assert.equal(div.className, 'undefined'); + + component.testName = null + ''; + assert.equal(div.className, 'null'); + + component.testName = 1; + assert.equal(div.className, '1'); + + component.testName = 0; + assert.equal(div.className, '0'); + + component.testName = false; + assert.equal(div.className, 'false'); + + component.testName = true; + assert.equal(div.className, 'true'); + + component.testName = {}; + assert.equal(div.className, '[object Object]'); + + component.testName = ''; + assert.equal(div.className, ''); + } +}; diff --git a/test/runtime/samples/attribute-null-classname-no-style/main.svelte b/test/runtime/samples/attribute-null-classname-no-style/main.svelte new file mode 100644 index 0000000000..8dfcd7af54 --- /dev/null +++ b/test/runtime/samples/attribute-null-classname-no-style/main.svelte @@ -0,0 +1,5 @@ + + + diff --git a/test/runtime/samples/attribute-null-classname-with-style/_config.js b/test/runtime/samples/attribute-null-classname-with-style/_config.js new file mode 100644 index 0000000000..3ebc593b5d --- /dev/null +++ b/test/runtime/samples/attribute-null-classname-with-style/_config.js @@ -0,0 +1,40 @@ +export default { + html: ``, + + test({ assert, component, target }) { + const div = target.querySelector('div'); + + component.testName = null; + assert.equal(div.className, ' svelte-x1o6ra'); + + component.testName = undefined; + assert.equal(div.className, ' svelte-x1o6ra'); + + component.testName = undefined + ''; + assert.equal(div.className, 'undefined svelte-x1o6ra'); + + component.testName = null + ''; + assert.equal(div.className, 'null svelte-x1o6ra'); + + component.testName = 1; + assert.equal(div.className, '1 svelte-x1o6ra'); + + component.testName = 0; + assert.equal(div.className, '0 svelte-x1o6ra'); + + component.testName = false; + assert.equal(div.className, 'false svelte-x1o6ra'); + + component.testName = true; + assert.equal(div.className, 'true svelte-x1o6ra'); + + component.testName = {}; + assert.equal(div.className, '[object Object] svelte-x1o6ra'); + + component.testName = ''; + assert.equal(div.className, ' svelte-x1o6ra'); + + component.testName = 'testClassName'; + assert.equal(div.className, 'testClassName svelte-x1o6ra'); + } +}; diff --git a/test/runtime/samples/attribute-null-classname-with-style/main.svelte b/test/runtime/samples/attribute-null-classname-with-style/main.svelte new file mode 100644 index 0000000000..013647952d --- /dev/null +++ b/test/runtime/samples/attribute-null-classname-with-style/main.svelte @@ -0,0 +1,11 @@ + + + + + diff --git a/test/runtime/samples/attribute-null-classnames-no-style/_config.js b/test/runtime/samples/attribute-null-classnames-no-style/_config.js new file mode 100644 index 0000000000..917cf565c0 --- /dev/null +++ b/test/runtime/samples/attribute-null-classnames-no-style/_config.js @@ -0,0 +1,45 @@ +export default { + props: { + testName1: "test1", + testName2: "test2", + }, + + html: ``, + + test({ assert, component, target }) { + const div = target.querySelector('div'); + assert.equal(div.className, 'test1test2'); + + component.testName1 = null; + component.testName2 = null; + assert.equal(div.className, '0'); + + component.testName1 = null; + component.testName2 = "test"; + assert.equal(div.className, 'nulltest'); + + component.testName1 = undefined; + component.testName2 = "test"; + assert.equal(div.className, 'undefinedtest'); + + component.testName1 = undefined; + component.testName2 = undefined; + assert.equal(div.className, 'NaN'); + + component.testName1 = null; + component.testName2 = 1; + assert.equal(div.className, '1'); + + component.testName1 = undefined; + component.testName2 = 1; + assert.equal(div.className, 'NaN'); + + component.testName1 = null; + component.testName2 = 0; + assert.equal(div.className, '0'); + + component.testName1 = undefined; + component.testName2 = 0; + assert.equal(div.className, 'NaN'); + } +}; diff --git a/test/runtime/samples/attribute-null-classnames-no-style/main.svelte b/test/runtime/samples/attribute-null-classnames-no-style/main.svelte new file mode 100644 index 0000000000..f8cf7d803e --- /dev/null +++ b/test/runtime/samples/attribute-null-classnames-no-style/main.svelte @@ -0,0 +1,6 @@ + + + diff --git a/test/runtime/samples/attribute-null-classnames-with-style/_config.js b/test/runtime/samples/attribute-null-classnames-with-style/_config.js new file mode 100644 index 0000000000..5762f628fb --- /dev/null +++ b/test/runtime/samples/attribute-null-classnames-with-style/_config.js @@ -0,0 +1,45 @@ +export default { + props: { + testName1: "test1", + testName2: "test2", + }, + + html: ``, + + test({ assert, component, target }) { + const div = target.querySelector('div'); + assert.equal(div.className, 'test1test2 svelte-x1o6ra'); + + component.testName1 = null; + component.testName2 = null; + assert.equal(div.className, '0 svelte-x1o6ra'); + + component.testName1 = null; + component.testName2 = "test"; + assert.equal(div.className, 'nulltest svelte-x1o6ra'); + + component.testName1 = undefined; + component.testName2 = "test"; + assert.equal(div.className, 'undefinedtest svelte-x1o6ra'); + + component.testName1 = undefined; + component.testName2 = undefined; + assert.equal(div.className, 'NaN svelte-x1o6ra'); + + component.testName1 = null; + component.testName2 = 1; + assert.equal(div.className, '1 svelte-x1o6ra'); + + component.testName1 = undefined; + component.testName2 = 1; + assert.equal(div.className, 'NaN svelte-x1o6ra'); + + component.testName1 = null; + component.testName2 = 0; + assert.equal(div.className, '0 svelte-x1o6ra'); + + component.testName1 = undefined; + component.testName2 = 0; + assert.equal(div.className, 'NaN svelte-x1o6ra'); + } +}; diff --git a/test/runtime/samples/attribute-null-classnames-with-style/main.svelte b/test/runtime/samples/attribute-null-classnames-with-style/main.svelte new file mode 100644 index 0000000000..06098fd50b --- /dev/null +++ b/test/runtime/samples/attribute-null-classnames-with-style/main.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/test/runtime/samples/attribute-null-func-classname-no-style/_config.js b/test/runtime/samples/attribute-null-func-classname-no-style/_config.js new file mode 100644 index 0000000000..4a78b680ef --- /dev/null +++ b/test/runtime/samples/attribute-null-func-classname-no-style/_config.js @@ -0,0 +1,42 @@ +export default { + props: { + testName: "testClassName" + }, + + html: ``, + + test({ assert, component, target }) { + const div = target.querySelector('div'); + assert.equal(div.className, 'testClassName'); + + component.testName = null; + assert.equal(div.className, ''); + + component.testName = undefined; + assert.equal(div.className, ''); + + component.testName = undefined + ''; + assert.equal(div.className, 'undefined'); + + component.testName = null + ''; + assert.equal(div.className, 'null'); + + component.testName = 1; + assert.equal(div.className, '1'); + + component.testName = 0; + assert.equal(div.className, '0'); + + component.testName = false; + assert.equal(div.className, 'false'); + + component.testName = true; + assert.equal(div.className, 'true'); + + component.testName = {}; + assert.equal(div.className, '[object Object]'); + + component.testName = ''; + assert.equal(div.className, ''); + } +}; diff --git a/test/runtime/samples/attribute-null-func-classname-no-style/main.svelte b/test/runtime/samples/attribute-null-func-classname-no-style/main.svelte new file mode 100644 index 0000000000..e2754cd421 --- /dev/null +++ b/test/runtime/samples/attribute-null-func-classname-no-style/main.svelte @@ -0,0 +1,9 @@ + + + diff --git a/test/runtime/samples/attribute-null-func-classname-with-style/_config.js b/test/runtime/samples/attribute-null-func-classname-with-style/_config.js new file mode 100644 index 0000000000..1ed43d05b9 --- /dev/null +++ b/test/runtime/samples/attribute-null-func-classname-with-style/_config.js @@ -0,0 +1,42 @@ +export default { + props: { + testName: "testClassName" + }, + + html: ``, + + test({ assert, component, target }) { + const div = target.querySelector('div'); + assert.equal(div.className, 'testClassName svelte-x1o6ra'); + + component.testName = null; + assert.equal(div.className, ' svelte-x1o6ra'); + + component.testName = undefined; + assert.equal(div.className, ' svelte-x1o6ra'); + + component.testName = undefined + ''; + assert.equal(div.className, 'undefined svelte-x1o6ra'); + + component.testName = null + ''; + assert.equal(div.className, 'null svelte-x1o6ra'); + + component.testName = 1; + assert.equal(div.className, '1 svelte-x1o6ra'); + + component.testName = 0; + assert.equal(div.className, '0 svelte-x1o6ra'); + + component.testName = false; + assert.equal(div.className, 'false svelte-x1o6ra'); + + component.testName = true; + assert.equal(div.className, 'true svelte-x1o6ra'); + + component.testName = {}; + assert.equal(div.className, '[object Object] svelte-x1o6ra'); + + component.testName = ''; + assert.equal(div.className, ' svelte-x1o6ra'); + } +}; diff --git a/test/runtime/samples/attribute-null-func-classname-with-style/main.svelte b/test/runtime/samples/attribute-null-func-classname-with-style/main.svelte new file mode 100644 index 0000000000..bcb858ab8d --- /dev/null +++ b/test/runtime/samples/attribute-null-func-classname-with-style/main.svelte @@ -0,0 +1,15 @@ + + + + + diff --git a/test/runtime/samples/attribute-null-func-classnames-no-style/_config.js b/test/runtime/samples/attribute-null-func-classnames-no-style/_config.js new file mode 100644 index 0000000000..917cf565c0 --- /dev/null +++ b/test/runtime/samples/attribute-null-func-classnames-no-style/_config.js @@ -0,0 +1,45 @@ +export default { + props: { + testName1: "test1", + testName2: "test2", + }, + + html: ``, + + test({ assert, component, target }) { + const div = target.querySelector('div'); + assert.equal(div.className, 'test1test2'); + + component.testName1 = null; + component.testName2 = null; + assert.equal(div.className, '0'); + + component.testName1 = null; + component.testName2 = "test"; + assert.equal(div.className, 'nulltest'); + + component.testName1 = undefined; + component.testName2 = "test"; + assert.equal(div.className, 'undefinedtest'); + + component.testName1 = undefined; + component.testName2 = undefined; + assert.equal(div.className, 'NaN'); + + component.testName1 = null; + component.testName2 = 1; + assert.equal(div.className, '1'); + + component.testName1 = undefined; + component.testName2 = 1; + assert.equal(div.className, 'NaN'); + + component.testName1 = null; + component.testName2 = 0; + assert.equal(div.className, '0'); + + component.testName1 = undefined; + component.testName2 = 0; + assert.equal(div.className, 'NaN'); + } +}; diff --git a/test/runtime/samples/attribute-null-func-classnames-no-style/main.svelte b/test/runtime/samples/attribute-null-func-classnames-no-style/main.svelte new file mode 100644 index 0000000000..e1cdf51412 --- /dev/null +++ b/test/runtime/samples/attribute-null-func-classnames-no-style/main.svelte @@ -0,0 +1,11 @@ + + + diff --git a/test/runtime/samples/attribute-null-func-classnames-with-style/_config.js b/test/runtime/samples/attribute-null-func-classnames-with-style/_config.js new file mode 100644 index 0000000000..5762f628fb --- /dev/null +++ b/test/runtime/samples/attribute-null-func-classnames-with-style/_config.js @@ -0,0 +1,45 @@ +export default { + props: { + testName1: "test1", + testName2: "test2", + }, + + html: ``, + + test({ assert, component, target }) { + const div = target.querySelector('div'); + assert.equal(div.className, 'test1test2 svelte-x1o6ra'); + + component.testName1 = null; + component.testName2 = null; + assert.equal(div.className, '0 svelte-x1o6ra'); + + component.testName1 = null; + component.testName2 = "test"; + assert.equal(div.className, 'nulltest svelte-x1o6ra'); + + component.testName1 = undefined; + component.testName2 = "test"; + assert.equal(div.className, 'undefinedtest svelte-x1o6ra'); + + component.testName1 = undefined; + component.testName2 = undefined; + assert.equal(div.className, 'NaN svelte-x1o6ra'); + + component.testName1 = null; + component.testName2 = 1; + assert.equal(div.className, '1 svelte-x1o6ra'); + + component.testName1 = undefined; + component.testName2 = 1; + assert.equal(div.className, 'NaN svelte-x1o6ra'); + + component.testName1 = null; + component.testName2 = 0; + assert.equal(div.className, '0 svelte-x1o6ra'); + + component.testName1 = undefined; + component.testName2 = 0; + assert.equal(div.className, 'NaN svelte-x1o6ra'); + } +}; diff --git a/test/runtime/samples/attribute-null-func-classnames-with-style/main.svelte b/test/runtime/samples/attribute-null-func-classnames-with-style/main.svelte new file mode 100644 index 0000000000..af43778365 --- /dev/null +++ b/test/runtime/samples/attribute-null-func-classnames-with-style/main.svelte @@ -0,0 +1,17 @@ + + + + + diff --git a/test/runtime/samples/attribute-null/_config.js b/test/runtime/samples/attribute-null/_config.js new file mode 100644 index 0000000000..ae2f0a8af6 --- /dev/null +++ b/test/runtime/samples/attribute-null/_config.js @@ -0,0 +1,3 @@ +export default { + html: ``, +}; diff --git a/test/runtime/samples/attribute-null/main.svelte b/test/runtime/samples/attribute-null/main.svelte new file mode 100644 index 0000000000..9b7f48eda3 --- /dev/null +++ b/test/runtime/samples/attribute-null/main.svelte @@ -0,0 +1 @@ + diff --git a/test/runtime/samples/attribute-undefined/_config.js b/test/runtime/samples/attribute-undefined/_config.js new file mode 100644 index 0000000000..ae2f0a8af6 --- /dev/null +++ b/test/runtime/samples/attribute-undefined/_config.js @@ -0,0 +1,3 @@ +export default { + html: ``, +}; diff --git a/test/runtime/samples/attribute-undefined/main.svelte b/test/runtime/samples/attribute-undefined/main.svelte new file mode 100644 index 0000000000..5108aa873f --- /dev/null +++ b/test/runtime/samples/attribute-undefined/main.svelte @@ -0,0 +1 @@ + diff --git a/test/validator/samples/undefined-value/input.svelte b/test/validator/samples/undefined-value/input.svelte index 03dda2c44f..0dec9632a7 100644 --- a/test/validator/samples/undefined-value/input.svelte +++ b/test/validator/samples/undefined-value/input.svelte @@ -1,6 +1,6 @@ - -{potato}
-{Math.max(1, 2)}
\ No newline at end of file +{Math.max(1, 2)}
+ +{#each window.something as foo, i (foo.x + i)} + hello +{/each} diff --git a/test/validator/samples/undefined-value/warnings.json b/test/validator/samples/undefined-value/warnings.json index c1813e6ab0..790f83acfd 100644 --- a/test/validator/samples/undefined-value/warnings.json +++ b/test/validator/samples/undefined-value/warnings.json @@ -1,15 +1,15 @@ [{ "code": "missing-declaration", - "message": "'potato' is not defined", - "pos": 67, + "message": "'potato' is not defined. Consider adding a