diff --git a/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts b/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts index aff06e4150..7c174acb46 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,26 +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 { - const renderedChunk = chunk.render(); - if (this.node.name === 'class') { - return chunk.get_precedence() <= 13 - ? `(${renderedChunk})` - : `(${renderedChunk} || '')`; - } else { - return chunk.get_precedence() <= 13 - ? `(${renderedChunk})` - : renderedChunk; - } - } - }) - .join(' + '); + const attrPrefix = this.node.chunks[0].type === 'Text' ? '' : `"" + `; + + const attrText = this.node.name === 'class' + ? this.get_class_name_text() + : this.get_attr_text(); + + value = `${attrPrefix}${attrText}`; } const is_select_value_attribute = @@ -217,6 +205,41 @@ export default class AttributeWrapper { } } + get_class_name_text() { + const isStyled = this.node.chunks + .filter((chunk) => chunk.type === 'Text') + .some((chunk: Text) => !chunk.start && !chunk.end); + + const classNameStringArray = this.render_attr(); + + + if (!isStyled || classNameStringArray.length !== 2) { + return classNameStringArray.join(' + '); + } + + const targetToken = 0; + return classNameStringArray + .map((token, index) => index === targetToken ? `@class_name_resolver(${token})` : token) + .join(' + '); + } + + get_attr_text() { + return this.render_attr().join(' + '); + } + + render_attr() { + return this.node.chunks.map((chunk) => { + if (chunk.type === 'Text') { + return stringify(chunk.data); + } + + const renderedChunk = chunk.render(); + return chunk.get_precedence() <= 13 + ? `(${renderedChunk})` + : renderedChunk; + }); + } + stringify() { if (this.node.is_true) return ''; diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts index 08410ec33a..1aec30ef2f 100644 --- a/src/runtime/internal/utils.ts +++ b/src/runtime/internal/utils.ts @@ -89,3 +89,7 @@ export function once(fn) { fn.call(this, ...args); }; } + +export function class_name_resolver(nextClassName) { + return nextClassName == undefined ? '' : nextClassName; +} \ No newline at end of file diff --git a/test/runtime/samples/attribute-false/_config.js b/test/runtime/samples/attribute-false/_config.js new file mode 100644 index 0000000000..0f37392806 --- /dev/null +++ b/test/runtime/samples/attribute-false/_config.js @@ -0,0 +1,5 @@ +export default { + skip_if_ssr: true, + + 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..e2098430e4 --- /dev/null +++ b/test/runtime/samples/attribute-null-classname-no-style/_config.js @@ -0,0 +1,44 @@ +export default { + skip_if_ssr: true, + + 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/_config.js b/test/runtime/samples/attribute-null-classname-with-style/_config.js similarity index 70% rename from test/runtime/samples/attribute-null-classname/_config.js rename to test/runtime/samples/attribute-null-classname-with-style/_config.js index 856bd8bcd0..635dd058ca 100644 --- a/test/runtime/samples/attribute-null-classname/_config.js +++ b/test/runtime/samples/attribute-null-classname-with-style/_config.js @@ -26,6 +26,18 @@ export default { 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-classname/main.svelte b/test/runtime/samples/attribute-null-classname-with-style/main.svelte similarity index 100% rename from test/runtime/samples/attribute-null-classname/main.svelte rename to test/runtime/samples/attribute-null-classname-with-style/main.svelte 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..6a734efff5 --- /dev/null +++ b/test/runtime/samples/attribute-null-classnames-no-style/_config.js @@ -0,0 +1,47 @@ +export default { + skip_if_ssr: true, + + 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/_config.js b/test/runtime/samples/attribute-null-classnames-with-style/_config.js similarity index 81% rename from test/runtime/samples/attribute-null-classnames/_config.js rename to test/runtime/samples/attribute-null-classnames-with-style/_config.js index 6eca569250..22f7a4d7e2 100644 --- a/test/runtime/samples/attribute-null-classnames/_config.js +++ b/test/runtime/samples/attribute-null-classnames-with-style/_config.js @@ -35,5 +35,13 @@ export default { 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/main.svelte b/test/runtime/samples/attribute-null-classnames-with-style/main.svelte similarity index 100% rename from test/runtime/samples/attribute-null-classnames/main.svelte rename to test/runtime/samples/attribute-null-classnames-with-style/main.svelte 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..e2098430e4 --- /dev/null +++ b/test/runtime/samples/attribute-null-func-classname-no-style/_config.js @@ -0,0 +1,44 @@ +export default { + skip_if_ssr: true, + + 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..635dd058ca --- /dev/null +++ b/test/runtime/samples/attribute-null-func-classname-with-style/_config.js @@ -0,0 +1,44 @@ +export default { + skip_if_ssr: true, + + 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..6a734efff5 --- /dev/null +++ b/test/runtime/samples/attribute-null-func-classnames-no-style/_config.js @@ -0,0 +1,47 @@ +export default { + skip_if_ssr: true, + + 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..22f7a4d7e2 --- /dev/null +++ b/test/runtime/samples/attribute-null-func-classnames-with-style/_config.js @@ -0,0 +1,47 @@ +export default { + skip_if_ssr: true, + + 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..938d525cf6 --- /dev/null +++ b/test/runtime/samples/attribute-null/_config.js @@ -0,0 +1,5 @@ +export default { + skip_if_ssr: true, + + 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..938d525cf6 --- /dev/null +++ b/test/runtime/samples/attribute-undefined/_config.js @@ -0,0 +1,5 @@ +export default { + skip_if_ssr: true, + + 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 @@ +