Skip to content

Instantly share code, notes, and snippets.

@emk
Created April 7, 2013 01:44

Revisions

  1. emk created this gist Apr 7, 2013.
    41 changes: 41 additions & 0 deletions ember-test.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,41 @@
    <!-- Mocha test output goes here. -->
    <div id="mocha"></div>

    <!-- Handlebars templates for our application. -->
    <script type="text/x-handlebars">
    <h1>Testing Ember.js with Mocha</h1>
    {{outlet}}
    </script>

    <script type="text/x-handlebars" data-template-name="index">
    <p>{{#linkTo 'employees'}}Show employees{{/linkTo}}</p>
    </script>

    <script type="text/x-handlebars" data-template-name="employees">
    <h2>Employees</h2>
    <ul>
    {{#each employee in controller}}
    <li>{{#linkTo 'employee' employee}}{{employee.name}}{{/linkTo}}</li>
    {{/each}}
    </ul>
    </script>

    <script type="text/x-handlebars" data-template-name="employee">
    <h2>{{name}}</h2>
    <p>Salary: <span class="salary">${{salary}}</span></p>
    <button {{action 'giveRaise'}}>Give Raise</button>
    {{#if managedBy}}
    <p class="managed-by">
    Managed by: {{#linkTo 'employee' managedBy}}{{managedBy.name}}{{/linkTo}}
    </p>
    {{/if}}
    {{#if manages}}
    <h3>Manages</h3>
    <ul class="manages">
    {{#each employee in manages}}
    <li>{{#linkTo 'employee' employee}}{{employee.name}}{{/linkTo}}</li>
    {{/each}}
    </ul>
    {{/if}}
    <p>{{#linkTo 'employees'}}All employees{{/linkTo}}</p>
    </script>
    207 changes: 207 additions & 0 deletions ember-test.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,207 @@
    //================================================================================
    // Application Code

    window.App = Ember.Application.create();

    // Our normal data store, which we be overridden when testing.
    App.Store = DS.Store.extend({
    revision: 12,
    adapter: DS.RESTAdapter.create()
    });

    App.Employee = DS.Model.extend({
    name: DS.attr("string"),
    salary: DS.attr("number"),
    managedBy: DS.belongsTo("App.Employee"),
    manages: DS.hasMany("App.Employee")
    });

    App.Router.map(function () {
    this.route("index", { path: "/" });
    this.route("employees", { path: "/employees" });
    this.route("employee", { path: "/employee/:employee_id" });
    });

    App.EmployeesRoute = Ember.Route.extend({
    model: function (params) {
    return App.Employee.find();
    }
    });

    App.EmployeeRoute = Ember.Route.extend({
    model: function (params) {
    return App.Employee.find(params.employee_id);
    }
    });

    App.EmployeesController = Ember.ArrayController.extend();

    App.EmployeeController = Ember.ObjectController.extend({
    giveRaise: function () {
    this.set("salary", this.get("salary") * 1.10);
    }
    });

    // Declare this explicitly so we can test it.
    App.EmployeeView = Ember.View.extend({
    // Only needed so we can be called outside of a route by our unit tests.
    templateName: 'employee'
    });

    //================================================================================
    // Test Code

    // Configure Mocha, telling both it and chai to use BDD-style tests.
    mocha.setup("bdd");
    chai.should();

    // Replace our fixture-based store with a REST-based store for testing, so we
    // don't need a server. We disable simulateRemoteResponse so that objects will
    // appear to load at the end of every Ember.run block instead of waiting for a
    // timer to fire.
    App.Store = DS.Store.extend({
    revision: 12,
    adapter: DS.FixtureAdapter.create({ simulateRemoteResponse: false })
    });

    // Declare some fixture objects to use in our test application. There's
    // nothing like factory_girl or machinist yet.
    App.Employee.FIXTURES = [{
    id: 1,
    name: "Jane Q. Public",
    salary: 80000,
    managedBy: null,
    manages: [2]
    }, {
    id: 2,
    name: "John Q. Public",
    salary: 60000,
    managedBy: 1,
    manages: []
    }];

    mocha.setup('bdd');

    // Run before each test case.
    beforeEach(function () {
    // Put the application into a known state, and destroy the defaultStore.
    // Be careful about DS.Model instances stored in App; they'll be invalid
    // after this.
    // Currently broken, see: https://github.com/emberjs/data/issues/847
    //App.reset();
    // Display an error if asynchronous operations are queued outside of
    // Ember.run. You need this if you want to stay sane.
    Ember.testing = true;
    });

    // Run after each test case.
    afterEach(function () {
    Ember.testing = false;
    });

    // Load associations immediately, instead of waiting for FixtureAdapter's
    // asynchronous loads. Basically, all we need to do is access each object
    // from inside Ember.run.
    // TODO: We can't test this or insert where needed until App.reset() works.
    // TODO: Handle hasMany.
    function loadAssociations(object /*, paths... */) {
    var paths = Array.prototype.slice.call(arguments, 1);
    for (var i = 0; i < paths.length; i++) {
    var components = paths[i].split(".");
    for (var j = 0; j < components.length; j++) {
    Ember.run(function () {
    var path = components.slice(0, j+1).join(".");
    object.get(path);
    });
    }
    }
    }

    // Sample model test.
    describe("App.Employee", function () {
    it("has a name", function () {
    var jane;
    Ember.run(function () {
    // Won't actually load until the end of the run-block.
    jane = App.Employee.find(1);
    });
    jane.get("name").should.equal("Jane Q. Public");
    });
    });

    // Sample controller test.
    describe("App.EmployeeController", function () {
    var model, controller;

    beforeEach(function () {
    Ember.run(function () {
    model = App.Employee.createRecord({ salary: 100000 });
    controller = App.EmployeeController.create({ content: model });
    });
    });

    it("can give the employee a raise", function () {
    var oldSalary = model.get("salary");
    Ember.run(function () {
    controller.giveRaise();
    });
    model.get("salary").should.be(oldSalary * 1.1);
    });
    });

    // Sample view test.
    describe("App.EmployeeView", function () {
    var controller, view;

    beforeEach(function () {
    Ember.run(function () {
    var model = App.Employee.find(1);
    controller = App.EmployeeController.create({
    // We need a container to test views with linkTo.
    container: App.__container__,
    content: model
    });
    // If for some reason we want to isolate this, we can use
    // a sinon stub to intercept certain calls.
    sinon.stub(controller, "giveRaise");
    view = App.EmployeeView.create({
    controller: controller,
    context: controller
    });
    view.append(); // Hook up to our document.
    });
    });

    afterEach(function () {
    Ember.run(function () {
    view.remove(); // Unhook from our document.
    });
    });

    it("shows the employee's name", function () {
    // This uses a chai-jquery assertion.
    view.$("h2").should.have.text("Jane Q. Public");
    view.$(".manages li").text().should.match(/John/);
    });

    it("has a button which gives the employee a raise", function () {
    view.$("button").click();
    // We use a sinon-chai method here.
    controller.giveRaise.should.have.been.calledOnce;
    });
    });

    // Sample acceptance test.
    describe("Employee features", function () {
    it("give John's boss a raise", function () {
    $("a:contains('Show employees')").click();
    $("a:contains('John')").click();
    $(".managed-by a").click();
    $(".salary").should.have.text("$80000");
    $("button:contains('Give Raise')").click();
    $(".salary").should.have.text("$88000");
    });
    });

    // Run all our test suites. Only necessary in the browser.
    mocha.run();