From c5867c713ac39516bad4d358225e215f36018af0 Mon Sep 17 00:00:00 2001
From: Godfrey Chan <godfreykfc@gmail.com>
Date: Sun, 18 Sep 2016 20:59:08 -0700
Subject: [PATCH 1/2] Begin working on `{{#-in-element`.

---
 packages/glimmer-runtime/lib/builder.ts       | 23 ++++++++++++++----
 .../lib/compiled/opcodes/dom.ts               | 24 +++++++++++++++++++
 .../lib/syntax/builtins/with-dynamic-vars.ts  |  8 +++----
 3 files changed, 46 insertions(+), 9 deletions(-)

diff --git a/packages/glimmer-runtime/lib/builder.ts b/packages/glimmer-runtime/lib/builder.ts
index 972fcc31c7..e2859f289e 100644
--- a/packages/glimmer-runtime/lib/builder.ts
+++ b/packages/glimmer-runtime/lib/builder.ts
@@ -127,7 +127,7 @@ export class ElementStack implements Cursor {
     return this.blockStack.current;
   }
 
-  private popElement() {
+  popElement() {
     let { elementStack, nextSiblingStack }  = this;
 
     let topElement = elementStack.pop();
@@ -193,16 +193,31 @@ export class ElementStack implements Cursor {
 
   flushElement() {
     let parent  = this.element;
-    let element = this.element = this.constructing;
+    let element = this.constructing;
 
     this.dom.insertBefore(parent, element, this.nextSibling);
 
     this.constructing = null;
     this.operations = null;
-    this.nextSibling = null;
+
+    this.pushElement(element);
+    this.blockStack.current.openElement(element);
+  }
+
+  pushRemoteElement(element: Simple.Element) {
+    this.pushElement(element);
+
+    let tracker = new SimpleBlockTracker(this.element);
+    this.pushBlockTracker(tracker);
+    return tracker;
+  }
+
+  private pushElement(element: Simple.Element) {
+    this.element = element;
     this.elementStack.push(element);
+
+    this.nextSibling = null;
     this.nextSiblingStack.push(null);
-    this.blockStack.current.openElement(element);
   }
 
   newDestroyable(d: Destroyable) {
diff --git a/packages/glimmer-runtime/lib/compiled/opcodes/dom.ts b/packages/glimmer-runtime/lib/compiled/opcodes/dom.ts
index cd0570de66..70f773312a 100644
--- a/packages/glimmer-runtime/lib/compiled/opcodes/dom.ts
+++ b/packages/glimmer-runtime/lib/compiled/opcodes/dom.ts
@@ -62,6 +62,30 @@ export class OpenPrimitiveElementOpcode extends Opcode {
   }
 }
 
+export class PushRemoteElementOpcode extends Opcode {
+  public type = "push-remote-element";
+
+  evaluate(vm: VM) {
+    let reference = vm.frame.getOperand();
+    let cache = isConst(reference) ? undefined : new ReferenceCache(reference);
+    let element = cache ? cache.peek() : reference.value();
+
+    vm.stack().pushRemoteElement(element);
+
+    if (cache) {
+      vm.updateWith(new Assert(cache));
+    }
+  }
+
+  toJSON(): OpcodeJSON {
+    return {
+      guid: this._guid,
+      type: this.type,
+      args: [JSON.stringify(this.tag)]
+    };
+  }
+}
+
 export class OpenComponentElementOpcode extends Opcode {
   public type = "open-component-element";
 
diff --git a/packages/glimmer-runtime/lib/syntax/builtins/with-dynamic-vars.ts b/packages/glimmer-runtime/lib/syntax/builtins/with-dynamic-vars.ts
index 37befcc473..56f01ba93b 100644
--- a/packages/glimmer-runtime/lib/syntax/builtins/with-dynamic-vars.ts
+++ b/packages/glimmer-runtime/lib/syntax/builtins/with-dynamic-vars.ts
@@ -22,12 +22,10 @@ export default class WithDynamicVarsSyntax extends StatementSyntax {
   compile(dsl: OpcodeBuilderDSL, env: Environment) {
     let { args, templates } = this;
 
-    dsl.unit({ templates }, dsl => {
-      dsl.putArgs(args);
-      dsl.pushDynamicScope();
-      dsl.bindDynamicScope(args.named.keys);
+    dsl.block({ templates, args }, (dsl) => {
+      dsl.pushRemoteElement();
       dsl.evaluate('default');
-      dsl.popDynamicScope();
+      dsl.popElement();
     });
   }
 }

From ad19235f0ffcb6af3f0d938ef36973eb0838f978 Mon Sep 17 00:00:00 2001
From: Robert Jackson <rwjblue@twitch.tv>
Date: Sun, 25 Sep 2016 10:18:57 -0400
Subject: [PATCH 2/2] Continue fleshing out `{{-in-element`.

---
 packages/glimmer-runtime/index.ts             |   4 +
 packages/glimmer-runtime/lib/builder.ts       |  37 +-
 .../lib/compiled/opcodes/builder.ts           |  12 +
 .../lib/compiled/opcodes/dom.ts               |  29 +-
 .../lib/syntax/builtins/in-element.ts         |  34 ++
 .../lib/syntax/builtins/with-dynamic-vars.ts  |   8 +-
 .../tests/ember-component-test.ts             |  16 +-
 .../glimmer-runtime/tests/in-element-test.ts  | 401 ++++++++++++++++++
 .../glimmer-test-helpers/lib/environment.ts   |   3 +
 9 files changed, 515 insertions(+), 29 deletions(-)
 create mode 100644 packages/glimmer-runtime/lib/syntax/builtins/in-element.ts
 create mode 100644 packages/glimmer-runtime/tests/in-element-test.ts

diff --git a/packages/glimmer-runtime/index.ts b/packages/glimmer-runtime/index.ts
index d32d688528..a5f80899fa 100644
--- a/packages/glimmer-runtime/index.ts
+++ b/packages/glimmer-runtime/index.ts
@@ -105,6 +105,10 @@ export {
   default as WithDynamicVarsSyntax
 } from './lib/syntax/builtins/with-dynamic-vars';
 
+export {
+  default as InElementSyntax
+} from './lib/syntax/builtins/in-element';
+
 export { PublicVM as VM, UpdatingVM, RenderResult } from './lib/vm';
 
 export { SafeString, isSafeString } from './lib/upsert';
diff --git a/packages/glimmer-runtime/lib/builder.ts b/packages/glimmer-runtime/lib/builder.ts
index e2859f289e..6a918ed0f6 100644
--- a/packages/glimmer-runtime/lib/builder.ts
+++ b/packages/glimmer-runtime/lib/builder.ts
@@ -27,11 +27,7 @@ export interface LastNode {
 }
 
 class First {
-  private node: Node;
-
-  constructor(node) {
-    this.node = node;
-  }
+  constructor(private node: Node) { }
 
   firstNode(): Node {
     return this.node;
@@ -39,11 +35,7 @@ class First {
 }
 
 class Last {
-  private node: Node;
-
-  constructor(node) {
-    this.node = node;
-  }
+  constructor(private node: Node) { }
 
   lastNode(): Node {
     return this.node;
@@ -151,12 +143,15 @@ export class ElementStack implements Cursor {
     return tracker;
   }
 
-  private pushBlockTracker(tracker: Tracker) {
+  private pushBlockTracker(tracker: Tracker, isRemote = false) {
     let current = this.blockStack.current;
 
     if (current !== null) {
       current.newDestroyable(tracker);
-      current.newBounds(tracker);
+
+      if (!isRemote) {
+        current.newBounds(tracker);
+      }
     }
 
     this.blockStack.push(tracker);
@@ -207,9 +202,13 @@ export class ElementStack implements Cursor {
   pushRemoteElement(element: Simple.Element) {
     this.pushElement(element);
 
-    let tracker = new SimpleBlockTracker(this.element);
-    this.pushBlockTracker(tracker);
-    return tracker;
+    let tracker = new RemoteBlockTracker(element);
+    this.pushBlockTracker(tracker, true);
+  }
+
+  popRemoteElement() {
+    this.popBlock();
+    this.popElement();
   }
 
   private pushElement(element: Simple.Element) {
@@ -346,6 +345,14 @@ export class SimpleBlockTracker implements Tracker {
   }
 }
 
+class RemoteBlockTracker extends SimpleBlockTracker {
+  destroy() {
+    super.destroy();
+
+    clear(this);
+  }
+}
+
 export interface UpdatableTracker extends Tracker {
   reset(env: Environment);
 }
diff --git a/packages/glimmer-runtime/lib/compiled/opcodes/builder.ts b/packages/glimmer-runtime/lib/compiled/opcodes/builder.ts
index 17f1eb31d4..9a6a255c4c 100644
--- a/packages/glimmer-runtime/lib/compiled/opcodes/builder.ts
+++ b/packages/glimmer-runtime/lib/compiled/opcodes/builder.ts
@@ -223,6 +223,18 @@ export abstract class BasicOpcodeBuilder extends StatementCompilationBufferProxy
 
   // vm
 
+  pushRemoteElement() {
+    this.append(new dom.PushRemoteElementOpcode());
+  }
+
+  popRemoteElement() {
+    this.append(new dom.PopRemoteElementOpcode());
+  }
+
+  popElement() {
+    this.append(new dom.PopElementOpcode());
+  }
+
   label(name: string) {
     this.append(this.labelFor(name));
   }
diff --git a/packages/glimmer-runtime/lib/compiled/opcodes/dom.ts b/packages/glimmer-runtime/lib/compiled/opcodes/dom.ts
index 70f773312a..2dc603b005 100644
--- a/packages/glimmer-runtime/lib/compiled/opcodes/dom.ts
+++ b/packages/glimmer-runtime/lib/compiled/opcodes/dom.ts
@@ -21,6 +21,7 @@ import { ValueReference } from '../../compiled/expressions/value';
 import { CompiledArgs, EvaluatedArgs } from '../../compiled/expressions/args';
 import { AttributeManager } from '../../dom/attribute-managers';
 import { ElementOperations } from '../../builder';
+import { Assert } from './vm';
 
 export class TextOpcode extends Opcode {
   public type = "text";
@@ -66,8 +67,8 @@ export class PushRemoteElementOpcode extends Opcode {
   public type = "push-remote-element";
 
   evaluate(vm: VM) {
-    let reference = vm.frame.getOperand();
-    let cache = isConst(reference) ? undefined : new ReferenceCache(reference);
+    let reference = vm.frame.getOperand<Simple.Element>();
+    let cache = isConstReference(reference) ? undefined : new ReferenceCache(reference);
     let element = cache ? cache.peek() : reference.value();
 
     vm.stack().pushRemoteElement(element);
@@ -81,11 +82,19 @@ export class PushRemoteElementOpcode extends Opcode {
     return {
       guid: this._guid,
       type: this.type,
-      args: [JSON.stringify(this.tag)]
+      args: ['$OPERAND']
     };
   }
 }
 
+export class PopRemoteElementOpcode extends Opcode {
+  public type = "pop-remote-element";
+
+  evaluate(vm: VM) {
+    vm.stack().popRemoteElement();
+  }
+}
+
 export class OpenComponentElementOpcode extends Opcode {
   public type = "open-component-element";
 
@@ -369,6 +378,20 @@ export class CloseElementOpcode extends Opcode {
   }
 }
 
+export class PopElementOpcode extends Opcode {
+  public type = "pop-element";
+
+  evaluate(vm: VM) {
+    vm.stack().popElement();
+  }
+}
+
+export interface StaticAttrOptions {
+  namespace: string;
+  name: string;
+  value: string;
+}
+
 export class StaticAttrOpcode extends Opcode {
   public type = "static-attr";
 
diff --git a/packages/glimmer-runtime/lib/syntax/builtins/in-element.ts b/packages/glimmer-runtime/lib/syntax/builtins/in-element.ts
new file mode 100644
index 0000000000..2a365f3c52
--- /dev/null
+++ b/packages/glimmer-runtime/lib/syntax/builtins/in-element.ts
@@ -0,0 +1,34 @@
+import {
+  Statement as StatementSyntax
+} from '../../syntax';
+
+import OpcodeBuilderDSL from '../../compiled/opcodes/builder';
+import * as Syntax from '../core';
+import Environment from '../../environment';
+
+export default class InElementSyntax extends StatementSyntax {
+  type = "in-element-statement";
+
+  public args: Syntax.Args;
+  public templates: Syntax.Templates;
+  public isStatic = false;
+
+  constructor({ args, templates }: { args: Syntax.Args, templates: Syntax.Templates }) {
+    super();
+    this.args = args;
+    this.templates = templates;
+  }
+
+  compile(dsl: OpcodeBuilderDSL, env: Environment) {
+    let { args, templates } = this;
+
+    dsl.block({ templates, args }, (dsl, BEGIN, END) => {
+      dsl.putArgs(args);
+      dsl.test('simple');
+      dsl.jumpUnless(END);
+      dsl.pushRemoteElement();
+      dsl.evaluate('default');
+      dsl.popRemoteElement();
+    });
+  }
+}
diff --git a/packages/glimmer-runtime/lib/syntax/builtins/with-dynamic-vars.ts b/packages/glimmer-runtime/lib/syntax/builtins/with-dynamic-vars.ts
index 56f01ba93b..37befcc473 100644
--- a/packages/glimmer-runtime/lib/syntax/builtins/with-dynamic-vars.ts
+++ b/packages/glimmer-runtime/lib/syntax/builtins/with-dynamic-vars.ts
@@ -22,10 +22,12 @@ export default class WithDynamicVarsSyntax extends StatementSyntax {
   compile(dsl: OpcodeBuilderDSL, env: Environment) {
     let { args, templates } = this;
 
-    dsl.block({ templates, args }, (dsl) => {
-      dsl.pushRemoteElement();
+    dsl.unit({ templates }, dsl => {
+      dsl.putArgs(args);
+      dsl.pushDynamicScope();
+      dsl.bindDynamicScope(args.named.keys);
       dsl.evaluate('default');
-      dsl.popElement();
+      dsl.popDynamicScope();
     });
   }
 }
diff --git a/packages/glimmer-runtime/tests/ember-component-test.ts b/packages/glimmer-runtime/tests/ember-component-test.ts
index 7b5382dc5a..3db25d2514 100644
--- a/packages/glimmer-runtime/tests/ember-component-test.ts
+++ b/packages/glimmer-runtime/tests/ember-component-test.ts
@@ -27,7 +27,7 @@ import { equalTokens, stripTight } from "glimmer-test-helpers";
 
 import { CLASS_META, UpdatableReference, setProperty as set } from 'glimmer-object-reference';
 
-class EmberishRootView extends EmberObject {
+export class EmberishRootView extends EmberObject {
   private parent: Element;
   protected _result: RenderResult;
   protected template: Template<{}>;
@@ -73,7 +73,7 @@ function module(name: string) {
 
 module("Components - generic - props");
 
-function appendViewFor(template: string, context: Object = {}) {
+export function appendViewFor(template: string, context: Object = {}) {
   class MyRootView extends EmberishRootView {
     protected env = env;
     protected template = env.compile(template);
@@ -90,7 +90,7 @@ function appendViewFor(template: string, context: Object = {}) {
   return view;
 }
 
-function assertAppended(content: string) {
+export function assertAppended(content: string) {
   equalTokens((<HTMLElement>document.querySelector('#qunit-fixture')), content);
 }
 
@@ -147,12 +147,12 @@ function assertEmberishElement(...args) {
   equalsElement(view.element, tagName, fullAttrs, contents);
 }
 
-function assertElementIsEmberishElement(element: Element, tagName: string, attrs: Object, contents: string);
-function assertElementIsEmberishElement(element: Element, tagName: string, attrs: Object);
-function assertElementIsEmberishElement(element: Element, tagName: string, contents: string);
-function assertElementIsEmberishElement(element: Element, tagName: string);
+export function assertElementIsEmberishElement(element: Element, tagName: string, attrs: Object, contents: string);
+export function assertElementIsEmberishElement(element: Element, tagName: string, attrs: Object);
+export function assertElementIsEmberishElement(element: Element, tagName: string, contents: string);
+export function assertElementIsEmberishElement(element: Element, tagName: string);
 
-function assertElementIsEmberishElement(element: Element, ...args) {
+export function assertElementIsEmberishElement(element: Element, ...args) {
   let tagName, attrs, contents;
   if (args.length === 2) {
     if (typeof args[1] === 'string') [tagName, attrs, contents] = [args[0], {}, args[1]];
diff --git a/packages/glimmer-runtime/tests/in-element-test.ts b/packages/glimmer-runtime/tests/in-element-test.ts
new file mode 100644
index 0000000000..e03513ce93
--- /dev/null
+++ b/packages/glimmer-runtime/tests/in-element-test.ts
@@ -0,0 +1,401 @@
+import {
+  TestEnvironment,
+  stripTight,
+  equalsElement,
+  EmberishCurlyComponent
+ } from "glimmer-test-helpers";
+
+import {
+  assertAppended,
+  assertElementIsEmberishElement,
+  EmberishRootView
+} from './ember-component-test';
+
+import { CLASS_META, setProperty as set } from 'glimmer-object-reference';
+
+let view, env;
+
+function rerender() {
+  view.rerender();
+}
+
+function appendViewFor(template: string, context: Object = {}) {
+  class MyRootView extends EmberishRootView {
+    protected env = env;
+    protected template = env.compile(template);
+  }
+
+  view = new MyRootView(context);
+  MyRootView[CLASS_META].seal();
+
+  env.begin();
+  view.appendTo('#qunit-fixture');
+  env.commit();
+
+  return view;
+}
+
+QUnit.module('Targeting a remote element', {
+  setup() {
+    env = new TestEnvironment();
+  }
+});
+
+QUnit.test('basic', function(assert) {
+  let externalElement = document.createElement('div');
+
+  appendViewFor(
+    stripTight`{{#-in-element externalElement}}[{{foo}}]{{/-in-element}}`,
+    { externalElement, foo: 'Yippie!' }
+  );
+
+  equalsElement(externalElement, 'div', {}, stripTight`[Yippie!]`);
+
+  set(view, 'foo', 'Double Yips!');
+  rerender();
+
+  equalsElement(externalElement, 'div', {}, stripTight`[Double Yips!]`);
+
+  set(view, 'foo', 'Yippie!');
+  rerender();
+
+  equalsElement(externalElement, 'div', {}, stripTight`[Yippie!]`);
+});
+
+QUnit.test('changing to falsey', function(assert) {
+  let first = document.createElement('div');
+  let second = document.createElement('div');
+
+  appendViewFor(
+    stripTight`
+      |{{foo}}|
+      {{#-in-element first}}[{{foo}}]{{/-in-element}}
+      {{#-in-element second}}[{{foo}}]{{/-in-element}}
+    `,
+    { first, second: null, foo: 'Yippie!' }
+  );
+
+  equalsElement(first, 'div', {}, `[Yippie!]`);
+  equalsElement(second, 'div', {}, ``);
+  assertAppended('|Yippie!|<!----><!---->');
+
+  set(view, 'foo', 'Double Yips!');
+  rerender();
+
+  equalsElement(first, 'div', {}, `[Double Yips!]`);
+  equalsElement(second, 'div', {}, ``);
+  assertAppended('|Double Yips!|<!----><!---->');
+
+  set(view, 'first', null);
+  rerender();
+
+  equalsElement(first, 'div', {}, ``);
+  equalsElement(second, 'div', {}, ``);
+  assertAppended('|Double Yips!|<!----><!---->');
+
+  set(view, 'second', second);
+  rerender();
+
+  equalsElement(first, 'div', {}, ``);
+  equalsElement(second, 'div', {}, `[Double Yips!]`);
+  assertAppended('|Double Yips!|<!----><!---->');
+
+  set(view, 'foo', 'Yippie!');
+  rerender();
+
+  equalsElement(first, 'div', {}, ``);
+  equalsElement(second, 'div', {}, `[Yippie!]`);
+  assertAppended('|Yippie!|<!----><!---->');
+
+  set(view, 'first', first);
+  set(view, 'second', null);
+  rerender();
+
+  equalsElement(first, 'div', {}, `[Yippie!]`);
+  equalsElement(second, 'div', {}, ``);
+  assertAppended('|Yippie!|<!----><!---->');
+});
+
+QUnit.test('with pre-existing content', function(assert) {
+  let externalElement = document.createElement('div');
+  let initialContent = externalElement.innerHTML = '<p>Hello there!</p>';
+
+  appendViewFor(
+    stripTight`{{#-in-element externalElement}}[{{foo}}]{{/-in-element}}`,
+    { externalElement, foo: 'Yippie!' }
+  );
+
+  assertAppended('<!---->');
+  equalsElement(externalElement, 'div', {}, `${initialContent}[Yippie!]`);
+
+  set(view, 'foo', 'Double Yips!');
+  rerender();
+
+  assertAppended('<!---->');
+  equalsElement(externalElement, 'div', {}, `${initialContent}[Double Yips!]`);
+
+  set(view, 'foo', 'Yippie!');
+  rerender();
+
+  assertAppended('<!---->');
+  equalsElement(externalElement, 'div', {}, `${initialContent}[Yippie!]`);
+
+  set(view, 'externalElement', null);
+  rerender();
+
+  assertAppended('<!---->');
+  equalsElement(externalElement, 'div', {}, `${initialContent}`);
+
+  set(view, 'externalElement', externalElement);
+  rerender();
+
+  assertAppended('<!---->');
+  equalsElement(externalElement, 'div', {}, `${initialContent}[Yippie!]`);
+});
+
+QUnit.test('updating remote element', function(assert) {
+  let first = document.createElement('div');
+  let second = document.createElement('div');
+
+  appendViewFor(
+    stripTight`{{#-in-element targetElement}}[{{foo}}]{{/-in-element}}`,
+    {
+      targetElement: first,
+      foo: 'Yippie!'
+    }
+  );
+
+  equalsElement(first, 'div', {}, `[Yippie!]`);
+  equalsElement(second, 'div', {}, ``);
+
+  set(view, 'foo', 'Double Yips!');
+  rerender();
+
+  equalsElement(first, 'div', {}, `[Double Yips!]`);
+  equalsElement(second, 'div', {}, ``);
+
+  set(view, 'foo', 'Yippie!');
+  rerender();
+
+  equalsElement(first, 'div', {}, `[Yippie!]`);
+  equalsElement(second, 'div', {}, ``);
+
+  set(view, 'targetElement', second);
+  rerender();
+
+  equalsElement(first, 'div', {}, ``);
+  equalsElement(second, 'div', {}, `[Yippie!]`);
+
+  set(view, 'foo', 'Double Yips!');
+  rerender();
+
+  equalsElement(first, 'div', {}, ``);
+  equalsElement(second, 'div', {}, `[Double Yips!]`);
+
+  set(view, 'foo', 'Yippie!');
+  rerender();
+
+  equalsElement(first, 'div', {}, ``);
+  equalsElement(second, 'div', {}, `[Yippie!]`);
+});
+
+QUnit.test('inside an `{{if}}', function(assert) {
+  let first = document.createElement('div');
+  let second = document.createElement('div');
+
+  appendViewFor(
+    stripTight`
+      {{#if showFirst}}
+        {{#-in-element first}}[{{foo}}]{{/-in-element}}
+      {{/if}}
+      {{#if showSecond}}
+        {{#-in-element second}}[{{foo}}]{{/-in-element}}
+      {{/if}}
+    `,
+    {
+      first,
+      second,
+      showFirst: true,
+      showSecond: false,
+      foo: 'Yippie!'
+    }
+  );
+
+  equalsElement(first, 'div', {}, stripTight`[Yippie!]`);
+  equalsElement(second, 'div', {}, stripTight``);
+
+  set(view, 'showFirst', false);
+  rerender();
+
+  equalsElement(first, 'div', {}, stripTight``);
+  equalsElement(second, 'div', {}, stripTight``);
+
+  set(view, 'showSecond', true);
+  rerender();
+
+  equalsElement(first, 'div', {}, stripTight``);
+  equalsElement(second, 'div', {}, stripTight`[Yippie!]`);
+
+  set(view, 'foo', 'Double Yips!');
+  rerender();
+
+  equalsElement(first, 'div', {}, stripTight``);
+  equalsElement(second, 'div', {}, stripTight`[Double Yips!]`);
+
+  set(view, 'showSecond', false);
+  rerender();
+
+  equalsElement(first, 'div', {}, stripTight``);
+  equalsElement(second, 'div', {}, stripTight``);
+
+  set(view, 'showFirst', true);
+  rerender();
+
+  equalsElement(first, 'div', {}, stripTight`[Double Yips!]`);
+  equalsElement(second, 'div', {}, stripTight``);
+
+  set(view, 'foo', 'Yippie!');
+  rerender();
+
+  equalsElement(first, 'div', {}, stripTight`[Yippie!]`);
+  equalsElement(second, 'div', {}, stripTight``);
+});
+
+QUnit.test('multiple', function(assert) {
+  let firstElement = document.createElement('div');
+  let secondElement = document.createElement('div');
+
+  appendViewFor(
+    stripTight`
+      {{#-in-element firstElement}}
+        [{{foo}}]
+      {{/-in-element}}
+      {{#-in-element secondElement}}
+        [{{bar}}]
+      {{/-in-element}}
+      `,
+    {
+      firstElement,
+      secondElement,
+      foo: 'Hello!',
+      bar: 'World!'
+    }
+  );
+
+  equalsElement(firstElement, 'div', {}, stripTight`[Hello!]`);
+  equalsElement(secondElement, 'div', {}, stripTight`[World!]`);
+
+  set(view, 'foo', 'GoodBye!');
+  rerender();
+
+  equalsElement(firstElement, 'div', {}, stripTight`[GoodBye!]`);
+  equalsElement(secondElement, 'div', {}, stripTight`[World!]`);
+
+  set(view, 'bar', 'Folks!');
+  rerender();
+
+  equalsElement(firstElement, 'div', {}, stripTight`[GoodBye!]`);
+  equalsElement(secondElement, 'div', {}, stripTight`[Folks!]`);
+
+  set(view, 'bar', 'World!');
+  rerender();
+
+  equalsElement(firstElement, 'div', {}, stripTight`[GoodBye!]`);
+  equalsElement(secondElement, 'div', {}, stripTight`[World!]`);
+
+  set(view, 'foo', 'Hello!');
+  rerender();
+
+  equalsElement(firstElement, 'div', {}, stripTight`[Hello!]`);
+  equalsElement(secondElement, 'div', {}, stripTight`[World!]`);
+});
+
+QUnit.test('nesting', function(assert) {
+  let firstElement = document.createElement('div');
+  let secondElement = document.createElement('div');
+
+  appendViewFor(
+    stripTight`
+      {{#-in-element firstElement}}
+        [{{foo}}]
+        {{#-in-element secondElement}}
+          [{{bar}}]
+        {{/-in-element}}
+      {{/-in-element}}
+      `,
+    {
+      firstElement,
+      secondElement,
+      foo: 'Hello!',
+      bar: 'World!'
+    }
+  );
+
+  equalsElement(firstElement, 'div', {}, stripTight`[Hello!]<!---->`);
+  equalsElement(secondElement, 'div', {}, stripTight`[World!]`);
+
+  set(view, 'foo', 'GoodBye!');
+  rerender();
+
+  equalsElement(firstElement, 'div', {}, stripTight`[GoodBye!]<!---->`);
+  equalsElement(secondElement, 'div', {}, stripTight`[World!]`);
+
+  set(view, 'bar', 'Folks!');
+  rerender();
+
+  equalsElement(firstElement, 'div', {}, stripTight`[GoodBye!]<!---->`);
+  equalsElement(secondElement, 'div', {}, stripTight`[Folks!]`);
+
+  set(view, 'bar', 'World!');
+  rerender();
+
+  equalsElement(firstElement, 'div', {}, stripTight`[GoodBye!]<!---->`);
+  equalsElement(secondElement, 'div', {}, stripTight`[World!]`);
+
+  set(view, 'foo', 'Hello!');
+  rerender();
+
+  equalsElement(firstElement, 'div', {}, stripTight`[Hello!]<!---->`);
+  equalsElement(secondElement, 'div', {}, stripTight`[World!]`);
+});
+
+QUnit.test('components are destroyed', function(assert) {
+  let destroyed = 0;
+  let DestroyMeComponent = EmberishCurlyComponent.extend({
+    destroy() {
+      this._super();
+      destroyed++;
+    }
+  });
+
+  env.registerEmberishCurlyComponent('destroy-me', DestroyMeComponent as any, 'destroy me!');
+
+  let externalElement = document.createElement('div');
+
+  appendViewFor(
+    stripTight`
+      {{#if showExternal}}
+        {{#-in-element externalElement}}[{{destroy-me}}]{{/-in-element}}
+      {{/if}}
+    `,
+    {
+      externalElement,
+      showExternal: false,
+    }
+  );
+
+  equalsElement(externalElement, 'div', {}, stripTight``);
+  assert.equal(destroyed, 0, 'component was destroyed');
+
+  set(view, 'showExternal', true);
+  rerender();
+
+  assertElementIsEmberishElement(externalElement.firstElementChild, 'div', { }, 'destroy me!');
+  assert.equal(destroyed, 0, 'component was destroyed');
+
+  set(view, 'showExternal', false);
+  rerender();
+
+  equalsElement(externalElement, 'div', {}, stripTight``);
+  assert.equal(destroyed, 1, 'component was destroyed');
+});
diff --git a/packages/glimmer-test-helpers/lib/environment.ts b/packages/glimmer-test-helpers/lib/environment.ts
index 69a93c6fac..9569713205 100644
--- a/packages/glimmer-test-helpers/lib/environment.ts
+++ b/packages/glimmer-test-helpers/lib/environment.ts
@@ -42,6 +42,7 @@ import {
   ArgsSyntax,
   OptimizedAppend,
   WithDynamicVarsSyntax,
+  InElementSyntax,
 
   // References
   ValueReference,
@@ -788,6 +789,8 @@ export class TestEnvironment extends Environment {
           return new RenderInverseIdentitySyntax({ args, templates });
         case '-with-dynamic-vars':
           return new WithDynamicVarsSyntax({ args, templates });
+        case '-in-element':
+          return new InElementSyntax({ args, templates });
       }
     }