<commit_before>import $ from 'jquery';
import Pretender from 'pretender';
import Service from '@ember/service';
import hbs from 'htmlbars-inline-precompile';
import sinon from 'sinon';
import {UnsupportedMediaTypeError} from 'ghost-admin/services/ajax';
import {click, find, findAll, render, settled, triggerEvent, waitFor, waitUntil} from '@ember/test-helpers';
import {createFile, fileUpload} from '../../helpers/file-upload';
import {describe, it} from 'mocha';
import {expect} from 'chai';
import {run} from '@ember/runloop';
import {setupRenderingTest} from 'ember-mocha';

const notificationsStub = Service.extend({
    showAPIError(/* error, options */) {
        // noop - to be stubbed
    }
});

const sessionStub = Service.extend({
    isAuthenticated: false,

    init() {
        this._super(...arguments);
        let authenticated = {access_token: 'AccessMe123'};
        this.authenticated = authenticated;
        this.data = {authenticated};
    }
});

const stubSuccessfulUpload = function (server, delay = 0) {
    server.post('/ghost/api/v3/admin/images/upload/', function () {
        return [200, {'Content-Type': 'application/json'}, '{"images": [{"url":"/content/images/test.png"}]}'];
    }, delay);
};

const stubFailedUpload = function (server, code, error, delay = 0) {
    server.post('/ghost/api/v3/admin/images/upload/', function () {
        return [code, {'Content-Type': 'application/json'}, JSON.stringify({
            errors: [{
                type: error,
                message: `Error: ${error}`
            }]
        })];
    }, delay);
};

describe('Integration: Component: gh-image-uploader', function () {
    setupRenderingTest();

    let server;

    beforeEach(function () {
        this.owner.register('service:session', sessionStub);
        this.owner.register('service:notifications', notificationsStub);
        this.set('update', function () {});
        server = new Pretender();
    });

    afterEach(function () {
        server.shutdown();
    });

    it('renders form with supplied alt text', async function () {
        await render(hbs`{{gh-image-uploader image=image altText="text test"}}`);
        expect(find('[data-test-file-input-description]')).to.have.trimmed.text('Upload image of "text test"');
    });

    it('renders form with supplied text', async function () {
        await render(hbs`{{gh-image-uploader image=image text="text test"}}`);
        expect(find('[data-test-file-input-description]')).to.have.trimmed.text('text test');
    });

    it('generates request to correct endpoint', async function () {
        stubSuccessfulUpload(server);

        await render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
        await fileUpload('input[type="file"]', ['test'], {name: 'test.png'});

        expect(server.handledRequests.length).to.equal(1);
        expect(server.handledRequests[0].url).to.equal('/ghost/api/v3/admin/images/upload/');
        expect(server.handledRequests[0].requestHeaders.Authorization).to.be.undefined;
    });

    it('fires update action on successful upload', async function () {
        let update = sinon.spy();
        this.set('update', update);

        stubSuccessfulUpload(server);

        await render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
        await fileUpload('input[type="file"]', ['test'], {name: 'test.png'});

        expect(update.calledOnce).to.be.true;
        expect(update.firstCall.args[0]).to.equal('/content/images/test.png');
    });

    it('doesn\'t fire update action on failed upload', async function () {
        let update = sinon.spy();
        this.set('update', update);

        stubFailedUpload(server, 500);

        await render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
        await fileUpload('input[type="file"]', ['test'], {name: 'test.png'});

        expect(update.calledOnce).to.be.false;
    });

    it('fires fileSelected action on file selection', async function () {
        let fileSelected = sinon.spy();
        this.set('fileSelected', fileSelected);

        stubSuccessfulUpload(server);

        await render(hbs`{{gh-image-uploader image=image fileSelected=(action fileSelected) update=(action update)}}`);
        await fileUpload('input[type="file"]', ['test'], {name: 'test.png'});

        expect(fileSelected.calledOnce).to.be.true;
        expect(fileSelected.args[0]).to.not.be.empty;
    });

    it('fires uploadStarted action on upload start', async function () {
        let uploadStarted = sinon.spy();
        this.set('uploadStarted', uploadStarted);

        stubSuccessfulUpload(server);

        await render(hbs`{{gh-image-uploader image=image uploadStarted=(action uploadStarted) update=(action update)}}`);
        await fileUpload('input[type="file"]', ['test'], {name: 'test.png'});

        expect(uploadStarted.calledOnce).to.be.true;
    });

    it('fires uploadFinished action on successful upload', async function () {
        let uploadFinished = sinon.spy();
        this.set('uploadFinished', uploadFinished);

        stubSuccessfulUpload(server);

        await render(hbs`{{gh-image-uploader image=image uploadFinished=(action uploadFinished) update=(action update)}}`);
        await fileUpload('input[type="file"]', ['test'], {name: 'test.png'});

        expect(uploadFinished.calledOnce).to.be.true;
    });

    it('fires uploadFinished action on failed upload', async function () {
        let uploadFinished = sinon.spy();
        this.set('uploadFinished', uploadFinished);

        stubFailedUpload(server);

        await render(hbs`{{gh-image-uploader image=image uploadFinished=(action uploadFinished) update=(action update)}}`);
        await fileUpload('input[type="file"]', ['test'], {name: 'test.png'});

        expect(uploadFinished.calledOnce).to.be.true;
    });

    it('displays invalid file type error', async function () {
        stubFailedUpload(server, 415, 'UnsupportedMediaTypeError');
        await render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
        await fileUpload('input[type="file"]', ['test'], {name: 'test.png'});

        expect(findAll('.failed').length, 'error message is displayed').to.equal(1);
        expect(find('.failed').textContent).to.match(/The image type you uploaded is not supported/);
        expect(findAll('.gh-btn-green').length, 'reset button is displayed').to.equal(1);
        expect(find('.gh-btn-green').textContent).to.equal('Try Again');
    });

    it('displays file too large for server error', async function () {
        stubFailedUpload(server, 413, 'RequestEntityTooLargeError');
        await render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
        await fileUpload('input[type="file"]', ['test'], {name: 'test.png'});

        expect(findAll('.failed').length, 'error message is displayed').to.equal(1);
        expect(find('.failed').textContent).to.match(/The image you uploaded was larger/);
    });

    it('handles file too large error directly from the web server', async function () {
        server.post('/ghost/api/v3/admin/images/upload/', function () {
            return [413, {}, ''];
        });
        await render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
        await fileUpload('input[type="file"]', ['test'], {name: 'test.png'});

        expect(findAll('.failed').length, 'error message is displayed').to.equal(1);
        expect(find('.failed').textContent).to.match(/The image you uploaded was larger/);
    });

    it('displays other server-side error with message', async function () {
        stubFailedUpload(server, 400, 'UnknownError');
        await render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
        await fileUpload('input[type="file"]', ['test'], {name: 'test.png'});

        expect(findAll('.failed').length, 'error message is displayed').to.equal(1);
        expect(find('.failed').textContent).to.match(/Error: UnknownError/);
    });

    it('handles unknown failure', async function () {
        server.post('/ghost/api/v3/admin/images/upload/', function () {
            return [500, {'Content-Type': 'application/json'}, ''];
        });
        await render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
        await fileUpload('input[type="file"]', ['test'], {name: 'test.png'});

        expect(findAll('.failed').length, 'error message is displayed').to.equal(1);
        expect(find('.failed').textContent).to.match(/Something went wrong/);
    });

    it('triggers notifications.showAPIError for VersionMismatchError', async function () {
        let showAPIError = sinon.spy();
        let notifications = this.owner.lookup('service:notifications');
        notifications.set('showAPIError', showAPIError);

        stubFailedUpload(server, 400, 'VersionMismatchError');

        await render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
        await fileUpload('input[type="file"]', ['test'], {name: 'test.png'});

        expect(showAPIError.calledOnce).to.be.true;
    });

    it('doesn\'t trigger notifications.showAPIError for other errors', async function () {
        let showAPIError = sinon.spy();
        let notifications = this.owner.lookup('service:notifications');
        notifications.set('showAPIError', showAPIError);

        stubFailedUpload(server, 400, 'UnknownError');
        await render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
        await fileUpload('input[type="file"]', ['test'], {name: 'test.png'});

        expect(showAPIError.called).to.be.false;
    });

    it('can be reset after a failed upload', async function () {
        stubFailedUpload(server, 400, 'UnknownError');
        await render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
        await fileUpload('input[type="file"]', ['test'], {type: 'test.png'});
        await click('.gh-btn-green');

        expect(findAll('input[type="file"]').length).to.equal(1);
    });

    it('handles drag over/leave', async function () {
        stubSuccessfulUpload(server);

        await render(hbs`{{gh-image-uploader image=image update=(action update)}}`);

        run(() => {
            // eslint-disable-next-line new-cap
            let dragover = $.Event('dragover', {
                dataTransfer: {
                    files: []
                }
            });
            $(find('.gh-image-uploader')).trigger(dragover);
        });
        await settled();

        expect(find('.gh-image-uploader').classList.contains('-drag-over'), 'has drag-over class').to.be.true;

        await triggerEvent('.gh-image-uploader', 'dragleave');

        expect(find('.gh-image-uploader').classList.contains('-drag-over'), 'has drag-over class').to.be.false;
    });

    it('triggers file upload on file drop', async function () {
        let uploadSuccess = sinon.spy();
        // eslint-disable-next-line new-cap
        let drop = $.Event('drop', {
            dataTransfer: {
                files: [createFile(['test'], {name: 'test.png'})]
            }
        });

        this.set('uploadSuccess', uploadSuccess);

        stubSuccessfulUpload(server);
        await render(hbs`{{gh-image-uploader uploadSuccess=(action uploadSuccess)}}`);

        run(() => {
            $(find('.gh-image-uploader')).trigger(drop);
        });
        await settled();

        expect(uploadSuccess.calledOnce).to.be.true;
        expect(uploadSuccess.firstCall.args[0]).to.equal('/content/images/test.png');
    });

    it('validates extension by default', async function () {
        let uploadSuccess = sinon.spy();
        let uploadFailed = sinon.spy();

        this.set('uploadSuccess', uploadSuccess);
        this.set('uploadFailed', uploadFailed);

        stubSuccessfulUpload(server);

        await render(hbs`{{gh-image-uploader
            uploadSuccess=(action uploadSuccess)
            uploadFailed=(action uploadFailed)}}`);

        await fileUpload('input[type="file"]', ['test'], {name: 'test.json'});

        expect(uploadSuccess.called).to.be.false;
        expect(uploadFailed.calledOnce).to.be.true;
        expect(findAll('.failed').length, 'error message is displayed').to.equal(1);
        expect(find('.failed').textContent).to.match(/The image type you uploaded is not supported/);
    });

    it('uploads if validate action supplied and returns true', async function () {
        let validate = sinon.stub().returns(true);
        let uploadSuccess = sinon.spy();

        this.set('validate', validate);
        this.set('uploadSuccess', uploadSuccess);

        stubSuccessfulUpload(server);

        await render(hbs`{{gh-image-uploader
            uploadSuccess=(action uploadSuccess)
            validate=(action validate)}}`);

        await fileUpload('input[type="file"]', ['test'], {name: 'test.txt'});

        expect(validate.calledOnce).to.be.true;
        expect(uploadSuccess.calledOnce).to.be.true;
    });

    it('skips upload and displays error if validate action supplied and doesn\'t return true', async function () {
        let validate = sinon.stub().returns(new UnsupportedMediaTypeError());
        let uploadSuccess = sinon.spy();
        let uploadFailed = sinon.spy();

        this.set('validate', validate);
        this.set('uploadSuccess', uploadSuccess);
        this.set('uploadFailed', uploadFailed);

        stubSuccessfulUpload(server);

        await render(hbs`{{gh-image-uploader
            uploadSuccess=(action uploadSuccess)
            uploadFailed=(action uploadFailed)
            validate=(action validate)}}`);

        await fileUpload('input[type="file"]', ['test'], {name: 'test.png'});

        expect(validate.calledOnce).to.be.true;
        expect(uploadSuccess.called).to.be.false;
        expect(uploadFailed.calledOnce).to.be.true;
        expect(findAll('.failed').length, 'error message is displayed').to.equal(1);
        expect(find('.failed').textContent).to.match(/The image type you uploaded is not supported/);
    });

    describe('unsplash', function () {
        it('has unsplash icon only when unsplash is active & allowed');
        it('opens unsplash modal when icon clicked');
        it('inserts unsplash image when selected');
        it('closes unsplash modal when close is triggered');
    });
});
<commit_msg>Fixed linting<commit_after>import $ from 'jquery';
import Pretender from 'pretender';
import Service from '@ember/service';
import hbs from 'htmlbars-inline-precompile';
import sinon from 'sinon';
import {UnsupportedMediaTypeError} from 'ghost-admin/services/ajax';
import {click, find, findAll, render, settled, triggerEvent} from '@ember/test-helpers';
import {createFile, fileUpload} from '../../helpers/file-upload';
import {describe, it} from 'mocha';
import {expect} from 'chai';
import {run} from '@ember/runloop';
import {setupRenderingTest} from 'ember-mocha';

