/* global AJS:false */
AJS.test.require([
    'com.atlassian.bitbucket.server.bitbucket-web:searchable-selector',
    'com.atlassian.bitbucket.server.bitbucket-it-javascript:js-test-utils'
], function() {

    var WAIT_FOR_OPEN_OPTIONS = {
        predicate: function () {
            return searchableSelector.dialog.is(':visible');
        },
        message: 'Waiting for dialog to open'
    };
    var WAIT_FOR_DIALOG_LOAD_OPTIONS = {
        initialWait: 400,
        predicate: function () {
            return searchableSelector.dialog.find('.spinner').is(':not(:visible)');
        },
        message: 'Waiting for dialog to finishing loading'
    };

    var $ = jQuery;
    var ajax = require('bitbucket/internal/util/ajax');
    var events = require('bitbucket/internal/util/events');
    var SearchableSelector = require('bitbucket/internal/widget/searchable-selector');
    var createWaiter = require('bitbucket/internal/test/waiter');
    var $button;
    var $field;
    var init;
    var searchableSelector;
    var ajaxRestHandler;
    var testDataPage;
    var getTriggerOptions;
    var getSelectorOptions;

    module('widget-searchable-selector', {
        setup: function(){

            getTriggerOptions = function() { return { id: 'test-trigger' }; }; //Override this in tests to add trigger template options.
            getSelectorOptions = function() { return { id: 'test-selector', url: '/some/rest/url', field: $field }; }; //Override this in tests to add selector options.
            ajaxRestHandler = function () { //Override this in tests to change the rest data.
                return $.Deferred().resolve(testDataPage);
            };

            testDataPage = {
                'start': 0,
                'size': 10,
                'limit': 10,
                'isLastPage': true,
                'values': [{
                    'id': 1,
                    'name': 'First item'
                }, {
                    'id': 2,
                    'name': 'Second item'
                }, {
                    'id': 3,
                    'name': 'Third item'
                }, {
                    'id': 4,
                    'name': 'Fourth item'
                }, {
                    'id': 5,
                    'name': 'Fifth item'
                }, {
                    'id': 6,
                    'name': 'Sixth item'
                }, {
                    'id': 7,
                    'name': 'Seventh item'
                }, {
                    'id': 8,
                    'name': 'Eighth item'
                }, {
                    'id': 9,
                    'name': 'Ninth item'
                }, {
                    'id': 10,
                    'name': 'Tenth item'
                }]
            };

            sinon.stub(ajax, "rest", function () {
                return ajaxRestHandler.apply(this, arguments);
            });

            init = function(){
                var $fixture = $("#qunit-fixture");
                var $buttonAndField = $(bitbucket.internal.widget.searchableSelectorTriggerWithField(getTriggerOptions())).appendTo($fixture);
                $button = $buttonAndField.eq(0);
                $field = $buttonAndField.eq(1);
                searchableSelector = new SearchableSelector($button, getSelectorOptions());
                sinon.spy(searchableSelector.dialog, 'show');
                sinon.spy(searchableSelector.dialog, 'hide');
            };
        },
        teardown: function(){
            searchableSelector && searchableSelector.destroy();
            searchableSelector = null;
            $button = null;
            $field = null;
            ajax.rest.restore();
            getTriggerOptions = null;
            getSelectorOptions = null;
            ajaxRestHandler = null;
            testDataPage = null;
            init = null;
            events.off('*');
        },
        simulateTyping: function(searchTerm) {
            searchableSelector.$searchField.val(searchTerm).trigger('keydown');
        },
        simulateKey: function(keyCode) {
            var event = jQuery.Event('keydown', { keyCode: keyCode, which: keyCode });
            searchableSelector.$searchField.trigger(event);
        },
        simulateTriggerClick: function() {
            $button.click();
        },
        waitForOpen: function(waiter) {
            return waiter.waitFor(WAIT_FOR_OPEN_OPTIONS)
        },
        thenWaitForDialogLoading: function(waiter) {
            return waiter.thenWaitFor(WAIT_FOR_DIALOG_LOAD_OPTIONS);
        }
    });

    QUnit.test("Basic operations", function(assert) {
        var waiter = createWaiter(assert);
        init();
        this.simulateTriggerClick();
        this.waitForOpen(waiter)
            .done(function() {
                assert.ok(searchableSelector.dialog.find('.results-list > li.result').length, "Dialog shows results");

                var itemSelectedSpy = sinon.spy();
                events.on('bitbucket.internal.widget.searchableSelector.itemSelected', itemSelectedSpy);
                assert.ok(!itemSelectedSpy.called, "itemSelectedSpy should not have been called before clicking a result");

                searchableSelector.dialog.find('.results-list > li:first > a').click();

                assert.deepEqual(searchableSelector.getSelectedItem(), testDataPage.values[0], "Clicking item sets selected item");
                assert.strictEqual($button.find('.name').data('id'), testDataPage.values[0].id, "Clicking item updates trigger data-id");
                assert.strictEqual($button.find('.name').text(), testDataPage.values[0].name, "Clicking item updates trigger text");
                assert.equal($field.val(), testDataPage.values[0].id, "Clicking item updates form field"); //form field values are always strings, so use equals instead of strictEquals
                assert.ok(itemSelectedSpy.calledOnce, "Clicking item triggers itemSelected event");
                assert.ok(itemSelectedSpy.alwaysCalledOn(searchableSelector), "Context was set correctly for itemSelected event");
                assert.ok(itemSelectedSpy.alwaysCalledWithExactly(testDataPage.values[0], null), "itemSelected event was triggered with correct arguments");
                events.off('bitbucket.internal.widget.searchableSelector.itemSelected', itemSelectedSpy);
            }).always(waiter.resume);
    });

    QUnit.test("buildObjectFromElementDataAttributes", function(assert) {
        var targetObjectFromDataAttributes = {
            some: {
                nested: {
                    json: "object"
                }
            }
        };

        init();

        var fakeElem = $('<div></div>').attr('data-item', JSON.stringify(targetObjectFromDataAttributes))[0];

        assert.deepEqual(searchableSelector._buildObjectFromElementDataAttributes(fakeElem), targetObjectFromDataAttributes, "Object restored from data attributes");
    });

    QUnit.test("constructDataPageFromPreloadArray", function(assert) {
        var expectedPage = $.extend(true, {}, testDataPage, {isLastPage:false});

        assert.deepEqual(SearchableSelector.constructDataPageFromPreloadArray(testDataPage.values), expectedPage, "constructDataPageFromPreloadArray works with valid array param");
        assert.strictEqual(SearchableSelector.constructDataPageFromPreloadArray({}), null, "constructDataPageFromPreloadArray returns null with non-array param");
        assert.strictEqual(SearchableSelector.constructDataPageFromPreloadArray(), null, "constructDataPageFromPreloadArray returns null with no params");
    });


    QUnit.test("dedupe", function(assert) {
        var allDupes = {
            'start': 0,
            'size': 5,
            'limit': 10,
            'isLastPage': true,
            'values': [{
                'id': 1,
                'name': 'First duplicate item'
            }, {
                'id': 2,
                'name': 'Second duplicate item'
            }, {
                'id': 3,
                'name': 'Third duplicate item'
            }, {
                'id': 4,
                'name': 'Fourth duplicate item'
            }, {
                'id': 5,
                'name': 'Fifth duplicate item'
            }]
        };
        var threeDupes = {
            'start': 0,
            'size': 5,
            'limit': 10,
            'isLastPage': true,
            'values': [{
                'id': 1,
                'name': 'First duplicate item'
            }, {
                'id': 2,
                'name': 'Second duplicate item'
            }, {
                'id': 3,
                'name': 'Third duplicate item'
            }, {
                'id': 14,
                'name': 'First unique item'
            }, {
                'id': 15,
                'name': 'Second unique item'
            }]
        };
        var noDupes = {
            'start': 0,
            'size': 5,
            'limit': 10,
            'isLastPage': true,
            'values': [{
                'id': 11,
                'name': 'First unique item'
            }, {
                'id': 12,
                'name': 'Second unique item'
            }, {
                'id': 13,
                'name': 'Third unique item'
            }, {
                'id': 14,
                'name': 'Fourth unique item'
            }, {
                'id': 15,
                'name': 'Fifth unique item'
            }]
        };
        var emptyValues = {
            'start': 0,
            'size': 0,
            'limit': 10,
            'isLastPage': true,
            'values': []
        };
        var noValues = {
            'start': 0,
            'size': 0,
            'limit': 10,
            'isLastPage': true
        };

        assert.strictEqual(SearchableSelector.prototype.dedupeData(testDataPage, allDupes).values.length, testDataPage.values.length - allDupes.values.length, "All dupes removed when preload is 100% dupes");
        assert.strictEqual(SearchableSelector.prototype.dedupeData(testDataPage, threeDupes).values.length, testDataPage.values.length - 3, "All dupes removed when preload has dupes, but isn't all dupes");
        assert.strictEqual(SearchableSelector.prototype.dedupeData(testDataPage, noDupes).values.length, testDataPage.values.length, "No items removed when preload has no dupes");
        assert.strictEqual(SearchableSelector.prototype.dedupeData(testDataPage, emptyValues).values.length, testDataPage.values.length, "No items removed when preload has empty values array");
        assert.strictEqual(SearchableSelector.prototype.dedupeData(testDataPage, noValues).values.length, testDataPage.values.length, "No items removed when preload has no values array");
        assert.strictEqual(SearchableSelector.prototype.dedupeData(testDataPage, {}).values.length, testDataPage.values.length, "No items removed when preload is empty object");
        assert.strictEqual(SearchableSelector.prototype.dedupeData(testDataPage).values.length, testDataPage.values.length, "No items removed when preload not supplied");
        assert.deepEqual(SearchableSelector.prototype.dedupeData([1,2,3], {}), [1,2,3], "Data incorrect format returns original data");
        assert.equal(SearchableSelector.prototype.dedupeData(null, {}), null, "Data not supplied returns null");
        assert.equal(SearchableSelector.prototype.dedupeData(), null, "No params supplied returns null");
    });

    QUnit.test("Selector has initial state", function(assert) {
        getTriggerOptions = function(){
            return {
                id: 'test-trigger',
                item: testDataPage.values[0]
            };
        };

        init();

        assert.deepEqual(searchableSelector.getSelectedItem(), testDataPage.values[0], "Supplying initial item sets selected item");
        assert.strictEqual($button.find('.name').data('id'), testDataPage.values[0].id, "Supplying initial item sets trigger data-id");
        assert.strictEqual($button.find('.name').text(), testDataPage.values[0].name, "Supplying initial item sets trigger text");
        assert.equal($field.val(), testDataPage.values[0].id, "Supplying initial item sets form field value"); //form field values are always strings, so use equals instead of strictEquals
    });

    QUnit.test("Manipulating selection", function(assert) {
        init();

        var itemSelectedSpy = sinon.spy();
        events.on('bitbucket.internal.widget.searchableSelector.itemSelected', itemSelectedSpy);
        assert.ok(!itemSelectedSpy.called, "itemSelectedSpy should not have been called before selecting an item");
        assert.equal(searchableSelector.getSelectedItem(), null, "No initial selection");

        searchableSelector.setSelectedItem(testDataPage.values[0]);

        assert.deepEqual(searchableSelector.getSelectedItem(), testDataPage.values[0], "Setting item sets selected item");
        assert.strictEqual($button.find('.name').data('id'), testDataPage.values[0].id, "Setting item sets trigger data-id");
        assert.strictEqual($button.find('.name').text(), testDataPage.values[0].name, "Setting item sets trigger text");
        assert.equal($field.val(), testDataPage.values[0].id, "Setting item sets form field value"); //form field values are always strings, so use equals instead of strictEquals
        assert.ok(itemSelectedSpy.calledOnce, "Setting item triggers itemSelected event");
        assert.ok(itemSelectedSpy.alwaysCalledOn(searchableSelector), "Context was set correctly for itemSelected event");
        assert.ok(itemSelectedSpy.alwaysCalledWithExactly(testDataPage.values[0], null), "itemSelected event was triggered with correct arguments");

        searchableSelector.setSelectedItem(testDataPage.values[0]);
        assert.ok(itemSelectedSpy.calledOnce, "Setting the same selected item should not trigger a change");

        searchableSelector.clearSelection();
        assert.equal(searchableSelector.getSelectedItem(), null, "No selected item after clearing");
        assert.strictEqual($button.find('.name').length, 0, "No item in trigger after clearing");
        assert.strictEqual($button.find('.placeholder').length, 1, "Placeholder in trigger after clearing");
        assert.strictEqual($field.val(), '', "Clearing the selected item sets empty form field value"); //form field values are always strings, so use equals instead of strictEquals
        assert.ok(itemSelectedSpy.calledOnce, "Clearing the selected item should not trigger a change");

        events.off('bitbucket.internal.widget.searchableSelector.itemSelected', itemSelectedSpy);
    });

    QUnit.test("Searching", function(assert) {
        ajaxRestHandler = function (options) {
            var results = testDataPage;

            if (options.data.filter) {
               results = {
                   'start': 0,
                   'size': 1,
                   'limit': 10,
                   'isLastPage': true,
                   'values': [{
                       'id': options.data.filter,
                       'name': options.data.filter
                   }]
               };
            }

            return $.Deferred().resolve(results);
        };

        init();

        var waiter = createWaiter(assert);
        this.simulateTriggerClick();
        this.waitForOpen(waiter)
            .done(function() {
                assert.strictEqual(searchableSelector.dialog.find('.results-list > li.result').length, 10, "Results are unfiltered initially");
            })
            .then(this.simulateTyping.bind(this, 'foo'))
            .then(this.thenWaitForDialogLoading(waiter))
            .done(function() {
                assert.strictEqual(searchableSelector._getSearchTerm(), "foo", "Entered search term is retrieved correctly");
                assert.strictEqual(searchableSelector.dialog.find('.results-list > li.result:first a').text(), "foo", "Search results are returned");
            })
            .then(this.simulateTyping.bind(this, ''))
            .then(this.thenWaitForDialogLoading(waiter))

            .done(function(){
                assert.strictEqual(searchableSelector.dialog.find('.results-list > li.result').length, 10, "Unfiltered results are restored when search is cleared");
            })
            .always(waiter.resume);
    });

    QUnit.test("Disable search", function(assert) {
        getSelectorOptions = function() {
            return {
                id: 'test-selector',
                url: '/some/rest/url',
                field: $field,
                searchable: false
            };
        };
        var waiter = createWaiter(assert);

        init();

        this.simulateTriggerClick();
        this.waitForOpen(waiter)
            .done(function(){
                assert.strictEqual(searchableSelector.dialog.find('input.filter').length, 0, 'No search field in dialog');
            })
            .always(waiter.resume);
    });

    QUnit.test("Use tabs", function(assert) {
        getSelectorOptions = function() {
            return {
                id: 'test-selector',
                field: $field,
                tabs: [
                        {
                            label: 'tab1',
                            url: 'tab1'
                        },
                        {
                            label: 'tab2',
                            url: 'tab2'
                        }
                    ]
            };
        };

        ajaxRestHandler = function (options) {
            var results = {
                    'start': 0,
                    'size': 1,
                    'limit': 10,
                    'isLastPage': true,
                    'values': [{
                        'id': options.url,
                        'name': options.url
                    }]
                };

            return $.Deferred().resolve(results);
        };
        var waiter = createWaiter(assert);

        init();

        this.simulateTriggerClick();
        this.waitForOpen(waiter)
            .then(function() {
                assert.strictEqual(searchableSelector.dialog.find('li.menu-item').length, 2, '2 tabs in dialog');
                assert.ok(searchableSelector.dialog.find('li.menu-item:first').is('.active-tab'), "First tab is active");
                assert.ok(searchableSelector.dialog.find('.tabs-pane:first').is(':visible'), "First tab pane is visible");
                assert.strictEqual(searchableSelector.dialog.find('.tabs-pane:first li.result:first a').text(), "tab1", "Correct results are shown for tab");

                searchableSelector.dialog.find('li.menu-item:eq(1) a').click();
                assert.ok(searchableSelector.dialog.find('li.menu-item:eq(1)').is('.active-tab'), "Second tab is active");
                assert.ok(searchableSelector.dialog.find('.tabs-pane:eq(1)').is(':visible'), "Second tab pane is visible");
                assert.strictEqual(searchableSelector.dialog.find('.tabs-pane:eq(1) li.result:first a').text(), "tab2", "Correct results are shown for tab");
            })
            .always(waiter.resume);
    });



    QUnit.test("Single tab doesn't show tab bar", function(assert) {
        getSelectorOptions = function() {
            return {
                id: 'test-selector',
                field: $field,
                tabs: [
                    {
                        label: 'tab1',
                        url: 'tab1'
                    }
                ]
            };
        };
        var waiter = createWaiter(assert);
        init();

        this.simulateTriggerClick();
        this.waitForOpen(waiter)
            .done(function() {
                assert.strictEqual(searchableSelector.dialog.find('li.menu-item').length, 0, 'No tabs in dialog');
                assert.strictEqual(searchableSelector.dialog.find('.results-list > li.result').length, 10, "Results are shown as expected");
            })
            .always(waiter.resume);
    });

    QUnit.test("preloadData", function(assert) {
        getSelectorOptions = function() {
            return {
                id: 'test-selector',
                field: $field,
                url: '/some/rest/url',
                preloadData: {
                    'start': 0,
                    'size': 10,
                    'limit': 10,
                    'isLastPage': true,
                    'values': [{
                        'id': 'p1',
                        'name': 'First preload item'
                    }, {
                        'id': 'p2',
                        'name': 'Second preload item'
                    }, {
                        'id': 'p3',
                        'name': 'Third preload item'
                    }, {
                        'id': 'p4',
                        'name': 'Fourth preload item'
                    }, {
                        'id': 'p5',
                        'name': 'Fifth preload item'
                    }]
                }
            };
        };

        var waiter = createWaiter(assert);
        init();

        this.simulateTriggerClick();
        this.waitForOpen(waiter)
            .done(function() {
                assert.strictEqual(searchableSelector.dialog.find('.results-list > li.result.preload').length, 5, "Preloaded results are shown");
                assert.strictEqual(searchableSelector.dialog.find('.results-list > li.result:not(.preload)').length, 10, "The rest of the results are shown");
            })
            .always(waiter.resume);
    });

    QUnit.test("Empty preloadData", function(assert) {
        getSelectorOptions = function() {
            return {
                id: 'test-selector',
                field: $field,
                url: '/some/rest/url',
                preloadData: {
                    'start': 0,
                    'size': 0,
                    'limit': 10,
                    'isLastPage': true,
                    'values': []
                }
            };
        };

        var waiter = createWaiter(assert);
        init();

        this.simulateTriggerClick();
        this.waitForOpen(waiter)
            .done(function() {
                assert.strictEqual(searchableSelector.dialog.find('.results-list > li.result.preload').length, 0, "No preloaded results are shown");
                assert.strictEqual(searchableSelector.dialog.find('.results-list > li.result:not(.preload)').length, 10, "The rest of the results are shown");
            })
            .always(waiter.resume);
    });

    QUnit.test("External search field - typing in search field shows dialog", function(assert) {
        getSelectorOptions = function() {
            return {
                id: 'test-selector',
                externalSearchField: $field
            };
        };
        var waiter = createWaiter(assert);
        init();

        this.simulateTyping("some query");
        this.waitForOpen(waiter)
            .done(function() {
                assert.strictEqual(searchableSelector.dialog.show.callCount, 1, "Typing in search field shows dialog");
                assert.strictEqual(searchableSelector.dialog.find('.results-list > li.result').length, 10, "Dialog shows correct number of results");
            })
            .always(waiter.resume);
    });

    QUnit.test("External search field - hides dialog when not performing search", function(assert) {
        getSelectorOptions = function() {
            return {
                id: 'test-selector',
                externalSearchField: $field,
                queryParamsBuilder: function(searchTerm) {
                    return searchTerm ? {} : null; // Don't search on empty query
                }
            };
        };

        var waiter = createWaiter(assert);
        init();

        this.simulateTyping("some query");
        this.waitForOpen(waiter)

            .done(function(){
                assert.strictEqual(searchableSelector.dialog.show.callCount, 1, "Typing in search field shows dialog");
            })
            .then(this.simulateTyping.bind(this, ""))
            .then(this.thenWaitForDialogLoading(waiter))
            .done(function () {
                assert.strictEqual(searchableSelector.dialog.hide.callCount, 1, "Clearing search field hides dialog");
            })
            .always(waiter.resume);
    });

    QUnit.test("External search field - pressing down arrow shows dialog", function(assert) {
        getSelectorOptions = function() {
            return {
                id: 'test-selector',
                externalSearchField: $field
            };
        };

        var waiter = createWaiter(assert);
        init();

        searchableSelector.$searchField.val('something');
        this.simulateKey(AJS.keyCode.DOWN);
        this.waitForOpen(waiter)
            .then(this.thenWaitForDialogLoading(waiter))
            .always(waiter.resume);
    });

    QUnit.test("External search field - pressing down arrow doesn't show dialog if it shouldn't search", function(assert) {
        getSelectorOptions = function() {
            return {
                id: 'test-selector',
                externalSearchField: $field,
                queryParamsBuilder: function(searchTerm) {
                    return searchTerm ? {} : null; // Don't search on empty query
                }
            };
        };

        var waiter = createWaiter(assert);
        init();

        this.simulateTyping('something');
        this.waitForOpen(waiter)
            .then(this.simulateKey.bind(this, AJS.keyCode.ESCAPE))
            .then(this.thenWaitForDialogLoading(waiter))
            .then(this.simulateTyping.bind(this, ''))
            .then(this.thenWaitForDialogLoading(waiter))
            .done(function(){
                searchableSelector.dialog.show.reset();
            })
            .then(this.simulateKey.bind(this, AJS.keyCode.DOWN))
            .then(this.thenWaitForDialogLoading(waiter))
            .done(function() {
                assert.strictEqual(searchableSelector.dialog.show.callCount, 0, "Down key in search field doesn't show dialog");
            })
            .always(waiter.resume);
    });

    QUnit.test("clearResultsOnSearch=false", function(assert) {
        var restResponses = [
            // First response is normal
            $.Deferred().resolve(testDataPage),
            // Second response doesn't resolve immediately
            $.Deferred()
        ];
        var nextRestResponse = 0;

        getSelectorOptions = function() {
            return {
                id: 'test-selector',
                clearResultsOnSearch: false
            };
        };

        ajaxRestHandler = function() {
            return restResponses[nextRestResponse++];
        };

        var waiter = createWaiter(assert);
        init();

        $button.click();

        this.simulateTyping('something');
        this.waitForOpen(waiter)
            .done(function() {
                assert.strictEqual(searchableSelector.dialog.find('.results-list > li.result').length, 10,
                    "Initial set of results are not cleared");

                restResponses[1].resolve({
                    start: 0,
                    size: 1,
                    limit: 10,
                    isLastPage: true,
                    values: [
                        {id: '1', name: 'Item 1'}
                    ]
                });
            })
            .then(this.thenWaitForDialogLoading(waiter))
            .then(function() {
                assert.strictEqual(searchableSelector.dialog.find('.results-list > li.result').length, 1,
                    "Only the new results are shown when they are returned");
            })
            .always(waiter.resume);
    });

    QUnit.test("clearResultsOnSearch=false with preloadData", function(assert) {
        var restResponses = [
            // First response is normal
            $.Deferred().resolve(testDataPage),
            // Second response doesn't resolve immediately
            $.Deferred()
        ];
        var nextRestResponse = 0;

        getSelectorOptions = function() {
            return {
                id: 'test-selector',
                clearResultsOnSearch: false,
                alwaysShowPreload: true,
                preloadData: {
                    'start': 0,
                    'size': 5,
                    'limit': 10,
                    'isLastPage': true,
                    'values': [{
                        'id': 'p1',
                        'name': 'First preload item'
                    }, {
                        'id': 'p2',
                        'name': 'Second preload item'
                    }, {
                        'id': 'p3',
                        'name': 'Third preload item'
                    }, {
                        'id': 'p4',
                        'name': 'Fourth preload item'
                    }, {
                        'id': 'p5',
                        'name': 'Fifth preload item'
                    }]
                }
            };
        };

        ajaxRestHandler = function() {
            return restResponses[nextRestResponse++];
        };

        var waiter = createWaiter(assert);
        init();

        $button.click();

        // We need a timeout here as the dialog wont finish loading but we need to wait an amount of time for the
        // initial data to be replaced with the preLoad data
        var timer = $.Deferred();
        setTimeout(timer.resolve.bind(timer), 1000);

        this.simulateTyping('something');
        this.waitForOpen(waiter)
            .then(timer.promise.bind(timer))
            .then(function() {
                assert.strictEqual(searchableSelector.dialog.find('.results-list > li.result').length, 5,
                    "Initial set of results are cleared and replaced with preload data");

                restResponses[1].resolve({
                    start     : 0,
                    size      : 1,
                    limit     : 10,
                    isLastPage: true,
                    values    : [
                        { id: '1', name: 'Item 1' }
                    ]
                });
            })
            .then(this.thenWaitForDialogLoading(waiter))
            .then(function() {
                assert.strictEqual(searchableSelector.dialog.find('.results-list > li.result').length, 6,
                    "New search results are added when they are returned");
            })
            .always(waiter.resume);
    });

    QUnit.test("Pressing enter immediately after typing will select first item after results load", function(assert) {
        var restCallCount = 0;
        ajaxRestHandler = function() {
            restCallCount++;
            return $.Deferred().resolve({
                'start': 0,
                'size': 10,
                'limit': 10,
                'isLastPage': true,
                'values': [{
                    'id': restCallCount,
                    'name': 'Item ' + restCallCount
                }]
            }).promise();
        };

        var waiter = createWaiter(assert);
        init();

        $button.click();

        this.simulateTyping('something', true);
        this.waitForOpen(waiter)
            .then(this.simulateKey.bind(this, AJS.keyCode.ENTER))
            .then(this.thenWaitForDialogLoading(waiter))
            .done(function() {
                assert.strictEqual($button.find('.name').data('id'), 2, "Trigger is updated with the correct id");
                assert.strictEqual($button.find('.name').text(), 'Item 2', "Trigger is updated with the correct text");
            })
            .always(waiter.resume);
    });

    QUnit.test("Pressing enter immediately after typing in external search field will select first item after results load", function(assert) {
        getSelectorOptions = function() {
            return {
                id: 'test-selector',
                externalSearchField: $field
            };
        };

        var waiter = createWaiter(assert);
        init();

        var selectedItem = null;
        searchableSelector._itemSelected = function(itemData) {
            selectedItem = itemData;
        };

        this.simulateTyping('something', true);
        this.waitForOpen(waiter)
            .then(this.simulateKey.bind(this, AJS.keyCode.ENTER))
            .then(this.thenWaitForDialogLoading(waiter))
            .done(function() {
                assert.strictEqual(selectedItem.id, 1, "Selected item has the correct id");
                assert.strictEqual(selectedItem.name, 'First item', "Selected item has the correct text");
            })
            .always(waiter.resume);
    });
});