<repo_name>c0urg3tt3/glimmer.js<file_sep>/packages/@glimmer/compiler-delegates/src/delegates/module-unification.ts
import { BundleCompilerDelegate, AddedTemplate, Builtins, BuiltinsMap, Name, ModulePath, Identifier } from '../bundle';
import { getImportStatements, OutputFiles } from '../utils/code-gen';
import { BundleCompiler, Specifier, specifierFor, SpecifierMap } from '@glimmer/bundle-compiler';
import { SymbolTable, ProgramSymbolTable, ComponentCapabilities } from '@glimmer/interfaces';
import { expect, Dict } from '@glimmer/util';
import { relative, extname, dirname } from 'path';
import { SerializedTemplateBlock } from '@glimmer/wire-format';
import { CompilableTemplate, CompileOptions, ICompilableTemplate } from '@glimmer/opcode-compiler';
import { ConstantPool } from '@glimmer/program';
import Debug from 'debug';
import { Project } from 'glimmer-analyzer';
import { CAPABILITIES } from '@glimmer/component';

const debug = Debug('@glimmer/compiler-delegates:mu-delegate');

export default class ModuleUnificationCompilerDelegate implements BundleCompilerDelegate {
  public bundleCompiler: BundleCompiler;
  protected project: Project;
  protected specifiersToSymbolTable: Map<Specifier, SymbolTable> = new Map();
  private builtinsMap: BuiltinsMap;

  constructor(protected projectPath: string, public outputFiles: OutputFiles, envBuiltIns: Builtins = {}) {
    debug('initialized MU compiler delegate; project=%s', projectPath);
    this.project = new Project(projectPath);
    let builtins = {
      main: specifierFor('main', 'mainTemplate'),
      if: specifierFor('@glimmer/application', 'ifHelper'),
      action: specifierFor('@glimmer/application', 'actionHelper'),
      ...envBuiltIns
    };

    let byName = new Map<Name, ModulePath>();
    let byModulePath = new Map<ModulePath, Name>();
    let byIdentifier = new Map<Identifier, Specifier>();

    Object.keys(builtins).forEach(builtin => {
      let specifier = builtins[builtin];
      byName.set(specifier.name, specifier.module);
      byModulePath.set(specifier.module, specifier.name);
      byIdentifier.set(builtin, specifier);
    });

    this.builtinsMap = {
      byModulePath,
      byName,
      byIdentifier
    };
  }

  hasComponentInScope(name: string, referrer: Specifier) {
    debug('hasComponentInScope; name=%s; referrer=%o', name, referrer);

    let referrerSpec = expect(
      this.project.specifierForPath(referrer.module),
      `The component <${name}> was used in ${referrer.module} but could not be found.`
    );

    return !!this.project.resolver.identify(`template:${name}`, referrerSpec);
  }

  resolveComponentSpecifier(name: string, referrer: Specifier) {
    let referrerSpec = expect(this.project.specifierForPath(referrer.module), `expected specifier for path ${referrer.module}`);
    let resolved = this.project.resolver.identify(`template:${name}`, referrerSpec);

    let resolvedSpecifier = this.getCompilerSpecifier(resolved);
    return resolvedSpecifier;
  }

  specifierFor(relativePath: string) {
    return specifierFor(relativePath, 'default');
  }

  /**
   * Converts a path relative to the current working directory into a path
   * relative to the project root.
   */
  normalizePath(modulePath: string): string {
    let project = this.project;
    let projectPath = relative(process.cwd(), project.projectDir);

    return relative(projectPath, modulePath);
  }

  protected getCompilerSpecifier(specifier: string): Specifier {
    let modulePath = expect(this.project.pathForSpecifier(specifier), `couldn't find module with specifier '${specifier}'`);

    return specifierFor(modulePath, 'default');
  }

  getComponentCapabilities(): ComponentCapabilities {
    return CAPABILITIES;
  }

