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: ``, 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`
`; 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 */ ` `, }); // Create an input outside of the search panel to simulate another input outside of the search panel await mountWithCleanup(/* xml */ ``); 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``; 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``; 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: ` `, }); 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: ` `, }); 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: ` `, }); 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: ` `, }); 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: ` `, }); 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: ` `, }); 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: ` `, }); 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: ` `, }); 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: ` `, }); 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*/ ` `, }); 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: ` `, 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: ` `, }); // 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: ` `, 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: ` `, 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: ` `, 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: ` `, 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: ` `, 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: ` `, 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: ` `, 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: ` `, 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: ` `, 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: ` `, 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: ` `, 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: ` `, 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: ` `, 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: ` `, }); 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: ` `, }); 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: ` `, }); 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: ` `, }); 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: ` `, }); 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: ` `, }); 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: ` `, }); 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)"); });