text:
suite('rb/pages/views/DiffViewerPageView', function() {
    /**
     * Make a replacement function for $.ajax(url).
     *
     * Args:
     *     url (string):
     *         The expected URL.
     *
     *     rsp (object):
     *         The response to trigger the done callback with.
     *
     * Returns:
     *     function:
     *     A function to use in ``spyOn().and.fallFake()``.
     *
     *     The returned function returns an object that mimics the return of
     *     ``$.ajax(url)``.
     */
    function makeAjaxFn(url, rsp) {
        return function(url) {
            expect(url).toBe(url);

            return {
                done(cb) {
                    cb(rsp);
                },
            };
        };
    }

    const tableTemplate = _.template(dedent`

             <a name="<%- fileID %>" class="file-anchor">

          <% _.each(chunks, function(chunk) { %>
           <tbody class="<%- chunk.type %>">
            <% _.each(chunk.lines, function(line, i) { %>
             <tr line="<%- line.vNumber %>">

               <% if (i === 0 && chunk.type !== "equal") { %>
                <a name="<%- chunk.id %>" class="chunk-anchor">
               <% } %>
               <%- line.leftNumber || "" %>

              <%- line.rightNumber || "" %>

            <% }); %>

          <% }); %>

    `);

    const pageTemplate = dedent`

    `;

    let page;
    let pageView;
    let $diffs;

    function setupPageView(modelAttrs) {
        page = new RB.DiffViewerPage(
            _.extend({
                checkForUpdates: false,
                pagination: {
                    current_page: 1,
                },
                reviewRequestData: {
                    id: 123,
                    loaded: true,
                    state: RB.ReviewRequest.PENDING,
                },
                revision: {
                    revision: 1,
                    interdiff_revision: null,
                    is_interdiff: false,
                },
                editorData: {
                    mutableByUser: true,
                    statusMutableByUser: true,
                },
            }, modelAttrs),
            {
                parse: true,
            });

        pageView = new RB.DiffViewerPageView({
            el: $(pageTemplate).appendTo($testsScratch),
            model: page,
        });
        pageView.render();
    }

    beforeEach(function() {
        /*
         * Disable the router so that the page doesn't change the URL on the
         * page while tests run.
         */
        spyOn(window.history, 'pushState');
        spyOn(window.history, 'replaceState');
        spyOn(RB.HeaderView.prototype, '_ensureSingleton');

        spyOn(RB, 'navigateTo');

        /* Ensure that tests don't alter cookies. Set defaults. */
        spyOn($, 'cookie');
        RB.UserSession.instance.set('canToggleExtraWhitespace', false);
        RB.UserSession.instance.set('diffsShowExtraWhitespace', true);
    });

    afterEach(function() {
        RB.DnDUploader.instance = null;
        Backbone.history.stop();
    });

    describe('Diff view buttons', function() {
        describe('Initial state', function() {
            it('Defaults', function() {
                setupPageView();

                expect(page.get('allChunksCollapsed')).toBeFalse();
                expect(page.get('canToggleExtraWhitespace')).toBeFalse();

                const $buttons = pageView.$('#view_controls button');
                expect($buttons.length).toBe(2);

                let $button = $buttons.eq(0);
                expect($button.attr('id'))
                    .toBe('action-diff-toggle-collapse-changes');
                expect($button.text().trim()).toBe('Collapse changes');
                expect($button.attr('title')).toBe(
                    'All lines of the files are being shown. Toggle to ' +
                    'collapse down to only modified sections instead.');

                $button = $buttons.eq(1);
                expect($button.attr('id'))
                    .toBe('action-diff-toggle-whitespace-only');
                expect($button.text().trim())
                    .toBe('Hide whitespace-only changes');
                expect($button.attr('title')).toBe(
                    'Sections of the diff containing only whitespace ' +
                    'changes are being shown. Toggle to hide those instead.');
            });

            it('With allChunksCollapsed=true', function() {
                setupPageView({
                    allChunksCollapsed: true,
                });

                expect(page.get('allChunksCollapsed')).toBeTrue();
                expect(page.get('canToggleExtraWhitespace')).toBeFalse();

                const $buttons = pageView.$('#view_controls button');
                expect($buttons.length).toBe(2);

                let $button = $buttons.eq(0);
                expect($button.attr('id'))
                    .toBe('action-diff-toggle-collapse-changes');
                expect($button.text().trim()).toBe('Expand changes');
                expect($button.attr('title')).toBe(
                    'Only modified sections of the files are being shown. ' +
                    'Toggle to show all lines instead.');

                $button = $buttons.eq(1);
                expect($button.attr('id'))
                    .toBe('action-diff-toggle-whitespace-only');
                expect($button.text().trim())
                    .toBe('Hide whitespace-only changes');
                expect($button.attr('title')).toBe(
                    'Sections of the diff containing only whitespace ' +
                    'changes are being shown. Toggle to hide those instead.');
            });

            it('With canToggleExtraWhitespace=true', function() {
                setupPageView({
                    canToggleExtraWhitespace: true,
                });

                expect(page.get('allChunksCollapsed')).toBeFalse();
                expect(page.get('canToggleExtraWhitespace')).toBeTrue();

                const $buttons = pageView.$('#view_controls button');
                expect($buttons.length).toBe(3);

                let $button = $buttons.eq(0);
                expect($button.attr('id'))
                    .toBe('action-diff-toggle-collapse-changes');
                expect($button.text().trim()).toBe('Collapse changes');
                expect($button.attr('title')).toBe(
                    'All lines of the files are being shown. Toggle to ' +
                    'collapse down to only modified sections instead.');

                $button = $buttons.eq(1);
                expect($button.attr('id'))
                    .toBe('action-diff-toggle-extra-whitespace');
                expect($button.text().trim()).toBe('Hide extra whitespace');
                expect($button.attr('title')).toBe(
                    'Mismatched indentation and trailing whitespace are ' +
                    'being shown. Toggle to hide instead.');

                $button = $buttons.eq(2);
                expect($button.attr('id'))
                    .toBe('action-diff-toggle-whitespace-only');
                expect($button.text().trim())
                    .toBe('Hide whitespace-only changes');
                expect($button.attr('title')).toBe(
                    'Sections of the diff containing only whitespace ' +
                    'changes are being shown. Toggle to hide those instead.');
            });

            it('With canToggleExtraWhitespace=true and ' +
               'diffsShowExtraWhitespace=true',
               function() {
                RB.UserSession.instance.set('diffsShowExtraWhitespace', true);
                setupPageView({
                    canToggleExtraWhitespace: true,
                });

                expect(page.get('allChunksCollapsed')).toBeFalse();
                expect(page.get('canToggleExtraWhitespace')).toBeTrue();

                const $buttons = pageView.$('#view_controls button');
                expect($buttons.length).toBe(3);

                let $button = $buttons.eq(0);
                expect($button.attr('id'))
                    .toBe('action-diff-toggle-collapse-changes');
                expect($button.text().trim()).toBe('Collapse changes');
                expect($button.attr('title')).toBe(
                    'All lines of the files are being shown. Toggle to ' +
                    'collapse down to only modified sections instead.');

                $button = $buttons.eq(1);
                expect($button.attr('id'))
                    .toBe('action-diff-toggle-extra-whitespace');
                expect($button.text().trim()).toBe('Hide extra whitespace');
                expect($button.attr('title')).toBe(
                    'Mismatched indentation and trailing whitespace are ' +
                    'being shown. Toggle to hide instead.');

                $button = $buttons.eq(2);
                expect($button.attr('id'))
                    .toBe('action-diff-toggle-whitespace-only');
                expect($button.text().trim())
                       .toBe('Hide whitespace-only changes');
                expect($button.attr('title')).toBe(
                    'Sections of the diff containing only whitespace ' +
                    'changes are being shown. Toggle to hide those instead.');
            });
        });

        describe('Actions', function() {
            it('Collapse changes', function() {
                setupPageView({
                    allChunksCollapsed: false,
                });

                const $button =
                    pageView.$('#action-diff-toggle-collapse-changes');
                expect($button.length).toBe(1);
                expect($button.text().trim()).toBe('Collapse changes');

                $button.click();

                expect(RB.navigateTo).toHaveBeenCalledWith('.?collapse=1');
                expect($button.text().trim()).toBe('Expand changes');
            });

            it('Expand changes', function() {
                setupPageView({
                    allChunksCollapsed: true,
                });

                const $button =
                    pageView.$('#action-diff-toggle-collapse-changes');
                expect($button.length).toBe(1);
                expect($button.text().trim()).toBe('Expand changes');

                $button.click();

                expect(RB.navigateTo).toHaveBeenCalledWith('.?expand=1');
                expect($button.text().trim()).toBe('Collapse changes');
            });

            it('Show extra whitespace', function() {
                RB.UserSession.instance.set('diffsShowExtraWhitespace', false);
                setupPageView({
                    canToggleExtraWhitespace: true
                });

                const $button =
                    pageView.$('#action-diff-toggle-extra-whitespace');
                expect($button.length).toBe(1);
                expect($button.text().trim()).toBe('Show extra whitespace');

                $button.click();

                expect(RB.UserSession.instance.get('diffsShowExtraWhitespace'))
                    .toBeTrue();
                expect($button.text().trim()).toBe('Hide extra whitespace');
            });

            it('Hide extra whitespace', function() {
                RB.UserSession.instance.set('diffsShowExtraWhitespace', true);
                setupPageView({
                    canToggleExtraWhitespace: true
                });

                const $button =
                    pageView.$('#action-diff-toggle-extra-whitespace');
                expect($button.length).toBe(1);
                expect($button.text().trim()).toBe('Hide extra whitespace');

                $button.click();

                expect(RB.UserSession.instance.get('diffsShowExtraWhitespace'))
                    .toBeFalse();
                expect($button.text().trim()).toBe('Show extra whitespace');
            });

            it('Show whitespace-only changes', function() {
                setupPageView();

                const diffReviewableView = new RB.DiffReviewableView();
                spyOn(diffReviewableView, 'toggleWhitespaceOnlyChunks');
                pageView._diffReviewableViews = [diffReviewableView];

                const $button =
                    pageView.$('#action-diff-toggle-whitespace-only');
                expect($button.length).toBe(1);
                expect($button.text().trim())
                    .toBe('Hide whitespace-only changes');

                /*
                 * We have to do this twice, since we hard-code a default
                 * active state to true.
                 */
                $button.click();

                expect(diffReviewableView.toggleWhitespaceOnlyChunks)
                    .toHaveBeenCalled();
                expect($button.text().trim())
                    .toBe('Show whitespace-only changes');

                /* Do it again. */
                $button.click();

                expect(diffReviewableView.toggleWhitespaceOnlyChunks)
                    .toHaveBeenCalled();
                expect($button.text().trim())
                    .toBe('Hide whitespace-only changes');
            });

            it('Hide whitespace-only changes', function() {
                setupPageView();

                const diffReviewableView = new RB.DiffReviewableView();
                spyOn(diffReviewableView, 'toggleWhitespaceOnlyChunks');
                pageView._diffReviewableViews = [diffReviewableView];

                const $button =
                    pageView.$('#action-diff-toggle-whitespace-only');
                expect($button.length).toBe(1);
                expect($button.text().trim())
                    .toBe('Hide whitespace-only changes');

                $button.click();

                expect(diffReviewableView.toggleWhitespaceOnlyChunks)
                    .toHaveBeenCalled();
                expect($button.text().trim())
                    .toBe('Show whitespace-only changes');
            });

        });
    });

    describe('Without commits', function() {
        beforeEach(function() {
            setupPageView({
                canToggleExtraWhitespace: true,
            });

            $diffs = pageView.$el.children('#diffs');

            /* Don't communicate with the server for page updates. */
            spyOn(page.get('reviewRequest'), 'ready').and.resolveTo();
        });

        describe('Anchors', function() {
            it('Tracks all types', function() {
                $diffs.html(tableTemplate({
                    fileID: 'file1',
                    chunks: [
                        {
                            id: '1.1',
                            lines: [
                                {
                                    type: 'insert',
                                    vNumber: 100,
                                    leftNumber: 100,
                                    rightNumber: 101,
                                },
                            ],
                        },
                        {
                            id: '1.2',
                            lines: [
                                {
                                    type: 'equal',
                                    vNumber: 101,
                                    leftNumber: 101,
                                    rightNumber: 101,
                                },
                            ],
                        },
                        {
                            id: '1.3',
                            lines: [
                                {
                                    type: 'delete',
                                    vNumber: 102,
                                    leftNumber: 102,
                                    rightNumber: 101,
                                },
                            ],
                        },
                    ],
                }));

                pageView.render();
                pageView._updateAnchors(pageView.$el.find('table').eq(0));

                expect(pageView._$anchors.length).toBe(4);
                expect(pageView._$anchors[0].name).toBe('file1');
                expect(pageView._$anchors[1].name).toBe('1.1');
                expect(pageView._$anchors[2].name).toBe('1.2');
                expect(pageView._$anchors[3].name).toBe('1.3');
                expect(pageView._selectedAnchorIndex).toBe(0);
            });

            describe('Navigation', function() {
                beforeEach(function() {
                    $diffs.html([
                        tableTemplate({
                            fileID: 'file1',
                            chunks: [
                                {
                                    id: '1.1',
                                    lines: [
                                        {
                                            type: 'insert',
                                            vNumber: 100,
                                            leftNumber: 100,
                                            rightNumber: 101,
                                        },
                                    ],
                                },
                                {
                                    id: '1.2',
                                    lines: [
                                        {
                                            type: 'equal',
                                            vNumber: 101,
                                            leftNumber: 101,
                                            rightNumber: 101,
                                        },
                                    ],
                                },
                            ],
                        }),
                        tableTemplate({
                            fileID: 'file2',
                            chunks: [],
                        }),
                        tableTemplate({
                            fileID: 'file3',
                            chunks: [
                                {
                                    id: '2.1',
                                    lines: [
                                        {
                                            type: 'insert',
                                            vNumber: 100,
                                            leftNumber: 100,
                                            rightNumber: 101,
                                        }
                                    ]
                                },
                                {
                                    id: '2.2',
                                    lines: [
                                        {
                                            type: 'equal',
                                            vNumber: 101,
                                            leftNumber: 101,
                                            rightNumber: 101,
                                        },
                                    ],
                                },
                            ],
                        }),
                    ]);

                    pageView.render();

                    pageView.$el.find('table').each(function() {
                        pageView._updateAnchors($(this));
                    });
                });

                describe('Previous file', function() {
                    it('From file', function() {
                        pageView.selectAnchorByName('file2');
                        expect(pageView._selectedAnchorIndex).toBe(3);

                        pageView._selectPreviousFile();

                        expect(pageView._selectedAnchorIndex).toBe(0);
                    });

                    it('From chunk', function() {
                        pageView.selectAnchorByName('2.2');
                        expect(pageView._selectedAnchorIndex).toBe(6);

                        pageView._selectPreviousFile();

                        expect(pageView._selectedAnchorIndex).toBe(4);
                    });

                    it('On first file', function() {
                        pageView.selectAnchorByName('file1');
                        expect(pageView._selectedAnchorIndex).toBe(0);

                        pageView._selectPreviousFile();

                        expect(pageView._selectedAnchorIndex).toBe(0);
                    });
                });

                describe('Next file', function() {
                    it('From file', function() {
                        pageView.selectAnchorByName('file1');
                        expect(pageView._selectedAnchorIndex).toBe(0);

                        pageView._selectNextFile();

                        expect(pageView._selectedAnchorIndex).toBe(3);
                    });

                    it('From chunk', function() {
                        pageView.selectAnchorByName('1.1');
                        expect(pageView._selectedAnchorIndex).toBe(1);

                        pageView._selectNextFile();

                        expect(pageView._selectedAnchorIndex).toBe(3);
                    });

                    it('On last file', function() {
                        pageView.selectAnchorByName('file3');
                        expect(pageView._selectedAnchorIndex).toBe(4);

                        pageView._selectNextFile();

                        expect(pageView._selectedAnchorIndex).toBe(4);
                    });
                });

                describe('Previous anchor', function() {
                    it('From file to file', function() {
                        pageView.selectAnchorByName('file3');
                        expect(pageView._selectedAnchorIndex).toBe(4);

                        pageView._selectPreviousDiff();

                        expect(pageView._selectedAnchorIndex).toBe(3);
                    });

                    it('From file to chunk', function() {
                        pageView.selectAnchorByName('file2');
                        expect(pageView._selectedAnchorIndex).toBe(3);

                        pageView._selectPreviousDiff();

                        expect(pageView._selectedAnchorIndex).toBe(2);
                    });

                    it('From chunk to file', function() {
                        pageView.selectAnchorByName('2.1');
                        expect(pageView._selectedAnchorIndex).toBe(5);

                        pageView._selectPreviousDiff();

                        expect(pageView._selectedAnchorIndex).toBe(4);
                    });

                    it('From chunk to chunk', function() {
                        pageView.selectAnchorByName('2.2');
                        expect(pageView._selectedAnchorIndex).toBe(6);

                        pageView._selectPreviousDiff();

                        expect(pageView._selectedAnchorIndex).toBe(5);
                    });

                    it('On first file', function() {
                        pageView.selectAnchorByName('file1');
                        expect(pageView._selectedAnchorIndex).toBe(0);

                        pageView._selectPreviousDiff();

                        expect(pageView._selectedAnchorIndex).toBe(0);
                    });
                });

                describe('Next anchor', function() {
                    it('From file to file', function() {
                        pageView.selectAnchorByName('file2');
                        expect(pageView._selectedAnchorIndex).toBe(3);

                        pageView._selectNextDiff();

                        expect(pageView._selectedAnchorIndex).toBe(4);
                    });

                    it('From file to chunk', function() {
                        pageView.selectAnchorByName('file1');
                        expect(pageView._selectedAnchorIndex).toBe(0);

                        pageView._selectNextDiff();

                        expect(pageView._selectedAnchorIndex).toBe(1);
                    });

                    it('From chunk to file', function() {
                        pageView.selectAnchorByName('1.2');
                        expect(pageView._selectedAnchorIndex).toBe(2);

                        pageView._selectNextDiff();

                        expect(pageView._selectedAnchorIndex).toBe(3);
                    });

                    it('From chunk to chunk', function() {
                        pageView.selectAnchorByName('2.1');
                        expect(pageView._selectedAnchorIndex).toBe(5);

                        pageView._selectNextDiff();

                        expect(pageView._selectedAnchorIndex).toBe(6);
                    });

                    it('On last chunk', function() {
                        pageView.selectAnchorByName('2.2');
                        expect(pageView._selectedAnchorIndex).toBe(6);

                        pageView._selectNextDiff();

                        expect(pageView._selectedAnchorIndex).toBe(6);
                    });
                });
            });
        });

        describe('Key bindings', function() {
            function triggerKeyPress(c) {
                const evt = $.Event('keypress');
                evt.which = c.charCodeAt(0);

                pageView.$el.trigger(evt);
            }

            function testKeys(description, funcName, keyList) {
                describe(description, function() {
                    keyList.forEach(key => {
                        let label;
                        let c;

                        if (key.length === 2) {
                            label = key[0];
                            c = key[1];
                        } else {
                            label = "'" + key + "'";
                            c = key;
                        }

                        it(label, function() {
                            pageView.render();
                            spyOn(pageView, funcName);
                            triggerKeyPress(c);
                            expect(pageView[funcName]).toHaveBeenCalled();
                        });
                    });
                });
            }

            testKeys('Previous file',
                     '_selectPreviousFile',
                     ['a', 'A', 'K', 'P', '<', 'm']);
            testKeys('Next file',
                     '_selectNextFile',
                     ['f', 'F', 'J', 'N', '>']);
            testKeys('Previous anchor',
                     '_selectPreviousDiff',
                     ['s', 'S', 'k', 'p', ',']);
            testKeys('Next anchor',
                     '_selectNextDiff',
                     ['d', 'D', 'j', 'n', '.']);
            testKeys('Previous comment',
                     '_selectPreviousComment',
                     ['[', 'x']);
            testKeys('Next comment',
                     '_selectNextComment',
                     [']', 'c']);
            testKeys('Recenter selected',
                     '_recenterSelected',
                     [['Enter', '\x0d']]);
            testKeys('Create comment',
                     '_createComment',
                     ['r', 'R']);
        });

        describe('Reviewable Management', function() {
            beforeEach(function() {
                spyOn(pageView, 'queueLoadDiff');

                pageView.render();
            });

            it('File added', function() {
                expect($diffs.find('.diff-container').length).toBe(0);
                expect(pageView.queueLoadDiff.calls.count()).toBe(0);

                page.files.reset([
                    new RB.DiffFile({
                        id: 100,
                        filediff: {
                            id: 200,
                            revision: 1,
                        },
                    }),
                ]);

                expect($diffs.find('.diff-container').length).toBe(1);
                expect(pageView.queueLoadDiff.calls.count()).toBe(1);
            });

            it('Files reset', function() {
                expect($diffs.find('.diff-container').length).toBe(0);
                expect(pageView.queueLoadDiff.calls.count()).toBe(0);

                /* Add an initial batch of files. */
                page.files.reset([
                    new RB.DiffFile({
                        id: 100,
                        filediff: {
                            id: 200,
                            revision: 1,
                        },
                    }),
                ]);

                expect($diffs.find('.diff-container').length).toBe(1);
                expect(pageView.queueLoadDiff.calls.count()).toBe(1);

                /* Now do another. */
                page.files.reset([
                    new RB.DiffFile({
                        id: 101,
                        filediff: {
                            id: 201,
                            revision: 2,
                        },
                    }),
                ]);

                const $containers = $diffs.find('.diff-container');
                expect($containers.length).toBe(1);
                expect($containers.find('.sidebyside')[0].id)
                    .toBe('file_container_101');
                expect(pageView.queueLoadDiff.calls.count()).toBe(2);
            });
        });

        describe('Page view/URL state', function() {
            let router;

            beforeEach(function() {
                spyOn(page, 'loadDiffRevision');

                /*
                 * Bypass all the actual history logic and get to the actual
                 * router handler.
                 */
                spyOn(Backbone.history, 'matchRoot').and.returnValue(true);

                router = pageView.router;
                spyOn(router, 'navigate').and.callFake((url, options) => {
                    if (!options || options.trigger !== false) {
                        Backbone.history.loadUrl(url);
                    }
                });
            });

            describe('Initial URL', function() {
                it('Initial default load', function() {
                    pageView._setInitialURL('', 'index_header');

                    expect(router.navigate).toHaveBeenCalledWith(
                        '1/#index_header',
                        {
                            replace: true,
                            trigger: false,
                        });
                    expect(page.loadDiffRevision).not.toHaveBeenCalled();
                });

                it('Initial load of first page explicit', function() {
                    pageView._setInitialURL('?page=1', 'index_header');

                    expect(router.navigate).toHaveBeenCalledWith(
                        '1/?page=1#index_header',
                        {
                            replace: true,
                            trigger: false,
                        });
                    expect(page.loadDiffRevision).not.toHaveBeenCalled();
                });

                it('Initial load of page > 1', function() {
                    pageView._setInitialURL('?page=2', 'index_header');

                    expect(router.navigate).toHaveBeenCalledWith(
                        '1/?page=2#index_header',
                        {
                            replace: true,
                            trigger: false,
                        });
                    expect(page.loadDiffRevision).not.toHaveBeenCalled();
                });

                it('Initial load of interdiff', function() {
                    page.revision.set('revision', 2);
                    page.revision.set('interdiffRevision', 3);
                    pageView._setInitialURL('?page=2', 'index_header');

                    expect(router.navigate).toHaveBeenCalledWith(
                        '2-3/?page=2#index_header',
                        {
                            replace: true,
                            trigger: false,
                        });
                    expect(page.loadDiffRevision).not.toHaveBeenCalled();
                });

                it('Initial load with filename patterns', function() {
                    pageView._setInitialURL('?filenames=*.js,src/*',
                                            'index_header');

                    expect(router.navigate).toHaveBeenCalledWith(
                        '1/?filenames=*.js,src/*#index_header',
                        {
                            replace: true,
                            trigger: false,
                        });
                    expect(page.loadDiffRevision).not.toHaveBeenCalled();
                });
            });

            describe('_navigate', function() {
                beforeEach(function() {
                    page.set('filenamePatterns', '*.js,src/*');
                });

                it('With page == 1', function() {
                    pageView._navigate({
                        page: 1,
                    });

                    expect(router.navigate).toHaveBeenCalledWith(
                        '1/?filenames=*.js%2Csrc%2F*',
                        {
                            trigger: true,
                        });
                    expect(page.loadDiffRevision).toHaveBeenCalledWith({
                        baseCommitID: null,
                        filenamePatterns: '*.js,src/*',
                        interdiffRevision: null,
                        page: 1,
                        revision: 1,
                        tipCommitID: null,
                    });
                });

                it('With page > 1', function() {
                    pageView._navigate({
                        page: 2,
                    });

                    expect(router.navigate).toHaveBeenCalledWith(
                        '1/?page=2&filenames=*.js%2Csrc%2F*',
                        {
                            trigger: true,
                        });
                    expect(page.loadDiffRevision).toHaveBeenCalledWith({
                        baseCommitID: null,
                        filenamePatterns: '*.js,src/*',
                        interdiffRevision: null,
                        page: 2,
                        revision: 1,
                        tipCommitID: null,
                    });
                });

                it('New revision on page > 1', function() {
                    page.pagination.set('currentPage', 2);
                    pageView._onRevisionSelected([0, 2]);

                    expect(router.navigate).toHaveBeenCalledWith(
                        '2/?filenames=*.js%2Csrc%2F*',
                        {
                            trigger: true,
                        });
                    expect(page.loadDiffRevision).toHaveBeenCalledWith({
                        baseCommitID: null,
                        filenamePatterns: '*.js,src/*',
                        interdiffRevision: null,
                        page: 1,
                        revision: 2,
                        tipCommitID: null,
                    });
                });

                it('Same revision on page > 1', function() {
                    page.pagination.set('currentPage', 2);

                    pageView._navigate({
                        revision: 1,
                    });

                    expect(router.navigate).toHaveBeenCalledWith(
                        '1/?page=2&filenames=*.js%2Csrc%2F*',
                        {
                            trigger: true,
                        });
                    expect(page.loadDiffRevision).toHaveBeenCalledWith({
                        baseCommitID: null,
                        filenamePatterns: '*.js,src/*',
                        interdiffRevision: null,
                        page: 2,
                        revision: 1,
                        tipCommitID: null,
                    });
                });

                it('With updateURLOnly', function() {
                    page.pagination.set('currentPage', 2);

                    pageView._navigate({
                        revision: 2,
                        interdiffRevision: 3,
                        updateURLOnly: true,
                    });

                    expect(router.navigate).toHaveBeenCalledWith(
                        '2-3/?filenames=*.js%2Csrc%2F*',
                        {
                            replace: true,
                            trigger: false,
                        });
                    expect(page.loadDiffRevision).not.toHaveBeenCalled();
                });

                it('With anchor', function() {
                    pageView._navigate({
                        anchor: 'test',
                    });

                    expect(router.navigate).toHaveBeenCalledWith(
                        '1/?filenames=*.js%2Csrc%2F*#test',
                        {
                            trigger: true,
                        });
                    expect(page.loadDiffRevision).toHaveBeenCalledWith({
                        baseCommitID: null,
                        filenamePatterns: '*.js,src/*',
                        interdiffRevision: null,
                        page: 1,
                        revision: 1,
                        tipCommitID: null,
                    });
                });
            });

            describe('Revision selector', function() {
                describe('New diff revision selected', function() {
                    it('From single revision', function() {
                        pageView._onRevisionSelected([0, 2]);

                        expect(router.navigate).toHaveBeenCalledWith(
                            '2/',
                            {
                                trigger: true,
                            });
                        expect(page.loadDiffRevision).toHaveBeenCalledWith({
                            baseCommitID: null,
                            filenamePatterns: null,
                            interdiffRevision: null,
                            page: 1,
                            revision: 2,
                            tipCommitID: null,
                        });
                    });

                    it('From interdiff revision', function() {
                        page.revision.set('interdiffRevision', 2);

                        pageView._onRevisionSelected([0, 2]);

                        expect(router.navigate).toHaveBeenCalledWith(
                            '2/',
                            {
                                trigger: true,
                            });
                        expect(page.loadDiffRevision).toHaveBeenCalledWith({
                            baseCommitID: null,
                            filenamePatterns: null,
                            interdiffRevision: null,
                            page: 1,
                            revision: 2,
                            tipCommitID: null,
                        });
                    });
                });

                it('New interdiff revision selected', function() {
                    pageView._onRevisionSelected([2, 5]);

                    expect(router.navigate).toHaveBeenCalledWith(
                        '2-5/',
                        {
                            trigger: true,
                        });
                    expect(page.loadDiffRevision).toHaveBeenCalledWith({
                        baseCommitID: null,
                        filenamePatterns: null,
                        interdiffRevision: 5,
                        page: 1,
                        revision: 2,
                        tipCommitID: null,
                    });
                });
            });

            describe('Page selector', function() {
                it('With page == 1', function() {
                    pageView._onPageSelected(true, 1);

                    expect(router.navigate).toHaveBeenCalledWith(
                        '1/',
                        {
                            trigger: true,
                        });
                    expect(page.loadDiffRevision).toHaveBeenCalledWith({
                        baseCommitID: null,
                        page: 1,
                        revision: 1,
                        interdiffRevision: null,
                        filenamePatterns: null,
                        tipCommitID: null,
                    });
                });

                it('With page > 1', function() {
                    pageView._onPageSelected(true, 2);

                    expect(router.navigate).toHaveBeenCalledWith(
                        '1/?page=2',
                        {
                            trigger: true,
                        });
                    expect(page.loadDiffRevision).toHaveBeenCalledWith({
                        baseCommitID: null,
                        page: 2,
                        revision: 1,
                        interdiffRevision: null,
                        filenamePatterns: null,
                        tipCommitID: null,
                    });
                });
            });

            it('Anchor selection', function() {
                const $anchor = $('');

                pageView.render();
                pageView.selectAnchor($anchor);

                expect(router.navigate).toHaveBeenCalledWith(
                    '1/#test',
                    {
                        replace: true,
                        trigger: false,
                    });
                expect(page.loadDiffRevision).not.toHaveBeenCalled();
            });
        });
    });

    describe('With commits', function() {
        let $commitList;

        beforeEach(function() {
            page = new RB.DiffViewerPage({
                checkForUpdates: false,
                pagination: {
                    current_page: 1,
                },
                reviewRequestData: {
                    id: 123,
                    loaded: true,
                    state: RB.ReviewRequest.PENDING,
                },
                revision: {
                    revision: 1,
                    interdiff_revision: null,
                    is_interdiff: false,
                },
                editorData: {
                    mutableByUser: true,
                    statusMutableByUser: true,
                },
                commits: [
                    {
                        author_name: 'Author Name',
                        commit_id: 'r123',
                        commit_message: 'Commit message 1',
                        id: 1,
                        parent_id: 'r122',
                    },
                    {
                        author_name: 'Author Name',
                        commit_id: 'r124',
                        commit_message: 'Commit message 2',
                        id: 2,
                        parent_id: 'r123',
                    },
                    {
                        author_name: 'Author Name',
                        commit_id: 'r125',
                        commit_message: 'Commit message 3',
                        id: 3,
                        parent_id: 'r124',
                    },
                ],
            }, {
                parse: true,
            });

            pageView = new RB.DiffViewerPageView({
                el: $(pageTemplate).appendTo($testsScratch),
                model: page,
            });

            $commitList = $testsScratch.find('#diff_commit_list');

            /* Don't communicate with the server for page updates. */
            spyOn(page.get('reviewRequest'), 'ready')
                .and.resolveTo();
        });

        describe('Render', function() {
            it('Initial render (without interdiff)', function() {
                pageView.render();

                const $table = $commitList.find('table');
                expect($table.length).toBe(1);
                expect($table.find('tbody tr').length).toBe(3);
            });

            it('Initial render (with interdiff)', function() {
                page.revision.set('interdiffRevision', 456);
                page.commitHistoryDiff.reset([
                    {
                        entry_type: RB.CommitHistoryDiffEntry.REMOVED,
                        old_commit_id: 1,
                    },
                    {
                        entry_type: RB.CommitHistoryDiffEntry.ADDED,
                        new_commit_id: 2,
                    },
                    {
                        entry_type: RB.CommitHistoryDiffEntry.ADDED,
                        new_commit_id: 3,
                    },
                ], {
                    parse: true,
                });

                pageView.render();

                const $table = $commitList.find('table');
                expect($table.length).toBe(1);
                expect($table.find('tbody tr').length).toBe(3);
            });

            it('Subsequent render (without interdiff)', function() {
                pageView.render();

                spyOn($, 'ajax').and.callFake(makeAjaxFn(
                    '/api/review-requests/123/diff-context/?revision=2',
                    {
                        diff_context: {
                            revision: {
                                revision: 2,
                                interdiff_revision: null,
                                is_interdiff: false,
                            },
                            commits: [
                                {
                                    author_name: 'Author Name',
                                    commit_id: 'r125',
                                    commit_message: 'Commit message',
                                    id: 4,
                                    parent_id: 'r124',
                                },
                            ],
                        }
                    }));

                page.loadDiffRevision({
                    revision: 2,
                });

                expect($.ajax).toHaveBeenCalled();

                const $table = $commitList.find('table');
                expect($table.length).toBe(1);
                expect($table.find('tbody tr').length).toBe(1);
            });

            it('Subsequent render (with interdiff)', function() {
                pageView.render();

                const rspPayload = {
                    diff_context: {
                        revision: {
                            revision: 2,
                            interdiff_revision: 3,
                            is_interdiff: true,
                        },
                        commits: [
                            {
                                author_name: 'Author Name',
                                commit_id: 'r124',
                                commit_message: 'Commit message',
                                id: 1,
                            },
                            {
                                author_name: 'Author Name',
                                commit_id: 'r125',
                                commit_message: 'Commit message',
                                id: 2,
                            },
                        ],
                        commit_history_diff: [
                            {
                                entry_type: RB.CommitHistoryDiffEntry.REMOVED,
                                old_commit_id: 1,
                            },
                            {
                                entry_type: RB.CommitHistoryDiffEntry.ADDED,
                                new_commit_id: 2,
                            },
                        ],
                    },
                };

                spyOn($, 'ajax').and.callFake(makeAjaxFn(
                    '/api/review-requests/123/diff-context/' +
                    '?revision=2&interdiff-revision=3',
                    rspPayload));

                page.loadDiffRevision({
                    revision: 2,
                    interdiffRevision: 3,
                });

                expect($.ajax).toHaveBeenCalled();

                const $table = $commitList.find('table');
                expect($table.length).toBe(1);
                expect($table.find('tbody tr').length).toBe(2);
            });

            it('Initial render with base commit ID', function() {
                page.revision.set('baseCommitID', 1);

                pageView.render();

                const commitListModel = pageView._commitListView.model;
                expect(commitListModel.get('baseCommitID')).toBe(1);
                expect(commitListModel.get('tipCommitID')).toBe(null);
            });

            it('Initial render with tip commit ID', function() {
                page.revision.set('tipCommitID', 2);

                pageView.render();

                const commitListModel = pageView._commitListView.model;
                expect(commitListModel.get('baseCommitID')).toBe(null);
                expect(commitListModel.get('tipCommitID')).toBe(2);
            });

            it('Initial render with base commit ID and tip commit ID',
               function() {
                   page.revision.set({
                       baseCommitID: 1,
                       tipCommitID: 2,
                   });

                   pageView.render();

                   const commitListModel = pageView._commitListView.model;
                   expect(commitListModel.get('baseCommitID')).toBe(1);
                   expect(commitListModel.get('tipCommitID')).toBe(2);
               });
        });

        describe('Page view/URL state', function() {
            beforeEach(function() {
                spyOn(page, 'loadDiffRevision').and.callThrough();

                spyOn(Backbone.history, 'matchRoot').and.returnValue(true);
                spyOn(pageView.router, 'navigate')
                    .and.callFake((url, options) => {
                        if (!options || options.trigger !== false) {
                            Backbone.history.loadUrl(url);
                        }
                    });
            });

            describe('Initial URL', function() {
                it('With base-commit-id', function() {
                    pageView._setInitialURL('?base-commit-id=2',
                                            'index_header');

                    expect(pageView.router.navigate).toHaveBeenCalledWith(
                        '1/?base-commit-id=2#index_header',
                        {
                            replace: true,
                            trigger: false,
                        });
                    expect(page.loadDiffRevision).not.toHaveBeenCalled();
                });

                it('With tip-commit-id', function() {
                    pageView._setInitialURL('?tip-commit-id=2',
                                            'index_header');

                    expect(pageView.router.navigate).toHaveBeenCalledWith(
                        '1/?tip-commit-id=2#index_header',
                        {
                            replace: true,
                            trigger: false,
                        });
                    expect(page.loadDiffRevision).not.toHaveBeenCalled();
                });

                it('With base-commit-id and tip-commit-id', function() {
                    pageView._setInitialURL('?base-commit-id=1&tip-commit-id=2',
                                            'index_header');

                    expect(pageView.router.navigate).toHaveBeenCalledWith(
                        '1/?base-commit-id=1&tip-commit-id=2#index_header',
                        {
                            replace: true,
                            trigger: false,
                        });
                    expect(page.loadDiffRevision).not.toHaveBeenCalled();
                });
            });

            describe('Commit range controls', function() {
                it('Selecting initial base commit ID', function() {
                    pageView.render();

                    const $table = $commitList.find('table');
                    const $rows = $table.find('tbody tr');

                    expect($table.length).toBe(1);
                    expect($rows.length).toBe(3);

                    spyOn($, 'ajax').and.callFake(makeAjaxFn(
                        '/api/review-requests/123/diff-context/' +
                        '?revision=1&base-commit-id=1',
                        {
                            diff_context: {
                                revision: {
                                    revision: 1,
                                    interdiff_revision: null,
                                    is_interdiff: false,
                                    base_commit_id: 1,
                                    tip_commit_id: null,
                                },
                            },
                        }));

                    $rows.eq(1).find('.base-commit-selector').click();

                    expect(pageView.router.navigate).toHaveBeenCalledWith(
                        '1/?base-commit-id=1',
                        {
                            trigger: true,
                        });

                    expect(page.loadDiffRevision).toHaveBeenCalledWith({
                        baseCommitID: 1,
                        filenamePatterns: null,
                        interdiffRevision: null,
                        page: 1,
                        revision: 1,
                        tipCommitID: null,
                    });

                    expect(page.revision.get('baseCommitID')).toBe(1);
                    expect(page.revision.get('tipCommitID')).toBe(null);

                    const diffCommitListModel = pageView._commitListView.model;
                    expect(diffCommitListModel.get('baseCommitID')).toBe(1);
                    expect(diffCommitListModel.get('tipCommitID')).toBe(null);
                });

                it('Selecting initial tip commit ID', function() {
                    pageView.render();

                    const $table = $commitList.find('table');
                    const $rows = $table.find('tbody tr');

                    expect($table.length).toBe(1);
                    expect($rows.length).toBe(3);

                    spyOn($, 'ajax').and.callFake(makeAjaxFn(
                        '/api/review-requests/123/diff-context/' +
                        '?revision=1&tip-commit-id=2',
                        {
                            diff_context: {
                                revision: {
                                    base_commit_id: null,
                                    interdiff_revision: null,
                                    is_interdiff: false,
                                    revision: 1,
                                    tip_commit_id: 2,
                                },
                            },
                        }));

                    $rows.eq(1).find('.tip-commit-selector').click();

                    expect(pageView.router.navigate).toHaveBeenCalledWith(
                        '1/?tip-commit-id=2',
                        {
                            trigger: true,
                        });

                    expect(page.loadDiffRevision).toHaveBeenCalledWith({
                        baseCommitID: null,
                        filenamePatterns: null,
                        interdiffRevision: null,
                        page: 1,
                        revision: 1,
                        tipCommitID: 2,
                    });

                    expect(page.revision.get('baseCommitID')).toBe(null);
                    expect(page.revision.get('tipCommitID')).toBe(2);

                    const diffCommitListModel = pageView._commitListView.model;
                    expect(diffCommitListModel.get('baseCommitID')).toBe(null);
                    expect(diffCommitListModel.get('tipCommitID')).toBe(2);
                });

                it('Selecting new base commit ID', function() {
                    page.revision.set('baseCommitID', 3);
                    pageView.render();

                    const $table = $commitList.find('table');
                    const $rows = $table.find('tbody tr');

                    expect($table.length).toBe(1);
                    expect($rows.length).toBe(3);

                    spyOn($, 'ajax').and.callFake(makeAjaxFn(
                        '/api/review-requests/123/diff-context/' +
                        '?revision=1&base-commit-id=1',
                        {
                            diff_context: {
                                revision: {
                                    base_commit_id: 1,
                                    interdiff_revision: null,
                                    is_interdiff: false,
                                    revision: 1,
                                    tip_commit_id: null,
                                },
                            },
                        }));

                    $rows.eq(1).find('.base-commit-selector').click();

                    expect(pageView.router.navigate).toHaveBeenCalledWith(
                        '1/?base-commit-id=1',
                        {
                            trigger: true,
                        });

                    expect(page.loadDiffRevision).toHaveBeenCalledWith({
                        baseCommitID: 1,
                        filenamePatterns: null,
                        interdiffRevision: null,
                        page: 1,
                        revision: 1,
                        tipCommitID: null,
                    });

                    expect(page.revision.get('baseCommitID')).toBe(1);
                    expect(page.revision.get('tipCommitID')).toBe(null);

                    const diffCommitListModel = pageView._commitListView.model;
                    expect(diffCommitListModel.get('baseCommitID')).toBe(1);
                    expect(diffCommitListModel.get('tipCommitID')).toBe(null);
                });

                it('Selecting new tip commit ID', function() {
                    page.revision.set('tipCommitID', 2);
                    pageView.render();

                    const $table = $commitList.find('table');
                    const $rows = $table.find('tbody tr');

                    expect($table.length).toBe(1);
                    expect($rows.length).toBe(3);

                    spyOn($, 'ajax').and.callFake(makeAjaxFn(
                        '/api/review-requests/123/diff-context/' +
                        '?revision=1&tip-commit-id=1',
                        {
                            diff_context: {
                                revision: {
                                    base_commit_id: null,
                                    interdiff_revision: null,
                                    is_interdiff: false,
                                    revision: 1,
                                    tip_commit_id: 1,
                                },
                            },
                        }));

                    $rows.eq(0).find('.tip-commit-selector').click();

                    expect(pageView.router.navigate).toHaveBeenCalledWith(
                        '1/?tip-commit-id=1',
                        {
                            trigger: true,
                        });

                    expect(page.loadDiffRevision).toHaveBeenCalledWith({
                        baseCommitID: null,
                        filenamePatterns: null,
                        interdiffRevision: null,
                        page: 1,
                        revision: 1,
                        tipCommitID: 1,
                    });

                    expect(page.revision.get('baseCommitID')).toBe(null);
                    expect(page.revision.get('tipCommitID')).toBe(1);

                    const diffCommitListModel = pageView._commitListView.model;
                    expect(diffCommitListModel.get('baseCommitID')).toBe(null);
                    expect(diffCommitListModel.get('tipCommitID')).toBe(1);
                });

                it('Selecting blank base commit ID', function() {
                    page.revision.set('baseCommitID', 2);
                    pageView.render();

                    const $table = $commitList.find('table');
                    const $rows = $table.find('tbody tr');

                    expect($table.length).toBe(1);
                    expect($rows.length).toBe(3);

                    spyOn($, 'ajax').and.callFake(makeAjaxFn(
                        '/api/review-requests/123/diff-context/' +
                        '?revision=1',
                        {
                            diff_context: {
                                revision: {
                                    base_commit_id: null,
                                    interdiff_revision: null,
                                    is_interdiff: false,
                                    revision: 1,
                                    tip_commit_id: null,
                                },
                            },
                        }));

                    $rows.eq(0).find('.base-commit-selector').click();

                    expect(pageView.router.navigate)
                        .toHaveBeenCalledWith('1/', {trigger: true});

                    expect(page.loadDiffRevision).toHaveBeenCalledWith({
                        baseCommitID: null,
                        filenamePatterns: null,
                        interdiffRevision: null,
                        page: 1,
                        revision: 1,
                        tipCommitID: null,
                    });

                    expect(page.revision.get('baseCommitID')).toBe(null);
                    expect(page.revision.get('tipCommitID')).toBe(null);

                    const diffCommitListModel = pageView._commitListView.model;
                    expect(diffCommitListModel.get('baseCommitID')).toBe(null);
                    expect(diffCommitListModel.get('tipCommitID')).toBe(null);
                });

                it('Selecting blank tip commit ID', function() {
                    page.revision.set('tipCommitID', 2);
                    pageView.render();

                    const $table = $commitList.find('table');
                    const $rows = $table.find('tbody tr');

                    expect($table.length).toBe(1);
                    expect($rows.length).toBe(3);

                    spyOn($, 'ajax').and.callFake(makeAjaxFn(
                        '/api/review-requests/123/diff-context/' +
                        '?revision=1',
                        {
                            diff_context: {
                                revision: {
                                    base_commit_id: null,
                                    interdiff_revision: null,
                                    is_interdiff: false,
                                    revision: 1,
                                    tip_commit_id: null,
                                },
                            },
                        }));

                    $rows.eq(2).find('.tip-commit-selector').click();

                    expect(pageView.router.navigate)
                        .toHaveBeenCalledWith('1/', {trigger: true});

                    expect(page.loadDiffRevision).toHaveBeenCalledWith({
                        baseCommitID: null,
                        filenamePatterns: null,
                        interdiffRevision: null,
                        page: 1,
                        revision: 1,
                        tipCommitID: null,
                    });

                    expect(page.revision.get('baseCommitID')).toBe(null);
                    expect(page.revision.get('tipCommitID')).toBe(null);

                    const diffCommitListModel = pageView._commitListView.model;
                    expect(diffCommitListModel.get('baseCommitID')).toBe(null);
                    expect(diffCommitListModel.get('tipCommitID')).toBe(null);
                });
            });
        });
    });
});


