Skip to content
Browse files

Run tests in an iframe, with <body id="konacha">

This change is mostly motivated by usability. When you're testing a JS
MVC application, having an iframe makes it much easier to visually
inspect the state of the application, and select specific elements to
look at in the DOM inspector.

I'm hoping that for my team mates that are getting started with the MVC
framework and Konacha, being able to *see* what's going will lessen the
pain of the initial learning curve.

Performance

There seems to be a very slight performance degradation (5-10%) from
painting everything. I feel OK incurring that though, as the iframe
makes Konacha so much more usable.

The delay added by the initial top-level frame is about 20ms on my
system.

Layout

At the moment, if you have a smaller window, it responsively scales the
iframe and the report, but it's all a bit squished for my taste. We have
to leave ample space so error messages don't run behind the iframe. But
perhaps a better way would be to leave a little less space, and add a
"toggle iframe" button to hide it in case an error message runs too
long.

This is perhaps not something that prevents us from merging this, but
I'd like to improve it sooner or later.
  • Loading branch information...
1 parent 546ced2 commit e94d046065f0ea8abf650fdd91770e0defd34eed @joliss joliss committed Aug 25, 2012
View
2 History.md
@@ -1,5 +1,7 @@
# master
+* Run tests in an iframe, with `<body id="konacha">`
+
# 1.5.0
* Update mocha (1.4.0+) and chai (1.2.0)
View
53 README.md
@@ -19,6 +19,12 @@ the asset pipeline and engines.
Photo credit: [FCartegnie](http://commons.wikimedia.org/wiki/File:Konacha.jpg), CC-BY-SA.
+## Upgrading from Konacha 1.x
+
+As of Konacha 2.0, your tests are run inside an iframe. See the section
+"[Using the DOM](#using-the-dom)" for details. You may be able to upgrade
+without any changes to your test code.
+
## Installation
Add konacha to the `:test` and `:development` groups in the Gemfile and `bundle install`:
@@ -143,17 +149,20 @@ Konacha can be configured in an initializer, e.g. `config/initializers/konacha.r
```ruby
Konacha.configure do |config|
- config.spec_dir = "spec/javascripts"
- config.driver = :selenium
+ config.spec_dir = "spec/javascripts"
+ config.driver = :selenium
+ config.stylesheets = %w(application)
end if defined?(Konacha)
```
The `defined?` check is necessary to avoid a dependency on Konacha in the production
environment.
-The `spec_dir` option tells Konacha where to find JavaScript specs. `driver` names a
-Capybara driver used for the `run` task (try `:webkit`, after installing
-[capybara-webkit](https://github.com/thoughtbot/capybara-webkit)).
+The `spec_dir` option tells Konacha where to find JavaScript specs. `driver`
+names a Capybara driver used for the `run` task (try `:webkit`, after
+installing [capybara-webkit](https://github.com/thoughtbot/capybara-webkit)).
+The `stylesheets` option sets the stylesheets to be linked from the `<head>`
+of the test runner iframe.
The values above are the defaults.
@@ -194,30 +203,30 @@ If you use jQuery, you may want to check out [chai-jquery](https://github.com/jf
for some jQuery-specific assertions. You can add it painlessly with the
[chai-jquery-rails](https://github.com/wordofchristian/chai-jquery-rails) gem.
-## Transactions
+## Using the DOM
-One problem often faced when writing unit tests for client side code is that changes
-to the page are not reverted for the next example, so that successive examples become
-dependent on each other. Konacha adds a special div to your page with an id of `konacha`.
-This div is automatically emptied before each example. You should avoid appending markup
-to the page body and instead append it to the `#konacha` div:
+Your tests are run inside an iframe. You have the entire `<body>` element to
+yourself. The body is automatically reset between tests.
-```coffeescript
-describe "transactions", ->
- it "should add stuff in one test...", ->
- $('#konacha').append('<h1 id="added">New Stuff</h1>')
- $('#konacha h1#added').length.should.equal(1)
+For compatibility with Konacha 1.x, the `<body>` element will have
+`id="konacha"` set on it.
- it "... should have been removed before the next starts", ->
- $('#konacha h1#added').length.should.equal(0)
-```
+## Debugging
+
+To debug tests, use the `debugger` statement anywhere in a test to halt
+execution.
+
+To run code in the JavaScript console, be sure to select the `#test-context`
+frame first, so your code runs in the correct context.
+
+![Selecting the test-context frame in Chrome](https://raw.github.com/jfirebaugh/konacha/master/images/frame-select.png)
## Templates / Fixtures
Konacha has no template (a.k.a. HTML fixture) support of its own. Instead, we suggest you use
Sprocket's built in support for JavaScript template (`.jst`) files. Add a `spec/javascripts/templates`
directory, place template files there (using any JS template language supported by Sprockets),
-require them in your spec or spec_helper, and render them into the `#konacha` div.
+require them in your spec or spec_helper, and render them into the `<body>`.
For example, in `spec/javascripts/templates/hello.jst.ejs`:
@@ -238,8 +247,8 @@ And your spec:
describe("templating", function() {
it("is built in to Sprockets", function() {
- $('#konacha').html(JST['templates/hello']());
- $('#konacha h1').text().should.equal('Hello Konacha!');
+ $('body').html(JST['templates/hello']());
+ $('body h1').text().should.equal('Hello Konacha!');
});
});
```
View
5 app/controllers/konacha/specs_controller.rb
@@ -4,8 +4,13 @@ class SpecsController < ActionController::Base
render :text => "Not found", :status => 404
end
+ def reporter
+ @iframe_path = runner_path + request.fullpath
+ end
+
def specs
@specs = Konacha::Spec.find(params[:path] || "")
+ @stylesheets = Konacha::Engine.config.konacha.stylesheets
end
end
end
View
2 app/models/konacha/spec.rb
@@ -22,7 +22,7 @@ def initialize(path)
end
def url
- "/#{asset_name}"
+ "/runner/#{asset_name}"
end
def asset_name
View
12 app/views/konacha/specs/reporter.html.erb
@@ -0,0 +1,12 @@
+<!doctype html>
+<html>
+ <head>
+ <meta http-equiv="content-type" content="text/html;charset=utf-8" />
+ <title>Konacha Tests</title>
+ <%= stylesheet_link_tag "konacha", :debug => false %>
+ </head>
+ <body>
+ <%= content_tag :iframe, '', :src => @iframe_path, :id => 'test-context' %>
+ <div id="mocha"></div>
+ </body>
+</html>
View
10 app/views/konacha/specs/specs.html.erb
@@ -3,14 +3,18 @@
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
<title>Konacha Tests</title>
- <%= stylesheet_link_tag "mocha", :debug => false %>
- <%= stylesheet_link_tag "konacha", :debug => false %>
+
+ <% @stylesheets.each do |file| %>
+ <%# Use :debug => false for stylesheets to improve page load performance. %>
+ <%= stylesheet_link_tag file, :debug => false %>
+ <% end %>
+
<%= javascript_include_tag "mocha", "chai", "konacha/#{Konacha.mode}", :debug => false %>
<%= javascript_include_tag("konacha_config", :debug => false) if Rails.application.assets.find_asset('konacha_config') %>
<%= javascript_include_tag "konacha", :debug => false %>
+
<%= spec_include_tag *@specs %>
</head>
<body>
- <div id="mocha"></div>
</body>
</html>
View
6 config/routes.rb
@@ -1,4 +1,6 @@
Konacha::Engine.routes.draw do
- match "/" => "specs#specs"
- match "*path" => "specs#specs"
+ match '/runner' => 'specs#specs', :as => 'runner'
+ match '/runner/*path' => 'specs#specs'
+ match '/' => 'specs#reporter', :as => 'reporter'
+ match '*path' => 'specs#reporter'
end
View
BIN images/frame-select.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
9 lib/assets/javascripts/konacha.js
@@ -1,14 +1,7 @@
mocha.setup(Konacha.mochaOptions);
mocha.suite.beforeEach(function () {
- var e = document.getElementById('konacha');
- if (e) {
- e.parentNode.removeChild(e);
- }
-
- e = document.createElement("div");
- e.id = "konacha";
- document.body.appendChild(e);
+ Konacha.reset();
});
var expect = chai.expect,
View
6 lib/assets/javascripts/konacha/common.js
@@ -0,0 +1,6 @@
+window.Konacha = {
+ reset: function() {
+ document.body = document.createElement('body');
+ document.body.id = 'konacha';
+ }
+};
View
78 lib/assets/javascripts/konacha/runner.js
@@ -1,50 +1,50 @@
-window.Konacha = {
- dots:"",
+//= require konacha/common
- mochaOptions: {
- ui: 'bdd',
+Konacha.dots = "";
- reporter: function (runner) {
- window.mocha.reporters.Base.call(this, runner);
+Konacha.mochaOptions = {
+ ui: 'bdd',
- runner.on('start', function () {
- Konacha.results = [];
- });
+ reporter: function(runner) {
+ window.mocha.reporters.Base.call(this, runner);
- runner.on('pass', function (test) {
- Konacha.dots += ".";
- Konacha.results.push({
- name:test.title,
- passed:true
- });
- });
+ runner.on('start', function() {
+ Konacha.results = [];
+ });
- runner.on('fail', function (test) {
- Konacha.dots += "F";
- Konacha.results.push({
- name:test.title,
- passed:false,
- message:test.err.message,
- trace:test.err.stack
- });
+ runner.on('pass', function(test) {
+ Konacha.dots += ".";
+ Konacha.results.push({
+ name:test.title,
+ passed:true
});
-
- runner.on('pending', function (test) {
- Konacha.dots += "P";
- Konacha.results.push({
- name:test.title,
- passed:false,
- pending:true
- });
+ });
+
+ runner.on('fail', function(test) {
+ Konacha.dots += "F";
+ Konacha.results.push({
+ name:test.title,
+ passed:false,
+ message:test.err.message,
+ trace:test.err.stack
});
-
- runner.on('end', function () {
- Konacha.done = true;
+ });
+
+ runner.on('pending', function(test) {
+ Konacha.dots += "P";
+ Konacha.results.push({
+ name:test.title,
+ passed:false,
+ pending:true
});
- }
- },
+ });
- getResults:function () {
- return JSON.stringify(Konacha.results);
+ runner.on('end', function() {
+ Konacha.done = true;
+ });
}
};
+
+Konacha.getResults = function() {
+ return JSON.stringify(Konacha.results);
+};
View
13 lib/assets/javascripts/konacha/server.js
@@ -1,6 +1,11 @@
-window.Konacha = {
- mochaOptions: {
- ui: 'bdd',
- reporter: mocha.reporters.HTML
+//= require konacha/common
+
+Konacha.mochaOptions = {
+ ui: 'bdd',
+
+ reporter: function(runner) {
+ var reporterRoot = parent.document.getElementById('mocha');
+
+ return mocha.reporters.HTML(runner, reporterRoot);
}
};
View
93 lib/assets/stylesheets/konacha.css
@@ -1,5 +1,90 @@
-/* Keep #konacha div visible so we can test against element visibility */
-#konacha {
- height: 0 ! important;
- overflow: hidden ! important;
+/*
+ *= require mocha
+ */
+
+body {
+ padding: 60px 0;
+}
+
+#report {
+ -webkit-transform-origin: left top;
+ -moz-transform-origin: left top;
+ -ms-transform-origin: left top;
+ -o-transform-origin: left top;
+ transform-origin: left top;
+}
+
+#test-context {
+ -webkit-transform-origin: right top;
+ -moz-transform-origin: right top;
+ -ms-transform-origin: right top;
+ -o-transform-origin: right top;
+ transform-origin: right top;
+
+ width: 1000px;
+ height: 700px;
+
+ position: fixed;
+ top: 80px;
+ right: 25px;
+
+ /* inset */
+ border-top: 2px solid #c8c8c8;
+ border-bottom: 2px solid #eee;
+ border-left: 2px solid #ddd;
+ border-right: 2px solid #ddd;
+
+ /* opaque */
+ z-index: 1;
+ background-color: white;
+}
+
+@media (max-width: 1860px) {
+ #test-context {
+ -webkit-transform: scale(0.6666666666);
+ -moz-transform: scale(0.6666666666);
+ -ms-transform: scale(0.6666666666);
+ -o-transform: scale(0.6666666666);
+ transform: scale(0.6666666666);
+ }
+}
+
+@media (max-width: 1550px) {
+ #test-context {
+ -webkit-transform: scale(0.5);
+ -moz-transform: scale(0.5);
+ -ms-transform: scale(0.5);
+ -o-transform: scale(0.5);
+ transform: scale(0.5);
+ }
+}
+
+@media (max-width: 1380px) {
+ #report {
+ -webkit-transform: scale(0.8);
+ -moz-transform: scale(0.8);
+ -ms-transform: scale(0.8);
+ -o-transform: scale(0.8);
+ transform: scale(0.8);
+ }
+}
+
+@media (max-width: 1230px) {
+ #test-context {
+ -webkit-transform: scale(0.3333333333);
+ -moz-transform: scale(0.3333333333);
+ -ms-transform: scale(0.3333333333);
+ -o-transform: scale(0.3333333333);
+ transform: scale(0.3333333333);
+ }
+}
+
+@media (max-width: 1050px) {
+ #test-context {
+ -webkit-transform: scale(0.2);
+ -moz-transform: scale(0.2);
+ -ms-transform: scale(0.2);
+ -o-transform: scale(0.2);
+ transform: scale(0.2);
+ }
}
View
1 lib/konacha/engine.rb
@@ -31,6 +31,7 @@ def self.application(app)
options.port ||= 3500
options.application ||= self.class.application(app)
options.driver ||= :selenium
+ options.stylesheets ||= %w(application)
app.config.assets.paths << app.root.join(options.spec_dir).to_s
end
View
5 ...c/javascripts/test_element_spec.js.coffee → ...ummy/spec/javascripts/body_spec.js.coffee
@@ -1,6 +1,9 @@
#= require jquery
-describe "the #konacha element", ->
+describe "the body#konacha element", ->
+ it "is empty", ->
+ $('#konacha').html().should.equal ''
+
it "can have content added in one test...", ->
$('#konacha').append('<h1 id="added">New Stuff</h1>')
$('#konacha h1#added').length.should.equal(1)
View
8 spec/dummy/spec/javascripts/templating_spec.js
@@ -1,9 +1,9 @@
//= require spec_helper
//= require jquery
-describe("templating", function(){
- it("is built in to Sprockets", function(){
- $('#konacha').html(JST['templates/hello']());
- $('#konacha h1').text().should.equal('Hello Konacha!');
+describe("templating", function() {
+ it("is built in to Sprockets", function() {
+ $('body').html(JST['templates/hello']());
+ $('body h1').text().should.equal('Hello Konacha!');
});
});
View
2 spec/models/spec_spec.rb
@@ -18,7 +18,7 @@
describe "#url" do
it "returns a URL path" do
- described_class.new("array_spec.js").url.should == "/array_spec"
+ described_class.new("array_spec.js").url.should include "array_spec"
end
end
View
5 spec/spec_helper.rb
@@ -32,3 +32,8 @@ def app
RSpec.configure do |config|
config.include Konacha::RequestSpec, :type => :request
end
+
+Konacha.configure do |config|
+ # We don't have an application.css in our dummy app.
+ config.stylesheets = []
+end
View
13 spec/views/specs/specs.html.erb_spec.rb
@@ -1,6 +1,10 @@
require 'spec_helper'
describe "konacha/specs/specs" do
+ before do
+ assign(:stylesheets, [])
+ end
+
it "includes konacha JS for given mode" do
assign(:specs, [])
Konacha.should_receive(:mode).any_number_of_times { :runner }
@@ -64,4 +68,13 @@ def spec_double(asset_name, dependencies = [])
rendered.should have_selector("script[src='/assets/dependency_a.js?body=1']", :count => 1)
rendered.should have_selector("script[src='/assets/dependency_b.js?body=1']", :count => 1)
end
+
+ it "render the stylesheets" do
+ assign(:stylesheets, %w(foo bar))
+
+ render
+
+ rendered.should have_selector("link[href='/assets/foo.css']")
+ rendered.should have_selector("link[href='/assets/bar.css']")
+ end
end

0 comments on commit e94d046

Please sign in to comment.
Something went wrong with that request. Please try again.