import { assign } from 'ember-utils';
import { run } from 'ember-metal';
import { jQuery } from 'ember-views';

import equalInnerHTML from '../equal-inner-html';
import equalTokens from '../equal-tokens';
import {
  equalsElement,
  regex,
  classes
} from '../matchers';

const TextNode = window.Text;
const HTMLElement = window.HTMLElement;
const Comment = window.Comment;

function isMarker(node) {
  if (node instanceof Comment && node.textContent === '') {
    return true;
  }

  if (node instanceof TextNode && node.textContent === '') {
    return true;
  }

  return false;
}

export default class AbstractTestCase {
  constructor() {
    this.element = null;
    this.snapshot = null;
    this.assert = QUnit.config.current.assert;

    let { fixture } = this;
    if (fixture) {
      this.setupFixture(fixture);
    }
  }

  teardown() {}
  afterEach() {}

  runTask(callback) {
    return run(callback);
  }

  runTaskNext(callback) {
    return run.next(callback);
  }

  setupFixture(innerHTML) {
    let fixture = document.getElementById('qunit-fixture');
    fixture.innerHTML = innerHTML;
  }

  // The following methods require `this.element` to work

  get firstChild() {
    return this.nthChild(0);
  }

  nthChild(n) {
    let i = 0;
    let node = this.element.firstChild;

    while (node) {
      if (!isMarker(node)) {
        i++;
      }

      if (i > n) {
        break;
      } else {
        node = node.nextSibling;
      }
    }

    return node;
  }

  get nodesCount() {
    let count = 0;
    let node = this.element.firstChild;

    while (node) {
      if (!isMarker(node)) {
        count++;
      }

      node = node.nextSibling;
    }

    return count;
  }

  $(sel) {
    return sel ? jQuery(sel, this.element) : jQuery(this.element);
  }

  click(selector) {
    let element;
    if (typeof selector === 'string') {
      element = this.element.querySelector(selector);
    } else {
      element = selector;
    }
    return element.click();
  }

  textValue() {
    return this.element.textContent;
  }

  takeSnapshot() {
    let snapshot = this.snapshot = [];

    let node = this.element.firstChild;

    while (node) {
      if (!isMarker(node)) {
        snapshot.push(node);
      }

      node = node.nextSibling;
    }

    return snapshot;
  }

  assertText(text) {
    this.assert.strictEqual(this.textValue(), text, `#qunit-fixture content should be: \`${text}\``);
  }

  assertInnerHTML(html) {
    equalInnerHTML(this.assert, this.element, html);
  }

  assertHTML(html) {
    equalTokens(this.element, html, `#qunit-fixture content should be: \`${html}\``);
  }

  assertElement(node, { ElementType = HTMLElement, tagName, attrs = null, content = null }) {
    if (!(node instanceof ElementType)) {
      throw new Error(`Expecting a ${ElementType.name}, but got ${node}`);
    }

    equalsElement(this.assert, node, tagName, attrs, content);
  }

  assertComponentElement(node, { ElementType = HTMLElement, tagName = 'div', attrs = null, content = null }) {
    attrs = assign({}, { id: regex(/^ember\d*$/), class: classes('ember-view') }, attrs || {});
    this.assertElement(node, { ElementType, tagName, attrs, content });
  }

  assertSameNode(actual, expected) {
    this.assert.strictEqual(actual, expected, 'DOM node stability');
  }

  assertInvariants(oldSnapshot, newSnapshot) {
    oldSnapshot = oldSnapshot || this.snapshot;
    newSnapshot = newSnapshot || this.takeSnapshot();

    this.assert.strictEqual(newSnapshot.length, oldSnapshot.length, 'Same number of nodes');

    for (let i = 0; i < oldSnapshot.length; i++) {
      this.assertSameNode(newSnapshot[i], oldSnapshot[i]);
    }
  }

  assertPartialInvariants(start, end) {
    this.assertInvariants(this.snapshot, this.takeSnapshot().slice(start, end));
  }

  assertStableRerender() {
    this.takeSnapshot();
    this.runTask(() => this.rerender());
    this.assertInvariants();
  }
}

@@ -264,16 +264,48 @@
 tchers';
+%0Aimport %7B Promise %7D from 'rsvp';
 %0A%0Aconst 
@@ -1991,33 +1991,593 @@
   %7D%0A
-    return element.click(
+%0A    let event = element.click();%0A%0A    return this.runLoopSettled(event);%0A  %7D%0A%0A  // TODO: Find a better name %F0%9F%98%8E%0A  runLoopSettled(value) %7B%0A    return new Promise(function(resolve) %7B%0A      // Every 5ms, poll for the async thing to have finished%0A      let watcher = setInterval(() =%3E %7B%0A        // If there are scheduled timers or we are inside of a run loop, keep polling%0A        if (run.hasScheduledTimers() %7C%7C run.currentRunLoop) %7B return; %7D%0A%0A        // Stop polling%0A        clearInterval(watcher);%0A%0A        // Synchronously resolve the promise%0A        resolve(value);%0A      %7D, 5);%0A    %7D
 );%0A 

Add `runLoopSettled` helper function to abstract test case.