<commit_before>'use strict';

var SwiftMock = require('../swift-mock.js');
var path = require('path');
var fs = require('fs');
var tmp = require('tmp');
var Q = require('q');

var mktemp = Q.nfbind(tmp.file);

describe('my app', function () {
    it('should redirect to /#/ when fragment is empty', function () {
        browser.get('index.html');
        expect(browser.getLocationAbsUrl()).toEqual('/');
    });
});

function uploadFile(path) {
    browser.executeScript(function () {
        var file = document.getElementById('file-1');
        angular.element(file).removeClass('hidden');
    }).then(function () {
        $('#file-1').sendKeys(path);
    });
}

describe('Container listing', function () {
    describe('should be sortable', function () {
        beforeEach(function () {
            SwiftMock.setObjects('foo', {
                'x.txt': {headers: {
                    'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                    'Content-Length': 1000,
                    'Content-Type': 'text/plain'
                }},
                'y.txt': {headers: {
                    'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                    'Content-Length': 234,
                    'Content-Type': 'text/plain'
                }}
            });
            SwiftMock.setObjects('bar', {
                'x.txt': {headers: {
                    'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                    'Content-Length': 2345,
                    'Content-Type': 'text/plain'
                }}
            });
            browser.get('index.html#/');
        });

        it('by name', function () {
            var rows = by.repeater('container in containers');
            var names = element.all(rows.column('container.name'));

            // Initial sort order is by name
            expect(names.getText()).toEqual(['bar', 'foo']);
            // Clicking the name header sorts reverses the order
            $('th:nth-child(2)').click();
            expect(names.getText()).toEqual(['foo', 'bar']);
        });

        it('by size', function () {
            var sizes = $$('td:nth-child(3)');

            // Initial sort is by name
            expect(sizes.getText()).toEqual(['2.3 KB', '1.2 KB']);
            // Clicking the header sorts
            $$('th').get(2).click();
            expect(sizes.getText()).toEqual(['1.2 KB', '2.3 KB']);
            // Clicking again reverses
            $$('th').get(2).click();
            expect(sizes.getText()).toEqual(['2.3 KB', '1.2 KB']);
        });

        it('by count', function () {
            var rows = by.repeater('container in containers');
            var counts = element.all(rows.column('container.count | number'));

            // Initial sort order is by name
            expect(counts.getText()).toEqual(['1 objects', '2 objects']);
            // Clicking the header sorts (no change)
            $$('th').get(3).click();
            expect(counts.getText()).toEqual(['1 objects', '2 objects']);
            // Clicking the header sorts reverses the order
            $$('th').get(3).click();
            expect(counts.getText()).toEqual(['2 objects', '1 objects']);
        });
    });

    describe('selection', function () {
        beforeEach(function () {
            SwiftMock.addContainer('foo');
            SwiftMock.addContainer('bar');
            browser.get('index.html#/');
        });

        var toggle = $('th.toggle input');
        var checkboxes = $$('td:nth-child(1) input');

        it('should be deselected by default', function () {
            expect(toggle.isSelected()).toBe(false);
            expect(checkboxes.isSelected()).toEqual([false, false]);
        });

        it('should allow toggle all', function () {
            toggle.click();
            expect(checkboxes.isSelected()).toEqual([true, true]);
        });

        it('should notice manually selecting all', function () {
            checkboxes.click();
            expect(toggle.isSelected()).toBe(true);
        });
    });

    describe('with no containers', function () {
        it('should not show all containers selected', function () {
            browser.get('index.html#/');

            var toggle = $('th.toggle input');
            expect(toggle.isSelected()).toBe(false);
        });
    });

    it('should allow creating a new container', function () {
        var rows = by.repeater('container in containers');
        var names = element.all(rows.column('container.name'));
        var sizes = $$('td:nth-child(3)');
        var counts = element.all(rows.column('container.count | number'));
        var openBtn = $('.btn[ng-click="create()"]');
        var createBtn = $('.btn[ng-click="$close(name)"]');
        var input = $('.modal-body input');

        browser.get('index.html#/');
        openBtn.click();
        input.sendKeys('foo');
        createBtn.click();

        expect(names.getText()).toEqual(['foo']);
        expect(sizes.getText()).toEqual(['0.0 B']);
        expect(counts.getText()).toEqual(['0 objects']);
    });

    it('should not allow a slash in a container name', function () {
        var openBtn = $('.btn[ng-click="create()"]');
        var createBtn = $('.btn[ng-click="$close(name)"]');
        var input = $('.modal-body input');
        var help = $('.modal-body .help-block');
        browser.get('index.html#/');

        openBtn.click();
        expect(createBtn.isEnabled()).toBe(false);
        expect(help.isDisplayed()).toBe(false);

        input.sendKeys('foo');
        expect(createBtn.isEnabled()).toBe(true);
        expect(help.isDisplayed()).toBe(false);

        input.sendKeys('/bar');
        expect(createBtn.isEnabled()).toBe(false);
        expect(help.isDisplayed()).toBe(true);
    });

    it('should create container when pressing enter', function () {
        var rows = by.repeater('container in containers');
        var names = element.all(rows.column('container.name'));
        var openBtn = $('.btn[ng-click="create()"]');
        var input = $('.modal-body input');
        browser.get('index.html#/');

        openBtn.click();
        input.sendKeys('foo', protractor.Key.ENTER);
        expect(names.getText()).toEqual(['foo']);
    });

    describe('deleting containers', function () {
        var deleteBtn = $('.btn[ng-click="delete()"]');
        var closeBtn = $('.btn[ng-click="$close()"]');
        var toggles = element.all(by.model('container.selected'));

        it('should succeed with empty container', function () {
            SwiftMock.addContainer('foo');
            browser.get('index.html#/');

            expect(deleteBtn.isEnabled()).toBe(false);
            toggles.first().click();
            deleteBtn.click();
            closeBtn.click();
            expect(toggles.count()).toBe(0);
        });

        it('should succeed with non-empty container', function () {
            SwiftMock.setObjects('foo', {
                'x.txt': {headers: {
                    'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                    'Content-Length': 10,
                    'Content-Type': 'text/plain'
                }},
                'nested/y.txt': {headers: {
                    'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                    'Content-Length': 20,
                    'Content-Type': 'text/plain'
                }}
            });
            browser.get('index.html#/');
            toggles.first().click();
            deleteBtn.click();
            closeBtn.click();
            expect(toggles.count()).toBe(0);
        });
    });
});