  hasHelperInScope(helperName: string, referrer: Specifier) {
    if (this.builtinsMap.byIdentifier.has(helperName)) { return true; }

    let referrerSpec = this.project.specifierForPath(referrer.module) || undefined;
    return !!this.project.resolver.identify(`helper:${helperName}`, referrerSpec);
  }

  resolveHelperSpecifier(helperName: string, referrer: Specifier) {
    if (this.builtinsMap.byIdentifier.has(helperName)) {
      return this.builtinsMap.byIdentifier.get(helperName);
    }

    let referrerSpec = this.project.specifierForPath(referrer.module) || undefined;
    let resolvedSpec = this.project.resolver.identify(`helper:${helperName}`, referrerSpec);

    return this.getCompilerSpecifier(resolvedSpec);
  }

  getComponentLayout(_specifier: Specifier, block: SerializedTemplateBlock, options: CompileOptions<Specifier>): ICompilableTemplate<ProgramSymbolTable> {
    return CompilableTemplate.topLevel(block, options);
  }

  generateDataSegment(map: SpecifierMap, pool: ConstantPool, table: number[], nextFreeHandle: number, compiledBlocks: Map<Specifier, AddedTemplate>) {
    debug('generating data segment');

    let externalModuleTable = this.generateExternalModuleTable(map);
    let constantPool = this.generateConstantPool(pool);
    let heapTable = this.generateHeapTable(table);
    let specifierMap = this.generateSpecifierMap(map);
    let symbolTables = this.generateSymbolTables(compiledBlocks);

    let source = strip`
      ${externalModuleTable}
      ${heapTable}
      ${constantPool}
      ${specifierMap}
      ${symbolTables}
      export default { moduleTable, heapTable, pool, specifierMap, symbolTables };`;
    debug('generated data segment; source=%s', source);

    return source;
  }

  generateSymbolTables(compiledBlocks: Map<Specifier, AddedTemplate>) {
    let symbolTables: Dict<ProgramSymbolTable> = {};

    for (let [specifier, template ] of compiledBlocks) {
      if (!(this.builtinsMap.byName.has(specifier.name))) {
        let muSpecifier = this.muSpecifierForSpecifier(specifier);

        symbolTables[muSpecifier] = {
          hasEval: (template as SerializedTemplateBlock).hasEval,
          symbols: (template as SerializedTemplateBlock).symbols,
          referrer: null
        };
      }
    }

    return `const symbolTables = ${inlineJSON(symbolTables)};`;
  }

  generateSpecifierMap(map: SpecifierMap) {
    let entries = Array.from(map.vmHandleBySpecifier.entries());
    let specifierMap: Dict<number> = {};

    for (let [specifier, handle] of entries) {
      if (!(this.builtinsMap.byName.has(specifier.name))) {
        let muSpecifier = this.muSpecifierForSpecifier(specifier);
        specifierMap[muSpecifier] = handle;
      }
    }

    return `const specifierMap = ${inlineJSON(specifierMap)};`;
  }

  muSpecifierForSpecifier(specifier: Specifier): string {
    let project = this.project;

    return expect(
      project.specifierForPath(specifier.module),
      `expected to have a MU specifier for module ${specifier.module}`
    );
  }

  generateHeapTable(table: number[]) {
    return strip`
      const heapTable = ${inlineJSON(table)};
    `;
  }

  generateConstantPool(pool: ConstantPool) {
    return strip`
      const pool = ${inlineJSON(pool)};
    `;
  }

