1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
|
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.
*/
const testUtilsDom = require('web.test_utils_dom');
const ARROW_KEYS_MAPPING = {
down: 'ArrowDown',
left: 'ArrowLeft',
right: 'ArrowRight',
up: 'ArrowUp',
};
//-------------------------------------------------------------------------
// Public functions
//-------------------------------------------------------------------------
/**
* Autofills the input of a many2one field and clicks on the "Create and Edit" option.
*
* @param {string} fieldName
* @param {string} text Used as default value for the record name
* @see clickM2OItem
*/
async function clickM2OCreateAndEdit(fieldName, text = "ABC") {
await clickOpenM2ODropdown(fieldName);
const match = document.querySelector(`.o_field_many2one[name=${fieldName}] input`);
await editInput(match, text);
return clickM2OItem(fieldName, "Create and Edit");
}
/**
* 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
* @returns {Promise}
*/
async function clickM2OHighlightedItem(fieldName, selector) {
const m2oSelector = `${selector || ''} .o_field_many2one[name=${fieldName}] input`;
const $dropdown = $(m2oSelector).autocomplete('widget');
// clicking on an li (no matter which one), will select the focussed one
return testUtilsDom.click($dropdown[0].querySelector('li'));
}
/**
* 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
* @returns {Promise}
*/
async function clickM2OItem(fieldName, searchText) {
const m2oSelector = `.o_field_many2one[name=${fieldName}] input`;
const $dropdown = $(m2oSelector).autocomplete('widget');
const $target = $dropdown.find(`li:contains(${searchText})`).first();
if ($target.length !== 1 || !$target.is(':visible')) {
throw new Error('Menu item should be visible');
}
$target.mouseenter(); // This is NOT a mouseenter event. See jquery.js:5516 for more headaches.
return testUtilsDom.click($target);
}
/**
* 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 {Promise<HTMLInputElement>} the main many2one input
*/
async function clickOpenM2ODropdown(fieldName, selector) {
const m2oSelector = `${selector || ''} .o_field_many2one[name=${fieldName}] input`;
const matches = document.querySelectorAll(m2oSelector);
if (matches.length !== 1) {
throw new Error(`cannot open m2o: selector ${selector} has been found ${matches.length} instead of 1`);
}
await testUtilsDom.click(matches[0]);
return matches[0];
}
/**
* 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|EventTarget} el should target an input, textarea or select
* @param {string|number} value
* @param {string[]} events
* @returns {Promise}
*/
async function editAndTrigger(el, value, events) {
if (el instanceof jQuery) {
if (el.length !== 1) {
throw new Error(`target ${el.selector} has length ${el.length} instead of 1`);
}
el.val(value);
} else {
el.value = value;
}
return testUtilsDom.triggerEvents(el, events);
}
/**
* 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|EventTarget} el should target an input, textarea or select
* @param {string|number} value
* @returns {Promise}
*/
async function editInput(el, value) {
return 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|EventTarget} el should target an input, textarea or select
* @param {string|number} value
* @returns {Promise}
*/
function editSelect(el, value) {
return editAndTrigger(el, value, ['change']);
}
/**
* 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}
*/
async function searchAndClickM2OItem(fieldName, options = {}) {
const input = await clickOpenM2ODropdown(fieldName, options.selector);
if (options.search) {
await editInput(input, options.search);
}
if (options.item) {
return clickM2OItem(fieldName, options.item);
} else {
return clickM2OHighlightedItem(fieldName, options.selector);
}
}
/**
* Helper to trigger a key event on an element.
*
* @param {string} type type of key event ('press', 'up' or 'down')
* @param {jQuery} $el
* @param {number|string} keyCode used as number, but if string, it'll check if
* the string corresponds to a key -otherwise it will keep only the first
* char to get a letter key- and convert it into a keyCode.
* @returns {Promise}
*/
function triggerKey(type, $el, keyCode) {
type = 'key' + type;
const params = {};
if (typeof keyCode === 'string') {
// Key (new API)
if (keyCode in ARROW_KEYS_MAPPING) {
params.key = ARROW_KEYS_MAPPING[keyCode];
} else {
params.key = keyCode[0].toUpperCase() + keyCode.slice(1).toLowerCase();
}
// KeyCode/which (jQuery)
if (keyCode.length > 1) {
keyCode = keyCode.toUpperCase();
keyCode = $.ui.keyCode[keyCode];
} else {
keyCode = keyCode.charCodeAt(0);
}
}
params.keyCode = keyCode;
params.which = keyCode;
return testUtilsDom.triggerEvent($el, type, params);
}
/**
* Helper to trigger a keydown event on an element.
*
* @param {jQuery} $el
* @param {number|string} keyCode @see triggerKey
* @returns {Promise}
*/
function triggerKeydown($el, keyCode) {
return triggerKey('down', $el, keyCode);
}
/**
* Helper to trigger a keyup event on an element.
*
* @param {jQuery} $el
* @param {number|string} keyCode @see triggerKey
* @returns {Promise}
*/
function triggerKeyup($el, keyCode) {
return triggerKey('up', $el, keyCode);
}
return {
clickM2OCreateAndEdit,
clickM2OHighlightedItem,
clickM2OItem,
clickOpenM2ODropdown,
editAndTrigger,
editInput,
editSelect,
searchAndClickM2OItem,
triggerKey,
triggerKeydown,
triggerKeyup,
};
});
|