kopia lustrzana https://github.com/c9/core
724 wiersze
25 KiB
JavaScript
724 wiersze
25 KiB
JavaScript
define(function(require, exports, module) {
|
|
main.consumes = [
|
|
"Panel", "ui", "settings", "panels", "menus", "commands", "Menu",
|
|
"MenuItem", "Divider", "tabManager", "fs", "dialog.error", "c9",
|
|
"preferences.experimental", "save", "watcher"
|
|
];
|
|
main.provides = ["test"];
|
|
return main;
|
|
|
|
function main(options, imports, register) {
|
|
var Panel = imports.Panel;
|
|
var ui = imports.ui;
|
|
var settings = imports.settings;
|
|
var panels = imports.panels;
|
|
var menus = imports.menus;
|
|
var commands = imports.commands;
|
|
var tabManager = imports.tabManager;
|
|
var Menu = imports.Menu;
|
|
var MenuItem = imports.MenuItem;
|
|
var Divider = imports.Divider;
|
|
var fs = imports.fs;
|
|
var c9 = imports.c9;
|
|
var save = imports.save;
|
|
var watcher = imports.watcher;
|
|
var showError = imports["dialog.error"].show;
|
|
var experimental = imports["preferences.experimental"];
|
|
|
|
var Coverage = require("./data/coverage");
|
|
var File = require("./data/file");
|
|
var TestSet = require("./data/testset");
|
|
var Test = require("./data/test");
|
|
var Node = require("./data/node");
|
|
var Data = require("./data/data");
|
|
|
|
// Destructive conversion process
|
|
Data.fromJSON = function(list) {
|
|
return list.map(function(node) {
|
|
if (node instanceof Data) return node;
|
|
if (node.items) node.items = Data.fromJSON(node.items);
|
|
|
|
if (node.type == "file")
|
|
return new File(node);
|
|
if (node.type == "testset")
|
|
return new TestSet(node);
|
|
if (node.type == "test")
|
|
return new Test(node);
|
|
if (node.type == "prepare")
|
|
return new Node(node);
|
|
|
|
// TODO what should happen with other types
|
|
console.error("unhandled type ", node);
|
|
}).filter(Boolean);
|
|
};
|
|
|
|
var ENABLED = options.enabled
|
|
|| experimental.addExperiment("test", false, "Panels/Test Panel");
|
|
|
|
if (!ENABLED && !options.enabled) {
|
|
return register(null, {
|
|
"test": {
|
|
Coverage: Coverage,
|
|
File: File,
|
|
TestSet: TestSet,
|
|
Test: Test,
|
|
Node: Node,
|
|
|
|
runners: [],
|
|
|
|
register: function() {},
|
|
unregister: function() {},
|
|
on: function() {},
|
|
once: function() {},
|
|
inactive: true
|
|
}
|
|
});
|
|
}
|
|
|
|
/***** Initialization *****/
|
|
|
|
var plugin = new Panel("Ajax.org", main.consumes, {
|
|
index: options.index || 400,
|
|
caption: "Test",
|
|
buttonCSSClass: "test",
|
|
minWidth: 150,
|
|
where: options.where || "left"
|
|
});
|
|
var emit = plugin.getEmitter();
|
|
|
|
var configPath = options.configPath || "/.c9/test.settings.yml";
|
|
var config, ready;
|
|
|
|
var runners = [];
|
|
var toolbar, container, btnRun, focussedPanel, mnuRun, mnuSettings;
|
|
var lastTest;
|
|
|
|
function load() {
|
|
plugin.setCommand({
|
|
name: "showtestpanel"
|
|
// hint: "search for a command and execute it",
|
|
// bindKey: { mac: "Command-.", win: "Ctrl-." }
|
|
});
|
|
|
|
commands.addCommand({
|
|
name: "runtest",
|
|
hint: "runs the selected test(s) in the test panel",
|
|
// bindKey: { mac: "F6", win: "Ctrl-F5" },
|
|
group: "Test",
|
|
exec: function(editor, args) {
|
|
if (settings.getBool("user/test/coverage/@alwayson"))
|
|
return commands.exec("runtestwithcoverage", editor, args);
|
|
|
|
transformRunButton("stop");
|
|
focussedPanel.run(args.nodes || null, {
|
|
transformRun: args.transformRun
|
|
}, function(err, nodes) {
|
|
transformRunButton("run");
|
|
if (err) return console.log(err);
|
|
|
|
if (nodes) {
|
|
nodes.forEach(function(node) {
|
|
emit(node.coverage
|
|
? "coverage"
|
|
: "clearCoverage", { node: node });
|
|
});
|
|
}
|
|
});
|
|
},
|
|
isAvailable: function() {
|
|
return focussedPanel ? true : false;
|
|
}
|
|
}, plugin);
|
|
|
|
commands.addCommand({
|
|
name: "runfocussedtest",
|
|
hint: "runs the focussed test or last run test",
|
|
bindKey: { mac: "F6", win: "Ctrl-F5" },
|
|
group: "Test",
|
|
exec: function(editor, args) {
|
|
var path = tabManager.focussedTab.path;
|
|
var test = focussedPanel.findFileByPath(path);
|
|
return commands.exec("runtest", editor, {
|
|
nodes: test ? [test] : lastTest,
|
|
transformRun: true
|
|
});
|
|
},
|
|
isAvailable: function() {
|
|
var path = (tabManager.focussedTab || 0).path;
|
|
return focussedPanel && (focussedPanel.findFileByPath(path) || lastTest)
|
|
? true : false;
|
|
}
|
|
}, plugin);
|
|
|
|
commands.addCommand({
|
|
name: "runfocussedtestwithcoverage",
|
|
hint: "runs the focussed test or last run test with code coverage",
|
|
bindKey: { mac: "Shift-F6", win: "Ctrl-Shift-F5" },
|
|
group: "Test",
|
|
exec: function(editor, args) {
|
|
var path = tabManager.focussedTab.path;
|
|
var test = focussedPanel.findFileByPath(path);
|
|
return commands.exec("runtestwithcoverage", editor, {
|
|
nodes: test ? [test] : lastTest,
|
|
transformRun: true
|
|
});
|
|
},
|
|
isAvailable: function() {
|
|
var path = (tabManager.focussedTab || 0).path;
|
|
return focussedPanel && (focussedPanel.findFileByPath(path) || lastTest)
|
|
? true : false;
|
|
}
|
|
}, plugin);
|
|
|
|
commands.addCommand({
|
|
name: "runtestwithcoverage",
|
|
hint: "runs the selected test(s) in the test panel with code coverage enabled",
|
|
// bindKey: { mac: "Shift-F6", win: "Ctrl-Shift-F5" },
|
|
group: "Test",
|
|
exec: function(editor, args) {
|
|
transformRunButton("stop");
|
|
focussedPanel.run(args.nodes || null, {
|
|
withCodeCoverage: true
|
|
}, function(err, nodes) {
|
|
transformRunButton("run");
|
|
if (err) return console.log(err);
|
|
|
|
if (nodes) {
|
|
nodes.forEach(function(node) {
|
|
emit(node.coverage
|
|
? "coverage"
|
|
: "clearCoverage", { node: node });
|
|
});
|
|
}
|
|
});
|
|
},
|
|
isAvailable: function() {
|
|
return focussedPanel ? true : false;
|
|
}
|
|
}, plugin);
|
|
|
|
commands.addCommand({
|
|
name: "stoptest",
|
|
// hint: "runs the selected test(s) in the test panel",
|
|
// bindKey: { mac: "Command-O", win: "Ctrl-O" },
|
|
group: "Test",
|
|
exec: function(editor, args) {
|
|
btnRun.disable();
|
|
focussedPanel.stop(function() {
|
|
btnRun.enable();
|
|
});
|
|
},
|
|
isAvailable: function() {
|
|
return focussedPanel ? true : false;
|
|
}
|
|
}, plugin);
|
|
|
|
commands.addCommand({
|
|
name: "cleartestresults",
|
|
// hint: "runs the selected test(s) in the test panel",
|
|
// bindKey: { mac: "Command-O", win: "Ctrl-O" },
|
|
group: "Test",
|
|
exec: function() {
|
|
emit("clear");
|
|
}
|
|
}, plugin);
|
|
|
|
commands.addCommand({
|
|
name: "skiptest",
|
|
// hint: "runs the selected test(s) in the test panel",
|
|
// bindKey: { mac: "Command-O", win: "Ctrl-O" },
|
|
group: "Test",
|
|
exec: function() {
|
|
focussedPanel.skip(function() {});
|
|
},
|
|
isAvailable: function() {
|
|
return focussedPanel && focussedPanel.tree
|
|
&& focussedPanel.tree.selectedNodes.some(function(n) {
|
|
if (n.type == "file") return true;
|
|
});
|
|
}
|
|
}, plugin);
|
|
|
|
commands.addCommand({
|
|
name: "removetest",
|
|
// hint: "runs the selected test(s) in the test panel",
|
|
// bindKey: { mac: "Command-O", win: "Ctrl-O" },
|
|
group: "Test",
|
|
exec: function() {
|
|
focussedPanel.remove(function() {});
|
|
},
|
|
isAvailable: function() {
|
|
return focussedPanel && focussedPanel.tree
|
|
&& focussedPanel.tree.selectedNodes.some(function(n) {
|
|
if (n.type == "file") return true;
|
|
});
|
|
}
|
|
}, plugin);
|
|
|
|
commands.addCommand({
|
|
name: "opentestoutput",
|
|
// hint: "runs the selected test(s) in the test panel",
|
|
// bindKey: { mac: "Command-O", win: "Ctrl-O" },
|
|
group: "Test",
|
|
exec: function(args) {
|
|
var nodes = args.nodes || focussedPanel.tree.selectedNodes;
|
|
nodes.forEach(function(n) {
|
|
var output = (n.findFileNode() || 0).fullOutput;
|
|
if (!output) return;
|
|
|
|
tabManager.open({
|
|
editorType: "ace",
|
|
focus: true,
|
|
document: {
|
|
title: "Raw Test Output",
|
|
meta: {
|
|
nofs: true,
|
|
ignoreState: true
|
|
}
|
|
},
|
|
value: output
|
|
}, function() {});
|
|
});
|
|
},
|
|
isAvailable: function() {
|
|
return focussedPanel && focussedPanel.tree
|
|
&& focussedPanel.tree.selectedNodes.some(function(n) {
|
|
return (n.findFileNode() || 0).fullOutput || false;
|
|
});
|
|
}
|
|
}, plugin);
|
|
|
|
menus.addItemByPath("Cloud9/Open Your Test Configuration", new ui.item({
|
|
onclick: openTestConfigFile
|
|
}), 900, plugin);
|
|
|
|
function readConfig(cb) {
|
|
fs.readFile(configPath, function(err, data) {
|
|
if (err && err.code != "ENOENT")
|
|
return showError("Could not load " + configPath
|
|
+ ". The test panel is disabled. Please restart "
|
|
+ "Cloud9 to retry.");
|
|
|
|
parse(data || "");
|
|
|
|
cb && cb();
|
|
|
|
ready = true;
|
|
emit.sticky("ready");
|
|
});
|
|
}
|
|
readConfig(function() {
|
|
watcher.watch(configPath);
|
|
watcher.on("change", function(e) {
|
|
if (e.path == configPath) {
|
|
readConfig(function() {
|
|
emit("updateConfig");
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
save.on("afterSave", function(e) {
|
|
if (e.path == configPath) {
|
|
parse(e.value);
|
|
emit("updateConfig");
|
|
}
|
|
}, plugin);
|
|
}
|
|
|
|
var drawnMenu = false;
|
|
function drawMenu() {
|
|
if (drawnMenu) return;
|
|
drawnMenu = true;
|
|
|
|
menus.addItemByPath("Run/Run Test", new ui.item({
|
|
command: "runfocussedtest"
|
|
}), 1250, plugin);
|
|
menus.addItemByPath("Run/Run Test with Code Coverage", new ui.item({
|
|
command: "runfocussedtestwithcoverage"
|
|
}), 1260, plugin);
|
|
}
|
|
|
|
var drawn = false;
|
|
function draw(opts) {
|
|
if (drawn) return;
|
|
drawn = true;
|
|
|
|
// Splitbox
|
|
var vbox = opts.aml.appendChild(new ui.vbox({
|
|
anchors: "0 0 0 0"
|
|
}));
|
|
|
|
// Toolbar
|
|
toolbar = vbox.appendChild(new ui.hbox({
|
|
id: "toolbar",
|
|
class: "toolbar-top",
|
|
align: "center",
|
|
edge: "0 2 0 0",
|
|
// padding: 3
|
|
// class: "fakehbox aligncenter debugger_buttons basic",
|
|
// style: "white-space:nowrap !important;"
|
|
style: "border-top:0"
|
|
}));
|
|
plugin.addElement(toolbar);
|
|
|
|
// Run Menu
|
|
var emptyLabel = new ui.label({ caption: "No Settings " });
|
|
mnuRun = new ui.menu({ class: "runner-config-menu" });
|
|
mnuRun.addEventListener("prop.visible", function(e) {
|
|
if (!e.value) return;
|
|
|
|
var runners = [], found = {};
|
|
|
|
for (var i = mnuRun.childNodes.length - 1; i >= 0; i--) {
|
|
mnuRun.removeChild(mnuRun.childNodes[i]);
|
|
}
|
|
|
|
if (focussedPanel.tree.selectedNode) {
|
|
focussedPanel.tree.selectedNodes.forEach(function(n) {
|
|
if (n.type == "all" || n.type == "root" || n.type == "runner") {
|
|
n.findAllNodes("runner").forEach(function(n) {
|
|
var runner = n.runner;
|
|
if (found[n.name]) return;
|
|
found[n.name] = true;
|
|
runners.push(runner);
|
|
});
|
|
}
|
|
else {
|
|
var runner = n.findRunner();
|
|
if (found[runner.name]) return;
|
|
found[runner.name] = true;
|
|
runners.push(runner);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (emit("showRunMenu", {
|
|
menu: mnuRun,
|
|
runners: runners
|
|
}) !== false && !runners.length) {
|
|
mnuRun.appendChild(emptyLabel);
|
|
return;
|
|
}
|
|
|
|
runners.forEach(function(runner) {
|
|
if (runner.form) {
|
|
if (!runner.meta.$label) {
|
|
runner.meta.$label = new ui.label({
|
|
caption: runner.root.label.toLowerCase(),
|
|
class: "runner-form-header"
|
|
});
|
|
}
|
|
mnuRun.appendChild(runner.meta.$label);
|
|
runner.form.attachTo(mnuRun);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Buttons
|
|
btnRun = ui.insertByIndex(toolbar, new ui.splitbutton({
|
|
caption: "Run",
|
|
icon: "run.png",
|
|
skinset: "default",
|
|
skin: "c9-menu-btn",
|
|
class: "runtestbtn stopped",
|
|
command: "runtest",
|
|
submenu: mnuRun
|
|
}), 100, plugin);
|
|
|
|
ui.insertByIndex(toolbar, new ui.button({
|
|
caption: "Clear",
|
|
skinset: "default",
|
|
skin: "c9-menu-btn",
|
|
command: "cleartestresults"
|
|
}), 100, plugin);
|
|
|
|
mnuSettings = new Menu({ items: [
|
|
new MenuItem({ caption: "Refresh", onclick: refresh }),
|
|
new Divider()
|
|
]}, plugin);
|
|
|
|
opts.aml.appendChild(new ui.button({
|
|
skin: "header-btn",
|
|
class: "panel-settings",
|
|
style: "top:46px",
|
|
submenu: mnuSettings.aml
|
|
}));
|
|
|
|
// Container
|
|
container = vbox.appendChild(new ui.bar({
|
|
style: "flex:1;display:flex;flex-direction: column;"
|
|
}));
|
|
|
|
emit.sticky("drawPanels", { html: container.$int, aml: container });
|
|
}
|
|
|
|
/***** Methods *****/
|
|
|
|
function registerTestRunner(runner) {
|
|
drawMenu();
|
|
|
|
runners.push(runner);
|
|
|
|
emit("register", { runner: runner });
|
|
}
|
|
|
|
function unregisterTestRunner(runner) {
|
|
runners.splice(runners.indexOf(runner), 1);
|
|
|
|
emit("unregister", { runner: runner });
|
|
}
|
|
|
|
function transformRunButton(type) {
|
|
if (!drawn) return;
|
|
btnRun.setAttribute("caption", type == "stop" ? "Stop" : "Run");
|
|
btnRun.setAttribute("command", type == "stop" ? "stoptest" : "runtest");
|
|
btnRun.setAttribute("class", "runtestbtn " + (type == "stop" ? "running" : "stopped"));
|
|
}
|
|
|
|
// TODO: https://github.com/tj/js-yaml/blob/master/lib/yaml.js
|
|
// OR: https://github.com/jeremyfa/yaml.js/blob/develop/dist/yaml.min.js
|
|
function convertToType(x) {
|
|
if (x.toLowerCase() == "true") return true;
|
|
if (x.toLowerCase() == "false") return false;
|
|
if (typeof parseFloat(x) == "number") return parseFloat(x);
|
|
throw new Error("Unknown Type");
|
|
}
|
|
function parse(data) {
|
|
if (!config) config = {};
|
|
|
|
var keepNewline, stack = [config], top = config, name, create, value;
|
|
|
|
data.split("\n").forEach(function(rawLine) {
|
|
// line = line.trim();
|
|
var line = rawLine.split("#")[0].trimRight();
|
|
|
|
if (line.match(/^\s*([\w-_ ]*):(\s?[|>]?)(.*)$/)) {
|
|
// stack.pop(); top = stack[stack.length - 1];
|
|
top = config; // Fucks use of stack, but will fix later
|
|
|
|
name = RegExp.$1;
|
|
create = true;
|
|
keepNewline = RegExp.$2 == "|"; // Not used
|
|
value = RegExp.$3;
|
|
|
|
if (value.trim()) {
|
|
top[name] = convertToType(value);
|
|
}
|
|
}
|
|
else if (line.match(/^\s*- (.*)$/)) {
|
|
if (create) {
|
|
stack.push(top = top[name] = {});
|
|
create = false;
|
|
}
|
|
|
|
top[RegExp.$1] = rawLine.replace(/^\s*- /, ""); // Not according to spec, but more useful
|
|
}
|
|
else if (line.match(/ {2}(.*)/)) {
|
|
if (create) {
|
|
top[name] = "";
|
|
stack.push(-1);
|
|
create = false;
|
|
}
|
|
|
|
top[name] += RegExp.$1 += "\n";
|
|
}
|
|
});
|
|
|
|
if (stack.pop() == -1 && typeof top[name] === "string")
|
|
top[name] = top[name].substr(0, top[name].length - 1); // Remove last \n of strings
|
|
|
|
return config;
|
|
}
|
|
|
|
function saveConfig(callback) {
|
|
var contents = "";
|
|
|
|
var item;
|
|
for (var prop in config) {
|
|
contents += prop + ":";
|
|
|
|
item = config[prop];
|
|
if (typeof item == "object") {
|
|
contents += "\n";
|
|
for (var name in item) {
|
|
contents += " - "
|
|
+ (typeof item[name] == "string" ? item[name] : name)
|
|
+ "\n";
|
|
}
|
|
}
|
|
else if (typeof item == "boolean") {
|
|
contents += " " + item.toString();
|
|
}
|
|
else if (typeof item == "number") {
|
|
contents += " " + item.toString();
|
|
}
|
|
else {
|
|
contents += " |\n";
|
|
contents += " " + item.toString().split("\n").join("\n ");
|
|
}
|
|
|
|
contents += "\n";
|
|
}
|
|
|
|
fs.writeFile(configPath, contents, callback);
|
|
|
|
emit("updateConfig");
|
|
}
|
|
|
|
function resize() {
|
|
emit("resize");
|
|
}
|
|
|
|
function refresh() {
|
|
emit("update");
|
|
}
|
|
|
|
function setCoverage(node) {
|
|
emit("coverage", { node: node });
|
|
}
|
|
|
|
function clearCoverage(node) {
|
|
emit("clearCoverage", { node: node });
|
|
}
|
|
|
|
function openTestConfigFile() {
|
|
tabManager.open({
|
|
path: configPath,
|
|
newOnError: true,
|
|
value: "excluded:\n - \n\nskipped:\n - \n",
|
|
focus: true
|
|
}, function() {});
|
|
}
|
|
|
|
/***** Lifecycle *****/
|
|
|
|
plugin.on("load", function() {
|
|
load();
|
|
});
|
|
plugin.on("draw", function(e) {
|
|
draw(e);
|
|
});
|
|
plugin.on("enable", function() {
|
|
|
|
});
|
|
plugin.on("disable", function() {
|
|
|
|
});
|
|
plugin.on("show", function(e) {
|
|
|
|
});
|
|
plugin.on("hide", function(e) {
|
|
|
|
});
|
|
plugin.on("unload", function() {
|
|
drawn = false;
|
|
drawnMenu = false;
|
|
toolbar = null;
|
|
lastTest = null;
|
|
container = null;
|
|
config = null;
|
|
ready = null;
|
|
runners = [];
|
|
btnRun = null;
|
|
focussedPanel = null;
|
|
mnuRun = null;
|
|
mnuSettings = null;
|
|
});
|
|
|
|
/***** Register and define API *****/
|
|
|
|
/**
|
|
*
|
|
*/
|
|
plugin.freezePublicAPI({
|
|
Coverage: Coverage,
|
|
File: File,
|
|
TestSet: TestSet,
|
|
Test: Test,
|
|
Node: Node,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
get ready() { return ready; },
|
|
|
|
/**
|
|
*
|
|
*/
|
|
get drawn() { return drawn; },
|
|
|
|
/**
|
|
*
|
|
*/
|
|
get config() { return config; },
|
|
|
|
/**
|
|
*
|
|
*/
|
|
get runners() { return runners; },
|
|
|
|
/**
|
|
*
|
|
*/
|
|
get settingsMenu() { return mnuSettings; },
|
|
|
|
/**
|
|
*
|
|
*/
|
|
get focussedPanel() { return focussedPanel; },
|
|
set focussedPanel(v) { focussedPanel = v; },
|
|
|
|
/**
|
|
*
|
|
*/
|
|
get lastTest() { return lastTest; },
|
|
set lastTest(v) { lastTest = v; },
|
|
|
|
/**
|
|
*
|
|
*/
|
|
saveConfig: saveConfig,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
resize: resize,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
refresh: refresh,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
register: registerTestRunner,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
unregister: unregisterTestRunner,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
setCoverage: setCoverage,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
clearCoverage: clearCoverage,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
transformRunButton: transformRunButton
|
|
});
|
|
|
|
register(null, {
|
|
test: plugin
|
|
});
|
|
}
|
|
});
|