const notificationsStub = Service.extend({
    showAPIError(/* error, options */) {
        // noop - to be stubbed
    }
});

const sessionStub = Service.extend({
    isAuthenticated: false,

    init() {
        this._super(...arguments);
        let authenticated = {access_token: 'AccessMe123'};
        this.authenticated = authenticated;
        this.data = {authenticated};
    }
});

const stubSuccessfulUpload = function (server, delay = 0) {
    server.post('/ghost/api/v3/admin/images/upload/', function () {
        return [200, {'Content-Type': 'application/json'}, '{"images": [{"url":"/content/images/test.png"}]}'];
    }, delay);
};

const stubFailedUpload = function (server, code, error, delay = 0) {
    server.post('/ghost/api/v3/admin/images/upload/', function () {
        return [code, {'Content-Type': 'application/json'}, JSON.stringify({
            errors: [{
                type: error,
                message: `Error: ${error}`
            }]
        })];
    }, delay);
};

describe('Integration: Component: gh-image-uploader', function () {
    setupRenderingTest();

    let server;

    beforeEach(function () {
        this.owner.register('service:session', sessionStub);
        this.owner.register('service:notifications', notificationsStub);
        this.set('update', function () {});
        server = new Pretender();
    });

    afterEach(function () {
        server.shutdown();
    });

    it('renders form with supplied alt text', async function () {
        await render(hbs`{{gh-image-uploader image=image altText="text test"}}`);
        expect(find('[data-test-file-input-description]')).to.have.trimmed.text('Upload image of "text test"');
    });

    it('renders form with supplied text', async function () {
        await render(hbs`{{gh-image-uploader image=image text="text test"}}`);
        expect(find('[data-test-file-input-description]')).to.have.trimmed.text('text test');
    });

    it('generates request to correct endpoint', async function () {
        stubSuccessfulUpload(server);

        await render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
        await fileUpload('input[type="file"]', ['test'], {name: 'test.png'});

        expect(server.handledRequests.length).to.equal(1);
        expect(server.handledRequests[0].url).to.equal('/ghost/api/v3/admin/images/upload/');
        expect(server.handledRequests[0].requestHeaders.Authorization).to.be.undefined;
    });

    it('fires update action on successful upload', async function () {
        let update = sinon.spy();
        this.set('update', update);

        stubSuccessfulUpload(server);

        await render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
        await fileUpload('input[type="file"]', ['test'], {name: 'test.png'});

        expect(update.calledOnce).to.be.true;
        expect(update.firstCall.args[0]).to.equal('/content/images/test.png');
    });

    it('doesn\'t fire update action on failed upload', async function () {
        let update = sinon.spy();
        this.set('update', update);

        stubFailedUpload(server, 500);

        await render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
        await fileUpload('input[type="file"]', ['test'], {name: 'test.png'});

        expect(update.calledOnce).to.be.false;
    });

    it('fires fileSelected action on file selection', async function () {
        let fileSelected = sinon.spy();
        this.set('fileSelected', fileSelected);

        stubSuccessfulUpload(server);

        await render(hbs`{{gh-image-uploader image=image fileSelected=(action fileSelected) update=(action update)}}`);
        await fileUpload('input[type="file"]', ['test'], {name: 'test.png'});

        expect(fileSelected.calledOnce).to.be.true;
        expect(fileSelected.args[0]).to.not.be.empty;
    });

    it('fires uploadStarted action on upload start', async function () {
        let uploadStarted = sinon.spy();
        this.set('uploadStarted', uploadStarted);

        stubSuccessfulUpload(server);

        await render(hbs`{{gh-image-uploader image=image uploadStarted=(action uploadStarted) update=(action update)}}`);
        await fileUpload('input[type="file"]', ['test'], {name: 'test.png'});

        expect(uploadStarted.calledOnce).to.be.true;
    });

    it('fires uploadFinished action on successful upload', async function () {
        let uploadFinished = sinon.spy();
        this.set('uploadFinished', uploadFinished);

        stubSuccessfulUpload(server);

        await render(hbs`{{gh-image-uploader image=image uploadFinished=(action uploadFinished) update=(action update)}}`);
        await fileUpload('input[type="file"]', ['test'], {name: 'test.png'});

        expect(uploadFinished.calledOnce).to.be.true;
    });

    it('fires uploadFinished action on failed upload', async function () {
        let uploadFinished = sinon.spy();
        this.set('uploadFinished', uploadFinished);

        stubFailedUpload(server);

        await render(hbs`{{gh-image-uploader image=image uploadFinished=(action uploadFinished) update=(action update)}}`);
        await fileUpload('input[type="file"]', ['test'], {name: 'test.png'});

        expect(uploadFinished.calledOnce).to.be.true;
    });

    it('displays invalid file type error', async function () {
        stubFailedUpload(server, 415, 'UnsupportedMediaTypeError');
        await render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
        await fileUpload('input[type="file"]', ['test'], {name: 'test.png'});

        expect(findAll('.failed').length, 'error message is displayed').to.equal(1);
        expect(find('.failed').textContent).to.match(/The image type you uploaded is not supported/);
        expect(findAll('.gh-btn-green').length, 'reset button is displayed').to.equal(1);
        expect(find('.gh-btn-green').textContent).to.equal('Try Again');
    });

    it('displays file too large for server error', async function () {
        stubFailedUpload(server, 413, 'RequestEntityTooLargeError');
        await render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
        await fileUpload('input[type="file"]', ['test'], {name: 'test.png'});

        expect(findAll('.failed').length, 'error message is displayed').to.equal(1);
        expect(find('.failed').textContent).to.match(/The image you uploaded was larger/);
    });

    it('handles file too large error directly from the web server', async function () {
        server.post('/ghost/api/v3/admin/images/upload/', function () {
            return [413, {}, ''];
        });
        await render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
        await fileUpload('input[type="file"]', ['test'], {name: 'test.png'});

        expect(findAll('.failed').length, 'error message is displayed').to.equal(1);
        expect(find('.failed').textContent).to.match(/The image you uploaded was larger/);
    });

    it('displays other server-side error with message', async function () {
        stubFailedUpload(server, 400, 'UnknownError');
        await render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
        await fileUpload('input[type="file"]', ['test'], {name: 'test.png'});

        expect(findAll('.failed').length, 'error message is displayed').to.equal(1);
        expect(find('.failed').textContent).to.match(/Error: UnknownError/);
    });

    it('handles unknown failure', async function () {
        server.post('/ghost/api/v3/admin/images/upload/', function () {
            return [500, {'Content-Type': 'application/json'}, ''];
        });
        await render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
        await fileUpload('input[type="file"]', ['test'], {name: 'test.png'});

        expect(findAll('.failed').length, 'error message is displayed').to.equal(1);
        expect(find('.failed').textContent).to.match(/Something went wrong/);
    });

    it('triggers notifications.showAPIError for VersionMismatchError', async function () {
        let showAPIError = sinon.spy();
        let notifications = this.owner.lookup('service:notifications');
        notifications.set('showAPIError', showAPIError);

        stubFailedUpload(server, 400, 'VersionMismatchError');

        await render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
        await fileUpload('input[type="file"]', ['test'], {name: 'test.png'});

        expect(showAPIError.calledOnce).to.be.true;
    });

    it('doesn\'t trigger notifications.showAPIError for other errors', async function () {
        let showAPIError = sinon.spy();
        let notifications = this.owner.lookup('service:notifications');
        notifications.set('showAPIError', showAPIError);

        stubFailedUpload(server, 400, 'UnknownError');
        await render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
        await fileUpload('input[type="file"]', ['test'], {name: 'test.png'});

        expect(showAPIError.called).to.be.false;
    });

    it('can be reset after a failed upload', async function () {
        stubFailedUpload(server, 400, 'UnknownError');
        await render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
        await fileUpload('input[type="file"]', ['test'], {type: 'test.png'});
        await click('.gh-btn-green');

        expect(findAll('input[type="file"]').length).to.equal(1);
    });

    it('handles drag over/leave', async function () {
        stubSuccessfulUpload(server);

        await render(hbs`{{gh-image-uploader image=image update=(action update)}}`);

        run(() => {
            // eslint-disable-next-line new-cap
            let dragover = $.Event('dragover', {
                dataTransfer: {
                    files: []
                }
            });
            $(find('.gh-image-uploader')).trigger(dragover);
        });
        await settled();

        expect(find('.gh-image-uploader').classList.contains('-drag-over'), 'has drag-over class').to.be.true;

        await triggerEvent('.gh-image-uploader', 'dragleave');

        expect(find('.gh-image-uploader').classList.contains('-drag-over'), 'has drag-over class').to.be.false;
    });

    it('triggers file upload on file drop', async function () {
        let uploadSuccess = sinon.spy();
        // eslint-disable-next-line new-cap
        let drop = $.Event('drop', {
            dataTransfer: {
                files: [createFile(['test'], {name: 'test.png'})]
            }
        });

        this.set('uploadSuccess', uploadSuccess);

        stubSuccessfulUpload(server);
        await render(hbs`{{gh-image-uploader uploadSuccess=(action uploadSuccess)}}`);

        run(() => {
            $(find('.gh-image-uploader')).trigger(drop);
        });
        await settled();

        expect(uploadSuccess.calledOnce).to.be.true;
        expect(uploadSuccess.firstCall.args[0]).to.equal('/content/images/test.png');
    });

    it('validates extension by default', async function () {
        let uploadSuccess = sinon.spy();
        let uploadFailed = sinon.spy();

        this.set('uploadSuccess', uploadSuccess);
        this.set('uploadFailed', uploadFailed);

        stubSuccessfulUpload(server);

        await render(hbs`{{gh-image-uploader
            uploadSuccess=(action uploadSuccess)
            uploadFailed=(action uploadFailed)}}`);

        await fileUpload('input[type="file"]', ['test'], {name: 'test.json'});

        expect(uploadSuccess.called).to.be.false;
        expect(uploadFailed.calledOnce).to.be.true;
        expect(findAll('.failed').length, 'error message is displayed').to.equal(1);
        expect(find('.failed').textContent).to.match(/The image type you uploaded is not supported/);
    });

    it('uploads if validate action supplied and returns true', async function () {
        let validate = sinon.stub().returns(true);
        let uploadSuccess = sinon.spy();

        this.set('validate', validate);
        this.set('uploadSuccess', uploadSuccess);

        stubSuccessfulUpload(server);

        await render(hbs`{{gh-image-uploader
            uploadSuccess=(action uploadSuccess)
            validate=(action validate)}}`);

        await fileUpload('input[type="file"]', ['test'], {name: 'test.txt'});

        expect(validate.calledOnce).to.be.true;
        expect(uploadSuccess.calledOnce).to.be.true;
    });

    it('skips upload and displays error if validate action supplied and doesn\'t return true', async function () {
        let validate = sinon.stub().returns(new UnsupportedMediaTypeError());
        let uploadSuccess = sinon.spy();
        let uploadFailed = sinon.spy();

        this.set('validate', validate);
        this.set('uploadSuccess', uploadSuccess);
        this.set('uploadFailed', uploadFailed);

        stubSuccessfulUpload(server);

        await render(hbs`{{gh-image-uploader
            uploadSuccess=(action uploadSuccess)
            uploadFailed=(action uploadFailed)
            validate=(action validate)}}`);

        await fileUpload('input[type="file"]', ['test'], {name: 'test.png'});

        expect(validate.calledOnce).to.be.true;
        expect(uploadSuccess.called).to.be.false;
        expect(uploadFailed.calledOnce).to.be.true;
        expect(findAll('.failed').length, 'error message is displayed').to.equal(1);
        expect(find('.failed').textContent).to.match(/The image type you uploaded is not supported/);
    });

    describe('unsplash', function () {
        it('has unsplash icon only when unsplash is active & allowed');
        it('opens unsplash modal when icon clicked');
        it('inserts unsplash image when selected');
        it('closes unsplash modal when close is triggered');
    });
});
<|endoftext|>