describe('Object listing', function () {
    describe('should be sortable', function () {
        beforeEach(function () {
            SwiftMock.setObjects('foo', {
                'x.txt': {headers: {
                    'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                    'Content-Length': 20,
                    'Content-Type': 'text/plain'
                }},
                'y.txt': {headers: {
                    'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                    'Content-Length': 10,
                    'Content-Type': 'text/plain'
                }}
            });
            browser.get('index.html#/foo/');
        });

        it('by name', function () {
            var rows = by.repeater('item in items');
            var names = element.all(rows.column('item.title'));

            // Initial sort order is by name
            expect(names.getText()).toEqual(['x.txt', 'y.txt']);
            // Clicking the name header sorts reverses the order
            $$('th').get(1).click();
            expect(names.getText()).toEqual(['y.txt', 'x.txt']);
        });

        it('by size', function () {
            var sizes = $$('td[sb-format-bytes]');

            // Initial sort order is by name
            expect(sizes.getText()).toEqual(['20.0 B', '10.0 B']);
            // Clicking the header sorts
            $('th:last-child').click();
            expect(sizes.getText()).toEqual(['10.0 B', '20.0 B']);
        });
    });

    it('should understand pseudo-directories', function () {
        SwiftMock.setObjects('foo', {
            'x.txt': {headers: {
                'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                'Content-Length': 13,
                'Content-Type': 'text/plain'
            }},
            'dir/y.txt': {headers: {
                'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                'Content-Length': 10,
                'Content-Type': 'text/plain'
            }},
        });
        browser.get('index.html#/foo/');

        var names = $$('td:nth-child(2)');
        expect(names.getText()).toEqual(['dir/', 'x.txt']);
    });

    it('should understand deep pseudo-directories', function () {
        SwiftMock.setObjects('foo', {
            'x.txt': {headers: {
                'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                'Content-Length': 13,
                'Content-Type': 'text/plain'
            }},
            'deeply/y.txt': {headers: {
                'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                'Content-Length': 10,
                'Content-Type': 'text/plain'
            }},
            'deeply/nested/z.txt': {headers: {
                'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                'Content-Length': 10,
                'Content-Type': 'text/plain'
            }}
        });
        browser.get('index.html#/foo/');

        var links = $$('td:nth-child(2) a');
        expect(links.getText()).toEqual(['deeply/', 'x.txt']);
        links.first().click();

        expect(links.getText()).toEqual(['nested/', 'y.txt']);
        links.first().click();

        expect(links.getText()).toEqual(['z.txt']);
    });

    describe('selection', function () {
        beforeEach(function () {
            SwiftMock.setObjects('foo', {
                'x.txt': {headers: {
                    'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                    'Content-Length': 20,
                    'Content-Type': 'text/plain'
                }},
                'y.txt': {headers: {
                    'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                    'Content-Length': 10,
                    'Content-Type': 'text/plain'
                }}
            });
            browser.get('index.html#/foo/');
        });

        var toggle = $('th.toggle input');
        var checkboxes = $$('td:nth-child(1) input');

        it('should be deselected by default', function () {
            expect(toggle.isSelected()).toBe(false);
            expect(checkboxes.isSelected()).toEqual([false, false]);
        });

        it('should allow toggle all', function () {
            toggle.click();
            expect(checkboxes.isSelected()).toEqual([true, true]);
        });

        it('should notice manually selecting all', function () {
            checkboxes.click();
            expect(toggle.isSelected()).toBe(true);
        });
    });

    describe('with no objects', function () {
        it('should not show all objects selected', function () {
            SwiftMock.addContainer('foo');
            browser.get('index.html#/foo/');

            var toggle = $('th.toggle input');
            expect(toggle.isSelected()).toBe(false);
        });
    });

    it('should allow deletion', function () {
        SwiftMock.setObjects('foo', {
            'x.txt': {headers: {
                'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                'Content-Length': 20,
                'Content-Type': 'text/plain'
            }},
            'y.txt': {headers: {
                'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                'Content-Length': 10,
                'Content-Type': 'text/plain'
            }},
            'z.txt': {headers: {
                'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                'Content-Length': 10,
                'Content-Type': 'text/plain'
            }}
        });
        browser.get('index.html#/foo/');
        var names = $$('td:nth-child(2)');
        var checkboxes = $$('td:nth-child(1) input');
        var deleteBtn = $('.btn[ng-click="delete()"]');

        expect(deleteBtn.isEnabled()).toBe(false);
        checkboxes.get(0).click();
        checkboxes.get(2).click();
        deleteBtn.click();

        var modalNames = $$('div.modal td:nth-child(2)');
        var modalCheckboxes = $$('div.modal td:nth-child(1) input');
        var modalTitle = $('div.modal h3');
        var closeBtn = $('div.modal .btn[ng-click="$close()"]');

        expect(modalTitle.getText()).toMatch('Deleting 2 objects');
        expect(modalNames.getText()).toEqual(['x.txt', 'z.txt']);
        expect(modalCheckboxes.isSelected()).toEqual([true, true]);

        $('div.modal th:nth-child(1) input').click();
        expect(modalCheckboxes.isSelected()).toEqual([false, false]);
        expect(closeBtn.isEnabled()).toBe(false);

        modalCheckboxes.last().click();
        expect(modalTitle.getText()).toMatch('Deleting 1 objects');

        closeBtn.click();
        expect(modalTitle.isPresent()).toBe(false);

        expect(checkboxes.isSelected()).toEqual([true, false]);
        expect(names.getText()).toEqual(['x.txt', 'y.txt']);
    });

    it('should allow deleting pseudo-directories', function () {
        SwiftMock.setObjects('foo', {
            'x.txt': {headers: {
                'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                'Content-Length': 20,
                'Content-Type': 'text/plain'
            }},
            'bar/y.txt': {headers: {
                'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                'Content-Length': 10,
                'Content-Type': 'text/plain'
            }},
            'bar/z.txt': {headers: {
                'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                'Content-Length': 10,
                'Content-Type': 'text/plain'
            }}
        });
        browser.get('index.html#/foo/');

        var names = $$('td:nth-child(2)');
        var modalNames = $$('div.modal td:nth-child(2)');
        var deleteBtn = $('.btn[ng-click="delete()"]');
        var closeBtn = $('div.modal .btn[ng-click="$close()"]');

        $$('td:nth-child(1) input').first().click();
        deleteBtn.click();
        expect(modalNames.getText()).toEqual(['bar/']);

        closeBtn.click();
        expect(names.getText()).toEqual(['x.txt']);
    });

    it('should allow uploading files', function () {
        SwiftMock.setObjects('foo', {
            'nested/x.txt': {headers: {
                'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                'Content-Length': 20,
                'Content-Type': 'text/plain'
            }}
        });
        browser.get('index.html#/foo/nested/');

        var openModalBtn = $('.btn[ng-click="upload()"]');
        var names = $$('td:nth-child(2)');
        expect(names.getText()).toEqual(['x.txt']);

        openModalBtn.click();
        expect($('div.modal h3').getText()).toMatch('to foo/nested/');

        // Test with two paths where the first sort after the second
        var p1 = mktemp({prefix: 'b'});
        var p2 = mktemp({prefix: 'a'});
        Q.all([p1, p2]).spread(function (res1, res2) {
            var paths = [res1[0], res2[0]];
            paths.forEach(uploadFile);

            var uploadBtn = $('.btn[ng-click="uploadFiles()"]');
            var rows = by.repeater('file in files');
            var uploads = element.all(rows.column('file.name'));
            var newNames = paths.map(path.basename);
            expect(uploads.getText()).toEqual(newNames);

            expect(uploadBtn.isEnabled()).toBe(true);
            uploadBtn.click();
            var progBar = $$('div.progress-bar').first();
            expect(progBar.getAttribute('aria-valuenow')).toBe('100');
            expect(uploadBtn.isEnabled()).toBe(false);

            $('.btn[ng-click="$dismiss()"]').click();
            expect($('div.modal h3').isPresent()).toBe(false);

            var expected = paths.map(path.basename);
            expected.push('x.txt');
            expected.sort();
            expect(names.getText()).toEqual(expected);
        });
    });

    it('should allow unscheduling files for upload', function () {
        SwiftMock.addContainer('foo');
        browser.get('index.html#/foo/');

        var names = $$('td:nth-child(2)');
        $('.btn[ng-click="upload()"]').click();

        Q.all([mktemp(), mktemp()]).spread(function (res1, res2) {
            var paths = [res1[0], res2[0]];
            var rows = by.repeater('file in files');
            var uploads = element.all(rows.column('file.name'));
            var base = path.basename(paths[1]);
            paths.forEach(uploadFile);

            // Remove the first file, expect that the second is still
            // there and that it's the only one.
            $$('a[ng-click="remove($index)"]').first().click();
            expect(uploads.getText()).toEqual([base]);

            $('.btn[ng-click="uploadFiles()"]').click();
            $('.btn[ng-click="$dismiss()"]').click();
            expect(names.getText()).toEqual([base]);
        });
    });

    it('should set Content-Length correctly for uploaded files', function () {
        SwiftMock.addContainer('foo');
        browser.get('index.html#/foo/');
        $('.btn[ng-click="upload()"]').click();

        var sizes = $$('td:nth-child(4)');

        // Two files with a known sort order
        var tmpX = mktemp({prefix: 'x'});
        var tmpY = mktemp({prefix: 'y'});
        Q.all([tmpX, tmpY]).spread(function (x, y) {
            var buf = new Buffer(1234);
            var bytesWritten = fs.writeSync(x[1], buf, 0, buf.length);
            expect(bytesWritten).toEqual(buf.length);

            uploadFile(x[0]);
            uploadFile(y[0]);
            expect(sizes.getText()).toEqual(['1.2 KB', '0.0 B']);

            $('.btn[ng-click="uploadFiles()"]').click();
            $('.btn[ng-click="$dismiss()"]').click();

            var contentLength = $('.content-length td:nth-child(2)');
            $$('td a').first().click();
            expect(contentLength.getText()).toEqual('1234');

            $$('.breadcrumb a').last().click();

            $$('td a').last().click();
            expect(contentLength.getText()).toEqual('0');
        });
    });

    it('should set Content-Type correctly for uploaded files', function () {
        SwiftMock.addContainer('foo');
        browser.get('index.html#/foo/');
        $('.btn[ng-click="upload()"]').click();

        var rows = by.repeater('file in files');
        var types = element.all(rows.column('file.type'));

        mktemp({postfix: '.html'}).spread(function (filename) {
            uploadFile(filename);
            expect(types.getText()).toEqual(['text/html']);
            $('.btn[ng-click="uploadFiles()"]').click();
            $('.btn[ng-click="$dismiss()"]').click();

            $('td a').click();
            var input = $('.content-type').$('input');
            expect(input.getAttribute('value')).toEqual('text/html');
        });
    });

    it('should allow copying files', function () {
        SwiftMock.setObjects('foo', {
            'nested/x.txt': {headers: {
                'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                'Content-Length': 20,
                'Content-Type': 'text/plain'
            }}
        });
        SwiftMock.addContainer('bar');
        browser.get('index.html#/foo/nested/');

        var checkboxes = $$('td:nth-child(1) input');
        var names = $$('td:nth-child(2)');
        var openCopyModalBtn = $('.btn[ng-click="copy()"]');
        var copyBtn = $('div.modal .btn[ng-click="copyObjects()"]');
        var closeBtn = $('div.modal .btn[ng-click="$dismiss()"]');
        var modalTitle = $('div.modal h3');
        var modalNames = $$('div.modal td:nth-child(1)');
        var successes = $$('div.modal td:nth-child(3) .glyphicon-ok');
        var containers = element.all(
            by.options('c.name as c.name for c in containers')
        );
        var destDirectory = element(by.model('directory'));

        expect(openCopyModalBtn.isEnabled()).toBe(false);
        checkboxes.click();
        openCopyModalBtn.click();

        expect(modalTitle.getText()).toEqual('Copying 1 objects');
        expect(modalNames.getText()).toEqual(['x.txt']);
        expect(containers.getText()).toEqual(['bar', 'foo']);
        expect(containers.get(1).isSelected()).toBe(true);
        expect(destDirectory.getAttribute('value')).toEqual('nested/');
        expect(copyBtn.isEnabled()).toBe(true);

        containers.get(0).click();
        destDirectory.clear();
        destDirectory.sendKeys('copied');

        copyBtn.click();
        expect(successes.count()).toBe(1);
        expect(copyBtn.isEnabled()).toBe(false);

        closeBtn.click();
        $$('.breadcrumb a').first().click();
        $$('td a').first().click(); // index.html#/bar/<commit_msg>e2e: use more specific selectors<commit_after>'use strict';

var SwiftMock = require('../swift-mock.js');
var path = require('path');
var fs = require('fs');
var tmp = require('tmp');
var Q = require('q');

var mktemp = Q.nfbind(tmp.file);

describe('my app', function () {
    it('should redirect to /#/ when fragment is empty', function () {
        browser.get('index.html');
        expect(browser.getLocationAbsUrl()).toEqual('/');
    });
});

function uploadFile(path) {
    browser.executeScript(function () {
        var file = document.getElementById('file-1');
        angular.element(file).removeClass('hidden');
    }).then(function () {
        $('#file-1').sendKeys(path);
    });
}

describe('Container listing', function () {
    describe('should be sortable', function () {
        beforeEach(function () {
            SwiftMock.setObjects('foo', {
                'x.txt': {headers: {
                    'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                    'Content-Length': 1000,
                    'Content-Type': 'text/plain'
                }},
                'y.txt': {headers: {
                    'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                    'Content-Length': 234,
                    'Content-Type': 'text/plain'
                }}
            });
            SwiftMock.setObjects('bar', {
                'x.txt': {headers: {
                    'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                    'Content-Length': 2345,
                    'Content-Type': 'text/plain'
                }}
            });
            browser.get('index.html#/');
        });

        it('by name', function () {
            var rows = by.repeater('container in containers');
            var names = element.all(rows.column('container.name'));

            // Initial sort order is by name
            expect(names.getText()).toEqual(['bar', 'foo']);
            // Clicking the name header sorts reverses the order
            $('th:nth-child(2)').click();
            expect(names.getText()).toEqual(['foo', 'bar']);
        });

        it('by size', function () {
            var sizes = $$('td[sb-format-bytes]');

            // Initial sort is by name
            expect(sizes.getText()).toEqual(['2.3 KB', '1.2 KB']);
            // Clicking the header sorts
            $$('th').get(2).click();
            expect(sizes.getText()).toEqual(['1.2 KB', '2.3 KB']);
            // Clicking again reverses
            $$('th').get(2).click();
            expect(sizes.getText()).toEqual(['2.3 KB', '1.2 KB']);
        });

        it('by count', function () {
            var rows = by.repeater('container in containers');
            var counts = element.all(rows.column('container.count | number'));

            // Initial sort order is by name
            expect(counts.getText()).toEqual(['1 objects', '2 objects']);
            // Clicking the header sorts (no change)
            $$('th').get(3).click();
            expect(counts.getText()).toEqual(['1 objects', '2 objects']);
            // Clicking the header sorts reverses the order
            $$('th').get(3).click();
            expect(counts.getText()).toEqual(['2 objects', '1 objects']);
        });
    });

    describe('selection', function () {
        beforeEach(function () {
            SwiftMock.addContainer('foo');
            SwiftMock.addContainer('bar');
            browser.get('index.html#/');
        });

        var toggle = $('th.toggle input');
        var checkboxes = element.all(by.model('container.selected'));

        it('should be deselected by default', function () {
            expect(toggle.isSelected()).toBe(false);
            expect(checkboxes.isSelected()).toEqual([false, false]);
        });

        it('should allow toggle all', function () {
            toggle.click();
            expect(checkboxes.isSelected()).toEqual([true, true]);
        });

        it('should notice manually selecting all', function () {
            checkboxes.click();
            expect(toggle.isSelected()).toBe(true);
        });
    });

    describe('with no containers', function () {
        it('should not show all containers selected', function () {
            browser.get('index.html#/');

            var toggle = $('th.toggle input');
            expect(toggle.isSelected()).toBe(false);
        });
    });

    it('should allow creating a new container', function () {
        var rows = by.repeater('container in containers');
        var names = element.all(rows.column('container.name'));
        var sizes = $$('td[sb-format-bytes]');
        var counts = element.all(rows.column('container.count | number'));
        var openBtn = $('.btn[ng-click="create()"]');
        var createBtn = $('.btn[ng-click="$close(name)"]');
        var input = $('.modal-body input');

        browser.get('index.html#/');
        openBtn.click();
        input.sendKeys('foo');
        createBtn.click();

        expect(names.getText()).toEqual(['foo']);
        expect(sizes.getText()).toEqual(['0.0 B']);
        expect(counts.getText()).toEqual(['0 objects']);
    });

    it('should not allow a slash in a container name', function () {
        var openBtn = $('.btn[ng-click="create()"]');
        var createBtn = $('.btn[ng-click="$close(name)"]');
        var input = $('.modal-body input');
        var help = $('.modal-body .help-block');
        browser.get('index.html#/');

        openBtn.click();
        expect(createBtn.isEnabled()).toBe(false);
        expect(help.isDisplayed()).toBe(false);

        input.sendKeys('foo');
        expect(createBtn.isEnabled()).toBe(true);
        expect(help.isDisplayed()).toBe(false);

        input.sendKeys('/bar');
        expect(createBtn.isEnabled()).toBe(false);
        expect(help.isDisplayed()).toBe(true);
    });

    it('should create container when pressing enter', function () {
        var rows = by.repeater('container in containers');
        var names = element.all(rows.column('container.name'));
        var openBtn = $('.btn[ng-click="create()"]');
        var input = $('.modal-body input');
        browser.get('index.html#/');

        openBtn.click();
        input.sendKeys('foo', protractor.Key.ENTER);
        expect(names.getText()).toEqual(['foo']);
    });

    describe('deleting containers', function () {
        var deleteBtn = $('.btn[ng-click="delete()"]');
        var closeBtn = $('.btn[ng-click="$close()"]');
        var toggles = element.all(by.model('container.selected'));

        it('should succeed with empty container', function () {
            SwiftMock.addContainer('foo');
            browser.get('index.html#/');

            expect(deleteBtn.isEnabled()).toBe(false);
            toggles.first().click();
            deleteBtn.click();
            closeBtn.click();
            expect(toggles.count()).toBe(0);
        });

        it('should succeed with non-empty container', function () {
            SwiftMock.setObjects('foo', {
                'x.txt': {headers: {
                    'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                    'Content-Length': 10,
                    'Content-Type': 'text/plain'
                }},
                'nested/y.txt': {headers: {
                    'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                    'Content-Length': 20,
                    'Content-Type': 'text/plain'
                }}
            });
            browser.get('index.html#/');
            toggles.first().click();
            deleteBtn.click();
            closeBtn.click();
            expect(toggles.count()).toBe(0);
        });
    });
});


describe('Object listing', function () {
    describe('should be sortable', function () {
        beforeEach(function () {
            SwiftMock.setObjects('foo', {
                'x.txt': {headers: {
                    'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                    'Content-Length': 20,
                    'Content-Type': 'text/plain'
                }},
                'y.txt': {headers: {
                    'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                    'Content-Length': 10,
                    'Content-Type': 'text/plain'
                }}
            });
            browser.get('index.html#/foo/');
        });

        it('by name', function () {
            var rows = by.repeater('item in items');
            var names = element.all(rows.column('item.title'));

            // Initial sort order is by name
            expect(names.getText()).toEqual(['x.txt', 'y.txt']);
            // Clicking the name header sorts reverses the order
            $$('th').get(1).click();
            expect(names.getText()).toEqual(['y.txt', 'x.txt']);
        });

        it('by size', function () {
            var sizes = $$('td[sb-format-bytes]');

            // Initial sort order is by name
            expect(sizes.getText()).toEqual(['20.0 B', '10.0 B']);
            // Clicking the header sorts
            $('th:last-child').click();
            expect(sizes.getText()).toEqual(['10.0 B', '20.0 B']);
        });
    });

    it('should understand pseudo-directories', function () {
        SwiftMock.setObjects('foo', {
            'x.txt': {headers: {
                'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                'Content-Length': 13,
                'Content-Type': 'text/plain'
            }},
            'dir/y.txt': {headers: {
                'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                'Content-Length': 10,
                'Content-Type': 'text/plain'
            }},
        });
        browser.get('index.html#/foo/');

        var rows = by.repeater('item in items');
        var names = element.all(rows.column('item.title'));
        expect(names.getText()).toEqual(['dir/', 'x.txt']);
    });

    it('should understand deep pseudo-directories', function () {
        SwiftMock.setObjects('foo', {
            'x.txt': {headers: {
                'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                'Content-Length': 13,
                'Content-Type': 'text/plain'
            }},
            'deeply/y.txt': {headers: {
                'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                'Content-Length': 10,
                'Content-Type': 'text/plain'
            }},
            'deeply/nested/z.txt': {headers: {
                'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                'Content-Length': 10,
                'Content-Type': 'text/plain'
            }}
        });
        browser.get('index.html#/foo/');

        var rows = by.repeater('item in items');
        var links = element.all(rows.column('item.title'));
        expect(links.getText()).toEqual(['deeply/', 'x.txt']);
        links.first().click();

        expect(links.getText()).toEqual(['nested/', 'y.txt']);
        links.first().click();

        expect(links.getText()).toEqual(['z.txt']);
    });

    describe('selection', function () {
        beforeEach(function () {
            SwiftMock.setObjects('foo', {
                'x.txt': {headers: {
                    'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                    'Content-Length': 20,
                    'Content-Type': 'text/plain'
                }},
                'y.txt': {headers: {
                    'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                    'Content-Length': 10,
                    'Content-Type': 'text/plain'
                }}
            });
            browser.get('index.html#/foo/');
        });

        var toggle = $('th.toggle input');
        var checkboxes = element.all(by.model('item.selected'));

        it('should be deselected by default', function () {
            expect(toggle.isSelected()).toBe(false);
            expect(checkboxes.isSelected()).toEqual([false, false]);
        });

        it('should allow toggle all', function () {
            toggle.click();
            expect(checkboxes.isSelected()).toEqual([true, true]);
        });

        it('should notice manually selecting all', function () {
            checkboxes.click();
            expect(toggle.isSelected()).toBe(true);
        });
    });

    describe('with no objects', function () {
        it('should not show all objects selected', function () {
            SwiftMock.addContainer('foo');
            browser.get('index.html#/foo/');

            var toggle = $('th.toggle input');
            expect(toggle.isSelected()).toBe(false);
        });
    });

    it('should allow deletion', function () {
        SwiftMock.setObjects('foo', {
            'x.txt': {headers: {
                'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                'Content-Length': 20,
                'Content-Type': 'text/plain'
            }},
            'y.txt': {headers: {
                'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                'Content-Length': 10,
                'Content-Type': 'text/plain'
            }},
            'z.txt': {headers: {
                'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                'Content-Length': 10,
                'Content-Type': 'text/plain'
            }}
        });
        browser.get('index.html#/foo/');
        var rows = by.repeater('item in items');
        var names = element.all(rows.column('item.title'));
        var checkboxes = element.all(by.model('item.selected'));
        var deleteBtn = $('.btn[ng-click="delete()"]');

        expect(deleteBtn.isEnabled()).toBe(false);
        checkboxes.get(0).click();
        checkboxes.get(2).click();
        deleteBtn.click();

        var modalNames = $('div.modal').all(rows.column('item.title'));
        var modalCheckboxes = $('div.modal').all(by.model('item.selected'));
        var modalTitle = $('div.modal h3');
        var closeBtn = $('div.modal .btn[ng-click="$close()"]');

        expect(modalTitle.getText()).toMatch('Deleting 2 objects');
        expect(modalNames.getText()).toEqual(['x.txt', 'z.txt']);
        expect(modalCheckboxes.isSelected()).toEqual([true, true]);

        $('div.modal th:nth-child(1) input').click();
        expect(modalCheckboxes.isSelected()).toEqual([false, false]);
        expect(closeBtn.isEnabled()).toBe(false);

        modalCheckboxes.last().click();
        expect(modalTitle.getText()).toMatch('Deleting 1 objects');

        closeBtn.click();
        expect(modalTitle.isPresent()).toBe(false);

        expect(checkboxes.isSelected()).toEqual([true, false]);
        expect(names.getText()).toEqual(['x.txt', 'y.txt']);
    });

    it('should allow deleting pseudo-directories', function () {
        SwiftMock.setObjects('foo', {
            'x.txt': {headers: {
                'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                'Content-Length': 20,
                'Content-Type': 'text/plain'
            }},
            'bar/y.txt': {headers: {
                'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                'Content-Length': 10,
                'Content-Type': 'text/plain'
            }},
            'bar/z.txt': {headers: {
                'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                'Content-Length': 10,
                'Content-Type': 'text/plain'
            }}
        });
        browser.get('index.html#/foo/');

        var rows = by.repeater('item in items');
        var names = element.all(rows.column('item.title'));
        var checkboxes = element.all(by.model('item.selected'));
        var modalNames = $('div.modal').all(rows.column('item.title'));
        var deleteBtn = $('.btn[ng-click="delete()"]');
        var closeBtn = $('div.modal .btn[ng-click="$close()"]');

        checkboxes.first().click();
        deleteBtn.click();
        expect(modalNames.getText()).toEqual(['bar/']);

        closeBtn.click();
        expect(names.getText()).toEqual(['x.txt']);
    });

    it('should allow uploading files', function () {
        SwiftMock.setObjects('foo', {
            'nested/x.txt': {headers: {
                'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                'Content-Length': 20,
                'Content-Type': 'text/plain'
            }}
        });
        browser.get('index.html#/foo/nested/');

        var openModalBtn = $('.btn[ng-click="upload()"]');
        var rows = by.repeater('item in items');
        var names = element.all(rows.column('item.title'));
        expect(names.getText()).toEqual(['x.txt']);

        openModalBtn.click();
        expect($('div.modal h3').getText()).toMatch('to foo/nested/');

        // Test with two paths where the first sort after the second
        var p1 = mktemp({prefix: 'b'});
        var p2 = mktemp({prefix: 'a'});
        Q.all([p1, p2]).spread(function (res1, res2) {
            var paths = [res1[0], res2[0]];
            paths.forEach(uploadFile);

            var uploadBtn = $('.btn[ng-click="uploadFiles()"]');
            var files = by.repeater('file in files');
            var uploads = element.all(files.column('file.name'));
            var newNames = paths.map(path.basename);
            expect(uploads.getText()).toEqual(newNames);

            expect(uploadBtn.isEnabled()).toBe(true);
            uploadBtn.click();
            var progBar = $$('div.progress-bar').first();
            expect(progBar.getAttribute('aria-valuenow')).toBe('100');
            expect(uploadBtn.isEnabled()).toBe(false);

            $('.btn[ng-click="$dismiss()"]').click();
            expect($('div.modal h3').isPresent()).toBe(false);

            var expected = paths.map(path.basename);
            expected.push('x.txt');
            expected.sort();
            expect(names.getText()).toEqual(expected);
        });
    });

    it('should allow unscheduling files for upload', function () {
        SwiftMock.addContainer('foo');
        browser.get('index.html#/foo/');

        var rows = by.repeater('item in items');
        var names = element.all(rows.column('item.title'));
        $('.btn[ng-click="upload()"]').click();

        Q.all([mktemp(), mktemp()]).spread(function (res1, res2) {
            var paths = [res1[0], res2[0]];
            var files = by.repeater('file in files');
            var uploads = element.all(files.column('file.name'));
            var base = path.basename(paths[1]);
            paths.forEach(uploadFile);

            // Remove the first file, expect that the second is still
            // there and that it's the only one.
            $$('a[ng-click="remove($index)"]').first().click();
            expect(uploads.getText()).toEqual([base]);

            $('.btn[ng-click="uploadFiles()"]').click();
            $('.btn[ng-click="$dismiss()"]').click();
            expect(names.getText()).toEqual([base]);
        });
    });

    it('should set Content-Length correctly for uploaded files', function () {
        SwiftMock.addContainer('foo');
        browser.get('index.html#/foo/');
        $('.btn[ng-click="upload()"]').click();

        var sizes = $$('td[sb-format-bytes]');

        // Two files with a known sort order
        var tmpX = mktemp({prefix: 'x'});
        var tmpY = mktemp({prefix: 'y'});
        Q.all([tmpX, tmpY]).spread(function (x, y) {
            var buf = new Buffer(1234);
            var bytesWritten = fs.writeSync(x[1], buf, 0, buf.length);
            expect(bytesWritten).toEqual(buf.length);

            uploadFile(x[0]);
            uploadFile(y[0]);
            expect(sizes.getText()).toEqual(['1.2 KB', '0.0 B']);

            $('.btn[ng-click="uploadFiles()"]').click();
            $('.btn[ng-click="$dismiss()"]').click();

            var contentLength = $('.content-length td:nth-child(2)');
            $$('td a').first().click();
            expect(contentLength.getText()).toEqual('1234');

            $$('.breadcrumb a').last().click();

            $$('td a').last().click();
            expect(contentLength.getText()).toEqual('0');
        });
    });

    it('should set Content-Type correctly for uploaded files', function () {
        SwiftMock.addContainer('foo');
        browser.get('index.html#/foo/');
        $('.btn[ng-click="upload()"]').click();

        var rows = by.repeater('file in files');
        var types = element.all(rows.column('file.type'));

        mktemp({postfix: '.html'}).spread(function (filename) {
            uploadFile(filename);
            expect(types.getText()).toEqual(['text/html']);
            $('.btn[ng-click="uploadFiles()"]').click();
            $('.btn[ng-click="$dismiss()"]').click();

            $('td a').click();
            var input = $('.content-type').$('input');
            expect(input.getAttribute('value')).toEqual('text/html');
        });
    });

    it('should allow copying files', function () {
        SwiftMock.setObjects('foo', {
            'nested/x.txt': {headers: {
                'Last-Modified': 'Sat, 16 Aug 2014 13:33:21 GMT',
                'Content-Length': 20,
                'Content-Type': 'text/plain'
            }}
        });
        SwiftMock.addContainer('bar');
        browser.get('index.html#/foo/nested/');

        var rows = by.repeater('item in items');
        var checkboxes = element.all(by.model('item.selected'));
        var names = element.all(rows.column('item.title'));
        var openCopyModalBtn = $('.btn[ng-click="copy()"]');
        var copyBtn = $('div.modal .btn[ng-click="copyObjects()"]');
        var closeBtn = $('div.modal .btn[ng-click="$dismiss()"]');
        var modalTitle = $('div.modal h3');
        var modalNames = $('div.modal').all(rows.column('item.title'));
        var successes = $$('div.modal td:nth-child(3) .glyphicon-ok');
        var containers = element.all(
            by.options('c.name as c.name for c in containers')
        );
        var destDirectory = element(by.model('directory'));

        expect(openCopyModalBtn.isEnabled()).toBe(false);
        checkboxes.click();
        openCopyModalBtn.click();

        expect(modalTitle.getText()).toEqual('Copying 1 objects');
        expect(modalNames.getText()).toEqual(['x.txt']);
        expect(containers.getText()).toEqual(['bar', 'foo']);
        expect(containers.get(1).isSelected()).toBe(true);
        expect(destDirectory.getAttribute('value')).toEqual('nested/');
        expect(copyBtn.isEnabled()).toBe(true);

        containers.get(0).click();
        destDirectory.clear();
        destDirectory.sendKeys('copied');

        copyBtn.click();
        expect(successes.count()).toBe(1);
        expect(copyBtn.isEnabled()).toBe(false);

        closeBtn.click();
        $$('.breadcrumb a').first().click();
        $$('td a').first().click(); // index.html#/bar/
{'fraction_non_alphanumeric': 0.1864414314292065, 'fraction_numerical': 0.018581907090464547, 'mean_word_length': 1.9471374295820778, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5964901', 'n_tokens_mistral': 13493, 'n_tokens_neox': 12406, 'n_words': 2394}