text:
using Orleans.Runtime;

namespace Orleankka.Behaviors
{
    public struct RequestOrigin
    {
        const string Key = "#ORLKKA_BHV_RO";

        internal static void Store(string behavior) => RequestContext.Set(Key, behavior);

        internal static RequestOrigin Restore()
        {
            var behavior = RequestContext.Get(Key);
            if (behavior == null)
                return Null;

            RequestContext.Remove(Key);
            return new RequestOrigin((string)behavior, true);
        }

        internal static readonly RequestOrigin Null = new RequestOrigin();

        internal RequestOrigin(string behavior, bool timer = false)
        {
            Behavior = behavior;
            Timer = timer;
        }

        public string Behavior { get; }
        public bool Timer {get;}

        public bool External => Behavior == null;
    }
}

redpajama_set_name: RedPajamaGithub


text:
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2019 The Chromium Authors
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<org.chromium.ui.widget.ViewLookupCachingFrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">
    <ImageButton
        android:id="@+id/tab_strip_item_button"
        style="@style/BottomToolbarButton"
        android:contentDescription="@string/accessibility_tabstrip_btn_close_tab" />
</org.chromium.ui.widget.ViewLookupCachingFrameLayout>


text:
    addon-left

      @
      <input id="addon-left" type="text" class="form-control">

    addon-right

      <input id="addon-right" type="text" class="form-control">
      .00

    addon-both

      $
      <input id="addon-both" type="text" class="form-control">
      .00