  generateExternalModuleTable(map: SpecifierMap) {
    let project = this.project;
    let self = this;
    let dataSegmentPath = dirname(this.outputFiles.dataSegment);

    // First, convert the map into an array of specifiers, using the handle
    // as the index.
    let modules = toSparseArray(map.byHandle)
      .map(normalizeModulePaths)
      .filter(m => m) as Specifier[];

    let source = generateExternalModuleTable(modules, this.builtinsMap);

    return source;

    function normalizeModulePaths(moduleSpecifier: Specifier) {
      if (self.builtinsMap.byName.has(moduleSpecifier.name)) {
        return moduleSpecifier;
      } else {
        let specifier = self.muSpecifierForSpecifier(moduleSpecifier);

        debug('resolved MU specifier; specifier=%s', specifier);

        let [type] = specifier.split(':');

        switch (type) {
          case 'template':
            return getComponentImport(specifier);
          case 'helper':
            return moduleSpecifier;
          default:
            throw new Error(`Unsupported type in specifier map: ${type}`);
        }
      }
    }

    function getComponentImport(referrer: string): Specifier | null {
      let componentSpec = project.resolver.identify('component:', referrer);
      if (componentSpec) {
        let componentPath = project.pathForSpecifier(componentSpec)!;
        componentPath = relative(dataSegmentPath, componentPath);
        debug('found corresponding component; referrer=%s; path=%s; dataSegment=%s', referrer, componentPath, dataSegmentPath);
        componentPath = componentPath.replace(extname(componentPath), '');

        return specifierFor(componentPath, 'default');
      }

      debug('no component for template; referrer=%s', referrer);
      return null;
    }
  }

  hasModifierInScope(_modifierName: string, _referrer: Specifier): boolean {
    return false;
  }
  resolveModifierSpecifier(_modifierName: string, _referrer: Specifier): Specifier {
    throw new Error("Method not implemented.");
  }
  hasPartialInScope(_partialName: string, _referrer: Specifier): boolean {
    return false;
  }
  resolvePartialSpecifier(_partialName: string, _referrer: Specifier): Specifier {
    throw new Error("Method not implemented.");
  }
}

function inlineJSON(data: any) {
  return `JSON.parse(${JSON.stringify(JSON.stringify(data))})`;
}

function toSparseArray<T>(map: Map<number, T>): T[] {
  let array: T[] = [];

  for (let [key, value] of map) {
    array[key] = value;
  }

  return array;
}

function generateExternalModuleTable(modules: Specifier[], builtins: BuiltinsMap) {
  let { imports, identifiers } = getImportStatements(modules, builtins);

  return `
${imports.join('\n')}
const moduleTable = [${identifiers.join(',')}];
`;
}

function strip(strings: TemplateStringsArray, ...args: string[]) {
  if (typeof strings === 'object') {
    return strings.map((str: string, i: number) => {
      return `${str.split('\n').map(s => s.trim()).join('')}${args[i] ? args[i] : ''}`;
    }).join('');
  } else {
    return strings[0].split('\n').map((s: string) => s.trim()).join(' ');
  }
}
<file_sep>/packages/@glimmer/application-test-helpers/src/app-builder.ts
import {
  Simple
} from '@glimmer/interfaces';
import Resolver, { BasicModuleRegistry, ResolverConfiguration } from '@glimmer/resolver';
import { Opaque, Dict, ProgramSymbolTable } from '@glimmer/interfaces';
import { FactoryDefinition } from '@glimmer/di';
import defaultResolverConfiguration from './default-resolver-configuration';
import { precompile } from './compiler';
import Application, { ApplicationConstructor, RuntimeCompilerLoader, BytecodeLoader, Loader } from '@glimmer/application';
import { ComponentManager, CAPABILITIES } from '@glimmer/component';
import { assert } from '@glimmer/util';
import { BundleCompiler, CompilerDelegate as ICompilerDelegate, Specifier, specifierFor } from '@glimmer/bundle-compiler';
import { buildAction, mainTemplate } from '@glimmer/application';
import { SerializedTemplateBlock } from '@glimmer/wire-format';
import { CompilableTemplate, CompileOptions } from '@glimmer/opcode-compiler';
import { CompilableTemplate as ICompilableTemplate, Cursor } from '@glimmer/runtime';

import { DOMBuilder, SyncRenderer } from '@glimmer/application';

export interface AppBuilderOptions<T> {
  appName?: string;
  loader?: string;
  ApplicationClass?: ApplicationConstructor<T>;
  ComponentManager?: any; // TODO - typing
  resolverConfiguration?: ResolverConfiguration;
  document?: Simple.Document;
}

