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

2986 lines
104 KiB
JavaScript

import { describe, expect, test } from "@odoo/hoot";
import { drag, queryAll, queryAllTexts, queryFirst, scroll } from "@odoo/hoot-dom";
import { Deferred, animationFrame } from "@odoo/hoot-mock";
import { Component, onWillUpdateProps, xml } from "@odoo/owl";
import {
contains,
defineActions,
defineModels,
defineParams,
fields,
getService,
models,
mountWithCleanup,
mountWithSearch,
onRpc,
toggleMenuItem,
toggleSearchBarMenu,
} from "@web/../tests/web_test_helpers";
import { SearchBarMenu } from "@web/search/search_bar_menu/search_bar_menu";
import { SearchPanel } from "@web/search/search_panel/search_panel";
import { WebClient } from "@web/webclient/webclient";
function parseContent(text) {
const [value, counter] = text.split(/\s+/);
return counter ? `${value}: ${counter}` : value;
}
function parseCounter(text) {
const [, counter] = text.split(/\s+/);
return isNaN(counter) ? null : Number(counter);
}
function getCategoriesContent() {
return queryAllTexts`.o_search_panel_category_value header`.map(parseContent).filter(Boolean);
}
function getCategoriesCounter() {
return queryAllTexts`.o_search_panel_category_value header`.map(parseCounter).filter(Boolean);
}
function getFiltersContent() {
return queryAllTexts`.o_search_panel_filter_value`.map(parseContent).filter(Boolean);
}
function getFiltersCounter() {
return queryAllTexts`.o_search_panel_filter_value`.map(parseCounter).filter(Boolean);
}
class TestComponent extends Component {
static components = { SearchBarMenu, SearchPanel };
static template = xml`
<div class="o_test_component">
<SearchPanel t-if="env.searchModel.display.searchPanel" />
<SearchBarMenu />
</div>
`;
static props = ["*"];
setup() {
this.domain = this.props.domain;
onWillUpdateProps((np) => this.willUpdateProps(np));
}
async willUpdateProps(np) {
this.domain = np.domain;
}
}
class Partner extends models.Model {
name = fields.Char();
foo = fields.Char();
bar = fields.Boolean();
int_field = fields.Integer({ string: "Int Field", aggregator: "sum" });
company_id = fields.Many2one({ string: "res.company", relation: "res.company" });
company_ids = fields.Many2many({ string: "Companies", relation: "res.company" });
category_id = fields.Many2one({ string: "category", relation: "category" });
state = fields.Selection({
selection: [
["abc", "ABC"],
["def", "DEF"],
["ghi", "GHI"],
],
});
_records = [
{
id: 1,
bar: true,
foo: "yop",
int_field: 1,
company_ids: [3],
company_id: 3,
state: "abc",
category_id: 6,
},
{
id: 2,
bar: true,
foo: "blip",
int_field: 2,
company_ids: [3],
company_id: 5,
state: "def",
category_id: 7,
},
{
id: 3,
bar: true,
foo: "gnap",
int_field: 4,
company_ids: [],
company_id: 3,
state: "ghi",
category_id: 7,
},
{
id: 4,
bar: false,
foo: "blip",
int_field: 8,
company_ids: [5],
company_id: 5,
state: "ghi",
category_id: 7,
},
];
_views = {
toy: /* xml */ `<toy/>`,
list: /* xml */ `<list><field name="foo"/></list>`,
kanban: /* xml */ `
<kanban>
<templates>
<div t-name="card">
<field name="foo"/>
</div>
</templates>
</kanban>
`,
form: /* xml */ `
<form>
<button name="1" type="action" string="multi view"/>
<field name="foo"/>
<field name="company_id"/>
</form>
`,
pivot: /* xml */ `<pivot><field name="int_field" type="measure"/></pivot>`,
search: /* xml */ `
<search>
<filter name="false_domain" string="False Domain" domain="[(0, '=', 1)]"/>
<filter name="filter" string="Filter" domain="[('bar', '=', true)]"/>
<filter name="true_domain" string="True Domain" domain="[(1, '=', 1)]"/>
<filter name="group_by_bar" string="Bar" context="{ 'group_by': 'bar' }"/>
<searchpanel view_types="kanban,list,toy">
<field name="company_id" enable_counters="1" expand="1"/>
<field name="category_id" select="multi" enable_counters="1" expand="1"/>
</searchpanel>
</search>
`,
};
}
class Company extends models.Model {
_name = "res.company";
name = fields.Char();
parent_id = fields.Many2one({ string: "Parent company", relation: "res.company" });
category_id = fields.Many2one({ string: "Category", relation: "category" });
_records = [
{ id: 3, name: "asustek", category_id: 6 },
{ id: 5, name: "agrolait", category_id: 7 },
];
}
class Category extends models.Model {
name = fields.Char({ string: "Category Name" });
_records = [
{ id: 6, name: "gold" },
{ id: 7, name: "silver" },
];
}
defineModels([Partner, Company, Category]);
defineActions([
{
id: 1,
name: "Partners",
res_model: "partner",
type: "ir.actions.act_window",
views: [
[false, "kanban"],
[false, "list"],
[false, "pivot"],
[false, "form"],
],
},
{
id: 2,
name: "Partners",
res_model: "partner",
type: "ir.actions.act_window",
views: [[false, "form"]],
},
]);
describe.current.tags("desktop");
test("basic rendering of a component without search panel", async () => {
onRpc(/search_panel_/, () => {
throw new Error("No search panel section should be loaded");
});
const component = await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
display: { searchPanel: false },
});
expect(`.o_search_panel`).toHaveCount(0);
expect(component.domain).toEqual([]); // initial domain
});
test("basic rendering of a component with empty search panel", async () => {
Partner._views = {
search: `<search><searchpanel/></search>`,
};
onRpc(/search_panel_/, () => {
throw new Error("should not step here");
});
const component = await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel`).toHaveCount(0);
expect(component.domain).toEqual([]); // initial domain
expect.verifySteps([]);
});
test("basic rendering of a component with search panel", async () => {
onRpc("partner", /search_panel_/, ({ method }) => expect.step(method));
const component = await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel`).toHaveCount(1);
expect(`.o_search_panel_section`).toHaveCount(2);
const firstSection = `.o_search_panel_section:eq(0)`;
expect(`${firstSection} .o_search_panel_section_header i`).toHaveClass("fa-folder");
expect(`${firstSection} .o_search_panel_section_header`).toHaveText(/company/i);
expect(`${firstSection} .o_search_panel_category_value`).toHaveCount(3);
expect(`${firstSection} .o_search_panel_category_value:first .active`).toHaveCount(1);
expect(queryAllTexts`${firstSection} .o_search_panel_category_value`).toEqual([
"All",
"asustek\n2",
"agrolait\n2",
]);
const secondSection = `.o_search_panel_section:eq(1)`;
expect(`${secondSection} .o_search_panel_section_header i`).toHaveClass("fa-filter");
expect(`${secondSection} .o_search_panel_section_header`).toHaveText(/category/i);
expect(`${secondSection} .o_search_panel_filter_value`).toHaveCount(2);
expect(queryAllTexts`${secondSection} .o_search_panel_filter_value`).toEqual([
"gold\n1",
"silver\n3",
]);
expect.verifySteps(["search_panel_select_range", "search_panel_select_multi_range"]);
expect(component.domain).toEqual([]); // initial domain (does not need the sections to be loaded)
});
test("sections with custom icon and color", async () => {
Partner._views = {
search: /* xml */ `
<search>
<searchpanel view_types="toy">
<field name="company_id" icon="fa-car" color="blue" enable_counters="1"/>
<field name="state" select="multi" icon="fa-star" color="#000" enable_counters="1"/>
</searchpanel>
</search>
`,
};
const component = await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_section_header:eq(0) i`).toHaveClass("fa-car");
expect(`.o_search_panel_section_header:eq(0) i`).toHaveStyle({ color: "rgb(0, 0, 255)" });
expect(`.o_search_panel_section_header:eq(1) i`).toHaveClass("fa-star");
expect(`.o_search_panel_section_header:eq(1) i`).toHaveStyle({ color: "rgb(0, 0, 0)" });
expect(component.domain).toEqual([]);
});
test(`sections with attr invisible="1" are ignored`, async () => {
// 'groups' attributes are converted server-side into invisible="1" when the user doesn't
// belong to the given group
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" enable_counters="1"/>
<field name="state" select="multi" invisible="1" enable_counters="1"/>
</searchpanel>
</search>
`,
};
onRpc(/search_panel_/, ({ method }) => expect.step(method));
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_section`).toHaveCount(1);
expect.verifySteps(["search_panel_select_range"]);
});
test("categories and filters order is kept", async () => {
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" enable_counters="1"/>
<field name="category_id" select="multi" enable_counters="1"/>
<field name="state" enable_counters="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_section`).toHaveCount(3);
expect(queryAllTexts`.o_search_panel_section_header`).toEqual([
"RES.COMPANY",
"CATEGORY",
"STATE",
]);
});
test("specify active category value in context and manually change category", async () => {
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" enable_counters="1"/>
<field name="state" enable_counters="1"/>
</searchpanel>
</search>
`,
};
const component = await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
context: {
searchpanel_default_company_id: false,
searchpanel_default_state: "ghi",
},
});
expect(
queryAllTexts`.o_search_panel_category_value header.active .o_search_panel_label`
).toEqual(["All", "GHI"]);
expect(component.domain).toEqual([["state", "=", "ghi"]]);
// select 'ABC' in the category 'state'
await contains(queryAll`.o_search_panel_category_value header`[4]).click();
expect(
queryAllTexts`.o_search_panel_category_value header.active .o_search_panel_label`
).toEqual(["All", "ABC"]);
expect(component.domain).toEqual([["state", "=", "abc"]]);
});
test("use category (on many2one) to refine search", async () => {
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" enable_counters="1"/>
</searchpanel>
</search>
`,
};
const component = await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
domain: [["bar", "=", true]],
context: {
searchpanel_default_company_id: false,
searchpanel_default_state: "ghi",
},
});
expect(component.domain).toEqual([["bar", "=", true]]);
// select "asustek"
await contains(queryAll`.o_search_panel_category_value header`[1]).click();
expect(`.o_search_panel_category_value .active`).toHaveCount(1);
expect(`.o_search_panel_category_value:eq(1) .active`).toHaveCount(1);
expect(component.domain).toEqual(["&", ["bar", "=", true], ["company_id", "child_of", 3]]);
// select "agrolait"
await contains(queryAll`.o_search_panel_category_value header`[2]).click();
expect(`.o_search_panel_category_value .active`).toHaveCount(1);
expect(`.o_search_panel_category_value:eq(2) .active`).toHaveCount(1);
expect(component.domain).toEqual(["&", ["bar", "=", true], ["company_id", "child_of", 5]]);
// select "All"
await contains(queryAll`.o_search_panel_category_value header`[0]).click();
expect(`.o_search_panel_category_value .active`).toHaveCount(1);
expect(`.o_search_panel_category_value:first .active`).toHaveCount(1);
expect(component.domain).toEqual([["bar", "=", true]]);
});
test("use category (on selection) to refine search", async () => {
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="state" enable_counters="1"/>
</searchpanel>
</search>
`,
};
const component = await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(component.domain).toEqual([]);
// select 'abc'
await contains(`.o_search_panel_category_value:nth-of-type(2) header`).click();
expect(`.o_search_panel_category_value .active`).toHaveCount(1);
expect(`.o_search_panel_category_value:nth-of-type(2) .active`).toHaveCount(1);
expect(component.domain).toEqual([["state", "=", "abc"]]);
// select 'ghi'
await contains(`.o_search_panel_category_value:nth-of-type(4) header`).click();
expect(`.o_search_panel_category_value .active`).toHaveCount(1);
expect(`.o_search_panel_category_value:nth-of-type(4) .active`).toHaveCount(1);
expect(component.domain).toEqual([["state", "=", "ghi"]]);
// select 'All' again
await contains(`.o_search_panel_category_value:nth-of-type(1) header`).click();
expect(`.o_search_panel_category_value:nth-of-type(1) .active`).toHaveCount(1);
expect(`.o_search_panel_category_value:first .active`).toHaveCount(1);
expect(component.domain).toEqual([]);
});
test("category has been archived", async () => {
Company._fields.active = fields.Boolean({ string: "Archived" });
Company._records = [
{ id: 3, name: "asustek" },
{
name: "Company 5",
id: 5,
active: true,
},
{
name: "child of 5 archived",
parent_id: 5,
id: 666,
active: false,
},
{
name: "child of 666",
parent_id: 666,
id: 777,
active: true,
},
];
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" enable_counters="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_category_value`).toHaveCount(2);
expect(`.o_toggle_fold > i`).toHaveCount(0);
});
test("use two categories to refine search", async () => {
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" enable_counters="1"/>
<field name="state" enable_counters="1"/>
</searchpanel>
</search>
`,
};
const component = await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
domain: [["bar", "=", true]],
});
expect(component.domain).toEqual([["bar", "=", true]]);
expect(`.o_search_panel_section`).toHaveCount(2);
// select 'asustek'
await contains(
`.o_search_panel_category_value header .o_search_panel_label_title:contains(asustek)`
).click();
expect(component.domain).toEqual(["&", ["bar", "=", true], ["company_id", "child_of", 3]]);
// select 'abc'
await contains(
`.o_search_panel_category_value header .o_search_panel_label_title:contains(abc)`
).click();
expect(component.domain).toEqual([
"&",
["bar", "=", true],
"&",
["company_id", "child_of", 3],
["state", "=", "abc"],
]);
// select 'ghi'
await contains(
`.o_search_panel_category_value header .o_search_panel_label_title:contains(ghi)`
).click();
expect(component.domain).toEqual([
"&",
["bar", "=", true],
"&",
["company_id", "child_of", 3],
["state", "=", "ghi"],
]);
// select 'All' in first category (company_id)
await contains(`.o_search_panel_section:eq(0) .o_search_panel_category_value header`).click();
expect(component.domain).toEqual(["&", ["bar", "=", true], ["state", "=", "ghi"]]);
// select 'All' in second category (state)
await contains(`.o_search_panel_section:eq(1) .o_search_panel_category_value header`).click();
expect(component.domain).toEqual([["bar", "=", true]]);
});
test("category with parent_field", async () => {
Company._records.push(
{ id: 40, name: "child company 1", parent_id: 5 },
{ id: 41, name: "child company 2", parent_id: 5 }
);
Partner._records[1].company_id = 40;
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" enable_counters="1" expand="1"/>
</searchpanel>
</search>
`,
};
const component = await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
// 'All' is selected by default
expect(`.o_search_panel_category_value .active`).toHaveCount(1);
expect(`.o_search_panel_category_value:first .active`).toHaveCount(1);
expect(`.o_search_panel_category_value`).toHaveCount(3);
expect(`.o_search_panel_category_value .o_toggle_fold > i`).toHaveCount(1);
// unfold parent category and select 'All' again
await contains(`.o_search_panel_category_value header:eq(2)`).click();
await contains(`.o_search_panel_category_value header:eq(0)`).click();
expect(`.o_search_panel_category_value .active`).toHaveCount(1);
expect(`.o_search_panel_category_value:first .active`).toHaveCount(1);
expect(`.o_search_panel_category_value`).toHaveCount(5);
expect(`.o_search_panel_category_value .o_search_panel_category_value`).toHaveCount(2);
expect(component.domain).toEqual([]);
// click on first child company
await contains(`.o_search_panel_category_value header:eq(3)`).click();
expect(`.o_search_panel_category_value .active`).toHaveCount(1);
expect(
`.o_search_panel_category_value .o_search_panel_category_value:first .active`
).toHaveCount(1);
expect(component.domain).toEqual([["company_id", "child_of", 40]]);
// click on parent company
await contains(`.o_search_panel_category_value header:eq(2)`).click();
expect(`.o_search_panel_category_value .active`).toHaveCount(1);
expect(`.o_search_panel_category_value:eq(2) .active`).toHaveCount(1);
expect(component.domain).toEqual([["company_id", "child_of", 5]]);
// fold parent company by clicking on it
await contains(`.o_search_panel_category_value header:eq(2)`).click();
expect(`.o_search_panel_category_value .active`).toHaveCount(1);
expect(`.o_search_panel_category_value:eq(2) .active`).toHaveCount(1);
// parent company should be folded
expect(`.o_search_panel_category_value .active`).toHaveCount(1);
expect(`.o_search_panel_category_value:eq(2) .active`).toHaveCount(1);
expect(`.o_search_panel_category_value`).toHaveCount(3);
expect(component.domain).toEqual([["company_id", "child_of", 5]]);
// fold category with children
await contains(`.o_search_panel_category_value header:eq(2)`).click();
await contains(`.o_search_panel_category_value header:eq(2)`).click();
expect(`.o_search_panel_category_value .active`).toHaveCount(1);
expect(`.o_search_panel_category_value:eq(2) .active`).toHaveCount(1);
expect(`.o_search_panel_category_value`).toHaveCount(3);
expect(component.domain).toEqual([["company_id", "child_of", 5]]);
});
test("category with no parent_field", async () => {
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="category_id" enable_counters="1"/>
</searchpanel>
</search>
`,
};
const component = await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(component.domain).toEqual([]);
// 'All' is selected by default
expect(`.o_search_panel_category_value .active`).toHaveCount(1);
expect(`.o_search_panel_category_value:first .active`).toHaveCount(1);
expect(`.o_search_panel_category_value`).toHaveCount(3);
// click on 'gold' category
await contains(queryAll`.o_search_panel_category_value header`[1]).click();
expect(`.o_search_panel_category_value .active`).toHaveCount(1);
expect(`.o_search_panel_category_value:eq(1) .active`).toHaveCount(1);
expect(component.domain).toEqual([["category_id", "=", 6]]); // must use '=' operator (instead of 'child_of')
});
test("can (un)fold parent category values", async () => {
Company._records.push(
{ id: 40, name: "child company 1", parent_id: 5 },
{ id: 41, name: "child company 2", parent_id: 5 }
);
Partner._records[1].company_id = 40;
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" enable_counters="1" expand="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_category_value:contains(agrolait) .o_toggle_fold > i`).toHaveCount(1);
expect(
`.o_search_panel_category_value header:contains(agrolait) .o_toggle_fold > i`
).toHaveClass("fa-caret-right");
expect(`.o_search_panel_category_value`).toHaveCount(3);
// unfold agrolait
await contains(
`.o_search_panel_category_value header:contains(agrolait) .o_toggle_fold > i`
).click();
expect(
`.o_search_panel_category_value header:contains(agrolait) .o_toggle_fold > i`
).toHaveClass("fa-caret-down");
expect(`.o_search_panel_category_value`).toHaveCount(5);
// fold agrolait
await contains(
`.o_search_panel_category_value header:contains(agrolait) .o_toggle_fold > i`
).click();
expect(
`.o_search_panel_category_value header:contains(agrolait) .o_toggle_fold > i`
).toHaveClass("fa-caret-right");
expect(`.o_search_panel_category_value`).toHaveCount(3);
});
test("fold status is kept at reload", async () => {
Company._records.push(
{ id: 40, name: "child company 1", parent_id: 5 },
{ id: 41, name: "child company 2", parent_id: 5 }
);
Partner._records[1].company_id = 40;
Partner._views = {
search: /* xml */ `
<search>
<filter name="True Domain" domain="[(1, '=', 1)]"/>
<searchpanel>
<field name="company_id" enable_counters="1" expand="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
// unfold agrolait
await contains(queryFirst`.o_search_panel_category_value > header:contains(agrolait)`).click();
expect(
queryFirst`.o_search_panel_category_value > header:contains(agrolait) .o_toggle_fold > i`
).toHaveClass("fa-caret-down");
expect(`.o_search_panel_category_value`).toHaveCount(5);
await toggleSearchBarMenu();
await toggleMenuItem("True Domain");
expect(
queryFirst`.o_search_panel_category_value > header:contains(agrolait) .o_toggle_fold > i`
).toHaveClass("fa-caret-down");
expect(`.o_search_panel_category_value`).toHaveCount(5);
});
test("concurrency: delayed component update", async () => {
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" enable_counters="1"/>
</searchpanel>
</search>
`,
};
let promise = new Deferred();
class DeferredTestComponent extends TestComponent {
async willUpdateProps(np) {
await promise;
super.willUpdateProps(np);
}
}
const component = await mountWithSearch(DeferredTestComponent, {
resModel: "partner",
searchViewId: false,
domain: [["bar", "=", true]],
});
// 'All' should be selected by default
expect(`.o_search_panel_category_value .active`).toHaveCount(1);
expect(`.o_search_panel_category_value:first .active`).toHaveCount(1);
expect(component.domain).toEqual([["bar", "=", true]]);
// select 'asustek' (delay the reload)
const asustekPromise = promise;
await contains(`.o_search_panel_category_value:eq(1) header`).click();
// 'asustek' should not be selected yet, and there should still be 3 records
expect(`.o_search_panel_category_value .active`).toHaveCount(1);
expect(`.o_search_panel_category_value:first .active`).toHaveCount(1);
expect(component.domain).toEqual([["bar", "=", true]]);
// select 'agrolait' (delay the reload)
promise = new Deferred();
const agrolaitPromise = promise;
await contains(`.o_search_panel_category_value:eq(2) header`).click();
// 'agrolait' should not be selected yet, and there should still be 3 records
expect(`.o_search_panel_category_value .active`).toHaveCount(1);
expect(`.o_search_panel_category_value:first .active`).toHaveCount(1);
expect(component.domain).toEqual([["bar", "=", true]]);
// unlock asustek search (should be ignored, so there should still be 3 records)
asustekPromise.resolve();
await animationFrame();
expect(`.o_search_panel_category_value .active`).toHaveCount(1);
expect(`.o_search_panel_category_value:first .active`).toHaveCount(1);
expect(component.domain).toEqual(["&", ["bar", "=", true], ["company_id", "child_of", 3]]);
// unlock agrolait search, there should now be 1 record
agrolaitPromise.resolve();
await animationFrame();
expect(`.o_search_panel_category_value .active`).toHaveCount(1);
expect(`.o_search_panel_category_value:eq(2) .active`).toHaveCount(1);
expect(component.domain).toEqual(["&", ["bar", "=", true], ["company_id", "child_of", 5]]);
});
test("concurrency: single category", async () => {
Partner._views = {
search: /* xml */ `
<search>
<filter name="Filter" domain="[('id', '=', 1)]"/>
<searchpanel>
<field name="company_id" enable_counters="1"/>
</searchpanel>
</search>
`,
};
let promise = new Deferred();
onRpc(async ({ method }) => {
await promise;
expect.step(method);
});
const compPromise = mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
context: {
searchpanel_default_company_id: [5],
},
});
// Case 1: search panel is awaited to build the query with search defaults
await animationFrame();
expect.verifySteps([]);
promise.resolve();
await compPromise;
expect.verifySteps(["get_views", "search_panel_select_range"]);
// Case 2: search domain changed so we wait for the search panel once again
promise = new Deferred();
await toggleSearchBarMenu();
await toggleMenuItem("Filter");
expect.verifySteps([]);
promise.resolve();
await animationFrame();
expect.verifySteps(["search_panel_select_range"]);
// Case 3: search domain is the same and default values do not matter anymore
promise = new Deferred();
await contains(`.o_search_panel_category_value header:eq(1)`).click();
// The search read is executed right away in this case
expect.verifySteps([]);
promise.resolve();
await animationFrame();
expect.verifySteps(["search_panel_select_range"]);
});
test("concurrency: category and filter", async () => {
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="category_id" enable_counters="1"/>
<field name="company_id" select="multi" enable_counters="1"/>
</searchpanel>
</search>
`,
};
const promise = new Deferred();
onRpc(async ({ method }) => {
await promise;
expect.step(method);
});
const compPromise = mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
context: {
searchpanel_default_company_id: [5],
},
});
await animationFrame();
expect.verifySteps([]);
promise.resolve();
await compPromise;
expect.verifySteps([
"get_views",
"search_panel_select_range",
"search_panel_select_multi_range",
]);
});
test("concurrency: category and filter with a domain", async () => {
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="category_id"/>
<field name="company_id" select="multi" domain="[['category_id', '=', category_id]]" enable_counters="1"/>
</searchpanel>
</search>
`,
};
const promise = new Deferred();
onRpc(async ({ method }) => {
await promise;
expect.step(method);
});
const compPromise = mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
await animationFrame();
expect.verifySteps([]);
promise.resolve();
await compPromise;
expect.verifySteps([
"get_views",
"search_panel_select_range",
"search_panel_select_multi_range",
]);
});
test("concurrency: misordered get_filters", async () => {
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="state" enable_counters="1"/>
<field name="company_id" select="multi" enable_counters="1"/>
</searchpanel>
</search>
`,
};
let promise;
onRpc("search_panel_select_multi_range", () => promise);
const component = await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_category_value .active`).toHaveCount(1);
expect(`.o_search_panel_category_value:first .active`).toHaveCount(1);
expect(component.domain).toEqual([]);
// select 'abc' (delay the reload)
promise = new Deferred();
const abcDef = promise;
await contains(`.o_search_panel_category_value header:eq(1)`).click();
// 'All' should still be selected
expect(`.o_search_panel_category_value .active`).toHaveCount(1);
expect(`.o_search_panel_category_value:first .active`).toHaveCount(1);
expect(component.domain).toEqual([["state", "=", "abc"]]);
// select 'ghi' (delay the reload)
promise = new Deferred();
const ghiDef = promise;
await contains(`.o_search_panel_category_value header:eq(3)`).click();
// 'All' should still be selected
expect(`.o_search_panel_category_value .active`).toHaveCount(1);
expect(`.o_search_panel_category_value:first .active`).toHaveCount(1);
expect(component.domain).toEqual([["state", "=", "ghi"]]);
// unlock ghi search
ghiDef.resolve();
await animationFrame();
expect(`.o_search_panel_category_value .active`).toHaveCount(1);
expect(`.o_search_panel_category_value:eq(3) .active`).toHaveCount(1);
expect(component.domain).toEqual([["state", "=", "ghi"]]);
// unlock abc search (should be ignored)
abcDef.resolve();
await animationFrame();
expect(`.o_search_panel_category_value .active`).toHaveCount(1);
expect(`.o_search_panel_category_value:eq(3) .active`).toHaveCount(1);
expect(component.domain).toEqual([["state", "=", "ghi"]]);
});
test("concurrency: delayed get_filter", async () => {
Partner._views = {
search: /* xml */ `
<search>
<filter name="Filter" domain="[('id', '=', 1)]"/>
<searchpanel>
<field name="company_id" select="multi" enable_counters="1"/>
</searchpanel>
</search>
`,
};
let promise;
onRpc("search_panel_select_multi_range", () => promise);
const component = await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(component.domain).toEqual([]);
// trigger a reload and delay the get_filter
promise = new Deferred();
await toggleSearchBarMenu();
await toggleMenuItem("Filter");
expect(component.domain).toEqual([]);
promise.resolve();
await animationFrame();
expect(component.domain).toEqual([["id", "=", 1]]);
});
test("use filter (on many2one) to refine search", async () => {
Partner._views = {
search: /* xml */ `
<search>
<filter name="Filter" domain="[('id', '=', 1)]"/>
<searchpanel>
<field name="company_id" select="multi" enable_counters="1"/>
</searchpanel>
</search>
`,
};
const component = await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
domain: [["bar", "=", true]],
});
expect(`.o_search_panel_filter_value`).toHaveCount(2);
expect(`.o_search_panel_filter_value input:checked`).toHaveCount(0);
expect(getFiltersContent()).toEqual(["asustek: 2", "agrolait: 1"]);
expect(component.domain).toEqual([["bar", "=", true]]);
// check 'asustek'
await contains(queryAll`.o_search_panel_filter_value:eq(0) input`).click();
expect(`.o_search_panel_filter_value input:checked`).toHaveCount(1);
expect(getFiltersContent()).toEqual(["asustek: 2", "agrolait: 1"]);
expect(component.domain).toEqual(["&", ["bar", "=", true], ["company_id", "in", [3]]]);
// check 'agrolait'
await contains(queryAll`.o_search_panel_filter_value:eq(1) input`).click();
expect(`.o_search_panel_filter_value input:checked`).toHaveCount(2);
expect(getFiltersContent()).toEqual(["asustek: 2", "agrolait: 1"]);
expect(component.domain).toEqual(["&", ["bar", "=", true], ["company_id", "in", [3, 5]]]);
// uncheck 'asustek'
await contains(queryAll`.o_search_panel_filter_value:eq(0) input`).click();
expect(`.o_search_panel_filter_value input:checked`).toHaveCount(1);
expect(getFiltersContent()).toEqual(["asustek: 2", "agrolait: 1"]);
expect(component.domain).toEqual(["&", ["bar", "=", true], ["company_id", "in", [5]]]);
// uncheck 'agrolait'
await contains(queryAll`.o_search_panel_filter_value:eq(1) input`).click();
expect(`.o_search_panel_filter_value input:checked`).toHaveCount(0);
expect(getFiltersContent()).toEqual(["asustek: 2", "agrolait: 1"]);
expect(component.domain).toEqual([["bar", "=", true]]);
});
test("use filter (on selection) to refine search", async () => {
Partner._views = {
search: /* xml */ `
<search>
<filter name="Filter" domain="[('id', '=', 1)]"/>
<searchpanel>
<field name="state" select="multi" enable_counters="1" expand="1"/>
</searchpanel>
</search>
`,
};
const component = await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
domain: [["bar", "=", true]],
});
expect(`.o_search_panel_filter_value`).toHaveCount(3);
expect(`.o_search_panel_filter_value input:checked`).toHaveCount(0);
expect(getFiltersContent()).toEqual(["ABC: 1", "DEF: 1", "GHI: 1"]);
expect(component.domain).toEqual([["bar", "=", true]]);
// check 'abc'
await contains(queryAll`.o_search_panel_filter_value:eq(0) input`).click();
expect(`.o_search_panel_filter_value input:checked`).toHaveCount(1);
expect(getFiltersContent()).toEqual(["ABC: 1", "DEF: 1", "GHI: 1"]);
expect(component.domain).toEqual(["&", ["bar", "=", true], ["state", "in", ["abc"]]]);
// check 'def'
await contains(queryAll`.o_search_panel_filter_value:eq(1) input`).click();
expect(`.o_search_panel_filter_value input:checked`).toHaveCount(2);
expect(getFiltersContent()).toEqual(["ABC: 1", "DEF: 1", "GHI: 1"]);
expect(component.domain).toEqual(["&", ["bar", "=", true], ["state", "in", ["abc", "def"]]]);
// uncheck 'abc'
await contains(queryAll`.o_search_panel_filter_value:eq(0) input`).click();
expect(`.o_search_panel_filter_value input:checked`).toHaveCount(1);
expect(getFiltersContent()).toEqual(["ABC: 1", "DEF: 1", "GHI: 1"]);
expect(component.domain).toEqual(["&", ["bar", "=", true], ["state", "in", ["def"]]]);
// uncheck 'def'
await contains(queryAll`.o_search_panel_filter_value:eq(1) input`).click();
expect(`.o_search_panel_filter_value input:checked`).toHaveCount(0);
expect(getFiltersContent()).toEqual(["ABC: 1", "DEF: 1", "GHI: 1"]);
expect(component.domain).toEqual([["bar", "=", true]]);
});
test("only reload categories and filters when domains change (counters disabled, selection)", async () => {
Partner._views = {
search: /* xml */ `
<search>
<filter name="Filter" domain="[('id', '&lt;', 5)]"/>
<searchpanel>
<field name="state" expand="1"/>
<field name="company_id" select="multi" enable_counters="1" expand="1"/>
</searchpanel>
</search>
`,
};
onRpc(/search_panel_/, ({ method }) => expect.step(method));
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect.verifySteps(["search_panel_select_range", "search_panel_select_multi_range"]);
// reload with another domain, so the filters should be reloaded
await toggleSearchBarMenu();
await toggleMenuItem("Filter");
expect.verifySteps(["search_panel_select_multi_range"]);
// change category value, so the filters should be reloaded
await contains(`.o_search_panel_category_value header:eq(1)`).click();
expect.verifySteps(["search_panel_select_multi_range"]);
});
test("only reload categories and filters when domains change (counters disabled, many2one)", async () => {
Partner._views = {
search: /* xml */ `
<search>
<filter name="domain" domain="[('id', '&lt;', 5)]"/>
<searchpanel>
<field name="category_id" expand="1"/>
<field name="company_id" select="multi" enable_counters="1" expand="1"/>
</searchpanel>
</search>
`,
};
onRpc(/search_panel_/, ({ method }) => expect.step(method));
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect.verifySteps(["search_panel_select_range", "search_panel_select_multi_range"]);
// reload with another domain, so the filters should be reloaded
await toggleSearchBarMenu();
await toggleMenuItem("domain");
expect.verifySteps(["search_panel_select_multi_range"]);
// change category value, so the filters should be reloaded
await contains(`.o_search_panel_category_value header:eq(1)`).click();
expect.verifySteps(["search_panel_select_multi_range"]);
});
test("category counters", async () => {
Partner._views = {
search: /* xml */ `
<search>
<filter name="Filter" domain="[('id', '&lt;', 3)]"/>
<searchpanel>
<field name="state" enable_counters="1" expand="1"/>
<field name="company_id" expand="1"/>
</searchpanel>
</search>
`,
};
onRpc(/search_panel_/, ({ args, method }) => {
expect.step(method);
if (method === "search_panel_select_range") {
expect.step(args[0]);
}
});
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect.verifySteps([
"search_panel_select_range",
"state",
"search_panel_select_range",
"company_id",
]);
expect(getCategoriesContent()).toEqual([
"All",
"ABC: 1",
"DEF: 1",
"GHI: 2",
"All",
"asustek",
"agrolait",
]);
// reload with another domain, so the categories 'state' and 'company_id' should be reloaded
await toggleSearchBarMenu();
await toggleMenuItem("Filter");
expect.verifySteps(["search_panel_select_range", "state"]);
expect(getCategoriesContent()).toEqual([
"All",
"ABC: 1",
"DEF: 1",
"GHI",
"All",
"asustek",
"agrolait",
]);
// change category value, so the category 'state' should be reloaded
await contains(`.o_search_panel_category_value header:eq(1)`).click();
expect.verifySteps(["search_panel_select_range", "state"]);
expect(getCategoriesContent()).toEqual([
"All",
"ABC: 1",
"DEF: 1",
"GHI",
"All",
"asustek",
"agrolait",
]);
});
test("category selection without counters", async () => {
Partner._views = {
search: /* xml */ `
<search>
<filter name="Filter" domain="[('id', '&lt;', 3)]"/>
<searchpanel>
<field name="state" expand="1"/>
</searchpanel>
</search>
`,
};
onRpc(/search_panel_/, ({ args, method }) => {
expect.step(method);
if (method === "search_panel_select_range") {
expect.step(args[0]);
}
});
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect.verifySteps(["search_panel_select_range", "state"]);
expect(getCategoriesContent()).toEqual(["All", "ABC", "DEF", "GHI"]);
// reload with another domain, so the category 'state' should be reloaded
await toggleSearchBarMenu();
await toggleMenuItem("Filter");
expect.verifySteps([]);
expect(getCategoriesContent()).toEqual(["All", "ABC", "DEF", "GHI"]);
// change category value, so the category 'state' should be reloaded
await contains(`.o_search_panel_category_value header:eq(1)`).click();
expect.verifySteps([]);
expect(getCategoriesContent()).toEqual(["All", "ABC", "DEF", "GHI"]);
});
test("filter with groupby", async () => {
Company._records.push({ id: 11, name: "camptocamp", category_id: 7 });
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" select="multi" groupby="category_id" enable_counters="1" expand="1"/>
</searchpanel>
</search>
`,
};
const component = await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
domain: [["bar", "=", true]],
});
expect(`.o_search_panel_filter_group`).toHaveCount(2);
expect(`.o_search_panel_filter_group:first .o_search_panel_filter_value`).toHaveCount(1);
expect(`.o_search_panel_filter_group:eq(0) header`).toHaveText("gold");
expect(queryAllTexts`.o_search_panel_filter_group:eq(0) .o_search_panel_filter_value`).toEqual([
"asustek\n2",
]);
expect(`.o_search_panel_filter_group:eq(1) .o_search_panel_filter_value`).toHaveCount(2);
expect(`.o_search_panel_filter_group:eq(1) header`).toHaveText("silver");
expect(queryAllTexts`.o_search_panel_filter_group:eq(1) .o_search_panel_filter_value`).toEqual([
"agrolait\n1",
"camptocamp",
]);
expect(`.o_search_panel_filter_value input:checked`).toHaveCount(0);
expect(component.domain).toEqual([["bar", "=", true]]);
// check 'asustek'
await contains(queryAll`.o_search_panel_filter_value:eq(0) input`).click();
expect(`.o_search_panel_filter_value input:checked`).toHaveCount(1);
expect(queryFirst(`.o_search_panel_filter_group:eq(0) header > div > input`)).toBeChecked();
expect(getFiltersContent()).toEqual(["asustek: 2", "agrolait", "camptocamp"]);
expect(component.domain).toEqual(["&", ["bar", "=", true], ["company_id", "in", [3]]]);
// check 'agrolait'
await contains(queryAll`.o_search_panel_filter_value:eq(1) input`).click();
expect(`.o_search_panel_filter_value input:checked`).toHaveCount(2);
expect(queryFirst(`.o_search_panel_filter_group:eq(1) header > div > input`)).not.toBeChecked();
expect(queryFirst(`.o_search_panel_filter_group:eq(1) header > div > input`)).toBeChecked({
indeterminate: true,
});
expect(getFiltersContent()).toEqual(["asustek", "agrolait", "camptocamp"]);
expect(component.domain).toEqual([
"&",
["bar", "=", true],
"&",
["company_id", "in", [3]],
["company_id", "in", [5]],
]);
// check 'camptocamp'
await contains(queryAll`.o_search_panel_filter_value:eq(2) input`).click();
expect(`.o_search_panel_filter_value input:checked`).toHaveCount(3);
expect(queryAll`.o_search_panel_filter_value:eq(1) input`).toBeChecked();
expect(queryAll`.o_search_panel_filter_value:eq(1) input`).not.toBeChecked({
indeterminate: true,
});
expect(getFiltersContent()).toEqual(["asustek", "agrolait", "camptocamp"]);
expect(component.domain).toEqual([
"&",
["bar", "=", true],
"&",
["company_id", "in", [3]],
["company_id", "in", [5, 11]],
]);
// uncheck second group
await contains(`.o_search_panel_filter_group:eq(1) header > div > input`).click();
expect(`.o_search_panel_filter_value input:checked`).toHaveCount(1);
expect(queryAll`.o_search_panel_filter_value:eq(1) input`).not.toBeChecked();
expect(queryAll`.o_search_panel_filter_value:eq(1) input`).not.toBeChecked({
indeterminate: true,
});
expect(getFiltersContent()).toEqual(["asustek: 2", "agrolait", "camptocamp"]);
expect(component.domain).toEqual(["&", ["bar", "=", true], ["company_id", "in", [3]]]);
});
test("filter with domain", async () => {
Company._records.push({ id: 40, name: "child company 1", parent_id: 3 });
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" select="multi" domain="[('parent_id','=',False)]" enable_counters="1" expand="1"/>
</searchpanel>
</search>
`,
};
onRpc("search_panel_select_multi_range", ({ kwargs }) => {
expect.step("search_panel_select_multi_range");
expect({ ...kwargs, context: {} }).toEqual({
group_by: false,
category_domain: [],
context: {},
expand: true,
filter_domain: [],
search_domain: [],
comodel_domain: [["parent_id", "=", false]],
group_domain: [],
enable_counters: true,
limit: 200,
});
});
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_filter_value`).toHaveCount(2);
expect(getFiltersContent()).toEqual(["asustek: 2", "agrolait: 2"]);
expect.verifySteps(["search_panel_select_multi_range"]);
});
test("filter with domain depending on category", async () => {
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="category_id"/>
<field name="company_id" select="multi" domain="[['category_id', '=', category_id]]" enable_counters="1"/>
</searchpanel>
</search>
`,
};
onRpc("search_panel_select_multi_range", ({ kwargs }) => {
// the following keys should have same value for all calls to this route
const { group_by, search_domain, filter_domain } = kwargs;
expect({ group_by, search_domain, filter_domain }).toEqual({
group_by: false,
filter_domain: [],
search_domain: [],
});
expect.step(kwargs.category_domain);
expect.step(kwargs.comodel_domain);
});
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
// select 'gold' category
await contains(`.o_search_panel_category_value:eq(1) header`).click();
expect(`.o_search_panel_category_value .active`).toHaveCount(1);
expect(`.o_search_panel_category_value:eq(1) .active`).toHaveCount(1);
expect(`.o_search_panel_filter_value`).toHaveCount(1);
expect(getFiltersContent()).toEqual(["asustek: 1"]);
// select 'silver' category
await contains(`.o_search_panel_category_value:eq(2) header`).click();
expect(`.o_search_panel_category_value:eq(2) .active`).toHaveCount(1);
expect(`.o_search_panel_filter_value`).toHaveCount(1);
expect(getFiltersContent()).toEqual(["agrolait: 2"]);
// select All
await contains(`.o_search_panel_category_value:eq(0) header`).click();
expect(`.o_search_panel_category_value:first .active`).toHaveCount(1);
expect(`.o_search_panel_filter_value`).toHaveCount(0);
expect.verifySteps([
[], // category_domain (All)
[["category_id", "=", false]], // comodel_domain (All)
[["category_id", "=", 6]], // category_domain ('gold')
[["category_id", "=", 6]], // comodel_domain ('gold')
[["category_id", "=", 7]], // category_domain ('silver')
[["category_id", "=", 7]], // comodel_domain ('silver')
[], // category_domain (All)
[["category_id", "=", false]], // comodel_domain (All)
]);
});
test("specify active filter values in context", async () => {
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" select="multi" enable_counters="1"/>
<field name="state" select="multi" enable_counters="1"/>
</searchpanel>
</search>
`,
};
const component = await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
context: {
searchpanel_default_company_id: [5],
searchpanel_default_state: ["abc", "ghi"],
},
});
expect(`.o_search_panel_filter_value input:checked`).toHaveCount(3);
expect(component.domain).toEqual([
"&",
["company_id", "in", [5]],
["state", "in", ["abc", "ghi"]],
]);
// manually untick a default value
await contains(queryAll`.o_search_panel_filter_value:eq(1) input`).click();
expect(`.o_search_panel_filter_value input:checked`).toHaveCount(2);
expect(component.domain).toEqual([["state", "in", ["abc", "ghi"]]]);
});
test("retrieved filter value from context does not exist", async () => {
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" select="multi" enable_counters="1"/>
</searchpanel>
</search>
`,
};
const component = await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
context: {
searchpanel_default_company_id: [1, 3],
},
});
expect(component.domain).toEqual([["company_id", "in", [3]]]);
});
test("filter with groupby and default values in context", async () => {
Company._records.push({ id: 11, name: "camptocamp", category_id: 7 });
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" select="multi" groupby="category_id" enable_counters="1" expand="1"/>
</searchpanel>
</search>
`,
};
const component = await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
context: {
searchpanel_default_company_id: [5],
},
});
expect(queryFirst`.o_search_panel_filter_group:eq(1) header > div > input`).toBeChecked({
indeterminate: true,
});
expect(component.domain).toEqual([["company_id", "in", [5]]]);
});
test('Does not confuse false and "false" groupby values', async () => {
Company._fields.char_field = fields.Char({ string: "Char Field" });
Company._records = [
{ id: 3, name: "A", char_field: false },
{ id: 5, name: "B", char_field: "false" },
];
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" select="multi" groupby="char_field"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
context: {
searchpanel_default_company_id: [5],
},
});
expect(`.o_search_panel_section`).toHaveCount(1);
// There should be a group 'false' displayed with only value B inside it.
expect(`.o_search_panel_filter_group`).toHaveCount(1);
expect(`.o_search_panel_filter_group header`).toHaveText("false");
expect(queryAllTexts`.o_search_panel_filter_group:eq(0) .o_search_panel_filter_value`).toEqual([
"B",
]);
expect(`.o_search_panel_filter_group .o_search_panel_filter_value`).toHaveCount(1);
// Globally, there should be two values, one displayed in the group 'false', and one at the end of the section
// (the group false is not displayed and its values are displayed at the first level)
expect(`.o_search_panel_filter_value`).toHaveCount(2);
expect(getFiltersContent()).toEqual(["B", "A"]);
});
test("tests conservation of category record order", async () => {
Company._records.push(
{ id: 56, name: "highID", category_id: 6 },
{ id: 2, name: "lowID", category_id: 6 }
);
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" enable_counters="1" expand="1"/>
<field name="category_id" select="multi" enable_counters="1" expand="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(getCategoriesContent()).toEqual(["All", "lowID", "asustek: 2", "agrolait: 2", "highID"]);
});
test("search panel is available on list and kanban by default", async () => {
Partner._views = {
...Partner._views,
[["search", false]]: /* xml */ `
<search>
<filter name="false_domain" string="False Domain" domain="[(0, '=', 1)]"/>
<filter name="filter" string="Filter" domain="[('bar', '=', true)]"/>
<filter name="true_domain" string="True Domain" domain="[(1, '=', 1)]"/>
<filter name="group_by_bar" string="Bar" context="{ 'group_by': 'bar' }"/>
<searchpanel>
<field name="company_id" enable_counters="1" expand="1"/>
<field name="category_id" select="multi" enable_counters="1" expand="1"/>
</searchpanel>
</search>
`,
};
onRpc("has_group", () => true);
await mountWithCleanup(WebClient);
await getService("action").doAction(1);
expect(`.o_kanban_view .o_content.o_component_with_search_panel`).toHaveCount(1);
expect(`.o_content.o_component_with_search_panel .o_search_panel`).toHaveCount(1);
await getService("action").switchView("pivot");
expect(`.o_pivot_view .o_content`).toHaveCount(1);
expect(`.o_pivot_view .o_content .o_search_panel`).toHaveCount(0);
await getService("action").switchView("list");
expect(`.o_list_view .o_content.o_component_with_search_panel`).toHaveCount(1);
expect(`.o_content.o_component_with_search_panel .o_search_panel`).toHaveCount(1);
await contains(`.o_data_row .o_data_cell`).click();
expect(`.o_form_view .o_content`).toHaveCount(1);
expect(`.o_form_view .o_content .o_search_panel`).toHaveCount(0);
});
test("search panel with view_types attribute", async () => {
Partner._views = {
...Partner._views,
[["search", false]]: /* xml */ `
<search>
<filter name="false_domain" string="False Domain" domain="[(0, '=', 1)]"/>
<filter name="filter" string="Filter" domain="[('bar', '=', true)]"/>
<filter name="true_domain" string="True Domain" domain="[(1, '=', 1)]"/>
<filter name="group_by_bar" string="Bar" context="{ 'group_by': 'bar' }"/>
<searchpanel view_types="kanban,pivot">
<field name="company_id" enable_counters="1" expand="1"/>
<field name="category_id" select="multi" enable_counters="1" expand="1"/>
</searchpanel>
</search>
`,
};
onRpc("has_group", () => true);
await mountWithCleanup(WebClient);
await getService("action").doAction(1);
expect(`.o_kanban_view .o_content.o_component_with_search_panel`).toHaveCount(1);
expect(`.o_content.o_component_with_search_panel .o_search_panel`).toHaveCount(1);
await getService("action").switchView("list");
expect(`.o_list_view .o_content`).toHaveCount(1);
expect(`.o_content .o_search_panel`).toHaveCount(0);
await getService("action").switchView("pivot");
expect(`.o_content.o_component_with_search_panel .o_pivot`).toHaveCount(1);
expect(`.o_content.o_component_with_search_panel .o_search_panel`).toHaveCount(1);
});
test("search panel state is shared between views", async () => {
onRpc("web_search_read", ({ kwargs }) => {
expect.step(kwargs.domain);
});
onRpc("has_group", () => true);
await mountWithCleanup(WebClient);
await getService("action").doAction(1);
expect(`.o_search_panel_category_value header:eq(0)`).toHaveClass("active");
expect(`.o_kanban_record:not(.o_kanban_ghost)`).toHaveCount(4);
// select 'asustek' company
await contains(`.o_search_panel_category_value header:eq(1)`).click();
expect(`.o_search_panel_category_value header:eq(1)`).toHaveClass("active");
expect(`.o_kanban_record:not(.o_kanban_ghost)`).toHaveCount(2);
await getService("action").switchView("list");
expect(`.o_search_panel_category_value header:eq(1)`).toHaveClass("active");
expect(`.o_data_row`).toHaveCount(2);
// select 'agrolait' company
await contains(`.o_search_panel_category_value header:eq(2)`).click();
expect(`.o_search_panel_category_value header:eq(2)`).toHaveClass("active");
expect(`.o_data_row`).toHaveCount(2);
await getService("action").switchView("kanban");
expect(`.o_search_panel_category_value header:eq(2)`).toHaveClass("active");
expect(`.o_kanban_record:not(.o_kanban_ghost)`).toHaveCount(2);
expect.verifySteps([
[], // initial search_read
[["company_id", "child_of", 3]], // kanban, after selecting the first company
[["company_id", "child_of", 3]], // list
[["company_id", "child_of", 5]], // list, after selecting the other company
[["company_id", "child_of", 5]], // kanban
]);
});
test("search panel filters are kept between switch views", async () => {
onRpc("web_search_read", ({ kwargs }) => {
expect.step(kwargs.domain);
});
onRpc("has_group", () => true);
await mountWithCleanup(WebClient);
await getService("action").doAction(1);
expect(`.o_search_panel_filter_value input:checked`).toHaveCount(0);
expect(`.o_kanban_record:not(.o_kanban_ghost)`).toHaveCount(4);
// select gold filter
await contains(queryAll`.o_search_panel_filter_value:eq(0) input`).click();
expect(`.o_search_panel_filter_value input:checked`).toHaveCount(1);
expect(`.o_kanban_record:not(.o_kanban_ghost)`).toHaveCount(1);
await getService("action").switchView("list");
expect(`.o_search_panel_filter_value input:checked`).toHaveCount(1);
expect(`.o_data_row`).toHaveCount(1);
// select silver filter
await contains(queryAll`.o_search_panel_filter_value:eq(1) input`).click();
expect(`.o_search_panel_filter_value input:checked`).toHaveCount(2);
expect(`.o_data_row`).toHaveCount(4);
await getService("action").switchView("kanban");
expect(`.o_search_panel_filter_value input:checked`).toHaveCount(2);
expect(`.o_kanban_record:not(.o_kanban_ghost)`).toHaveCount(4);
await contains(`.o_kanban_record`).click();
await contains(`.breadcrumb-item`).click();
expect.verifySteps([
[], // initial search_read
[["category_id", "in", [6]]], // kanban, after selecting the gold filter
[["category_id", "in", [6]]], // list
[["category_id", "in", [6, 7]]], // list, after selecting the silver filter
[["category_id", "in", [6, 7]]], // kanban
[["category_id", "in", [6, 7]]], // kanban, after switching back from form view
]);
});
test("search panel filters are kept when switching to a view with no search panel", async () => {
onRpc("has_group", () => true);
await mountWithCleanup(WebClient);
await getService("action").doAction(1);
expect(`.o_kanban_view .o_content.o_component_with_search_panel`).toHaveCount(1);
expect(`.o_content.o_component_with_search_panel .o_search_panel`).toHaveCount(1);
expect(`.o_search_panel_filter_value input:checked`).toHaveCount(0);
expect(`.o_kanban_record:not(.o_kanban_ghost)`).toHaveCount(4);
// select gold filter
await contains(queryAll`.o_search_panel_filter_value:eq(0) input`).click();
expect(`.o_search_panel_filter_value input:checked`).toHaveCount(1);
expect(`.o_kanban_record:not(.o_kanban_ghost)`).toHaveCount(1);
// switch to pivot
await getService("action").switchView("pivot");
expect(`.o_pivot_view .o_content`).toHaveCount(1);
expect(`.o_content .o_search_panel`).toHaveCount(0);
expect(`.o_pivot_cell_value`).toHaveText("15");
// switch to list
await getService("action").switchView("list");
expect(`.o_list_view .o_content.o_component_with_search_panel`).toHaveCount(1);
expect(`.o_content.o_component_with_search_panel .o_search_panel`).toHaveCount(1);
expect(`.o_search_panel_filter_value input:checked`).toHaveCount(1);
expect(`.o_data_row`).toHaveCount(1);
});
test('after onExecuteAction, selects "All" as default category value', async () => {
await mountWithCleanup(WebClient);
await getService("action").doAction(1, { viewType: "form" });
await contains(`.o_form_view .o_form_nosheet button`).click();
expect(`.o_kanban_view`).toHaveCount(1);
expect(`.o_search_panel`).toHaveCount(1);
expect(`.o_search_panel_category_value:first .active`).toHaveCount(1);
});
test("categories and filters are not reloaded when switching between views", async () => {
onRpc(/search_panel_/, ({ method }) => expect.step(method));
onRpc("has_group", () => true);
await mountWithCleanup(WebClient);
await getService("action").doAction(1);
await getService("action").switchView("list");
await getService("action").switchView("kanban");
expect.verifySteps([
"search_panel_select_range", // kanban: categories
"search_panel_select_multi_range", // kanban: filters
]);
});
test("categories and filters are loaded when switching from a view without the search panel", async () => {
// set the pivot view as the default view
defineParams(
{
actions: {
1: {
id: 1,
name: "Partners",
res_model: "partner",
type: "ir.actions.act_window",
views: [
[false, "pivot"],
[false, "kanban"],
[false, "list"],
],
},
},
},
"replace"
);
onRpc(/search_panel_/, ({ method }) => expect.step(method));
onRpc("has_group", () => true);
await mountWithCleanup(WebClient);
await getService("action").doAction(1);
expect.verifySteps([]);
await getService("action").switchView("list");
expect.verifySteps(["search_panel_select_range", "search_panel_select_multi_range"]);
await getService("action").switchView("kanban");
expect.verifySteps([]);
});
test("scroll kanban view with searchpanel and kept scroll position", async () => {
for (let i = 10; i < 20; i++) {
Category._records.push({ id: i, name: "Cat " + i });
for (let j = 0; j < 9; j++) {
Partner._records.push({
id: 100 + i * 10 + j,
foo: `Record ${i * 10 + j}`,
});
}
}
class WebClientContainer extends Component {
static props = ["*"];
static components = { WebClient };
static template = xml`
<div class="o_web_client" style="max-height: 300px"><WebClient/></div>
`;
}
await mountWithCleanup(WebClientContainer);
await getService("action").doAction(1);
await getService("action").switchView("kanban");
// simulate a scroll in the kanban view
queryFirst(`.o_renderer`).scrollTop = 100;
await getService("action").doAction(2);
// execute a second action (in which we don't scroll)
expect(`.o_content`).toHaveProperty("scrollTop", 0);
// go back using the breadcrumbs
await contains(`.o_control_panel .breadcrumb a`).click();
expect(`.o_renderer`).toHaveProperty("scrollTop", 100);
});
test("scroll position is kept when switching between controllers", async () => {
for (let i = 10; i < 20; i++) {
Category._records.push({ id: i, name: "Cat " + i });
}
class WebClientContainer extends Component {
static props = ["*"];
static components = { WebClient };
static template = xml`
<div class="o_web_client" style="max-height: 300px"><WebClient/></div>
`;
}
onRpc("has_group", () => true);
await mountWithCleanup(WebClientContainer);
await getService("action").doAction(1);
expect(`.o_kanban_view .o_content`).toHaveCount(1);
expect(queryFirst(`.o_search_panel`).scrollTop).toBe(0);
// simulate a scroll in the search panel and switch into list
await scroll(`.o_search_panel`, { y: 100 });
await animationFrame();
await getService("action").switchView("list");
expect(`.o_list_view .o_content`).toHaveCount(1);
expect(queryFirst(`.o_search_panel`).scrollTop).toBe(100);
// simulate another scroll and switch back to kanban
await scroll(`.o_search_panel`, { y: 25 });
await getService("action").switchView("kanban");
expect(`.o_kanban_view .o_content`).toHaveCount(1);
expect(queryFirst(`.o_search_panel`).scrollTop).toBe(25);
});
test("search panel is not instantiated in dialogs", async () => {
Company._records = Array.from(Array(8), (_, i) => ({
id: i + 1,
name: `Company${i + 1}`,
}));
Company._views = {
[["list", false]]: /* xml */ `<list><field name="name"/></list>`,
[["search", false]]: /* xml */ `
<search>
<field name="name"/>
<searchpanel>
<field name="category_id" enable_counters="1"/>
</searchpanel>
</search>
`,
};
onRpc("has_group", () => true);
await mountWithCleanup(WebClient);
await getService("action").doAction(1, { viewType: "form" });
await contains(`.o_field_widget[name="company_id"] .dropdown input`).click();
await contains(`.o_field_widget[name="company_id"] .o_m2o_dropdown_option_search_more`).click();
expect(`.modal .o_list_view`).toHaveCount(1);
expect(`.modal .o_search_panel`).toHaveCount(0);
});
test("Reload categories with counters when filter values are selected", async () => {
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="category_id" enable_counters="1"/>
<field name="state" select="multi" enable_counters="1"/>
</searchpanel>
</search>
`,
};
onRpc(/search_panel_/, ({ method }) => expect.step(method));
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect.verifySteps(["search_panel_select_range", "search_panel_select_multi_range"]);
expect(getCategoriesCounter()).toEqual([1, 3]);
expect(getFiltersCounter()).toEqual([1, 1, 2]);
await contains(queryAll`.o_search_panel_filter_value:eq(0) input`).click();
expect(getCategoriesCounter()).toEqual([1]);
expect(getFiltersCounter()).toEqual([1, 1, 2]);
expect.verifySteps(["search_panel_select_range", "search_panel_select_multi_range"]);
});
test("many2one: select one, expand, hierarchize, counters", async () => {
Company._records.push(
{ id: 50, name: "agrobeurre", parent_id: 5 },
{ id: 51, name: "agrocrèmefraiche", parent_id: 5 }
);
Partner._records[1].company_id = 50;
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" enable_counters="1" expand="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_field .o_search_panel_category_value`).toHaveCount(3);
expect(`.o_toggle_fold > i`).toHaveCount(1);
expect(getCategoriesCounter()).toEqual([2, 1]);
await contains(`.o_search_panel_category_value header:contains(agrolait)`).click();
expect(`.o_search_panel_field .o_search_panel_category_value`).toHaveCount(5);
expect(getCategoriesCounter()).toEqual([2, 1, 1]);
});
test("many2one: select one, no expand, hierarchize, counters", async () => {
Company._records.push(
{ id: 50, name: "agrobeurre", parent_id: 5 },
{ id: 51, name: "agrocrèmefraiche", parent_id: 5 }
);
Partner._records[1].company_id = 50;
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" enable_counters="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_field .o_search_panel_category_value`).toHaveCount(3);
expect(`.o_toggle_fold > i`).toHaveCount(1);
expect(getCategoriesCounter()).toEqual([2, 1]);
await contains(`.o_search_panel_category_value header:contains(agrolait)`).click();
expect(`.o_search_panel_field .o_search_panel_category_value`).toHaveCount(4);
expect(getCategoriesCounter()).toEqual([2, 1, 1]);
});
test("many2one: select one, expand, no hierarchize, counters", async () => {
Company._records.push(
{ id: 50, name: "agrobeurre", parent_id: 5 },
{ id: 51, name: "agrocrèmefraiche", parent_id: 5 }
);
Partner._records[1].company_id = 50;
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" hierarchize="0" enable_counters="1" expand="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_field .o_search_panel_category_value`).toHaveCount(5);
expect(`.o_toggle_fold > i`).toHaveCount(0);
expect(getCategoriesCounter()).toEqual([2, 1, 1]);
});
test("many2one: select one, no expand, no hierarchize, counters", async () => {
Company._records.push(
{ id: 50, name: "agrobeurre", parent_id: 5 },
{ id: 51, name: "agrocrèmefraiche", parent_id: 5 }
);
Partner._records[1].company_id = 50;
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" hierarchize="0" enable_counters="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_field .o_search_panel_category_value`).toHaveCount(4);
expect(`.o_toggle_fold > i`).toHaveCount(0);
expect(getCategoriesCounter()).toEqual([2, 1, 1]);
});
test("many2one: select one, expand, hierarchize, no counters", async () => {
Company._records.push(
{ id: 50, name: "agrobeurre", parent_id: 5 },
{ id: 51, name: "agrocrèmefraiche", parent_id: 5 }
);
Partner._records[1].company_id = 50;
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" expand="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_field .o_search_panel_category_value`).toHaveCount(3);
expect(`.o_toggle_fold > i`).toHaveCount(1);
expect(getCategoriesCounter()).toEqual([]);
await contains(`.o_search_panel_category_value header:contains(agrolait)`).click();
expect(`.o_search_panel_field .o_search_panel_category_value`).toHaveCount(5);
expect(getCategoriesCounter()).toEqual([]);
});
test("many2one: select one, no expand, hierarchize, no counters", async () => {
Company._records.push(
{ id: 50, name: "agrobeurre", parent_id: 5 },
{ id: 51, name: "agrocrèmefraiche", parent_id: 5 }
);
Partner._records[1].company_id = 50;
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_field .o_search_panel_category_value`).toHaveCount(3);
expect(`.o_toggle_fold > i`).toHaveCount(1);
expect(getCategoriesCounter()).toEqual([]);
await contains(`.o_search_panel_category_value header:contains(agrolait)`).click();
expect(`.o_search_panel_field .o_search_panel_category_value`).toHaveCount(4);
expect(getCategoriesCounter()).toEqual([]);
});
test("many2one: select one, expand, no hierarchize, no counters", async () => {
Company._records.push(
{ id: 50, name: "agrobeurre", parent_id: 5 },
{ id: 51, name: "agrocrèmefraiche", parent_id: 5 }
);
Partner._records[1].company_id = 50;
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" hierarchize="0" expand="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_field .o_search_panel_category_value`).toHaveCount(5);
expect(`.o_toggle_fold > i`).toHaveCount(0);
expect(getCategoriesCounter()).toEqual([]);
});
test("many2one: select one, no expand, no hierarchize, no counters", async () => {
Company._records.push(
{ id: 50, name: "agrobeurre", parent_id: 5 },
{ id: 51, name: "agrocrèmefraiche", parent_id: 5 }
);
Partner._records[1].company_id = 50;
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" hierarchize="0"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_field .o_search_panel_category_value`).toHaveCount(4);
expect(`.o_toggle_fold > i`).toHaveCount(0);
expect(getCategoriesCounter()).toEqual([]);
});
test("many2one: select multi, expand, groupby, counters", async () => {
Company._records.push({ id: 666, name: "Mordor Inc.", category_id: 6 });
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" select="multi" groupby="category_id" enable_counters="1" expand="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_label`).toHaveCount(5);
expect(`.o_toggle_fold > i`).toHaveCount(0);
expect(getFiltersCounter()).toEqual([2, 2]);
});
test("many2one: select multi, no expand, groupby, counters", async () => {
Company._records.push({ id: 666, name: "Mordor Inc.", category_id: 6 });
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" select="multi" groupby="category_id" enable_counters="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_label`).toHaveCount(4);
expect(`.o_toggle_fold > i`).toHaveCount(0);
expect(getFiltersCounter()).toEqual([2, 2]);
});
test("many2one: select multi, expand, no groupby, counters", async () => {
Company._records.push({ id: 666, name: "Mordor Inc.", category_id: 6 });
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" select="multi" enable_counters="1" expand="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_label`).toHaveCount(3);
expect(`.o_toggle_fold > i`).toHaveCount(0);
expect(getFiltersCounter()).toEqual([2, 2]);
});
test("many2one: select multi, no expand, no groupby, counters", async () => {
Company._records.push({ id: 666, name: "Mordor Inc.", category_id: 6 });
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" select="multi" enable_counters="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_label`).toHaveCount(2);
expect(`.o_toggle_fold > i`).toHaveCount(0);
expect(getFiltersCounter()).toEqual([2, 2]);
});
test("many2one: select multi, expand, groupby, no counters", async () => {
Company._records.push({ id: 666, name: "Mordor Inc.", category_id: 6 });
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" select="multi" groupby="category_id" expand="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_label`).toHaveCount(5);
expect(`.o_toggle_fold > i`).toHaveCount(0);
expect(getFiltersCounter()).toEqual([]);
});
test("many2one: select multi, no expand, groupby, no counters", async () => {
Company._records.push({ id: 666, name: "Mordor Inc.", category_id: 6 });
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" select="multi" groupby="category_id"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_label`).toHaveCount(4);
expect(`.o_toggle_fold > i`).toHaveCount(0);
expect(getFiltersCounter()).toEqual([]);
});
test("many2one: select multi, expand, no groupby, no counters", async () => {
Company._records.push({ id: 666, name: "Mordor Inc.", category_id: 6 });
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" select="multi" expand="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_label`).toHaveCount(3);
expect(`.o_toggle_fold > i`).toHaveCount(0);
expect(getFiltersCounter()).toEqual([]);
});
test("many2one: select multi, no expand, no groupby, no counters", async () => {
Company._records.push({ id: 666, name: "Mordor Inc.", category_id: 6 });
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" select="multi"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_label`).toHaveCount(2);
expect(`.o_toggle_fold > i`).toHaveCount(0);
expect(getFiltersCounter()).toEqual([]);
});
test("many2many: select multi, expand, groupby, counters", async () => {
Company._records.push({ id: 666, name: "Mordor Inc.", category_id: 6 });
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_ids" select="multi" groupby="category_id" enable_counters="1" expand="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_label`).toHaveCount(5);
expect(`.o_toggle_fold > i`).toHaveCount(0);
expect(getFiltersCounter()).toEqual([2, 1]);
});
test("many2many: select multi, no expand, groupby, counters", async () => {
Company._records.push({ id: 666, name: "Mordor Inc.", category_id: 6 });
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_ids" select="multi" groupby="category_id" enable_counters="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_label`).toHaveCount(4);
expect(`.o_toggle_fold > i`).toHaveCount(0);
expect(getFiltersCounter()).toEqual([2, 1]);
});
test("many2many: select multi, expand, no groupby, counters", async () => {
Company._records.push({ id: 666, name: "Mordor Inc.", category_id: 6 });
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_ids" select="multi" enable_counters="1" expand="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_label`).toHaveCount(3);
expect(`.o_toggle_fold > i`).toHaveCount(0);
expect(getFiltersCounter()).toEqual([2, 1]);
});
test("many2many: select multi, no expand, no groupby, counters", async () => {
Company._records.push({ id: 666, name: "Mordor Inc.", category_id: 6 });
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_ids" select="multi" enable_counters="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_label`).toHaveCount(2);
expect(`.o_toggle_fold > i`).toHaveCount(0);
expect(getFiltersCounter()).toEqual([2, 1]);
});
test("many2many: select multi, expand, groupby, no counters", async () => {
Company._records.push({ id: 666, name: "Mordor Inc.", category_id: 6 });
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_ids" select="multi" groupby="category_id" expand="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_label`).toHaveCount(5);
expect(`.o_toggle_fold > i`).toHaveCount(0);
expect(getFiltersCounter()).toEqual([]);
});
test("many2many: select multi, no expand, groupby, no counters", async () => {
Company._records.push({ id: 666, name: "Mordor Inc.", category_id: 6 });
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_ids" select="multi" groupby="category_id"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_label`).toHaveCount(4);
expect(`.o_toggle_fold > i`).toHaveCount(0);
expect(getFiltersCounter()).toEqual([]);
});
test("many2many: select multi, expand, no groupby, no counters", async () => {
Company._records.push({ id: 666, name: "Mordor Inc.", category_id: 6 });
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_ids" select="multi" expand="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_label`).toHaveCount(3);
expect(`.o_toggle_fold > i`).toHaveCount(0);
expect(getFiltersCounter()).toEqual([]);
});
test("many2many: select multi, no expand, no groupby, no counters", async () => {
Company._records.push({ id: 666, name: "Mordor Inc.", category_id: 6 });
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_ids" select="multi"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_label`).toHaveCount(2);
expect(`.o_toggle_fold > i`).toHaveCount(0);
expect(getFiltersCounter()).toEqual([]);
});
test("selection: select one, expand, counters", async () => {
Partner._records.shift();
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="state" enable_counters="1" expand="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_field .o_search_panel_category_value`).toHaveCount(4);
expect(`.o_toggle_fold > i`).toHaveCount(0);
expect(getCategoriesCounter()).toEqual([1, 2]);
});
test("selection: select one, no expand, counters", async () => {
Partner._records.shift();
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="state" enable_counters="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_field .o_search_panel_category_value`).toHaveCount(3);
expect(`.o_toggle_fold > i`).toHaveCount(0);
expect(getCategoriesCounter()).toEqual([1, 2]);
});
test("selection: select one, expand, no counters", async () => {
Partner._records.shift();
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="state" expand="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_field .o_search_panel_category_value`).toHaveCount(4);
expect(`.o_toggle_fold > i`).toHaveCount(0);
expect(getCategoriesCounter()).toEqual([]);
});
test("selection: select one, no expand, no counters", async () => {
Partner._records.shift();
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="state"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_field .o_search_panel_category_value`).toHaveCount(3);
expect(`.o_toggle_fold > i`).toHaveCount(0);
expect(getCategoriesCounter()).toEqual([]);
});
test("selection: select multi, expand, counters", async () => {
Partner._records.shift();
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="state" select="multi" enable_counters="1" expand="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_label`).toHaveCount(3);
expect(`.o_toggle_fold > i`).toHaveCount(0);
expect(getFiltersCounter()).toEqual([1, 2]);
});
test("selection: select multi, no expand, counters", async () => {
Partner._records.shift();
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="state" select="multi" enable_counters="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_label`).toHaveCount(2);
expect(`.o_toggle_fold > i`).toHaveCount(0);
expect(getFiltersCounter()).toEqual([1, 2]);
});
test("selection: select multi, expand, no counters", async () => {
Partner._records.shift();
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="state" select="multi" expand="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_label`).toHaveCount(3);
expect(`.o_toggle_fold > i`).toHaveCount(0);
expect(getFiltersCounter()).toEqual([]);
});
test("selection: select multi, no expand, no counters", async () => {
Partner._records.shift();
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="state" select="multi"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_label`).toHaveCount(2);
expect(`.o_toggle_fold > i`).toHaveCount(0);
expect(getFiltersCounter()).toEqual([]);
});
//-------------------------------------------------------------------------
// Model domain and count domain distinction
//-------------------------------------------------------------------------
test("selection: select multi, no expand, counters, extra_domain", async () => {
Partner._records.shift();
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id"/>
<field name="state" select="multi" enable_counters="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_label`).toHaveCount(5);
expect(`.o_toggle_fold > i`).toHaveCount(0);
expect(getFiltersCounter()).toEqual([1, 2]);
await contains(`.o_search_panel_category_value header:contains(asustek)`).click();
expect(`.o_search_panel_label`).toHaveCount(5);
expect(getFiltersCounter()).toEqual([1]);
});
//-------------------------------------------------------------------------
// Limit
//-------------------------------------------------------------------------
test("reached limit for a category", async () => {
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" limit="2"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_section`).toHaveCount(1);
expect(`.o_search_panel_section_header`).toHaveCount(1);
expect(`.o_search_panel_section_header`).toHaveText("RES.COMPANY");
expect(`section div.alert.alert-warning`).toHaveCount(1);
expect(`section div.alert.alert-warning`).toHaveText("Too many items to display.");
expect(`.o_search_panel_category_value`).toHaveCount(0);
});
test("reached limit for a filter", async () => {
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" select="multi" limit="2"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_section`).toHaveCount(1);
expect(`.o_search_panel_section_header`).toHaveCount(1);
expect(`.o_search_panel_section_header`).toHaveText("RES.COMPANY");
expect(`section div.alert.alert-warning`).toHaveCount(1);
expect(`section div.alert.alert-warning`).toHaveText("Too many items to display.");
expect(`.o_search_panel_filter_value`).toHaveCount(0);
});
test("a selected value becomming invalid should no more impact the view", async () => {
Partner._views = {
search: /* xml */ `
<search>
<filter name="filter_on_def" string="DEF" domain="[('state', '=', 'def')]"/>
<searchpanel>
<field name="state" enable_counters="1"/>
</searchpanel>
</search>
`,
};
onRpc(/search_panel_/, ({ method }) => expect.step(method));
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect.verifySteps(["search_panel_select_range"]);
// select 'ABC' in search panel
await contains(`.o_search_panel_category_value header:eq(1)`).click();
expect.verifySteps(["search_panel_select_range"]);
// select DEF in filter menu
await toggleSearchBarMenu();
await toggleMenuItem("DEF");
expect.verifySteps(["search_panel_select_range"]);
expect(`.o_search_panel_category_value header:eq(0)`).toHaveText("All");
expect(`.o_search_panel_category_value header:eq(0)`).toHaveClass("active");
});
test("Categories with default attributes should be udpated when external domain changes", async () => {
Partner._views = {
search: /* xml */ `
<search>
<filter name="filter_on_def" string="DEF" domain="[('state', '=', 'def')]"/>
<searchpanel>
<field name="state"/>
</searchpanel>
</search>
`,
};
onRpc(/search_panel_/, ({ method }) => expect.step(method));
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect.verifySteps(["search_panel_select_range"]);
expect(getCategoriesContent()).toEqual(["All", "ABC", "DEF", "GHI"]);
// select 'ABC' in search panel --> no need to update the category value
await contains(`.o_search_panel_category_value header:eq(1)`).click();
expect.verifySteps([]);
expect(getCategoriesContent()).toEqual(["All", "ABC", "DEF", "GHI"]);
// select DEF in filter menu --> the external domain changes --> the values should be updated
await toggleSearchBarMenu();
await toggleMenuItem("DEF");
expect.verifySteps(["search_panel_select_range"]);
expect(getCategoriesContent()).toEqual(["All", "DEF"]);
});
test("Category with counters and filter with domain", async () => {
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="category_id"/>
<field name="company_id" select="multi" domain="[['category_id', '=', category_id]]"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(getCategoriesContent()).toEqual(["All", "gold", "silver"]);
});
test("Category with counters and filter with domain and context", async () => {
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="category_id"/>
<field name="company_id" select="multi" domain="[['category_id', '=', category_id]]"/>
</searchpanel>
</search>
`,
};
onRpc("search_panel_select_range", ({ kwargs }) => expect.step(kwargs.context.special_key));
onRpc("search_panel_select_multi_range", ({ kwargs }) =>
expect.step(kwargs.context.special_key)
);
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
context: {
special_key: "special_key",
},
});
expect.verifySteps(["special_key", "special_key"]);
});
test("Display message when no filter availible", async () => {
Partner._records = [];
Company._records = [];
Category._records = [];
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_empty_state`).toHaveCount(1);
expect(`.o_search_panel_empty_state button`).toHaveCount(1);
});
test("Don't display empty state message when some filters are available", async () => {
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel_empty_state`).toHaveCount(0);
});
test("search panel can be collapsed/expanded", async () => {
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel`).toHaveCount(1);
expect(`.o_search_panel_section`).toHaveCount(2);
await contains(`.o_search_panel button`).click();
expect(`.o_search_panel`).toHaveCount(0);
expect(`.o_search_panel_sidebar`).toHaveCount(1);
expect(`.o_search_panel_sidebar`).toHaveText("All");
await contains(`.o_search_panel_sidebar button`).click();
expect(`.o_search_panel`).toHaveCount(1);
await contains(queryAll`.o_search_panel_category_value header`[1]).click();
await contains(queryAll`.o_search_panel_filter_value input`[1]).click();
await contains(`.o_search_panel button`).click();
expect(`.o_search_panel`).toHaveCount(0);
expect(`.o_search_panel_sidebar`).toHaveCount(1);
expect(`.o_search_panel_sidebar`).toHaveText("asusteksilver");
});
test("search panel can be collapsed by default", async () => {
Partner._views = {
search: /* xml */ `
<search>
<searchpanel fold="true">
<field name="company_id" enable_counters="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel`).toHaveCount(0);
expect(`.o_search_panel_sidebar`).toHaveCount(1);
expect(`.o_search_panel_sidebar`).toHaveText("All");
});
test("search panel collapse with multiple filter categories selected", async () => {
Partner._views = {
search: /* xml */ `
<search>
<searchpanel>
<field name="company_id" enable_counters="1"/>
<field name="category_id" select="multi" enable_counters="1"/>
<field name="state" select="multi" enable_counters="1"/>
</searchpanel>
</search>
`,
};
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
expect(`.o_search_panel`).toHaveCount(1);
expect(`.o_search_panel_section`).toHaveCount(3);
await contains(queryAll`.o_search_panel_category_value header`[1]).click();
await contains(queryAll`.o_search_panel_filter_value input`[1]).click();
await contains(queryAll`.o_search_panel_filter_value input`[2]).click();
await contains(`.o_search_panel button`).click();
expect(`.o_search_panel`).toHaveCount(0);
expect(`.o_search_panel_sidebar`).toHaveCount(1);
expect(`.o_search_panel_sidebar`).toHaveText("asusteksilverABC");
});
test("expand/collapse state is kept when switching between controllers", async () => {
onRpc("has_group", () => true);
await mountWithCleanup(WebClient);
await getService("action").doAction(1);
await contains(`.o_search_panel button`).click();
expect(`.o_search_panel`).toHaveCount(0);
expect(`.o_search_panel_sidebar`).toHaveCount(1);
await getService("action").switchView("list");
expect(`.o_search_panel`).toHaveCount(0);
expect(`.o_search_panel_sidebar`).toHaveCount(1);
await contains(`.o_search_panel_sidebar button`).click();
expect(`.o_search_panel`).toHaveCount(1);
expect(`.o_search_panel_sidebar`).toHaveCount(0);
await getService("action").switchView("kanban");
expect(`.o_search_panel`).toHaveCount(1);
expect(`.o_search_panel_sidebar`).toHaveCount(0);
});
test("search panel should be resizable", async () => {
await mountWithSearch(TestComponent, {
resModel: "partner",
searchViewId: false,
});
const searchPanel = queryFirst(".o_search_panel");
const resizeHandle = queryFirst(".o_search_panel_resize");
const originalWidth = searchPanel.offsetWidth;
const { drop } = await drag(resizeHandle);
await drop(resizeHandle, { position: { x: 500 } });
expect(searchPanel.offsetWidth).toBeGreaterThan(originalWidth);
});
test("search panel width is kept when switching between controllers", async () => {
onRpc("has_group", () => true);
await mountWithCleanup(WebClient);
await getService("action").doAction(1);
const searchPanel = queryFirst(".o_search_panel");
const resizeHandle = queryFirst(".o_search_panel_resize");
const originalWidth = searchPanel.offsetWidth;
const { drop } = await drag(resizeHandle);
await drop(resizeHandle, { position: { x: 500 } });
const newWidth = searchPanel.offsetWidth;
expect(newWidth).toBeGreaterThan(originalWidth);
await getService("action").switchView("list");
expect(queryFirst(".o_search_panel").offsetWidth).toBe(newWidth);
await getService("action").switchView("kanban");
expect(queryFirst(".o_search_panel").offsetWidth).toBe(newWidth);
});