<commit_before>// ==UserScript==
// @name        Stack Exchange, Cross-site IP Checker
// @namespace   https://github.com/Glorfindel83/
// @description Adds a button to check activity from an IP address on other sites you're moderating
// @author      Glorfindel
// @version     0.1
// @updateURL   https://raw.githubusercontent.com/Glorfindel83/SE-Userscripts/master/cross-site-ip-checker/cross-site-ip-checker.user.js
// @downloadURL https://raw.githubusercontent.com/Glorfindel83/SE-Userscripts/master/cross-site-ip-checker/cross-site-ip-checker.user.js
// @match       *://*.stackexchange.com/admin/users-with-ip/*
// @match       *://*.stackoverflow.com/admin/users-with-ip/*
// @match       *://*.superuser.com/admin/users-with-ip/*
// @match       *://*.serverfault.com/admin/users-with-ip/*
// @match       *://*.askubuntu.com/admin/users-with-ip/*
// @match       *://*.stackapps.com/admin/users-with-ip/*
// @match       *://*.mathoverflow.net/admin/users-with-ip/*
// @connect     *
// @require     https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
// @grant       GM_xmlhttpRequest
// @grant       GM.xmlHttpRequest
// ==/UserScript==
/* global $, waitForKeyElements */

(function () {
  'use strict';

  // Add button
  let button = $('Cross-site check');
  $("h1").after(button);
  button.click(function () {
    // On which sites do I have a diamond?
    let cacheExpiration = window.localStorage.getItem("CrossSiteIPChecker-CacheExpiration");
    if (cacheExpiration != null && new Date(cacheExpiration) > new Date()) {
      checkSites();
      return;
    }

    // Read network account list
    GM.xmlHttpRequest({
      method: 'GET',
      url: 'https://stackexchange.com/users/current?tab=accounts',
      onload: function(response) {
        // Collect domains
        var hosts = [];
        let document = new DOMParser().parseFromString(response.response, 'text/html');
        for (let diamond of document.querySelectorAll("span[title='moderator']")) {
          let host = new URL(diamond.parentElement.getAttribute('href')).host;
          if (host != location.host) {
            hosts.push(host);
          }
        }

        // Cache
        var date = new Date();
        date.setDate(date.getDate() + 1);
        window.localStorage.setItem("CrossSiteIPChecker-CacheExpiration", date);
        window.localStorage.setItem("CrossSiteIPChecker-Domains", hosts.join(','));

        // Check sites
        checkSites();
      }
    });
  });
}) ();

function checkSites() {
  var found = false;
  var sitesChecked = 0;
  let hosts = window.localStorage.getItem("CrossSiteIPChecker-Domains").split(',');
  for (let host of hosts) {
    let url = 'https://' + host + location.pathname;
    GM.xmlHttpRequest({
      method: 'GET',
      url: url,
      onload: function(response) {
        // Any users found?
        let document = new DOMParser().parseFromString(response.response, 'text/html');
        if (document.getElementsByTagName("tbody").length != 0) {
          found = true;

          // Open link in new tab (only works properly if the browser is configured to
          // allow this site to open (multiple) popups)
          window.open(url, "_blank");
        }

        if (++sitesChecked == hosts.length && !found) {
          alert("No activity found on other sites you're moderating.");
        }
      }
    });
  }
}
<commit_msg>Cross-site IP Checker / Activity dump v0.2<commit_after>// ==UserScript==
// @name        Stack Exchange, Cross-site IP Checker
// @namespace   https://github.com/Glorfindel83/
// @description Adds a button to check activity from an IP address on other sites you're moderating, and one to dump all activity from a certain IP address (currently questions only)
// @author      Glorfindel
// @version     0.2
// @updateURL   https://raw.githubusercontent.com/Glorfindel83/SE-Userscripts/master/cross-site-ip-checker/cross-site-ip-checker.user.js
// @downloadURL https://raw.githubusercontent.com/Glorfindel83/SE-Userscripts/master/cross-site-ip-checker/cross-site-ip-checker.user.js
// @match       *://*.stackexchange.com/admin/users-with-ip/*
// @match       *://*.stackoverflow.com/admin/users-with-ip/*
// @match       *://*.superuser.com/admin/users-with-ip/*
// @match       *://*.serverfault.com/admin/users-with-ip/*
// @match       *://*.askubuntu.com/admin/users-with-ip/*
// @match       *://*.stackapps.com/admin/users-with-ip/*
// @match       *://*.mathoverflow.net/admin/users-with-ip/*
// @connect     *
// @require     https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
// @grant       GM_xmlhttpRequest
// @grant       GM.xmlHttpRequest
// ==/UserScript==
/* global $, waitForKeyElements */

var urls, users, questions;
const fkey = window.localStorage["se:fkey"].split(",")[0];

(function () {
  'use strict';

  // Add buttons
  let crossSiteCheckButton = $('Cross-site check');
  $("h1").after(crossSiteCheckButton);
  crossSiteCheckButton.click(function () {
    // On which sites do I have a diamond?
    let cacheExpiration = window.localStorage.getItem("CrossSiteIPChecker-CacheExpiration");
    if (cacheExpiration != null && new Date(cacheExpiration) > new Date()) {
      checkSites();
      return;
    }

    // Read network account list
    GM.xmlHttpRequest({
      method: 'GET',
      url: 'https://stackexchange.com/users/current?tab=accounts',
      onload: function(response) {
        // Collect domains
        var hosts = [];
        let document = new DOMParser().parseFromString(response.response, 'text/html');
        for (let diamond of document.querySelectorAll("span[title='moderator']")) {
          let host = new URL(diamond.parentElement.getAttribute('href')).host;
          if (host != location.host) {
            hosts.push(host);
          }
        }

        // Cache
        var date = new Date();
        date.setDate(date.getDate() + 1);
        window.localStorage.setItem("CrossSiteIPChecker-CacheExpiration", date);
        window.localStorage.setItem("CrossSiteIPChecker-Domains", hosts.join(','));

        // Check sites
        checkSites();
      }
    });
  });

  let activityDumpButton = $('Activity dump');
  $("h1").after(activityDumpButton);
  activityDumpButton.click(function () {
    urls = []; users = []; questions = [];
    for (let anchor of $("tbody a[href^='/users/']")) {
      urls.push(anchor.href);
    }
    analyzeNextUser();
  });
}) ();

function checkSites() {
  var found = false;
  var sitesChecked = 0;
  let hosts = window.localStorage.getItem("CrossSiteIPChecker-Domains").split(',');
  for (let host of hosts) {
    let url = 'https://' + host + location.pathname;
    GM.xmlHttpRequest({
      method: 'GET',
      url: url,
      onload: function(response) {
        // Any users found?
        let document = new DOMParser().parseFromString(response.response, 'text/html');
        if (document.getElementsByTagName("tbody").length != 0) {
          found = true;

          // Open link in new tab (only works properly if the browser is configured to
          // allow this site to open (multiple) popups)
          window.open(url, "_blank");
        }

        if (++sitesChecked == hosts.length && !found) {
          alert("No activity found on other sites you're moderating.");
        }
      }
    });
  }
}