export interface ComponentFactory extends FactoryDefinition<Opaque> {};

export class TestApplication extends Application {
  rootElement: Element;
}

export class AppBuilder<T extends TestApplication> {
  rootName: string;
  modules: Dict<Opaque> = {};
  options: AppBuilderOptions<T>;

  constructor(name: string, options: AppBuilderOptions<T>) {
    this.rootName = name;
    this.options = options;
    this.modules[`component-manager:/${this.rootName}/component-managers/main`] = this.options.ComponentManager;
    this.template('Main', '<div />');
    this.helper('action', buildAction);
  }

  template(name: string, template: string) {
    assert(name.charAt(0) === name.charAt(0).toUpperCase(), 'template names must start with a capital letter');

    let specifier = `template:/${this.rootName}/components/${name}`;
    this.modules[specifier] = precompile(template, { meta: { specifier }});
    return this;
  }

  component(name: string, componentFactory: ComponentFactory) {
    let specifier = `component:/${this.rootName}/components/${name}`;
    this.modules[specifier] = componentFactory;
    return this;
  }

  helper(name: string, helperFunc: Function) {
    let specifier = `helper:/${this.rootName}/components/${name}`;
    this.modules[specifier] = helperFunc;
    return this;
  }

  protected buildResolver() {
    let resolverConfiguration = this.options.resolverConfiguration || defaultResolverConfiguration;
    resolverConfiguration.app = resolverConfiguration.app || {
      name: this.rootName,
      rootName: this.rootName
    };

    let registry = new BasicModuleRegistry(this.modules);
    return new Resolver(resolverConfiguration, registry);
  }

  protected buildRuntimeCompilerLoader(resolver: Resolver) {
    return new RuntimeCompilerLoader(resolver);
  }

  protected buildBytecodeLoader(resolver: Resolver) {
    let delegate = new CompilerDelegate(resolver);
    let compiler = new BundleCompiler(delegate);

    let mainSpecifier = specifierFor(mainTemplate.meta.specifier, 'default');
    compiler.addCustom(mainSpecifier, JSON.parse(mainTemplate.block));

    for (let mod in this.modules) {
      let [key] = mod.split(':');

      if (key === 'template') {
        compiler.addCustom(specifierFor(mod, 'default'), JSON.parse((this.modules[mod] as any).block));
      }
    }

    let { heap, pool } = compiler.compile();

    let specifierMap = compiler.getSpecifierMap();
    let entryHandle = specifierMap.vmHandleBySpecifier.get(mainSpecifier);

    let table = [];
    let map = new Map();
    let symbols = new Map();

    for (let [spec, handle] of specifierMap.vmHandleBySpecifier.entries()) {
      map.set(spec.module, handle);
    }

    for (let [handle, mod] of specifierMap.byHandle.entries()) {
      table[handle] = mod;
    }

    for (let [spec, block] of compiler.compiledBlocks.entries()) {
      symbols.set(spec.module, { symbols: (block as SerializedTemplateBlock).symbols, hasEval: (block as SerializedTemplateBlock).hasEval });
    }

    let bytecode = heap.buffer;
    let data = {
      pool,
      table,
      map,
      symbols,
      entryHandle,
      heap: {
        table: heap.table,
        handle: heap.handle
      }
    };

    return new BytecodeLoader({ bytecode, data });
  }

  async boot(): Promise<T> {
    let resolver = this.buildResolver();
    let loader: Loader;

    switch (this.options.loader) {
      case 'runtime-compiler':
        loader = this.buildRuntimeCompilerLoader(resolver);
        break;
      case 'bytecode':
        loader = this.buildBytecodeLoader(resolver);
        break;
      default:
        throw new Error(`Unrecognized loader ${this.options.loader}`);
    }

    let doc: Document = this.options.document as Document || document;
    let element = doc.body;
    let cursor = new Cursor(element, null);
    let builder = new DOMBuilder(cursor);
    let renderer = new SyncRenderer();

    let app = new this.options.ApplicationClass({
      resolver,
      builder,
      loader,
      renderer,
      rootName: this.rootName,
      document: this.options.document
    });

    let rootElement = doc.createElement('div');
    app.rootElement = rootElement;
    app.renderComponent('Main', rootElement);

    await app.boot();

    return app;
  }
}

