mirror of
https://github.com/xibyte/jsketcher
synced 2026-02-09 00:43:22 +01:00
first approach to test framework UI
This commit is contained in:
parent
a2e90819b7
commit
9f3276fd87
13 changed files with 338 additions and 4 deletions
|
|
@ -3,7 +3,7 @@
|
|||
"version": "0.1.0",
|
||||
"description": "JS.Sketcher is a parametric 2D and 3D CAD modeler written in pure javascript",
|
||||
"scripts": {
|
||||
"start": "webpack-dev-server --content-base web/ --port 3000",
|
||||
"start": "webpack-dev-server --config webpack.config.dev.js --content-base web/ --port 3000",
|
||||
"pack": "webpack --config webpack.config.js --progress --profile --colors",
|
||||
"build": "grunt",
|
||||
"lint": "eslint web/app -c ./build/.eslintrc.json --ignore-path ./build/.eslintignore"
|
||||
|
|
@ -45,7 +45,7 @@
|
|||
"diff-match-patch": "1.0.0",
|
||||
"numeric": "1.2.6",
|
||||
"jwerty": "0.3.2",
|
||||
"mustache-loader": "0.3.3",
|
||||
"handlebars-loader": "1.4.0",
|
||||
"jquery": "2.1.0",
|
||||
"less": "2.7.1",
|
||||
"libtess": "1.2.2"
|
||||
|
|
|
|||
7
web/app/ui/helpers/eachInMap.js
Normal file
7
web/app/ui/helpers/eachInMap.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export default function( map, block ) {
|
||||
let out = '';
|
||||
Object.keys( map ).map(function( prop ) {
|
||||
out += block.fn( {key: prop, value: map[ prop ]} );
|
||||
});
|
||||
return out;
|
||||
};
|
||||
5
web/test/cases/arc-io.js
Normal file
5
web/test/cases/arc-io.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export default {
|
||||
testSaveLoad: function() {
|
||||
|
||||
}
|
||||
}
|
||||
7
web/test/cases/segment-io.js
Normal file
7
web/test/cases/segment-io.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import * as test from '../test'
|
||||
|
||||
export default {
|
||||
testSaveLoad: function() {
|
||||
test.fail('Nothing works');
|
||||
}
|
||||
}
|
||||
40
web/test/menu.js
Normal file
40
web/test/menu.js
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
//import {DefaultMouseEvent} from '../app/3d/ui/utils'
|
||||
|
||||
export class Menu {
|
||||
|
||||
constructor(actions) {
|
||||
//this.mouseInfo = new DefaultMouseEvent();
|
||||
this.actions = actions;
|
||||
this.popup = $('#popup-menu');
|
||||
$(document)
|
||||
.on('mousemove', (e) => this.mouseInfo = e)
|
||||
.on('click', (e) => this.popup.hide())
|
||||
.on('click', '.right-click-menu', (e) => this.onShowMenu(e, $(e.currentTarget)))
|
||||
.on('contextmenu', (e) => {
|
||||
const target = $(e.target).closest('.right-click-menu');
|
||||
if (target.length == 0) return true;
|
||||
return this.onShowMenu(e, target);
|
||||
});
|
||||
}
|
||||
|
||||
onShowMenu(e, target) {
|
||||
const popup = this.popup;
|
||||
popup.empty();
|
||||
const actions = target.data('menu').split(',').map(s => s.trim());
|
||||
for (let actionId of actions) {
|
||||
const action = this.actions[actionId];
|
||||
if (action) {
|
||||
popup.append($('<div>', {text: action.label, 'class': 'menu-item'}).click(() => {
|
||||
popup.hide();
|
||||
action.invoke(target)
|
||||
}))
|
||||
}
|
||||
}
|
||||
popup.show();
|
||||
popup.offset({
|
||||
left: e.pageX,
|
||||
top: e.pageY
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
18
web/test/runner.html
Normal file
18
web/test/runner.html
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
<script src="../static/test_runner.bundle.js"></script>
|
||||
<link rel="stylesheet" href="../lib/font-awesome/css/font-awesome.min.css?modeler">
|
||||
</head>
|
||||
<body>
|
||||
<div id="control-bar" class="page-row">
|
||||
<button id="run-button"><i class="fa fa-play"></i></button>
|
||||
<button id="pause-button"><i class="fa fa-pause"></i></button>
|
||||
<span id="status" style="float: right">0 run / 0 passed / 0 failures</span>
|
||||
</div>
|
||||
<iframe id="main-sandbox"></iframe>
|
||||
<div class="page-row" id="test-list">
|
||||
</div>
|
||||
<div id="popup-menu" style="position: absolute; display: none;"></div>
|
||||
</body>
|
||||
</html>
|
||||
127
web/test/runner.js
Normal file
127
web/test/runner.js
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
import './runner.less'
|
||||
import TestList from './tmpl/test-list.html'
|
||||
import '../app/utils/jqueryfy'
|
||||
import suites from './suites'
|
||||
import {Menu} from './menu'
|
||||
import {AssertionError} from './test'
|
||||
|
||||
$(() => {
|
||||
const runBtn = $('#run-button');
|
||||
const pauseBtn = $('#pause-button');
|
||||
|
||||
disableBtn(pauseBtn);
|
||||
|
||||
runBtn.click(() => {
|
||||
run();
|
||||
disableBtn(runBtn);
|
||||
enableBtn(pauseBtn);
|
||||
});
|
||||
|
||||
pauseBtn.click(() => {
|
||||
disableBtn(pauseBtn);
|
||||
enableBtn(runBtn);
|
||||
});
|
||||
console.log(suites);
|
||||
$('#test-list').html(TestList({suites}));
|
||||
new Menu(ACTIONS);
|
||||
});
|
||||
|
||||
|
||||
function runSuite(name) {
|
||||
const testCases = suites[name];
|
||||
let success = true;
|
||||
for (let testCase of testCases) {
|
||||
if (!runTestCase(testCase, name + ':' +testCase.name)) {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
updateIcon($('#suite-' + name), success);
|
||||
}
|
||||
|
||||
function runTestCase(testCase, caseId) {
|
||||
let success = true;
|
||||
for (let test of testCase.tests) {
|
||||
if (!runTest(test, caseId + ':' + test.name)) {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
updateIcon($('#case-' + caseId.replace(/:/g, '-')), success);
|
||||
}
|
||||
|
||||
function runTest(test, testId) {
|
||||
let success = true;
|
||||
let testDom = $('#test-' + testId.replace(/:/g, '-'));
|
||||
testDom.find('.status').hide();
|
||||
testDom.find('.progress').show();
|
||||
try {
|
||||
test();
|
||||
} catch (e) {
|
||||
success = false;
|
||||
if (e instanceof AssertionError) {
|
||||
testDom.find('.result').text(e.msg);
|
||||
}
|
||||
}
|
||||
testDom.find('.progress').hide();
|
||||
testDom.find('.status').show();
|
||||
updateIcon(testDom, success);
|
||||
return success;
|
||||
}
|
||||
|
||||
function run() {
|
||||
for (let suite of Object.keys(suites)) {
|
||||
runSuite(suite);
|
||||
}
|
||||
}
|
||||
|
||||
function pause() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
function updateIcon(dom, success) {
|
||||
dom.find('.status').addClass(success ? 'status-success' : 'status-fail');
|
||||
}
|
||||
|
||||
function findTestCaseById(id) {
|
||||
const suite = suites[id[0]];
|
||||
return suite.filter(tc => tc.name == id[1])[0];
|
||||
}
|
||||
|
||||
function findTestById(id) {
|
||||
const testCase = findTestCaseById(id);
|
||||
return testCase.tests.filter(t => t.name == id[2])[0];
|
||||
}
|
||||
|
||||
function disableBtn(btn) {
|
||||
btn.attr('disabled', 'disabled');
|
||||
}
|
||||
|
||||
function enableBtn(btn) {
|
||||
btn.removeAttr('disabled');
|
||||
}
|
||||
|
||||
|
||||
const ACTIONS = {
|
||||
RunSuite: {
|
||||
label: "Run Suite",
|
||||
invoke: (target) => runSuite(target.data('suiteName'))
|
||||
},
|
||||
|
||||
RunTestCase: {
|
||||
label: "Run Test Case",
|
||||
invoke: (target) => {
|
||||
var testCaseIdStr = target.data('testCaseId');
|
||||
const testCaseId = testCaseIdStr.split(':');
|
||||
runTestCase(findTestCaseById(testCaseId), testCaseIdStr);
|
||||
}
|
||||
},
|
||||
|
||||
RunTest: {
|
||||
label: "Run Test",
|
||||
invoke: (target) => {
|
||||
var testIdStr = target.data('testId');
|
||||
const testId = testIdStr.split(':');
|
||||
runTest(findTestById(testId), testIdStr)
|
||||
}
|
||||
}
|
||||
};
|
||||
58
web/test/runner.less
Normal file
58
web/test/runner.less
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
body {
|
||||
font-family: 'Helvetica Neue Light', HelveticaNeue-Light, 'Helvetica Neue', Helvetica, sans-serif;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#main-sandbox {
|
||||
border: 3px plum solid;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 60%;
|
||||
}
|
||||
|
||||
.page-row {
|
||||
margin: 1px 1px;
|
||||
}
|
||||
|
||||
button {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.status {
|
||||
color: #ccc;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
#popup-menu {
|
||||
padding: 5px 0 5px 0;
|
||||
border: 2px #777 solid;
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
padding: 2px 3px 2px 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
background-color: #0074D9;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.right-click-menu {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.status-success {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.status-fail {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.test-node .result {
|
||||
font-style: italic;
|
||||
font-size: 12px;
|
||||
color: #aaa;
|
||||
}
|
||||
27
web/test/suites.js
Normal file
27
web/test/suites.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
export default {
|
||||
SketcherIO: [
|
||||
TestCase('segment-io'),
|
||||
TestCase('arc-io')
|
||||
],
|
||||
|
||||
SketcherTools: [
|
||||
|
||||
],
|
||||
|
||||
Sketcher: [
|
||||
|
||||
],
|
||||
|
||||
ModellerOperations: [
|
||||
|
||||
],
|
||||
|
||||
};
|
||||
|
||||
function TestCase(name) {
|
||||
let tests = require('./cases/' + name).default;
|
||||
tests = Object.keys(tests).filter(key => key.startsWith('test')).map(key => tests[key]);
|
||||
return {
|
||||
name, tests
|
||||
}
|
||||
}
|
||||
15
web/test/test.js
Normal file
15
web/test/test.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
export function fail(msg, optionalMsg) {
|
||||
optionalMsg = (optionalMsg === undefined ? '' : ' ' + optionalMsg);
|
||||
throw new AssertionError(msg + optionalMsg);
|
||||
}
|
||||
|
||||
export function assertEquals(expected, actual, msg) {
|
||||
if (expected !== actual) {
|
||||
fail('assertEquals: Expected: ' + expected + ' but was ' + actual, msg);
|
||||
}
|
||||
}
|
||||
|
||||
export function AssertionError(msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
26
web/test/tmpl/test-list.html
Normal file
26
web/test/tmpl/test-list.html
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{{#eachInMap suites}}
|
||||
<div class="suite-node" id="suite-{{key}}">
|
||||
<span class="status"><i class="fa fa-circle"></i></span>
|
||||
<span class="right-click-menu" data-menu="RunSuite" data-suite-name="{{key}}">
|
||||
<b>{{key}}</b>
|
||||
</span>
|
||||
{{#value}}
|
||||
<div class="test-case-node" id="case-{{../key}}-{{name}}" style="margin-left: 15px;">
|
||||
<span class="status"><i class="fa fa-circle"></i></span>
|
||||
<span class="right-click-menu" data-menu="RunTestCase" data-test-case-id="{{../key}}:{{name}}">
|
||||
{{name}}
|
||||
</span>
|
||||
{{#tests}}
|
||||
<div class="test-node" id="test-{{../../key}}-{{../name}}-{{name}}" style="margin-left: 30px;">
|
||||
<span class="status"><i class="fa fa-circle"></i></span>
|
||||
<span class="progress" style="display: none"><i class="fa fa-cog fa-spin"></i></span>
|
||||
<span class="right-click-menu" data-menu="RunTest" data-test-id="{{../../key}}:{{../name}}:{{name}}">
|
||||
{{name}}
|
||||
<span class="result"></span>
|
||||
</span>
|
||||
</div>
|
||||
{{/tests}}
|
||||
</div>
|
||||
{{/value}}
|
||||
</div>
|
||||
{{/eachInMap}}
|
||||
4
webpack.config.dev.js
Normal file
4
webpack.config.dev.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
const config = require('./webpack.config.js');
|
||||
|
||||
config.entry.test_runner = ['./web/test/runner'];
|
||||
module.exports = config;
|
||||
|
|
@ -20,7 +20,7 @@ module.exports = {
|
|||
loaders: [{
|
||||
test: /\.js$/,
|
||||
loaders: ['babel'],
|
||||
include: path.join(__dirname, 'web/app')
|
||||
include: [path.join(__dirname, 'web/app'), path.join(__dirname, 'web/test')]
|
||||
}, {
|
||||
test: /\.css$/,
|
||||
loader: 'style!css'
|
||||
|
|
@ -31,7 +31,7 @@ module.exports = {
|
|||
},
|
||||
{
|
||||
test: /\.html$/,
|
||||
loader: 'mustache'
|
||||
loader: 'handlebars?helperDirs[]=' + __dirname + '/web/app/ui/helpers'
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue