Repository: sveltejs/svelte (JavaScript)
Date: 2018-04-22 14:51:32+00:00
Task type: Bug Fix

Issue:
onstate: changed is empty if data property not mentioned in template
[REPL](https://svelte.technology/repl?version=2.0.0&gist=b000d1c0362207f646684a88d18d3785) contains just a property `test` and an `onstate` listener.

`onstate` fires, but `changed` is empty. If `test` is used in the template, `changed` is `{test: 1}`, as expected.
onstate: changed is empty if data property not mentioned in template
[REPL](https://svelte.technology/repl?version=2.0.0&gist=b000d1c0362207f646684a88d18d3785) contains just a property `test` and an `onstate` listener.

`onstate` fires, but `changed` is empty. If `test` is used in the template, `changed` is `{test: 1}`, as expected.


Issue discussion:

Solution patch:
diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts
--- a/src/generators/dom/index.ts
+++ b/src/generators/dom/index.ts
@@ -218,16 +218,6 @@ export default function dom(
 			`if (!document.getElementById("${generator.stylesheet.id}-style")) @add_css();`)
 		}
 
-		${hasInitHooks && deindent`
-			var self = this;
-			var _oncreate = function() {
-				var changed = { ${expectedProperties.map(p => `${p}: 1`).join(', ')} };
-				${templateProperties.onstate && `%onstate.call(self, { changed: changed, current: self._state });`}
-				${templateProperties.oncreate && `%oncreate.call(self);`}
-				self.fire("update", { changed: changed, current: self._state });
-			};
-		`}
-
 		${(hasInitHooks || generator.hasComponents || generator.hasComplexBindings || generator.hasIntroTransitions) && deindent`
 			if (!options.root) {
 				this._oncreate = [];
@@ -241,7 +231,11 @@ export default function dom(
 		this._fragment = @create_main_fragment(this, this._state);
 
 		${hasInitHooks && deindent`
-			this.root._oncreate.push(_oncreate);
+			this.root._oncreate.push(() => {
+				${templateProperties.onstate && `%onstate.call(this, { changed: @assignTrue({}, this._state), current: this._state });`}
+				${templateProperties.oncreate && `%oncreate.call(this);`}
+				this.fire("update", { changed: @assignTrue({}, this._state), current: this._state });
+			});
 		`}
 
 		${generator.customElement ? deindent`
diff --git a/src/shared/utils.js b/src/shared/utils.js
--- a/src/shared/utils.js
+++ b/src/shared/utils.js
@@ -4,3 +4,8 @@ export function assign(tar, src) {
 	for (var k in src) tar[k] = src[k];
 	return tar;
 }
+
+export function assignTrue(tar, src) {
+	for (var k in src) tar[k] = 1;
+	return tar;
+}
\ No newline at end of file


Test patch:
diff --git a/test/js/samples/deconflict-globals/expected-bundle.js b/test/js/samples/deconflict-globals/expected-bundle.js
--- a/test/js/samples/deconflict-globals/expected-bundle.js
+++ b/test/js/samples/deconflict-globals/expected-bundle.js
@@ -5,6 +5,11 @@ function assign(tar, src) {
 	return tar;
 }
 
+function assignTrue(tar, src) {
+	for (var k in src) tar[k] = 1;
+	return tar;
+}
+
 function blankObject() {
 	return Object.create(null);
 }
@@ -150,20 +155,16 @@ function SvelteComponent(options) {
 	init(this, options);
 	this._state = assign(data_1(), options.data);
 
-	var self = this;
-	var _oncreate = function() {
-		var changed = {  };
-		oncreate.call(self);
-		self.fire("update", { changed: changed, current: self._state });
-	};
-
 	if (!options.root) {
 		this._oncreate = [];
 	}
 
 	this._fragment = create_main_fragment(this, this._state);
 
-	this.root._oncreate.push(_oncreate);
+	this.root._oncreate.push(() => {
+		oncreate.call(this);
+		this.fire("update", { changed: assignTrue({}, this._state), current: this._state });
+	});
 
 	if (options.target) {
 		this._fragment.c();
diff --git a/test/js/samples/deconflict-globals/expected.js b/test/js/samples/deconflict-globals/expected.js
--- a/test/js/samples/deconflict-globals/expected.js
+++ b/test/js/samples/deconflict-globals/expected.js
@@ -1,5 +1,5 @@
 /* generated by Svelte vX.Y.Z */
-import { assign, callAll, init, noop, proto } from "svelte/shared.js";
+import { assign, assignTrue, callAll, init, noop, proto } from "svelte/shared.js";
 
 function data_1() {
 	return {
@@ -30,20 +30,16 @@ function SvelteComponent(options) {
 	init(this, options);
 	this._state = assign(data_1(), options.data);
 
-	var self = this;
-	var _oncreate = function() {
-		var changed = {  };
-		oncreate.call(self);
-		self.fire("update", { changed: changed, current: self._state });
-	};
-
 	if (!options.root) {
 		this._oncreate = [];
 	}
 
 	this._fragment = create_main_fragment(this, this._state);
 
-	this.root._oncreate.push(_oncreate);
+	this.root._oncreate.push(() => {
+		oncreate.call(this);
+		this.fire("update", { changed: assignTrue({}, this._state), current: this._state });
+	});
 
 	if (options.target) {
 		this._fragment.c();
diff --git a/test/runtime/samples/onstate-no-template/_config.js b/test/runtime/samples/onstate-no-template/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/onstate-no-template/_config.js
@@ -0,0 +1,11 @@
+export default {
+	'skip-ssr': true,
+
+	data: {
+		foo: 'woo!'
+	},
+
+	test(assert, component) {
+		assert.deepEqual(component.changed, { foo: 1 });
+	}
+};
diff --git a/test/runtime/samples/onstate-no-template/main.html b/test/runtime/samples/onstate-no-template/main.html
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/onstate-no-template/main.html
@@ -0,0 +1,7 @@
+<script>
+	export default {
+		onstate({ changed }) {
+			this.changed = changed;
+		}
+	};
+</script>