class CompilerDelegate implements ICompilerDelegate {
  constructor(protected resolver: Resolver) {
  }

  hasComponentInScope(name: string, referrer: Specifier): boolean {
    return !!this.resolver.identify(`template:${name}`, referrer.module);
  }

  resolveComponentSpecifier(name: string, referrer: Specifier): Specifier {
    let resolved = this.resolver.identify(`template:${name}`, referrer.module);
    return specifierFor(resolved, 'default');
  }

  getComponentCapabilities() {
    return CAPABILITIES;
  }

  hasHelperInScope(helperName: string, referrer: Specifier): boolean {
    return !!this.resolver.identify(`helper:${helperName}`, referrer.module);
  }

  resolveHelperSpecifier(helperName: string, referrer: Specifier): Specifier {
    let resolved = this.resolver.identify(`helper:${helperName}`, referrer.module);
    return specifierFor(resolved, 'default');
  }

  hasPartialInScope(partialName: string, referrer: Specifier): boolean {
    throw new Error("Method not implemented.");
  }

  resolvePartialSpecifier(partialName: string, referrer: Specifier): Specifier {
    throw new Error("Method not implemented.");
  }

  getComponentLayout(specifier: Specifier, block: SerializedTemplateBlock, options: CompileOptions<Specifier>): ICompilableTemplate<ProgramSymbolTable> {
    return CompilableTemplate.topLevel(block, options);
  }

  hasModifierInScope(modifierName: string, referrer: Specifier): boolean {
    throw new Error("Method not implemented.");
  }

  resolveModifierSpecifier(modifierName: string, referrer: Specifier): Specifier {
    throw new Error("Method not implemented.");
  }
}

function buildApp<T extends TestApplication>(options: AppBuilderOptions<T> = {}): AppBuilder<T> {
  options.appName = options.appName || 'test-app';
  options.loader = options.loader || 'runtime-compiler';
  options.ComponentManager = options.ComponentManager || ComponentManager;
  options.ApplicationClass = options.ApplicationClass || TestApplication as ApplicationConstructor<T>;

  return new AppBuilder(options.appName, options);
}

export { buildApp };
<file_sep>/packages/@glimmer/application/src/loaders/bytecode/resolver.ts
import { RuntimeResolver, ComponentDefinition, SymbolTable } from '@glimmer/interfaces';
import { Specifier } from '@glimmer/bundle-compiler';
import { unreachable, Opaque, expect } from '@glimmer/util';
import { ComponentManager, Helper } from '@glimmer/runtime';
import { Owner, Factory } from '@glimmer/di';
import { CAPABILITIES } from '@glimmer/component';

function buildComponentDefinition(ComponentClass: Factory<Opaque>, manager: ComponentManager<Opaque, Opaque>, handle?: number, symbolTable?: SymbolTable) {
  return {
    manager,
    state: {
      handle,
      symbolTable,
      ComponentClass,
      capabilities: CAPABILITIES
    }
  };
}

/**
 * Exchanges VM handles for concrete implementations.
 */
export default class BytecodeResolver implements RuntimeResolver<Specifier> {
  constructor(protected owner: Owner, protected table: Opaque[], protected map: Map<string, number>, protected symbols: Map<string, SymbolTable>) {
  }

  lookupComponent(name: string, referrer: Specifier): ComponentDefinition {
    let owner = this.owner;

    let manager = expect(owner.lookup('component-manager:main'), 'expected to find component manager');
    let resolved = owner.identify(`template:${name}`, referrer.module);
    let layout = this.map.get(resolved);
    let symbolTable = this.symbols.get(resolved);

    let resolvedClass = owner.identify('component:', resolved);
    let ComponentClass;

    if (resolvedClass) {
      ComponentClass = owner.lookup(resolvedClass);
    }

    return buildComponentDefinition(ComponentClass, manager, layout, symbolTable);
  }