text:
Every official release of TorqueBox is published to
[rubygems.org][tb_gems]. Until TorqueBox 4 comes out of alpha or beta,
you'll need to make sure to include a version identifier in your
Gemfile to pick up the prerelease gems.

For example:

    gem "torquebox", "4.0.0.alpha1"

You can also pick and choose various TorqueBox components if you don't
want the entire suite of services. To do that, remove the `torquebox`
gem from your Gemfile and add the appropriate individual gems.

An example of using only the web and scheduling services:

    gem "torquebox-web", "4.0.0.alpha1"
    gem "torquebox-scheduling", "4.0.0.alpha1"

The list of official TorqueBox gems are:

- torquebox (aggregate of all the other gems if you want the entire stack)
- torquebox-core (dependency of others - no need to specify in your Gemfile)
- torquebox-caching
- torquebox-messaging
- torquebox-scheduling
- torquebox-web

## Incremental builds from our CI server

Every successful build from our continuous integration server
publishes updated gems to our incremental gem repository. To see the
latest incremental builds and for instructions on how to consume the
incremental gems head over to our [TorqueBox 4 incremental builds
page][4x_builds].

[tb_gems]: http://rubygems.org/gems/torquebox
[4x_builds]: http://torquebox.org/4x/builds/


text:
Wrap Media