function analyzeNextUser() {
  // Finished?
  let url = urls.pop();
  if (typeof(url) == 'undefined') {
    // Wait a bit, for any pending PII calls
    setTimeout(function () {
      // Users
      var message = '| Name | Email | Main IP address | Avatar |'
                + '\n|------|-------|-----------------|--------|';
      users.sort((a, b) => a.name.localeCompare(b.name));
      for (let user of users) {
        message += '\n| [' + user.name + '](//' + location.host + '/u/' + user.ID + ') | '
          + user.email + ' | '
          + user.ips[0].address + ' (' + user.ips[0].timesSeen + 'x) | '
          + '![](' + user.avatar + ') |';
      }

      // Questions
      message += '\n\n| Date | User | Question | Deleted? |'
                 + '\n|------|------|----------|----------|';
      questions.sort((a, b) => a.timestamp - b.timestamp);
      for (let question of questions) {
        let month = new Intl.DateTimeFormat('en', { month: 'short' }).format(question.timestamp);
        let day = new Intl.DateTimeFormat('en', { day: 'numeric' }).format(question.timestamp);
        message += '\n| ' + month + ' ' + day + ' | '
          + '[' + question.user.name + '](//' + location.host + '/u/' + question.user.ID + ') | '
          + '[' + question.title + '](//' + location.host + '/q/' + question.ID + ') | '
          + (question.deleted ? 'Yes' : 'No') + ' |';
      }

      // IP addresses
      message += '\n\n| IP address | # of accounts | Total times seen |'
                 + '\n|------------|--------------:|-----------------:|';
      var allIPs = [];
      var allIPsByAddress = {};
      for (let user of users) {
        for (let ip of user.ips) {
          var allIP;
          if (ip.address in allIPsByAddress) {
            allIP = allIPsByAddress[ip.address];
          } else {
            allIP = {};
            allIP.address = ip.address;
            allIP.accounts = 0;
            allIP.timesSeen = 0;
            allIPs.push(allIP);
            allIPsByAddress[ip.address] = allIP;
          }
          allIP.accounts += 1;
          allIP.timesSeen += ip.timesSeen;
        }
      }
      allIPs.sort((a, b) => {
        if (a.accounts != b.accounts)
          return b.accounts - a.accounts;
        return b.timesSeen - a.timesSeen;
      });
      for (let allIP of allIPs) {
        message += '\n| [' + allIP.address + '](//' + location.host + '/admin/users-with-ip/' + allIP.address + ') | '
          + allIP.accounts + ' | ' + allIP.timesSeen + ' |';
      }

      console.log(message);
    }, 2000);
    return;
  }

  // Read profile
  $.get(url + '?tab=topactivity', function(data) {
    let $activity = $(data);

    // User
    var user = {};
    user.ID = url.split('/users/')[1];
    user.name = $activity.find(".fs-headline2").text().trim();
    console.log('Checking ' + user.name);
    user.avatar = $activity.find("#mainbar-full img[alt='user avatar']")[0].getAttribute('src').replace('s=256', 's=64');
    users.push(user);

    // PII
    $.post('https://' + location.host + '/admin/pii', {
      field: user.ID + ":email",
      fkey: fkey
    }, function(data) {
      user.email = $(data).text().trim();
    });
    $.get('https://' + location.host + '/admin/show-user-ips/' + user.ID, function(data) {
      user.ips = [];
      for (let row of $(data).find("tbody tr")) {
        let $row = $(row);
        var ip = {};
        ip.address = $row.children()[0].innerText;
        ip.timesSeen = parseInt($row.children()[1].innerText);
        user.ips.push(ip);
      }
    });

    // Questions
    let $questions = $activity.find("#user-panel-questions");
    for (let $question of $questions.find("a.question-hyperlink")) {
      var question = {};
      question.ID = $question.getAttribute('href').split('/')[2];
      question.title = $question.title;
      question.deleted = $($question).closest(".bg-red-050").length > 0;
      question.timestamp = Date.parse(($($question).parent().parent().find("span.relativetime")[0]).title);
      question.user = user;
      questions.push(question);
    }

    analyzeNextUser();
  });
}
<|endoftext|>


<commit_before>/***
 *
 * Paper.js
 *
 * A JavaScript Vector Graphics Library, based on Scriptographer.org and
 * designed to be largely API compatible.
 * http://paperjs.org/
 * http://scriptographer.org/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
 * http://lehni.org/ & http://jonathanpuckey.com/
 *
 * All rights reserved.
 *
 ***
 *
 * Bootstrap.js JavaScript Framework.
 * http://bootstrapjs.org/
 *
 * Distributed under the MIT license.
 *
 * Copyright (c) 2006 - 2011 Juerg Lehni
 * http://lehni.org/
 *
 ***
 *
 * Parse-JS, A JavaScript tokenizer / parser / generator.
 *
 * Distributed under the BSD license.
 *
 * Copyright (c) 2010, Mihai Bazon <mihai.bazon@gmail.com>
 * http://mihai.bazon.net/blog/
 *
 ***/

var paper = new function() {

// Inline Bootstrap core (the Base class) inside the paper scope first:
//#include "../lib/bootstrap.js"

var PaperScope = Base.extend({
	initialize: function() {
		this.document = null;
		this.documents = [];
		this.tools = [];
	},

	/**
	 * Installs the paper scope into any other given scope. Can be used for
	 * examle to inject the currently active PaperScope into the window's global
	 * scope, to emulate PaperScript-style globally accessible Paper classes:
	 *
	 * paper.install(window);
	 */
	install: function(scope) {
		for (var i in this) {
			scope[i] = this[i];
		}
	}
});

// Extend Base with utility functions used across the library.
Base.inject({
	statics: true,

	read: function(list, start, length) {
		var start = start || 0,
			length = length || list.length - start;
		var obj = list[start];
		if (obj instanceof this
				// If the class defines _readNull, return null when nothing
				// was provided
				|| this.prototype._readNull && obj == null && length <= 1)
			return obj;
		obj = new this(this.dont);
		return obj.initialize.apply(obj, start > 0 || length < list.length
			? Array.prototype.slice.call(list, start, start + length)
			: list) || obj;
	},

	readAll: function(list, start) {
		var res = [], entry;
		for (var i = start || 0, l = list.length; i < l; i++) {
			res.push(Array.isArray(entry = list[i])
				? this.read(entry, 0)
				: this.read(list, i, 1));
		}
		return res;
	},

	/**
	 * Utility function for adding and removing items from a list of which
	 * each entry keeps a reference to its index in the list in the private
	 * _index property. Used for paper.documents and Item#children.
	 */
	splice: function(list, items, index, remove) {
		var amount = items && items.length,
			append = index === undefined;
		index = append ? list.length : index;
		// Update _index on the items to be added first.
		for (var i = 0; i < amount; i++) {
			items[i]._index = index + i;
		}
		if (append) {
			// Append them all at the end by using push
			list.push.apply(list, items);
			// Nothing removed, and nothing to adjust above
			return [];
		} else {
			// Insert somewhere else and/or remove
			var args = [index, remove];
			if (items)
				args.push.apply(args, items);
			var removed = list.splice.apply(list, args);
			// Adjust the indices of the items above.
			for (var i = index + amount, l = list.length; i < l; i++) {
				list[i]._index = i;
			}
			return removed;
		}
	},

	capitalize: function(str) {
		return str.replace(/\b[a-z]/g, function(match) {
			return match.toUpperCase();
		});
	},

	camelize: function(str) {
		return str.replace(/-(\w)/g, function(all, chr) {
			return chr.toUpperCase();
		});
	},

	/**
	 * Utility function for rendering numbers to strings at a precision of up
	 * to 5 fractional digits.
	 */
	formatNumber: function(num) {
		return (Math.round(num * 100000) / 100000).toString();
	},

	/**
	 * Utility function for rendering objects to strings, in object literal
	 * notation.
	 */
	formatObject: function(obj) {
		return '{ ' + Base.each(obj, function(value, key) {
			this.push(key + ': ' + value);
		}, []).join(', ') + ' }';
	}
});

//#include "basic/Point.js"
//#include "basic/Size.js"
//#include "basic/Rectangle.js"
//#include "basic/Matrix.js"
//#include "basic/Line.js"

//#include "document/DocumentView.js"
//#include "document/Document.js"
//#include "document/Symbol.js"

//#include "item/ChangeFlags.js"
//#include "item/Item.js"
//#include "item/Group.js"
//#include "item/Layer.js"
//#include "item/Raster.js"
//#include "item/PlacedSymbol.js"
//#include "item/PathStyle.js"

//#include "path/Segment.js"
//#include "path/SegmentPoint.js"
//#include "path/SelectionState.js"
//#include "path/Curve.js"
//#include "path/CurveLocation.js"
//#include "path/PathItem.js"
//#include "path/Path.js"
//#include "path/CompoundPath.js"
//#include "path/Path.Constructors.js"

//#include "color/Color.js"
//#include "color/RGBColor.js"
//#include "color/HSBColor.js"
//#include "color/GrayColor.js"
//#include "color/GradientColor.js"
//#include "color/Gradient.js"
//#include "color/GradientStop.js"

//#ifdef BROWSER

//#include "browser/DomElement.js"
//#include "browser/DomEvent.js"

//#include "ui/Event.js"
//#include "ui/KeyEvent.js"
//#include "ui/Key.js"

//#include "tool/ToolEvent.js"
//#include "tool/ToolHandler.js"
//#include "tool/Tool.js"

//#endif // BROWSER

//#include "util/CanvasProvider.js"
//#include "util/Numerical.js"
//#include "util/PaperScript.js"
//#include "util/BlendMode.js"

// new create the first Paper scope and return it.

return new PaperScope();
};
<commit_msg>Improve comments.<commit_after>/***
 *
 * Paper.js
 *
 * A JavaScript Vector Graphics Library, based on Scriptographer.org and
 * designed to be largely API compatible.
 * http://paperjs.org/
 * http://scriptographer.org/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
 * http://lehni.org/ & http://jonathanpuckey.com/
 *
 * All rights reserved.
 *
 ***
 *
 * Bootstrap.js JavaScript Framework.
 * http://bootstrapjs.org/
 *
 * Distributed under the MIT license.
 *
 * Copyright (c) 2006 - 2011 Juerg Lehni
 * http://lehni.org/
 *
 ***
 *
 * Parse-JS, A JavaScript tokenizer / parser / generator.
 *
 * Distributed under the BSD license.
 *
 * Copyright (c) 2010, Mihai Bazon <mihai.bazon@gmail.com>
 * http://mihai.bazon.net/blog/
 *
 ***/

var paper = new function() {
// Inline Bootstrap core (the Base class) inside the paper scope first:
//#include "../lib/bootstrap.js"

/**
 * Define internal PaperScope class that handles all the fields available on the
 * global paper object, which simply is a pointer to the currently active scope.
 */
var PaperScope = Base.extend({
	initialize: function() {
		this.document = null;
		this.documents = [];
		this.tools = [];
	},

	/**
	 * Installs the paper scope into any other given scope. Can be used for
	 * examle to inject the currently active PaperScope into the window's global
	 * scope, to emulate PaperScript-style globally accessible Paper classes:
	 *
	 * paper.install(window);
	 */
	install: function(scope) {
		for (var i in this) {
			scope[i] = this[i];
		}
	}
});

// Extend Base with utility functions used across the library.
Base.inject({
	statics: true,

	read: function(list, start, length) {
		var start = start || 0,
			length = length || list.length - start;
		var obj = list[start];
		if (obj instanceof this
				// If the class defines _readNull, return null when nothing
				// was provided
				|| this.prototype._readNull && obj == null && length <= 1)
			return obj;
		obj = new this(this.dont);
		return obj.initialize.apply(obj, start > 0 || length < list.length
			? Array.prototype.slice.call(list, start, start + length)
			: list) || obj;
	},

	readAll: function(list, start) {
		var res = [], entry;
		for (var i = start || 0, l = list.length; i < l; i++) {
			res.push(Array.isArray(entry = list[i])
				? this.read(entry, 0)
				: this.read(list, i, 1));
		}
		return res;
	},

	/**
	 * Utility function for adding and removing items from a list of which
	 * each entry keeps a reference to its index in the list in the private
	 * _index property. Used for paper.documents and Item#children.
	 */
	splice: function(list, items, index, remove) {
		var amount = items && items.length,
			append = index === undefined;
		index = append ? list.length : index;
		// Update _index on the items to be added first.
		for (var i = 0; i < amount; i++) {
			items[i]._index = index + i;
		}
		if (append) {
			// Append them all at the end by using push
			list.push.apply(list, items);
			// Nothing removed, and nothing to adjust above
			return [];
		} else {
			// Insert somewhere else and/or remove
			var args = [index, remove];
			if (items)
				args.push.apply(args, items);
			var removed = list.splice.apply(list, args);
			// Adjust the indices of the items above.
			for (var i = index + amount, l = list.length; i < l; i++) {
				list[i]._index = i;
			}
			return removed;
		}
	},

	capitalize: function(str) {
		return str.replace(/\b[a-z]/g, function(match) {
			return match.toUpperCase();
		});
	},

	camelize: function(str) {
		return str.replace(/-(\w)/g, function(all, chr) {
			return chr.toUpperCase();
		});
	},

	/**
	 * Utility function for rendering numbers to strings at a precision of up
	 * to 5 fractional digits.
	 */
	formatNumber: function(num) {
		return (Math.round(num * 100000) / 100000).toString();
	},

	/**
	 * Utility function for rendering objects to strings, in object literal
	 * notation.
	 */
	formatObject: function(obj) {
		return '{ ' + Base.each(obj, function(value, key) {
			this.push(key + ': ' + value);
		}, []).join(', ') + ' }';
	}
});

//#include "basic/Point.js"
//#include "basic/Size.js"
//#include "basic/Rectangle.js"
//#include "basic/Matrix.js"
//#include "basic/Line.js"

//#include "document/DocumentView.js"
//#include "document/Document.js"
//#include "document/Symbol.js"

//#include "item/ChangeFlags.js"
//#include "item/Item.js"
//#include "item/Group.js"
//#include "item/Layer.js"
//#include "item/Raster.js"
//#include "item/PlacedSymbol.js"
//#include "item/PathStyle.js"

//#include "path/Segment.js"
//#include "path/SegmentPoint.js"
//#include "path/SelectionState.js"
//#include "path/Curve.js"
//#include "path/CurveLocation.js"
//#include "path/PathItem.js"
//#include "path/Path.js"
//#include "path/CompoundPath.js"
//#include "path/Path.Constructors.js"

//#include "color/Color.js"
//#include "color/RGBColor.js"
//#include "color/HSBColor.js"
//#include "color/GrayColor.js"
//#include "color/GradientColor.js"
//#include "color/Gradient.js"
//#include "color/GradientStop.js"

//#ifdef BROWSER

//#include "browser/DomElement.js"
//#include "browser/DomEvent.js"

//#include "ui/Event.js"
//#include "ui/KeyEvent.js"
//#include "ui/Key.js"

//#include "tool/ToolEvent.js"
//#include "tool/ToolHandler.js"
//#include "tool/Tool.js"

//#endif // BROWSER

//#include "util/CanvasProvider.js"
//#include "util/Numerical.js"
//#include "util/PaperScript.js"
//#include "util/BlendMode.js"

// Now create the first PaperScope and return it.
return new PaperScope();
};
<|endoftext|>