  lookupPartial(name: string, referrer: Specifier): number {
    throw unreachable();
  }

  resolve<U>(handle: number): U {
    let spec = this.table[handle] as Specifier;
    if (spec.module.substring(0, 6) === 'helper') {
      return this.resolveHelper(spec) as any as U;
    } else {
      return this.resolveComponentDefinition(spec) as any as U;
    }
  }

  resolveComponentDefinition(spec: Specifier): ComponentDefinition {
    let manager = this.owner.lookup(`component-manager:main`);
    let ComponentClass = this.owner.factoryFor(`component:`, spec.module);

    return buildComponentDefinition(ComponentClass, manager);
  }

  resolveHelper(spec: Specifier): Helper {
    return this.owner.lookup(spec.module);
  }
}
<file_sep>/packages/@glimmer/compiler-delegates/index.ts
export { default as ModuleUnificationCompilerDelegate } from './src/delegates/module-unification';
export { BundleCompilerDelegate, Builtins } from './src/bundle';
export { OutputFiles } from './src/utils/code-gen';
<file_sep>/packages/@glimmer/compiler-delegates/src/bundle.ts
import { CompilerDelegate, SpecifierMap, Specifier } from '@glimmer/bundle-compiler';
import { ConstantPool } from '@glimmer/program';
import { SerializedTemplateBlock } from '@glimmer/wire-format';
import { ICompilableTemplate } from '@glimmer/opcode-compiler';
import { ProgramSymbolTable } from '@glimmer/interfaces';

export type Metadata = {};

export type AddedTemplate = SerializedTemplateBlock | ICompilableTemplate<ProgramSymbolTable>;

export interface BundleCompilerDelegate extends CompilerDelegate {
  normalizePath(absolutePath: string): string;
  specifierFor(relativePath: string): Specifier;
  generateDataSegment(map: SpecifierMap, pool: ConstantPool, heapTable: number[], nextFreeHandle: number, blocks: Map<Specifier, AddedTemplate>): string;
}

export interface Builtins {
  [key: string]: Specifier;
}

export type ModulePath = string;
export type Name = string;
export type Identifier = string;

export interface BuiltinsMap {
  byName: Map<Name, ModulePath>;
  byIdentifier: Map<Identifier, Specifier>;
  byModulePath: Map<ModulePath, Name>;
}
<file_sep>/packages/@glimmer/compiler-delegates/src/utils/code-gen.ts
import { Specifier } from "@glimmer/bundle-compiler";
import { dict, Dict } from "@glimmer/util";
import { Option } from "@glimmer/interfaces";
import { BuiltinsMap } from '../bundle';

export interface OutputFiles {
  dataSegment: Option<string>;
  heapFile: Option<string>;
}

/**
* Generates a valid JavaScript identifier for a module path. Can optionally take
* a dictionary of already-seen identifiers to avoid naming collisions.
*
* @param {string} modulePath the module path
* @param {object} seen an object containing already-seen identifiers as keys
* @returns {string} identifier a valid JavaScript identifier
*/
export function getIdentifier(modulePath: string, seen: Dict<boolean> = {}) {
  let identifier = modulePath
    // replace any non letter, non-number, non-underscore
    .replace(/[\W]/g, '_');

  // if we have already generated this identifier
  // prefix with an _ until we find a unique one
  while (identifier in seen) {
    identifier = `_${identifier}`;
  }

  seen[identifier] = true;

  return `__${identifier}__`;
}

/**
 * Generates the import clause of an import statement based on both the export
 * name and the desired local identifier.
 *
 *     getImportClause('default', 'MyClass') => 'MyClass'
 *     getImportClause('MyClass', 'MyClass') => '{ MyClass }'
 *     getImportClause('MyClass', 'GlimmerMyClass') => '{ MyClass as GlimmerMyClass }'
 */