Micro App Templates

A template mobile experience for every use case at your fingertips — Think Squarespace but for mobile apps

Over the course of 6 months, myself and the rest of the design team at Wrap Media created 150+ template micro apps for our customers to use. Templates gave our users a starting point in our platform and provided them some inspiration to create their own.

        <video src="images/app-templates/from-fb.mp4" poster="images/app-templates/from-fb.jpg" class="img-responsive play-on-scroll"></video>

Using a template required sign up for our SAAS product shown above. Once they had an account, making a new wrap from a template simply took one click.

One of the biggest challenges in constructing templates was making them easy to use so that a brand/buisiness could simply swap out their content.

Here are a few cards of a featured handful of templates we created. Here you can see many of the common use cases for the Wrap platform such as commerce, lead generation, real estate and events/conferences.

We grouped templates under visual style families — This was crucial for their find-ability in our product

Conference

Modern Sans Family

Lead Generation

Bold Modern Family

Fashion Commerce

Modern Sans Family

Creative Agency

Modern Sans Family


text:
layout: course
title: (Fall 2021) CS 410/510 - Foundations of emerging technologies
courseid: f21 410/510
desc: f21 410/510
permalink: /teaching/f21_4510_fet/
modified: <2021-10-04 Mon>
---

## Announcement
*  <2021-10-04 Mon> Future communications will be primarily via
   Slack. Check frequently "Schedule" page for reading materials, and
   Google Classroom for classwork.
