Repository: sveltejs/svelte (JavaScript) — Bug Fix

Issue:
transitions in await blocks
Follow-up to #952. This currently doesn't work:

```html
{{#await promise}}
  <p transition:fade>loading...</p>
{{then value}}
  <p class='content' transition:fade>{{value}}</p>
{{catch error}}
  <div class='toast' transition:fly='{y: 100}'>{{error.message}}</div>
{{/await}}
```
transitions in await blocks
Follow-up to #952. This currently doesn't work:

```html
{{#await promise}}
  <p transition:fade>loading...</p>
{{then value}}
  <p class='content' transition:fade>{{value}}</p>
{{catch error}}
  <div class='toast' transition:fly='{y: 100}'>{{error.message}}</div>
{{/await}}
```


Discussion:

Resolving patch:
diff --git a/src/compile/nodes/AwaitBlock.ts b/src/compile/nodes/AwaitBlock.ts
--- a/src/compile/nodes/AwaitBlock.ts
+++ b/src/compile/nodes/AwaitBlock.ts
@@ -42,6 +42,8 @@ export default class AwaitBlock extends Node {
 		block.addDependencies(this.expression.dependencies);
 
 		let isDynamic = false;
+		let hasIntros = false;
+		let hasOutros = false;
 
 		['pending', 'then', 'catch'].forEach(status => {
 			const child = this[status];
@@ -58,11 +60,22 @@ export default class AwaitBlock extends Node {
 				isDynamic = true;
 				block.addDependencies(child.block.dependencies);
 			}
+
+			if (child.block.hasIntroMethod) hasIntros = true;
+			if (child.block.hasOutroMethod) hasOutros = true;
 		});
 
 		this.pending.block.hasUpdateMethod = isDynamic;
 		this.then.block.hasUpdateMethod = isDynamic;
 		this.catch.block.hasUpdateMethod = isDynamic;
+
+		this.pending.block.hasIntroMethod = hasIntros;
+		this.then.block.hasIntroMethod = hasIntros;
+		this.catch.block.hasIntroMethod = hasIntros;
+
+		this.pending.block.hasOutroMethod = hasOutros;
+		this.then.block.hasOutroMethod = hasOutros;
+		this.catch.block.hasOutroMethod = hasOutros;
 	}
 
 	build(
@@ -92,7 +105,8 @@ export default class AwaitBlock extends Node {
 			this.then.block.name && `then: ${this.then.block.name}`,
 			this.catch.block.name && `catch: ${this.catch.block.name}`,
 			this.then.block.name && `value: '${this.value}'`,
-			this.catch.block.name && `error: '${this.error}'`
+			this.catch.block.name && `error: '${this.error}'`,
+			this.pending.block.hasOutroMethod && `blocks: Array(3)`
 		].filter(Boolean);
 
 		block.builders.init.addBlock(deindent`
@@ -101,10 +115,6 @@ export default class AwaitBlock extends Node {
 			};
 		`);
 
-		// the `#component.root.set({})` below is just a cheap way to flush
-		// any oncreate handlers. We could have a dedicated `flush()` method
-		// but it's probably not worth it
-
 		block.builders.init.addBlock(deindent`
 			@handlePromise(${promise} = ${snippet}, ${info});
 		`);
@@ -123,7 +133,7 @@ export default class AwaitBlock extends Node {
 		const anchorNode = parentNode ? 'null' : 'anchor';
 
 		block.builders.mount.addBlock(deindent`
-			${info}.block.m(${initialMountNode}, ${info}.anchor = ${anchorNode});
+			${info}.block.${this.pending.block.hasIntroMethod ? 'i' : 'm'}(${initialMountNode}, ${info}.anchor = ${anchorNode});
 			${info}.mount = () => ${updateMountNode};
 		`);
 
diff --git a/src/shared/await-block.js b/src/shared/await-block.js
--- a/src/shared/await-block.js
+++ b/src/shared/await-block.js
@@ -3,7 +3,7 @@ import { assign, isPromise } from './utils.js';
 export function handlePromise(promise, info) {
 	var token = info.token = {};
 
-	function update(type, key, value) {
+	function update(type, index, key, value) {
 		if (info.token !== token) return;
 
 		info.resolved = key && { [key]: value };
@@ -12,32 +12,44 @@ export function handlePromise(promise, info) {
 		const block = type && (info.current = type)(info.component, child_ctx);
 
 		if (info.block) {
-			info.block.u();
-			info.block.d();
+			if (info.blocks) {
+				info.blocks.forEach((block, i) => {
+					if (i !== index && block) block.o(() => {
+						block.u();
+						block.d();
+						info.blocks[i] = null;
+					});
+				});
+			} else {
+				info.block.u();
+				info.block.d();
+			}
+
 			block.c();
-			block.m(info.mount(), info.anchor);
+			block[block.i ? 'i' : 'm'](info.mount(), info.anchor);
 
-			info.component.root.set({});
+			info.component.root.set({}); // flush any handlers that were created
 		}
 
 		info.block = block;
+		if (info.blocks) info.blocks[index] = block;
 	}
 
 	if (isPromise(promise)) {
 		promise.then(value => {
-			update(info.then, info.value, value);
+			update(info.then, 1, info.value, value);
 		}, error => {
-			update(info.catch, info.error, error);
+			update(info.catch, 2, info.error, error);
 		});
 
 		// if we previously had a then/catch block, destroy it
 		if (info.current !== info.pending) {
-			update(info.pending);
+			update(info.pending, 0);
 			return true;
 		}
 	} else {
 		if (info.current !== info.then) {
-			update(info.then, info.value, promise);
+			update(info.then, 1, info.value, promise);
 			return true;
 		}
 


Test patch:
diff --git a/test/runtime/samples/transition-js-await-block/_config.js b/test/runtime/samples/transition-js-await-block/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/transition-js-await-block/_config.js
@@ -0,0 +1,35 @@
+let fulfil;
+let reject;
+
+let promise = new Promise((f, r) => {
+	fulfil = f;
+	reject = r;
+});
+
+export default {
+	data: {
+		promise
+	},
+
+	test(assert, component, target, window, raf) {
+		component.set({ visible: true });
+		let p = target.querySelector('p');
+
+		assert.equal(p.className, 'pending');
+		assert.equal(p.foo, 0);
+
+		raf.tick(50);
+		assert.equal(p.foo, 0.5);
+
+		fulfil(42);
+
+		return promise.then(() => {
+			raf.tick(80);
+			let ps = document.querySelectorAll('p');
+			assert.equal(ps[0].className, 'pending');
+			assert.equal(ps[1].className, 'then');
+			assert.equal(ps[0].foo, 0.2);
+			assert.equal(ps[1].foo, 0.3);
+		});
+	}
+};
diff --git a/test/runtime/samples/transition-js-await-block/main.html b/test/runtime/samples/transition-js-await-block/main.html
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/transition-js-await-block/main.html
@@ -0,0 +1,22 @@
+{#await promise}
+	<p class='pending' transition:foo>loading...</p>
+{:then value}
+	<p class='then' transition:foo>{value}</p>
+{:catch error}
+	<p class='catch' transition:foo>{error.message}</p>
+{/await}
+
+<script>
+	export default {
+		transitions: {
+			foo(node, params) {
+				return {
+					duration: 100,
+					tick: t => {
+						node.foo = t;
+					}
+				};
+			}
+		}
+	};
+</script>
\ No newline at end of file