<commit_before>describe('Global helpers', function () {

  describe('classes helper', function () {

    it('should return the template and path of a page without the forward slash', function () {

      // - - SETUP
      spyOn(Letterpress.Collections.Pages, 'findOne').and.returnValue({
        _id: 'someId',
        template: 'the-template',
        path: '/some-page'
      });

      // - - EXECUTE
      var classes = UI._globalHelpers['classes']();

      // - - VERIFY
      expect(Letterpress.Collections.Pages.findOne).toHaveBeenCalledWith({path: window.location.pathname});
      expect(classes).toBe('the-template some-page');

    });

    it('should return an empty string if no page is found', function () {

      // - - SETUP
      spyOn(Letterpress.Collections.Pages, 'findOne').and.returnValue(null);

      // - - EXECUTE
      var classes = UI._globalHelpers['classes']();

      // - - VERIFY
      expect(classes).toBe('');

    });

  });

});<commit_msg>updated assertion to match helper<commit_after>describe('Global helpers', function () {

  describe('classes helper', function () {

    it('should return the template and path of a page without the forward slash', function () {

      // - - SETUP
      spyOn(Letterpress.Collections.Pages, 'findOne').and.returnValue({
        _id: 'someId',
        template: 'the-template',
        path: '/some-page'
      });

      // - - EXECUTE
      var classes = UI._globalHelpers['classes']();

      // - - VERIFY
      expect(Letterpress.Collections.Pages.findOne).toHaveBeenCalledWith({path: Iron.Location.get().path});
      expect(classes).toBe('the-template some-page');

    });

    it('should return an empty string if no page is found', function () {

      // - - SETUP
      spyOn(Letterpress.Collections.Pages, 'findOne').and.returnValue(null);

      // - - EXECUTE
      var classes = UI._globalHelpers['classes']();

      // - - VERIFY
      expect(classes).toBe('');

    });

  });

});<|endoftext|>


<commit_before>module.exports = function () {
    'use strict';

    var ArgumentParser = require('argparse').ArgumentParser;
    var parser = new ArgumentParser({
        addHelp: true
    });

    // flags
    parser.addArgument('-K', {
        choices: ['circo', 'dot', 'fdp', 'neato', 'osage', 'twopi'],
        metavar: 'layout',
        help: 'Specifies which default layout algorithm to use, overriding the default from the executable.'
    });
    parser.addArgument('-T', {
        choices: ['svg', 'xdot', 'plain', 'ps'],
        defaultValue: 'xdot',
        metavar: 'format',
        help: 'Set output language to one of the supported formats. By default, xdot is produced.'
    });
    parser.addArgument('-V', {
        action: 'storeTrue',
        help: 'Emit version information and exit.'
    });
    parser.addArgument('-o', {
        metavar: 'outfile',
        help: 'Write output to file outfile. By default, output goes to stdout.'
    });

    // pos args
    parser.addArgument('files', {
        nargs: '*',
        metavar: 'file',
        help: 'Specifies the input files. If no input files are supplied, the program reads from stdin.'
    });

    var args = parser.parseArgs();
    args.prog = parser.prog;
    return args;
};
<commit_msg>usage: file... -> input file...<commit_after>module.exports = function () {
    'use strict';

    var ArgumentParser = require('argparse').ArgumentParser;
    var parser = new ArgumentParser({
        addHelp: true
    });

    // flags
    parser.addArgument('-K', {
        choices: ['circo', 'dot', 'fdp', 'neato', 'osage', 'twopi'],
        metavar: 'layout',
        help: 'Specifies which default layout algorithm to use, overriding the default from the executable.'
    });
    parser.addArgument('-T', {
        choices: ['svg', 'xdot', 'plain', 'ps'],
        defaultValue: 'xdot',
        metavar: 'format',
        help: 'Set output language to one of the supported formats. By default, xdot is produced.'
    });
    parser.addArgument('-V', {
        action: 'storeTrue',
        help: 'Emit version information and exit.'
    });
    parser.addArgument('-o', {
        metavar: 'outfile',
        help: 'Write output to file outfile. By default, output goes to stdout.'
    });

    // pos args
    parser.addArgument('files', {
        nargs: '*',
        metavar: 'input file',
        help: 'Specifies the input files. If no input files are supplied, the program reads from stdin.'
    });

    var args = parser.parseArgs();
    args.prog = parser.prog;
    return args;
};
<|endoftext|>


<commit_before>( function( mw, $ ) {"use strict";

	mw.PluginManager.add( 'related', mw.KBaseComponent.extend({

		defaultConfig: {
			parent: "topBarContainer",
         	order: 4,
         	align: "right",
         	//template: "

",
         	template: '<% _.each(items, function(item, idx) { %> \
         					<% if( idx == 0 ) { var className = "medium"; } else { var className = "small"; } %> \
         					<div class="item <%=className%>">
 \

<%=item.name%>
 \
         					<img src="<%=item.thumbnailUrl%>/width/350" />

<% }); %>',
         	playlistId: "1_qui13sz2",
		},
		$screen: null,
		setup: function(){
			var _this = this;

			// Hogan templates
			this.template = _.template( this.getConfig('template', true) );

			this.bind('playerReady', function(){
				_this.getItemsData(function(){
					if( _this.$screen ){
						_this.$screen.remove();
					}
				});
			});
		},
		getItemsData: function( callback ){
			if( !this.itemsData ){
				var _this = this;
				this.getKalturaClient().doRequest( {
					'service' : 'playlist',
					'action' : 'execute',
					'id' : this.getConfig( 'playlistId' ),
					'filter:objectType': 'KalturaMediaEntryFilterForPlaylist',
					'filter:idNotIn': this.getPlayer().kentryid,
					'filter:limit': 12
				}, function( data ){
					// Check if we got error
					if( data.code && data.message ){
						_this.log('Error getting related items: ' + data.message);
						_this.getBtn().hide();
					}
					_this.itemsData = data;
					callback();
				});
				return;
			}
			callback();
			return;
		},
		getItems: function(){
			return this.template({items: this.itemsData});
		},
		hide: function(){
			this.opened = false;
			this.getScreen().hide();
			this.getPlayer().restoreComponentsHover();
		},
		show: function(){
			this.opened = true;
			this.getPlayer().disableComponentsHover();
			this.getScreen().show();
		},
		toggle: function(){
			if( this.opened ){
				this.hide();
			} else {
				this.show();
			}
		},
		getScreen: function(){
			if( ! this.$screen ){
				var _this = this;
				this.$screen = $('
')
									.addClass( 'screen ' + this.pluginName )
									.append(
										$('
 ').append(
											_this.getItems()
										)
									)
									.hide();

				this.$screen.find('.item').hover(function(){
					$( this ).addClass('hover');
				},function(){
					$( this ).removeClass('hover');
				});

				this.getPlayer().getVideoHolder().append( this.$screen );
			}
			return this.$screen;
		},
		getComponent: function() {
			if( !this.$el ) {
				var _this = this;
				this.$el = $( '<button />' )
							.attr( 'title', this.playTitle )
							.addClass( "btn icon-related" + this.getCssClass() )
							.click( function(){
								_this.toggle();
							});

			}
			return this.$el;
		}
	}));

} )( window.mw, window.jQuery );<commit_msg>Related: updated template, hide screen on play, show screen on playback done<commit_after>( function( mw, $ ) {"use strict";

	mw.PluginManager.add( 'related', mw.KBaseComponent.extend({

		defaultConfig: {
			parent: "topBarContainer",
         	order: 4,
         	align: "right",
         	//visible: false,
         	itemsLimit: 12,
         	displayOnPlaybackDone: true,
         	autoContinueTime: null,
         	nextItemText: "Next: {mediaProxy.entry.name} in {related.timeRemaining|timeFormat}",
         	template: '<% _.each(items, function(item, idx) { %> \
         					<% if( idx == 0 ) { var className = "medium"; } else { var className = "small"; } %> \
         					<div class="item <%=className%>" data-entryid="<%=item.id%>">
 \

<%=item.name%>
 \
         					<img src="<%=item.thumbnailUrl%>/width/350" />

<% }); %>',
         	playlistId: null,
		},
		$screen: null,
		setup: function(){
			var _this = this;

			// Underscore templates
			this.template = _.template( this.getConfig('template', true) );

			this.bind('playerReady', function(){
				_this.itemsData = null;
				_this.getItemsData(function(){
					if( _this.$screen ){
						_this.$screen.remove();
						_this.$screen = null;
					}
				});
			});

			this.bind('onplay', function(){
				_this.hide();
			});

			if( this.getConfig('displayOnPlaybackDone') ){
				this.bind('onEndedDone', function(){
					_this.show();
					//_this.startTimer();
				});
			}
		},
		getItemsData: function( callback ){
			if( !this.itemsData ){
				var _this = this;
				this.getKalturaClient().doRequest( {
					'service' : 'playlist',
					'action' : 'execute',
					'id' : this.getConfig( 'playlistId' ),
					'filter:objectType': 'KalturaMediaEntryFilterForPlaylist',
					'filter:idNotIn': this.getPlayer().kentryid,
					'filter:limit': 12
				}, function( data ){
					// Check if we got error
					if( data.code && data.message ){
						_this.log('Error getting related items: ' + data.message);
						_this.getBtn().hide();
					}
					_this.itemsData = data;
					callback();
				});
				return;
			}
			callback();
			return;
		},
		getItems: function(){
			return this.template({items: this.itemsData});
		},
		hide: function(){
			this.opened = false;
			this.getScreen().hide();
			this.getPlayer().restoreComponentsHover();
		},
		show: function(){
			this.opened = true;
			this.getPlayer().disableComponentsHover();
			this.getScreen().show();
		},
		toggle: function(){
			if( this.opened ){
				this.hide();
			} else {
				this.show();
			}
		},
		getScreen: function(){
			if( ! this.$screen ){
				var _this = this;
				this.$screen = $('
')
									.addClass( 'screen ' + this.pluginName )
									.append(
										$('
 ').append(
											_this.getItems()
										)
									)
									.hide();

				this.$screen.find('.item').hover(function(){
					$( this ).addClass('hover');
				},function(){
					$( this ).removeClass('hover');
				});

				this.getPlayer().getVideoHolder().append( this.$screen );
			}
			return this.$screen;
		},
		getComponent: function() {
			if( !this.$el ) {
				var _this = this;
				this.$el = $( '<button />' )
							.attr( 'title', this.playTitle )
							.addClass( "btn icon-related" + this.getCssClass() )
							.click( function(){
								_this.toggle();
							});

			}
			return this.$el;
		}
	}));

} )( window.mw, window.jQuery );<|endoftext|>