*  <2021-09-27 Mon> The Fall term is officially here. Let's get started!
*  <2021-09-09 Thu> This class is switched to [Attend Anywhere](https://www.pdx.edu/covid-19-response/course-delivery-methods){:target="_blank"} mode in Fall'21. Stay tuned for more information.
*  <2021-08-03 Tue> Details such as the syllabus added.
*  <2021-05-10 Mon> Welcome! Please stay tuned for updates.

## About
*  **Syllabus**:
   [PDF]({{base}}/teaching/f21_4510_fet/f21_4510_fet_syllabus.pdf){:target="_blank"}.
*  **Instructor**: Prof. [Fang Song]({{base}}/)
*  **Email**: fsong "AT" pdx.edu. Start your email subject line
   with "f21-4510-fet".
*  **Lectures**: TR 10:00 - 11:50 @ Hoff 109.
*  **Office hours**: TBD.
*  **Google classroom**: join with code _u6njqvt_.
*  **Overview**: Emerging technologies, such as machine learning,
   quantum computing, modern cryptography, and big data analysis, have
   shown increasing significance of mathematical skills that go beyond
   a conventional discrete math course. Probability theory and linear
   algebra are evidently among the most essential. This course
   synthesizes fundamental theories and tools of probability and
   linear algebra with a slightly heavier weight on probability
   theory, and showcases applications in various topics in computer
   science. It aims to build a stronger foundation to facilitate
   future exploration in the emerging technologies.
*  **Prerequisite**: CS 350. This course is theoretical in nature and
   contains extensive practice on math and analytical problems as well
   as writing mathematical proofs.
*  **Text**: No required text. See [Resource
   page]({{base}}/teaching/w21_5610_pgm/resource/) for suggested texts
   and additional useful materials.
{% comment %}
*  **Zoom links**: Check
   "[Schedule]({{base}}/teaching/f21_4510_fet/schedule/)" page. Also
   check "PSU Classes" calendar under your PSU Google Suite.
{% endcomment %}


text:
<?php

use Illuminate\Database\Seeder;

class ClientsTableSedder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {

		$faker = \Faker\Factory::create();

        $faker->addProvider(new Faker\Provider\pt_BR\Person($faker));
        $faker->addProvider(new Faker\Provider\pt_BR\Address($faker));
        $faker->addProvider(new Faker\Provider\pt_BR\PhoneNumber($faker));

        foreach (range(1,50) as $index)
        {
            $client = new \App\Nay\Model\EntitiesModel();

            $client->entity_category       = \App\Nay\Model\EntityCategory::Client;
            $client->name                  = $faker->name;
            $client->phone                 =  $faker->phoneNumber;
            $client->cellphone             = $faker->cellphone;
            $client->cpf                   = $faker->cpf(false);
            $client->shipping_address      = $faker->address;
            $client->shipping_number       = $faker->buildingNumber;
            $client->shipping_neighborhood = $faker->word;
            $client->shipping_postalcode   = $faker->postcode;
            $client->shipping_city         = $faker->city;
            $client->shipping_state        = $faker->stateAbbr;
            $client->shipping_country      = 'BR';
            $client->shipping_complement   = $faker->secondaryAddress;
            $client->billing_address       = $faker->address;
            $client->billing_number        = $faker->buildingNumber;
            $client->billing_neighborhood  = $faker->word;
            $client->billing_postalcode    = $faker->postcode;
            $client->billing_city          = $faker->city;
            $client->billing_state         = $faker->stateAbbr;
            $client->billing_country       = 'BR';
            $client->billing_complement    = $faker->secondaryAddress;

            $client->created_by  = 1;
            $client->updated_by  = 1;
            $client->deleted_by  = 1;
            $client->created_at  = \Carbon\Carbon::now();

            $client->save();
        }
    }
}


