diff --git a/configs/client-default.js b/configs/client-default.js
index ad581a8a..39874673 100644
--- a/configs/client-default.js
+++ b/configs/client-default.js
@@ -21,6 +21,7 @@ module.exports = function(options) {
var workspaceDir = options.workspaceDir;
var debug = options.debug !== undefined ? options.debug : false;
+
var collab = options.collab;
var packaging = options.packaging;
var staticPrefix = options.staticPrefix;
@@ -99,6 +100,10 @@ module.exports = function(options) {
{
packagePath: "plugins/c9.ide.plugins/market"
},
+ {
+ packagePath: "plugins/c9.ide.plugins/test",
+ staticPrefix: staticPrefix + "/plugins/c9.ide.plugins"
+ },
// VFS
"plugins/c9.vfs.client/vfs.ping",
diff --git a/node_modules/architect/architect.js b/node_modules/architect/architect.js
index 17c5fe43..9b2d602a 100644
--- a/node_modules/architect/architect.js
+++ b/node_modules/architect/architect.js
@@ -7,7 +7,7 @@ var EventEmitter = events.EventEmitter;
var exports = {};
-var DEBUG = typeof location != "undefined" && location.href.match(/debug=[12]/) ? true : false;
+var DEBUG = typeof location != "undefined" && location.href.match(/debug=[123]/) ? true : false;
// Only define Node-style usage using sync I/O if in node.
if (typeof module === "object") (function () {
diff --git a/package.json b/package.json
index 4da34d8c..a9fcc448 100644
--- a/package.json
+++ b/package.json
@@ -88,7 +88,7 @@
"c9.ide.navigate": "#64156c7f4a",
"c9.ide.newresource": "#f1f0624768",
"c9.ide.openfiles": "#28a4f5af16",
- "c9.ide.preview": "#dba2f4214d",
+ "c9.ide.preview": "#0bd8dd6e8c",
"c9.ide.preview.browser": "#ac18aaf31d",
"c9.ide.preview.markdown": "#ab8d30ad9f",
"c9.ide.pubsub": "#b83cf15ade",
diff --git a/plugins/c9.cli.publish/publish.js b/plugins/c9.cli.publish/publish.js
index 75fdbef2..eec65672 100644
--- a/plugins/c9.cli.publish/publish.js
+++ b/plugins/c9.cli.publish/publish.js
@@ -372,11 +372,10 @@ define(function(require, exports, module) {
console.warn("WARNING: Plugin '" + name + "' is not listed in package.json.");
warned = true;
}
- // @TODO temporarily disabled the requirement for tests while tests cannot actually run yet
- // else if (!fs.existsSync(join(cwd, name.replace(/\.js$/, "_test.js")))) {
- // console.warn("ERROR: Plugin '" + name + "' has no test associated with it.");
- // failed = true;
- // }
+ else if (!fs.existsSync(join(cwd, name.replace(/\.js$/, "_test.js")))) {
+ console.warn("ERROR: Plugin '" + name + "' has no test associated with it. There must be a file called '" + name + "_test.js' containing tests.");
+ failed = true;
+ }
});
if (failed)
diff --git a/plugins/c9.ide.ace/themes.js b/plugins/c9.ide.ace/themes.js
index 94fe9cce..a1cd5ea5 100644
--- a/plugins/c9.ide.ace/themes.js
+++ b/plugins/c9.ide.ace/themes.js
@@ -35,10 +35,10 @@ define(function(require, exports, module) {
if (!drawn) return;
var list = getThemes();
- plugin.form.update({
+ plugin.form.update([{
id: "syntax",
items: list
- });
+ }]);
}
ace.on("addTheme", update);
diff --git a/plugins/c9.ide.plugins/debug.js b/plugins/c9.ide.plugins/debug.js
index d488322d..c23dd2b5 100644
--- a/plugins/c9.ide.plugins/debug.js
+++ b/plugins/c9.ide.plugins/debug.js
@@ -36,7 +36,9 @@ define(function(require, exports, module) {
/***** Initialization *****/
var plugin = new Plugin("Ajax.org", main.consumes);
- // var emit = plugin.getEmitter();
+ var emit = plugin.getEmitter();
+
+ var plugins = [];
var ENABLED = c9.location.indexOf("debug=2") > -1;
var HASSDK = c9.location.indexOf("sdk=0") === -1;
@@ -53,14 +55,17 @@ define(function(require, exports, module) {
menus.addItemByPath("Tools/~", new ui.divider(), 100000, plugin);
menus.addItemByPath("Tools/Developer", null, 100100, plugin);
- menus.addItemByPath("Tools/Developer/Start in Debug Mode", new ui.item({
- onclick: function(){
- var url = location.href + (location.href.indexOf("?") > -1
- ? "&debug=2"
- : "?debug=2");
- window.open(url);
- }
- }), 100100, plugin);
+
+ if (!ENABLED) {
+ menus.addItemByPath("Tools/Developer/Start in Debug Mode", new ui.item({
+ onclick: function(){
+ var url = location.href + (location.href.indexOf("?") > -1
+ ? "&debug=2"
+ : "?debug=2");
+ window.open(url);
+ }
+ }), 900, plugin);
+ }
if (!ENABLED) return;
@@ -104,7 +109,7 @@ define(function(require, exports, module) {
menus.addItemByPath("Tools/Developer/Restart Plugin", new ui.item({
command: "restartplugin"
- }), 100100, plugin);
+ }), 1000, plugin);
}
/***** Methods *****/
@@ -177,6 +182,7 @@ define(function(require, exports, module) {
cfg.apikey = "0000000000000000000000000000=";
config.push(cfg);
+ plugins.push(name + "/" + path);
});
// Set version for package manager
@@ -234,6 +240,8 @@ define(function(require, exports, module) {
load();
}, function(){
+ emit.sticky("ready");
+
if (!config.length) return;
// Load config
@@ -490,11 +498,23 @@ define(function(require, exports, module) {
*/
get architect(){ throw new Error(); },
set architect(v){ architect = v; },
+ /**
+ *
+ */
+ get plugins(){ return plugins; },
+
+ _events: [
+ /**
+ * @event ready
+ */
+ "ready"
+ ],
/**
*
*/
addStaticPlugin: addStaticPlugin,
+
/**
*
*/
diff --git a/plugins/c9.ide.plugins/templates/plugin.default/plugin_test.js b/plugins/c9.ide.plugins/templates/plugin.default/plugin_test.js
index 13007893..72b1a7fa 100644
--- a/plugins/c9.ide.plugins/templates/plugin.default/plugin_test.js
+++ b/plugins/c9.ide.plugins/templates/plugin.default/plugin_test.js
@@ -21,7 +21,7 @@ define(function(require, exports, module) {
/***** Initialization *****/
- describe("The module", function(){
+ describe(myplugin.name, function(){
this.timeout(2000);
beforeEach(function() {
diff --git a/plugins/c9.ide.plugins/test.html b/plugins/c9.ide.plugins/test.html
new file mode 100644
index 00000000..309727c7
--- /dev/null
+++ b/plugins/c9.ide.plugins/test.html
@@ -0,0 +1,60 @@
+
+
+
+
+ Test Runner
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/c9.ide.plugins/test.js b/plugins/c9.ide.plugins/test.js
index c5fd81ad..c6af4465 100644
--- a/plugins/c9.ide.plugins/test.js
+++ b/plugins/c9.ide.plugins/test.js
@@ -1,85 +1,28 @@
-//@TODO look at jasmine instead
-
-require(["lib/architect/architect", "lib/chai/chai", "/vfs-root"], function() {
-
-mocha.setup('bdd');
- mocha.bail(false);
- mocha.ignoreLeaks(true);
-mocha.run(done)
-/*global Mocha, mocha*/
- mocha.reporter(function(runner) {
- Mocha.reporters.Base.call(this, runner);
- Mocha.reporters.HTML.call(this, runner);
-
- var tests = [];
- var stats = this.stats;
- mocha.report = { stats: stats, tests: tests };
-
- runner.on('test end', function(test) {
- stats.percent = stats.tests / runner.total * 100 | 0;
- tests.push(clean(test));
- });
-
- runner.on('end', function() {
- console.log(JSON.stringify(mocha.report, null, 4));
- });
-
- function parseError(err) {
- var str = err.stack || err.toString();
-
- // FF / Opera do not add the message
- if (!~str.indexOf(err.message)) {
- str = err.message + '\n' + str;
- }
-
- // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we
- // check for the result of the stringifying.
- if ('[object Error]' == str) str = err.message;
-
- // Safari doesn't give you a stack. Let's at least provide a source line.
- if (!err.stack && err.sourceURL && err.line !== undefined) {
- str += "\n(" + err.sourceURL + ":" + err.line + ")";
- }
- return str;
- }
- function clean(test) {
- return {
- title: test.title,
- duration: test.duration,
- error: test.err && parseError(test.err),
- speed: test.speed,
- state: test.state
- };
- }
- });
-
-/*global requirejs*/
+/* global requirejs */
define(function(require, exports, module) {
main.consumes = [
- "Plugin", "vfs", "fs", "plugin.loader", "c9", "ext", "watcher",
- "dialog.notification"
+ "Plugin", "plugin.debug", "c9", "menus", "ui", "ext", "preview",
+ "preview.browser"
];
- main.provides = ["plugin.editor"];
+ main.provides = ["plugin.test"];
return main;
function main(options, imports, register) {
var Plugin = imports.Plugin;
- var vfs = imports.vfs;
- var watcher = imports.watcher;
- var ext = imports.ext;
- var fs = imports.fs;
var c9 = imports.c9;
- var loader = imports["plugin.loader"];
- var notify = imports["dialog.notification"].show;
-
- var dirname = require("path").dirname;
-
- var architect;
+ var menus = imports.menus;
+ var preview = imports.preview;
+ var ext = imports.ext;
+ var ui = imports.ui;
+ var debug = imports["plugin.debug"];
+ var browser = imports["preview.browser"];
/***** Initialization *****/
var plugin = new Plugin("Ajax.org", main.consumes);
- // var emit = plugin.getEmitter();
+ var emit = plugin.getEmitter();
+
+ var chai, mocha, iframe, architect;
var ENABLED = c9.location.indexOf("debug=2") > -1;
@@ -90,187 +33,133 @@ define(function(require, exports, module) {
if (!ENABLED) return;
- notify("You are in Debug Mode. "
- + "Don't forget to open the browser's dev tools to see any errors.",
- false);
-
- fs.readdir("~/.c9/plugins", function(err, list){
- if (err) return console.error(err);
+ debug.once("ready", function(){
+ menus.addItemByPath("Tools/Developer/Tests", null, 200, plugin);
- var names = loader.plugins;
- var toLoad = [];
-
- list.forEach(function(stat){
- var name = stat.name;
- // If the plugin doesn't exist
- if (names.indexOf(name) == -1 && name.charAt(0) != ".")
- toLoad.push(name);
+ debug.plugins.forEach(function(name, i){
+ menus.addItemByPath("Tools/Developer/Tests/" + name.replace(/\//g, "\\/"), new ui.item({
+ onclick: function(){
+ run(name, function(err){
+ if (err) console.error(err);
+ });
+ }
+ }), i + 1, plugin);
});
-
- loadPlugins(toLoad);
+ });
+
+ ext.on("register", function(){
+ // TODO
+ }, plugin);
+ ext.on("unregister", function(){
+ // TODO
+ }, plugin);
+
+ var reloading;
+ function loadPreview(url, session){
+ var idx = url.indexOf(options.staticPrefix);
+ if (!reloading && idx > -1) {
+ reloading = true;
+
+ var name = session.doc.meta.pluginName;
+ run(name, function(err){
+ if (err) console.error(err);
+ });
+
+ reloading = false;
+ }
+ }
+
+ browser.on("reload", function(e){
+ loadPreview(e.session.path, e.session);
});
}
/***** Methods *****/
- function loadPlugins(list){
- if (!vfs.connected) {
- vfs.once("connect", loadPlugins.bind(this, config));
- return;
+ function setReferences(c, m){
+ chai = c;
+ mocha = m;
+
+ emit("ready");
+ }
+
+ function loadIframe(pluginName, callback){
+ var url = options.staticPrefix + "/test.html";
+ if (url.indexOf("http") !== 0)
+ url = location.origin + url;
+
+ var tab = preview.openPreview(url, null, true);
+ iframe = tab.document.getSession().iframe;
+ iframe.addEventListener("load", handle);
+ iframe.addEventListener("error", onError);
+
+ function handle(err){
+ iframe.removeEventListener("load", handle);
+ iframe.removeEventListener("error", onError);
+ callback(err instanceof Error ? err : null, tab);
}
- var config = [];
- var count = list.length;
+ function onError(e){
+ debugger; // e.??
+ handle(new Error());
+ }
- function next(name){
- if (!name) {
- if (--count === 0) finish();
- return;
- }
+ tab.document.meta.ignoreState = true;
+ tab.document.meta.pluginName = pluginName;
+ }
+
+ function loadTestSuite(name, callback){
+ // Clear require cache
+ requirejs.undef("plugins/" + name + "_test"); // global
+
+ // Load plugin
+ architect.loadAdditionalPlugins([{
+ packagePath: "plugins/" + name + "_test"
+ }], function(err){
+ callback(err);
+ });
+ }
+
+ function run(pluginName, callback){
+ // Load test runner
+ loadIframe(pluginName, function(err, tab){
+ if (err) return callback(err);
- // Fetch package.json
- fs.readFile("~/.c9/plugins/" + name + "/package.json", function(err, data){
- if (err) {
- console.error(err);
- return next();
- }
+ tab.editor.setLocation("test://" + pluginName)
+
+ // Wait until iframe is loaded
+ plugin.once("ready", function(){
- try{
- var options = JSON.parse(data);
- if (!options.plugins)
- throw new Error("Missing plugins property in package.json of " + name);
- }
- catch(e){
- console.error(err);
- return next();
- }
-
- options.plugins.forEach(function(path){
- var pluginPath = "~/.c9/plugins/" + name + "/" + path + ".js";
- var url = vfs.url(pluginPath);
+ // Load the test for the plugin
+ loadTestSuite(pluginName, function(err){
+ if (err) return callback(err);
- // Watch project path
- watch(pluginPath);
-
- config.push({
- packagePath: url,
- staticPrefix: dirname(url),
- apikey: "00000000-0000-4000-y000-" + String(config.length).pad(12, "0")
+ // Run the test
+ mocha.run(function(){
+
+ // Done
+ callback();
});
});
- next();
- });
- }
-
- function finish(){
- // Load config
- architect.loadAdditionalPlugins(config, function(err){
- if (err) console.error(err);
- });
- }
-
- list.forEach(next);
- }
-
- // Check if require.s.contexts._ can help watching all dependencies
- function watch(path){
- watcher.watch(path);
-
- watcher.on("change", function(e){
- if (e.path == path)
- reloadPackage(path.replace(/^~\/\.c9\//, ""));
- });
- watcher.on("delete", function(e){
- if (e.path == path)
- reloadPackage(path.replace(/^~\/\.c9\//, ""));
- });
- watcher.on("failed", function(e){
- if (e.path == path) {
- setTimeout(function(){
- watcher.watch(path); // Retries once after 1s
- });
- }
- });
- }
-
- function reloadPackage(path){
- var unloaded = [];
-
- function recurUnload(name){
- var plugin = architect.services[name];
- unloaded.push(name);
-
- // Find all the dependencies
- var deps = ext.getDependencies(plugin.name);
-
- // Unload all the dependencies (and their deps)
- deps.forEach(function(name){
- recurUnload(name);
});
- // Unload plugin
- plugin.unload();
- }
-
- // Recursively unload plugin
- var p = architect.lut[path];
- if (p.provides) { // Plugin might not been initialized all the way
- p.provides.forEach(function(name){
- recurUnload(name);
- });
- }
-
- // create reverse lookup table
- var rlut = {};
- for (var packagePath in architect.lut) {
- var provides = architect.lut[packagePath].provides;
- if (provides) { // Plugin might not been initialized all the way
- provides.forEach(function(name){
- rlut[name] = packagePath;
- });
- }
- }
-
- // Build config of unloaded plugins
- var config = [], done = {};
- unloaded.forEach(function(name){
- var packagePath = rlut[name];
-
- // Make sure we include each plugin only once
- if (done[packagePath]) return;
- done[packagePath] = true;
-
- var options = architect.lut[packagePath];
- delete options.provides;
- delete options.consumes;
- delete options.setup;
-
- config.push(options);
-
- // Clear require cache
- requirejs.undef(options.packagePath); // global
- });
-
- // Load all plugins again
- architect.loadAdditionalPlugins(config, function(err){
- if (err) console.error(err);
- });
+ // Load iframe with new test runner frame
+ iframe.contentWindow.start(plugin);
+ })
}
/***** Lifecycle *****/
plugin.on("load", function() {
load();
- });
- plugin.on("enable", function() {
-
- });
- plugin.on("disable", function() {
-
});
plugin.on("unload", function() {
loaded = false;
+ chai = null;
+ mocha = null;
+ iframe = null;
+ architect = null;
});
/***** Register and define API *****/
@@ -288,12 +177,104 @@ define(function(require, exports, module) {
/**
*
*/
- reloadPackage: reloadPackage
+ get describe(){ return mocha.describe; },
+ /**
+ *
+ */
+ get it(){ return mocha.it; },
+ /**
+ *
+ */
+ get before(){ return mocha.before; },
+ /**
+ *
+ */
+ get after(){ return mocha.after; },
+ /**
+ *
+ */
+ get beforeEach(){ return mocha.beforeEach; },
+ /**
+ *
+ */
+ get afterEach(){ return mocha.afterEach; },
+ /**
+ *
+ */
+ get assert(){ return chai.assert; },
+ /**
+ *
+ */
+ get expect(){ return chai.expect; },
+
+ /**
+ *
+ */
+ setReferences: setReferences,
+
+ /**
+ *
+ */
+ run: run
});
register(null, {
- "plugin.editor": plugin
+ "plugin.test": plugin
});
}
});
-});
\ No newline at end of file
+
+//@TODO look at jasmine instead
+
+// require(["lib/architect/architect", "lib/chai/chai", "/vfs-root"], function() {
+
+// mocha.setup('bdd');
+// mocha.bail(false);
+// mocha.ignoreLeaks(true);
+// mocha.run(done)
+// /*global Mocha, mocha*/
+// mocha.reporter(function(runner) {
+// Mocha.reporters.Base.call(this, runner);
+// Mocha.reporters.HTML.call(this, runner);
+
+// var tests = [];
+// var stats = this.stats;
+// mocha.report = { stats: stats, tests: tests };
+
+// runner.on('test end', function(test) {
+// stats.percent = stats.tests / runner.total * 100 | 0;
+// tests.push(clean(test));
+// });
+
+// runner.on('end', function() {
+// console.log(JSON.stringify(mocha.report, null, 4));
+// });
+
+// function parseError(err) {
+// var str = err.stack || err.toString();
+
+// // FF / Opera do not add the message
+// if (!~str.indexOf(err.message)) {
+// str = err.message + '\n' + str;
+// }
+
+// // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we
+// // check for the result of the stringifying.
+// if ('[object Error]' == str) str = err.message;
+
+// // Safari doesn't give you a stack. Let's at least provide a source line.
+// if (!err.stack && err.sourceURL && err.line !== undefined) {
+// str += "\n(" + err.sourceURL + ":" + err.line + ")";
+// }
+// return str;
+// }
+// function clean(test) {
+// return {
+// title: test.title,
+// duration: test.duration,
+// error: test.err && parseError(test.err),
+// speed: test.speed,
+// state: test.state
+// };
+// }
+// });
\ No newline at end of file
diff --git a/plugins/c9.ide.ui/menus.js b/plugins/c9.ide.ui/menus.js
index 94461231..57206a7b 100644
--- a/plugins/c9.ide.ui/menus.js
+++ b/plugins/c9.ide.ui/menus.js
@@ -181,6 +181,21 @@ define(function(require, exports, module) {
/***** Methods *****/
+ function splitSafe(path){
+ var pieces = [], escaped;
+ path.split("/").forEach(function(n){
+ if (escaped) n = escaped + "/" + n;
+ escaped = n.substr(-1) == "\\" ? n : false; //.substr(n, n.length - 1)
+ if (!escaped) pieces.push(n);
+ });
+ if (escaped) pieces.push(escaped);
+ return pieces;
+ }
+
+ function popSafe(path){
+ return splitSafe(path).pop().replace(/\\\//g, "/");
+ }
+
function init(){
inited = true;
layout.initMenus(plugin);
@@ -365,7 +380,8 @@ define(function(require, exports, module) {
if (item) {
item.setAttribute("submenu", menu);
item.setAttribute("caption",
- apf.escapeXML((debug ? "(" + index + ")" : "") + name.split("/").pop()));
+ apf.escapeXML((debug ? "(" + index + ")" : "")
+ + popSafe(name)));
items[name] = item;
}
else {
@@ -374,7 +390,7 @@ define(function(require, exports, module) {
item = items[name] = new apf.item({
submenu: menu,
caption: (debug ? "(" + index + ") " : "") +
- name.split("/").pop()
+ popSafe(name)
});
}
else {
@@ -402,7 +418,7 @@ define(function(require, exports, module) {
function setMenuItem(parent, name, menuItem, index, item, plugin) {
if (item && !item.nodeFunc) plugin = item, item = null;
- var itemName = name.split("/").pop();
+ var itemName = popSafe(name);
if (itemName == "~")
name += index;
@@ -462,7 +478,7 @@ define(function(require, exports, module) {
assert(plugin !== undefined, "addItemByPath requires a plugin argument");
- var steps = path.split("/"), name, p = [], isLast;
+ var steps = splitSafe(path), name, p = [], isLast;
var curpath;
if (!menuItem)
@@ -568,7 +584,7 @@ define(function(require, exports, module) {
if (!items[path])
throw new Error("Could not find menu item " + path);
- var steps = path.split("/"), p = [], item;
+ var steps = splitSafe(path), p = [], item;
var curpath;
for (var name, i = 0, l = steps.length; i < l; i++) {
diff --git a/plugins/c9.vfs.client/vfs_client.js b/plugins/c9.vfs.client/vfs_client.js
index 31ec4845..5339ee80 100644
--- a/plugins/c9.vfs.client/vfs_client.js
+++ b/plugins/c9.vfs.client/vfs_client.js
@@ -39,6 +39,9 @@ define(function(require, exports, module) {
var protocolVersion = require("kaefer/version").protocol;
var smith = require("smith");
var URL = require("url");
+ var DEBUG = options.debug
+ && (typeof location == "undefined"
+ || location.href.indexOf("debug=3") > -1);
// The connected vfs unique id
var id;
@@ -76,11 +79,11 @@ define(function(require, exports, module) {
if (loaded) return false;
loaded = true;
- smith.debug = options.debug;
+ smith.debug = DEBUG;
connection = connectClient(connectEngine, {
preConnectCheck: preConnectCheck,
- debug: options.debug
+ debug: DEBUG
});
connection.on("away", emit.bind(null, "away"));
diff --git a/plugins/c9.vfs.standalone/standalone.js b/plugins/c9.vfs.standalone/standalone.js
index 83ddbf60..9aa56371 100644
--- a/plugins/c9.vfs.standalone/standalone.js
+++ b/plugins/c9.vfs.standalone/standalone.js
@@ -119,7 +119,7 @@ function plugin(options, imports, register) {
token: req.params.token
});
- opts.options.debug = req.params.debug == 1;
+ opts.options.debug = req.params.debug !== undefined;
res.setHeader("Cache-Control", "no-cache, no-store");
res.render(__dirname + "/views/standalone.html.ejs", {
architectConfig: getConfig(configType, opts),
diff --git a/plugins/c9.vfs.standalone/views/standalone.html.ejs b/plugins/c9.vfs.standalone/views/standalone.html.ejs
index c1162d09..008507a3 100644
--- a/plugins/c9.vfs.standalone/views/standalone.html.ejs
+++ b/plugins/c9.vfs.standalone/views/standalone.html.ejs
@@ -101,7 +101,9 @@
});
app.on("service", function(name, plugin, options){
- if (name == "plugin.loader" || name == "plugin.installer" || name == "plugin.debug" || name == "plugin.manager")
+ if (name == "plugin.loader" || name == "plugin.installer"
+ || name == "plugin.debug" || name == "plugin.manager"
+ || name == "plugin.test")
plugin.architect = app;
if (!plugin.name)
plugin.name = name;