<commit_before>/*jslint node: true */

/*
 * #%L
 * %%
 * Copyright (C) 2017 BMW Car IT GmbH
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */

var joynr = require("joynr");
var testbase = require("test-base");

var prettyLog = testbase.logging.prettyLog;
var error = testbase.logging.error;
var log = testbase.logging.log;

exports.implementation = {

    simpleAttribute: {

        set : function() {
        },

        get: function () {

        }
    },

    echoString: function(opArgs) {
        return { responseData: opArgs.data };
    },

    echoByteArray: function(opArgs) {
        return new Promise(function(resolve, reject) {
            if (opArgs.data === undefined) {
                reject(new joynr.exceptions.ProviderRuntimeException(
                        { detailMessage: "echoByteArray: invalid argument data" }));
            } else {
                resolve({ responseData: opArgs.data });
            }
        });
    },

    echoComplexStruct: function(opArgs) {
        return new Promise(function(resolve, reject) {
            if (opArgs.data === undefined) {
                reject(new joynr.exceptions.ProviderRuntimeException(
                        { detailMessage: "echoComplexStruct: invalid argument data" }));
            } else {
                resolve({ responseData: opArgs.data });
            }
        });
    }
};

self = exports.implementation;
<commit_msg>[JS] performance-tests simplify providerImpl<commit_after>/*jslint node: true */

/*
 * #%L
 * %%
 * Copyright (C) 2017 BMW Car IT GmbH
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */

var joynr = require("joynr");
var testbase = require("test-base");

var prettyLog = testbase.logging.prettyLog;
var error = testbase.logging.error;
var log = testbase.logging.log;

exports.implementation = {
    simpleAttribute: {
        set: function() {},

        get: function() {}
    },

    echoString: function(opArgs) {
        return { responseData: opArgs.data };
    },

    echoByteArray: function(opArgs) {
        if (opArgs.data === undefined) {
            throw new joynr.exceptions.ProviderRuntimeException({
                detailMessage: "echoByteArray: invalid argument data"
            });
        } else {
            return { responseData: opArgs.data };
        }
    },

    echoComplexStruct: function(opArgs) {
        if (opArgs.data === undefined) {
            throw new joynr.exceptions.ProviderRuntimeException({
                detailMessage: "echoComplexStruct: invalid argument data"
            });
        } else {
            return { responseData: opArgs.data };
        }
    }
};

self = exports.implementation;
<|endoftext|>


<commit_before>'use strict';

var Immutable = require('immutable');
var path = require('./path');

var tryParseInt = function(n) {
  var int = parseInt(n);
  return isNaN(int) ? n : int;
};

var primitivePatch = function (op, value) {
  if (op === 'add' || op === 'replace') {
    return value;
  } else if (op === 'remove') {
    return null;
  }
};

var mapPatch = function(map, firstPath, restPath, op, value) {
  if (op === 'add') {
    if (restPath.length > 0 && map.get(firstPath) === undefined) {
      var baseValue = (restPath[0].match(/^\d+$/)) ? Immutable.List() : Immutable.Map();
      return map.set(firstPath, anyPatch(baseValue, restPath, op, value));
    } else {
      return map.set(firstPath, anyPatch(map.get(firstPath), restPath, op, value));
    }
  } else if (op === 'replace') {
    if (restPath.length > 0) {
      return map.set(firstPath, anyPatch(map.get(firstPath), restPath, op, value));
    } else {
      return map.set(firstPath, value);
    }
  } else if (op === 'remove') {
    if (restPath.length > 0) {
      return map.set(firstPath, anyPatch(map.get(firstPath), restPath, op, value));
    } else {
      return map.remove(firstPath);
    }
  } else {
    throw new Error('map patch Error, unknown op: ' + op);
  }
};

var sequencePatch = function(sequence, firstPath, restPath, op, value) {
  firstPath = tryParseInt(firstPath);
  if (op === 'add') {
    if (sequence.get(firstPath) === undefined) {
      if (restPath.length > 0) {
        var baseValue = (restPath[0].match(/^\d+$/)) ? Immutable.List() : Immutable.Map();
        return sequence.set(firstPath, anyPatch(baseValue, restPath, op, value));
      } else {
        // special case, add to the end
        if (firstPath === '-') {
          return sequence.splice(sequence.size, 0, value);
        }
        // special case, return the value
        return sequence.splice(firstPath, 0, value);
      }
    } else {
      if (restPath.length > 0) {
        return sequence.set(firstPath, anyPatch(sequence.get(firstPath), restPath, op, value));
      } else {
        // special case, return the value
        return sequence.splice(firstPath, 0, value);
      }
    }
  } else if (op === 'replace') {
    if (restPath.length > 0) {
      return sequence.set(firstPath, anyPatch(sequence.get(firstPath), restPath, op, value));
    } else {
      return sequence.set(firstPath, value);
    }
  } else if (op === 'remove') {
    if (restPath.length > 0) {
      return sequence.set(firstPath, anyPatch(sequence.get(firstPath), restPath, op, value));
    } else {
      return sequence.remove(firstPath);
    }
  } else {
    throw new Error('sequence patch Error, unknown op: ' + op);
  }
};

var anyPatch = function(any, pathArray, op, value) {
  var firstPath, restPath;

  if (Immutable.Iterable.isKeyed(any)) {
    if (pathArray.length === 0) { return any; }
    firstPath = pathArray[0];
    restPath = pathArray.slice(1);
    return mapPatch(any, firstPath, restPath, op, value);
  } else if (Immutable.Iterable.isIndexed(any)) {
    if (pathArray.length === 0) { return any; }
    firstPath = pathArray[0];
    restPath = pathArray.slice(1);
    return sequencePatch(any, firstPath, restPath, op, value);
  } else {
    if (pathArray.length === 0) { return value; }
    return primitivePatch(op, value);
  }
};

var eachPatchInternal = function(value, patches) {
  while (patches.size) {
    var firstPatch = patches.get(0);
    var patches = patches.slice(1);
    var pathArray = firstPatch.get('path').split('/').slice(1).map(path.unescape);
    value = anyPatch(value, pathArray, firstPatch.get('op'), firstPatch.get('value'));
  }
  return value;
};

var eachPatch = function(value, patches) {
  if (patches.size === 1) {
    var onlyPatch = patches.get(0);
    if (onlyPatch.get('op') === 'replace' && onlyPatch.get('path') === '/') {
      return onlyPatch.get('value');
    }
  }
  return eachPatchInternal(value, patches);
};

eachPatch.default = eachPatch;
module.exports = eachPatch;
<commit_msg>Adding support for immutable records<commit_after>'use strict';

var Immutable = require('immutable');
var path = require('./path');

var tryParseInt = function(n) {
  var int = parseInt(n);
  return isNaN(int) ? n : int;
};

var primitivePatch = function (op, value) {
  if (op === 'add' || op === 'replace') {
    return value;
  } else if (op === 'remove') {
    return null;
  }
};

var mapPatch = function(map, firstPath, restPath, op, value) {
  if (op === 'add') {
    if (restPath.length > 0 && map.get(firstPath) === undefined) {
      var baseValue = (restPath[0].match(/^\d+$/)) ? Immutable.List() : Immutable.Map();
      return map.set(firstPath, anyPatch(baseValue, restPath, op, value));
    } else {
      return map.set(firstPath, anyPatch(map.get(firstPath), restPath, op, value));
    }
  } else if (op === 'replace') {
    if (restPath.length > 0) {
      return map.set(firstPath, anyPatch(map.get(firstPath), restPath, op, value));
    } else {
      return map.set(firstPath, value);
    }
  } else if (op === 'remove') {
    if (restPath.length > 0) {
      return map.set(firstPath, anyPatch(map.get(firstPath), restPath, op, value));
    } else {
      return map.remove(firstPath);
    }
  } else {
    throw new Error('map patch Error, unknown op: ' + op);
  }
};

var sequencePatch = function(sequence, firstPath, restPath, op, value) {
  firstPath = tryParseInt(firstPath);
  if (op === 'add') {
    if (sequence.get(firstPath) === undefined) {
      if (restPath.length > 0) {
        var baseValue = (restPath[0].match(/^\d+$/)) ? Immutable.List() : Immutable.Map();
        return sequence.set(firstPath, anyPatch(baseValue, restPath, op, value));
      } else {
        // special case, add to the end
        if (firstPath === '-') {
          return sequence.splice(sequence.size, 0, value);
        }
        // special case, return the value
        return sequence.splice(firstPath, 0, value);
      }
    } else {
      if (restPath.length > 0) {
        return sequence.set(firstPath, anyPatch(sequence.get(firstPath), restPath, op, value));
      } else {
        // special case, return the value
        return sequence.splice(firstPath, 0, value);
      }
    }
  } else if (op === 'replace') {
    if (restPath.length > 0) {
      return sequence.set(firstPath, anyPatch(sequence.get(firstPath), restPath, op, value));
    } else {
      return sequence.set(firstPath, value);
    }
  } else if (op === 'remove') {
    if (restPath.length > 0) {
      return sequence.set(firstPath, anyPatch(sequence.get(firstPath), restPath, op, value));
    } else {
      return sequence.remove(firstPath);
    }
  } else {
    throw new Error('sequence patch Error, unknown op: ' + op);
  }
};

var isRecord = function(any) {
  return (
    any != null
    && typeof any.updateIn === 'function'
    && typeof any.set === 'function'
  )
}

var anyPatch = function(any, pathArray, op, value) {
  var firstPath, restPath;

  if (Immutable.Iterable.isIndexed(any)) {
   if (pathArray.length === 0) { return any; }
   firstPath = pathArray[0];
   restPath = pathArray.slice(1);
   return sequencePatch(any, firstPath, restPath, op, value);
  } else if (Immutable.Iterable.isKeyed(any) || isRecord(any)) {
    // if the object is a record or a keyed iterable immutable object
    if (pathArray.length === 0) { return any; }
    firstPath = pathArray[0];
    restPath = pathArray.slice(1);
    return mapPatch(any, firstPath, restPath, op, value);
  } else {
    if (pathArray.length === 0) { return value; }
    return primitivePatch(op, value);
  }
};