text:
This plugin is used for mouse hover and tap on a point of plot series.
It supports the following options:
```js
grid: {
    hoverable: false, //to trigger plothover event on mouse hover or tap on a point
    clickable: false //to trigger plotclick event on mouse hover
}
```

It listens to native mouse move event or click, as well as artificial generated
tap and touchevent.

When the mouse is over a point or a tap on a point is performed, that point or
the correscponding bar will be highlighted and a "plothover" event will be generated.

Custom "touchevent" is triggered when any touch interaction is made. Hover plugin
handles this events by unhighlighting all of the previously highlighted points and generates
"plothovercleanup" event to notify any part that is handling plothover (for exemple to cleanup
the tooltip from webcharts).


text:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
         xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

  <filter>
    <filter-name>webFilter</filter-name>
    <filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
  </filter>

  <filter-mapping>
    <filter-name>webFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <listener>
    <listener-class>com.google.sitebricks.mail.webapp.WebConfig</listener-class>
  </listener>

</web-app>


text:
Ephemerald manages pools of short-lived servers to be used for testing purposes.  It was built to allow paralallel integration tests.

[![asciicast](https://asciinema.org/a/117629.png)](https://asciinema.org/a/117629)

It has [REST API](#api) for accessing server instances from any language and comes with a built-in [go client](net/client.go). See the [examples](_example/) directory for example configurations and client usage.

The ephemerald server can run on a remote host; container connection parameters are rewritten so that the client
connects to the right place.  This way ephemerald can run on a large server and be used from less powerful
development machines.

* [Running](#running)
* [Configuration](#building)
  * [Params](#params)
  * [Container](#container)
  * [Lifecycle Actions](#lifecycle-actions)
    * [noop](#noop)
    * [exec](#exec)
    * [http.get](#httpget)
    * [tcp.connect](#tcpconnect)
    * [postgres.exec](#postgresexec)
    * [postgres.ping](#postgresping)
    * [postgres.truncate](#postgrestruncate)
    * [redis.exec](#redisexec)
    * [redis.ping](#redisping)
    * [redis.truncate](#redistruncate)
* [API](#api)
  * [Checkout](#checkout)
  * [Return](#return)
  * [Batch Checkout](#batch-checkout)
  * [Batch Return](#batch-return)
* [Building](#building)
* [Installing](#installing)
  * [Homebrew](#homebrew)
  * [Binary](#binary)
  * [Source](#source)
* [TODO](#todo)

## Running

To run the server, supply a configuration file:

```sh
$ ephemerald -c config.yaml
```

Press Q to quit the server.

### Flags

 * `--help` print help message.
 * `-p <port>` changes the listen port.  Defaults to 6000
 * `--ui stream` will dump the event steam to the console in lieu of a curses-esque UI.
 * `--ui none` will not print any UI information (useful with `--log-file /dev/stdout`)
 * `--log-file <path>` write logs to file at `path`.  Defaults to `/dev/null`
 * `--log-level <level>` log level.  defaults to `info`.  Options are `debug`,`info`,`warn`,`error`

Note: use Ctrl-C to stop the server wen not in `--ui tui` mode (`SIGINT`,`SIGQUIT` always work too)

For example, to see only log messages (at debug level) use:

```sh
$ ephememerald --ui none --log-level debug --log-file /dev/stdout -c config.yaml
```

## Configuration

Container pools are configured in a yaml (or json) file.  Each pool has options for the container parameters and
for lifecycle actions.

The following configuration creates a single pool called "pg" which maintains five containers from the "postgres" image and
exposes port 5432 to clients.  See the [`params`](#params) and [`actions`](#lifecycle-actions) below for documentation on those fields.

```yaml
pools:
  pg:
    image: postgres
    size: 5
    port: 5432
    params:
      username: postgres
      database: postgres
      url: postgres://{{.Username}}:@{{.Hostname}}:{{.Port}}/{{.Database}}?sslmode=disable
    actions:
      healthcheck:
        type: postgres.ping
        retries: 10
        delay:   50ms
      initialize:
        type:    exec
        path:    make
        args:    [ 'db:migrate' ]
        env:     [ 'DATABASE_URL={{.Url}}' ]
        timeout: 10s
      reset:
        type: postgres.truncate
```

See [example/config.yaml](_example/config.yaml) for a full working configuration.

### Params

The `params` entry allows for declaring parameters needed for connecting to the service.  There are three fields
with arbitrary values: `username`, `password`, `database`.

The `url` can be a golang template and will be executed with access to the following fields:

Name | Value
--- | ---
Hostname | The hostname that the container can be connected at
Port | The (automatically-generated) port number that is mapped to the exposed container port
Username | The `username` field declared in `params`
Password | The `password` field declared in `params`
Database | The `database` field declared in `params`

A `params` section for postgres may look like this:

```yaml
username: postgres
database: postgres
url: postgres://{{.Username}}:{{.Password}}@{{.Hostname}}:{{.Port}}/{{.Database}}?sslmode=disable
```

### Container

The `container` section is passed-through to docker when creating the container.  The available
options are:

 * labels
 * env
 * cmd
 * volumes
 * entrypoint
 * user
 * capadd
 * capdrop

### Lifecycle Actions

There are three lifecycle actions: `healthcheck`, `initialize`, and `reset`.

 * `healthcheck` is used to determine when the container is ready to be used.
 * `initialize` is used to initialize the container (run migrations, etc...)
 * `reset` may be used to revert the container to a state where it can be used again.

All of them are optional (though `healthcheck` should be used).  If `reset` is not given,
the container will be killed and a new one will be created to replace it.

Each action has, at a minimum, the following three parameters:

Name | Default | Description
---  | --- | ---
retries | 3 | number of times to retry the action
timeout | 1s | amount of time to allow for the action
delay | 500ms | amount of time to delay before retrying

`timeout` and `delay` are durations; they must have a unit suffix as described [here](https://golang.org/pkg/time/#ParseDuration).

Note: actions may have different defaults for these fields.

#### noop

Does nothing.  Useful as the `reset` action so that a container is always reused.

#### exec

Execute a command on the host operating system.  Useful for running migrations to initialize a database.

Extra Parameters:

Name | Default | Description
--- | --- | ---
command| `""` | command to execute
args | `[]` | command-line arguments
env | `[]` | environment variables
dir | `""` | directory to execute in.

The `env` entries may be templates with access to the same fields as the [`params`](#params) url template.  Additionally,
the following environment variables are always set:

 * `EPHEMERALD_ID`
 * `EPHEMERALD_HOSTNAME`
 * `EPHEMERALD_PORT`
 * `EPHEMERALD_USERNAME`
 * `EPHEMERALD_PASSWORD`
 * `EPHEMERALD_DATABASE`
 * `EPHEMERALD_URL`

If `dir` is not set, the working directory of the server isused.

#### http.get

Run a HTTP GET request.

Extra Parameters:

Name | Default | Description
--- | --- | ---
url | `""` | url to request

If `url` is blank, the `url` from the [`params`](#params) is used.

If `url` is not blank, it may be a template which has access to the same fields that [`params`](#params) url template does.

#### tcp.connect

Connect to the exposed container port over TCP.

#### postgres.exec

Executes a query on the database.

Extra Parameters:

Name | Default | Description
--- | --- | ---
command | `"SELECT 1=1"` | query to execute
args | `[]` | values to be escaped with positional arguments in `command`.

Example:

```yaml
type:     postgres.exec
command: 'INSERT INTO users (name) VALUES ($1)'
args:    "Robert'); DROP TABLE STUDENTS;--"
```

#### postgres.ping

Pings the database.  Useful for healthcheck.

#### postgres.truncate

Runs `TRUNCATE TABLE x CASCADE` for all tables `x`.

Extra Parameters:

Name | Default | Description
--- | --- | ---
exclude | `[]` | an array of table names to not truncate (eg migration versions)

#### redis.exec

Execute a redis command.

Extra Parameters:

Name | Default | Description
--- | --- | ---
command | `"PING"` | redis command to execute

#### redis.ping

This is an alias for `redis.exec`.

#### redis.truncate

This is an alias for `redis.exec` with a default command of `"FLUSHALL"`.

## API

There is a REST API for clients to checkout and return items from one or more pools.

### Checkout

`POST /checkout/{pool}` checks out an instance from the given pool and returns
that instance's parameters:

```sh
$ curl -s -XPOST localhost:6000/checkout/postgres | jq
{
  "id":"8482c266192f013346d03f71b2aa6d4b647909e3502ac525039bdd0fe9fcac30",
  "hostname":"localhost",
  "port":"34031",
  "username":"postgres",
  "database":"postgres",
  "url":"postgres://postgres:@localhost:34031/postgres?sslmode=disable"
}
```

### Return

`DELETE /return/{pool}/{id}` returns the instance given by `id` to the pool `pool`:

```sh
$ curl -s -XDELETE localhost:6000/return/postgres/8482c266192f013346d03f71b2aa6d4b647909e3502ac525039bdd0fe9fcac30
```

### Batch Checkout

`POST /checkout` checks out an instance from every configured pool.

```sh
$ curl -s -XPOST localhost:6000/checkout | tee checkout.json | jq
{
  "postgres": {
    "id": "2dedf5dbe9cc8d7a0cd71ed75455c7310db79aea44925562b82c01b959d85e7e",
    "hostname": "localhost",
    "port": "34023",
    "username": "postgres",
    "database": "postgres",
    "url": "postgres://postgres:@localhost:34023/postgres?sslmode=disable"
  },
  "redis": {
    "id": "a8dbf5043c7145510f48ccffa6f1e20b9f2c8140dda73d567a29dc2ec823ca46",
    "hostname": "localhost",
    "port": "34019",
    "database": "0",
    "url": "redis://localhost:34019/0"
  },
  "vault": {
    "id": "11f4752d5e0b762c65b05809c9500a6e0a20ee4a79b861638a084adf77dbfb78",
    "hostname": "localhost",
    "port": "34021",
    "url": "http://localhost:34021"
  }
}
```

### Batch Return

`DELETE /return` returns multiple instances at once.  Meant to be used with [batch checkout](#batch-checkout).

```sh
$ cat checkout.json
{
  "postgres": {
    "id": "2dedf5dbe9cc8d7a0cd71ed75455c7310db79aea44925562b82c01b959d85e7e"
  },
  "redis": {
    "id": "a8dbf5043c7145510f48ccffa6f1e20b9f2c8140dda73d567a29dc2ec823ca46"
  },
  "vault": {
    "id": "11f4752d5e0b762c65b05809c9500a6e0a20ee4a79b861638a084adf77dbfb78"
  }
}
$ curl -XDELETE -H'Content-Type: application/json' -d @checkout.json localhost:6000/return
```

Note that the complete response from [batch checkout](#batch-checkout) may be sent.  The only requirement is the `id` field for each pool instance.

## Building

```sh
$ govendor get -d github.com/boz/ephemerald/...
$ cd $GOPATH/src/github.com/boz/ephemerald
$ make server example
```
Run the example server and client in separate terminals

```sh
$ ./ephemerald/ephemerald -c _example/config.yaml
```

```sh
$ ./_example/example
```

## Installing

### Source

Follow the [building](#building) steps then run `make install`:

```sh
$ make install
```

### Binary

Download the [latest release](https://github.com/boz/ephemerald/releases/latest) for your system.  Unpack the archive and put the binary in your path.

```sh
$ release="https://github.com/boz/ephemerald/releases/download/v0.3.1/ephemerald_Linux_x86_64.tar.gz"
$ curl -L "$release" | tar -C /tmp -zxv
$ /tmp/ephemerald -c config.yaml
```

### Homebrew

```
$ brew install boz/repo/ephemerald
```

## TODO

 * Configuration
   * Current parsing is a disaster
   * Allow yaml
   * Allow built-in defaults (postgres, redis, etc...)
 * Polish/Optimize/Cleanup/Refactor UI.
 * Re-add remote actions (websockets API)
 * Clients: nodejs, ruby, python, etc...
 * Documentation
 * Tests


text:
RMDateSelectionViewController [![Build Status](https://travis-ci.org/CooperRS/RMDateSelectionViewController.svg?branch=master)](https://travis-ci.org/CooperRS/RMDateSelectionViewController/) [![Pod Version](https://img.shields.io/cocoapods/v/RMDateSelectionViewController.svg)](https://cocoapods.org/pods/RMDateSelectionViewController) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
=============================

This framework allows you to select a date by presenting an action sheet. In addition, it allows you to add actions arround the presented date picker which behave like a button and can be tapped by the user. The result looks very much like an UIActionSheet or UIAlertController with a UIDatePicker and some UIActions attached.

Besides being a fully-usable project, RMDateSelectionViewController also is an example for an use case of [RMActionController](https://github.com/CooperRS/RMActionController). You can use it to learn how to present a date picker other than UIDatePicker.

## Screenshots

### Portrait

| White | Black |
|:-----:|:-----:|
|![Portrait](http://cooperrs.github.io/RMDateSelectionViewController/Images/Blur-Screen-Portrait.png)|![Colors](http://cooperrs.github.io/RMDateSelectionViewController/Images/Blur-Screen-Portrait-Black.png)|

### Landscape
![Landscape](http://cooperrs.github.com/RMDateSelectionViewController/Images/Blur-Screen-Landscape.png)

## Demo Project
If you want to run the demo project do not forget to initialize submodules.

## Installation (CocoaPods)
```ruby
platform :ios, '8.0'
pod "RMDateSelectionViewController", "~> 2.3.1"
```

## Usage

For a detailed description on how to use RMDateSelectionViewController take a look at the [Wiki Pages](https://github.com/CooperRS/RMDateSelectionViewController/wiki). The following four steps are a very short intro:

* Import RMDateSelectionViewController:

```objc
#import <RMDateSelectionViewController/RMDateSelectionViewController.h>
```

* Create select and cancel actions:

```objc
RMAction<UIDatePicker *> *selectAction = [RMAction<UIDatePicker *> actionWithTitle:@"Select" style:RMActionStyleDone andHandler:^(RMActionController<UIDatePicker *> *controller) {
    NSLog(@"Successfully selected date: %@", controller.contentView.date);
}];

RMAction<UIDatePicker *> *cancelAction = [RMAction<UIDatePicker *> actionWithTitle:@"Cancel" style:RMActionStyleCancel andHandler:^(RMActionController<UIDatePicker *> *controller) {
    NSLog(@"Date selection was canceled");
}];
```

* Create and instance of RMDateSelectionViewController and present it:

```objc
RMDateSelectionViewController *dateSelectionController = [RMDateSelectionViewController actionControllerWithStyle:RMActionControllerStyleWhite title:@"Test" message:@"This is a test message.\nPlease choose a date and press 'Select' or 'Cancel'." selectAction:selectAction andCancelAction:cancelAction];

[self presentViewController:dateSelectionController animated:YES completion:nil];
```

* The following code block shows you a complete method:

```objc
- (IBAction)openDateSelectionController:(id)sender {
    RMAction<UIDatePicker *> *selectAction = [RMAction<UIDatePicker *> actionWithTitle:@"Select" style:RMActionStyleDone andHandler:^(RMActionController<UIDatePicker *> *controller) {
        NSLog(@"Successfully selected date: %@", controller.contentView.date);
    }];

    RMAction<UIDatePicker *> *cancelAction = [RMAction<UIDatePicker *> actionWithTitle:@"Cancel" style:RMActionStyleCancel andHandler:^(RMActionController<UIDatePicker *> *controller) {
        NSLog(@"Date selection was canceled");
    }];

    RMDateSelectionViewController *dateSelectionController = [RMDateSelectionViewController actionControllerWithStyle:RMActionControllerStyleWhite title:@"Test" message:@"This is a test message.\nPlease choose a date and press 'Select' or 'Cancel'." selectAction:selectAction andCancelAction:cancelAction];

    [self presentViewController:dateSelectionController animated:YES completion:nil];
}
```

## Migration

See [Migration](https://github.com/CooperRS/RMDateSelectionViewController/wiki/Migration) on how to migrate to the latest version of RMDateSelectionViewController.

## Documentation
There is an additional documentation available provided by the CocoaPods team. Take a look at [cocoadocs.org](http://cocoadocs.org/docsets/RMDateSelectionViewController/).

## Requirements

| Compile Time  | Runtime       |
| :------------ | :------------ |
| Xcode 7       | iOS 8         |
| iOS 9 SDK     |               |
| ARC           |               |

Note: ARC can be turned on and off on a per file basis.

Version 1.5.0 and above of RMDateSelectionViewController use custom transitions for presenting the date selection controller. Custom transitions are a new feature introduced by Apple in iOS 7. Unfortunately, custom transitions are totally broken in landscape mode on iOS 7. This issue has been fixed with iOS 8. So if your application supports landscape mode (even on iPad), version 1.5.0 and above of this control require iOS 8. Otherwise, iOS 7 should be fine. In particular, iOS 7 is fine for version 1.4.3 and below.

## Apps using this control
Using this control in your app or know anyone who does?

Feel free to add the app to this list: [Apps using RMDateSelectionViewController](https://github.com/CooperRS/RMDateSelectionViewController/wiki/Apps-using-RMDateSelectionViewController)

## Further Info
If you want to show an UIPickerView instead of an UIDatePicker, you may take a look at my other control called [RMPickerViewController](https://github.com/CooperRS/RMPickerViewController).

If you want to show any other control you may want to take a look at [RMActionController](https://github.com/CooperRS/RMActionController).

## Credits
Code contributions:
* AnthonyMDev
    * Cancel delegate method should be optional
* Digeon Benjamin
    * Delegate method when now button is pressed
    * Cancel delegate method is called when background view is tapped
* Denis Andrasec
    * Bugfixes
* Robin Franssen
	* Block support
* Scott Chou
    * Images for cancel and select button
* steveoleary
	* Bugfixes

Localizations:
* Vincent Xue (Chinese)
* Alex Studnička (Czech)
* Robin Franssen (Dutch)
* tobiasgr (Danish)
* Thomas Besnehard (French)
* Heberti Almeida (Portuguese)
* Anton Rusanov (Russian)
* Pedro Ventura (Spanish)
* Aron Manucheri (Swedish)
* Vinh Nguyen (Vietnamese)

I want to thank everyone who has contributed code and/or time to this project!

## License (MIT License)

```
Copyright (c) 2013-2016 Roland Moers

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 NONINFRINGEMENT. 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.
```


text:
package org.apache.camel.component.ibatis;

import org.apache.camel.ShutdownRunningTask;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
import org.junit.Before;
import org.junit.Test;

/**
 * @version
 */
public class IBatisShutdownAllTasksTest extends IBatisTestSupport {

    @Override
    @Before
    public void setUp() throws Exception {
        super.setUp();

        // super will insert 2 accounts already

        Account account = new Account();
        account.setId(881);
        account.setFirstName("A");
        account.setLastName("A");
        account.setEmailAddress("a@gmail.com");

        template.sendBody("ibatis:insertAccount?statementType=Insert", account);

        account = new Account();
        account.setId(882);
        account.setFirstName("B");
        account.setLastName("B");
        account.setEmailAddress("b@gmail.com");

        template.sendBody("ibatis:insertAccount?statementType=Insert", account);

        account = new Account();
        account.setId(883);
        account.setFirstName("C");
        account.setLastName("C");
        account.setEmailAddress("c@gmail.com");

        template.sendBody("ibatis:insertAccount?statementType=Insert", account);

        account = new Account();
        account.setId(884);
        account.setFirstName("D");
        account.setLastName("D");
        account.setEmailAddress("d@gmail.com");

        template.sendBody("ibatis:insertAccount?statementType=Insert", account);

        account = new Account();
        account.setId(885);
        account.setFirstName("E");
        account.setLastName("E");
        account.setEmailAddress("e@gmail.com");

        template.sendBody("ibatis:insertAccount?statementType=Insert", account);

        account = new Account();
        account.setId(886);
        account.setFirstName("F");
        account.setLastName("F");
        account.setEmailAddress("f@gmail.com");

        template.sendBody("ibatis:insertAccount?statementType=Insert", account);
    }

    @Test
    public void testShutdownAllTasks() throws Exception {
        context.startRoute("route1");

        MockEndpoint bar = getMockEndpoint("mock:bar");
        bar.expectedMinimumMessageCount(1);
        bar.setResultWaitTime(3000);

        assertMockEndpointsSatisfied();

        // shutdown during processing
        context.stop();

        // sleep a little
        Thread.sleep(1000);

        // should route all 8
        assertEquals("Should complete all messages", 8, bar.getReceivedCounter());
    }

    @Override
    protected RouteBuilder createRouteBuilder() throws Exception {
        return new RouteBuilder() {
            @Override
            public void configure() throws Exception {
                from("ibatis:selectAllAccounts").noAutoStartup().routeId("route1")
                     // let it complete all tasks
                     .shutdownRunningTask(ShutdownRunningTask.CompleteAllTasks)
                     .delay(1000).to("seda:foo");

                from("seda:foo").routeId("route2").to("mock:bar");
            }
        };
    }

}


text:
#  `amp-hulu`

    Description
    Displays a simple embedded Hulu video.

    Required Script
    <script async custom-element="amp-hulu" src="https://cdn.ampproject.org/v0/amp-hulu-0.1.js"></script>

    Supported Layouts
    fill, fixed, fixed-height, flex-item, responsive

    Examples
    Annotated code example for amp-hulu

[TOC]

## Example

```html
<amp-hulu width="412" height="213" layout="responsive"
  data-eid="4Dk5F2PYTtrgciuvloH3UA">
</amp-hulu>
```

## Attributes

##### data-eid

The ID of the video. For example, `4Dk5F2PYTtrgciuvloH3UA` is the eid in the following URL: https://player.hulu.com/site/dash/mobile_embed.html?eid=4Dk5F2PYTtrgciuvloH3UA.

##### common attributes

This element includes [common attributes](https://www.ampproject.org/docs/reference/common_attributes) extended to AMP components.

## Validation

See [amp-hulu rules](https://github.com/ampproject/amphtml/blob/master/extensions/amp-hulu/validator-amp-hulu.protoascii) in the AMP validator specification.