Files
2024-09-27 01:01:17 +08:00

413 lines
9.3 KiB
JavaScript
Executable File

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();
});