var eachPatchInternal = function(value, patches) {
  while (patches.size) {
    var firstPatch = patches.get(0);
    var patches = patches.slice(1);
    var pathArray = firstPatch.get('path').split('/').slice(1).map(path.unescape);
    value = anyPatch(value, pathArray, firstPatch.get('op'), firstPatch.get('value'));
  }
  return value;
};

var eachPatch = function(value, patches) {
  if (patches.size === 1) {
    var onlyPatch = patches.get(0);
    if (onlyPatch.get('op') === 'replace' && onlyPatch.get('path') === '/') {
      return onlyPatch.get('value');
    }
  }
  return eachPatchInternal(value, patches);
};

eachPatch.default = eachPatch;
module.exports = eachPatch;
<|endoftext|>


<commit_before>/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Schmoopiie
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

var filesystem = require('fs');
var util       = require('util');

var details    = false;
var ignored    = [];
var timestamp  = true;

function smartTrim(str, length, delim, appendix) {
    if (str.length <= length) return str;

    var trimmedStr = str.substr(0, length+delim.length);

    var lastDelimIndex = trimmedStr.lastIndexOf(delim);
    if (lastDelimIndex >= 0) trimmedStr = trimmedStr.substr(0, lastDelimIndex);

    if (trimmedStr) trimmedStr += appendix;
    return trimmedStr;
}

/* Ensure that the directory exists */
function ensureExists(path, mask, cb) {
    if (typeof mask == 'function') {
        cb = mask;
        mask = 0777;
    }

    filesystem.mkdir(path, mask, function(err) {
        if (err) {
            if (err.code == 'EEXIST') cb(null);
            else cb(err);
        } else cb(null);
    });
}

/* Our awesome logger */
function Logger(config) {
    var self    = this;
    var config  = config || {};
    var options = config.options || {};
    var debug   = options.debug || false;
    var dev     = options.dev || false;
    var logging = config.logging || {};

    details = options.debugDetails || false;
    ignored = options.debugIgnore || [];

    if (typeof logging !== 'object') { logging = {}; }

    var enabled = typeof logging.enabled !== 'undefined' ? logging.enabled : false;
    var rewrite = typeof logging.rewrite !== 'undefined' ? logging.rewrite : true;
    var chat    = typeof logging.chat !== 'undefined' ? logging.chat : false;

    timestamp = typeof logging.timestamp !== 'undefined' ? logging.timestamp : true;

    this.options = {
        stream: process.stdout,
        wstream: null,
        level: 'raw'
    };

    if (debug && details) { this.options.level = 'chat'; }
    if (dev) { this.options.level = 'dev'; }
    if (!debug) { this.options.level = 'crash'; }

    if (enabled) {
        ensureExists('./logs', function(err) {
            if (!err) {
                var flag = rewrite ? 'w' : 'a';
                var stream = filesystem.createWriteStream('./logs/status.log', { flags: flag });
                self.options.wstream = stream;
                if (chat) { self.options.level = 'chat'; }
            }
        });
    }

    this.templates = {
        chat:  '[:timestamp] \x1b[35mchat\x1b[39m  - :message',
        dev:   '\x1b[90m  :message\x1b[39m',
        raw:   '[:timestamp] \x1b[36mraw\x1b[39m   - :message',
        event: '[:timestamp] \x1b[32mevent\x1b[39m - :message',
        info:  '[:timestamp] \x1b[33minfo\x1b[39m  - :message',
        error: '[:timestamp] \x1b[31merror\x1b[39m - :message',
        crash: '[:timestamp] \x1b[31merror\x1b[39m - :message',
        inspect: true
    };
}

/* Tokens used in templates */
Logger.prototype.tokens = {
    timestamp: function() {
        var str         = '';
        var currentTime = new Date();
        var hours       = currentTime.getHours();
        var minutes     = currentTime.getMinutes();
        var seconds     = currentTime.getSeconds();

        if (hours < 10) { hours = '0' + hours }
        if (minutes < 10) { minutes = '0' + minutes }
        if (seconds < 10) { seconds = '0' + seconds }
        str += hours + ':' + minutes + ':' + seconds;

        return str;
    }
};

/* Custom levels */
Logger.prototype.levels = {
    dev:  7,
    chat:  6,
    raw:   5,
    event: 4,
    info:  3,
    error: 2,
    crash: 1
};

/* Replace the message and it's tokens */
Logger.prototype.message = function(level, str) {
    var message = this.templates[level];
    for (var token in this.tokens) {
        if (!Object.prototype.hasOwnProperty.call(this.tokens, token)) {
            continue;
        }
        message = message.replace(':' + token, this.tokens[token]());
    }
    return message.replace(':message', str);
};

