first commit
This commit is contained in:
1
uebersicht-code/server/spec/.pydio
Normal file
1
uebersicht-code/server/spec/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
53020e3e-873e-430a-87dd-1ec10c273ad9
|
||||
1
uebersicht-code/server/spec/backend/.pydio
Normal file
1
uebersicht-code/server/spec/backend/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
4eca1fdb-0cf9-45cc-8990-38c4f95d5c6a
|
||||
20
uebersicht-code/server/spec/backend/SharedSocket_spec.js
Executable file
20
uebersicht-code/server/spec/backend/SharedSocket_spec.js
Executable file
@@ -0,0 +1,20 @@
|
||||
var test = require('tape');
|
||||
var WebSocket = require('ws');
|
||||
|
||||
var server = new WebSocket.Server({ port: 8890 });
|
||||
var sharedSocket = require('../../src/SharedSocket');
|
||||
var url = 'ws://localhost:8890';
|
||||
|
||||
test('subscribing listeners', (t) => {
|
||||
sharedSocket.onMessage((message) => {
|
||||
t.equal(message, 'yay');
|
||||
sharedSocket.close();
|
||||
server.close(() => t.end());
|
||||
});
|
||||
|
||||
sharedSocket.open(url);
|
||||
|
||||
server.on('connection', (ws) => {
|
||||
ws.send('yay');
|
||||
});
|
||||
});
|
||||
82
uebersicht-code/server/spec/backend/WidgetBundler_spec.js
Executable file
82
uebersicht-code/server/spec/backend/WidgetBundler_spec.js
Executable file
@@ -0,0 +1,82 @@
|
||||
const test = require('tape');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const WidgetBundler = require('../../src/WidgetBundler.js');
|
||||
const fixturePath = path.resolve(__dirname, '../test_widgets');
|
||||
const bundler = WidgetBundler();
|
||||
var callback = () => {};
|
||||
|
||||
test('bundling widgets', (t) => {
|
||||
const action = {
|
||||
type: 'added',
|
||||
filePath: path.join(fixturePath, 'widget-1.coffee'),
|
||||
id: 'widget-1',
|
||||
};
|
||||
|
||||
callback = (event) => {
|
||||
t.equal(event.type, 'added', 'it emits an "added" event');
|
||||
t.equal(typeof event.widget, 'object', 'it emits a widget object');
|
||||
t.equal(event.widget.id, 'widget-1', 'the widget object has an id');
|
||||
t.equal(
|
||||
event.widget.filePath,
|
||||
action.filePath,
|
||||
'the widget object contains the original file path',
|
||||
);
|
||||
t.equal(
|
||||
typeof event.widget.body,
|
||||
'string',
|
||||
'it also contains a string with the widget source code',
|
||||
);
|
||||
|
||||
const widget = eval(event.widget.body)('widget-1');
|
||||
t.equal(
|
||||
widget.command,
|
||||
'foo',
|
||||
'the source evals to a require function which returns the widget by id',
|
||||
);
|
||||
callback = () => {};
|
||||
t.end();
|
||||
};
|
||||
|
||||
bundler.push(action, (event) => callback(event));
|
||||
});
|
||||
|
||||
test('watching widgets', (t) => {
|
||||
callback = (event) => {
|
||||
t.equal(event.type, 'added', 'it emits another "added" event');
|
||||
t.equal(event.widget.id, 'widget-1', 'for the correct widget');
|
||||
t.equal(typeof event.widget.body, 'string', 'with the widget source code');
|
||||
t.end();
|
||||
};
|
||||
|
||||
fs.utimes(
|
||||
path.join(fixturePath, 'widget-1.coffee'),
|
||||
Date.now(),
|
||||
Date.now(),
|
||||
() => {},
|
||||
);
|
||||
});
|
||||
|
||||
test('removing widgets', (t) => {
|
||||
const action = {
|
||||
type: 'removed',
|
||||
filePath: path.join(fixturePath, 'widget-1.coffee'),
|
||||
id: 'widget-1',
|
||||
};
|
||||
|
||||
callback = (event) => {
|
||||
t.equal(event.type, 'removed', 'it emits a "removed" event');
|
||||
t.equal(event.id, 'widget-1', 'for the correct widget');
|
||||
callback = () => {};
|
||||
t.end();
|
||||
};
|
||||
|
||||
bundler.push(action, (event) => callback(event));
|
||||
});
|
||||
|
||||
test('closing', (t) => {
|
||||
bundler.close();
|
||||
t.pass('it closes');
|
||||
t.end();
|
||||
});
|
||||
78
uebersicht-code/server/spec/backend/bundleWidget_spec.js
Executable file
78
uebersicht-code/server/spec/backend/bundleWidget_spec.js
Executable file
@@ -0,0 +1,78 @@
|
||||
const test = require('tape');
|
||||
const path = require('path');
|
||||
const bundleWidget = require('../../src/bundleWidget');
|
||||
|
||||
const testDir = path.resolve(__dirname, path.join('..', 'test_widgets'));
|
||||
|
||||
test('bundling coffeescript widgets', (t) => {
|
||||
const widgetPath = path.join(testDir, 'widget-1.coffee');
|
||||
const bundle = bundleWidget('widget-id', widgetPath);
|
||||
|
||||
t.plan(2);
|
||||
t.ok(
|
||||
bundle.constructor.name === 'Browserify',
|
||||
'it returns a browserify bundle',
|
||||
);
|
||||
bundle.bundle((err, src) => {
|
||||
t.ok(
|
||||
!err && src && src.indexOf('command') > -1,
|
||||
'the source code it generates looks ok',
|
||||
);
|
||||
bundle.close();
|
||||
});
|
||||
});
|
||||
|
||||
test('bundling javascript widgets', (t) => {
|
||||
const widgetPath = path.join(testDir, 'widget-2.js');
|
||||
const bundle = bundleWidget('other-widget-id', widgetPath);
|
||||
|
||||
t.plan(2);
|
||||
t.ok(
|
||||
bundle.constructor.name === 'Browserify',
|
||||
'it returns a browserify bundle',
|
||||
);
|
||||
bundle.bundle((err, src) => {
|
||||
t.ok(
|
||||
!err && src && src.indexOf('command') > -1,
|
||||
'the source code looks ok',
|
||||
);
|
||||
bundle.close();
|
||||
});
|
||||
});
|
||||
|
||||
test('bundling jsx widgets', (t) => {
|
||||
const widgetPath = path.join(testDir, 'widget-3.jsx');
|
||||
const bundle = bundleWidget('widget-3', widgetPath);
|
||||
|
||||
t.plan(2);
|
||||
t.ok(
|
||||
bundle.constructor.name === 'Browserify',
|
||||
'it returns a browserify bundle',
|
||||
);
|
||||
bundle.bundle((err, src) => {
|
||||
t.ok(
|
||||
!err && src && src.indexOf('command') > -1,
|
||||
'the source code looks ok',
|
||||
);
|
||||
bundle.close();
|
||||
});
|
||||
});
|
||||
|
||||
test('bundling widgets with syntax errors', (t) => {
|
||||
const widgetPath = path.join(testDir, 'broken-widget.coffee');
|
||||
const bundle = bundleWidget('broken', widgetPath);
|
||||
|
||||
t.plan(2);
|
||||
t.ok(
|
||||
bundle.constructor.name === 'Browserify',
|
||||
'it returns a browserify bundle',
|
||||
);
|
||||
bundle.bundle((err, src) => {
|
||||
t.equal(
|
||||
err && err.message,
|
||||
'unexpected indentation while parsing file: ' + widgetPath,
|
||||
'it spits out an error when bundling',
|
||||
);
|
||||
bundle.close();
|
||||
});
|
||||
});
|
||||
94
uebersicht-code/server/spec/backend/command_server_spec.js
Executable file
94
uebersicht-code/server/spec/backend/command_server_spec.js
Executable file
@@ -0,0 +1,94 @@
|
||||
var test = require('tape');
|
||||
var connect = require('connect');
|
||||
var path = require('path');
|
||||
|
||||
var httpGet = require('../helpers/httpGet');
|
||||
var httpPost = require('../helpers/httpPost');
|
||||
var commandServer = require('../../src/command_server.coffee');
|
||||
|
||||
var workingDir = path.resolve(__dirname, path.join('..', 'test_widgets'));
|
||||
var server = connect().use(commandServer(workingDir)).listen(8887);
|
||||
|
||||
var url = 'http://localhost:8887/run/';
|
||||
|
||||
test('responding to POST /run/', (t) => {
|
||||
t.plan(3);
|
||||
|
||||
httpPost(url, 'echo', (res) => {
|
||||
t.equal(res.statusCode, 200, 'it reponds');
|
||||
});
|
||||
|
||||
httpPost('http://localhost:8887/foo/', 'echo', (res) => {
|
||||
t.equal(res.statusCode, 404, 'it ignores requests to other paths');
|
||||
});
|
||||
|
||||
httpGet(url, (res) => {
|
||||
t.equal(res.statusCode, 404, 'it ignores GET requests');
|
||||
});
|
||||
});
|
||||
|
||||
test('running commands', (t) => {
|
||||
t.plan(2);
|
||||
|
||||
httpPost(url, 'echo "yay"', (res, body) => {
|
||||
t.equal(body, 'yay\n', 'it runs commands');
|
||||
});
|
||||
|
||||
httpPost(url, 'pwd', (res, body) => {
|
||||
t.equal(
|
||||
body,
|
||||
workingDir + '\n',
|
||||
'it runs commands in the supplied working dir',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('shell type', (t) => {
|
||||
httpPost(url, 'echo $(shopt | grep login_shell)', (res, body) => {
|
||||
t.equal(body, 'login_shell off\n', 'it is not a login shell');
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('running broken commands', (t) => {
|
||||
t.plan(2);
|
||||
|
||||
httpPost(url, 'fake-command', (res, body) => {
|
||||
t.equal(res.statusCode, 500, 'it responds with a 500 code');
|
||||
t.equal(
|
||||
body,
|
||||
'bash: line 1: fake-command: command not found\n',
|
||||
'it responds with an error message',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('forwarding stderr', (t) => {
|
||||
t.plan(2);
|
||||
|
||||
httpPost(url, 'echo "yay" >&2', (res, body) => {
|
||||
t.equal(res.statusCode, 500, 'it responds with a 500 code');
|
||||
t.equal(body, 'yay\n', 'it sends stderr along');
|
||||
});
|
||||
});
|
||||
|
||||
test('closing', (t) => {
|
||||
server.close();
|
||||
t.pass('it closes');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('using a login shell', (t) => {
|
||||
server = connect().use(commandServer(workingDir, true)).listen(8887);
|
||||
|
||||
httpPost(url, 'echo $(shopt | grep login_shell)', (res, body) => {
|
||||
const lines = body.trim().split('\n');
|
||||
t.equal(
|
||||
lines[lines.length - 1],
|
||||
'login_shell on',
|
||||
'it indeed runs in a login shell',
|
||||
);
|
||||
server.close();
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
116
uebersicht-code/server/spec/backend/directory_watcher_spec.js
Executable file
116
uebersicht-code/server/spec/backend/directory_watcher_spec.js
Executable file
@@ -0,0 +1,116 @@
|
||||
var test = require('tape');
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var execSync = require('child_process').execSync;
|
||||
|
||||
var DirWatcher = require('../../src/directory_watcher.coffee');
|
||||
var fixturePath = path.resolve(__dirname, '../test_widgets');
|
||||
var newWidgetPath = path.join(fixturePath, 'new-widget.coffee');
|
||||
|
||||
var stopWatching;
|
||||
var callback;
|
||||
|
||||
const throwError = (err) => {
|
||||
if (err) throw err;
|
||||
};
|
||||
|
||||
test('files that are already present in the widget dir', (t) => {
|
||||
t.timeoutAfter(300);
|
||||
var expectedWidgets = [
|
||||
path.join(fixturePath, 'widget-1.coffee'),
|
||||
path.join(fixturePath, 'widget-2.js'),
|
||||
path.join(fixturePath, 'some-dir.widget', 'index-1.coffee'),
|
||||
path.join(fixturePath, 'broken-widget.coffee'),
|
||||
path.join(fixturePath, 'invalid-widget.coffee'),
|
||||
];
|
||||
|
||||
callback = (event) => {
|
||||
if (event.type !== 'added') {
|
||||
return;
|
||||
}
|
||||
var idx = expectedWidgets.indexOf(event.filePath);
|
||||
if (idx > -1) {
|
||||
expectedWidgets.splice(idx, 1);
|
||||
}
|
||||
|
||||
if (expectedWidgets.length === 0) {
|
||||
callback = () => {};
|
||||
t.pass('it emits an event for all widgets already in the folder');
|
||||
t.end();
|
||||
}
|
||||
};
|
||||
|
||||
stopWatching = DirWatcher(fixturePath, (event) => callback(event));
|
||||
});
|
||||
|
||||
test('adding files', (t) => {
|
||||
t.timeoutAfter(300);
|
||||
callback = (event) => {
|
||||
if (event.type === 'added' && event.filePath === newWidgetPath) {
|
||||
callback = () => {};
|
||||
t.pass('it emits an event for new files');
|
||||
t.equal(event.rootPath, fixturePath, 'the event includes the root path');
|
||||
t.end();
|
||||
}
|
||||
};
|
||||
fs.writeFile(newWidgetPath, "command: ''", throwError);
|
||||
});
|
||||
|
||||
test('removing files', (t) => {
|
||||
t.timeoutAfter(300);
|
||||
callback = (event) => {
|
||||
if (event.type === 'removed' && event.filePath === newWidgetPath) {
|
||||
callback = () => {};
|
||||
t.pass('it emits a removed event when a widget file is removed');
|
||||
t.equal(event.rootPath, fixturePath, 'the event includes the root path');
|
||||
t.end();
|
||||
}
|
||||
};
|
||||
fs.unlink(newWidgetPath, throwError);
|
||||
});
|
||||
|
||||
test('adding folders', (t) => {
|
||||
t.timeoutAfter(300);
|
||||
var aWidgetFolder = path.resolve(__dirname, '../tmp2');
|
||||
if (fs.existsSync(aWidgetFolder)) {
|
||||
execSync('rm -rf ' + aWidgetFolder);
|
||||
}
|
||||
|
||||
fs.mkdirSync(aWidgetFolder);
|
||||
fs.writeFileSync(path.join(aWidgetFolder, 'widget.js'), "command: 'yay'");
|
||||
|
||||
var expectedPath = path.join(fixturePath, 'another', 'widget.js');
|
||||
callback = (event) => {
|
||||
if (event.type === 'added' && event.filePath === expectedPath) {
|
||||
callback = () => {};
|
||||
t.pass('it emits an event when a subfolder containing a widget is added');
|
||||
t.end();
|
||||
}
|
||||
};
|
||||
fs.rename(aWidgetFolder, path.join(fixturePath, 'another'), throwError);
|
||||
});
|
||||
|
||||
test('removing folders', (t) => {
|
||||
t.timeoutAfter(300);
|
||||
var expectedPath = path.join(fixturePath, 'another', 'widget.js');
|
||||
callback = (event) => {
|
||||
if (event.type === 'removed' && event.filePath === expectedPath) {
|
||||
callback = () => {};
|
||||
t.pass(
|
||||
'it emits a removed event when a subfolder containing a ' +
|
||||
'widget is removed',
|
||||
);
|
||||
t.end();
|
||||
}
|
||||
};
|
||||
|
||||
var newPath = path.resolve(__dirname, '../tmp3');
|
||||
fs.renameSync(path.join(fixturePath, 'another'), newPath);
|
||||
execSync('rm -rf ' + newPath);
|
||||
});
|
||||
|
||||
test('stopping', (t) => {
|
||||
stopWatching();
|
||||
t.pass('it can be stopped');
|
||||
t.end();
|
||||
});
|
||||
33
uebersicht-code/server/spec/backend/dispatch_spec.js
Executable file
33
uebersicht-code/server/spec/backend/dispatch_spec.js
Executable file
@@ -0,0 +1,33 @@
|
||||
var test = require('tape');
|
||||
var WebSocket = require('ws');
|
||||
|
||||
var server = new WebSocket.Server({ port: 8888 });
|
||||
var url = 'ws://localhost:8888';
|
||||
var sharedSocket = require('../../src/SharedSocket');
|
||||
var dispatch = require('../../src/dispatch');
|
||||
|
||||
test('queuing up messages', (t) => {
|
||||
expectedMessages = ['a', 'b'];
|
||||
|
||||
server.on('connection', (ws) => {
|
||||
ws.on('message', (message) => {
|
||||
parsed = JSON.parse(message);
|
||||
|
||||
var idx = expectedMessages.indexOf(parsed);
|
||||
if (idx > -1) {
|
||||
expectedMessages.splice(idx, 1);
|
||||
}
|
||||
|
||||
if (expectedMessages.length === 0) {
|
||||
t.pass('it queues up messages and sends them once the socket opens');
|
||||
server.close(() => t.end());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
dispatch('a');
|
||||
dispatch('b');
|
||||
sharedSocket.open(url);
|
||||
});
|
||||
|
||||
|
||||
26
uebersicht-code/server/spec/backend/listen_spec.js
Executable file
26
uebersicht-code/server/spec/backend/listen_spec.js
Executable file
@@ -0,0 +1,26 @@
|
||||
var test = require('tape');
|
||||
var WebSocket = require('ws');
|
||||
|
||||
var server = new WebSocket.Server({ port: 8889 });
|
||||
var sharedSocket = require('../../src/SharedSocket');
|
||||
var listen = require('../../src/listen');
|
||||
|
||||
test('listen', (t) => {
|
||||
sharedSocket.open('ws://localhost:8889');
|
||||
|
||||
listen((message) => {
|
||||
t.looseEqual(
|
||||
message,
|
||||
{ type: 'YASS', payload: 'yay' },
|
||||
'it calls listeners with deserialized messages'
|
||||
);
|
||||
server.close(() => t.end());
|
||||
});
|
||||
|
||||
server.on('connection', (ws) => {
|
||||
ws.send(JSON.stringify({
|
||||
type: 'YASS',
|
||||
payload: 'yay',
|
||||
}));
|
||||
});
|
||||
});
|
||||
115
uebersicht-code/server/spec/backend/reducer_spec.js
Executable file
115
uebersicht-code/server/spec/backend/reducer_spec.js
Executable file
@@ -0,0 +1,115 @@
|
||||
var test = require('tape');
|
||||
var reduce = require('../../src/reducer');
|
||||
|
||||
|
||||
test('WIDGET_ADDED', (t) => {
|
||||
var action = {
|
||||
type: 'WIDGET_ADDED',
|
||||
payload: { id: 'foo', error: 'oh no', filePath: '/foo/' },
|
||||
};
|
||||
var newState = reduce({ widgets: {} }, action);
|
||||
|
||||
t.looseEqual(
|
||||
newState.widgets,
|
||||
{ foo: { id: 'foo', error: 'oh no', filePath: '/foo/' } },
|
||||
'it adds new widgets'
|
||||
);
|
||||
|
||||
t.ok(
|
||||
typeof newState.settings === 'object',
|
||||
'it creates a new settings hash if none exists'
|
||||
);
|
||||
|
||||
t.looseEqual(
|
||||
newState.settings.foo, {
|
||||
showOnAllScreens: true,
|
||||
showOnMainScreen: false,
|
||||
showOnSelectedScreens: false,
|
||||
hidden: false,
|
||||
screens: [],
|
||||
},
|
||||
'it initializes settings for a widget'
|
||||
);
|
||||
|
||||
action = {
|
||||
type: 'WIDGET_ADDED',
|
||||
payload: { id: 'foo', body: 'yay', filePath: '/foo/' },
|
||||
};
|
||||
newState = reduce(newState, action);
|
||||
|
||||
t.looseEqual(
|
||||
newState.widgets,
|
||||
{ foo: { id: 'foo', body: 'yay', filePath: '/foo/' } },
|
||||
'it updates existing widgets'
|
||||
);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('WIDGET_REMOVED', (t) => {
|
||||
var action = { type: 'WIDGET_REMOVED', payload: 'foo' };
|
||||
var state = { widgets: {} };
|
||||
var newState = reduce(state, action);
|
||||
t.equal(state, newState, 'it ignores non existing widgets');
|
||||
|
||||
newState = reduce({
|
||||
widgets: { foo: {}, bar: {}},
|
||||
}, action);
|
||||
t.looseEqual(newState.widgets, {bar: {}}, 'it removes existing widgets');
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
||||
test('WIDGET_SETTINGS_CHANGED', (t) => {
|
||||
var action = {
|
||||
type: 'WIDGET_SETTINGS_CHANGED',
|
||||
payload: { id: 'foo', settings: { a: 'b' } },
|
||||
};
|
||||
|
||||
newState = reduce({ settings: {} }, action);
|
||||
t.looseEqual(
|
||||
newState.settings,
|
||||
{ foo: { a: 'b' } },
|
||||
'it applies new settings'
|
||||
);
|
||||
|
||||
newState = reduce({ settings: { bar: {} } }, action);
|
||||
t.looseEqual(
|
||||
newState.settings,
|
||||
{ foo: { a: 'b' }, bar: {}},
|
||||
'it merges with existing settings'
|
||||
);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('WIDGET_SET_TO_HIDE / SHOW', (t) => {
|
||||
var action = { type: 'WIDGET_SET_TO_HIDE', payload: 'bar' };
|
||||
var state = {
|
||||
settings: {
|
||||
foo: { hidden: false, some: 'other', stuff: 1 },
|
||||
bar: { hidden: false, many: 'other', things: 42 },
|
||||
},
|
||||
};
|
||||
var newState = reduce(state, action);
|
||||
t.looseEqual(
|
||||
state.settings,
|
||||
{
|
||||
foo: { hidden: false, some: 'other', stuff: 1 },
|
||||
bar: { hidden: false, many: 'other', things: 42 },
|
||||
},
|
||||
'it hides widgets'
|
||||
);
|
||||
|
||||
action = { type: 'WIDGET_SET_TO_SHOW', payload: 'bar' };
|
||||
newState = reduce(newState, action);
|
||||
t.looseEqual(
|
||||
state.settings,
|
||||
{
|
||||
foo: { hidden: false, some: 'other', stuff: 1 },
|
||||
bar: { hidden: false, many: 'other', things: 42 },
|
||||
},
|
||||
'it shows widgets'
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
102
uebersicht-code/server/spec/backend/resolveWidget_spec.js
Executable file
102
uebersicht-code/server/spec/backend/resolveWidget_spec.js
Executable file
@@ -0,0 +1,102 @@
|
||||
const test = require('tape');
|
||||
const resolveWidget = require('../../src/resolveWidget.js');
|
||||
|
||||
test('resolving widget actions from file events', (t) => {
|
||||
const action = resolveWidget({
|
||||
type: 'added',
|
||||
filePath: '/widget/dir/widget File name.js',
|
||||
rootPath: '/widget/dir/',
|
||||
});
|
||||
|
||||
t.plan(3);
|
||||
t.equal(action.id, 'widget_File_name-js', 'it derives a widget id');
|
||||
t.equal(
|
||||
action.filePath, '/widget/dir/widget File name.js',
|
||||
'it contains the original file path'
|
||||
);
|
||||
|
||||
const action2 = resolveWidget({
|
||||
type: 'removed',
|
||||
filePath: '/widget/dir/widget File name.js',
|
||||
rootPath: '/widget/dir/',
|
||||
});
|
||||
|
||||
t.ok(
|
||||
action.type === 'added' && action2.type === 'removed',
|
||||
'it passes on the action type'
|
||||
);
|
||||
});
|
||||
|
||||
test('separating widgets from non-wigets', (t) => {
|
||||
var action = resolveWidget({
|
||||
filePath: '/widget/dir/file.js',
|
||||
rootPath: '/widget/dir/',
|
||||
});
|
||||
t.ok(!!action, 'it accepts js files');
|
||||
|
||||
action = resolveWidget({
|
||||
filePath: '/widget/dir/file.coffee',
|
||||
rootPath: '/widget/dir/',
|
||||
});
|
||||
t.ok(!!action, 'it accepts coffee files');
|
||||
|
||||
action = resolveWidget({
|
||||
filePath: '/widget/dir/file.jsx',
|
||||
rootPath: '/widget/dir/',
|
||||
});
|
||||
t.ok(!!action, 'it accepts jsx files');
|
||||
|
||||
action = resolveWidget({
|
||||
filePath: '/widget/dir/file.txt',
|
||||
rootPath: '/widget/dir/',
|
||||
});
|
||||
t.ok(!action, 'it ignores other files');
|
||||
|
||||
action = resolveWidget({
|
||||
filePath: '/widget/dir/node_modules/file.js',
|
||||
rootPath: '/widget/dir/',
|
||||
});
|
||||
t.ok(!action, 'it ignores files inside node_modules');
|
||||
|
||||
action = resolveWidget({
|
||||
filePath: '/widget/dir/src/file.js',
|
||||
rootPath: '/widget/dir/',
|
||||
});
|
||||
t.ok(!action, 'it ignores files inside a src dir');
|
||||
|
||||
action = resolveWidget({
|
||||
filePath: '/widget/dir/lib/file.js',
|
||||
rootPath: '/widget/dir/',
|
||||
});
|
||||
t.ok(!action, 'it ignores files inside a lib dir');
|
||||
|
||||
action = resolveWidget({
|
||||
filePath: '/widget/dir/some/other/dir/file.js',
|
||||
rootPath: '/widget/dir/',
|
||||
});
|
||||
t.ok(!!action, 'it detects widgets in any other dir');
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('deriving widget ids', (t) => {
|
||||
var action = resolveWidget({
|
||||
type: 'added',
|
||||
filePath: '/widget/dir/spaces in the Name.js',
|
||||
rootPath: '/widget/dir/',
|
||||
});
|
||||
t.equal(
|
||||
action.id, 'spaces_in____the__Name-js', 'it replaces spaces with underscores'
|
||||
);
|
||||
|
||||
action = resolveWidget({
|
||||
type: 'added',
|
||||
filePath: '/widget/dir/some-dir/widget.js',
|
||||
rootPath: '/widget/dir/',
|
||||
});
|
||||
t.equal(
|
||||
action.id, 'some-dir-widget-js', 'it replaces slashes with dashes'
|
||||
);
|
||||
|
||||
t.end();
|
||||
});
|
||||
36
uebersicht-code/server/spec/backend/validateWidget_spec.js
Executable file
36
uebersicht-code/server/spec/backend/validateWidget_spec.js
Executable file
@@ -0,0 +1,36 @@
|
||||
var test = require('tape');
|
||||
var validateWidget = require('../../src/validateWidget');
|
||||
|
||||
test('checking for empty implementations', (t) => {
|
||||
var issues = validateWidget();
|
||||
t.ok(
|
||||
issues.indexOf('empty implementation') > -1,
|
||||
'it does not allow them'
|
||||
);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('checking for commands', (t) => {
|
||||
var issues = validateWidget({});
|
||||
t.ok(
|
||||
issues.indexOf('no command given') > -1,
|
||||
'it complains if when there is no command'
|
||||
);
|
||||
|
||||
issues = validateWidget({ refreshFrequency: false });
|
||||
t.ok(
|
||||
issues.indexOf('no command given') === -1,
|
||||
'it allows no commands when refreshFrequency is false'
|
||||
);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('valid widgets', (t) => {
|
||||
var issues = validateWidget({
|
||||
command: 'yay'
|
||||
});
|
||||
t.ok(issues.length === 0, 'it finds no issues');
|
||||
t.end();
|
||||
});
|
||||
83
uebersicht-code/server/spec/backend/widgetify_spec.js
Executable file
83
uebersicht-code/server/spec/backend/widgetify_spec.js
Executable file
@@ -0,0 +1,83 @@
|
||||
var test = require('tape');
|
||||
var widgetify = require('../../src/widgetify');
|
||||
var through = require('through2');
|
||||
|
||||
function grabOutput(then) {
|
||||
var output = '';
|
||||
return through(
|
||||
(chunk, enc, next) => { output += chunk; next(); },
|
||||
(next) => { then(output); next(); }
|
||||
);
|
||||
}
|
||||
|
||||
test('transforming valid widgets', (t) => {
|
||||
var transform = widgetify('path/', { id: 'foo' });
|
||||
var src = `
|
||||
var color = '#ff';
|
||||
var stuff = 1+2;
|
||||
color = color + 'f';
|
||||
({
|
||||
foo: 14,
|
||||
style: 'color: ' + color,
|
||||
refreshFrequency: '2s'
|
||||
})
|
||||
`;
|
||||
|
||||
transform.pipe( grabOutput((transformed) => {
|
||||
const module = {};
|
||||
new Function('module', transformed)(module);
|
||||
|
||||
t.ok(
|
||||
typeof module.exports === 'object',
|
||||
'it assigns the last object expression to module.exports'
|
||||
);
|
||||
t.equal(
|
||||
module.exports.id, 'foo',
|
||||
'it adds the widget id'
|
||||
);
|
||||
t.equal(
|
||||
module.exports.refreshFrequency, 2000,
|
||||
'it parses string refresh frequencies'
|
||||
);
|
||||
t.equal(
|
||||
module.exports.css, '#foo {\n color: #fff;\n}\n',
|
||||
'it parses and scopes styles, including interpolated variables'
|
||||
);
|
||||
t.equal(
|
||||
module.exports.style, undefined,
|
||||
'it cleans up the style property'
|
||||
);
|
||||
|
||||
t.end();
|
||||
}));
|
||||
|
||||
transform.write(src);
|
||||
transform.end();
|
||||
});
|
||||
|
||||
test('transforming a widget with a syntax error', (t) => {
|
||||
var transform = widgetify('path/', { id: 'foo' });
|
||||
var src = `
|
||||
({
|
||||
foo: 14,
|
||||
style: 'color: ' + color,
|
||||
refreshFrequency: '2s'
|
||||
})
|
||||
`;
|
||||
|
||||
transform
|
||||
.on('error', (e) => {
|
||||
t.pass('it emits an error');
|
||||
t.ok(
|
||||
e.name === 'ReferenceError' && e.message === 'color is not defined',
|
||||
'the error looks ok'
|
||||
);
|
||||
t.end();
|
||||
})
|
||||
.pipe(grabOutput((transformed) => {
|
||||
t.ok(!transformed, 'and there is no outout');
|
||||
}));
|
||||
|
||||
transform.write(src);
|
||||
transform.end();
|
||||
});
|
||||
1
uebersicht-code/server/spec/frontend/.pydio
Normal file
1
uebersicht-code/server/spec/frontend/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
0a8f99dc-3af2-4fb0-8999-8ba5fba7c2f9
|
||||
195
uebersicht-code/server/spec/frontend/render_spec.js
Executable file
195
uebersicht-code/server/spec/frontend/render_spec.js
Executable file
@@ -0,0 +1,195 @@
|
||||
var test = require('tape');
|
||||
|
||||
var render = require('../../src/render');
|
||||
var domEl = document.createElement('div');
|
||||
document.body.appendChild(domEl); // needed to use selectors
|
||||
|
||||
function buildWidget(id) {
|
||||
return {
|
||||
id: id,
|
||||
implementation: {id: id, refreshFrequency: false},
|
||||
mtime: new Date(),
|
||||
};
|
||||
}
|
||||
|
||||
var state = {
|
||||
widgets: {
|
||||
foo: buildWidget('foo'),
|
||||
bar: buildWidget('bar'),
|
||||
},
|
||||
settings: {},
|
||||
screens: ['123'],
|
||||
};
|
||||
|
||||
test('rendering a clean slate', (t) => {
|
||||
render(state, '123', domEl);
|
||||
t.equal(domEl.childNodes.length, 2, 'it renders 2 widgets');
|
||||
t.ok(!!domEl.querySelector('#foo'), 'it renders widget foo');
|
||||
t.ok(!!domEl.querySelector('#bar'), 'it renders widget bar');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('rendering new widgets', (t) => {
|
||||
state.widgets.baz = buildWidget('baz');
|
||||
|
||||
render(state, '123', domEl);
|
||||
t.equal(domEl.childNodes.length, 3, 'it renders 3 widgets');
|
||||
t.ok(!!domEl.querySelector('#foo'), 'it renders widget foo');
|
||||
t.ok(!!domEl.querySelector('#bar'), 'it renders widget bar');
|
||||
t.ok(!!domEl.querySelector('#baz'), 'it renders widget baz');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('destroying removed widgets', (t) => {
|
||||
delete state.widgets.bar;
|
||||
|
||||
render(state, '123', domEl);
|
||||
t.equal(domEl.childNodes.length, 2, 'it leaves 2');
|
||||
t.ok(!!domEl.querySelector('#foo'), 'it does not remove widget foo');
|
||||
t.ok(!!domEl.querySelector('#baz'), 'it does not remove widget baz');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('rendering widgets that are visible on all screens', (t) => {
|
||||
state.settings.baz = {
|
||||
showOnAllScreens: true,
|
||||
};
|
||||
|
||||
render(state, '123', domEl);
|
||||
t.equal(domEl.childNodes.length, 2, 'it renders them');
|
||||
|
||||
render(state, '678', domEl);
|
||||
t.equal(domEl.childNodes.length, 2, 'it renders them on any screen');
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('rendering widgets that are pinned to the main screen', (t) => {
|
||||
state.settings.baz = {
|
||||
showOnAllScreens: false,
|
||||
showOnMainScreen: true,
|
||||
};
|
||||
|
||||
render(state, '123', domEl);
|
||||
t.equal(
|
||||
domEl.childNodes.length,
|
||||
2,
|
||||
'it renders them if current screen is main',
|
||||
);
|
||||
|
||||
render(state, '156', domEl);
|
||||
t.equal(
|
||||
domEl.childNodes.length,
|
||||
1,
|
||||
'it does not render them if current screen is mot main',
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('rendering widgets that are pinned to selected screens', (t) => {
|
||||
state.settings.baz = {
|
||||
showOnAllScreens: false,
|
||||
showOnMainScreen: false,
|
||||
showOnSelectedScreens: true,
|
||||
};
|
||||
|
||||
render(state, '123', domEl);
|
||||
t.equal(
|
||||
domEl.childNodes.length,
|
||||
1,
|
||||
'it does not render them if no screen is selected',
|
||||
);
|
||||
|
||||
state.settings.baz.screens = ['567'];
|
||||
render(state, '123', domEl);
|
||||
t.equal(
|
||||
domEl.childNodes.length,
|
||||
1,
|
||||
'it does not render them if current screen is not in selected screens',
|
||||
);
|
||||
|
||||
state.settings.baz.screens = ['567', '123'];
|
||||
render(state, '123', domEl);
|
||||
t.equal(
|
||||
domEl.childNodes.length,
|
||||
2,
|
||||
'it renders them if current screen is in selected screens',
|
||||
);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('performance when re-rendering', (t) => {
|
||||
var prevNode = domEl.querySelector('#foo');
|
||||
render(state, '123', domEl);
|
||||
var newNode = domEl.querySelector('#foo');
|
||||
|
||||
t.ok(
|
||||
prevNode === newNode,
|
||||
'it does not re-render nodes if it does not need to',
|
||||
);
|
||||
|
||||
prevNode = domEl.querySelector('#foo');
|
||||
|
||||
// new mtime
|
||||
state.widgets.foo = buildWidget('foo');
|
||||
render(state, '123', domEl);
|
||||
newNode = domEl.querySelector('#foo');
|
||||
|
||||
t.ok(prevNode !== newNode, 'it does re-render nodes when it has to');
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('rendering background widgets', (t) => {
|
||||
var state = {
|
||||
widgets: {
|
||||
foo: buildWidget('foo'),
|
||||
},
|
||||
settings: {foo: {inBackground: true, showOnAllScreens: true}},
|
||||
screens: ['123'],
|
||||
};
|
||||
|
||||
render(state, '123', domEl);
|
||||
t.equal(
|
||||
domEl.childNodes.length,
|
||||
1,
|
||||
'it renders them if window.isBackground is not set',
|
||||
);
|
||||
|
||||
window.isBackground = false;
|
||||
render(state, '123', domEl);
|
||||
t.equal(
|
||||
domEl.childNodes.length,
|
||||
0,
|
||||
'it does not render them if window.background is false',
|
||||
);
|
||||
|
||||
window.isBackground = true;
|
||||
render(state, '123', domEl);
|
||||
t.equal(
|
||||
domEl.childNodes.length,
|
||||
1,
|
||||
'it renders them if window.background is true',
|
||||
);
|
||||
|
||||
window.isBackground = false;
|
||||
|
||||
var state = {
|
||||
widgets: {
|
||||
foo: buildWidget('foo'),
|
||||
},
|
||||
settings: {foo: {showOnAllScreens: true}},
|
||||
screens: ['123'],
|
||||
};
|
||||
|
||||
render(state, '123', domEl);
|
||||
t.equal(
|
||||
domEl.childNodes.length,
|
||||
1,
|
||||
'it renders them in foreground if setting is undefined',
|
||||
);
|
||||
|
||||
window.isBackground = undefined;
|
||||
t.end();
|
||||
});
|
||||
412
uebersicht-code/server/spec/frontend/widget_spec.js
Executable file
412
uebersicht-code/server/spec/frontend/widget_spec.js
Executable file
@@ -0,0 +1,412 @@
|
||||
var test = require('tape');
|
||||
var sinon = require('sinon');
|
||||
var tosource = require('tosource');
|
||||
var Widget = require('../../src/Widget.js');
|
||||
|
||||
function makeFakeServer() {
|
||||
var server = sinon.fakeServer.create();
|
||||
server.respondToRun = function respondToRun(body) {
|
||||
server.respondWith('POST', '/run/', [
|
||||
status,
|
||||
{ 'Content-Type': 'text/plain' },
|
||||
body,
|
||||
]);
|
||||
};
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
function buildWidget(impl) {
|
||||
return Widget({implementation: impl});
|
||||
}
|
||||
|
||||
test('widget creation', (t) => {
|
||||
var widget = buildWidget({ command: '', id: 'foo', css: 'background: red' });
|
||||
var el = widget.create();
|
||||
|
||||
t.ok(
|
||||
el && el.tagName === 'DIV',
|
||||
'it creates a wrapper dom element for a widget'
|
||||
);
|
||||
|
||||
t.ok(
|
||||
!!el.querySelector('#foo'),
|
||||
'it creates an element for the widget itself'
|
||||
);
|
||||
|
||||
var style = el.querySelector('style');
|
||||
t.ok(!!style, "it includes a style tag for the widget's css");
|
||||
t.ok(
|
||||
style.innerHTML.indexOf('background: red') > -1,
|
||||
"the tag includes the widget's style"
|
||||
);
|
||||
|
||||
widget.destroy();
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('defaults', (t) => {
|
||||
var implementation = buildWidget({ command: '', id: 'foo', css: '' })
|
||||
.implementation();
|
||||
|
||||
t.equal(
|
||||
implementation.refreshFrequency, 1000,
|
||||
'it sets the refresh frequency to 1s'
|
||||
);
|
||||
|
||||
t.equal(
|
||||
typeof implementation.render, 'function',
|
||||
'it provides a default render function'
|
||||
);
|
||||
|
||||
t.ok(
|
||||
implementation.render('stuff') === 'stuff',
|
||||
'the default render method returns what is passed to it'
|
||||
);
|
||||
|
||||
t.equal(
|
||||
typeof implementation.afterRender, 'function',
|
||||
'it provides a default afterRender function'
|
||||
);
|
||||
|
||||
implementation = buildWidget({
|
||||
id: 'foo',
|
||||
command: '',
|
||||
css: '',
|
||||
refreshFrequency: 42,
|
||||
render: () => 'render!',
|
||||
afterRender: () => 'afterRender!',
|
||||
}).implementation();
|
||||
|
||||
t.equal(
|
||||
implementation.refreshFrequency, 42,
|
||||
"it doesn't override the refreshFrequency"
|
||||
);
|
||||
|
||||
t.equal(
|
||||
implementation.render(), 'render!',
|
||||
"it doesn't override the render method"
|
||||
);
|
||||
|
||||
t.equal(
|
||||
implementation.afterRender(), 'afterRender!',
|
||||
"it doesn't override the afterRender method"
|
||||
);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('internal api', (t) => {
|
||||
var api = buildWidget({ command: '', id: 'foo', css: '' })
|
||||
.internalApi();
|
||||
|
||||
t.equal(typeof api.start, 'function', 'it has a start method');
|
||||
t.equal(typeof api.stop, 'function', 'it has a stop method');
|
||||
t.equal(typeof api.refresh, 'function', 'it has a refresh method');
|
||||
t.equal(typeof api.run, 'function', 'it has a run method');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('running commands', (t) => {
|
||||
var clock = sinon.useFakeTimers();
|
||||
var instance = buildWidget({ id: 'foo', command: 'command', css: ''});
|
||||
var widget = instance.implementation();
|
||||
var server = makeFakeServer();
|
||||
|
||||
server.respondToRun('some output');
|
||||
server.autoRespond = true;
|
||||
server.respondImmediately = true;
|
||||
var requests = server.requests;
|
||||
|
||||
instance.create();
|
||||
|
||||
t.equal(
|
||||
server.requests[0].requestBody, 'command',
|
||||
'it sends the command to the server'
|
||||
);
|
||||
|
||||
clock.tick(1000);
|
||||
t.ok(
|
||||
requests.length === 2 && requests[1].requestBody === 'command',
|
||||
'for every tick'
|
||||
);
|
||||
|
||||
widget.command = 'new command';
|
||||
clock.tick(1000);
|
||||
t.equal(
|
||||
requests[2].requestBody, 'new command',
|
||||
'when updating command it sends the new command to the server'
|
||||
);
|
||||
|
||||
t.end();
|
||||
instance.destroy();
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
test('manually running commands', (t) => {
|
||||
var widget = buildWidget({ id: 'foo', command: '', css: ''}).internalApi();
|
||||
var server = makeFakeServer();
|
||||
server.respondToRun('some output');
|
||||
|
||||
widget.run('some command', (err, output) => {
|
||||
t.equal(null, err, 'sends no errors');
|
||||
t.equal(output, 'some output', 'it responds with the output');
|
||||
server.restore();
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.equal(
|
||||
server.requests[0].requestBody, 'some command',
|
||||
'it sends the command to the server'
|
||||
);
|
||||
|
||||
server.respond();
|
||||
});
|
||||
|
||||
test('standard rendering', (t) => {
|
||||
var clock = sinon.useFakeTimers();
|
||||
var instance = buildWidget({
|
||||
id: 'foo',
|
||||
command: '',
|
||||
refreshFrequency: 100,
|
||||
numRenders: 0,
|
||||
render(out) {
|
||||
this.numRenders++;
|
||||
return `rendered ${this.numRenders} ${out}`;
|
||||
},
|
||||
});
|
||||
|
||||
var server = makeFakeServer();
|
||||
server.respondToRun('Hello World!');
|
||||
server.autoRespond = true;
|
||||
server.respondImmediately = true;
|
||||
|
||||
var domEl = instance.create();
|
||||
var contentEl = domEl.querySelector('.widget');
|
||||
t.equal(
|
||||
contentEl.textContent, 'rendered 1 Hello World!',
|
||||
'it does an initial render'
|
||||
);
|
||||
|
||||
clock.tick(100);
|
||||
t.equal(
|
||||
contentEl.textContent, 'rendered 2 Hello World!',
|
||||
'it renders after the first tick'
|
||||
);
|
||||
|
||||
clock.tick(100);
|
||||
t.equal(
|
||||
contentEl.textContent, 'rendered 3 Hello World!',
|
||||
'it renders after the second tick'
|
||||
);
|
||||
|
||||
var internalApi = instance.internalApi();
|
||||
internalApi.stop();
|
||||
clock.tick(100);
|
||||
clock.tick(100);
|
||||
t.equal(
|
||||
contentEl.textContent, 'rendered 3 Hello World!',
|
||||
'it pauses when stopped'
|
||||
);
|
||||
|
||||
internalApi.start();
|
||||
t.equal(
|
||||
contentEl.textContent, 'rendered 4 Hello World!',
|
||||
'it resumes when started'
|
||||
);
|
||||
|
||||
t.equal(
|
||||
contentEl.textContent, 'rendered 4 Hello World!',
|
||||
'it continues rendering'
|
||||
);
|
||||
|
||||
instance.destroy();
|
||||
server.restore();
|
||||
clock.restore();
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('rendering when refreshFrequency is false', (t) => {
|
||||
var clock = sinon.useFakeTimers();
|
||||
var instance = buildWidget({
|
||||
id: 'foo',
|
||||
refreshFrequency: false,
|
||||
numRenders: 0,
|
||||
render(out) {
|
||||
this.numRenders++;
|
||||
return `rendered ${this.numRenders} times`;
|
||||
},
|
||||
});
|
||||
|
||||
var server = makeFakeServer();
|
||||
server.respondToRun('');
|
||||
server.autoRespond = true;
|
||||
server.respondImmediately = true;
|
||||
|
||||
var domEl = instance.create();
|
||||
var contentEl = domEl.querySelector('.widget');
|
||||
t.equal(
|
||||
contentEl.textContent, 'rendered 1 times',
|
||||
'it does an initial render'
|
||||
);
|
||||
|
||||
clock.tick(1000);
|
||||
clock.tick(1000);
|
||||
t.equal(
|
||||
contentEl.textContent, 'rendered 1 times',
|
||||
'it does\'t render after that'
|
||||
);
|
||||
|
||||
clock.restore();
|
||||
instance.destroy();
|
||||
server.restore();
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('refreshing manually', (t) => {
|
||||
var instance = buildWidget({
|
||||
id: 'bar',
|
||||
refreshFrequency: false,
|
||||
command: 'refresh me',
|
||||
css: '',
|
||||
});
|
||||
var domEl = instance.create();
|
||||
var server = makeFakeServer();
|
||||
var internalApi = instance.internalApi();
|
||||
|
||||
server.respondToRun('some output');
|
||||
internalApi.start();
|
||||
|
||||
internalApi.refresh();
|
||||
t.equal(server.requests[0].requestBody, 'refresh me');
|
||||
|
||||
server.respond();
|
||||
t.equal(
|
||||
domEl.textContent.replace(/^\s+/g, ''), 'some output',
|
||||
'it renders the output to the DOM'
|
||||
);
|
||||
|
||||
instance.destroy();
|
||||
server.restore();
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('afterRender hook', (t) => {
|
||||
var server = makeFakeServer();
|
||||
var clock = sinon.useFakeTimers();
|
||||
|
||||
server.respondToRun('Hello World!');
|
||||
server.autoRespond = true;
|
||||
|
||||
var instance = buildWidget({
|
||||
id: 'fred',
|
||||
command: '',
|
||||
refreshFrequency: 100,
|
||||
numCalls: 0,
|
||||
afterRender(el) {
|
||||
this.numCalls++;
|
||||
el.innerHTML = `called ${this.numCalls} times`;
|
||||
},
|
||||
});
|
||||
|
||||
var domEl = instance.create();
|
||||
clock.tick(300);
|
||||
t.equal(
|
||||
domEl.querySelector('.widget').textContent, 'called 3 times',
|
||||
'it get\'s called after every render, with the content dom element'
|
||||
);
|
||||
|
||||
instance.destroy();
|
||||
server.restore();
|
||||
clock.restore();
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('update', (t) => {
|
||||
var instance = buildWidget({
|
||||
id: 'fred',
|
||||
command: '',
|
||||
refreshFrequency: false,
|
||||
update(output, el) {
|
||||
el.innerHTML = `content: ${el.textContent}, output: ${output}`;
|
||||
},
|
||||
});
|
||||
|
||||
var server = makeFakeServer();
|
||||
server.respondToRun('stuff');
|
||||
server.autoRespond = true;
|
||||
server.respondImmediately = true;
|
||||
|
||||
var domEl = instance.create();
|
||||
var contentEl = domEl.querySelector('.widget');
|
||||
t.equal(
|
||||
contentEl.textContent, 'content: stuff, output: stuff',
|
||||
'it calls update after rendering, with the output and widget dom el'
|
||||
);
|
||||
|
||||
instance.destroy();
|
||||
server.restore();
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('error handling', (t) => {
|
||||
var instance = buildWidget({
|
||||
id: 'foo',
|
||||
refreshFrequency: false,
|
||||
render() { throw new Error('something went sorry'); },
|
||||
update() { throw new Error('should not call update when render fails'); },
|
||||
});
|
||||
|
||||
var domEl = instance.create();
|
||||
t.equal(
|
||||
domEl.querySelector('.widget').textContent, 'something went sorry\n',
|
||||
'it catches and renders errors in render()'
|
||||
);
|
||||
|
||||
instance.destroy();
|
||||
instance = buildWidget({
|
||||
id: 'foo',
|
||||
refreshFrequency: false,
|
||||
update() { throw new Error('ohoh'); },
|
||||
});
|
||||
|
||||
domEl = instance.create();
|
||||
t.equal(
|
||||
domEl.querySelector('.widget').textContent, 'ohoh\n',
|
||||
'it catches and renders errors in update()'
|
||||
);
|
||||
|
||||
instance.destroy();
|
||||
instance = buildWidget({
|
||||
id: 'foo',
|
||||
command: 'yay',
|
||||
refreshFrequency: 100,
|
||||
});
|
||||
|
||||
var clock = sinon.useFakeTimers();
|
||||
var server = sinon.fakeServer.create({ respondImmediately: true });
|
||||
server.respondWith('POST', '/run/', [ 500, {}, 'oh noez!']);
|
||||
server.respondImmediately = true;
|
||||
|
||||
domEl = instance.create();
|
||||
server.respond();
|
||||
|
||||
t.equal(
|
||||
domEl.querySelector('.widget').textContent, 'oh noez!\n',
|
||||
'it renders command errors'
|
||||
);
|
||||
|
||||
server.respondWith('POST', '/run/', [ 200, {}, 'all good']);
|
||||
clock.tick(100);
|
||||
server.respond();
|
||||
|
||||
t.equal(
|
||||
domEl.querySelector('.widget').textContent, 'all good',
|
||||
'it recovers from command errors'
|
||||
);
|
||||
|
||||
clock.restore();
|
||||
server.restore();
|
||||
instance.destroy();
|
||||
t.end();
|
||||
});
|
||||
1
uebersicht-code/server/spec/helpers/.pydio
Normal file
1
uebersicht-code/server/spec/helpers/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
11e03a6d-84be-4539-a4b3-5cd819ff4112
|
||||
12
uebersicht-code/server/spec/helpers/httpGet.js
Executable file
12
uebersicht-code/server/spec/helpers/httpGet.js
Executable file
@@ -0,0 +1,12 @@
|
||||
http = require('http');
|
||||
|
||||
module.exports = function httpGet(url, callback) {
|
||||
var buffer = '';
|
||||
|
||||
http.get(url, function(res) {
|
||||
res.setEncoding('utf8');
|
||||
res.on('data', (chunk) => buffer += chunk );
|
||||
res.on('end', () => callback(res, buffer) );
|
||||
});
|
||||
};
|
||||
|
||||
18
uebersicht-code/server/spec/helpers/httpPost.js
Executable file
18
uebersicht-code/server/spec/helpers/httpPost.js
Executable file
@@ -0,0 +1,18 @@
|
||||
var http = require('http');
|
||||
var URL = require('url');
|
||||
|
||||
module.exports = function httpPost(url, postData, callback) {
|
||||
var options = URL.parse(url);
|
||||
options.method = 'POST';
|
||||
options.headers = { 'Content-Length': postData.length };
|
||||
|
||||
var req = http.request(options, (res) => {
|
||||
var buffer = '';
|
||||
res.setEncoding('utf8');
|
||||
res.on('data', (chunk) => buffer += chunk);
|
||||
res.on('end', () => callback(res, buffer));
|
||||
});
|
||||
|
||||
req.write(postData);
|
||||
req.end();
|
||||
};
|
||||
1
uebersicht-code/server/spec/test_files/.pydio
Normal file
1
uebersicht-code/server/spec/test_files/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
f57a5179-eb69-450a-b864-836643c3a65b
|
||||
1
uebersicht-code/server/spec/test_files/WidgetSettings.json
Executable file
1
uebersicht-code/server/spec/test_files/WidgetSettings.json
Executable file
@@ -0,0 +1 @@
|
||||
{"spec-test_widgets-broken-widget-coffee":{"showOnAllScreens":true,"showOnMainScreen":false,"showOnSelectedScreens":false,"hidden":false,"screens":[]},"spec-test_widgets-widget-2-js":{"showOnAllScreens":true,"showOnMainScreen":false,"showOnSelectedScreens":false,"hidden":false,"screens":[]},"spec-test_widgets-widget-1-coffee":{"showOnAllScreens":true,"showOnMainScreen":false,"showOnSelectedScreens":false,"hidden":false,"screens":[]},"spec-test_widgets-invalid-widget-coffee":{"showOnAllScreens":true,"showOnMainScreen":false,"showOnSelectedScreens":false,"hidden":false,"screens":[]},"spec-test_widgets-some-dir-widget-index-1-coffee":{"showOnAllScreens":true,"showOnMainScreen":false,"showOnSelectedScreens":false,"hidden":false,"screens":[]}}
|
||||
1
uebersicht-code/server/spec/test_widgets/.pydio
Normal file
1
uebersicht-code/server/spec/test_widgets/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
126b9e6a-6167-4ab9-8404-6c546f66fba6
|
||||
5
uebersicht-code/server/spec/test_widgets/broken-widget.coffee
Executable file
5
uebersicht-code/server/spec/test_widgets/broken-widget.coffee
Executable file
@@ -0,0 +1,5 @@
|
||||
command: ""
|
||||
|
||||
render: ->
|
||||
'this is a broken widget'
|
||||
'unexpected indentation'
|
||||
0
uebersicht-code/server/spec/test_widgets/garbage.exe
Executable file
0
uebersicht-code/server/spec/test_widgets/garbage.exe
Executable file
3
uebersicht-code/server/spec/test_widgets/invalid-widget.coffee
Executable file
3
uebersicht-code/server/spec/test_widgets/invalid-widget.coffee
Executable file
@@ -0,0 +1,3 @@
|
||||
refreshFrequency: 1000
|
||||
|
||||
render: (output) -> output
|
||||
@@ -0,0 +1 @@
|
||||
3d8f802f-b31a-4e65-b7df-87a88645cd2c
|
||||
2
uebersicht-code/server/spec/test_widgets/some-dir.widget/index-1.coffee
Executable file
2
uebersicht-code/server/spec/test_widgets/some-dir.widget/index-1.coffee
Executable file
@@ -0,0 +1,2 @@
|
||||
command: "do stuff"
|
||||
refreshFrequency: 3000
|
||||
BIN
uebersicht-code/server/spec/test_widgets/test.jpg
Executable file
BIN
uebersicht-code/server/spec/test_widgets/test.jpg
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
14
uebersicht-code/server/spec/test_widgets/widget-1.coffee
Executable file
14
uebersicht-code/server/spec/test_widgets/widget-1.coffee
Executable file
@@ -0,0 +1,14 @@
|
||||
command: "foo"
|
||||
|
||||
refreshFrequency: 1000
|
||||
|
||||
style: """
|
||||
bottom: 10px
|
||||
left: 100px
|
||||
|
||||
div
|
||||
font-size: 12px
|
||||
"""
|
||||
|
||||
render: (output) ->
|
||||
output
|
||||
2
uebersicht-code/server/spec/test_widgets/widget-2.js
Executable file
2
uebersicht-code/server/spec/test_widgets/widget-2.js
Executable file
@@ -0,0 +1,2 @@
|
||||
command: "bar",
|
||||
refreshFrequency: 1020
|
||||
13
uebersicht-code/server/spec/test_widgets/widget-3.jsx
Executable file
13
uebersicht-code/server/spec/test_widgets/widget-3.jsx
Executable file
@@ -0,0 +1,13 @@
|
||||
export const command = "echo Hello World!"
|
||||
|
||||
export const refreshFrequency = 5000 // ms
|
||||
|
||||
export const render = ({ output }) => (
|
||||
<h1>{output}</h1>
|
||||
)
|
||||
|
||||
export const className = `
|
||||
left: 20px
|
||||
top: 20px
|
||||
color: #fff
|
||||
`
|
||||
Reference in New Issue
Block a user