Merge branch 'master', remote-tracking branch 'origin/master'
This commit is contained in:
commit
dde8c0d89b
109
README.md
109
README.md
|
@ -2,6 +2,37 @@
|
||||||
|
|
||||||
## What's this?
|
## What's this?
|
||||||
|
|
||||||
|
Something like this:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
describe("pure awesomeness", function() {
|
||||||
|
it("should be amazing!", function() {
|
||||||
|
expect(stuff).toEqual("amazing");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be awesome", function() {
|
||||||
|
expect(moreStuff).toBe("awesome");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("coolness", function() {
|
||||||
|
it("should be cooler than ice when freezed", function() {
|
||||||
|
var coolness = CoolingRepository.beCool();
|
||||||
|
coolness.freeze();
|
||||||
|
expect(coolness.coolnessRatio).toBe(-100);
|
||||||
|
});
|
||||||
|
it("should be cool enough by default", function() {
|
||||||
|
expect(CoolingRepository.beCool().coolnessRatio).toBe(-5);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Being translated into something like this:
|
||||||
|
|
||||||
|
![Junit Eclipse runner](http://i54.tinypic.com/rswjrl.jpg)
|
||||||
|
|
||||||
|
* * *
|
||||||
|
|
||||||
Quite simple, it's a custsom Java Junit Runner that allows you to embed Javascript Unit tests (using Jasmine) in your Java-based projects. It fully integrates with your most beloved IDE, your most hated version control system and of course your most needed CI env.
|
Quite simple, it's a custsom Java Junit Runner that allows you to embed Javascript Unit tests (using Jasmine) in your Java-based projects. It fully integrates with your most beloved IDE, your most hated version control system and of course your most needed CI env.
|
||||||
|
|
||||||
So let's rephrase:
|
So let's rephrase:
|
||||||
|
@ -151,81 +182,3 @@ When the debug mode flag has been set to _true_, you can use the <a href="http:/
|
||||||
After pressing "GO", the tests will run and you can inspect stuff and step through the code.
|
After pressing "GO", the tests will run and you can inspect stuff and step through the code.
|
||||||
|
|
||||||
Integrated debugging into for example Eclipse does not work for the moment.
|
Integrated debugging into for example Eclipse does not work for the moment.
|
||||||
|
|
||||||
* * *
|
|
||||||
|
|
||||||
# Advanced: Implementation details
|
|
||||||
|
|
||||||
## RhinoContext API
|
|
||||||
|
|
||||||
The _RhinoContext_ class is basically a wrapper/facade/whatever which allows you to easily manipulate the Javascript scope.
|
|
||||||
Read <a href="https://developer.mozilla.org/En/Rhino_documentation/Scopes_and_Contexts" target="_blank">Rhino docs: scopes and contexts</a> first please!
|
|
||||||
|
|
||||||
Creating a new RhinoContext initializes one "root" scope (toplevel), and assignes one context to the current Thread.
|
|
||||||
|
|
||||||
### Evaluating async javascript code
|
|
||||||
|
|
||||||
Creating another RhinoContext while passing the root scope, uses prototypal inheritance to create a new toplevel scope. This means the root scope is shared across different contexts (and thus different threads).
|
|
||||||
You can execute the _runAsync_ method, which does this:
|
|
||||||
|
|
||||||
* create a new thread and thus a new context
|
|
||||||
* create a new scope based on the root one -> shared
|
|
||||||
* execute stuff in the new scope (You can access root JS functions but not modify them, remember prototypal inheritance!)
|
|
||||||
* cleanup
|
|
||||||
|
|
||||||
For example, JasmineSpec uses the _execute_ Jasmine JS function on a spec and calls it in another thread:
|
|
||||||
|
|
||||||
```java
|
|
||||||
baseContext.runAsync(new RhinoRunnable() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run(RhinoContext context) {
|
|
||||||
// get some random spec from Jasmine
|
|
||||||
NativeObject someSpec = (NativeObject) context.evalJS("jasmine.getEnv().currentRunner().suites()[0].specs()[0]");
|
|
||||||
context.executeFunction(someSpec, "execute");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Creating a Rhino debugger
|
|
||||||
|
|
||||||
Basically creates a _org.mozilla.javascript.tools.debugger.Main_ object. Pitfall: create before loading all required JS files, but after creating the rhino context!
|
|
||||||
To acutally break once (so users can set breakpoints and press GO), use this:
|
|
||||||
|
|
||||||
> debugger.doBreak();
|
|
||||||
|
|
||||||
### Executing functions
|
|
||||||
|
|
||||||
_executeFunction_ is a convenience method to call a function on a passed NativeObject. The function pointer may reside in the object's prototype, you don't need to explicitly check this in Javascript but you do using Rhino!
|
|
||||||
|
|
||||||
## Envjs Utils/Hacks
|
|
||||||
|
|
||||||
### Error.stack fix
|
|
||||||
|
|
||||||
In firefox, you can get a stacktrace from a JS exception using:
|
|
||||||
|
|
||||||
> new Error("BOOM").stack
|
|
||||||
|
|
||||||
Of course this does not work in Envjs. But Rhino attaches an internal _rhinoException_ to each JS Error object, so using a bit of magic, now it's possible to call _getStackTrace()_
|
|
||||||
|
|
||||||
### Envjs.uri Windows relative paths fix
|
|
||||||
|
|
||||||
Use _file:///_ (three forward slashes) if no context has been provided. Works like this:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Envjs.uri(path, "file:///" + ("" + Envjs.getcwd()).replace(/\\/g, '/') + "/")
|
|
||||||
```
|
|
||||||
|
|
||||||
### window.setTimeout fix
|
|
||||||
|
|
||||||
Used by Jasmine internally for async spec execution, but for some reason the Envjs Javascript implementation is broken.
|
|
||||||
A simple fix is possible, since using Rhino you can call Java objects in Javascript space! Wow awesome. So just create a new thread and use _sleep_:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
window.setTimeout = function(closure, timeout) {
|
|
||||||
spawn(function() {
|
|
||||||
java.lang.Thread.sleep(timeout);
|
|
||||||
closure();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
load("./../src/test/javascript/lib/env.rhino.1.2.js");
|
||||||
|
load("./../src/test/javascript/lib/env.utils.js");
|
||||||
|
load("./../src/test/javascript/envJsOptions.js");
|
||||||
|
|
||||||
|
load("jquery-1.6.1.js");
|
||||||
|
|
||||||
|
function append(itm) {
|
||||||
|
$('body').append($(itm));
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
@ -0,0 +1,7 @@
|
||||||
|
@echo off
|
||||||
|
REM -opt -1 is needed for Envjs (interpretation, not compilation mode)
|
||||||
|
|
||||||
|
REM Rhino JAR options: See https://developer.mozilla.org/en/Rhino_Shell
|
||||||
|
REM EnvJS usage: See http://www.envjs.com/doc/guides#running-rhino
|
||||||
|
|
||||||
|
java -cp js.jar org.mozilla.javascript.tools.shell.Main -opt -1
|
|
@ -0,0 +1,2 @@
|
||||||
|
@echo off
|
||||||
|
java -cp js.jar org.mozilla.javascript.tools.debugger.Main -opt -1
|
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
|
@ -10,7 +10,7 @@ import org.mozilla.javascript.NativeObject;
|
||||||
|
|
||||||
import be.klak.rhino.RhinoContext;
|
import be.klak.rhino.RhinoContext;
|
||||||
|
|
||||||
public class JasmineJSSuiteConverter {
|
class JasmineJSSuiteConverter {
|
||||||
|
|
||||||
private final RhinoContext context;
|
private final RhinoContext context;
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ public class JasmineJSSuiteConverter {
|
||||||
specs.addAll(convertToJunitDescription(suite, suiteDescription));
|
specs.addAll(convertToJunitDescription(suite, suiteDescription));
|
||||||
|
|
||||||
NativeArray subSuites = (NativeArray) context.executeFunction(suite, "suites");
|
NativeArray subSuites = (NativeArray) context.executeFunction(suite, "suites");
|
||||||
convertSuiteArrayToDescriptions(subSuites, suiteDescription, processed);
|
specs.addAll(convertSuiteArrayToDescriptions(subSuites, suiteDescription, processed));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ package be.klak.junit.jasmine;
|
||||||
import org.mozilla.javascript.NativeObject;
|
import org.mozilla.javascript.NativeObject;
|
||||||
import org.mozilla.javascript.ScriptableObject;
|
import org.mozilla.javascript.ScriptableObject;
|
||||||
|
|
||||||
public class JasmineSpecFailureException extends Exception {
|
class JasmineSpecFailureException extends Exception {
|
||||||
|
|
||||||
private final ScriptableObject trace;
|
private final ScriptableObject trace;
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import java.io.IOException;
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
|
|
||||||
public class JasmineSpecRunnerGenerator {
|
class JasmineSpecRunnerGenerator {
|
||||||
|
|
||||||
private enum TemplatePlaceholders {
|
private enum TemplatePlaceholders {
|
||||||
RELATIVE_PATH("<!--RelativePath-->"),
|
RELATIVE_PATH("<!--RelativePath-->"),
|
||||||
|
|
|
@ -9,6 +9,7 @@ import org.junit.Before;
|
||||||
import org.junit.runner.Description;
|
import org.junit.runner.Description;
|
||||||
import org.junit.runner.Runner;
|
import org.junit.runner.Runner;
|
||||||
import org.junit.runner.notification.RunNotifier;
|
import org.junit.runner.notification.RunNotifier;
|
||||||
|
import org.mozilla.javascript.ContextFactory;
|
||||||
import org.mozilla.javascript.NativeArray;
|
import org.mozilla.javascript.NativeArray;
|
||||||
import org.mozilla.javascript.tools.debugger.Main;
|
import org.mozilla.javascript.tools.debugger.Main;
|
||||||
|
|
||||||
|
@ -35,7 +36,7 @@ public class JasmineTestRunner extends Runner {
|
||||||
|
|
||||||
Main debugger = null;
|
Main debugger = null;
|
||||||
if (this.suiteAnnotation.debug()) {
|
if (this.suiteAnnotation.debug()) {
|
||||||
debugger = this.rhinoContext.createDebugger();
|
debugger = createDebugger();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.rhinoContext = setUpRhinoScope();
|
this.rhinoContext = setUpRhinoScope();
|
||||||
|
@ -62,6 +63,23 @@ public class JasmineTestRunner extends Runner {
|
||||||
context.evalJS("jasmine.getEnv().addReporter(new jasmine.DelegatorJUnitReporter());");
|
context.evalJS("jasmine.getEnv().addReporter(new jasmine.DelegatorJUnitReporter());");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Main createDebugger() {
|
||||||
|
Main debugger = new Main("JS Debugger");
|
||||||
|
|
||||||
|
debugger.setExitAction(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
debugger.attachTo(ContextFactory.getGlobal());
|
||||||
|
debugger.pack();
|
||||||
|
debugger.setSize(600, 460);
|
||||||
|
debugger.setVisible(true);
|
||||||
|
|
||||||
|
return debugger;
|
||||||
|
}
|
||||||
|
|
||||||
private JasmineSuite getJasmineSuiteAnnotationFromTestClass() {
|
private JasmineSuite getJasmineSuiteAnnotationFromTestClass() {
|
||||||
JasmineSuite suiteAnnotation = testClass.getAnnotation(JasmineSuite.class);
|
JasmineSuite suiteAnnotation = testClass.getAnnotation(JasmineSuite.class);
|
||||||
if (suiteAnnotation == null) {
|
if (suiteAnnotation == null) {
|
||||||
|
|
|
@ -5,7 +5,6 @@ import org.mozilla.javascript.ContextFactory;
|
||||||
import org.mozilla.javascript.Function;
|
import org.mozilla.javascript.Function;
|
||||||
import org.mozilla.javascript.Scriptable;
|
import org.mozilla.javascript.Scriptable;
|
||||||
import org.mozilla.javascript.ScriptableObject;
|
import org.mozilla.javascript.ScriptableObject;
|
||||||
import org.mozilla.javascript.tools.debugger.Main;
|
|
||||||
import org.mozilla.javascript.tools.shell.Global;
|
import org.mozilla.javascript.tools.shell.Global;
|
||||||
|
|
||||||
public class RhinoContext {
|
public class RhinoContext {
|
||||||
|
@ -134,21 +133,4 @@ public class RhinoContext {
|
||||||
public void exit() {
|
public void exit() {
|
||||||
Context.exit();
|
Context.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Main createDebugger() {
|
|
||||||
Main debugger = new Main("JS Rhino Debugger");
|
|
||||||
|
|
||||||
debugger.setExitAction(new Runnable() {
|
|
||||||
|
|
||||||
public void run() {
|
|
||||||
System.exit(0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
debugger.attachTo(ContextFactory.getGlobal());
|
|
||||||
debugger.pack();
|
|
||||||
debugger.setSize(600, 460);
|
|
||||||
debugger.setVisible(true);
|
|
||||||
return debugger;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
package be.klak.env;
|
||||||
|
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import be.klak.junit.jasmine.JasmineSuite;
|
||||||
|
import be.klak.junit.jasmine.JasmineTestRunner;
|
||||||
|
|
||||||
|
@RunWith(JasmineTestRunner.class)
|
||||||
|
@JasmineSuite
|
||||||
|
public class EnvUtilsTest {
|
||||||
|
|
||||||
|
}
|
|
@ -16,7 +16,7 @@ public class DescriptionsRecursiveTreeInRunnerTest {
|
||||||
Description baseTestDescription = new JasmineTestRunner(RecursiveTreeTest.class).getDescription();
|
Description baseTestDescription = new JasmineTestRunner(RecursiveTreeTest.class).getDescription();
|
||||||
assertThat(baseTestDescription.getDisplayName()).contains(RecursiveTreeTest.class.getSimpleName());
|
assertThat(baseTestDescription.getDisplayName()).contains(RecursiveTreeTest.class.getSimpleName());
|
||||||
|
|
||||||
assertThat(baseTestDescription.getChildren()).hasSize(1);
|
assertThat(baseTestDescription.getChildren()).hasSize(2);
|
||||||
Description root = baseTestDescription.getChildren().get(0);
|
Description root = baseTestDescription.getChildren().get(0);
|
||||||
assertThat(root.getDisplayName()).isEqualTo("root");
|
assertThat(root.getDisplayName()).isEqualTo("root");
|
||||||
assertThat(root.getChildren()).hasSize(3);
|
assertThat(root.getChildren()).hasSize(3);
|
||||||
|
@ -24,6 +24,12 @@ public class DescriptionsRecursiveTreeInRunnerTest {
|
||||||
assertThat(root.getChildren().get(0).getDisplayName()).isEqualTo("rootTest");
|
assertThat(root.getChildren().get(0).getDisplayName()).isEqualTo("rootTest");
|
||||||
assertChild1AndChildren(root);
|
assertChild1AndChildren(root);
|
||||||
assertChild2AndChildren(root);
|
assertChild2AndChildren(root);
|
||||||
|
|
||||||
|
Description root2 = baseTestDescription.getChildren().get(1);
|
||||||
|
assertThat(root2.getDisplayName()).isEqualTo("root2");
|
||||||
|
assertThat(root2.getChildren()).hasSize(1);
|
||||||
|
|
||||||
|
assertThat(root2.getChildren().get(0).getDisplayName()).isEqualTo("root2Test");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertChild2AndChildren(Description root) {
|
private void assertChild2AndChildren(Description root) {
|
||||||
|
|
|
@ -55,6 +55,7 @@
|
||||||
* Envjs specific hacks
|
* Envjs specific hacks
|
||||||
* 1) Fix Envjs relative path system to work with Windows path systems
|
* 1) Fix Envjs relative path system to work with Windows path systems
|
||||||
* 2) Fix window.setTimeout() using Rhino specific functions
|
* 2) Fix window.setTimeout() using Rhino specific functions
|
||||||
|
* 3) Fix CSS2Properties support: all properties have the same objmaps, wtf?
|
||||||
*/
|
*/
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
|
@ -73,4 +74,17 @@
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
(function(css) {
|
||||||
|
|
||||||
|
var setCssProperty = css.prototype.setProperty;
|
||||||
|
css.prototype.setProperty = function(name, value) {
|
||||||
|
// create a shallow clone of __supportedStyles__ (styleIndex' default value) if prototype not yet set
|
||||||
|
if(Object.keys(Object.getPrototypeOf(this.styleIndex)).length === 0) {
|
||||||
|
this.styleIndex = Object.create(this.styleIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return setCssProperty.call(this, name, value);
|
||||||
|
}
|
||||||
|
})(CSS2Properties);
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
|
||||||
|
describe("envjs fixes", function() {
|
||||||
|
|
||||||
|
describe("CSS2 style property support", function() {
|
||||||
|
|
||||||
|
var someColor = "#FFFFFF";
|
||||||
|
var someFont = "12px 'Bitstream Vera Sans Mono','Courier',monospace";
|
||||||
|
|
||||||
|
it("should be visible and displayed by default for all new elements", function() {
|
||||||
|
var elStyle = document.createElement("b").style;
|
||||||
|
|
||||||
|
expect(elStyle.display).toBeFalsy();
|
||||||
|
expect(elStyle.visibility).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be able to set a style value through setters", function() {
|
||||||
|
var someB = document.createElement("b");
|
||||||
|
someB.style.color = someColor;
|
||||||
|
|
||||||
|
expect(someB.style.color).toBe(someColor);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have unique style values per DOM element", function() {
|
||||||
|
var someEl1 = document.createElement("b");
|
||||||
|
var someEl2 = document.createElement("b");
|
||||||
|
|
||||||
|
someEl1.style.color = someColor;
|
||||||
|
someEl2.style.font = someFont;
|
||||||
|
|
||||||
|
expect(someEl1.style.font).toBeFalsy();
|
||||||
|
expect(someEl2.style.color).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("window setTimeout", function() {
|
||||||
|
|
||||||
|
it("should wait one second before executing", function() {
|
||||||
|
var done = false;
|
||||||
|
window.setTimeout(function() {
|
||||||
|
done = true;
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
waitsFor(function() {
|
||||||
|
return done === true;
|
||||||
|
});
|
||||||
|
|
||||||
|
runs(function() {
|
||||||
|
expect(done).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -30,3 +30,10 @@ describe("root", function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("root2", function() {
|
||||||
|
|
||||||
|
it("root2Test", function() {
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
Reference in New Issue