/* Send the message to the stream(s) */
Logger.prototype.log = function(level, str) {
    if (this.levels[level] > this.levels[this.options.level]) return;

    if (arguments.length > 2) { str = Array.prototype.slice.call(arguments, 1).join(' '); }
    else if (str.toString() == '[object Arguments]') { str = Array.prototype.slice.call(str).join(' '); }

    if (this.levels[level] === 7) { str = str.toUpperCase(); }

    var message = this.message(level, str) + '\n';
    if (!timestamp && this.levels[level] !== 7) {
        var string = message.split(' ');
        string.shift();
        message = string.join(' ');
    }

    if (this.levels[level] !== 6) {
        if (this.levels[level] === 4) {
            if (ignored.indexOf(str) < 0) { this.options.stream.write(message); }
        }
        else if (this.levels[level] === 5) {
            if (ignored.indexOf('raw') < 0) { this.options.stream.write(message); }
        }
        else if (this.levels[level] === 3) {
            if (ignored.indexOf('info') < 0) { this.options.stream.write(message); }
        }
        else if (this.levels[level] === 2) {
            if (ignored.indexOf('error') < 0) { this.options.stream.write(message); }
        }
        else if (this.levels[level] === 1) {
            if (ignored.indexOf('crash') < 0) { this.options.stream.write(message); }
        }
        else {
            this.options.stream.write(message);
        }
    }

    if (this.levels[level] === 6 && details) {
        this.options.stream.write(smartTrim(message, 100, ' ', '..\r\n'));
    }

    if (this.options.wstream !== null) {
        if (this.levels[level] === 4) {
            if (str !== 'chat') {
                this.options.wstream.write(this.message(level, str).replace(/\x1B\[\d+m/g, '') + '\n');
            }
        } else {
            this.options.wstream.write(this.message(level, str).replace(/\x1B\[\d+m/g, '') + '\n');
        }
    }
};

/* Logger prototypes */
Logger.prototype.chat    = function(str) { return this.log('chat', arguments) };
Logger.prototype.dev     = function(str) { return this.log('dev', arguments) };
Logger.prototype.inspect = function(obj) { return this.log('info', util.inspect(obj)); };
Logger.prototype.raw     = function(str) { return this.log('raw', arguments); };
Logger.prototype.event   = function(str) { return this.log('event', arguments) };
Logger.prototype.info    = function(str) { return this.log('info', arguments) };
Logger.prototype.error   = function(str) { return this.log('error', arguments) };
Logger.prototype.crash   = function(str) { return this.log('crash', arguments) };

module.exports = exports = Logger;
exports.Logger = Logger;<commit_msg>Added action messages to logger #76<commit_after>/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Schmoopiie
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

var filesystem = require('fs');
var util       = require('util');

var details    = false;
var ignored    = [];
var timestamp  = true;

function smartTrim(str, length, delim, appendix) {
    if (str.length <= length) return str;

    var trimmedStr = str.substr(0, length+delim.length);

    var lastDelimIndex = trimmedStr.lastIndexOf(delim);
    if (lastDelimIndex >= 0) trimmedStr = trimmedStr.substr(0, lastDelimIndex);

    if (trimmedStr) trimmedStr += appendix;
    return trimmedStr;
}

/* Ensure that the directory exists */
function ensureExists(path, mask, cb) {
    if (typeof mask == 'function') {
        cb = mask;
        mask = 0777;
    }

    filesystem.mkdir(path, mask, function(err) {
        if (err) {
            if (err.code == 'EEXIST') cb(null);
            else cb(err);
        } else cb(null);
    });
}

/* Our awesome logger */
function Logger(config) {
    var self    = this;
    var config  = config || {};
    var options = config.options || {};
    var debug   = options.debug || false;
    var dev     = options.dev || false;
    var logging = config.logging || {};

    details = options.debugDetails || false;
    ignored = options.debugIgnore || [];

    if (typeof logging !== 'object') { logging = {}; }

    var enabled = typeof logging.enabled !== 'undefined' ? logging.enabled : false;
    var rewrite = typeof logging.rewrite !== 'undefined' ? logging.rewrite : true;
    var chat    = typeof logging.chat !== 'undefined' ? logging.chat : false;

    timestamp = typeof logging.timestamp !== 'undefined' ? logging.timestamp : true;

    this.options = {
        stream: process.stdout,
        wstream: null,
        level: 'raw'
    };

    if (debug && details) { this.options.level = 'chat'; }
    if (dev) { this.options.level = 'dev'; }
    if (!debug) { this.options.level = 'crash'; }

    if (enabled) {
        ensureExists('./logs', function(err) {
            if (!err) {
                var flag = rewrite ? 'w' : 'a';
                var stream = filesystem.createWriteStream('./logs/status.log', { flags: flag });
                self.options.wstream = stream;
                if (chat) { self.options.level = 'chat'; }
            }
        });
    }

    this.templates = {
        chat:  '[:timestamp] \x1b[35mchat\x1b[39m   - :message',
        action:  '[:timestamp] \x1b[35maction\x1b[39m - :message',
        dev:   '\x1b[90m  :message\x1b[39m',
        raw:   '[:timestamp] \x1b[36mraw\x1b[39m    - :message',
        event: '[:timestamp] \x1b[32mevent\x1b[39m  - :message',
        info:  '[:timestamp] \x1b[33minfo\x1b[39m   - :message',
        error: '[:timestamp] \x1b[31merror\x1b[39m  - :message',
        crash: '[:timestamp] \x1b[31merror\x1b[39m  - :message',
        inspect: true
    };
}

/* Tokens used in templates */
Logger.prototype.tokens = {
    timestamp: function() {
        var str         = '';
        var currentTime = new Date();
        var hours       = currentTime.getHours();
        var minutes     = currentTime.getMinutes();
        var seconds     = currentTime.getSeconds();

        if (hours < 10) { hours = '0' + hours }
        if (minutes < 10) { minutes = '0' + minutes }
        if (seconds < 10) { seconds = '0' + seconds }
        str += hours + ':' + minutes + ':' + seconds;

        return str;
    }
};

/* Custom levels */
Logger.prototype.levels = {
    dev:  7,
    chat:  6,
    action:  6,
    raw:   5,
    event: 4,
    info:  3,
    error: 2,
    crash: 1
};

/* Replace the message and it's tokens */
Logger.prototype.message = function(level, str) {
    var message = this.templates[level];
    for (var token in this.tokens) {
        if (!Object.prototype.hasOwnProperty.call(this.tokens, token)) {
            continue;
        }
        message = message.replace(':' + token, this.tokens[token]());
    }
    return message.replace(':message', str);
};

/* Send the message to the stream(s) */
Logger.prototype.log = function(level, str) {
    if (this.levels[level] > this.levels[this.options.level]) return;

    if (arguments.length > 2) { str = Array.prototype.slice.call(arguments, 1).join(' '); }
    else if (str.toString() == '[object Arguments]') { str = Array.prototype.slice.call(str).join(' '); }

    if (this.levels[level] === 7) { str = str.toUpperCase(); }

    var message = this.message(level, str) + '\n';
    if (!timestamp && this.levels[level] !== 7) {
        var string = message.split(' ');
        string.shift();
        message = string.join(' ');
    }

    if (this.levels[level] !== 6) {
        if (this.levels[level] === 4) {
            if (ignored.indexOf(str) < 0) { this.options.stream.write(message); }
        }
        else if (this.levels[level] === 5) {
            if (ignored.indexOf('raw') < 0) { this.options.stream.write(message); }
        }
        else if (this.levels[level] === 3) {
            if (ignored.indexOf('info') < 0) { this.options.stream.write(message); }
        }
        else if (this.levels[level] === 2) {
            if (ignored.indexOf('error') < 0) { this.options.stream.write(message); }
        }
        else if (this.levels[level] === 1) {
            if (ignored.indexOf('crash') < 0) { this.options.stream.write(message); }
        }
        else {
            this.options.stream.write(message);
        }
    }

    if (this.levels[level] === 6 && details) {
        this.options.stream.write(smartTrim(message, 100, ' ', '..\r\n'));
    }

    if (this.options.wstream !== null) {
        if (this.levels[level] === 4) {
            if (str !== 'chat' && str !== 'action') {
                this.options.wstream.write(this.message(level, str).replace(/\x1B\[\d+m/g, '') + '\n');
            }
        } else {
            this.options.wstream.write(this.message(level, str).replace(/\x1B\[\d+m/g, '') + '\n');
        }
    }
};

/* Logger prototypes */
Logger.prototype.chat    = function(str) { return this.log('chat', arguments) };
Logger.prototype.action  = function(str) { return this.log('action', arguments) };
Logger.prototype.dev     = function(str) { return this.log('dev', arguments) };
Logger.prototype.inspect = function(obj) { return this.log('info', util.inspect(obj)); };
Logger.prototype.raw     = function(str) { return this.log('raw', arguments); };
Logger.prototype.event   = function(str) { return this.log('event', arguments) };
Logger.prototype.info    = function(str) { return this.log('info', arguments) };
Logger.prototype.error   = function(str) { return this.log('error', arguments) };
Logger.prototype.crash   = function(str) { return this.log('crash', arguments) };

module.exports = exports = Logger;
exports.Logger = Logger;<|endoftext|>


<commit_before>var RTCPeerConnection = null;
var getUserMedia = null;
var attachMediaStream = null;
var reattachMediaStream = null;
var webrtcDetectedBrowser = null;
var webrtcDetectedVersion = null;

function trace(text) {
  // This function is used for logging.
  if (text[text.length - 1] == '\n') {
    text = text.substring(0, text.length - 1);
  }
  console.log((performance.now() / 1000).toFixed(3) + ": " + text);
}

if (navigator.mozGetUserMedia) {
  console.log("This appears to be Firefox");

  webrtcDetectedBrowser = "firefox";

  webrtcDetectedVersion =
           parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);

  // The RTCPeerConnection object.
  RTCPeerConnection = mozRTCPeerConnection;

  // The RTCSessionDescription object.
  RTCSessionDescription = mozRTCSessionDescription;

  // The RTCIceCandidate object.
  RTCIceCandidate = mozRTCIceCandidate;

  // Get UserMedia (only difference is the prefix).
  // Code from Adam Barth.
  getUserMedia = navigator.mozGetUserMedia.bind(navigator);

  // Creates iceServer from the url for FF.
  createIceServer = function(url, username, password) {
    var iceServer = null;
    var url_parts = url.split(':');
    if (url_parts[0].indexOf('stun') === 0) {
      // Create iceServer with stun url.
      iceServer = { 'url': url };
    } else if (url_parts[0].indexOf('turn') === 0) {
      if (webrtcDetectedVersion < 27) {
        // Create iceServer with turn url.
        // Ignore the transport parameter from TURN url for FF version <=27.
        var turn_url_parts = url.split("?");
        // Return null for createIceServer if transport=tcp.
        if (turn_url_parts[1].indexOf('transport=udp') === 0) {
          iceServer = { 'url': turn_url_parts[0],
                        'credential': password,
                        'username': username };
        }
      } else {
        // FF 27 and above supports transport parameters in TURN url,
        // So passing in the full url to create iceServer.
        iceServer = { 'url': url,
                      'credential': password,
                      'username': username };
      }
    }
    return iceServer;
  };

  // Attach a media stream to an element.
  attachMediaStream = function(element, stream) {
    console.log("Attaching media stream");
    element.mozSrcObject = stream;
    element.play();
  };

  reattachMediaStream = function(to, from) {
    console.log("Reattaching media stream");
    to.mozSrcObject = from.mozSrcObject;
    to.play();
  };

  // Fake get{Video,Audio}Tracks
  if (!MediaStream.prototype.getVideoTracks) {
    MediaStream.prototype.getVideoTracks = function() {
      return [];
    };
  }

  if (!MediaStream.prototype.getAudioTracks) {
    MediaStream.prototype.getAudioTracks = function() {
      return [];
    };
  }
} else if (navigator.webkitGetUserMedia) {
  console.log("This appears to be Chrome");

  webrtcDetectedBrowser = "chrome";
  webrtcDetectedVersion =
         parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10);

  // Creates iceServer from the url for Chrome.
  createIceServer = function(url, username, password) {
    var iceServer = null;
    var url_parts = url.split(':');
    if (url_parts[0].indexOf('stun') === 0) {
      // Create iceServer with stun url.
      iceServer = { 'url': url };
    } else if (url_parts[0].indexOf('turn') === 0) {
      // Chrome M28 & above uses below TURN format.
      iceServer = { 'url': url,
                    'credential': password,
                    'username': username };
    }
    return iceServer;
  };

  // The RTCPeerConnection object.
  RTCPeerConnection = webkitRTCPeerConnection;

  // Get UserMedia (only difference is the prefix).
  // Code from Adam Barth.
  getUserMedia = navigator.webkitGetUserMedia.bind(navigator);

  // Attach a media stream to an element.
  attachMediaStream = function(element, stream) {
    if (typeof element.srcObject !== 'undefined') {
      element.srcObject = stream;
    } else if (typeof element.mozSrcObject !== 'undefined') {
      element.mozSrcObject = stream;
    } else if (typeof element.src !== 'undefined') {
      element.src = URL.createObjectURL(stream);
    } else {
      console.log('Error attaching stream to element.');
    }
  };

  reattachMediaStream = function(to, from) {
    to.src = from.src;
  };
} else {
  console.log("Browser does not appear to be WebRTC-capable");
}
<commit_msg>Fix JS error in adapter.js for FF for the case when ?transport=xxx is missing in TURN url. BUG=2737 R=juberti@webrtc.org<commit_after>var RTCPeerConnection = null;
var getUserMedia = null;
var attachMediaStream = null;
var reattachMediaStream = null;
var webrtcDetectedBrowser = null;
var webrtcDetectedVersion = null;

function trace(text) {
  // This function is used for logging.
  if (text[text.length - 1] == '\n') {
    text = text.substring(0, text.length - 1);
  }
  console.log((performance.now() / 1000).toFixed(3) + ": " + text);
}

if (navigator.mozGetUserMedia) {
  console.log("This appears to be Firefox");

  webrtcDetectedBrowser = "firefox";

  webrtcDetectedVersion =
           parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);

  // The RTCPeerConnection object.
  RTCPeerConnection = mozRTCPeerConnection;

  // The RTCSessionDescription object.
  RTCSessionDescription = mozRTCSessionDescription;

  // The RTCIceCandidate object.
  RTCIceCandidate = mozRTCIceCandidate;

  // Get UserMedia (only difference is the prefix).
  // Code from Adam Barth.
  getUserMedia = navigator.mozGetUserMedia.bind(navigator);

  // Creates iceServer from the url for FF.
  createIceServer = function(url, username, password) {
    var iceServer = null;
    var url_parts = url.split(':');
    if (url_parts[0].indexOf('stun') === 0) {
      // Create iceServer with stun url.
      iceServer = { 'url': url };
    } else if (url_parts[0].indexOf('turn') === 0) {
      if (webrtcDetectedVersion < 27) {
        // Create iceServer with turn url.
        // Ignore the transport parameter from TURN url for FF version <=27.
        var turn_url_parts = url.split("?");
        // Return null for createIceServer if transport=tcp.
        if (turn_url_parts.length === 1 ||
            turn_url_parts[1].indexOf('transport=udp') === 0) {
          iceServer = { 'url': turn_url_parts[0],
                        'credential': password,
                        'username': username };
        }
      } else {
        // FF 27 and above supports transport parameters in TURN url,
        // So passing in the full url to create iceServer.
        iceServer = { 'url': url,
                      'credential': password,
                      'username': username };
      }
    }
    return iceServer;
  };

  // Attach a media stream to an element.
  attachMediaStream = function(element, stream) {
    console.log("Attaching media stream");
    element.mozSrcObject = stream;
    element.play();
  };

  reattachMediaStream = function(to, from) {
    console.log("Reattaching media stream");
    to.mozSrcObject = from.mozSrcObject;
    to.play();
  };

  // Fake get{Video,Audio}Tracks
  if (!MediaStream.prototype.getVideoTracks) {
    MediaStream.prototype.getVideoTracks = function() {
      return [];
    };
  }

  if (!MediaStream.prototype.getAudioTracks) {
    MediaStream.prototype.getAudioTracks = function() {
      return [];
    };
  }
} else if (navigator.webkitGetUserMedia) {
  console.log("This appears to be Chrome");

  webrtcDetectedBrowser = "chrome";
  webrtcDetectedVersion =
         parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10);

  // Creates iceServer from the url for Chrome.
  createIceServer = function(url, username, password) {
    var iceServer = null;
    var url_parts = url.split(':');
    if (url_parts[0].indexOf('stun') === 0) {
      // Create iceServer with stun url.
      iceServer = { 'url': url };
    } else if (url_parts[0].indexOf('turn') === 0) {
      // Chrome M28 & above uses below TURN format.
      iceServer = { 'url': url,
                    'credential': password,
                    'username': username };
    }
    return iceServer;
  };

  // The RTCPeerConnection object.
  RTCPeerConnection = webkitRTCPeerConnection;

  // Get UserMedia (only difference is the prefix).
  // Code from Adam Barth.
  getUserMedia = navigator.webkitGetUserMedia.bind(navigator);

  // Attach a media stream to an element.
  attachMediaStream = function(element, stream) {
    if (typeof element.srcObject !== 'undefined') {
      element.srcObject = stream;
    } else if (typeof element.mozSrcObject !== 'undefined') {
      element.mozSrcObject = stream;
    } else if (typeof element.src !== 'undefined') {
      element.src = URL.createObjectURL(stream);
    } else {
      console.log('Error attaching stream to element.');
    }
  };

  reattachMediaStream = function(to, from) {
    to.src = from.src;
  };
} else {
  console.log("Browser does not appear to be WebRTC-capable");
}
<|endoftext|>