From 13bcd5226e6a5463335e5afbf6dbc413baec8b55 Mon Sep 17 00:00:00 2001
From: John Bintz <john@coswellproductions.com>
Date: Fri, 22 Jul 2011 07:42:25 -0400
Subject: [PATCH] ensure a spec can't leave the page, causing a craaaazy loop

---
 ext/jasmine-webkit-specrunner/specrunner.cpp | 37 ++++++++++++++++++--
 jasmine/jasmine.headless-reporter.coffee     |  3 +-
 jasmine/jasmine.headless-reporter.js         |  6 +++-
 lib/jasmine/template_writer.rb               | 14 +++++++-
 spec/bin/jasmine-headless-webkit_spec.rb     |  9 +++++
 spec/jasmine/leave_page/leave_page.js        |  4 +++
 spec/jasmine/leave_page/leave_page.yml       |  9 +++++
 spec/jasmine/leave_page/leave_page_spec.js   | 11 ++++++
 8 files changed, 88 insertions(+), 5 deletions(-)
 create mode 100644 spec/jasmine/leave_page/leave_page.js
 create mode 100644 spec/jasmine/leave_page/leave_page.yml
 create mode 100644 spec/jasmine/leave_page/leave_page_spec.js

diff --git a/ext/jasmine-webkit-specrunner/specrunner.cpp b/ext/jasmine-webkit-specrunner/specrunner.cpp
index 1a52982..d6b0c3c 100644
--- a/ext/jasmine-webkit-specrunner/specrunner.cpp
+++ b/ext/jasmine-webkit-specrunner/specrunner.cpp
@@ -35,6 +35,9 @@
 class HeadlessSpecRunnerPage: public QWebPage
 {
   Q_OBJECT
+public:
+  HeadlessSpecRunnerPage();
+  void oneFalseConfirm();
 signals:
   void consoleLog(const QString &msg, int lineNumber, const QString &sourceID);
   void internalLog(const QString &note, const QString &msg);
@@ -42,8 +45,17 @@ protected:
   void javaScriptConsoleMessage(const QString & message, int lineNumber, const QString & sourceID);
   bool javaScriptConfirm(QWebFrame *frame, const QString &msg);
   void javaScriptAlert(QWebFrame *frame, const QString &msg);
+private:
+  bool confirmResult;
 };
 