export function getImportClause(name: string, id: string) {
  if (name === 'default') { return id; }
  if (name === 'id') { return `{ ${id} }`; }
  return `{ ${name} as ${id} }`;
}

export function getImportStatement(specifier: Specifier, id: string) {
  let { module, name } = specifier;

  let importClause = getImportClause(name, id);
  let moduleSpecifier = getModuleSpecifier(module);

    return `import ${importClause} from ${moduleSpecifier};`;
}

export function getModuleSpecifier(modulePath: string) {
  return JSON.stringify(`./${modulePath}`);
}

export function getImportStatements(modules: Specifier[], builtins: BuiltinsMap) {
  let identifiers = new Array<string>(modules.length).fill('');
  let seen = dict<boolean>();

  let imports = modules.map((specifier, i) => {
    let { module, name } = specifier;

    if (builtins.byModulePath.has(module)) {
      let id = `${name}_${i}`;
      identifiers[i] = id;
      return `import { ${name} as ${id} } from "${module}";`;
    }

    let id = getIdentifier(module, seen);
    identifiers[i] = id;

    return getImportStatement(specifier, id);
  });

  return { imports, identifiers };
}
<file_sep>/packages/@glimmer/application/test/browser/application-test.ts
import Application, { RuntimeCompilerLoader, SyncRenderer, DOMBuilder } from '@glimmer/application';
import { BlankResolver } from '@glimmer/test-utils';
import { Document } from 'simple-dom';

const { module, test } = QUnit;

module('[@glimmer/application] Application');

test('can be instantiated', function(assert) {
  let resolver = new BlankResolver();
  let app = new Application({
    rootName: 'app',
    loader: new RuntimeCompilerLoader(resolver),
    renderer: new SyncRenderer(),
    builder: new DOMBuilder({ element: document.body, nextSibling: null }),
    resolver
  });
  assert.ok(app, 'app exists');
});

test('accepts options for rootName, resolver and document', function(assert) {
  const resolver = new BlankResolver;
  let app = new Application({
    rootName: 'app',
    loader: new RuntimeCompilerLoader(resolver),
    renderer: new SyncRenderer(),
    builder: new DOMBuilder({ element: document.body, nextSibling: null }),
    resolver
  });
  assert.equal(app.rootName, 'app');
  assert.equal(app.resolver, resolver);
  assert.equal(app.document, window.document, 'defaults to window document if document is not provided in options');
  let customDocument = new Document();

  app = new Application({
    rootName: 'app',
    resolver,
    document: customDocument,
    loader: new RuntimeCompilerLoader(resolver),
    renderer: new SyncRenderer(),
    builder: new DOMBuilder({ element: document.body, nextSibling: null })
  });
  assert.equal(app.document, customDocument);
});
<file_sep>/packages/@glimmer/application/test/browser/render-component-test.ts
import { buildApp, didRender } from '@glimmer/application-test-helpers';
import { DEBUG } from '@glimmer/env';

const { module, test } = QUnit;

module('[@glimmer/application] renderComponent');

test('renders a component', async function(assert) {
  assert.expect(1);

  let containerElement = document.createElement('div');

  let app = await buildApp()
    .template('HelloWorld', `<h1>Hello Glimmer!</h1>`)
    .boot();

  app.renderComponent('HelloWorld', containerElement);

  await didRender(app);

  assert.equal(containerElement.innerHTML, '<h1>Hello Glimmer!</h1>');
});

test('renders a component without affecting existing content', async function(assert) {
  assert.expect(2);

  let containerElement = document.createElement('div');
  let previousSibling = document.createElement('p');

  previousSibling.appendChild(document.createTextNode('foo'));
  containerElement.appendChild(previousSibling);
  containerElement.appendChild(document.createTextNode('bar'));

  let app = await buildApp()
    .template('HelloWorld', `<h1>Hello Glimmer!</h1>`)
    .boot();

  assert.equal(containerElement.innerHTML, '<p>foo</p>bar');

  app.renderComponent('HelloWorld', containerElement);

  await didRender(app);

  assert.equal(containerElement.innerHTML, '<p>foo</p>bar<h1>Hello Glimmer!</h1>');
});

