Odoo18-Base/addons/web/static/tests/search/search_bar.test.js
2025-01-06 10:57:38 +07:00

1844 lines
61 KiB
JavaScript

import { expect, test } from "@odoo/hoot";
import {
click,
hover,
keyDown,
pointerDown,
press,
queryAll,
queryAllTexts,
queryAllValues,
queryFirst,
} from "@odoo/hoot-dom";
import { Deferred, animationFrame, mockDate, mockTimeZone } from "@odoo/hoot-mock";
import { Component, onWillUpdateProps, xml } from "@odoo/owl";
import {
SELECTORS,
clickOnButtonDeleteNode,
getCurrentOperator,
getCurrentPath,
getCurrentValue,
} from "@web/../tests/core/tree_editor/condition_tree_editor_test_helpers";
import {
contains,
defineActions,
defineModels,
editSearch,
fields,
getFacetTexts,
models,
mountWithCleanup,
mountWithSearch,
onRpc,
removeFacet,
selectGroup,
serverState,
toggleMenuItem,
toggleMenuItemOption,
toggleSearchBarMenu,
validateSearch,
} from "@web/../tests/web_test_helpers";
import { browser } from "@web/core/browser/browser";
import { pick } from "@web/core/utils/objects";
import { SearchBar } from "@web/search/search_bar/search_bar";
import { useSearchBarToggler } from "@web/search/search_bar/search_bar_toggler";
class Partner extends models.Model {
name = fields.Char();
bar = fields.Many2one({ relation: "partner" });
birthday = fields.Date();
birth_datetime = fields.Datetime({ string: "Birth DateTime" });
foo = fields.Char();
bool = fields.Boolean();
company = fields.Many2one({ relation: "partner" });
properties = fields.Properties({
definition_record: "bar",
definition_record_field: "child_properties",
});
child_properties = fields.PropertiesDefinition();
_records = [
{
id: 1,
name: "First record",
foo: "yop",
bar: 2,
bool: true,
birthday: "1983-07-15",
birth_datetime: "1983-07-15 01:00:00",
},
{
id: 2,
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,
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,
name: "Fourth record",
foo: "plop",
bar: 2,
bool: true,
birthday: "1983-05-05",
birth_datetime: "1983-05-05 04:00:00",
},
{
id: 5,
name: "Fifth record",
foo: "zoup",
bar: 2,
bool: true,
birthday: "1800-01-01",
birth_datetime: "1800-01-01 05:00:00",
},
];
_views = {
list: `<list><field name="foo"/></list>`,
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>
`,
};
}
defineModels([Partner]);
defineActions([
{
id: 1,
name: "Partners Action",
res_model: "partner",
search_view_id: [false, "search"],
type: "ir.actions.act_window",
views: [[false, "list"]],
},
]);
test.tags`desktop`("basic rendering", async () => {
await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
});
expect(queryFirst`.o_searchview input`).toBeFocused();
});
test.tags`desktop`("navigation with facets", async () => {
await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: ["groupBy"],
searchViewId: false,
context: { search_default_date_group_by: 1 },
});
expect(`.o_searchview .o_searchview_facet`).toHaveCount(1);
expect(queryFirst`.o_searchview input`).toBeFocused();
await keyDown("ArrowLeft"); // press left to focus the facet
await animationFrame();
expect(queryFirst`.o_searchview .o_searchview_facet`).toBeFocused();
await keyDown("ArrowRight"); // press right to focus the input
await animationFrame();
expect(queryFirst`.o_searchview input`).toBeFocused();
});
test.tags`desktop`("navigation with facets (2)", async () => {
await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: ["groupBy"],
searchViewId: false,
context: {
search_default_date_group_by: 1,
search_default_foo: 1,
},
});
expect(`.o_searchview .o_searchview_facet`).toHaveCount(2);
expect(queryFirst`.o_searchview input`).toBeFocused();
// press left to focus the rightmost facet
await keyDown("ArrowLeft");
await animationFrame();
expect(queryFirst`.o_searchview .o_searchview_facet:nth-child(2)`).toBeFocused();
// press left to focus the leftmost facet
await keyDown("ArrowLeft");
await animationFrame();
expect(queryFirst`.o_searchview .o_searchview_facet:nth-child(1)`).toBeFocused();
// press left to focus the input
await keyDown("ArrowLeft");
await animationFrame();
expect(queryFirst`.o_searchview input`).toBeFocused();
// press left to focus the leftmost facet
await keyDown("ArrowRight");
await animationFrame();
expect(queryFirst`.o_searchview .o_searchview_facet:nth-child(1)`).toBeFocused();
});
test.tags("mobile");
test("search input is focused when being toggled", async () => {
class Parent extends Component {
static template = xml`
<div>
<t t-component="searchBarToggler.component" t-props="searchBarToggler.props"/>
<SearchBar toggler="searchBarToggler"/>
</div>
`;
static components = { SearchBar };
static props = ["*"];
setup() {
this.searchBarToggler = useSearchBarToggler();
}
}
await mountWithSearch(Parent, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
});
expect(".o_searchview input").toHaveCount(0);
await contains(`button .fa-search`).click();
expect(".o_searchview input").toHaveCount(1);
expect(queryFirst`.o_searchview input`).toBeFocused();
});
test("search date and datetime fields. Support of timezones", async () => {
mockTimeZone(6);
const searchBar = await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
});
// Date case
await editSearch("07/15/1983");
await keyDown("ArrowDown");
await animationFrame();
await keyDown("Enter");
await animationFrame();
expect(getFacetTexts().map((str) => str.replace(/\s+/g, " "))).toEqual(["Birthday 07/15/1983"]);
expect(searchBar.env.searchModel.domain).toEqual([["birthday", "=", "1983-07-15"]]);
// Close Facet
await click(`.o_searchview_facet .o_facet_remove`);
await animationFrame();
// DateTime case
await editSearch("07/15/1983 00:00:00");
await keyDown("ArrowDown");
await animationFrame();
await keyDown("Enter");
await animationFrame();
expect(getFacetTexts().map((str) => str.replace(/\s+/g, " "))).toEqual([
"Birth DateTime 07/15/1983 00:00:00",
]);
expect(searchBar.env.searchModel.domain).toEqual([
["birth_datetime", "=", "1983-07-14 18:00:00"],
]);
});
test("autocomplete menu clickout interactions", async () => {
await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
searchViewArch: /* xml */ `
<search>
<field name="bar"/>
<field name="birthday"/>
<field name="birth_datetime"/>
<field name="foo"/>
<field name="bool"/>
</search>
`,
});
// Create an input outside of the search panel to simulate another input outside of the search panel
await mountWithCleanup(/* xml */ `<input id="foo"/>`);
expect(`.o_searchview_autocomplete`).toHaveCount(0);
await editSearch("Hello there");
expect(`.o_searchview input`).toHaveValue("Hello there");
expect(`.o_searchview_autocomplete`).toHaveCount(1);
await keyDown("Escape");
await animationFrame();
expect(`.o_searchview input`).toHaveValue("");
expect(`.o_searchview_autocomplete`).toHaveCount(0);
await editSearch("General Kenobi");
expect(`.o_searchview input`).toHaveValue("General Kenobi");
expect(`.o_searchview_autocomplete`).toHaveCount(1);
await contains(`input#foo`).click();
expect(`.o_searchview input`).toHaveValue("");
expect(`.o_searchview_autocomplete`).toHaveCount(0);
expect("input#foo").toBeFocused();
});
test("select an autocomplete field", async () => {
const searchBar = await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
});
await editSearch("a");
expect(`.o_searchview_autocomplete li`).toHaveCount(4);
await keyDown("Enter");
await animationFrame();
expect(`.o_searchview_input_container .o_facet_values`).toHaveText("a");
expect(searchBar.env.searchModel.domain).toEqual([["foo", "ilike", "a"]]);
});
test("select an autocomplete field with `context` key", async () => {
let updateCount = 0;
class TestComponent extends Component {
static template = xml`<SearchBar/>`;
static components = { SearchBar };
static props = ["*"];
setup() {
onWillUpdateProps(() => {
updateCount++;
});
}
}
const searchBar = await mountWithSearch(TestComponent, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
});
// 'r' key to filter on bar "First Record"
await editSearch("record");
await keyDown("ArrowDown");
await animationFrame();
await keyDown("ArrowRight");
await animationFrame();
await keyDown("ArrowDown");
await animationFrame();
await keyDown("Enter");
await animationFrame();
expect(getFacetTexts().map((str) => str.replace(/\s+/g, " "))).toEqual(["Bar First record"]);
expect(updateCount).toBe(1);
expect(searchBar.env.searchModel.domain).toEqual([["bar", "=", 1]]);
expect(searchBar.env.searchModel.context.bar).toEqual([1]);
// 'r' key to filter on bar "Second Record"
await editSearch("record");
await keyDown("ArrowDown");
await animationFrame();
await keyDown("ArrowRight");
await animationFrame();
await keyDown("ArrowDown");
await animationFrame();
await keyDown("ArrowDown");
await animationFrame();
await keyDown("Enter");
await animationFrame();
expect(getFacetTexts().map((str) => str.replace(/\s+/g, " "))).toEqual([
"Bar First record or Second record",
]);
expect(updateCount).toBe(2);
expect(searchBar.env.searchModel.domain).toEqual(["|", ["bar", "=", 1], ["bar", "=", 2]]);
expect(searchBar.env.searchModel.context.bar).toEqual([1, 2]);
});
test.tags`desktop`("no search text triggers a reload", async () => {
let updateCount = 0;
class TestComponent extends Component {
static template = xml`<SearchBar/>`;
static components = { SearchBar };
static props = ["*"];
setup() {
onWillUpdateProps(() => {
updateCount++;
});
}
}
await mountWithSearch(TestComponent, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
});
await keyDown("Enter");
await animationFrame();
expect(`.o_searchview_facet_label`).toHaveCount(0);
expect(updateCount).toBe(1);
});
test("selecting (no result) triggers a search bar rendering", async () => {
await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
searchViewArch: `
<search>
<field name="bar"/>
</search>
`,
});
await editSearch("hello there");
// 'a' key to filter nothing on bar
await keyDown("ArrowRight");
await animationFrame();
await keyDown("ArrowDown");
await animationFrame();
expect(`.o_searchview_autocomplete .focus`).toHaveText("(no result)");
await keyDown("Enter");
await animationFrame();
expect(`.o_searchview_facet_label`).toHaveCount(0);
expect(`.o_searchview input`).toHaveValue("");
});
test("update suggested filters in autocomplete menu with Japanese IME", async () => {
// 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 mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
});
// Simulate typing "TEST" on search view.
await contains(`.o_searchview input`).edit(TEST, { composition: true, confirm: false });
expect(`.o_searchview_autocomplete`).toHaveCount(1);
expect(queryFirst`.o_searchview_autocomplete li`).toHaveText("Search Foo for: TEST");
// Simulate soft-selection of another suggestion from IME through keyboard navigation.
await contains(`.o_searchview input`).edit(テスト, { composition: true, confirm: false });
expect(queryFirst`.o_searchview_autocomplete li`).toHaveText("Search Foo for: テスト");
// Simulate selection on suggestion item "TEST" from IME.
await contains(`.o_searchview input`).edit(TEST, { composition: true, confirm: false });
expect(`.o_searchview_autocomplete`).toHaveCount(1);
expect(queryFirst`.o_searchview_autocomplete li`).toHaveText("Search Foo for: TEST");
});
test("open search view autocomplete on paste value using mouse", async () => {
await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
});
// Simulate paste text through the mouse.
await navigator.clipboard.writeText("ABC");
await pointerDown(".o_searchview input");
await press(["ctrl", "v"]);
await animationFrame();
expect(`.o_searchview_autocomplete`).toHaveCount(1);
});
test("select autocompleted many2one", async () => {
const searchBar = await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
searchViewArch: `
<search>
<field name="foo"/>
<field name="birthday"/>
<field name="birth_datetime"/>
<field name="bar" operator="child_of"/>
</search>
`,
});
expect(searchBar.env.searchModel.domain).toEqual([]);
await editSearch("rec");
await contains(".o_searchview_autocomplete li:nth-last-child(2)").click();
expect(searchBar.env.searchModel.domain).toEqual([["bar", "child_of", "rec"]]);
await removeFacet("Bar rec");
expect(searchBar.env.searchModel.domain).toEqual([]);
await editSearch("rec");
await contains(".o_expand").click();
await contains(".o_searchview_autocomplete li.o_menu_item.o_indent").click();
expect(searchBar.env.searchModel.domain).toEqual([["bar", "child_of", 1]]);
});
test(`"null" as autocomplete value`, async () => {
const searchBar = await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
});
expect(searchBar.env.searchModel.domain).toEqual([]);
await editSearch("null");
expect(`.o_searchview_autocomplete .focus`).toHaveText("Search Foo for: null");
await contains(".o_searchview_autocomplete li.focus a").click();
expect(searchBar.env.searchModel.domain).toEqual([["foo", "ilike", "null"]]);
});
test("autocompletion with a boolean field", async () => {
const searchBar = await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
searchViewArch: `
<search>
<field name="bool"/>
</search>
`,
});
expect(searchBar.env.searchModel.domain).toEqual([]);
await editSearch("y");
expect(`.o_searchview_autocomplete li`).toHaveCount(2);
expect(`.o_searchview_autocomplete li:nth-last-child(2)`).toHaveText("Search Bool for: Yes");
// select "Yes"
await contains(".o_searchview_autocomplete li:nth-last-child(2)").click();
expect(searchBar.env.searchModel.domain).toEqual([["bool", "=", true]]);
await removeFacet("Bool Yes");
expect(searchBar.env.searchModel.domain).toEqual([]);
await editSearch("No");
expect(`.o_searchview_autocomplete li`).toHaveCount(2);
expect(`.o_searchview_autocomplete li:nth-last-child(2)`).toHaveText("Search Bool for: No");
// select "No"
await contains(".o_searchview_autocomplete li:nth-last-child(2)").click();
expect(searchBar.env.searchModel.domain).toEqual([["bool", "=", false]]);
});
test("the search value is trimmed to remove unnecessary spaces", async () => {
const searchBar = await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
searchViewArch: `
<search>
<field name="foo" filter_domain="[('foo', 'ilike', self)]"/>
</search>
`,
});
await editSearch("bar");
await validateSearch();
expect(searchBar.env.searchModel.domain).toEqual([["foo", "ilike", "bar"]]);
await removeFacet("Foo bar");
expect(searchBar.env.searchModel.domain).toEqual([]);
await editSearch(" bar ");
await validateSearch();
expect(searchBar.env.searchModel.domain).toEqual([["foo", "ilike", "bar"]]);
});
test("reference fields are supported in search view", async () => {
Partner._fields.ref = fields.Reference({ selection: [["partner", "Partner"]] });
const searchBar = await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
searchViewArch: `
<search>
<field name="ref"/>
</search>
`,
});
expect(searchBar.env.searchModel.domain).toEqual([]);
await editSearch("ref");
await validateSearch();
expect(searchBar.env.searchModel.domain).toEqual([["ref", "ilike", "ref"]]);
await removeFacet("Ref ref");
expect(searchBar.env.searchModel.domain).toEqual([]);
await editSearch("ref002");
await validateSearch();
expect(searchBar.env.searchModel.domain).toEqual([["ref", "ilike", "ref002"]]);
});
test("expand an asynchronous menu and change the selected item with the mouse during expansion", async () => {
const def = new Deferred();
onRpc("name_search", () => def);
await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
searchViewArch: `
<search>
<field name="bar" operator="child_of"/>
</search>
`,
});
await editSearch("rec");
await contains(`.o_expand`).click();
await contains(`.o_searchview_autocomplete li.o_menu_item:first-child`).hover();
expect(`.o_searchview_autocomplete li.o_menu_item.o_indent`).toHaveCount(0);
def.resolve();
await animationFrame();
expect(`.o_searchview_autocomplete li.o_menu_item.o_indent`).toHaveCount(5);
});
test("expand an asynchronous menu and change the selected item with the arrow during expansion", async () => {
const def = new Deferred();
onRpc("name_search", () => def);
await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
searchViewArch: `
<search>
<field name="bar" operator="child_of"/>
</search>
`,
});
await editSearch("rec");
await contains(".o_expand").click();
await keyDown("ArrowDown");
await animationFrame();
expect(`.o_searchview_autocomplete li.o_menu_item.o_indent`).toHaveCount(0);
def.resolve();
await animationFrame();
expect(`.o_searchview_autocomplete li.o_menu_item.o_indent`).toHaveCount(5);
});
test("checks that an arrowDown always selects an item", async () => {
await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
searchViewArch: `
<search>
<field name="bar" operator="child_of"/>
</search>
`,
});
await editSearch("rec");
await contains(".o_expand").click();
await click(".o_expand"); // don't wait for a frame
await hover(`.o_searchview_autocomplete li.o_menu_item.o_indent:last-child`);
await animationFrame();
await keyDown("ArrowDown");
await animationFrame();
expect(".focus").toHaveCount(1);
});
test("checks that an arrowUp always selects an item", async () => {
await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
searchViewArch: `
<search>
<field name="bar" operator="child_of"/>
</search>
`,
});
await editSearch("rec");
await contains(".o_expand").click();
await click(".o_expand"); // don't wait for a frame
await hover(`.o_searchview_autocomplete li.o_menu_item.o_indent:last-child`);
await animationFrame();
await keyDown("ArrowUp");
await animationFrame();
expect(".focus").toHaveCount(1);
});
test("many2one_reference fields are supported in search view", async () => {
Partner._fields.res_id = fields.Many2oneReference({
string: "Resource ID",
model_field: "bar",
relation: "partner",
});
const searchBar = await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
searchViewArch: /*xml*/ `
<search>
<field name="foo" />
<field name="res_id" />
</search>
`,
});
expect(searchBar.env.searchModel.domain).toEqual([]);
await editSearch("12");
expect(queryAllTexts`.o_searchview ul li.dropdown-item`).toEqual([
"Search Foo for: 12",
"Search Resource ID for: 12",
"Add Custom Filter",
]);
await keyDown("ArrowDown");
await validateSearch();
expect(searchBar.env.searchModel.domain).toEqual([["res_id", "=", 12]]);
await removeFacet("Resource ID 12");
expect(searchBar.env.searchModel.domain).toEqual([]);
await editSearch("1a");
expect(queryAllTexts`.o_searchview ul li.dropdown-item`).toEqual([
"Search Foo for: 1a",
"Add Custom Filter",
]);
await validateSearch();
expect(searchBar.env.searchModel.domain).toEqual([["foo", "ilike", "1a"]]);
});
test("check kwargs of a rpc call with a domain", async () => {
onRpc("name_search", (params) => {
expect(pick(params, "args", "kwargs", "method", "model")).toEqual({
model: "partner",
method: "name_search",
args: [],
kwargs: {
args: [["bool", "=", true]],
context: { lang: "en", uid: 7, tz: "taht", allowed_company_ids: [1] },
limit: 8 + 1,
operator: "ilike",
name: "F",
},
});
});
const searchBar = await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
});
await editSearch("F");
expect(`.o_searchview_autocomplete li`).toHaveCount(4);
await keyDown("ArrowDown");
await animationFrame();
await keyDown("ArrowDown");
await animationFrame();
await keyDown("ArrowRight");
await animationFrame();
await keyDown("ArrowDown");
await animationFrame();
await keyDown("ArrowDown");
await animationFrame();
await keyDown("ArrowDown");
await animationFrame();
await keyDown("Enter");
await animationFrame();
expect(searchBar.env.searchModel.domain).toEqual([["company", "=", 5]]);
});
test("should wait label promises for one2many search defaults", async () => {
const def = new Deferred();
onRpc("read", () => def);
mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
context: { search_default_company: 1 },
});
expect(`.o_cp_searchview`).toHaveCount(0);
def.resolve();
await animationFrame();
expect(`.o_cp_searchview`).toHaveCount(1);
expect(getFacetTexts()[0].replace("\n", "")).toBe("CompanyFirst record");
});
test("globalContext keys in name_search", async () => {
onRpc("name_search", ({ kwargs }) => {
expect.step("name_search");
expect(kwargs.context.specialKey).toBe("ABCD");
});
await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
searchViewArch: `
<search>
<field name="company"/>
</search>
`,
context: { specialKey: "ABCD" },
});
await editSearch("F");
await keyDown("ArrowRight");
await animationFrame();
expect.verifySteps(["name_search"]);
});
test("search a property", async () => {
onRpc("web_search_read", ({ kwargs }) => {
if (kwargs.specification.display_name && kwargs.specification.child_properties) {
const definition1 = [
{
type: "many2one",
string: "My Partner",
name: "my_partner",
comodel: "partner",
},
{
type: "many2many",
string: "My Partners",
name: "my_partners",
comodel: "partner",
},
{
type: "selection",
string: "My Selection",
name: "my_selection",
selection: [
["a", "A"],
["b", "B"],
["c", "C"],
["aa", "AA"],
],
},
{
type: "tags",
string: "My Tags",
name: "my_tags",
tags: [
["a", "A", 1],
["b", "B", 5],
["c", "C", 3],
["aa", "AA", 2],
],
},
];
const definition2 = [
{
type: "char",
string: "My Text",
name: "my_text",
},
];
return {
records: [
{ id: 1, display_name: "Bar 1", child_properties: definition1 },
{ id: 2, display_name: "Bar 2", child_properties: definition2 },
],
};
}
});
onRpc("name_search", ({ kwargs }) => {
if (kwargs.name === "Bo") {
return [
[5, "Bob"],
[6, "Bobby"],
];
} else if (kwargs.name === "Ali") {
return [
[9, "Alice"],
[10, "Alicia"],
];
}
});
const searchBar = await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
searchViewArch: `
<search>
<field name="properties"/>
</search>
`,
});
// expand the properties field
await editSearch("a");
await contains(".o_expand").click();
expect(`.o_searchview_input_container li`).toHaveCount(9);
expect(queryAllTexts`.o_searchview_input_container li`).toEqual([
"Search Properties",
"My Partner (Bar 1)",
"My Partners (Bar 1)",
"My Selection (Bar 1) for: A",
"My Selection (Bar 1) for: AA",
"My Tags (Bar 1) for: A",
"My Tags (Bar 1) for: AA",
"My Text (Bar 2) for: a",
"Add Custom Filter",
]);
// click again on the expand icon to hide the properties
await contains(".o_expand").click();
expect(`.o_searchview_input_container li`).toHaveCount(2);
expect(queryAllTexts`.o_searchview_input_container li`).toEqual([
"Search Properties",
"Add Custom Filter",
]);
// search for a partner, and expand the many2many property
await contains(`.o_searchview_input`).clear();
await editSearch("Bo");
await contains(".o_expand").click();
await contains("li:nth-child(3) .o_expand").click();
expect(`.o_searchview_input_container li`).toHaveCount(7);
expect(queryAllTexts`.o_searchview_input_container li`).toEqual([
"Search Properties",
"My Partner (Bar 1)",
"My Partners (Bar 1)",
"Bob",
"Bobby",
"My Text (Bar 2) for: Bo",
"Add Custom Filter",
]);
// fold all the properties (included the search result)
await contains(".o_expand").click();
expect(`.o_searchview_input_container li`).toHaveCount(2);
expect(queryAllTexts`.o_searchview_input_container li`).toEqual([
"Search Properties",
"Add Custom Filter",
]);
// unfold all the properties but fold the search result
await contains(".o_expand").click();
await contains("li:nth-child(3) .o_expand").click();
expect(`.o_searchview_input_container li`).toHaveCount(5);
expect(queryAllTexts`.o_searchview_input_container li`).toEqual([
"Search Properties",
"My Partner (Bar 1)",
"My Partners (Bar 1)",
"My Text (Bar 2) for: Bo",
"Add Custom Filter",
]);
// select Bobby
await contains("li:nth-child(3) .o_expand").click();
await contains(".o_searchview_input_container li:nth-child(5)").click();
expect(searchBar.env.searchModel.domain).toEqual([
"&",
["bar", "=", 1],
["properties.my_partners", "in", 6],
]);
// expand the selection properties
await contains(".o_cp_searchview").click();
await editSearch("a");
await contains(".o_expand").click();
expect(`.o_searchview_input_container li`).toHaveCount(9);
expect(queryAllTexts`.o_searchview_input_container li`).toEqual([
"Search Properties",
"My Partner (Bar 1)",
"My Partners (Bar 1)",
"My Selection (Bar 1) for: A",
"My Selection (Bar 1) for: AA",
"My Tags (Bar 1) for: A",
"My Tags (Bar 1) for: AA",
"My Text (Bar 2) for: a",
"Add Custom Filter",
]);
// select the selection option "AA"
await contains(".o_searchview_input_container li:nth-child(5)").click();
expect(searchBar.env.searchModel.domain).toEqual([
"&",
"&",
["bar", "=", 1],
["properties.my_partners", "in", 6],
"&",
["bar", "=", 1],
["properties.my_selection", "=", "aa"],
]);
// select the selection option "A"
await contains(".o_cp_searchview").click();
await editSearch("a");
await contains(".o_expand").click();
await contains(".o_searchview_input_container li:nth-child(4)").click();
expect(searchBar.env.searchModel.domain).toEqual([
"&",
"&",
["bar", "=", 1],
["properties.my_partners", "in", 6],
"|",
"&",
["bar", "=", 1],
["properties.my_selection", "=", "aa"],
"&",
["bar", "=", 1],
["properties.my_selection", "=", "a"],
]);
// reset the search
await contains(".o_facet_remove").click();
await contains(".o_facet_remove").click();
// search a many2one value
await contains(".o_cp_searchview").click();
await editSearch("Ali");
await contains(".o_expand").click();
await contains("li:nth-child(2) .o_expand").click();
expect(`.o_searchview_input_container li`).toHaveCount(7);
expect(queryAllTexts`.o_searchview_input_container li`).toEqual([
"Search Properties",
"My Partner (Bar 1)",
"Alice",
"Alicia",
"My Partners (Bar 1)",
"My Text (Bar 2) for: Ali",
"Add Custom Filter",
]);
await contains(".o_searchview_input_container li:nth-child(4)").click();
expect(searchBar.env.searchModel.domain).toEqual([
"&",
["bar", "=", 1],
["properties.my_partner", "=", 10],
]);
// search a tag value
await contains(".o_cp_searchview").click();
await editSearch("A");
await contains(".o_expand").click();
expect(`.o_searchview_input_container li`).toHaveCount(9);
expect(queryAllTexts`.o_searchview_input_container li`).toEqual([
"Search Properties",
"My Partner (Bar 1)",
"My Partners (Bar 1)",
"My Selection (Bar 1) for: A",
"My Selection (Bar 1) for: AA",
"My Tags (Bar 1) for: A",
"My Tags (Bar 1) for: AA",
"My Text (Bar 2) for: A",
"Add Custom Filter",
]);
await contains(".o_searchview_input_container li:nth-child(7)").click();
expect(searchBar.env.searchModel.domain).toEqual([
"&",
"&",
["bar", "=", 1],
["properties.my_partner", "=", 10],
"&",
["bar", "=", 1],
["properties.my_tags", "in", "aa"],
]);
// add the tag "B"
await contains(".o_cp_searchview").click();
await editSearch("B");
await contains(".o_expand").click();
expect(`.o_searchview_input_container li`).toHaveCount(7);
expect(queryAllTexts`.o_searchview_input_container li`).toEqual([
"Search Properties",
"My Partner (Bar 1)",
"My Partners (Bar 1)",
"My Selection (Bar 1) for: B",
"My Tags (Bar 1) for: B",
"My Text (Bar 2) for: B",
"Add Custom Filter",
]);
await contains(".o_searchview_input_container li:nth-child(5)").click();
expect(searchBar.env.searchModel.domain).toEqual([
"&",
"&",
["bar", "=", 1],
["properties.my_partner", "=", 10],
"|",
"&",
["bar", "=", 1],
["properties.my_tags", "in", "aa"],
"&",
["bar", "=", 1],
["properties.my_tags", "in", "b"],
]);
// try to click on the many2one properties without unfolding
// it should not add the domain, but unfold the item
await editSearch("Bobby");
await contains(".o_expand").click();
await contains(".o_searchview_input_container li:nth-child(2)").click();
expect(searchBar.env.searchModel.domain).toEqual([
"&",
"&",
["bar", "=", 1],
["properties.my_partner", "=", 10],
"|",
"&",
["bar", "=", 1],
["properties.my_tags", "in", "aa"],
"&",
["bar", "=", 1],
["properties.my_tags", "in", "b"],
]);
expect(`.o_searchview_input_container li`).toHaveCount(6);
expect(queryAllTexts`.o_searchview_input_container li`).toEqual([
"Search Properties",
"My Partner (Bar 1)",
"(no result)",
"My Partners (Bar 1)",
"My Text (Bar 2) for: Bobby",
"Add Custom Filter",
]);
// test the navigation with keyboard
await contains(`.o_searchview_input`).clear();
await editSearch("Bo");
expect(`.o_menu_item.focus`).toHaveText("Search Properties");
// unfold the properties field
await keyDown("ArrowRight");
await animationFrame();
expect(`.o_menu_item.focus`).toHaveText("Search Properties");
expect(".o_menu_item.focus:only .fa-caret-down").toHaveCount(1);
// move on the many2one property
await keyDown("ArrowRight");
await animationFrame();
expect(`.o_menu_item.focus`).toHaveText("My Partner (Bar 1)");
expect(".o_menu_item.focus:only .fa-caret-right").toHaveCount(1);
// move on the many2many property
await keyDown("ArrowDown");
await animationFrame();
expect(`.o_menu_item.focus`).toHaveText("My Partners (Bar 1)");
expect(".o_menu_item.focus:only .fa-caret-right").toHaveCount(1);
// move on the many2one property again
await keyDown("ArrowUp");
await animationFrame();
expect(`.o_menu_item.focus`).toHaveText("My Partner (Bar 1)");
expect(".o_menu_item.focus:only .fa-caret-right").toHaveCount(1);
// unfold the many2one
await keyDown("ArrowRight");
await animationFrame();
expect(`.o_menu_item.focus`).toHaveText("My Partner (Bar 1)");
expect(".o_menu_item.focus:only .fa-caret-down").toHaveCount(1);
// select the first many2one
await keyDown("ArrowRight");
await animationFrame();
expect(`.o_menu_item.focus`).toHaveText("Bob");
// go up on the parent
await keyDown("ArrowLeft");
await animationFrame();
expect(`.o_menu_item.focus`).toHaveText("My Partner (Bar 1)");
expect(".o_menu_item.focus:only .fa-caret-down").toHaveCount(1);
// fold the parent
await keyDown("ArrowLeft");
await animationFrame();
expect(`.o_menu_item.focus`).toHaveText("My Partner (Bar 1)");
expect(".o_menu_item.focus:only .fa-caret-right").toHaveCount(1);
// go up on the properties field
await keyDown("ArrowLeft");
await animationFrame();
expect(`.o_menu_item.focus`).toHaveText("Search Properties");
expect(".o_menu_item.focus:only .fa-caret-down").toHaveCount(1);
// fold the properties field
await keyDown("ArrowLeft");
await animationFrame();
expect(`.o_menu_item.focus`).toHaveText("Search Properties");
expect(".o_menu_item.focus:only .fa-caret-right").toHaveCount(1);
});
test("search a property: definition record id in the context", async () => {
onRpc("web_search_read", ({ kwargs }) => {
if (kwargs.specification.display_name && kwargs.specification.child_properties) {
expect.step("web_search_read");
expect(kwargs.domain).toEqual(["&", ["child_properties", "!=", false], ["id", "=", 2]]);
const definition2 = [
{
type: "char",
string: "My Text",
name: "my_text",
},
];
return {
records: [{ id: 2, display_name: "Bar 2", child_properties: definition2 }],
};
}
});
await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
searchViewArch: `
<search>
<field name="properties"/>
</search>
`,
context: { active_id: 2 },
});
await contains(".o_cp_searchview").click();
await editSearch("a");
await contains(".o_expand").click();
expect.verifySteps(["web_search_read"]);
expect(`.o_searchview_input_container li`).toHaveCount(3);
expect(queryAll`.o_searchview_input_container li`[1]).toHaveText("My Text (Bar 2) for: a");
});
test("edit a filter", async () => {
onRpc("/web/domain/validate", () => true);
await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: ["groupBy"], // we need it to have facet (see facets getter in search_model)
searchViewId: false,
searchViewArch: `
<search>
<filter name="filter" string="Filter" domain="[('birthday', '>=', context_today())]"/>
<filter name="bool" string="Bool" domain="[]" context="{'group_by': 'bool'}"/>
</search>
`,
context: {
search_default_filter: true,
search_default_bool: true,
},
});
expect(getFacetTexts()).toEqual(["Filter", "Bool"]);
expect(`.o_searchview_facet .o_searchview_facet_label`).toHaveCount(2);
expect(`.o_searchview_facet.o_facet_with_domain .o_searchview_facet_label`).toHaveCount(1);
expect(`.modal`).toHaveCount(0);
await contains(".o_facet_with_domain .o_searchview_facet_label").click();
expect(`.modal`).toHaveCount(1);
expect(`.modal header`).toHaveText("Modify Condition");
expect(`.modal .o_domain_selector`).toHaveCount(1);
expect(SELECTORS.condition).toHaveCount(1);
expect(queryAllTexts`.modal footer button`).toEqual(["Confirm", "Discard"]);
expect(getCurrentPath()).toBe("Birthday");
expect(getCurrentOperator()).toBe(">=");
expect(getCurrentValue()).toBe("context_today()");
expect(`.modal footer button`).toBeEnabled();
await clickOnButtonDeleteNode();
expect(SELECTORS.condition).toHaveCount(0);
expect(`.modal footer button`).not.toBeEnabled();
await contains(`.modal ${SELECTORS.addNewRule}`).click();
expect(SELECTORS.condition).toHaveCount(1);
expect(getCurrentPath()).toBe("Id");
expect(getCurrentOperator()).toBe("=");
expect(getCurrentValue()).toBe("1");
await contains(".modal footer button").click();
expect(`.modal`).toHaveCount(0);
expect(getFacetTexts()).toEqual(["Bool", "Id = 1"]);
});
test("edit a filter with context: context is kept after edition", async () => {
onRpc("/web/domain/validate", () => true);
const searchBar = await mountWithSearch(SearchBar, {
resModel: "partner",
searchViewId: false,
searchViewArch: `
<search>
<filter name="filter" string="Filter" context="{'specialKey': 'abc'}" domain="[('foo', '=', 'abc')]"/>
</search>
`,
context: {
search_default_filter: true,
},
});
expect(getFacetTexts()).toEqual(["Filter"]);
expect(searchBar.env.searchModel.context.specialKey).toBe("abc");
await contains(".o_facet_with_domain .o_searchview_facet_label").click();
await contains(".modal footer button").click();
expect(getFacetTexts()).toEqual([`Foo = abc`]);
expect(searchBar.env.searchModel.context.specialKey).toBe("abc");
});
test("edit a favorite", async () => {
const irFilters = [
{
context: "{ 'some_key': 'some_value', 'group_by': ['bool'] }",
domain: "[('foo', 'ilike', 'abc')]",
id: 1,
is_default: true,
name: "My favorite",
sort: "[]",
user_id: [2, "Mitchell Admin"],
},
];
onRpc("/web/domain/validate", () => true);
await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: ["groupBy"], // we need it to have facet (see facets getter in search_model)
searchViewId: false,
searchViewArch: `
<search>
<filter name="company" string="Company" domain="[]" context="{'group_by': 'company'}"/>
</search>
`,
irFilters,
});
expect(getFacetTexts()).toEqual(["My favorite"]);
expect(`.o_searchview_facet.o_facet_with_domain .o_searchview_facet_label`).toHaveCount(1);
await toggleSearchBarMenu();
await toggleMenuItem("Company");
expect(getFacetTexts()).toEqual(["My favorite", "Company"]);
expect(`.o_searchview_facet .o_searchview_facet_label`).toHaveCount(2);
expect(`.o_searchview_facet.o_facet_with_domain .o_searchview_facet_label`).toHaveCount(1);
await contains(".o_facet_with_domain .o_searchview_facet_label").click();
expect(`.modal`).toHaveCount(1);
expect(getCurrentPath()).toBe("Foo");
expect(getCurrentOperator()).toBe("contains");
expect(getCurrentValue()).toBe("abc");
await contains(".modal footer button").click();
expect(`.modal`).toHaveCount(0);
expect(getFacetTexts()).toEqual(["Bool\n>\nCompany", "Foo contains abc"]);
});
test("edit a date filter with comparison active", async () => {
mockDate("2023-04-28T13:40:00");
onRpc("/web/domain/validate", () => true);
await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: ["filter", "comparison"],
searchViewId: false,
searchViewArch: `
<search>
<filter name="birthday" string="Birthday" date="birthday"/>
</search>
`,
context: {
search_default_birthday: true,
},
});
expect(getFacetTexts()).toEqual(["Birthday: April 2023"]);
expect(`.o_searchview_facet.o_facet_with_domain .o_searchview_facet_label`).toHaveCount(1);
await toggleSearchBarMenu();
await toggleMenuItem("Birthday: Previous Period");
expect(getFacetTexts()).toEqual(["Birthday: April 2023", "Birthday: Previous Period"]);
expect(`.o_searchview_facet.o_facet_with_domain .o_searchview_facet_label`).toHaveCount(1);
await contains(".o_facet_with_domain .o_searchview_facet_label").click();
expect(`.modal`).toHaveCount(1);
expect(SELECTORS.condition).toHaveCount(1);
expect(getCurrentPath()).toBe("Birthday");
expect(getCurrentOperator()).toBe("is between");
expect(queryAllValues`.o_datetime_input`).toEqual(["04/01/2023", "04/30/2023"]);
await contains(".modal footer button").click();
expect(`.modal`).toHaveCount(0);
expect(getFacetTexts()).toEqual([`Birthday is between 04/01/2023 and 04/30/2023`]);
});
test("toggle a custom option in a date filter", async () => {
mockDate("2023-04-28T13:40:00");
onRpc("/web/domain/validate", () => true);
await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: ["filter", "comparison"],
searchViewId: false,
searchViewArch: `
<search>
<filter name="birthday" string="Birthday" date="birthday">
<filter name="birthday_today" string="Today" domain="[('birthday', '=', context_today().strftime('%Y-%m-%d'))]"/>
</filter>
</search>
`,
context: {
search_default_birthday: true,
},
});
expect(getFacetTexts()).toEqual(["Birthday: April 2023"]);
await toggleSearchBarMenu();
expect(`.o_dropdown_container.o_comparison_menu`).toHaveCount(1);
await toggleMenuItem("Birthday");
await toggleMenuItemOption("Birthday", "Today");
expect(getFacetTexts()).toEqual(["Birthday: Today"]);
await toggleSearchBarMenu();
expect(`.o_dropdown_container.o_comparison_menu`).toHaveCount(0);
});
test("toggle a custom option in a date filter with comparison active", async () => {
mockDate("2023-04-28T13:40:00");
onRpc("/web/domain/validate", () => true);
await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: ["filter", "comparison"],
searchViewId: false,
searchViewArch: `
<search>
<filter name="birthday" string="Birthday" date="birthday">
<filter name="birthday_today" string="Today" domain="[('birthday', '=', context_today().strftime('%Y-%m-%d'))]"/>
</filter>
</search>
`,
context: {
search_default_birthday: true,
},
});
await toggleSearchBarMenu();
await toggleMenuItem("Birthday: Previous Period");
expect(getFacetTexts()).toEqual(["Birthday: April 2023", "Birthday: Previous Period"]);
await toggleMenuItem("Birthday");
await toggleMenuItemOption("Birthday", "Today");
expect(getFacetTexts()).toEqual(["Birthday: Today"]);
await toggleSearchBarMenu();
expect(`.o_dropdown_container.o_comparison_menu`).toHaveCount(0);
});
test("edit a field", async () => {
onRpc("/web/domain/validate", () => true);
await mountWithSearch(SearchBar, {
resModel: "partner",
searchViewId: false,
searchViewArch: `
<search>
<field name="foo"/>
</search>
`,
context: {
search_default_foo: "abc",
},
});
expect(getFacetTexts()).toEqual(["Foo\nabc"]);
expect(`.o_searchview_facet.o_facet_with_domain .o_searchview_facet_label`).toHaveCount(1);
await editSearch("def");
await keyDown("Enter"); // select
await animationFrame();
expect(getFacetTexts()).toEqual(["Foo\nabc\nor\ndef"]);
await contains(".o_facet_with_domain .o_searchview_facet_label").click();
expect(SELECTORS.condition).toHaveCount(2);
expect(getCurrentPath(0)).toBe("Foo");
expect(getCurrentOperator(0)).toBe("contains");
expect(getCurrentValue(0)).toBe("abc");
expect(getCurrentPath(1)).toBe("Foo");
expect(getCurrentOperator(1)).toBe("contains");
expect(getCurrentValue(1)).toBe("def");
await contains(".modal footer button").click();
expect(getFacetTexts()).toEqual([`Foo contains abc or Foo contains def`]);
});
test("no rpc for getting display_name for facets if known", async () => {
onRpc("/web/domain/validate", () => true);
onRpc("name_search", ({ kwargs }) => {
expect.step(kwargs.args /** domain */);
});
onRpc(({ method }) => expect.step(method));
await mountWithSearch(SearchBar, {
resModel: "partner",
searchViewId: false,
searchViewArch: `
<search>
<filter name="filter" string="Filter" domain="[('bar', 'in', [])]"/>
</search>
`,
context: {
search_default_filter: true,
},
});
expect(getFacetTexts()).toEqual(["Filter"]);
expect.verifySteps(["get_views"]);
await contains(".o_facet_with_domain .o_searchview_facet_label").click();
expect.verifySteps(["fields_get"]);
await contains(".o-autocomplete--input").click();
expect.verifySteps(["name_search", ["!", ["id", "in", []]]]);
await contains(".dropdown-menu li").click();
await contains(".modal footer button").click();
expect(getFacetTexts()).toEqual(["Bar is in ( First record )"]);
});
test.tags`desktop`("clicking on search input trigger the search menu", async () => {
await mountWithSearch(SearchBar, {
resModel: "partner",
});
await contains(`.o_searchview_input`).click();
expect(`.o_search_bar_menu`).toHaveCount(1);
});
test("clicking on the searchview icon trigger the search", async () => {
await mountWithSearch(SearchBar, {
resModel: "partner",
searchViewId: false,
});
await editSearch("a");
await contains(`.o_searchview button`).click();
expect(`.o_searchview_input_container .o_facet_values`).toHaveText("a");
});
test("facets display with any / not any operator", async function () {
onRpc(({ method }) => expect.step(method));
onRpc("/web/domain/validate", () => {
expect.step("/web/domain/validate");
return true;
});
await mountWithSearch(SearchBar, {
resModel: "partner",
searchViewId: false,
searchViewArch: `
<search>
<filter name="filter" string="Filter" domain="[('company', 'any', [('bar', 'any', [('company', 'in', ['JD7', 'KDB'])])])]"/>
</search>
`,
context: {
search_default_filter: true,
},
});
expect(getFacetTexts()).toEqual(["Filter"]);
expect.verifySteps([`get_views`]);
await contains(".o_facet_with_domain .o_searchview_facet_label").click();
expect.verifySteps([`fields_get`]);
await contains(".modal footer button").click();
expect(getFacetTexts()).toEqual([
"Company matches ( Bar matches ( Company is in ( JD7 , KDB ) ) )",
]);
expect.verifySteps([`/web/domain/validate`]);
});
test("facets display with any / not any operator (with a complex path)", async function () {
onRpc(({ method }) => expect.step(method));
onRpc("/web/domain/validate", () => {
expect.step("/web/domain/validate");
return true;
});
await mountWithSearch(SearchBar, {
resModel: "partner",
searchViewId: false,
searchViewArch: `
<search>
<filter name="filter" string="Filter" domain="['|', ('company.company', 'any', [('id', '=', 1)]), ('Bar', '=', false)]"/>
</search>
`,
context: {
search_default_filter: true,
},
});
expect(getFacetTexts()).toEqual(["Filter"]);
expect.verifySteps([`get_views`]);
await contains(".o_facet_with_domain .o_searchview_facet_label").click();
expect.verifySteps([`fields_get`]);
await contains(".modal footer button").click();
expect(getFacetTexts()).toEqual(["Company ➔ Company matches ( Id = 1 ) or Bar = false"]);
expect.verifySteps([`/web/domain/validate`]);
});
test("facets display with any / not any operator (with a or)", async function () {
onRpc(({ method }) => expect.step(method));
onRpc("/web/domain/validate", () => {
expect.step("/web/domain/validate");
return true;
});
await mountWithSearch(SearchBar, {
resModel: "partner",
searchViewId: false,
searchViewArch: `
<search>
<filter name="filter" string="Filter" domain="['|', ('company', 'any', [('id', '=', 1)]), ('bar', '=', false)]"/>
</search>
`,
context: {
search_default_filter: true,
},
});
expect(getFacetTexts()).toEqual(["Filter"]);
expect.verifySteps([`get_views`]);
await contains(".o_facet_with_domain .o_searchview_facet_label").click();
expect.verifySteps([`fields_get`]);
await contains(".modal footer button").click();
expect(getFacetTexts()).toEqual(["Company matches ( Id = 1 ) or Bar = false"]);
expect.verifySteps([`/web/domain/validate`]);
});
test("facets display with any / not any operator (check brackets)", async function () {
onRpc(({ method }) => expect.step(method));
onRpc("/web/domain/validate", () => {
expect.step("/web/domain/validate");
return true;
});
await mountWithSearch(SearchBar, {
resModel: "partner",
searchViewId: false,
searchViewArch: `
<search>
<filter isDebugMode="true" name="filter" string="Filter" domain="['|', ('company', 'any', [('bar', 'any', [('bool', 'is', False)]), ('bar', 'any', [('bool', 'is', True)])]), ('bar', '=', false)]"/>
</search>
`,
context: {
search_default_filter: true,
},
});
expect(getFacetTexts()).toEqual(["Filter"]);
expect.verifySteps([`get_views`]);
await contains(".o_facet_with_domain .o_searchview_facet_label").click();
expect.verifySteps([`fields_get`]);
await contains(".modal footer button").click();
expect(getFacetTexts()).toEqual([
"Company matches ( Bar matches ( Bool is not set ) and Bar matches ( Bool is set ) ) or Bar = false",
]);
expect.verifySteps([`/web/domain/validate`]);
});
test("select autocompleted many2one with allowed_company_ids domain", async () => {
// allowed_company_ids is initially set by the company_service
serverState.companies = [
...serverState.companies,
// company_service only includes existing companies from cids
{
id: 5,
name: "Hierophant",
},
];
browser.location.search = "cids=1,5";
await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
searchViewArch: `
<search>
<field name="bar" domain="[('company', 'in', allowed_company_ids)]"/>
</search>
`,
});
await editSearch("rec");
await contains(`.o_expand`).click();
expect(queryAllTexts(`.o_searchview_input_container li`)).toEqual([
"Search Bar for: rec",
"Second record",
"Third record",
"Add Custom Filter",
]);
serverState.userContext = { allowed_company_ids: [1] };
await editSearch("rec");
await contains(`.o_expand`).click();
expect(queryAllTexts(`.o_searchview_input_container li`)).toEqual([
"Search Bar for: rec",
"Second record",
"Add Custom Filter",
]);
});
test("dropdown menu last element is 'Add Custom Filter'", async () => {
await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
searchViewArch: `
<search>
<field name="foo"/>
</search>
`,
});
await editSearch("a");
await animationFrame();
const dropdownMenu = queryFirst(".o_searchview_autocomplete");
const lastElement = dropdownMenu.querySelector("li:last-child");
expect(lastElement.textContent.trim()).toBe("Add Custom Filter");
});
test("order by count resets when there is no group left", async () => {
const searchBar = await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: ["groupBy", "filter"],
searchViewId: false,
searchViewArch: `
<search>
<filter string="Foo" name="foo" domain="[('foo', '=', 'qsdf')]"/>
</search>
`,
});
searchBar.env.searchModel.canOrderByCount = true;
await toggleSearchBarMenu();
await selectGroup("bool");
await selectGroup("bar");
await toggleMenuItem("Foo");
expect(".fa-sort").toHaveCount(1);
await contains(".fa-sort", { visible: false }).click();
expect(".fa-sort-numeric-desc").toHaveCount(1);
await contains(".fa-sort-numeric-desc").click();
expect(".fa-sort-numeric-asc").toHaveCount(1);
await toggleSearchBarMenu();
await toggleMenuItem("Foo");
expect(".fa-sort-numeric-asc").toHaveCount(1);
await toggleMenuItem("Foo");
await toggleMenuItem("Bool");
expect(".fa-sort-numeric-asc").toHaveCount(1);
await toggleMenuItem("Bar");
expect(".fa-sort-numeric-asc").toHaveCount(0);
await toggleMenuItem("Bar");
expect(".fa-sort-numeric-asc").toHaveCount(0);
expect(".fa-sort").toHaveCount(1);
await contains(".fa-sort", { visible: false }).click();
await contains(".fa-sort-numeric-desc").click();
expect(".fa-sort-numeric-asc").toHaveCount(1);
await toggleSearchBarMenu();
await toggleMenuItem("Bool");
expect(".fa-sort-numeric-asc").toHaveCount(1);
await contains(".o_facet_remove").click();
expect(".fa-sort-numeric-asc").toHaveCount(1);
await contains(".o_facet_remove").click();
expect(".o_searchview_facet").toHaveCount(0);
await toggleSearchBarMenu();
await toggleMenuItem("Bar");
expect(".fa-sort-numeric-asc").toHaveCount(0);
expect(".fa-sort").toHaveCount(1);
});
test("quoted search term performs an exact match search", async () => {
const searchBar = await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
});
await editSearch(`"yop"`);
await keyDown("Enter");
await animationFrame();
expect(searchBar.env.searchModel._domain).toEqual([["foo", "=", "yop"]]);
});
test(`quoted search term performs an exact match search on view defined's "filter_domain"`, async () => {
const searchBar = await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
searchViewArch: `
<search>
<field string="Foo" name="foo" filter_domain="[('name', 'ilike', self)]"/>
</search>
`,
});
await editSearch(`"Second record"`);
await keyDown("Enter");
await animationFrame();
expect(searchBar.env.searchModel._domain).toEqual([["name", "=", "Second record"]]);
});
test(`quoted search term performs a name_search with operator = for subitems`, async () => {
await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
searchViewArch: `
<search>
<field string="Company" name="company"/>
</search>
`,
});
await editSearch(`"First"`);
await contains(".o_expand").click();
expect(".o_searchview_autocomplete li.o_menu_item.o_indent").toHaveText("(no result)");
await editSearch(`"First record"`);
await contains(".o_expand").click();
expect(".o_searchview_autocomplete li.o_menu_item.o_indent").toHaveText("First record");
});
test("subitems have a load more item if there is more records available", async () => {
for (let i = 0; i < 20; i++) {
Partner._records.push({
id: 100 + i,
name: `Home Depot ${i}`,
});
}
await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
searchViewArch: `
<search>
<field name="company"/>
</search>
`,
});
await editSearch("Home");
await contains(".o_expand").click();
await expect(".o_searchview_autocomplete li.o_menu_item.o_indent").toHaveCount(8 + 1);
await expect(".o_searchview_autocomplete li.o_menu_item.o_indent:last").toHaveText("Load more");
await contains(".o_searchview_autocomplete li.o_menu_item.o_indent:last").click();
await expect(".o_searchview_autocomplete li.o_menu_item.o_indent").toHaveCount(8 + 8 + 1);
await expect(".o_searchview_autocomplete li.o_menu_item.o_indent:last").toHaveText("Load more");
});
test("subitems do not have a load more item if there is no more records available", async () => {
await mountWithSearch(SearchBar, {
resModel: "partner",
searchMenuTypes: [],
searchViewId: false,
searchViewArch: `
<search>
<field name="company"/>
</search>
`,
});
await editSearch("home");
await contains(".o_expand").click();
await expect(".o_searchview_autocomplete li.o_menu_item.o_indent").toHaveCount(1);
await expect(".o_searchview_autocomplete li.o_menu_item.o_indent").toHaveText("(no result)");
});