1038 lines
36 KiB
JavaScript
1038 lines
36 KiB
JavaScript
/** @odoo-module **/
|
|
|
|
import { ControlPanel } from "@web/search/control_panel/control_panel";
|
|
import {
|
|
click,
|
|
getFixture,
|
|
makeDeferred,
|
|
nextTick,
|
|
patchTimeZone,
|
|
patchWithCleanup,
|
|
triggerEvent,
|
|
} from "../helpers/utils";
|
|
import {
|
|
editSearch,
|
|
getFacetTexts,
|
|
makeWithSearch,
|
|
removeFacet,
|
|
setupControlPanelServiceRegistry,
|
|
validateSearch,
|
|
} from "./helpers";
|
|
|
|
function getDomain(controlPanel) {
|
|
return controlPanel.env.searchModel.domain;
|
|
}
|
|
|
|
import { onWillUpdateProps } from "@odoo/owl";
|
|
|
|
let target;
|
|
let serverData;
|
|
QUnit.module("Search", (hooks) => {
|
|
hooks.beforeEach(async () => {
|
|
serverData = {
|
|
models: {
|
|
partner: {
|
|
fields: {
|
|
bar: { string: "Bar", type: "many2one", relation: "partner" },
|
|
birthday: { string: "Birthday", type: "date" },
|
|
birth_datetime: { string: "Birth DateTime", type: "datetime" },
|
|
foo: { string: "Foo", type: "char" },
|
|
bool: { string: "Bool", type: "boolean" },
|
|
company: { string: "Company", type: "many2one", relation: "partner" },
|
|
},
|
|
records: [
|
|
{
|
|
id: 1,
|
|
display_name: "First record",
|
|
foo: "yop",
|
|
bar: 2,
|
|
bool: true,
|
|
birthday: "1983-07-15",
|
|
birth_datetime: "1983-07-15 01:00:00",
|
|
},
|
|
{
|
|
id: 2,
|
|
display_name: "Second record",
|
|
foo: "blip",
|
|
bar: 1,
|
|
bool: false,
|
|
birthday: "1982-06-04",
|
|
birth_datetime: "1982-06-04 02:00:00",
|
|
company: 1,
|
|
},
|
|
{
|
|
id: 3,
|
|
display_name: "Third record",
|
|
foo: "gnap",
|
|
bar: 1,
|
|
bool: false,
|
|
birthday: "1985-09-13",
|
|
birth_datetime: "1985-09-13 03:00:00",
|
|
company: 5,
|
|
},
|
|
{
|
|
id: 4,
|
|
display_name: "Fourth record",
|
|
foo: "plop",
|
|
bar: 2,
|
|
bool: true,
|
|
birthday: "1983-05-05",
|
|
birth_datetime: "1983-05-05 04:00:00",
|
|
},
|
|
{
|
|
id: 5,
|
|
display_name: "Fifth record",
|
|
foo: "zoup",
|
|
bar: 2,
|
|
bool: true,
|
|
birthday: "1800-01-01",
|
|
birth_datetime: "1800-01-01 05:00:00",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
views: {
|
|
"partner,false,list": `<tree><field name="foo"/></tree>`,
|
|
"partner,false,search": `
|
|
<search>
|
|
<field name="foo"/>
|
|
<field name="birthday"/>
|
|
<field name="birth_datetime"/>
|
|
<field name="bar" context="{'bar': self}"/>
|
|
<field name="company" domain="[('bool', '=', True)]"/>
|
|
<filter string="Birthday" name="date_filter" date="birthday"/>
|
|
<filter string="Birthday" name="date_group_by" context="{'group_by': 'birthday:day'}"/>
|
|
</search>
|
|
`,
|
|
},
|
|
actions: {
|
|
1: {
|
|
id: 1,
|
|
name: "Partners Action",
|
|
res_model: "partner",
|
|
search_view_id: [false, "search"],
|
|
type: "ir.actions.act_window",
|
|
views: [[false, "list"]],
|
|
},
|
|
},
|
|
};
|
|
setupControlPanelServiceRegistry();
|
|
target = getFixture();
|
|
});
|
|
|
|
QUnit.module("SearchBar");
|
|
|
|
QUnit.test("basic rendering", async function (assert) {
|
|
assert.expect(1);
|
|
|
|
await makeWithSearch({
|
|
serverData,
|
|
resModel: "partner",
|
|
Component: ControlPanel,
|
|
searchMenuTypes: [],
|
|
searchViewId: false,
|
|
});
|
|
|
|
assert.strictEqual(
|
|
document.activeElement,
|
|
target.querySelector(".o_searchview input"),
|
|
"searchview input should be focused"
|
|
);
|
|
});
|
|
|
|
QUnit.test("navigation with facets", async function (assert) {
|
|
assert.expect(4);
|
|
|
|
await makeWithSearch({
|
|
serverData,
|
|
resModel: "partner",
|
|
Component: ControlPanel,
|
|
searchMenuTypes: ["groupBy"],
|
|
searchViewId: false,
|
|
context: { search_default_date_group_by: 1 },
|
|
});
|
|
|
|
assert.containsOnce(
|
|
target,
|
|
".o_searchview .o_searchview_facet",
|
|
"there should be one facet"
|
|
);
|
|
assert.strictEqual(document.activeElement, target.querySelector(".o_searchview input"));
|
|
|
|
// press left to focus the facet
|
|
await triggerEvent(document.activeElement, null, "keydown", { key: "ArrowLeft" });
|
|
assert.strictEqual(
|
|
document.activeElement,
|
|
target.querySelector(".o_searchview .o_searchview_facet")
|
|
);
|
|
|
|
// press right to focus the input
|
|
await triggerEvent(document.activeElement, null, "keydown", { key: "ArrowRight" });
|
|
assert.strictEqual(document.activeElement, target.querySelector(".o_searchview input"));
|
|
});
|
|
|
|
QUnit.test("navigation with facets (2)", async function (assert) {
|
|
await makeWithSearch({
|
|
serverData,
|
|
resModel: "partner",
|
|
Component: ControlPanel,
|
|
searchMenuTypes: ["groupBy"],
|
|
searchViewId: false,
|
|
context: {
|
|
search_default_date_group_by: 1,
|
|
search_default_foo: 1,
|
|
},
|
|
});
|
|
|
|
assert.containsN(target, ".o_searchview .o_searchview_facet", 2);
|
|
assert.strictEqual(document.activeElement, target.querySelector(".o_searchview input"));
|
|
|
|
// press left to focus the rightmost facet
|
|
await triggerEvent(document.activeElement, null, "keydown", { key: "ArrowLeft" });
|
|
assert.strictEqual(
|
|
document.activeElement,
|
|
target.querySelector(".o_searchview .o_searchview_facet:nth-child(2)")
|
|
);
|
|
|
|
// press left to focus the leftmost facet
|
|
await triggerEvent(document.activeElement, null, "keydown", { key: "ArrowLeft" });
|
|
assert.strictEqual(
|
|
document.activeElement,
|
|
target.querySelector(".o_searchview .o_searchview_facet:nth-child(1)")
|
|
);
|
|
|
|
// press left to focus the input
|
|
await triggerEvent(document.activeElement, null, "keydown", { key: "ArrowLeft" });
|
|
assert.strictEqual(document.activeElement, target.querySelector(".o_searchview input"));
|
|
|
|
// press left to focus the leftmost facet
|
|
await triggerEvent(document.activeElement, null, "keydown", { key: "ArrowRight" });
|
|
assert.strictEqual(
|
|
document.activeElement,
|
|
target.querySelector(".o_searchview .o_searchview_facet:nth-child(1)")
|
|
);
|
|
});
|
|
|
|
QUnit.test("search date and datetime fields. Support of timezones", async function (assert) {
|
|
assert.expect(4);
|
|
|
|
patchTimeZone(360);
|
|
|
|
const controlPanel = await makeWithSearch({
|
|
serverData,
|
|
resModel: "partner",
|
|
Component: ControlPanel,
|
|
searchMenuTypes: [],
|
|
searchViewId: false,
|
|
});
|
|
|
|
// Date case
|
|
await editSearch(target, "07/15/1983");
|
|
let searchInput = target.querySelector(".o_searchview input");
|
|
await triggerEvent(searchInput, null, "keydown", { key: "ArrowDown" });
|
|
await triggerEvent(searchInput, null, "keydown", { key: "Enter" }); // select
|
|
|
|
assert.deepEqual(
|
|
getFacetTexts(target).map((str) => str.replace(/\s+/, " ")),
|
|
["Birthday 07/15/1983"],
|
|
"The format of the date in the facet should be in locale"
|
|
);
|
|
|
|
assert.deepEqual(getDomain(controlPanel), [["birthday", "=", "1983-07-15"]]);
|
|
|
|
// Close Facet
|
|
await click(target.querySelector(".o_searchview_facet .o_facet_remove"));
|
|
|
|
// DateTime case
|
|
await editSearch(target, "07/15/1983 00:00:00");
|
|
searchInput = target.querySelector(".o_searchview input");
|
|
await triggerEvent(searchInput, null, "keydown", { key: "ArrowDown" });
|
|
await triggerEvent(searchInput, null, "keydown", { key: "Enter" }); // select
|
|
|
|
assert.deepEqual(
|
|
getFacetTexts(target).map((str) => str.replace(/\s+/, " ")),
|
|
["Birth DateTime\n07/15/1983 00:00:00"],
|
|
"The format of the datetime in the facet should be in locale"
|
|
);
|
|
|
|
assert.deepEqual(getDomain(controlPanel), [["birth_datetime", "=", "1983-07-14 18:00:00"]]);
|
|
});
|
|
|
|
QUnit.test("autocomplete menu clickout interactions", async function (assert) {
|
|
assert.expect(10);
|
|
|
|
await makeWithSearch({
|
|
serverData,
|
|
resModel: "partner",
|
|
Component: ControlPanel,
|
|
searchMenuTypes: [],
|
|
searchViewId: false,
|
|
searchViewArch: `
|
|
<search>
|
|
<field name="bar"/>
|
|
<field name="birthday"/>
|
|
<field name="birth_datetime"/>
|
|
<field name="foo"/>
|
|
<field name="bool"/>
|
|
</search>
|
|
`,
|
|
});
|
|
|
|
const input = target.querySelector(".o_searchview input");
|
|
|
|
// Create an input outside of the search panel to simulate another input outside of the search panel
|
|
const outsideInput = document.createElement('input');
|
|
getFixture().appendChild(outsideInput);
|
|
|
|
assert.containsNone(target, ".o_searchview_autocomplete");
|
|
|
|
await editSearch(target, "Hello there");
|
|
|
|
assert.strictEqual(input.value, "Hello there", "input value should be updated");
|
|
assert.containsOnce(target, ".o_searchview_autocomplete");
|
|
|
|
await triggerEvent(input, null, "keydown", { key: "Escape" });
|
|
|
|
assert.strictEqual(input.value, "", "input value should be empty");
|
|
assert.containsNone(target, ".o_searchview_autocomplete");
|
|
|
|
await editSearch(target, "General Kenobi");
|
|
|
|
assert.strictEqual(input.value, "General Kenobi", "input value should be updated");
|
|
assert.containsOnce(target, ".o_searchview_autocomplete");
|
|
|
|
outsideInput.focus();
|
|
await click(outsideInput);
|
|
|
|
assert.strictEqual(input.value, "", "input value should be empty");
|
|
assert.containsNone(target, ".o_searchview_autocomplete");
|
|
assert.strictEqual(document.activeElement, outsideInput);
|
|
});
|
|
|
|
QUnit.test("select an autocomplete field", async function (assert) {
|
|
assert.expect(3);
|
|
|
|
const controlPanel = await makeWithSearch({
|
|
serverData,
|
|
resModel: "partner",
|
|
Component: ControlPanel,
|
|
searchMenuTypes: [],
|
|
searchViewId: false,
|
|
});
|
|
|
|
await editSearch(target, "a");
|
|
assert.containsN(
|
|
target,
|
|
".o_searchview_autocomplete li",
|
|
3,
|
|
"there should be 3 result for 'a' in search bar autocomplete"
|
|
);
|
|
|
|
const searchInput = target.querySelector(".o_searchview input");
|
|
await triggerEvent(searchInput, null, "keydown", { key: "Enter" });
|
|
assert.strictEqual(
|
|
target.querySelector(".o_searchview_input_container .o_facet_values").innerText.trim(),
|
|
"a",
|
|
"There should be a field facet with label 'a'"
|
|
);
|
|
|
|
assert.deepEqual(getDomain(controlPanel), [["foo", "ilike", "a"]]);
|
|
});
|
|
|
|
QUnit.test("select an autocomplete field with `context` key", async function (assert) {
|
|
assert.expect(8);
|
|
|
|
let updateCount = 0;
|
|
patchWithCleanup(ControlPanel.prototype, {
|
|
setup() {
|
|
this._super();
|
|
onWillUpdateProps(() => {
|
|
updateCount++;
|
|
});
|
|
},
|
|
});
|
|
|
|
const controlPanel = await makeWithSearch({
|
|
serverData,
|
|
resModel: "partner",
|
|
Component: ControlPanel,
|
|
searchMenuTypes: [],
|
|
searchViewId: false,
|
|
});
|
|
|
|
// 'r' key to filter on bar "First Record"
|
|
await editSearch(target, "record");
|
|
const searchInput = target.querySelector(".o_searchview input");
|
|
await triggerEvent(searchInput, null, "keydown", { key: "ArrowDown" });
|
|
await triggerEvent(searchInput, null, "keydown", { key: "ArrowRight" });
|
|
await triggerEvent(searchInput, null, "keydown", { key: "ArrowDown" });
|
|
await triggerEvent(searchInput, null, "keydown", { key: "Enter" });
|
|
|
|
assert.deepEqual(
|
|
getFacetTexts(target).map((str) => str.replace(/\s+/, "")),
|
|
["BarFirst record"]
|
|
);
|
|
|
|
assert.strictEqual(updateCount, 1);
|
|
|
|
assert.deepEqual(getDomain(controlPanel), [["bar", "=", 1]]);
|
|
assert.deepEqual(controlPanel.env.searchModel.context.bar, [1]);
|
|
|
|
// 'r' key to filter on bar "Second Record"
|
|
await editSearch(target, "record");
|
|
await triggerEvent(searchInput, null, "keydown", { key: "ArrowDown" });
|
|
await triggerEvent(searchInput, null, "keydown", { key: "ArrowRight" });
|
|
await triggerEvent(searchInput, null, "keydown", { key: "ArrowDown" });
|
|
await triggerEvent(searchInput, null, "keydown", { key: "ArrowDown" });
|
|
await triggerEvent(searchInput, null, "keydown", { key: "Enter" });
|
|
|
|
assert.deepEqual(
|
|
getFacetTexts(target).map((str) => str.replace(/\s+/, "")),
|
|
["BarFirst recordorSecond record"]
|
|
);
|
|
|
|
assert.strictEqual(updateCount, 2);
|
|
|
|
assert.deepEqual(getDomain(controlPanel), ["|", ["bar", "=", 1], ["bar", "=", 2]]);
|
|
assert.deepEqual(controlPanel.env.searchModel.context.bar, [1, 2]);
|
|
});
|
|
|
|
QUnit.test("no search text triggers a reload", async function (assert) {
|
|
assert.expect(2);
|
|
|
|
let updateCount = 0;
|
|
patchWithCleanup(ControlPanel.prototype, {
|
|
setup() {
|
|
this._super();
|
|
onWillUpdateProps(() => {
|
|
updateCount++;
|
|
});
|
|
},
|
|
});
|
|
|
|
await makeWithSearch({
|
|
serverData,
|
|
resModel: "partner",
|
|
Component: ControlPanel,
|
|
searchMenuTypes: [],
|
|
searchViewId: false,
|
|
});
|
|
|
|
const searchInput = target.querySelector(".o_searchview input");
|
|
await triggerEvent(searchInput, null, "keydown", { key: "Enter" });
|
|
|
|
assert.containsNone(target, ".o_searchview_facet_label");
|
|
assert.strictEqual(updateCount, 1, "should have been updated once");
|
|
});
|
|
|
|
QUnit.test("selecting (no result) triggers a search bar rendering", async function (assert) {
|
|
assert.expect(3);
|
|
|
|
await makeWithSearch({
|
|
serverData,
|
|
resModel: "partner",
|
|
Component: ControlPanel,
|
|
searchMenuTypes: [],
|
|
searchViewId: false,
|
|
searchViewArch: `
|
|
<search>
|
|
<field name="bar"/>
|
|
</search>
|
|
`,
|
|
});
|
|
|
|
await editSearch(target, "hello there");
|
|
|
|
// 'a' key to filter nothing on bar
|
|
const searchInput = target.querySelector(".o_searchview input");
|
|
await triggerEvent(searchInput, null, "keydown", { key: "ArrowDown" });
|
|
await triggerEvent(searchInput, null, "keydown", { key: "ArrowRight" });
|
|
await triggerEvent(searchInput, null, "keydown", { key: "ArrowDown" });
|
|
|
|
assert.strictEqual(
|
|
target.querySelector(".o_searchview_autocomplete .focus").innerText.trim(),
|
|
"(no result)",
|
|
"there should be no result for 'a' in bar"
|
|
);
|
|
|
|
await triggerEvent(searchInput, null, "keydown", { key: "Enter" });
|
|
|
|
assert.containsNone(target, ".o_searchview_facet_label");
|
|
assert.strictEqual(
|
|
target.querySelector(".o_searchview input").value,
|
|
"",
|
|
"the search input should be re-rendered"
|
|
);
|
|
});
|
|
|
|
QUnit.test(
|
|
"update suggested filters in autocomplete menu with Japanese IME",
|
|
async function (assert) {
|
|
assert.expect(4);
|
|
|
|
// The goal here is to simulate as many events happening during an IME
|
|
// assisted composition session as possible. Some of these events are
|
|
// not handled but are triggered to ensure they do not interfere.
|
|
const TEST = "TEST";
|
|
const テスト = "テスト";
|
|
await makeWithSearch({
|
|
serverData,
|
|
resModel: "partner",
|
|
Component: ControlPanel,
|
|
searchMenuTypes: [],
|
|
searchViewId: false,
|
|
});
|
|
|
|
const searchInput = target.querySelector(".o_searchview input");
|
|
|
|
// Simulate typing "TEST" on search view.
|
|
for (let i = 0; i < TEST.length; i++) {
|
|
const key = TEST[i].toUpperCase();
|
|
await triggerEvent(searchInput, null, "keydown", {
|
|
key,
|
|
isComposing: true,
|
|
});
|
|
if (i === 0) {
|
|
// Composition is initiated after the first keydown
|
|
await triggerEvent(searchInput, null, "compositionstart");
|
|
}
|
|
await triggerEvent(searchInput, null, "keypress", {
|
|
key,
|
|
isComposing: true,
|
|
});
|
|
searchInput.value = TEST.slice(0, i + 1);
|
|
await triggerEvent(searchInput, null, "keyup", { key, isComposing: true });
|
|
await triggerEvent(searchInput, null, "input", {
|
|
inputType: "insertCompositionText",
|
|
isComposing: true,
|
|
});
|
|
}
|
|
assert.containsOnce(
|
|
target,
|
|
".o_searchview_autocomplete",
|
|
"should display autocomplete dropdown menu on typing something in search view"
|
|
);
|
|
assert.strictEqual(
|
|
target.querySelector(".o_searchview_autocomplete li").innerText.trim(),
|
|
"Search Foo for: TEST",
|
|
`1st filter suggestion should be based on typed word "TEST"`
|
|
);
|
|
|
|
// Simulate soft-selection of another suggestion from IME through keyboard navigation.
|
|
await triggerEvent(searchInput, null, "keydown", {
|
|
key: "ArrowDown",
|
|
isComposing: true,
|
|
});
|
|
await triggerEvent(searchInput, null, "keypress", {
|
|
key: "ArrowDown",
|
|
isComposing: true,
|
|
});
|
|
searchInput.value = テスト;
|
|
await triggerEvent(searchInput, null, "keyup", {
|
|
key: "ArrowDown",
|
|
isComposing: true,
|
|
});
|
|
await triggerEvent(searchInput, null, "input", {
|
|
inputType: "insertCompositionText",
|
|
isComposing: true,
|
|
});
|
|
|
|
assert.strictEqual(
|
|
target.querySelector(".o_searchview_autocomplete li").innerText.trim(),
|
|
"Search Foo for: テスト",
|
|
`1st filter suggestion should be updated with soft-selection typed word "テスト"`
|
|
);
|
|
|
|
// Simulate selection on suggestion item "TEST" from IME.
|
|
await triggerEvent(searchInput, null, "keydown", {
|
|
key: "Enter",
|
|
isComposing: true,
|
|
});
|
|
await triggerEvent(searchInput, null, "keypress", {
|
|
key: "Enter",
|
|
isComposing: true,
|
|
});
|
|
searchInput.value = TEST;
|
|
await triggerEvent(searchInput, null, "keyup", {
|
|
key: "Enter",
|
|
isComposing: true,
|
|
});
|
|
await triggerEvent(searchInput, null, "input", {
|
|
inputType: "insertCompositionText",
|
|
isComposing: true,
|
|
});
|
|
|
|
// End of the composition
|
|
await triggerEvent(searchInput, null, "compositionend");
|
|
|
|
assert.strictEqual(
|
|
target.querySelector(".o_searchview_autocomplete li").innerText.trim(),
|
|
"Search Foo for: TEST",
|
|
`1st filter suggestion should finally be updated with click selection on word "TEST" from IME`
|
|
);
|
|
}
|
|
);
|
|
|
|
QUnit.test("open search view autocomplete on paste value using mouse", async function (assert) {
|
|
assert.expect(1);
|
|
|
|
await makeWithSearch({
|
|
serverData,
|
|
resModel: "partner",
|
|
Component: ControlPanel,
|
|
searchMenuTypes: [],
|
|
searchViewId: false,
|
|
});
|
|
|
|
// Simulate paste text through the mouse.
|
|
const searchInput = target.querySelector(".o_searchview input");
|
|
searchInput.value = "ABC";
|
|
await triggerEvent(searchInput, null, "input", { inputType: "insertFromPaste" });
|
|
assert.containsOnce(
|
|
target,
|
|
".o_searchview_autocomplete",
|
|
"should display autocomplete dropdown menu on paste in search view"
|
|
);
|
|
});
|
|
|
|
QUnit.test("select autocompleted many2one", async function (assert) {
|
|
assert.expect(4);
|
|
|
|
const controlPanel = await makeWithSearch({
|
|
serverData,
|
|
resModel: "partner",
|
|
Component: ControlPanel,
|
|
searchMenuTypes: [],
|
|
searchViewId: false,
|
|
searchViewArch: `
|
|
<search>
|
|
<field name="foo"/>
|
|
<field name="birthday"/>
|
|
<field name="birth_datetime"/>
|
|
<field name="bar" operator="child_of"/>
|
|
</search>
|
|
`,
|
|
});
|
|
|
|
assert.deepEqual(getDomain(controlPanel), []);
|
|
|
|
await editSearch(target, "rec");
|
|
await click(target.querySelector(".o_searchview_autocomplete li:last-child"));
|
|
|
|
assert.deepEqual(getDomain(controlPanel), [["bar", "child_of", "rec"]]);
|
|
|
|
await removeFacet(target);
|
|
|
|
assert.deepEqual(getDomain(controlPanel), []);
|
|
|
|
await editSearch(target, "rec");
|
|
await click(target.querySelector(".o_expand"));
|
|
await click(target.querySelector(".o_searchview_autocomplete li.o_menu_item.o_indent"));
|
|
|
|
assert.deepEqual(getDomain(controlPanel), [["bar", "child_of", 1]]);
|
|
});
|
|
|
|
QUnit.test('"null" as autocomplete value', async function (assert) {
|
|
assert.expect(3);
|
|
|
|
const controlPanel = await makeWithSearch({
|
|
serverData,
|
|
resModel: "partner",
|
|
Component: ControlPanel,
|
|
searchMenuTypes: [],
|
|
searchViewId: false,
|
|
});
|
|
|
|
assert.deepEqual(getDomain(controlPanel), []);
|
|
|
|
await editSearch(target, "null");
|
|
|
|
assert.strictEqual(
|
|
target.querySelector(".o_searchview_autocomplete .focus").innerText,
|
|
"Search Foo for: null"
|
|
);
|
|
|
|
await click(target.querySelector(".o_searchview_autocomplete li.focus a"));
|
|
|
|
assert.deepEqual(getDomain(controlPanel), [["foo", "ilike", "null"]]);
|
|
});
|
|
|
|
QUnit.test("autocompletion with a boolean field", async function (assert) {
|
|
assert.expect(8);
|
|
|
|
const controlPanel = await makeWithSearch({
|
|
serverData,
|
|
resModel: "partner",
|
|
Component: ControlPanel,
|
|
searchMenuTypes: [],
|
|
searchViewId: false,
|
|
searchViewArch: `
|
|
<search>
|
|
<field name="bool"/>
|
|
</search>
|
|
`,
|
|
});
|
|
|
|
assert.deepEqual(getDomain(controlPanel), []);
|
|
|
|
await editSearch(target, "y");
|
|
|
|
assert.containsN(target, ".o_searchview_autocomplete li", 1);
|
|
assert.strictEqual(
|
|
target.querySelector(".o_searchview_autocomplete li:last-child").innerText,
|
|
"Search Bool for: Yes"
|
|
);
|
|
|
|
// select "Yes"
|
|
await click(target.querySelector(".o_searchview_autocomplete li:last-child"));
|
|
|
|
assert.deepEqual(getDomain(controlPanel), [["bool", "=", true]]);
|
|
|
|
await removeFacet(target);
|
|
|
|
assert.deepEqual(getDomain(controlPanel), []);
|
|
|
|
await editSearch(target, "No");
|
|
|
|
assert.containsN(target, ".o_searchview_autocomplete li", 1);
|
|
assert.strictEqual(
|
|
target.querySelector(".o_searchview_autocomplete li:last-child").innerText,
|
|
"Search Bool for: No"
|
|
);
|
|
|
|
// select "No"
|
|
await click(target.querySelector(".o_searchview_autocomplete li:last-child"));
|
|
|
|
assert.deepEqual(getDomain(controlPanel), [["bool", "=", false]]);
|
|
});
|
|
|
|
QUnit.test("the search value is trimmed to remove unnecessary spaces", async function (assert) {
|
|
const controlPanel = await makeWithSearch({
|
|
serverData,
|
|
resModel: "partner",
|
|
Component: ControlPanel,
|
|
searchMenuTypes: [],
|
|
searchViewId: false,
|
|
searchViewArch: `
|
|
<search>
|
|
<field name="foo" filter_domain="[('foo', 'ilike', self)]"/>
|
|
</search>
|
|
`,
|
|
});
|
|
await editSearch(target, "bar");
|
|
await validateSearch(target);
|
|
|
|
assert.deepEqual(getDomain(controlPanel), [["foo", "ilike", "bar"]]);
|
|
|
|
await removeFacet(target);
|
|
|
|
assert.deepEqual(getDomain(controlPanel), []);
|
|
|
|
await editSearch(target, " bar ");
|
|
await validateSearch(target);
|
|
|
|
assert.deepEqual(
|
|
getDomain(controlPanel),
|
|
[["foo", "ilike", "bar"]],
|
|
"the value has been trimmed"
|
|
);
|
|
});
|
|
|
|
QUnit.test("reference fields are supported in search view", async function (assert) {
|
|
assert.expect(4);
|
|
|
|
const partnerModel = serverData.models.partner;
|
|
partnerModel.fields.ref = { type: "reference", string: "Reference" };
|
|
partnerModel.records.forEach((record, i) => {
|
|
record.ref = `ref${String(i).padStart(3, "0")}`;
|
|
});
|
|
|
|
const controlPanel = await makeWithSearch({
|
|
serverData,
|
|
resModel: "partner",
|
|
Component: ControlPanel,
|
|
searchMenuTypes: [],
|
|
searchViewId: false,
|
|
searchViewArch: `
|
|
<search>
|
|
<field name="ref"/>
|
|
</search>
|
|
`,
|
|
});
|
|
|
|
assert.deepEqual(getDomain(controlPanel), []);
|
|
|
|
await editSearch(target, "ref");
|
|
await validateSearch(target);
|
|
|
|
assert.deepEqual(getDomain(controlPanel), [["ref", "ilike", "ref"]]);
|
|
|
|
await removeFacet(target);
|
|
|
|
assert.deepEqual(getDomain(controlPanel), []);
|
|
|
|
await editSearch(target, "ref002");
|
|
await validateSearch(target);
|
|
|
|
assert.deepEqual(getDomain(controlPanel), [["ref", "ilike", "ref002"]]);
|
|
});
|
|
|
|
QUnit.test(
|
|
"expand an asynchronous menu and change the selected item with the mouse during expansion",
|
|
async function (assert) {
|
|
assert.expect(2);
|
|
|
|
const def = makeDeferred();
|
|
const mockRPC = async (route) => {
|
|
if (route.includes("/partner/name_search")) {
|
|
await def;
|
|
}
|
|
};
|
|
await makeWithSearch({
|
|
serverData,
|
|
mockRPC,
|
|
resModel: "partner",
|
|
Component: ControlPanel,
|
|
searchMenuTypes: [],
|
|
searchViewId: false,
|
|
searchViewArch: `
|
|
<search>
|
|
<field name="bar" operator="child_of"/>
|
|
</search>
|
|
`,
|
|
});
|
|
await editSearch(target, "rec");
|
|
await click(target.querySelector(".o_expand"));
|
|
await triggerEvent(
|
|
target,
|
|
".o_searchview_autocomplete li.o_menu_item:first-child",
|
|
"mousemove"
|
|
);
|
|
assert.containsNone(target, ".o_searchview_autocomplete li.o_menu_item.o_indent");
|
|
|
|
def.resolve();
|
|
await nextTick();
|
|
assert.containsN(target, ".o_searchview_autocomplete li.o_menu_item.o_indent", 5);
|
|
}
|
|
);
|
|
|
|
QUnit.test(
|
|
"expand an asynchronous menu and change the selected item with the arrow during expansion",
|
|
async function (assert) {
|
|
assert.expect(2);
|
|
|
|
const def = makeDeferred();
|
|
const mockRPC = async (route) => {
|
|
if (route.includes("/partner/name_search")) {
|
|
await def;
|
|
}
|
|
};
|
|
await makeWithSearch({
|
|
serverData,
|
|
mockRPC,
|
|
resModel: "partner",
|
|
Component: ControlPanel,
|
|
searchMenuTypes: [],
|
|
searchViewId: false,
|
|
searchViewArch: `
|
|
<search>
|
|
<field name="bar" operator="child_of"/>
|
|
</search>
|
|
`,
|
|
});
|
|
await editSearch(target, "rec");
|
|
await click(target.querySelector(".o_expand"));
|
|
const searchInput = target.querySelector(".o_searchview input");
|
|
await triggerEvent(searchInput, null, "keydown", { key: "ArrowDown" });
|
|
assert.containsNone(target, ".o_searchview_autocomplete li.o_menu_item.o_indent");
|
|
|
|
def.resolve();
|
|
await nextTick();
|
|
assert.containsN(target, ".o_searchview_autocomplete li.o_menu_item.o_indent", 5);
|
|
}
|
|
);
|
|
|
|
QUnit.test("checks that an arrowDown always selects an item", async function (assert) {
|
|
assert.expect(1);
|
|
|
|
await makeWithSearch({
|
|
serverData,
|
|
resModel: "partner",
|
|
Component: ControlPanel,
|
|
searchMenuTypes: [],
|
|
searchViewId: false,
|
|
searchViewArch: `
|
|
<search>
|
|
<field name="bar" operator="child_of"/>
|
|
</search>
|
|
`,
|
|
});
|
|
await editSearch(target, "rec");
|
|
await click(target.querySelector(".o_expand"));
|
|
click(target.querySelector(".o_expand"));
|
|
triggerEvent(
|
|
target,
|
|
".o_searchview_autocomplete li.o_menu_item.o_indent:last-child",
|
|
"mousemove"
|
|
);
|
|
const searchInput = target.querySelector(".o_searchview input");
|
|
await triggerEvent(searchInput, null, "keydown", { key: "ArrowDown" });
|
|
assert.containsOnce(target, ".focus");
|
|
});
|
|
|
|
QUnit.test("checks that an arrowUp always selects an item", async function (assert) {
|
|
assert.expect(1);
|
|
|
|
await makeWithSearch({
|
|
serverData,
|
|
resModel: "partner",
|
|
Component: ControlPanel,
|
|
searchMenuTypes: [],
|
|
searchViewId: false,
|
|
searchViewArch: `
|
|
<search>
|
|
<field name="bar" operator="child_of"/>
|
|
</search>
|
|
`,
|
|
});
|
|
await editSearch(target, "rec");
|
|
await click(target.querySelector(".o_expand"));
|
|
click(target.querySelector(".o_expand"));
|
|
triggerEvent(
|
|
target,
|
|
".o_searchview_autocomplete li.o_menu_item.o_indent:last-child",
|
|
"mousemove"
|
|
);
|
|
const searchInput = target.querySelector(".o_searchview input");
|
|
await triggerEvent(searchInput, null, "keydown", { key: "ArrowUp" });
|
|
assert.containsOnce(target, ".focus");
|
|
});
|
|
|
|
QUnit.test("many2one_reference fields are supported in search view", async function (assert) {
|
|
serverData.models.partner.fields.res_id = {
|
|
string: "Resource ID",
|
|
type: "many2one_reference",
|
|
};
|
|
|
|
const controlPanel = await makeWithSearch({
|
|
serverData,
|
|
resModel: "partner",
|
|
Component: ControlPanel,
|
|
searchMenuTypes: [],
|
|
searchViewId: false,
|
|
searchViewArch: /*xml*/ `
|
|
<search>
|
|
<field name="foo" />
|
|
<field name="res_id" />
|
|
</search>
|
|
`,
|
|
});
|
|
|
|
assert.deepEqual(getDomain(controlPanel), []);
|
|
|
|
await editSearch(target, "12");
|
|
assert.deepEqual(
|
|
[...target.querySelectorAll(".o_searchview ul li.dropdown-item")].map(
|
|
(el) => el.innerText
|
|
),
|
|
["Search Foo for: 12", "Search Resource ID for: 12"]
|
|
);
|
|
await triggerEvent(target.querySelector(".o_searchview input"), null, "keydown", {
|
|
key: "ArrowDown",
|
|
});
|
|
await validateSearch(target);
|
|
assert.deepEqual(getDomain(controlPanel), [["res_id", "=", 12]]);
|
|
|
|
await removeFacet(target);
|
|
assert.deepEqual(getDomain(controlPanel), []);
|
|
|
|
await editSearch(target, "1a");
|
|
assert.deepEqual(
|
|
[...target.querySelectorAll(".o_searchview ul li.dropdown-item")].map(
|
|
(el) => el.innerText
|
|
),
|
|
["Search Foo for: 1a"]
|
|
);
|
|
await validateSearch(target);
|
|
assert.deepEqual(getDomain(controlPanel), [["foo", "ilike", "1a"]]);
|
|
});
|
|
|
|
QUnit.test("check kwargs of a rpc call with a domain", async function (assert) {
|
|
assert.expect(3);
|
|
|
|
const mockRPC = async (route, args) => {
|
|
if (route.includes("/partner/name_search")) {
|
|
assert.deepEqual(args, {
|
|
model: "partner",
|
|
method: "name_search",
|
|
args: [],
|
|
kwargs: {
|
|
args: [["bool", "=", true]],
|
|
context: { lang: "en", uid: 7, tz: "taht" },
|
|
limit: 8,
|
|
name: "F",
|
|
},
|
|
});
|
|
}
|
|
};
|
|
|
|
const controlPanel = await makeWithSearch({
|
|
serverData,
|
|
mockRPC,
|
|
resModel: "partner",
|
|
Component: ControlPanel,
|
|
searchMenuTypes: [],
|
|
searchViewId: false,
|
|
});
|
|
|
|
await editSearch(target, "F");
|
|
assert.containsN(
|
|
target,
|
|
".o_searchview_autocomplete li",
|
|
3,
|
|
"there should be 3 result for 'F' in search bar autocomplete"
|
|
);
|
|
|
|
const searchInput = target.querySelector(".o_searchview input");
|
|
await triggerEvent(searchInput, null, "keydown", { key: "ArrowDown" });
|
|
await triggerEvent(searchInput, null, "keydown", { key: "ArrowDown" });
|
|
await triggerEvent(searchInput, null, "keydown", { key: "ArrowRight" });
|
|
await triggerEvent(searchInput, null, "keydown", { key: "ArrowDown" });
|
|
await triggerEvent(searchInput, null, "keydown", { key: "ArrowDown" });
|
|
await triggerEvent(searchInput, null, "keydown", { key: "ArrowDown" });
|
|
await triggerEvent(searchInput, null, "keydown", { key: "Enter" });
|
|
assert.deepEqual(getDomain(controlPanel), [["company", "=", 5]]);
|
|
});
|
|
|
|
QUnit.test("should wait label promises for one2many search defaults", async function (assert) {
|
|
assert.expect(3);
|
|
|
|
const target = getFixture();
|
|
|
|
const def = makeDeferred();
|
|
const mockRPC = async (_, args) => {
|
|
if (args.method === "name_get") {
|
|
await def;
|
|
}
|
|
};
|
|
|
|
makeWithSearch({
|
|
serverData,
|
|
mockRPC,
|
|
resModel: "partner",
|
|
Component: ControlPanel,
|
|
searchMenuTypes: [],
|
|
searchViewId: false,
|
|
context: { search_default_company: 1 },
|
|
});
|
|
|
|
await nextTick();
|
|
assert.containsNone(target, ".o_control_panel");
|
|
|
|
def.resolve();
|
|
await nextTick();
|
|
assert.containsOnce(target, ".o_control_panel");
|
|
assert.strictEqual(getFacetTexts(target)[0].replace("\n", ""), "CompanyFirst record");
|
|
});
|
|
});
|