<commit_before>var LinksEditor=function(e){self=this,self.input=e,self.links=e.val().length>0?JSON.parse(e.val()):[],self.init()};LinksEditor.prototype.factory={},LinksEditor.prototype.generateItem=function(e){var e=self.factory.linkItem(e);return self.factory.linkItemDOM(e)},LinksEditor.prototype.init=function(){var e=$('

'),i=$('

+

');i.on("click",function(){e.append(self.generateItem())});for(var t in self.links)e.append(self.generateItem(self.links[t]));i.insertAfter(self.input),e.insertAfter(self.input),e.sortable({axis:"y",handle:".drag",stop:self.handleLinksChange}),self.container=e},LinksEditor.prototype.handleLinksChange=function(){$(this).hasClass("remove")&&$(this).closest(".links-editor-item").remove();var e=$(self.container).find(".links-editor-item"),i=[];e.length>0&&e.map(function(e,t){var n={id:$(t).data("id"),title:$(t).find("input").eq(0).val(),url:$(t).find("input").eq(1).val(),order:e,enabled:$(t).find('input[type="checkbox"]:checked').length>0};n.title&&n.url&&i.push(n)}),self.input.val(JSON.stringify(i))},LinksEditor.prototype.factory.linkItem=function(e){return e?e:{id:"",title:"",url:"",order:"",enabled:1}},LinksEditor.prototype.factory.linkItemDOM=function(e){var i={title:$('input[name="trans-links-editor-title"]').val(),url:$('input[name="trans-links-editor-url"]').val(),enabled:$('input[name="trans-links-editor-enabled"]').val()},t=$('
		<input class="form-control input-sm" placeholder="'+i.title+'" type="text" value="'+e.title+'">
<input class="form-control input-sm" placeholder="'+i.url+'" type="text" value="'+e.url+'">

		<input type="checkbox" value="1"> '+i.enabled+"
");return 1==e.enabled&&t.find('input[type="checkbox"]').prop("checked","checked"),t.find("input").on("keyup click",self.handleLinksChange),t.find(".remove").on("click",self.handleLinksChange),t},$.fn.loadLinksEditor=function(){new LinksEditor(this)},$(document).ready(function(){var e=".links-editor";$(e).length>0&&$(e).map(function(){$(this).loadLinksEditor()})});<commit_msg>Fix hidden links editor<commit_after>var LinksEditor=function(e){self=this,self.input=e,self.links=e.val().length>0?JSON.parse(e.val()):[],self.init()};LinksEditor.prototype.factory={},LinksEditor.prototype.generateItem=function(e){var e=self.factory.linkItem(e);return self.factory.linkItemDOM(e)},LinksEditor.prototype.init=function(){this.input.parents(".form-group").removeClass("hide");var e=$('

'),i=$('

+

');i.on("click",function(){e.append(self.generateItem())});for(var t in self.links)e.append(self.generateItem(self.links[t]));i.insertAfter(self.input),e.insertAfter(self.input),e.sortable({axis:"y",handle:".drag",stop:self.handleLinksChange}),self.container=e},LinksEditor.prototype.handleLinksChange=function(){$(this).hasClass("remove")&&$(this).closest(".links-editor-item").remove();var e=$(self.container).find(".links-editor-item"),i=[];e.length>0&&e.map(function(e,t){var n={id:$(t).data("id"),title:$(t).find("input").eq(0).val(),url:$(t).find("input").eq(1).val(),order:e,enabled:$(t).find('input[type="checkbox"]:checked').length>0};n.title&&n.url&&i.push(n)}),self.input.val(JSON.stringify(i))},LinksEditor.prototype.factory.linkItem=function(e){return e?e:{id:"",title:"",url:"",order:"",enabled:1}},LinksEditor.prototype.factory.linkItemDOM=function(e){var i={title:$('input[name="trans-links-editor-title"]').val(),url:$('input[name="trans-links-editor-url"]').val(),enabled:$('input[name="trans-links-editor-enabled"]').val()},t=$('
		<input class="form-control input-sm" placeholder="'+i.title+'" type="text" value="'+e.title+'">
<input class="form-control input-sm" placeholder="'+i.url+'" type="text" value="'+e.url+'">

		<input type="checkbox" value="1"> '+i.enabled+"
");return 1==e.enabled&&t.find('input[type="checkbox"]').prop("checked","checked"),t.find("input").on("keyup click",self.handleLinksChange),t.find(".remove").on("click",self.handleLinksChange),t},$.fn.loadLinksEditor=function(){new LinksEditor(this)},$(document).ready(function(){var e=".links-editor";$(e).length>0&&$(e).map(function(){console.log($(this)),$(this).loadLinksEditor()})});<|endoftext|>


<commit_before>const test = require('ava');
const {spawn} = require('child_process');
const WebSocket = require('ws');
const WebSocketServer = require('ws').Server;
const ReconnectingWebSocket = require('..');

const PORT = 50123;
const PORT_UNRESPONSIVE = 50124;
const URL = `ws://localhost:${PORT}`;

test.beforeEach(() => {
    global.WebSocket = WebSocket;
});

test.afterEach(() => {
    delete global.WebSocket;
});

test('throws with invalid constructor', t => {
    delete global.WebSocket;
    t.throws(() => {
        new ReconnectingWebSocket(URL, undefined, {WebSocket: 123});
    });
});

test('throws with missing constructor', t => {
    delete global.WebSocket;
    t.throws(() => {
        new ReconnectingWebSocket(URL, undefined);
    });
});

test('throws if not created with `new`', t => {
    t.throws(() => {
        ReconnectingWebSocket(URL, undefined);
    }, TypeError);
});

test.cb('global WebSocket is used if available', t => {
    const ws = new ReconnectingWebSocket(URL, undefined, {
        maxRetries: 0,
    });
    ws.onerror = () => {
        t.true(ws._ws instanceof WebSocket);
        t.end();
    };
});

test.cb('send is ignored when not ready', t => {
    const ws = new ReconnectingWebSocket(URL, undefined, {
        maxRetries: 0,
    });
    ws.send('message');
    ws.onerror = () => {
        t.pass();
        t.end();
    };
});

test.cb('getters when not ready', t => {
    const ws = new ReconnectingWebSocket(URL, undefined, {
        maxRetries: 0,
    });
    t.is(ws.bufferedAmount, 0);
    t.is(ws.protocol, '');
    t.is(ws.url, '');
    t.is(ws.extensions, '');
    t.is(ws.binaryType, 'blob');

    ws.onerror = () => {
        t.pass();
        t.end();
    };
});

test.cb('debug', t => {
    const log = console.log;
    console.log = () => t.pass();

    const ws = new ReconnectingWebSocket(URL, undefined, {
        maxRetries: 0,
        debug: true,
    });

    ws.onerror = () => {
        ws._options.debug = false;
        console.log = log;
        t.end();
    };
});

test.cb('pass WebSocket via options', t => {
    delete global.WebSocket;
    const ws = new ReconnectingWebSocket(URL, undefined, {
        WebSocket,
        maxRetries: 0,
    });
    ws.onerror = () => {
        t.true(ws._ws instanceof WebSocket);
        t.end();
    };
});

test('URL provider', async t => {
    const url = 'example.com';
    const ws = new ReconnectingWebSocket(URL, undefined, {
        maxRetries: 0,
    });
    t.is(await ws._getNextUrl(url), url, 'string');
    t.is(await ws._getNextUrl(() => url), url, '() -> string');
    t.is(await ws._getNextUrl(() => Promise.resolve(url)), url, '() -> Promise<string>');

    try {
        await ws._getNextUrl(123);
        t.fail();
    } catch (e) {
        t.pass();
    }

    try {
        await ws._getNextUrl(() => 123);
        t.fail();
    } catch (e) {
        t.pass();
    }
});

test('connection status constants', t => {
    const ws = new ReconnectingWebSocket(URL, null, {maxRetries: 0});

    t.is(ReconnectingWebSocket.CONNECTING, 0);
    t.is(ReconnectingWebSocket.OPEN, 1);
    t.is(ReconnectingWebSocket.CLOSING, 2);
    t.is(ReconnectingWebSocket.CLOSED, 3);

    t.is(ws.CONNECTING, 0);
    t.is(ws.OPEN, 1);
    t.is(ws.CLOSING, 2);
    t.is(ws.CLOSED, 3);
    ws.close();
});

const maxRetriesTest = (count, t) => {
    const ws = new ReconnectingWebSocket(URL, null, {
        maxRetries: count,
        maxReconnectionDelay: 200,
    });
    t.plan(count + 1);

    ws.addEventListener('error', event => {
        t.pass();
        if (ws.retryCount === count) {
            setTimeout(() => t.end(), 500);
        }
        if (ws.retryCount > count) {
            t.fail(`too many retries: ${ws.retryCount}`);
        }
    });
};

test.cb('max retries: 0', t => maxRetriesTest(0, t));
test.cb('max retries: 1', t => maxRetriesTest(1, t));
test.cb('max retries: 5', t => maxRetriesTest(5, t));

test.cb('level0 event listeners are kept after reconnect', t => {
    const ws = new ReconnectingWebSocket(URL, null, {
        maxRetries: 4,
        reconnectionDelayFactor: 1.2,
        maxReconnectionDelay: 20,
        minReconnectionDelay: 10,
    });

    const handleOpen = () => {};
    const handleClose = () => {};
    const handleMessage = () => {};
    const handleError = () => {
        t.is(ws.onopen, handleOpen);
        t.is(ws.onclose, handleClose);
        t.is(ws.onmessage, handleMessage);
        t.is(ws.onerror, handleError);
        if (ws.retryCount === 4) {
            t.end();
        }
    };

    ws.onopen = handleOpen;
    ws.onclose = handleClose;
    ws.onmessage = handleMessage;
    ws.onerror = handleError;
});

test.cb('level2 event listeners', t => {
    const anyProtocol = 'foobar';
    const wss = new WebSocketServer({port: PORT});
    const ws = new ReconnectingWebSocket(URL, anyProtocol, {});

    ws.addEventListener('open', () => {
        t.is(ws.protocol, anyProtocol);
        t.is(ws.extensions, '');
        t.is(ws.bufferedAmount, 0);
        ws.close();
    });

    const fail = () => {
        t.fail();
    };
    ws.addEventListener('unknown1', fail);
    ws.addEventListener('open', fail);
    ws.addEventListener('open', fail);
    ws.removeEventListener('open', fail);
    ws.removeEventListener('unknown2', fail);

    ws.addEventListener('close', () => {
        wss.close();
        setTimeout(() => t.end(), 500);
    });
});

test.cb('connection timeout', t => {
    const proc = spawn('node', [`${__dirname}/unresponsive-server.js`, PORT_UNRESPONSIVE, 5000]);
    t.plan(2);

    let lock = false;
    proc.stdout.on('data', d => {
        if (lock) return;
        lock = true;

        const ws = new ReconnectingWebSocket(`ws://localhost:${PORT_UNRESPONSIVE}`, null, {
            minReconnectionDelay: 50,
            connectionTimeout: 500,
            maxRetries: 1,
        });

        ws.addEventListener('error', event => {
            t.is(event.message, 'TIMEOUT');
            if (ws.retryCount === 1) {
                setTimeout(() => t.end(), 1000);
            }
        });

        ws.addEventListener('close', event => {
            console.log('>>>> CLOSE', event.message);
        });
    });
});

test.cb('getters', t => {
    const anyProtocol = 'foobar';
    const wss = new WebSocketServer({port: PORT});
    const ws = new ReconnectingWebSocket(URL, anyProtocol, {maxReconnectionDelay: 100});

    ws.addEventListener('open', () => {
        t.is(ws.protocol, anyProtocol);
        t.is(ws.extensions, '');
        t.is(ws.bufferedAmount, 0);
        t.is(ws.binaryType, 'nodebuffer');
        ws.close();
    });

    ws.addEventListener('close', () => {
        wss.close();
        setTimeout(() => t.end(), 500);
    });
});

test.cb('binaryType', t => {
    const wss = new WebSocketServer({port: PORT});
    const ws = new ReconnectingWebSocket(URL, undefined, {minReconnectionDelay: 0});

    t.is(ws.binaryType, 'blob');
    ws.binaryType = 'arraybuffer';
    ws.addEventListener('open', () => {
        t.is(ws.binaryType, 'arraybuffer', 'assigned after open');
        ws.binaryType = 'nodebuffer';
        t.is(ws.binaryType, 'nodebuffer');
        ws.close();
    });

    ws.addEventListener('close', () => {
        wss.close();
        setTimeout(() => t.end(), 500);
    });
});

test.cb('calling to close multiple times', t => {
    const wss = new WebSocketServer({port: PORT});
    const ws = new ReconnectingWebSocket(URL, undefined, {});

    ws.addEventListener('open', () => {
        ws.close();
        ws.close();
        ws.close();
    });

    ws.addEventListener('close', () => {
        wss.close();
        setTimeout(() => t.end(), 500);
    });
});

test.cb('calling to reconnect when not ready', t => {
    const wss = new WebSocketServer({port: PORT});
    const ws = new ReconnectingWebSocket(URL, undefined, {});
    ws.reconnect();
    ws.reconnect();

    ws.addEventListener('open', () => {
        ws.close();
    });

    ws.addEventListener('close', () => {
        wss.close();
        setTimeout(() => t.end(), 500);
    });
});

test.cb('connect, send, receive, close', t => {
    const anyMessageText = 'hello';
    const anyProtocol = 'foobar';

    const wss = new WebSocketServer({port: PORT});
    wss.on('connection', ws => {
        ws.on('message', msg => {
            ws.send(msg);
        });
    });
    wss.on('error', () => {
        t.fail();
    });

    t.plan(7);

    const ws = new ReconnectingWebSocket(URL, anyProtocol, {
        minReconnectionDelay: 100,
        maxReconnectionDelay: 200,
    });
    t.is(ws.readyState, ws.CONNECTING);

    ws.addEventListener('open', () => {
        t.is(ws.protocol, anyProtocol);
        t.is(ws.readyState, ws.OPEN);
        ws.send(anyMessageText);
    });

    ws.addEventListener('message', msg => {
        t.is(msg.data, anyMessageText);
        ws.close(1000, '');
        t.is(ws.readyState, ws.CLOSING);
    });

    ws.addEventListener('close', () => {
        t.is(ws.readyState, ws.CLOSED);
        t.is(ws.url, URL);
        wss.close();
        setTimeout(() => t.end(), 1000);
    });
});

test.cb('connect, send, receive, reconnect', t => {
    const anyMessageText = 'hello';
    const anyProtocol = 'foobar';

    const wss = new WebSocketServer({port: PORT});
    wss.on('connection', ws => {
        ws.on('message', msg => {
            ws.send(msg);
        });
    });

    const totalRounds = 3;
    let currentRound = 0;

    // 6 = 3 * 2 open
    // 8 = 2 * 3 message + 2 reconnect
    // 7 = 2 * 3 close + 1 closed
    t.plan(21);

    const ws = new ReconnectingWebSocket(URL, anyProtocol, {
        minReconnectionDelay: 100,
        maxReconnectionDelay: 200,
    });

    ws.onopen = () => {
        currentRound++;
        t.is(ws.protocol, anyProtocol);
        t.is(ws.readyState, ws.OPEN);
        ws.send(anyMessageText);
    };

    ws.onmessage = msg => {
        t.is(msg.data, anyMessageText);
        if (currentRound < totalRounds) {
            ws.reconnect(1000, 'reconnect');
            t.is(ws.retryCount, 0);
        } else {
            ws.close(1000, 'close');
        }
        t.is(ws.readyState, ws.CLOSING);
    };

    ws.addEventListener('close', event => {
        t.is(ws.url, URL);
        if (currentRound >= totalRounds) {
            t.is(ws.readyState, ws.CLOSED);
            wss.close();
            setTimeout(() => t.end(), 1000);
            t.is(event.reason, 'close');
        } else {
            t.is(event.reason, 'reconnect');
        }
    });
});
<commit_msg>add test 'immediatly-failed connection should not timeout'<commit_after>const test = require('ava');
const {spawn} = require('child_process');
const WebSocket = require('ws');
const WebSocketServer = require('ws').Server;
const ReconnectingWebSocket = require('..');

const PORT = 50123;
const PORT_UNRESPONSIVE = 50124;
const URL = `ws://localhost:${PORT}`;

test.beforeEach(() => {
    global.WebSocket = WebSocket;
});

test.afterEach(() => {
    delete global.WebSocket;
});

test('throws with invalid constructor', t => {
    delete global.WebSocket;
    t.throws(() => {
        new ReconnectingWebSocket(URL, undefined, {WebSocket: 123});
    });
});

test('throws with missing constructor', t => {
    delete global.WebSocket;
    t.throws(() => {
        new ReconnectingWebSocket(URL, undefined);
    });
});

test('throws if not created with `new`', t => {
    t.throws(() => {
        ReconnectingWebSocket(URL, undefined);
    }, TypeError);
});

test.cb('global WebSocket is used if available', t => {
    const ws = new ReconnectingWebSocket(URL, undefined, {
        maxRetries: 0,
    });
    ws.onerror = () => {
        t.true(ws._ws instanceof WebSocket);
        t.end();
    };
});

test.cb('send is ignored when not ready', t => {
    const ws = new ReconnectingWebSocket(URL, undefined, {
        maxRetries: 0,
    });
    ws.send('message');
    ws.onerror = () => {
        t.pass();
        t.end();
    };
});

test.cb('getters when not ready', t => {
    const ws = new ReconnectingWebSocket(URL, undefined, {
        maxRetries: 0,
    });
    t.is(ws.bufferedAmount, 0);
    t.is(ws.protocol, '');
    t.is(ws.url, '');
    t.is(ws.extensions, '');
    t.is(ws.binaryType, 'blob');

    ws.onerror = () => {
        t.pass();
        t.end();
    };
});

test.cb('debug', t => {
    const log = console.log;
    console.log = () => t.pass();

    const ws = new ReconnectingWebSocket(URL, undefined, {
        maxRetries: 0,
        debug: true,
    });

    ws.onerror = () => {
        ws._options.debug = false;
        console.log = log;
        t.end();
    };
});

test.cb('pass WebSocket via options', t => {
    delete global.WebSocket;
    const ws = new ReconnectingWebSocket(URL, undefined, {
        WebSocket,
        maxRetries: 0,
    });
    ws.onerror = () => {
        t.true(ws._ws instanceof WebSocket);
        t.end();
    };
});

test('URL provider', async t => {
    const url = 'example.com';
    const ws = new ReconnectingWebSocket(URL, undefined, {
        maxRetries: 0,
    });
    t.is(await ws._getNextUrl(url), url, 'string');
    t.is(await ws._getNextUrl(() => url), url, '() -> string');
    t.is(await ws._getNextUrl(() => Promise.resolve(url)), url, '() -> Promise<string>');

    try {
        await ws._getNextUrl(123);
        t.fail();
    } catch (e) {
        t.pass();
    }

    try {
        await ws._getNextUrl(() => 123);
        t.fail();
    } catch (e) {
        t.pass();
    }
});

test('connection status constants', t => {
    const ws = new ReconnectingWebSocket(URL, null, {maxRetries: 0});

    t.is(ReconnectingWebSocket.CONNECTING, 0);
    t.is(ReconnectingWebSocket.OPEN, 1);
    t.is(ReconnectingWebSocket.CLOSING, 2);
    t.is(ReconnectingWebSocket.CLOSED, 3);

    t.is(ws.CONNECTING, 0);
    t.is(ws.OPEN, 1);
    t.is(ws.CLOSING, 2);
    t.is(ws.CLOSED, 3);
    ws.close();
});

const maxRetriesTest = (count, t) => {
    const ws = new ReconnectingWebSocket(URL, null, {
        maxRetries: count,
        maxReconnectionDelay: 200,
    });
    t.plan(count + 1);

    ws.addEventListener('error', event => {
        t.pass();
        if (ws.retryCount === count) {
            setTimeout(() => t.end(), 500);
        }
        if (ws.retryCount > count) {
            t.fail(`too many retries: ${ws.retryCount}`);
        }
    });
};

test.cb('max retries: 0', t => maxRetriesTest(0, t));
test.cb('max retries: 1', t => maxRetriesTest(1, t));
test.cb('max retries: 5', t => maxRetriesTest(5, t));

test.cb('level0 event listeners are kept after reconnect', t => {
    const ws = new ReconnectingWebSocket(URL, null, {
        maxRetries: 4,
        reconnectionDelayFactor: 1.2,
        maxReconnectionDelay: 20,
        minReconnectionDelay: 10,
    });

    const handleOpen = () => {};
    const handleClose = () => {};
    const handleMessage = () => {};
    const handleError = () => {
        t.is(ws.onopen, handleOpen);
        t.is(ws.onclose, handleClose);
        t.is(ws.onmessage, handleMessage);
        t.is(ws.onerror, handleError);
        if (ws.retryCount === 4) {
            t.end();
        }
    };

    ws.onopen = handleOpen;
    ws.onclose = handleClose;
    ws.onmessage = handleMessage;
    ws.onerror = handleError;
});

test.cb('level2 event listeners', t => {
    const anyProtocol = 'foobar';
    const wss = new WebSocketServer({port: PORT});
    const ws = new ReconnectingWebSocket(URL, anyProtocol, {});

    ws.addEventListener('open', () => {
        t.is(ws.protocol, anyProtocol);
        t.is(ws.extensions, '');
        t.is(ws.bufferedAmount, 0);
        ws.close();
    });

    const fail = () => {
        t.fail();
    };
    ws.addEventListener('unknown1', fail);
    ws.addEventListener('open', fail);
    ws.addEventListener('open', fail);
    ws.removeEventListener('open', fail);
    ws.removeEventListener('unknown2', fail);

    ws.addEventListener('close', () => {
        wss.close();
        setTimeout(() => t.end(), 500);
    });
});

test.cb('connection timeout', t => {
    const proc = spawn('node', [`${__dirname}/unresponsive-server.js`, PORT_UNRESPONSIVE, 5000]);
    t.plan(2);

    let lock = false;
    proc.stdout.on('data', d => {
        if (lock) return;
        lock = true;

        const ws = new ReconnectingWebSocket(`ws://localhost:${PORT_UNRESPONSIVE}`, null, {
            minReconnectionDelay: 50,
            connectionTimeout: 500,
            maxRetries: 1,
        });

        ws.addEventListener('error', event => {
            t.is(event.message, 'TIMEOUT');
            if (ws.retryCount === 1) {
                setTimeout(() => t.end(), 1000);
            }
        });

        ws.addEventListener('close', event => {
            console.log('>>>> CLOSE', event.message);
        });
    });
});

test.cb('getters', t => {
    const anyProtocol = 'foobar';
    const wss = new WebSocketServer({port: PORT});
    const ws = new ReconnectingWebSocket(URL, anyProtocol, {maxReconnectionDelay: 100});

    ws.addEventListener('open', () => {
        t.is(ws.protocol, anyProtocol);
        t.is(ws.extensions, '');
        t.is(ws.bufferedAmount, 0);
        t.is(ws.binaryType, 'nodebuffer');
        ws.close();
    });

    ws.addEventListener('close', () => {
        wss.close();
        setTimeout(() => t.end(), 500);
    });
});

test.cb('binaryType', t => {
    const wss = new WebSocketServer({port: PORT});
    const ws = new ReconnectingWebSocket(URL, undefined, {minReconnectionDelay: 0});

    t.is(ws.binaryType, 'blob');
    ws.binaryType = 'arraybuffer';
    ws.addEventListener('open', () => {
        t.is(ws.binaryType, 'arraybuffer', 'assigned after open');
        ws.binaryType = 'nodebuffer';
        t.is(ws.binaryType, 'nodebuffer');
        ws.close();
    });

    ws.addEventListener('close', () => {
        wss.close();
        setTimeout(() => t.end(), 500);
    });
});

test.cb('calling to close multiple times', t => {
    const wss = new WebSocketServer({port: PORT});
    const ws = new ReconnectingWebSocket(URL, undefined, {});

    ws.addEventListener('open', () => {
        ws.close();
        ws.close();
        ws.close();
    });

    ws.addEventListener('close', () => {
        wss.close();
        setTimeout(() => t.end(), 500);
    });
});

test.cb('calling to reconnect when not ready', t => {
    const wss = new WebSocketServer({port: PORT});
    const ws = new ReconnectingWebSocket(URL, undefined, {});
    ws.reconnect();
    ws.reconnect();

    ws.addEventListener('open', () => {
        ws.close();
    });

    ws.addEventListener('close', () => {
        wss.close();
        setTimeout(() => t.end(), 500);
    });
});

test.cb('connect, send, receive, close', t => {
    const anyMessageText = 'hello';
    const anyProtocol = 'foobar';

    const wss = new WebSocketServer({port: PORT});
    wss.on('connection', ws => {
        ws.on('message', msg => {
            ws.send(msg);
        });
    });
    wss.on('error', () => {
        t.fail();
    });

    t.plan(7);

    const ws = new ReconnectingWebSocket(URL, anyProtocol, {
        minReconnectionDelay: 100,
        maxReconnectionDelay: 200,
    });
    t.is(ws.readyState, ws.CONNECTING);

    ws.addEventListener('open', () => {
        t.is(ws.protocol, anyProtocol);
        t.is(ws.readyState, ws.OPEN);
        ws.send(anyMessageText);
    });

    ws.addEventListener('message', msg => {
        t.is(msg.data, anyMessageText);
        ws.close(1000, '');
        t.is(ws.readyState, ws.CLOSING);
    });

    ws.addEventListener('close', () => {
        t.is(ws.readyState, ws.CLOSED);
        t.is(ws.url, URL);
        wss.close();
        setTimeout(() => t.end(), 1000);
    });
});

test.cb('connect, send, receive, reconnect', t => {
    const anyMessageText = 'hello';
    const anyProtocol = 'foobar';

    const wss = new WebSocketServer({port: PORT});
    wss.on('connection', ws => {
        ws.on('message', msg => {
            ws.send(msg);
        });
    });

    const totalRounds = 3;
    let currentRound = 0;

    // 6 = 3 * 2 open
    // 8 = 2 * 3 message + 2 reconnect
    // 7 = 2 * 3 close + 1 closed
    t.plan(21);

    const ws = new ReconnectingWebSocket(URL, anyProtocol, {
        minReconnectionDelay: 100,
        maxReconnectionDelay: 200,
    });

    ws.onopen = () => {
        currentRound++;
        t.is(ws.protocol, anyProtocol);
        t.is(ws.readyState, ws.OPEN);
        ws.send(anyMessageText);
    };

    ws.onmessage = msg => {
        t.is(msg.data, anyMessageText);
        if (currentRound < totalRounds) {
            ws.reconnect(1000, 'reconnect');
            t.is(ws.retryCount, 0);
        } else {
            ws.close(1000, 'close');
        }
        t.is(ws.readyState, ws.CLOSING);
    };

    ws.addEventListener('close', event => {
        t.is(ws.url, URL);
        if (currentRound >= totalRounds) {
            t.is(ws.readyState, ws.CLOSED);
            wss.close();
            setTimeout(() => t.end(), 1000);
            t.is(event.reason, 'close');
        } else {
            t.is(event.reason, 'reconnect');
        }
    });
});

test.cb.only('immediatly-failed connection should not timeout', t => {
    const ws = new ReconnectingWebSocket('ws://thiswillfail.com', null, {
        maxRetries: 2,
        connectionTimeout: 500,
        debug: 1,
    });

    ws.addEventListener('error', err => {
        if (err.message === 'TIMEOUT') {
            t.fail();
        }
        if (ws.retryCount === 2) {
            setTimeout(() => t.end(), 500);
        }
        if (ws.retryCount > 2) {
            t.fail();
        }
    });
});
<|endoftext|>


<commit_before>var amortize = require('../index.js');

var testVal = amortize({amount: 180000, rate: 4.25, totalTerm: 360, amortizeTerm: 60}),
    testVal2 = amortize({amount: 180000, rate: 4.375, totalTerm: 360, amortizeTerm: 60});

exports['After 5 years a borrower with a 30 year, $180,000 loan with a 4.25% interest rate will have paid $36583.362108097754 in raw interest'] = function (test) {
  test.equal(testVal.interest, 36583.362108097754);
  test.done();
};

exports['After 5 years a borrower with a 30 year, $180,000 loan with a 4.25% interest rate will have paid $36,583.36 in interest'] = function (test) {
  test.equal(testVal.interestRound, 36583.36);
  test.done();
};

exports['After 5 years a borrower with a 30 year, $180,000 loan with a 4.25% interest rate will have paid $16,546.15 in principal'] = function (test) {
  test.equal(testVal.principalRound, 16546.15);
  test.done();
};

exports['After 5 years a borrower with a 30 year, $180,000 loan with a 4.25% interest rate will owe $163,453.85'] = function (test) {
  test.equal(testVal.balanceRound, 163453.85);
  test.done();
};

exports['A borrower with a 30 year, $180,000 loan with a 4.25% interest rate will pay $885.49 monthly'] = function (test) {
  test.equal(testVal.paymentRound, 885.49);
  test.done();
};

exports['After 5 years a borrower with a 30 year, $180,000 loan with a 4.375% interest rate will have paid $37,694.10 in interest'] = function (test) {
  test.equal(testVal2.interestRound, 37694.10);
  test.done();
};

exports['Throw an error if a string is passed'] = function (test) {
  test.throws(function() {
    amortize({amount: 'Gregor Samsa', rate: 4.25, totalTerm: 360, amortizeTerm: 60});
  },
  Error, 'Specify all values as a positive number');
  test.done();
};

exports['Throw an error if a negative value is passed'] = function (test) {
  test.throws(function() {
    amortize({amount: -180000, rate: 4.25, totalTerm: 360, amortizeTerm: 60});
  },
  Error, 'Specify all values as a positive number');
  test.done();
};<commit_msg>Add test for a loan amount of zero<commit_after>var amortize = require('../index.js');

var testVal = amortize({amount: 180000, rate: 4.25, totalTerm: 360, amortizeTerm: 60}),
    testVal2 = amortize({amount: 180000, rate: 4.375, totalTerm: 360, amortizeTerm: 60}),
    testVal3 = amortize({amount: 0, rate: 4.375, totalTerm: 360, amortizeTerm: 60});

exports['After 5 years a borrower with a 30 year, $180,000 loan with a 4.25% interest rate will have paid $36583.362108097754 in raw interest'] = function (test) {
  test.equal(testVal.interest, 36583.362108097754);
  test.done();
};

exports['After 5 years a borrower with a 30 year, $180,000 loan with a 4.25% interest rate will have paid $36,583.36 in interest'] = function (test) {
  test.equal(testVal.interestRound, 36583.36);
  test.done();
};

exports['After 5 years a borrower with a 30 year, $180,000 loan with a 4.25% interest rate will have paid $16,546.15 in principal'] = function (test) {
  test.equal(testVal.principalRound, 16546.15);
  test.done();
};

exports['After 5 years a borrower with a 30 year, $180,000 loan with a 4.25% interest rate will owe $163,453.85'] = function (test) {
  test.equal(testVal.balanceRound, 163453.85);
  test.done();
};

exports['A borrower with a 30 year, $180,000 loan with a 4.25% interest rate will pay $885.49 monthly'] = function (test) {
  test.equal(testVal.paymentRound, 885.49);
  test.done();
};

exports['After 5 years a borrower with a 30 year, $180,000 loan with a 4.375% interest rate will have paid $37,694.10 in interest'] = function (test) {
  test.equal(testVal2.interestRound, 37694.10);
  test.done();
};

exports['After 5 years a borrower borrowing nothing will owe nothing'] = function (test) {
  test.equal(testVal3.interestRound, 0);
  test.done();
};

exports['Throw an error if a string is passed'] = function (test) {
  test.throws(function() {
    amortize({amount: 'Gregor Samsa', rate: 4.25, totalTerm: 360, amortizeTerm: 60});
  },
  Error, 'Specify all values as a positive number');
  test.done();
};

exports['Throw an error if a negative value is passed'] = function (test) {
  test.throws(function() {
    amortize({amount: -180000, rate: 4.25, totalTerm: 360, amortizeTerm: 60});
  },
  Error, 'Specify all values as a positive number');
  test.done();
};<|endoftext|>


<commit_before>$.ajaxSetup({
  beforeSend: function() { $("#header .spinner").show() },
  complete:   function() { $("#header .spinner").hide() }
})

// http://javascriptly.com/2008/09/javascript-to-detect-google-chrome/
var userAgent = navigator.userAgent.toLowerCase();
$.browser = {
  version: (userAgent.match( /.+(?:rv|it|ra|ie|me)[\/: ]([\d.]+)/ ) || [])[1],
  chrome:  /chrome/.test( userAgent ),
  safari:  /webkit/.test( userAgent ) && !/chrome/.test( userAgent ),
  opera:   /opera/.test( userAgent ),
  msie:    /msie/.test( userAgent ) && !/opera/.test( userAgent ),
  mozilla: /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent )
}

var RepoManager = {
  onload: function() {
    // observe commit selection
    $("#commits li").live("click", function() {
      $("#diffs").removeClass("hiding_overflow")
      $("#diffs .content").html("")
      $("#diffs .spinner").show()

      var c = $(this)
      $("#commits li.selected").removeClass("selected")
      c.addClass("selected")

      var sha = /[^_]+$/.exec(c.attr("id"))[0]
      $.get(RepoManager.url("diffs", sha), function(data) {
        $("#diffs .spinner").hide()
        $("#diffs").addClass("hiding_overflow")
        $("#diffs .content").html(data)
      })
      document.location.hash = sha
      $("#sha").html(sha)
    })

    // fetch first page of commits
    RepoManager.get_commits()

    // load initial commit
    if (document.location.hash) {
      var sha = document.location.hash.substring(1)
      $("#commit_" + sha).click()
    } else {
      $("#commits li").eq(0).click()
    }

    // observe link to next page of commits
    $("#more a").click(function() {
      var current_page = RepoManager.page
      var new_page     = RepoManager.page + 1
      RepoManager.get_commits(new_page)
      RepoManager.page += 1
      return false
    })

    // observe branch selection
    $("#select_branch select").change(function() {
      document.location.href = "/" + RepoManager.project + "/heads/" + RepoManager.selected_branch(true)
    })

    RepoManager.fit_window()
    RepoManager.observe_window_resize()
    RepoManager.observe_hotkeys()
  },

  url: function() {
    var args = $.makeArray(arguments)
    args.unshift(RepoManager.project)
    return "/" + args.join("/")
  },

  selected_branch: function(escaped) {
    var select = $("#select_branch select")
    var branch_name = select.attr("options")[select.attr("selectedIndex")].value

    if (escaped) branch_name = encodeURIComponent(branch_name.replace(/\//g, '--'))
    return branch_name
  },

  observe_hotkeys: function() {
    handlers = {
      h: function() {
        document.location.href = $("#home a").attr("href")
      }
    }

    $.each(handlers, function(key,handler) {
      $(document).bind('keydown', key, handler)
    })

    $("input[type=text]").add("textarea").keydown(function(e) { e.stopPropagation() })
  },

  get_commits: function(page) {
    if (!page) var page = RepoManager.page

    $.ajax({
      async: false,
      url: RepoManager.url("heads", RepoManager.selected_branch(true), "commits"),
      data: { page: page },
      success: function(commits) {
        $("#commits ol li:last").addClass("page-end")
        $("#commits ol").append(commits)
      }
    })
  },

  observe_window_resize: function() {
    $(window).resize(RepoManager.fit_window)
  },

  fit_window: function() {
    // TODO test other browsers.. number used here is specific to Firefox.. wah wah..
    var height      = window.innerHeight - parseInt($("#header").css("height")) - 18
    var diffs_width = window.innerWidth  - parseInt($("#commits").css("width")) - RepoManager.fit_window_width_offset()

    $("#commits").css({ height: height })
    $("#diffs").css({ width: diffs_width, height: height })
  },

  fit_window_width_offset: function() {
    if ($.browser.chrome || $.browser.safari) {
      return 16
    } else { // firefox
      return 3
    }
  }
}

$(RepoManager.onload)
<commit_msg>ok, i should probably add some tests, soon, shhhhh<commit_after>$.ajaxSetup({
  beforeSend: function() { $("#header .spinner").show() },
  complete:   function() { $("#header .spinner").hide() }
})

// http://javascriptly.com/2008/09/javascript-to-detect-google-chrome/
var userAgent = navigator.userAgent.toLowerCase();
$.browser = {
  version: (userAgent.match( /.+(?:rv|it|ra|ie|me)[\/: ]([\d.]+)/ ) || [])[1],
  chrome:  /chrome/.test( userAgent ),
  safari:  /webkit/.test( userAgent ) && !/chrome/.test( userAgent ),
  opera:   /opera/.test( userAgent ),
  msie:    /msie/.test( userAgent ) && !/opera/.test( userAgent ),
  mozilla: /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent )
}

var RepoManager = {
  onload: function() {
    // observe commit selection
    $("#commits li").live("click", function() {
      $("#diffs").removeClass("hiding_overflow")
      $("#diffs .content").html("")
      $("#diffs .spinner").show()

      var c = $(this)
      $("#commits li.selected").removeClass("selected")
      c.addClass("selected")

      var sha = /[^_]+$/.exec(c.attr("id"))[0]
      $.get(RepoManager.url("diffs", sha), function(data) {
        $("#diffs .spinner").hide()
        $("#diffs").addClass("hiding_overflow")
        $("#diffs .content").html(data)
      })
      document.location.hash = sha
      $("#sha").html(sha)
    })

    // fetch first page of commits
    RepoManager.get_commits()

    // load initial commit
    if (document.location.hash) {
      var sha = document.location.hash.substring(1)
      $("#commit_" + sha).click()
    } else {
      $("#commits li").eq(0).click()
    }

    // observe link to next page of commits
    $("#more a").click(function() {
      var current_page = RepoManager.current_page
      var new_page     = RepoManager.current_page + 1
      RepoManager.get_commits(new_page)
      RepoManager.current_page += 1
      return false
    })

    // observe branch selection
    $("#select_branch select").change(function() {
      document.location.href = "/" + RepoManager.project + "/heads/" + RepoManager.selected_branch(true)
    })

    RepoManager.fit_window()
    RepoManager.observe_window_resize()
    RepoManager.observe_hotkeys()
  },

  url: function() {
    var args = $.makeArray(arguments)
    args.unshift(RepoManager.project)
    return "/" + args.join("/")
  },

  selected_branch: function(escaped) {
    var select = $("#select_branch select")
    var branch_name = select.attr("options")[select.attr("selectedIndex")].value

    if (escaped) branch_name = encodeURIComponent(branch_name.replace(/\//g, '--'))
    return branch_name
  },

  observe_hotkeys: function() {
    handlers = {
      h: function() {
        document.location.href = $("#home a").attr("href")
      }
    }

    $.each(handlers, function(key,handler) {
      $(document).bind('keydown', key, handler)
    })

    $("input[type=text]").add("textarea").keydown(function(e) { e.stopPropagation() })
  },

  get_commits: function(page) {
    if (!page) var page = RepoManager.page

    $.ajax({
      async: false,
      url: RepoManager.url("heads", RepoManager.selected_branch(true), "commits"),
      data: { page: page },
      success: function(commits) {
        $("#commits ol li:last").addClass("page-end")
        $("#commits ol").append(commits)
      }
    })
  },

  observe_window_resize: function() {
    $(window).resize(RepoManager.fit_window)
  },

  fit_window: function() {
    // TODO test other browsers.. number used here is specific to Firefox.. wah wah..
    var height      = window.innerHeight - parseInt($("#header").css("height")) - 18
    var diffs_width = window.innerWidth  - parseInt($("#commits").css("width")) - RepoManager.fit_window_width_offset()

    $("#commits").css({ height: height })
    $("#diffs").css({ width: diffs_width, height: height })
  },

  fit_window_width_offset: function() {
    if ($.browser.chrome || $.browser.safari) {
      return 16
    } else { // firefox
      return 3
    }
  }
}

$(RepoManager.onload)
<|endoftext|>


<commit_before>/* global describe, it */

'use strict';

var assert = require('chai').assert,
	Rx = require('rx'),
	umd = require('..'),
	parse = require('esprima').parse,
	generate = require('escodegen').generate;

it('test', function (done) {
	var input = [{
			type: 'File',
			program: parse('return [jquery, jade, lodash]', {tolerant: true}),
			loc: {
				source: 'superLib.js'
			}
		}],
		expected = [
			'(function (factory) {\n' +
			'    if (typeof define === \'function\' && define.amd) {\n' +
			'        define([\n' +
			'            \'jquery\',\n' +
			'            \'jade\',\n' +
			'            \'../lodash\'\n' +
			'        ], factory);\n' +
			'    } else if (typeof exports === \'object\') {\n' +
			'        module.exports = factory(require(\'jquery\'), require(\'jade\'), require(\'lodash\'));\n' +
			'    } else {\n' +
			'        this.superLib = factory(root.jQuery, root.jade, root._);\n' +
			'    }\n' +
			'}(function (jquery, jade, lodash) {\n' +
			'    return [\n' +
			'        jquery,\n' +
			'        jade,\n' +
			'        lodash\n' +
			'    ];\n' +
			'}));'
		];

	// simulating file sequence and applying transformation
	umd({
		deps: [
			{name: 'jquery', globalName: 'jQuery'},
			'jade',
			{name: 'lodash', globalName: '_', amdName: '../lodash'}
		]
	})(Rx.Observable.fromArray(input))
	.pluck('program')
	.do(function (ast) {
		require('fs').writeFileSync('ast.json', JSON.stringify(ast));
	})
	.map(generate)
	// checking against array of expected results iteratively
	.zip(expected, assert.equal)
	// subscribing to check results
	.subscribe(function () {}, done, done);
});

<commit_msg>Update test.js<commit_after>/* global describe, it */

'use strict';

var assert = require('chai').assert,
	Rx = require('rx'),
	umd = require('..'),
	parse = require('esprima').parse,
	generate = require('escodegen').generate;

it('test', function (done) {
	var input = [{
			type: 'File',
			program: parse('return [jquery, jade, lodash]', {tolerant: true}),
			loc: {
				source: 'superLib.js'
			}
		}],
		expected = [
			'(function (factory) {\n' +
			'    if (typeof define === \'function\' && define.amd) {\n' +
			'        define([\n' +
			'            \'jquery\',\n' +
			'            \'jade\',\n' +
			'            \'../lodash\'\n' +
			'        ], factory);\n' +
			'    } else if (typeof exports === \'object\') {\n' +
			'        module.exports = factory(require(\'jquery\'), require(\'jade\'), require(\'lodash\'));\n' +
			'    } else {\n' +
			'        this.superLib = factory(root.jQuery, root.jade, root._);\n' +
			'    }\n' +
			'}(function (jquery, jade, lodash) {\n' +
			'    return [\n' +
			'        jquery,\n' +
			'        jade,\n' +
			'        lodash\n' +
			'    ];\n' +
			'}));'
		];

	// simulating file sequence and applying transformation
	umd({
		deps: [
			{name: 'jquery', globalName: 'jQuery'},
			'jade',
			{name: 'lodash', globalName: '_', amdName: '../lodash'}
		]
	})(Rx.Observable.fromArray(input))
	.pluck('program')
	.map(generate)
	// checking against array of expected results iteratively
	.zip(expected, assert.equal)
	// subscribing to check results
	.subscribe(function () {}, done, done);
});

<|endoftext|>


<commit_before>var Backbone = require('backbone');
var _ = require('underscore');

var app = require('../adminui');

var Server = require('../models/server');
var Nics = require('../models/nics');
var Vms = require('../models/vms');

var React = require('react');
var NotesComponent = require('../components/notes');

var VmsList = require('./vms-list');
var JSONEditor = require('./traits-editor');
var JobProgressView = require('./job-progress');
var ChangeRackForm = require('./server-change-rack');
var ChangePlatformForm = require('./server-change-platform');
var ServerNicsView = require('./server-nics');
var ServerSetup = require('./server-setup');

var JobsList = require('./jobs-list');
var ServerNicsEdit = require('./server-nics-edit');

var ServerTemplate = require('../tpl/server.hbs');
var ServerView = Backbone.Marionette.Layout.extend({
    id: 'page-server',
    sidebar: 'servers',
    template: ServerTemplate,
    regions: {
        'vmsRegion': '.vms-region',
        'jobsRegion': '.jobs-region'
    },

    events: {
        'click .setup': 'showSetupModal',
        'click .change-rack-id': 'showChangeRackField',
        'click .change-platform': 'showChangePlatformField',
        'click .change-reservation-ratio': 'showChangeReservationRatio',
        'click .change-serial-console': 'showChangeSerialConsole',
        'click .modify-traits': 'showTraitsModal',
        'click .factory-reset': 'factoryReset',
        'click .reboot': 'reboot',
        'click .forget': 'forget',
        'click .change-reserve': 'toggleReserve',
        'click .manage-nics': 'showManageNics',
        'click .manage-link-aggr': 'showManageLinkAggr'
    },

    url: function() {
        return _.str.sprintf('servers/%s', this.model.get('uuid'));
    },

    modelEvents: {
        'change': 'render',
        'error': 'onError'
    },

    initialize: function(options) {
        this.model = options.server || new Server({uuid: options.uuid});

        this.nics = new Nics(null, {
            params: {
                belongs_to_type: 'server',
                belongs_to_uuid: this.model.get('uuid')
            }
        });

        this.listenTo(this.nics, 'sync', this.mergeSysinfo);

        this.vms = new Vms(null, {
            params: {
                server_uuid: this.model.get('uuid'),
                state: 'active'
            },
            perPage: 20
        });
    },

    mergeSysinfo: function(nics) {
        var sysinfo = this.model.get('sysinfo');
        this.nics.mergeSysinfo(sysinfo);
    },

    templateHelpers: {
        platform_version: function() {
            return this.sysinfo['Live Image'];
        },
        cpu_type: function() {
            return this.sysinfo['CPU Type'];
        },
        cpu_physical_cores: function() {
            return this.sysinfo['CPU Physical Cores'];
        },
        cpu_total_cores: function() {
            return this.sysinfo['CPU Total Cores'];
        },
        serial_number: function() {
            return this.sysinfo['Serial Number'];
        },
        total_memory: function() {
            return this.sysinfo['MiB of Memory'];
        }
    },

    serializeData: function() {
        var data = Backbone.Marionette.ItemView.prototype.serializeData.call(this);
        data.disks = _.map(data.sysinfo['Disks'], function(v, k) {
            return {
                name: k,
                size: v['Size in GB']
            };
        });
        data.traits = _.map(data.traits, function(v, k) {
            return {name: k, value: v};
        });
        return data;
    },

    toggleReserve: function() {
        var newValue = !this.model.get('reserved');
        this.model.save({'reserved': newValue}, {patch: true});
    },

    onError: function(e, xhr, s) {
        app.vent.trigger('notification', {
            level: 'error',
            message: 'Error retreving Server Information. CNAPI said: ' + xhr.responseData.message,
            persistent: true
        });
        this.close();
    },

    showManageNics: function() {
        var view = new ServerNicsEdit({
            server: this.model,
            nics: this.nics
        });
        view.show();
    },

    showChangeSerialConsole: function() {
        function input(fieldName, value) {
            return $("<input type='text'>").attr('value', value).attr('name', fieldName);
        }
        this.$('.change-serial-console').hide();

        var $defaultConsole = input('default_console', this.model.get('default_console'));
        this.$('.serial-console .default-console').html($defaultConsole);

        var $serial = input('serial', this.model.get('serial'));
        this.$('.serial-console .serial').html($serial);

        var btn = $("<button>").addClass('btn btn-primary pull-right').html('Save');
        this.$('.serial-console .change').append(btn);
        var cancel = $("<button>").addClass('btn pull-right').html('Cancel');
        this.$('.serial-console .change').append(cancel);

        var self = this;
        var model = this.model;

        cancel.on('click', function() {
            self.render();
        });

        btn.on('click', function() {
            model.save({
                default_console: $defaultConsole.val(),
                serial: $serial.val()
            }, {patch: true}).done(function() {
                app.vent.trigger('notification', {
                    level: 'success',
                    message: 'Serial console settings updated.'
                });
                self.render();
            });
        });
    },

    showManageLinkAggr: function() {
        var $node = $("

");
        var container = $node.find('.modal-body').get(0);

        var ManageLinkAggr = require('../components/server-link-aggr/main');
        React.renderComponent(new ManageLinkAggr({
            server: this.model.get('uuid')
        }), container);
        $node.modal();
    },

    showChangeReservationRatio: function() {
        var $reservationRatio = this.$('span.reservation-ratio');
        var $changeReservationRatio = this.$('.change-reservation-ratio');
        var $input = $("<input type='text'/>").addClass('reservation-ratio');
        $input.val(this.model.get('reservation_ratio'));

        enterEditMode();

        var self = this;

        $input.on('blur', function(e) {
            saveVal();
        });

        $input.on('keyup', function(e) {
            if (e.which === 27) {
                exitEditMode();
            }
            if (e.which === 13) {
                saveVal();
            }
        });

        function enterEditMode() {
            $reservationRatio.after($input);
            $input.focus();
            $changeReservationRatio.hide();
            $reservationRatio.hide();
            showTooltip('Press ENTER to  Save
Press ESC to  Cancel');
        }

        function showTooltip(t) {
            $input.tooltip('hide');
            $input.tooltip('destroy');
            $input.tooltip({title: t, html: true}).tooltip('show');
        }

        function saveVal() {
            var val = $input.val();
            var n = Number(val);
            if (/^[0-9.]+$/.test(val) && (n >= 0 && n <= 1.0)) {
                self.model.save({reservation_ratio: n}, {patch: true}).done(exitEditMode);
            } else {
                showTooltip('Ratio should be a number between 0 and 1');
            }
        }

        function exitEditMode() {
            $changeReservationRatio.show();
            $reservationRatio.show();
            $input.tooltip('hide');
            $input.tooltip('destroy');
            $input.remove();
        }
    },

    showChangePlatformField: function() {
        var self = this;
        var $link = this.$('.platform a');
        var $value = this.$('.platform .value');
        var view = new ChangePlatformForm({ model: this.model });

        this.listenTo(view, 'cancel', function() {
            $value.show();
            $link.show();
        });

        this.listenTo(view, 'save', function(platform) {
            var message = _.str.sprintf('Server has been configured to use platform: %s on next boot.', platform);
            app.vent.trigger('notification', {
                level: 'success',
                message: message
            });
            view.remove();
            $value.show();
            $link.show();
        });

        this.$('.platform .boot .item-content').append(view.el);
        $value.hide();
        $link.hide();
        view.render();
    },

    showChangeRackField: function() {
        var self = this;
        var view = new ChangeRackForm({
            model: this.model
        });
        var $span = this.$('.rack .item-content span');
        var $link = this.$('.rack .item-content a');

        this.listenTo(view, 'cancel', function() {
            $span.show();
            $link.show();
        }, this);

        this.listenTo(view, 'save', function(rack) {
            self.model.set({
                rack_identifier: rack
            });
            app.vent.trigger('notification', {
                level: 'success',
                message: 'Rack assigned to ' + rack
            });
            view.remove();
            $span.show();
            $link.show();
        });
        this.$('.rack .item-content').append(view.el);
        $span.hide();
        $link.hide();
        view.render();
    },

    showTraitsModal: function() {
        var modal = new JSONEditor({
            data: this.model.get('traits'),
            title: _.str.sprintf('Traits for server: %s', this.model.get('hostname'))
        });
        modal.show();
        var server = this.model;
        modal.on('save', function(data) {
            this.model.save(
                {traits: data},
                {patch: true})
            .done(function() {
                modal.close();
                app.vent.trigger('notification', {
                    level: 'success',
                    message: 'Traits updated'
                });
            });
        }, this);
    },

    showSetupModal: function() {
        var view = new ServerSetup({ model: this.model });
        view.render();
    },

    factoryReset: function() {
        var confirm = window.confirm('!!!!!! WARNING !!!!!!! \n\nAre you sure you want to run Factory Reset on this Server?');
        if (confirm) {
            this.model.factoryReset(function(err, job) {
                if (err) {
                    window.alert(err);
                } else {
                    app.vent.trigger('showjob', job);
                }
            });
        }
    },

    reboot: function() {
        var confirm = window.confirm('!!!!!!! WARNING !!!!!!!! \n\nAre you sure you want to reboot this server? All customer zones will be rebooted');
        if (confirm) {
            this.model.reboot(function(job) {
                app.vent.trigger('showjob', job);
            });
        }
    },

    forget: function() {
        var confirm = window.confirm('!!!!!!!!! WARNING !!!!!!!! \n\nAre you sure you want to remove this server?');
        if (confirm) {
            this.model.forget(function(err) {
                app.vent.trigger('notification', {
                    level: 'success',
                    message: 'Server removed from SmartDataCenter.'
                });
                app.vent.trigger('showview', 'servers');
            });
        }
    },

    onShow: function() {
        this.model.fetch();
        this.nics.fetch();
        this.vms.fetch();
    },

    onRender: function() {
        console.log('Server', this.model);

        if (app.user.role('operators')) {
            React.renderComponent(
                new NotesComponent({item: this.model.get('uuid')}),
                this.$('.notes-component-container').get(0));
        }

        this.nicsView = new ServerNicsView({
            nics: this.nics,
            el: this.$('.nics')
        });

        this.jobsListView = new JobsList({
            perPage: 100,
            params: {server_uuid: this.model.get('uuid')}
        });

        this.jobsRegion.show(this.jobsListView);

        this.nicsView.render();
        this.$("[data-toggle=tooltip]").tooltip();

        this.vmsListView = new VmsList({collection: this.vms });
        this.vmsRegion.show(this.vmsListView);
    }
});

module.exports = ServerView;
<commit_msg>ADMINUI-1674 server traits display does not deal with nested objects<commit_after>var Backbone = require('backbone');
var _ = require('underscore');

var app = require('../adminui');

var Server = require('../models/server');
var Nics = require('../models/nics');
var Vms = require('../models/vms');

var React = require('react');
var NotesComponent = require('../components/notes');

var VmsList = require('./vms-list');
var JSONEditor = require('./traits-editor');
var JobProgressView = require('./job-progress');
var ChangeRackForm = require('./server-change-rack');
var ChangePlatformForm = require('./server-change-platform');
var ServerNicsView = require('./server-nics');
var ServerSetup = require('./server-setup');

var JobsList = require('./jobs-list');
var ServerNicsEdit = require('./server-nics-edit');

var ServerTemplate = require('../tpl/server.hbs');
var ServerView = Backbone.Marionette.Layout.extend({
    id: 'page-server',
    sidebar: 'servers',
    template: ServerTemplate,
    regions: {
        'vmsRegion': '.vms-region',
        'jobsRegion': '.jobs-region'
    },

    events: {
        'click .setup': 'showSetupModal',
        'click .change-rack-id': 'showChangeRackField',
        'click .change-platform': 'showChangePlatformField',
        'click .change-reservation-ratio': 'showChangeReservationRatio',
        'click .change-serial-console': 'showChangeSerialConsole',
        'click .modify-traits': 'showTraitsModal',
        'click .factory-reset': 'factoryReset',
        'click .reboot': 'reboot',
        'click .forget': 'forget',
        'click .change-reserve': 'toggleReserve',
        'click .manage-nics': 'showManageNics',
        'click .manage-link-aggr': 'showManageLinkAggr'
    },

    url: function() {
        return _.str.sprintf('servers/%s', this.model.get('uuid'));
    },

    modelEvents: {
        'change': 'render',
        'error': 'onError'
    },

    initialize: function(options) {
        this.model = options.server || new Server({uuid: options.uuid});

        this.nics = new Nics(null, {
            params: {
                belongs_to_type: 'server',
                belongs_to_uuid: this.model.get('uuid')
            }
        });

        this.listenTo(this.nics, 'sync', this.mergeSysinfo);

        this.vms = new Vms(null, {
            params: {
                server_uuid: this.model.get('uuid'),
                state: 'active'
            },
            perPage: 20
        });
    },

    mergeSysinfo: function(nics) {
        var sysinfo = this.model.get('sysinfo');
        this.nics.mergeSysinfo(sysinfo);
    },

    templateHelpers: {
        platform_version: function() {
            return this.sysinfo['Live Image'];
        },
        cpu_type: function() {
            return this.sysinfo['CPU Type'];
        },
        cpu_physical_cores: function() {
            return this.sysinfo['CPU Physical Cores'];
        },
        cpu_total_cores: function() {
            return this.sysinfo['CPU Total Cores'];
        },
        serial_number: function() {
            return this.sysinfo['Serial Number'];
        },
        total_memory: function() {
            return this.sysinfo['MiB of Memory'];
        }
    },

    serializeData: function() {
        var data = Backbone.Marionette.ItemView.prototype.serializeData.call(this);
        data.disks = _.map(data.sysinfo['Disks'], function(v, k) {
            return {
                name: k,
                size: v['Size in GB']
            };
        });
        data.traits = _.map(data.traits, function(v, k) {
            if (typeof(v) === 'object') {
                v = JSON.stringify(v, null, 2);
            } else {
                v = v.toString();
            }
            return {name: k, value: v};
        });
        return data;
    },

    toggleReserve: function() {
        var newValue = !this.model.get('reserved');
        this.model.save({'reserved': newValue}, {patch: true});
    },

    onError: function(e, xhr, s) {
        app.vent.trigger('notification', {
            level: 'error',
            message: 'Error retreving Server Information. CNAPI said: ' + xhr.responseData.message,
            persistent: true
        });
        this.close();
    },

    showManageNics: function() {
        var view = new ServerNicsEdit({
            server: this.model,
            nics: this.nics
        });
        view.show();
    },

    showChangeSerialConsole: function() {
        function input(fieldName, value) {
            return $("<input type='text'>").attr('value', value).attr('name', fieldName);
        }
        this.$('.change-serial-console').hide();

        var $defaultConsole = input('default_console', this.model.get('default_console'));
        this.$('.serial-console .default-console').html($defaultConsole);

        var $serial = input('serial', this.model.get('serial'));
        this.$('.serial-console .serial').html($serial);

        var btn = $("<button>").addClass('btn btn-primary pull-right').html('Save');
        this.$('.serial-console .change').append(btn);
        var cancel = $("<button>").addClass('btn pull-right').html('Cancel');
        this.$('.serial-console .change').append(cancel);

        var self = this;
        var model = this.model;

        cancel.on('click', function() {
            self.render();
        });

        btn.on('click', function() {
            model.save({
                default_console: $defaultConsole.val(),
                serial: $serial.val()
            }, {patch: true}).done(function() {
                app.vent.trigger('notification', {
                    level: 'success',
                    message: 'Serial console settings updated.'
                });
                self.render();
            });
        });
    },

    showManageLinkAggr: function() {
        var $node = $("

");
        var container = $node.find('.modal-body').get(0);

        var ManageLinkAggr = require('../components/server-link-aggr/main');
        React.renderComponent(new ManageLinkAggr({
            server: this.model.get('uuid')
        }), container);
        $node.modal();
    },

    showChangeReservationRatio: function() {
        var $reservationRatio = this.$('span.reservation-ratio');
        var $changeReservationRatio = this.$('.change-reservation-ratio');
        var $input = $("<input type='text'/>").addClass('reservation-ratio');
        $input.val(this.model.get('reservation_ratio'));

        enterEditMode();

        var self = this;

        $input.on('blur', function(e) {
            saveVal();
        });

        $input.on('keyup', function(e) {
            if (e.which === 27) {
                exitEditMode();
            }
            if (e.which === 13) {
                saveVal();
            }
        });

        function enterEditMode() {
            $reservationRatio.after($input);
            $input.focus();
            $changeReservationRatio.hide();
            $reservationRatio.hide();
            showTooltip('Press ENTER to  Save
Press ESC to  Cancel');
        }

        function showTooltip(t) {
            $input.tooltip('hide');
            $input.tooltip('destroy');
            $input.tooltip({title: t, html: true}).tooltip('show');
        }

        function saveVal() {
            var val = $input.val();
            var n = Number(val);
            if (/^[0-9.]+$/.test(val) && (n >= 0 && n <= 1.0)) {
                self.model.save({reservation_ratio: n}, {patch: true}).done(exitEditMode);
            } else {
                showTooltip('Ratio should be a number between 0 and 1');
            }
        }

        function exitEditMode() {
            $changeReservationRatio.show();
            $reservationRatio.show();
            $input.tooltip('hide');
            $input.tooltip('destroy');
            $input.remove();
        }
    },

    showChangePlatformField: function() {
        var self = this;
        var $link = this.$('.platform a');
        var $value = this.$('.platform .value');
        var view = new ChangePlatformForm({ model: this.model });

        this.listenTo(view, 'cancel', function() {
            $value.show();
            $link.show();
        });

        this.listenTo(view, 'save', function(platform) {
            var message = _.str.sprintf('Server has been configured to use platform: %s on next boot.', platform);
            app.vent.trigger('notification', {
                level: 'success',
                message: message
            });
            view.remove();
            $value.show();
            $link.show();
        });

        this.$('.platform .boot .item-content').append(view.el);
        $value.hide();
        $link.hide();
        view.render();
    },

    showChangeRackField: function() {
        var self = this;
        var view = new ChangeRackForm({
            model: this.model
        });
        var $span = this.$('.rack .item-content span');
        var $link = this.$('.rack .item-content a');

        this.listenTo(view, 'cancel', function() {
            $span.show();
            $link.show();
        }, this);

        this.listenTo(view, 'save', function(rack) {
            self.model.set({
                rack_identifier: rack
            });
            app.vent.trigger('notification', {
                level: 'success',
                message: 'Rack assigned to ' + rack
            });
            view.remove();
            $span.show();
            $link.show();
        });
        this.$('.rack .item-content').append(view.el);
        $span.hide();
        $link.hide();
        view.render();
    },

    showTraitsModal: function() {
        var modal = new JSONEditor({
            data: this.model.get('traits'),
            title: _.str.sprintf('Traits for server: %s', this.model.get('hostname'))
        });
        modal.show();
        var server = this.model;
        modal.on('save', function(data) {
            this.model.save(
                {traits: data},
                {patch: true})
            .done(function() {
                modal.close();
                app.vent.trigger('notification', {
                    level: 'success',
                    message: 'Traits updated'
                });
            });
        }, this);
    },

    showSetupModal: function() {
        var view = new ServerSetup({ model: this.model });
        view.render();
    },

    factoryReset: function() {
        var confirm = window.confirm('!!!!!! WARNING !!!!!!! \n\nAre you sure you want to run Factory Reset on this Server?');
        if (confirm) {
            this.model.factoryReset(function(err, job) {
                if (err) {
                    window.alert(err);
                } else {
                    app.vent.trigger('showjob', job);
                }
            });
        }
    },

    reboot: function() {
        var confirm = window.confirm('!!!!!!! WARNING !!!!!!!! \n\nAre you sure you want to reboot this server? All customer zones will be rebooted');
        if (confirm) {
            this.model.reboot(function(job) {
                app.vent.trigger('showjob', job);
            });
        }
    },

    forget: function() {
        var confirm = window.confirm('!!!!!!!!! WARNING !!!!!!!! \n\nAre you sure you want to remove this server?');
        if (confirm) {
            this.model.forget(function(err) {
                app.vent.trigger('notification', {
                    level: 'success',
                    message: 'Server removed from SmartDataCenter.'
                });
                app.vent.trigger('showview', 'servers');
            });
        }
    },

    onShow: function() {
        this.model.fetch();
        this.nics.fetch();
        this.vms.fetch();
    },

    onRender: function() {
        console.log('Server', this.model);

        if (app.user.role('operators')) {
            React.renderComponent(
                new NotesComponent({item: this.model.get('uuid')}),
                this.$('.notes-component-container').get(0));
        }

        this.nicsView = new ServerNicsView({
            nics: this.nics,
            el: this.$('.nics')
        });

        this.jobsListView = new JobsList({
            perPage: 100,
            params: {server_uuid: this.model.get('uuid')}
        });

        this.jobsRegion.show(this.jobsListView);

        this.nicsView.render();
        this.$("[data-toggle=tooltip]").tooltip();

        this.vmsListView = new VmsList({collection: this.vms });
        this.vmsRegion.show(this.vmsListView);
    }
});

module.exports = ServerView;
<|endoftext|>


<commit_before>var demand = require('must');
var wm = require('..');

function describeNode(node) {

  it("should be frozen", function() {
    var a = node();
    demand(Object.isFrozen(a)).be.truthy();
  });

  it("should be linked symmetrically", function() {
    var a = node(), b = node();
    a.set(b, 'x');
    demand(a.has(b)).be.true();
    demand(b.has(a)).be.true();
    demand(a.get(b)).equal('x');
    demand(b.get(a)).equal('x');
  });

  it("should be unlinked symmetrically", function() {
    var a = node(), b = node();
    a.set(b, 'x');
    b.remove(a);
    demand(a.has(b)).be.false();
    demand(b.has(a)).be.false();
  });

  it("should be allowed to link to itself", function() {
    var a = node();
    demand(a.set(a)).be.true();
    demand(a.has(a)).be.true();
    demand(a.set(a, 'x')).be.true();
    demand(a.get(a)).equal('x');
    demand(a.remove(a)).be.true();
    demand(a.has(a)).be.false();
  });

  describe(".set()", function() {

    it("should return true if there's a change", function() {
      var a = node(), b = node();
      demand(a.set(b)).be.true();
      demand(a.set(b, 'x')).be.true();
      demand(a.set(b)).be.true();
    });

    it("should return false otherwise", function() {
      var a = node(), b = node();
      a.set(b);
      demand(a.set(b)).be.false();
      a.set(b, 'x');
      demand(a.set(b, 'x')).be.false();
    });

  });

  describe(".remove()", function() {

    it("should return true if there's a change", function() {
      var a = node(), b = node();
      a.set(b);
      demand(a.remove(b)).be.true();
      a.set(b, 'x');
      demand(a.remove(b)).be.true();
    });

    it("should return false otherwise", function() {
      var a = node(), b = node();
      demand(a.remove(b)).be.false();
      a.set(b, 'x');
      a.remove(b);
      demand(a.remove(b)).be.false();
    });

  });

  describe(".has()", function() {

    it("should return true if the nodes are linked", function() {
      var a = node(), b = node();
      a.set(b);
      demand(a.has(b)).be.true();
    });

    it("should return false otherwise", function() {
      var a = node(), b = node();
      demand(a.has(b)).be.false();
      a.set(b);
      a.remove(b);
      demand(a.has(b)).be.false();
    });

    it("should work when the callee has more nodes than the argument", function() {
      var a = node(), b = node();
      a.set(a);
      demand(a.has(b)).be.false();
      a.set(b);
      demand(a.has(b)).be.true();
      a.remove(b);
      demand(a.has(b)).be.false();
    });

  });

  describe(".get()", function() {

    it("should return the nodes' link value", function() {
      var a = node(), b = node();
      a.set(b, 'x');
      demand(a.get(b)).equal('x');
    });

    it("should return the second argument if the nodes aren't linked", function() {
      var a = node(), b = node();
      var o = {};
      demand(a.get(b, o)).equal(o);
      a.set(b, 'x');
      a.remove(b);
      demand(a.get(b, b)).equal(b);
    });

    it("should return undefined if the nodes' link value is undefined", function() {
      var a = node(), b = node();
      a.set(b);
      demand(a.get(b, 'x')).be.undefined();
    });

    it("should use undefined as the default if no second argument is passed", function() {
      var a = node(), b = node();
      demand(a.get(b)).be.undefined();
    });

    it("should work when the callee has more nodes than the argument", function() {
      var a = node(), b = node();
      a.set(a);
      a.set(b, 'x');
      demand(a.get(b)).equal('x');
      a.set(b, 'y');
      demand(a.get(b)).equal('y');
      a.remove(b);
      demand(a.get(b, b)).equal(b);
    });

  });

  describe(".getNodes()", function() {

    it("should return an array of linked nodes", function() {
      var a = node(), b = node(), c = node();
      demand(a.getNodes()).be.empty();
      a.set(b);
      demand(a.getNodes()).eql([b]);
      a.set(c);
      demand(a.getNodes()).have.length(2);
      demand(a.getNodes()).include(b);
      demand(a.getNodes()).include(c);
      a.remove(b);
      demand(a.getNodes()).eql([c]);
      a.remove(c);
      demand(a.getNodes()).be.empty();
    });

    it("should return a new array on each invocation by default", function() {
      var a = node(), b = node();
      demand(a.getNodes()).not.equal(a.getNodes());
      a.set(b);
      demand(a.getNodes()).not.equal(a.getNodes());
    });

    it("should return a new array on each invocation when passed a truthy value", function() {
      var a = node(), b = node();
      demand(a.getNodes(false)).not.equal(a.getNodes(1));
      a.set(b);
      demand(a.getNodes(false)).not.equal(a.getNodes(true));
    });

    // there's no test for the inverse because that behavior is undefined.

  });

  describe(".name", function() {

    it("should be equal to the passed-in name", function() {
      var a = node('a'), b = node('b');
      demand(a.name).equal('a');
      demand(b.name).equal('b');
    });

    it("should be undefined if no name is given", function() {
      var a = node();
      demand(a.name).be.undefined();
    });

  });

  describe(".nodes", function() {

    it("should be an array of linked nodes", function() {
      var a = node(), b = node(), c = node();
      demand(a.nodes).be.empty();
      a.set(b);
      demand(a.nodes).eql([b]);
      a.set(c);
      demand(a.nodes).have.length(2);
      demand(a.nodes).include(b);
      demand(a.nodes).include(c);
      a.remove(b);
      demand(a.nodes).eql([c]);
      a.remove(c);
      demand(a.nodes).be.empty();
    });

  });

}

describe("Node", function() {

  describeNode(wm.node);

});

describe("Supernode", function() {

  describeNode(wm.supernode);

  describe(".on()", function() {

    it("should allow listening for 'add'", function() {
      var x = wm.supernode(), calls = 0;
      x.on('add', function() {
        ++calls;
      });
      x.set(x);
      demand(calls).equal(1);
    });

    it("should allow listening for 'update'", function() {
      var x = wm.supernode(), calls = 0;
      x.on('update', function() {
        ++calls;
      });
      x.set(x);
      x.set(x, 'x');
      demand(calls).equal(1);
    });

    it("should allow listening for 'remove'", function() {
      var x = wm.supernode(), calls = 0;
      x.on('remove', function() {
        ++calls;
      });
      x.set(x);
      x.remove(x);
      demand(calls).equal(1);
    });

    it("should not notify 'update' when a node is first linked", function() {
      var x = wm.supernode(), calls = 0;
      x.on('update', function() {
        ++calls;
      });
      x.set(x, 'x');
      demand(calls).equal(0);
    });

    it("should not notify 'add' when a node is updated", function() {
      var x = wm.supernode(), calls = 0;
      x.set(x);
      x.on('add', function() {
        ++calls;
      });
      x.set(x, 'x');
      demand(calls).equal(0);
    });

    it("should notify 'add' and not 'update' when a node is re-added", function() {
      var x = wm.supernode(), adds = 0, updates = 0;
      x.set(x);
      x.remove(x);
      x.on('add', function() {
        ++adds;
      });
      x.on('update', function() {
        ++updates;
      });
      x.set(x, 'x');
      demand(adds).equal(1);
      demand(updates).equal(0);
    });

    it("should accept multiple alert types", function() {
      var x = wm.supernode('x'), a = wm.node('a'), calls = 0;
      x.on('add', 'remove', function() {
        ++calls;
      });
      demand(calls).equal(0);
      x.set(a);
      demand(calls).equal(1);
      x.set(a, 'x');
      demand(calls).equal(1);
      x.remove(a);
      demand(calls).equal(2);
    });

  });

});
<commit_msg>Added tests for arguments to listeners<commit_after>var demand = require('must');
var wm = require('..');

function describeNode(node) {

  it("should be frozen", function() {
    var a = node();
    demand(Object.isFrozen(a)).be.truthy();
  });

  it("should be linked symmetrically", function() {
    var a = node(), b = node();
    a.set(b, 'x');
    demand(a.has(b)).be.true();
    demand(b.has(a)).be.true();
    demand(a.get(b)).equal('x');
    demand(b.get(a)).equal('x');
  });

  it("should be unlinked symmetrically", function() {
    var a = node(), b = node();
    a.set(b, 'x');
    b.remove(a);
    demand(a.has(b)).be.false();
    demand(b.has(a)).be.false();
  });

  it("should be allowed to link to itself", function() {
    var a = node();
    demand(a.set(a)).be.true();
    demand(a.has(a)).be.true();
    demand(a.set(a, 'x')).be.true();
    demand(a.get(a)).equal('x');
    demand(a.remove(a)).be.true();
    demand(a.has(a)).be.false();
  });

  describe(".set()", function() {

    it("should return true if there's a change", function() {
      var a = node(), b = node();
      demand(a.set(b)).be.true();
      demand(a.set(b, 'x')).be.true();
      demand(a.set(b)).be.true();
    });

    it("should return false otherwise", function() {
      var a = node(), b = node();
      a.set(b);
      demand(a.set(b)).be.false();
      a.set(b, 'x');
      demand(a.set(b, 'x')).be.false();
    });

  });

  describe(".remove()", function() {

    it("should return true if there's a change", function() {
      var a = node(), b = node();
      a.set(b);
      demand(a.remove(b)).be.true();
      a.set(b, 'x');
      demand(a.remove(b)).be.true();
    });

    it("should return false otherwise", function() {
      var a = node(), b = node();
      demand(a.remove(b)).be.false();
      a.set(b, 'x');
      a.remove(b);
      demand(a.remove(b)).be.false();
    });

  });

  describe(".has()", function() {

    it("should return true if the nodes are linked", function() {
      var a = node(), b = node();
      a.set(b);
      demand(a.has(b)).be.true();
    });

    it("should return false otherwise", function() {
      var a = node(), b = node();
      demand(a.has(b)).be.false();
      a.set(b);
      a.remove(b);
      demand(a.has(b)).be.false();
    });

    it("should work when the callee has more nodes than the argument", function() {
      var a = node(), b = node();
      a.set(a);
      demand(a.has(b)).be.false();
      a.set(b);
      demand(a.has(b)).be.true();
      a.remove(b);
      demand(a.has(b)).be.false();
    });

  });

  describe(".get()", function() {

    it("should return the nodes' link value", function() {
      var a = node(), b = node();
      a.set(b, 'x');
      demand(a.get(b)).equal('x');
    });

    it("should return the second argument if the nodes aren't linked", function() {
      var a = node(), b = node();
      var o = {};
      demand(a.get(b, o)).equal(o);
      a.set(b, 'x');
      a.remove(b);
      demand(a.get(b, b)).equal(b);
    });

    it("should return undefined if the nodes' link value is undefined", function() {
      var a = node(), b = node();
      a.set(b);
      demand(a.get(b, 'x')).be.undefined();
    });

    it("should use undefined as the default if no second argument is passed", function() {
      var a = node(), b = node();
      demand(a.get(b)).be.undefined();
    });

    it("should work when the callee has more nodes than the argument", function() {
      var a = node(), b = node();
      a.set(a);
      a.set(b, 'x');
      demand(a.get(b)).equal('x');
      a.set(b, 'y');
      demand(a.get(b)).equal('y');
      a.remove(b);
      demand(a.get(b, b)).equal(b);
    });

  });

  describe(".getNodes()", function() {

    it("should return an array of linked nodes", function() {
      var a = node(), b = node(), c = node();
      demand(a.getNodes()).be.empty();
      a.set(b);
      demand(a.getNodes()).eql([b]);
      a.set(c);
      demand(a.getNodes()).have.length(2);
      demand(a.getNodes()).include(b);
      demand(a.getNodes()).include(c);
      a.remove(b);
      demand(a.getNodes()).eql([c]);
      a.remove(c);
      demand(a.getNodes()).be.empty();
    });

    it("should return a new array on each invocation by default", function() {
      var a = node(), b = node();
      demand(a.getNodes()).not.equal(a.getNodes());
      a.set(b);
      demand(a.getNodes()).not.equal(a.getNodes());
    });

    it("should return a new array on each invocation when passed a truthy value", function() {
      var a = node(), b = node();
      demand(a.getNodes(false)).not.equal(a.getNodes(1));
      a.set(b);
      demand(a.getNodes(false)).not.equal(a.getNodes(true));
    });

    // there's no test for the inverse because that behavior is undefined.

  });

  describe(".name", function() {

    it("should be equal to the passed-in name", function() {
      var a = node('a'), b = node('b');
      demand(a.name).equal('a');
      demand(b.name).equal('b');
    });

    it("should be undefined if no name is given", function() {
      var a = node();
      demand(a.name).be.undefined();
    });

  });

  describe(".nodes", function() {

    it("should be an array of linked nodes", function() {
      var a = node(), b = node(), c = node();
      demand(a.nodes).be.empty();
      a.set(b);
      demand(a.nodes).eql([b]);
      a.set(c);
      demand(a.nodes).have.length(2);
      demand(a.nodes).include(b);
      demand(a.nodes).include(c);
      a.remove(b);
      demand(a.nodes).eql([c]);
      a.remove(c);
      demand(a.nodes).be.empty();
    });

  });

}

describe("Node", function() {

  describeNode(wm.node);

});

describe("Supernode", function() {

  describeNode(wm.supernode);

  describe(".on()", function() {

    it("should allow listening for 'add'", function() {
      var x = wm.supernode(), calls = 0;
      x.on('add', function() {
        ++calls;
      });
      x.set(x);
      demand(calls).equal(1);
    });

    it("should allow listening for 'update'", function() {
      var x = wm.supernode(), calls = 0;
      x.on('update', function() {
        ++calls;
      });
      x.set(x);
      x.set(x, 'x');
      demand(calls).equal(1);
    });

    it("should allow listening for 'remove'", function() {
      var x = wm.supernode(), calls = 0;
      x.on('remove', function() {
        ++calls;
      });
      x.set(x);
      x.remove(x);
      demand(calls).equal(1);
    });

    it("should not notify 'update' when a node is first linked", function() {
      var x = wm.supernode(), calls = 0;
      x.on('update', function() {
        ++calls;
      });
      x.set(x, 'x');
      demand(calls).equal(0);
    });

    it("should not notify 'add' when a node is updated", function() {
      var x = wm.supernode(), calls = 0;
      x.set(x);
      x.on('add', function() {
        ++calls;
      });
      x.set(x, 'x');
      demand(calls).equal(0);
    });

    it("should notify 'add' and not 'update' when a node is re-added", function() {
      var x = wm.supernode(), adds = 0, updates = 0;
      x.set(x);
      x.remove(x);
      x.on('add', function() {
        ++adds;
      });
      x.on('update', function() {
        ++updates;
      });
      x.set(x, 'x');
      demand(adds).equal(1);
      demand(updates).equal(0);
    });

    it("should accept multiple alert types", function() {
      var x = wm.supernode('x'), a = wm.node('a'), calls = 0;
      x.on('add', 'remove', function() {
        ++calls;
      });
      demand(calls).equal(0);
      x.set(a);
      demand(calls).equal(1);
      x.set(a, 'x');
      demand(calls).equal(1);
      x.remove(a);
      demand(calls).equal(2);
    });

    it("should pass the new node and value to the 'add' listener", function() {
      var x = wm.supernode('x'), a = wm.node('a'), calls = 0, node, value;
      x.on('add', function(n, v) {
        ++calls;
        demand(n).equal(node);
        demand(v).equal(value);
      });
      node = x; value = 'x'; x.set(x, 'x');
      node = a; value = undefined; x.set(a);
      x.remove(x); x.remove(a);
      node = a; value = 12; x.set(a, 12);
      node = x; value = undefined; x.set(x);
      demand(calls).equal(4);
    });

    it("should pass the new node and value to the 'update' listener", function() {
      var x = wm.supernode('x'), a = wm.node('a'), calls = 0, node, value;
      x.on('update', function(n, v) {
        ++calls;
        demand(n).equal(node);
        demand(v).equal(value);
      });
      x.set(a, 'x'); x.set(x);
      node = x; value = 'x'; x.set(x, 'x');
      node = a; value = undefined; x.set(a);
      node = a; value = 12; x.set(a, 12);
      node = x; value = undefined; x.set(x);
      demand(calls).equal(4);
    });

    it("should pass the old node and value to the 'remove' listener", function() {
      var x = wm.supernode('x'), a = wm.node('a'), calls = 0, node, value;
      x.on('remove', function(n, v) {
        ++calls;
        demand(n).equal(node);
        demand(v).equal(value);
      });
      x.set(x, 'x');
      x.set(a);
      x.set(x);
      x.set(a, 12);
      node = a; value = 12; x.remove(a);
      node = x; value = undefined; x.remove(x);
      demand(calls).equal(2);
    });

    // listeners shouldn't be called until after the change is applied
    // listeners should only be called when a change actually occurs

  });

});
<|endoftext|>


<commit_before>'use strict';

const expect = require('chai').expect;

const domainPing = require('../domain-ping');

describe('Testing domain-ping', () => {

    describe('Basic errors', () => {

        it('should return error for empty domain', (done) => {
            domainPing('')
                .then((res) => {
                    done(Error('Expected to fail'));
                })
                .catch((res) => {
                    expect(res).to.be.an('object');
                    expect(res).to.have.property('success');
                    expect(res.success).to.be.false;
                    expect(res).to.have.property('error');
                    expect(res.error).to.have.property('message');
                    expect(res.error.message).to.be.a('string');
                    expect(res.error.message).to.be.equal('Invalid domain name');
                    done();
                })
                .catch(done);
        });

        it('should return error for invalid domain', (done) => {
            domainPing('abc123')
                .then((res) => {
                    done(Error('Expected to fail'));
                })
                .catch((res) => {
                    expect(res).to.be.an('object');
                    expect(res).to.have.property('success');
                    expect(res.success).to.be.false;
                    expect(res).to.have.property('error');
                    expect(res.error).to.have.property('message');
                    expect(res.error.message).to.be.a('string');
                    expect(res.error.message).to.be.equal('Invalid domain name');
                    done();
                })
                .catch(done);
        });

    });

    describe('Valid domains', () => {

        it('should return correct data for github.com', (done) => {
            domainPing('github.com')
                .then((res) => {
                    expect(res).to.be.an('object');
                    expect(res).to.have.property('success');
                    expect(res.success).to.be.true;
                    expect(res).to.have.property('ip');
                    expect(res.ip).to.be.a('string');
                    expect(res).to.have.property('ping');
                    expect(res.ping).to.be.a('boolean');
                    expect(res.ping).to.be.true;
                    expect(res).to.have.property('online');
                    expect(res.online).to.be.a('boolean');
                    expect(res.online).to.be.true;
                    expect(res).to.have.property('statusCode');
                    expect(res.statusCode).to.be.a('number');
                    expect(res.statusCode).to.be.equal(200);
                    done();
                })
                .catch(done);
        });

        it('should return correct data for netflix.com (no ping)', (done) => {
            domainPing('netflix.com')
                .then((res) => {
                    expect(res).to.be.an('object');
                    expect(res).to.have.property('success');
                    expect(res.success).to.be.true;
                    expect(res).to.have.property('ip');
                    expect(res.ip).to.be.a('string');
                    expect(res).to.have.property('ping');
                    expect(res.ping).to.be.a('boolean');
                    expect(res.ping).to.be.false; // Firewall blocking ICMP
                    expect(res).to.have.property('online');
                    expect(res.online).to.be.a('boolean');
                    expect(res.online).to.be.true;
                    expect(res).to.have.property('statusCode');
                    expect(res.statusCode).to.be.a('number');
                    expect(res.statusCode).to.be.equal(200);
                    done();
                })
                .catch(done);
        });

    });

});
<commit_msg>Updated test to try pinging google.com<commit_after>'use strict';

const expect = require('chai').expect;

const domainPing = require('../domain-ping');

describe('Testing domain-ping', () => {

    describe('Basic errors', () => {

        it('should return error for empty domain', (done) => {
            domainPing('')
                .then((res) => {
                    done(Error('Expected to fail'));
                })
                .catch((res) => {
                    expect(res).to.be.an('object');
                    expect(res).to.have.property('success');
                    expect(res.success).to.be.false;
                    expect(res).to.have.property('error');
                    expect(res.error).to.have.property('message');
                    expect(res.error.message).to.be.a('string');
                    expect(res.error.message).to.be.equal('Invalid domain name');
                    done();
                })
                .catch(done);
        });

        it('should return error for invalid domain', (done) => {
            domainPing('abc123')
                .then((res) => {
                    done(Error('Expected to fail'));
                })
                .catch((res) => {
                    expect(res).to.be.an('object');
                    expect(res).to.have.property('success');
                    expect(res.success).to.be.false;
                    expect(res).to.have.property('error');
                    expect(res.error).to.have.property('message');
                    expect(res.error.message).to.be.a('string');
                    expect(res.error.message).to.be.equal('Invalid domain name');
                    done();
                })
                .catch(done);
        });

    });

    describe('Valid domains', () => {

        it('should return correct data for github.com', (done) => {
            domainPing('google.com')
                .then((res) => {
                    expect(res).to.be.an('object');
                    expect(res).to.have.property('success');
                    expect(res.success).to.be.true;
                    expect(res).to.have.property('ip');
                    expect(res.ip).to.be.a('string');
                    expect(res).to.have.property('ping');
                    expect(res.ping).to.be.a('boolean');
                    // expect(res.ping).to.be.true; // Travis CI can't ping, uncomment to test locally
                    expect(res).to.have.property('online');
                    expect(res.online).to.be.a('boolean');
                    expect(res.online).to.be.true;
                    expect(res).to.have.property('statusCode');
                    expect(res.statusCode).to.be.a('number');
                    expect(res.statusCode).to.be.equal(200);
                    done();
                })
                .catch(done);
        });

        it('should return correct data for netflix.com (no ping)', (done) => {
            domainPing('netflix.com')
                .then((res) => {
                    expect(res).to.be.an('object');
                    expect(res).to.have.property('success');
                    expect(res.success).to.be.true;
                    expect(res).to.have.property('ip');
                    expect(res.ip).to.be.a('string');
                    expect(res).to.have.property('ping');
                    expect(res.ping).to.be.a('boolean');
                    expect(res.ping).to.be.false; // Firewall blocking ICMP
                    expect(res).to.have.property('online');
                    expect(res.online).to.be.a('boolean');
                    expect(res.online).to.be.true;
                    expect(res).to.have.property('statusCode');
                    expect(res.statusCode).to.be.a('number');
                    expect(res.statusCode).to.be.equal(200);
                    done();
                })
                .catch(done);
        });

    });

});
<|endoftext|>