diff --git a/src/generators/dom/visitors/Component/Binding.ts b/src/generators/dom/visitors/Component/Binding.ts
index fa7d539a4242..ac8e90f0f221 100644
--- a/src/generators/dom/visitors/Component/Binding.ts
+++ b/src/generators/dom/visitors/Component/Binding.ts
@@ -5,6 +5,7 @@ import { DomGenerator } from '../../index';
import Block from '../../Block';
import { Node } from '../../../../interfaces';
import { State } from '../../interfaces';
+import getObject from '../../../../utils/getObject';
export default function visitBinding(
generator: DomGenerator,
@@ -14,16 +15,11 @@ export default function visitBinding(
attribute,
local
) {
- const { name } = flattenReference(attribute.value);
+ const { name } = getObject(attribute.value);
const { snippet, contexts, dependencies } = block.contextualise(
attribute.value
);
- if (dependencies.length > 1)
- throw new Error(
- 'An unexpected situation arose. Please raise an issue at https://github.com/sveltejs/svelte/issues — thanks!'
- );
-
contexts.forEach(context => {
if (!~local.allUsedContexts.indexOf(context))
local.allUsedContexts.push(context);
@@ -38,8 +34,9 @@ export default function visitBinding(
obj = block.listNames.get(name);
prop = block.indexNames.get(name);
} else if (attribute.value.type === 'MemberExpression') {
- prop = `'[✂${attribute.value.property.start}-${attribute.value.property
- .end}✂]'`;
+ prop = `[✂${attribute.value.property.start}-${attribute.value.property
+ .end}✂]`;
+ if (!attribute.value.computed) prop = `'${prop}'`;
obj = `[✂${attribute.value.object.start}-${attribute.value.object.end}✂]`;
} else {
obj = 'state';
@@ -85,7 +82,7 @@ export default function visitBinding(
local.update.addBlock(deindent`
if ( !${updating} && ${dependencies
.map(dependency => `'${dependency}' in changed`)
- .join('||')} ) {
+ .join(' || ')} ) {
${updating} = true;
${local.name}._set({ ${attribute.name}: ${snippet} });
${updating} = false;
diff --git a/src/generators/dom/visitors/Element/Binding.ts b/src/generators/dom/visitors/Element/Binding.ts
index fdac104a8743..7963677d19a9 100644
--- a/src/generators/dom/visitors/Element/Binding.ts
+++ b/src/generators/dom/visitors/Element/Binding.ts
@@ -6,12 +6,7 @@ import { DomGenerator } from '../../index';
import Block from '../../Block';
import { Node } from '../../../../interfaces';
import { State } from '../../interfaces';
-
-function getObject(node) {
- // TODO validation should ensure this is an Identifier or a MemberExpression
- while (node.type === 'MemberExpression') node = node.object;
- return node;
-}
+import getObject from '../../../../utils/getObject';
export default function visitBinding(
generator: DomGenerator,
@@ -21,15 +16,7 @@ export default function visitBinding(
attribute: Node
) {
const { name } = getObject(attribute.value);
- const { snippet, contexts } = block.contextualise(attribute.value);
- const dependencies = block.contextDependencies.has(name)
- ? block.contextDependencies.get(name)
- : [name];
-
- if (dependencies.length > 1)
- throw new Error(
- 'An unexpected situation arose. Please raise an issue at https://github.com/sveltejs/svelte/issues — thanks!'
- );
+ const { snippet, contexts, dependencies } = block.contextualise(attribute.value);
contexts.forEach(context => {
if (!~state.allUsedContexts.indexOf(context))
diff --git a/src/generators/dom/visitors/shared/binding/getSetter.ts b/src/generators/dom/visitors/shared/binding/getSetter.ts
index 5cb4cc4c6251..7ce77b448907 100644
--- a/src/generators/dom/visitors/shared/binding/getSetter.ts
+++ b/src/generators/dom/visitors/shared/binding/getSetter.ts
@@ -1,4 +1,6 @@
import deindent from '../../../../../utils/deindent';
+import getTailSnippet from '../../../../../utils/getTailSnippet';
+import { Node } from '../../../../../interfaces';
export default function getSetter({
block,
@@ -40,15 +42,7 @@ export default function getSetter({
return `${block.component}._set({ ${name}: ${value} });`;
}
-function getTailSnippet(node) {
- const end = node.end;
- while (node.type === 'MemberExpression') node = node.object;
- const start = node.end;
-
- return `[✂${start}-${end}✂]`;
-}
-
-function isComputed(node) {
+function isComputed(node: Node) {
while (node.type === 'MemberExpression') {
if (node.computed) return true;
node = node.object;
diff --git a/src/generators/server-side-rendering/Block.ts b/src/generators/server-side-rendering/Block.ts
index 1ad36fff5a2a..8d908ca8eac3 100644
--- a/src/generators/server-side-rendering/Block.ts
+++ b/src/generators/server-side-rendering/Block.ts
@@ -2,6 +2,7 @@ import deindent from '../../utils/deindent';
import flattenReference from '../../utils/flattenReference';
import { SsrGenerator } from './index';
import { Node } from '../../interfaces';
+import getObject from '../../utils/getObject';
interface BlockOptions {
// TODO
@@ -25,13 +26,13 @@ export default class Block {
this.conditions.map(c => `(${c})`)
);
- const { keypath } = flattenReference(binding.value);
+ const { name: prop } = getObject(binding.value);
this.generator.bindings.push(deindent`
if ( ${conditions.join('&&')} ) {
tmp = ${name}.data();
- if ( '${keypath}' in tmp ) {
- state.${binding.name} = tmp.${keypath};
+ if ( '${prop}' in tmp ) {
+ state.${binding.name} = tmp.${prop};
settled = false;
}
}
diff --git a/src/generators/server-side-rendering/visitors/Component.ts b/src/generators/server-side-rendering/visitors/Component.ts
index 05523a29a879..6fb87973e418 100644
--- a/src/generators/server-side-rendering/visitors/Component.ts
+++ b/src/generators/server-side-rendering/visitors/Component.ts
@@ -3,6 +3,8 @@ import visit from '../visit';
import { SsrGenerator } from '../index';
import Block from '../Block';
import { Node } from '../../../interfaces';
+import getObject from '../../../utils/getObject';
+import getTailSnippet from '../../../utils/getTailSnippet';
export default function visitComponent(
generator: SsrGenerator,
@@ -52,9 +54,13 @@ export default function visitComponent(
})
.concat(
bindings.map(binding => {
- const { name, keypath } = flattenReference(binding.value);
- const value = block.contexts.has(name) ? keypath : `state.${keypath}`;
- return `${binding.name}: ${value}`;
+ const { name } = getObject(binding.value);
+ const tail = binding.value.type === 'MemberExpression'
+ ? getTailSnippet(binding.value)
+ : '';
+
+ const keypath = block.contexts.has(name) ? `${name}${tail}` : `state.${name}${tail}`;
+ return `${binding.name}: ${keypath}`;
})
)
.join(', ');
diff --git a/src/utils/deindent.ts b/src/utils/deindent.ts
index 13278b26b18e..5f42e896041c 100644
--- a/src/utils/deindent.ts
+++ b/src/utils/deindent.ts
@@ -1,6 +1,6 @@
const start = /\n(\t+)/;
-export default function deindent(strings: string[], ...values: any[]) {
+export default function deindent(strings: TemplateStringsArray, ...values: any[]) {
const indentation = start.exec(strings[0])[1];
const pattern = new RegExp(`^${indentation}`, 'gm');
diff --git a/src/utils/getObject.ts b/src/utils/getObject.ts
new file mode 100644
index 000000000000..0d3ce4d789e8
--- /dev/null
+++ b/src/utils/getObject.ts
@@ -0,0 +1,6 @@
+import { Node } from '../interfaces';
+
+export default function getObject(node: Node) {
+ while (node.type === 'MemberExpression') node = node.object;
+ return node;
+}
\ No newline at end of file
diff --git a/src/utils/getTailSnippet.ts b/src/utils/getTailSnippet.ts
new file mode 100644
index 000000000000..499e705ec65f
--- /dev/null
+++ b/src/utils/getTailSnippet.ts
@@ -0,0 +1,9 @@
+import { Node } from '../interfaces';
+
+export default function getTailSnippet(node: Node) {
+ const end = node.end;
+ while (node.type === 'MemberExpression') node = node.object;
+ const start = node.end;
+
+ return `[✂${start}-${end}✂]`;
+}
\ No newline at end of file
diff --git a/test/helpers.js b/test/helpers.js
index 1d2d6c10890b..33131da5585e 100644
--- a/test/helpers.js
+++ b/test/helpers.js
@@ -2,6 +2,7 @@ import jsdom from 'jsdom';
import assert from 'assert';
import glob from 'glob';
import fs from 'fs';
+import path from 'path';
import chalk from 'chalk';
import * as consoleGroup from 'console-group';
@@ -162,13 +163,17 @@ export function addLineNumbers(code) {
.join('\n');
}
-export function showOutput(cwd, shared) {
+function capitalize(str) {
+ return str[0].toUpperCase() + str.slice(1);
+}
+
+export function showOutput(cwd, options) {
glob.sync('**/*.html', { cwd }).forEach(file => {
const { code } = svelte.compile(
fs.readFileSync(`${cwd}/${file}`, 'utf-8'),
- {
- shared
- }
+ Object.assign(options, {
+ name: capitalize(file.slice(0, -path.extname(file).length))
+ })
);
console.log( // eslint-disable-line no-console
diff --git a/test/runtime/index.js b/test/runtime/index.js
index a21648b60410..222ac8b5fbc6 100644
--- a/test/runtime/index.js
+++ b/test/runtime/index.js
@@ -57,41 +57,26 @@ describe("runtime", () => {
throw new Error("Forgot to remove `solo: true` from test");
}
- (config.skip ? it.skip : config.solo ? it.only : it)(`${dir} (${shared ? 'shared' : 'inline'} helpers`, () => {
+ (config.skip ? it.skip : config.solo ? it.only : it)(`${dir} (${shared ? 'shared' : 'inline'} helpers)`, () => {
if (failed.has(dir)) {
// this makes debugging easier, by only printing compiled output once
throw new Error('skipping inline helpers test');
}
const cwd = path.resolve(`test/runtime/samples/${dir}`);
- let compiled;
compileOptions = config.compileOptions || {};
compileOptions.shared = shared;
compileOptions.dev = config.dev;
- try {
- const source = fs.readFileSync(
- `test/runtime/samples/${dir}/main.html`,
- "utf-8"
- );
- compiled = svelte.compile(source, compileOptions);
- } catch (err) {
- if (config.compileError) {
- config.compileError(err);
- return;
- } else {
- failed.add(dir);
- showOutput(cwd, shared);
- throw err;
- }
- }
-
- const { code } = compiled;
-
// check that no ES2015+ syntax slipped in
if (!config.allowES2015) {
try {
+ const source = fs.readFileSync(
+ `test/runtime/samples/${dir}/main.html`,
+ "utf-8"
+ );
+ const { code } = svelte.compile(source, compileOptions);
const startIndex = code.indexOf("function create_main_fragment"); // may change!
if (startIndex === -1)
throw new Error("missing create_main_fragment");
@@ -101,7 +86,7 @@ describe("runtime", () => {
acorn.parse(es5, { ecmaVersion: 5 });
} catch (err) {
failed.add(dir);
- showOutput(cwd, shared); // eslint-disable-line no-console
+ showOutput(cwd, { shared }); // eslint-disable-line no-console
throw err;
}
}
@@ -146,7 +131,7 @@ describe("runtime", () => {
try {
SvelteComponent = require(`./samples/${dir}/main.html`).default;
} catch (err) {
- showOutput(cwd, shared); // eslint-disable-line no-console
+ showOutput(cwd, { shared }); // eslint-disable-line no-console
throw err;
}
@@ -210,12 +195,12 @@ describe("runtime", () => {
config.error(assert, err);
} else {
failed.add(dir);
- showOutput(cwd, shared); // eslint-disable-line no-console
+ showOutput(cwd, { shared }); // eslint-disable-line no-console
throw err;
}
})
.then(() => {
- if (config.show) showOutput(cwd, shared);
+ if (config.show) showOutput(cwd, { shared });
});
});
}
diff --git a/test/runtime/samples/component-binding-computed/Nested.html b/test/runtime/samples/component-binding-computed/Nested.html
new file mode 100644
index 000000000000..ca75184db2ac
--- /dev/null
+++ b/test/runtime/samples/component-binding-computed/Nested.html
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/test/runtime/samples/component-binding-computed/_config.js b/test/runtime/samples/component-binding-computed/_config.js
new file mode 100644
index 000000000000..08590c49543f
--- /dev/null
+++ b/test/runtime/samples/component-binding-computed/_config.js
@@ -0,0 +1,34 @@
+export default {
+ html: `
+
+
+ `,
+
+ test ( assert, component, target, window ) {
+ const input = new window.Event( 'input' );
+ const inputs = target.querySelectorAll( 'input' );
+
+ inputs[0].value = 'Ada';
+ inputs[0].dispatchEvent(input);
+ assert.deepEqual(component.get('values'), {
+ firstname: 'Ada',
+ lastname: ''
+ });
+
+ inputs[1].value = 'Lovelace';
+ inputs[1].dispatchEvent(input);
+ assert.deepEqual(component.get('values'), {
+ firstname: 'Ada',
+ lastname: 'Lovelace'
+ });
+
+ component.set({
+ values: {
+ firstname: 'Grace',
+ lastname: 'Hopper'
+ }
+ });
+ assert.equal(inputs[0].value, 'Grace');
+ assert.equal(inputs[1].value, 'Hopper');
+ }
+};
diff --git a/test/runtime/samples/component-binding-computed/main.html b/test/runtime/samples/component-binding-computed/main.html
new file mode 100644
index 000000000000..9ac402fec8ea
--- /dev/null
+++ b/test/runtime/samples/component-binding-computed/main.html
@@ -0,0 +1,22 @@
+{{#each fields as field}}
+
+{{/each}}
+
+
\ No newline at end of file
diff --git a/test/server-side-rendering/index.js b/test/server-side-rendering/index.js
index 02f4a50e9f60..e70bb13e734a 100644
--- a/test/server-side-rendering/index.js
+++ b/test/server-side-rendering/index.js
@@ -3,10 +3,9 @@ import * as fs from "fs";
import * as path from "path";
import {
- addLineNumbers,
+ showOutput,
loadConfig,
setupHtmlEqual,
- svelte,
tryToLoadJson
} from "../helpers.js";
@@ -19,10 +18,6 @@ function tryToReadFile(file) {
}
}
-function capitalize(str) {
- return str[0].toUpperCase() + str.slice(1);
-}
-
describe("ssr", () => {
before(() => {
require(process.env.COVERAGE
@@ -64,18 +59,7 @@ describe("ssr", () => {
error = e;
}
- if (show) {
- fs.readdirSync(dir).forEach(file => {
- if (file[0] === "_") return;
- const source = fs.readFileSync(`${dir}/${file}`, "utf-8");
- const name = capitalize(file.slice(0, -path.extname(file).length));
- const { code } = svelte.compile(source, { generate: "ssr", name });
- console.group(file);
- console.log(addLineNumbers(code));
- console.groupEnd();
- });
- }
-
+ if (show) showOutput(dir, { generate: "ssr" });
if (error) throw error;
fs.writeFileSync(`${dir}/_actual.html`, html);
@@ -102,22 +86,7 @@ describe("ssr", () => {
if (config["skip-ssr"]) return;
(config.skip ? it.skip : config.solo ? it.only : it)(dir, () => {
- let compiled;
-
- try {
- const source = fs.readFileSync(
- `test/runtime/samples/${dir}/main.html`,
- "utf-8"
- );
- compiled = svelte.compile(source, { generate: "ssr" });
- } catch (err) {
- if (config.compileError) {
- config.compileError(err);
- return;
- } else {
- throw err;
- }
- }
+ const cwd = path.resolve("test/runtime/samples", dir);
fs.readdirSync(`test/runtime/samples/${dir}`).forEach(file => {
const resolved = require.resolve(`../runtime/samples/${dir}/${file}`);
@@ -134,7 +103,7 @@ describe("ssr", () => {
assert.htmlEqual(html, config.html);
}
} catch (err) {
- console.log(addLineNumbers(compiled.code)); // eslint-disable-line no-console
+ showOutput(cwd, { generate: "ssr" });
throw err;
}
});