odoo.define('web.test_utils_fields', function (require) {
"use strict";

/**
 * Field Test Utils
 *
 * This module defines various utility functions to help testing field widgets.
 *
 * Note that all methods defined in this module are exported in the main
 * testUtils file.
 */

var concurrency = require('web.concurrency');

//------------------------------------------------------------------------------
// Public functions
//------------------------------------------------------------------------------

/**
 * Sets the value of an element and then, trigger all specified events.
 * Note that this helper also checks the unicity of the target.
 *
 * Example:
 *     testUtils.fields.editAndTrigger($('selector'), 'test', ['input', 'change']);
 *
 * @param {jQuery} $el should target an input, textarea or select
 * @param {string|number} value
 * @param {string[]} events
 */
function editAndTrigger($el, value, events) {
    if ($el.length !== 1) {
        throw new Error(`target ${$el.selector} has length ${$el.length} instead of 1`);
    }
    $el.val(value);
    events.forEach(function (event) {
        $el.trigger(event);
    });
}

/**
 * Sets the value of an input.
 *
 * Note that this helper also checks the unicity of the target.
 *
 * Example:
 *     testUtils.fields.editInput($('selector'), 'somevalue');
 *
 * @param {jQuery} $el
 * @param {string|number} value
 */
function editInput($el, value) {
    editAndTrigger($el, value, ['input']);
}

/**
 * Sets the value of a select.
 *
 * Note that this helper also checks the unicity of the target.
 *
 * Example:
 *     testUtils.fields.editSelect($('selector'), 'somevalue');
 *
 * @param {jQuery} $el
 * @param {string|number} value
 */
function editSelect($el, value) {
    editAndTrigger($el, value, ['change']);
}

/**
 * Click to open the dropdown on a many2one
 *
 * @param {string} fieldName
 * @param {[string]} selector if set, this will restrict the search for the m2o
 *    input
 * @returns {HTMLInputElement} the main many2one input
 */
function clickOpenM2ODropdown(fieldName, selector) {
    var m2oSelector = `${selector || ''} .o_field_many2one[name=${fieldName}] input`;
    var matches = document.querySelectorAll(m2oSelector);
    if (matches.length !== 1) {
        throw new Error(`cannot open m2o: selector ${selector} has been found ${matches.length} instead of 1`);
    }
    matches[0].click();
    return matches[0];
}

/**
 * Click on the active (highlighted) selection in a m2o dropdown.
 *
 * @param {string} fieldName
 * @param {[string]} selector if set, this will restrict the search for the m2o
 *    input
 */
function clickM2OHighlightedItem(fieldName, selector) {
    var m2oSelector = `${selector || ''} .o_field_many2one[name=${fieldName}] input`;
    var $dropdown = $(m2oSelector).autocomplete('widget');
    // clicking on an li (no matter which one), will select the focussed one
    $dropdown.find('li:first()').click();
}

/**
 * Click on a menuitem in the m2o dropdown.  This helper will target an element
 * which contains some specific text. Note that it assumes that the dropdown
 * is currently open.
 *
 * Example:
 *    testUtils.fields.many2one.clickM2OItem('partner_id', 'George');
 *
 * @param {string} fieldName
 * @param {string} searchText
 */
function clickM2OItem(fieldName, searchText) {
    var m2oSelector = `.o_field_many2one[name=${fieldName}] input`;
    var $dropdown = $(m2oSelector).autocomplete('widget');
    var $target = $dropdown.find(`li:contains(${searchText})`).first();
    if ($target.length !== 1 || !$target.is(':visible')) {
        throw new Error('Menu item should be unique and visible');
    }
    $target.mouseenter().click();
}

/**
 * This helper is useful to test many2one fields. Here is what it does:
 * - click to open the dropdown
 * - enter a search string in the input
 * - wait for the selection
 * - click on the requested menuitem, or the active one by default
 *
 * Example:
 *    testUtils.fields.many2one.searchAndClickM2OItem('partner_id', {search: 'George'});
 *
 * @param {string} fieldName
 * @param {[Object]} options
 * @param {[string]} options.selector
 * @param {[string]} options.search
 * @param {[string]} options.item
 * @returns {Promise}
 */
function searchAndClickM2OItem(fieldName, options) {
    options = options || {};

    var input = clickOpenM2ODropdown(fieldName, options.selector);

    var def;
    if (options.search) {
        // jquery autocomplete refines the search in a setTimeout() parameterized
        // with a delay, so we force this delay to 0 s.t. the dropdown is filtered
        // directly on the next tick
        var $input = $(input);
        var delay = $input.autocomplete('option', 'delay');
        $input.autocomplete('option', 'delay', 0);
        input.value = options.search;
        input.dispatchEvent(new Event('input'));
        $input.autocomplete('option', 'delay', delay);
        def = concurrency.delay(0);
    }

    return $.when(def).then(function () {
        if (options.item) {
            clickM2OItem(fieldName, options.item);
        } else {
            clickM2OHighlightedItem(fieldName, options.selector);
        }
    });
}
return {
    clickOpenM2ODropdown: clickOpenM2ODropdown,
    clickM2OHighlightedItem: clickM2OHighlightedItem,
    clickM2OItem: clickM2OItem,
    editAndTrigger: editAndTrigger,
    editInput: editInput,
    editSelect: editSelect,
    searchAndClickM2OItem: searchAndClickM2OItem,
};

});