test('renders a component before a given sibling', async function(assert) {
  assert.expect(2);

  let containerElement = document.createElement('div');
  let previousSibling = document.createElement('p');
  let nextSibling = document.createElement('aside');

  containerElement.appendChild(previousSibling);
  containerElement.appendChild(nextSibling);

  let app = await buildApp()
    .template('HelloWorld', `<h1>Hello Glimmer!</h1>`)
    .boot();

  assert.equal(containerElement.innerHTML, '<p></p><aside></aside>');

  app.renderComponent('HelloWorld', containerElement, nextSibling);

  await didRender(app);

  assert.equal(containerElement.innerHTML, '<p></p><h1>Hello Glimmer!</h1><aside></aside>');
});

test('renders multiple components in different places', async function(assert) {
  assert.expect(2);

  let firstContainerElement = document.createElement('div');
  let secondContainerElement = document.createElement('div');

  let app = await buildApp()
    .template('HelloWorld', `<h1>Hello Glimmer!</h1>`)
    .template('HelloRobbie', `<h1>Hello Robbie!</h1>`)
    .boot();

  app.renderComponent('HelloWorld', firstContainerElement);
  app.renderComponent('HelloRobbie', secondContainerElement);

  await didRender(app);

  assert.equal(firstContainerElement.innerHTML, '<h1>Hello Glimmer!</h1>');
  assert.equal(secondContainerElement.innerHTML, '<h1>Hello Robbie!</h1>');
});

test('renders multiple components in the same container', async function(assert) {
  assert.expect(1);

  let containerElement = document.createElement('div');

  let app = await buildApp()
    .template('HelloWorld', `<h1>Hello Glimmer!</h1>`)
    .template('HelloRobbie', `<h1>Hello Robbie!</h1>`)
    .boot();

  app.renderComponent('HelloWorld', containerElement);
  app.renderComponent('HelloRobbie', containerElement);

  await didRender(app);

  assert.equal(containerElement.innerHTML, '<h1>Hello Glimmer!</h1><h1>Hello Robbie!</h1>');
});

test('renders multiple components in the same container in particular places', async function(assert) {
  assert.expect(2);

  let containerElement = document.createElement('div');
  let nextSibling = document.createElement('aside');

  containerElement.appendChild(nextSibling);

  let app = await buildApp()
    .template('HelloWorld', `<h1>Hello Glimmer!</h1>`)
    .template('HelloRobbie', `<h1>Hello Robbie!</h1>`)
    .boot();

  assert.equal(containerElement.innerHTML, '<aside></aside>');

  app.renderComponent('HelloWorld', containerElement);
  app.renderComponent('HelloRobbie', containerElement, nextSibling);

  await didRender(app);

  assert.equal(containerElement.innerHTML, '<h1>Hello Robbie!</h1><aside></aside><h1>Hello Glimmer!</h1>');
});

if (DEBUG) {
  test('throws an exception if an invoked component is not found', async function(assert) {
    assert.expect(1);

    let containerElement = document.createElement('div');

    let app = await buildApp()
      .template('HelloWorld', `<NonExistent />`)
      .boot();

    app.renderComponent('HelloWorld', containerElement);

    await rejects(assert, didRender(app), /Could not find the component 'NonExistent'/);
  });
}

async function rejects(assert: Assert, promise: Promise<any>, message: RegExp) {
  try {
    let value = await promise;
    assert.ok(false, `Expected promise to reject, got ${value}`);
  } catch (err) {
    assert.pushResult({
      result: err.message.match(message),
      actual: err.message,
      expected: message,
      message: 'Expected error message to match'
    });
  }
}