+HeadlessSpecRunnerPage::HeadlessSpecRunnerPage()
+  : QWebPage()
+  , confirmResult(true)
+{
+
+}
+
 void HeadlessSpecRunnerPage::javaScriptConsoleMessage(const QString &message, int lineNumber, const QString &sourceID)
 {
   emit consoleLog(message, lineNumber, sourceID);
@@ -51,8 +63,13 @@ void HeadlessSpecRunnerPage::javaScriptConsoleMessage(const QString &message, in
 
 bool HeadlessSpecRunnerPage::javaScriptConfirm(QWebFrame *frame, const QString &msg)
 {
-  emit internalLog("TODO", "jasmine-headless-webkit can't handle confirm() yet! You should mock window.confirm for now. Returning true."); 
-  return true;
+  if (confirmResult) {
+    emit internalLog("TODO", "jasmine-headless-webkit can't handle confirm() yet! You should mock window.confirm for now. Returning true."); 
+    return true;
+  } else {
+    confirmResult = true;
+    return false;
+  }
 }
 
 void HeadlessSpecRunnerPage::javaScriptAlert(QWebFrame *frame, const QString &msg)
@@ -60,6 +77,11 @@ void HeadlessSpecRunnerPage::javaScriptAlert(QWebFrame *frame, const QString &ms
   emit internalLog("alert", msg);
 }
 
+void HeadlessSpecRunnerPage::oneFalseConfirm()
+{
+  confirmResult = false;
+}
+
 class HeadlessSpecRunner: public QObject
 {
     Q_OBJECT
@@ -71,6 +93,7 @@ public:
     void go();
 public slots:
     void log(const QString &msg);
+    void leavePageAttempt(const QString &msg);
     void specPassed();
     void specFailed(const QString &specDetail);
     void printName(const QString &name);
@@ -250,6 +273,16 @@ void HeadlessSpecRunner::log(const QString &msg)
   std::cout << std::endl;
 }
 
+void HeadlessSpecRunner::leavePageAttempt(const QString &msg)
+{
+  red();
+  std::cout << "[error] ";
+  clear();
+  std::cout << qPrintable(msg) << std::endl;
+  m_page.oneFalseConfirm();
+  hasErrors = true;
+}
+
 void HeadlessSpecRunner::printName(const QString &name)
 {
   std::cout << std::endl << std::endl;
diff --git a/jasmine/jasmine.headless-reporter.coffee b/jasmine/jasmine.headless-reporter.coffee
index 447c102..b759f64 100644
--- a/jasmine/jasmine.headless-reporter.coffee
+++ b/jasmine/jasmine.headless-reporter.coffee
@@ -50,7 +50,7 @@ jasmine.Spec.prototype.getSpecSplitName = ->
   parts
 
 class jasmine.HeadlessReporter
-  constructor: ->
+  constructor: (@callback = null) ->
     @results = []
     @failedCount = 0
     @length = 0
@@ -59,6 +59,7 @@ class jasmine.HeadlessReporter
       do (result) =>
         result.print()
 
+    this.callback() if @callback
     JHW.finishSuite((new Date() - @startTime) / 1000.0, @length, @failedCount)
   reportRunnerStarting: (runner) ->
     @startTime = new Date()
diff --git a/jasmine/jasmine.headless-reporter.js b/jasmine/jasmine.headless-reporter.js
index d9b4eb5..53f2be2 100644
--- a/jasmine/jasmine.headless-reporter.js
+++ b/jasmine/jasmine.headless-reporter.js
@@ -83,7 +83,8 @@
     return parts;
   };
   jasmine.HeadlessReporter = (function() {
-    function HeadlessReporter() {
+    function HeadlessReporter(callback) {
+      this.callback = callback != null ? callback : null;
       this.results = [];
       this.failedCount = 0;
       this.length = 0;
@@ -98,6 +99,9 @@
         result = _ref[_i];
         _fn(result);
       }
+      if (this.callback) {
+        this.callback();
+      }
       return JHW.finishSuite((new Date() - this.startTime) / 1000.0, this.length, this.failedCount);
     };
     HeadlessReporter.prototype.reportRunnerStarting = function(runner) {
diff --git a/lib/jasmine/template_writer.rb b/lib/jasmine/template_writer.rb
index 6a99ca4..762dd7e 100644
--- a/lib/jasmine/template_writer.rb
+++ b/lib/jasmine/template_writer.rb
@@ -31,6 +31,16 @@ module Jasmine
     }, pp: function(data) {
       JHW.log(jasmine ? jasmine.pp(data) : JSON.stringify(data));
     } };
+
+    window.onbeforeunload = function(e) {
+      JHW.leavePageAttempt('The code tried to leave the test page. Check for unhandled form submits and link clicks.');
+
+      if (e = e || window.event) {
+        e.returnValue = "leaving";
+      }
+
+      return "leaving";
+    };
   </script>
   #{files.join("\n")}
   <script type="text/javascript">
@@ -40,7 +50,9 @@ HeadlessReporterResult.specLineNumbers = #{MultiJson.encode(spec_lines)};
 <body>
 
 <script type="text/javascript">
-  jasmine.getEnv().addReporter(new jasmine.HeadlessReporter());
+  jasmine.getEnv().addReporter(new jasmine.HeadlessReporter(function() {
+    window.onbeforeunload = null;
+  }));
   jasmine.getEnv().execute();
 </script>
 
diff --git a/spec/bin/jasmine-headless-webkit_spec.rb b/spec/bin/jasmine-headless-webkit_spec.rb
index cee4707..c40590d 100644
--- a/spec/bin/jasmine-headless-webkit_spec.rb
+++ b/spec/bin/jasmine-headless-webkit_spec.rb
@@ -60,6 +60,15 @@ describe "jasmine-headless-webkit" do
     end
   end
 
+  describe 'tries to leave page' do
+    it "should not leave the page nor loop" do
+      system %{bin/jasmine-headless-webkit -j spec/jasmine/leave_page/leave_page.yml --report #{report}}
+      $?.exitstatus.should == 1
+
+      report.should be_a_report_containing(2, 0, false)
+    end
+  end
+
   describe 'with filtered run' do
     context "don't run a full run, just the filtered run" do
       it "should succeed and run both" do
diff --git a/spec/jasmine/leave_page/leave_page.js b/spec/jasmine/leave_page/leave_page.js
new file mode 100644
index 0000000..22f336b
--- /dev/null
+++ b/spec/jasmine/leave_page/leave_page.js
@@ -0,0 +1,4 @@
+function yes() {
+  document.location.href = 'http://www.google.com/'
+}
+
diff --git a/spec/jasmine/leave_page/leave_page.yml b/spec/jasmine/leave_page/leave_page.yml
new file mode 100644
index 0000000..f434433
--- /dev/null
+++ b/spec/jasmine/leave_page/leave_page.yml
@@ -0,0 +1,9 @@
+src_files:
+  - spec/jasmine/leave_page/leave_page.js
+
+spec_files:
+  - spec/jasmine/leave_page/leave_page_spec.js
+
+src_dir: .
+spec_dir: .
+
diff --git a/spec/jasmine/leave_page/leave_page_spec.js b/spec/jasmine/leave_page/leave_page_spec.js
new file mode 100644
index 0000000..6593c00
--- /dev/null
+++ b/spec/jasmine/leave_page/leave_page_spec.js
@@ -0,0 +1,11 @@
+describe("something", function() {
+  it("should be true", function() {
+    yes();
+    expect(true).toEqual(true);
+  });
+
+  it("should so something else", function() {
+    expect(true).toEqual(true);
+  });
+});
+