mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-20 08:52:08 +02:00
vanilla 19.0
This commit is contained in:
parent
991d2234ca
commit
d1963a3c3a
3066 changed files with 1651266 additions and 922560 deletions
|
|
@ -0,0 +1,293 @@
|
|||
import {
|
||||
contains,
|
||||
defineModels,
|
||||
fields,
|
||||
models,
|
||||
mountView,
|
||||
onRpc,
|
||||
stepAllNetworkCalls,
|
||||
} from "../web_test_helpers";
|
||||
import { beforeEach, describe, expect, test } from "@odoo/hoot";
|
||||
import { queryAllTexts } from "@odoo/hoot-dom";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
/** Foo is dummy model to test `action.report` with domain of its field `value`. **/
|
||||
class Foo extends models.Model {
|
||||
_name = "foo";
|
||||
|
||||
value = fields.Boolean();
|
||||
|
||||
_records = [
|
||||
{
|
||||
id: 1,
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
value: false,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
class IrActionsReport extends models.Model {
|
||||
_name = "ir.actions.report";
|
||||
|
||||
get_valid_action_reports(self, model, recordIds) {
|
||||
const validActionIds = [1];
|
||||
if (recordIds.includes(1)) {
|
||||
validActionIds.push(2);
|
||||
}
|
||||
if (recordIds.includes(2)) {
|
||||
validActionIds.push(3);
|
||||
}
|
||||
if (!recordIds.includes(1) && !recordIds.includes(2)) {
|
||||
// new record are initialized with value=False so domain of action 3 is satisfied
|
||||
validActionIds.push(3);
|
||||
}
|
||||
return validActionIds;
|
||||
}
|
||||
}
|
||||
|
||||
defineModels([Foo, IrActionsReport]);
|
||||
|
||||
describe.current.tags("desktop");
|
||||
|
||||
beforeEach(() => {
|
||||
onRpc("has_group", () => true);
|
||||
});
|
||||
|
||||
const printItems = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Some Report always visible",
|
||||
type: "ir.actions.action_report",
|
||||
domain: "",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Some Report with domain 1",
|
||||
type: "ir.actions.action_report",
|
||||
domain: [["value", "=", "True"]],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Some Report with domain 2",
|
||||
type: "ir.actions.action_report",
|
||||
domain: [["value", "=", "False"]],
|
||||
},
|
||||
];
|
||||
|
||||
test("render ActionMenus in list view", async () => {
|
||||
stepAllNetworkCalls();
|
||||
await mountView({
|
||||
type: "list",
|
||||
resModel: "foo",
|
||||
actionMenus: {
|
||||
action: [],
|
||||
print: printItems,
|
||||
},
|
||||
loadActionMenus: true,
|
||||
arch: /* xml */ `
|
||||
<list>
|
||||
<field name="value"/>
|
||||
</list>
|
||||
`,
|
||||
});
|
||||
expect.verifySteps([
|
||||
"/web/webclient/translations",
|
||||
"/web/webclient/load_menus",
|
||||
"get_views",
|
||||
"web_search_read",
|
||||
"has_group",
|
||||
]);
|
||||
|
||||
// select all records
|
||||
await contains(`thead .o_list_record_selector input`).click();
|
||||
expect(`div.o_control_panel .o_cp_action_menus`).toHaveCount(1);
|
||||
expect(queryAllTexts(`div.o_control_panel .o_cp_action_menus .dropdown-toggle`)).toEqual([
|
||||
"Print",
|
||||
"Actions",
|
||||
]);
|
||||
|
||||
// select Print dropdown
|
||||
await contains(`.o_cp_action_menus .dropdown-toggle:eq(0)`).click();
|
||||
expect(`.o-dropdown--menu .o-dropdown-item`).toHaveCount(3);
|
||||
expect(queryAllTexts(`.o-dropdown--menu .o-dropdown-item`)).toEqual([
|
||||
"Some Report always visible",
|
||||
"Some Report with domain 1",
|
||||
"Some Report with domain 2",
|
||||
]);
|
||||
|
||||
// the last RPC call to retrieve print items only happens when the dropdown is clicked
|
||||
expect.verifySteps(["get_valid_action_reports"]);
|
||||
|
||||
// select only the record that satisfies domain 1
|
||||
await contains(`.o_data_row:eq(1) input`).click();
|
||||
await contains(`.o_cp_action_menus .dropdown-toggle:eq(0)`).click();
|
||||
expect(`.o-dropdown--menu .o-dropdown-item`).toHaveCount(2);
|
||||
expect(queryAllTexts(`.o-dropdown--menu .o-dropdown-item`)).toEqual([
|
||||
"Some Report always visible",
|
||||
"Some Report with domain 1",
|
||||
]);
|
||||
|
||||
expect.verifySteps(["get_valid_action_reports"]);
|
||||
});
|
||||
|
||||
test("render ActionMenus in form view", async () => {
|
||||
stepAllNetworkCalls();
|
||||
await mountView({
|
||||
type: "form",
|
||||
resModel: "foo",
|
||||
resId: 1,
|
||||
actionMenus: {
|
||||
action: [],
|
||||
print: printItems,
|
||||
},
|
||||
loadActionMenus: true,
|
||||
arch: /* xml */ `
|
||||
<form>
|
||||
<field name="value"/>
|
||||
</form>
|
||||
`,
|
||||
});
|
||||
expect.verifySteps([
|
||||
"/web/webclient/translations",
|
||||
"/web/webclient/load_menus",
|
||||
"get_views",
|
||||
"web_read",
|
||||
]);
|
||||
|
||||
// select CogMenu
|
||||
await contains(`div.o_control_panel_breadcrumbs_actions i.fa-cog`).click();
|
||||
|
||||
// select Print dropdown
|
||||
await contains(`button.o-dropdown:contains(Print)`).click();
|
||||
expect(queryAllTexts(`.o-dropdown--menu-submenu span.o-dropdown-item`)).toEqual([
|
||||
"Some Report always visible",
|
||||
"Some Report with domain 1",
|
||||
]);
|
||||
|
||||
// the RPC call to retrieve print items only happens when the dropdown is clicked
|
||||
expect.verifySteps(["get_valid_action_reports"]);
|
||||
|
||||
// create a new record
|
||||
await contains(`button.o_form_button_create`).click();
|
||||
await contains(`button.o_form_button_save`).click();
|
||||
expect(`.o_pager_counter`).toHaveText("2 / 2");
|
||||
expect.verifySteps(["onchange", "web_save"]);
|
||||
await contains(`div.o_control_panel_breadcrumbs_actions i.fa-cog`).click();
|
||||
await contains(`button.o-dropdown:contains(Print)`).click();
|
||||
expect(queryAllTexts(`.o-dropdown--menu-submenu span.o-dropdown-item`)).toEqual([
|
||||
"Some Report always visible",
|
||||
"Some Report with domain 2",
|
||||
]);
|
||||
expect.verifySteps(["get_valid_action_reports"]);
|
||||
|
||||
// switch back to first record
|
||||
await contains(`.o_pager_previous`).click();
|
||||
expect(`.o_pager_counter`).toHaveText("1 / 2");
|
||||
await contains(`div.o_control_panel_breadcrumbs_actions i.fa-cog`).click();
|
||||
await contains(`button.o-dropdown:contains(Print)`).click();
|
||||
expect(queryAllTexts(`.o-dropdown--menu-submenu span.o-dropdown-item`)).toEqual([
|
||||
"Some Report always visible",
|
||||
"Some Report with domain 1",
|
||||
]);
|
||||
expect.verifySteps(["web_read", "get_valid_action_reports"]);
|
||||
});
|
||||
|
||||
test("render ActionMenus in list view with extraPrintItems", async () => {
|
||||
stepAllNetworkCalls();
|
||||
const listView = registry.category("views").get("list");
|
||||
class ExtraPrintController extends listView.Controller {
|
||||
get actionMenuProps() {
|
||||
return {
|
||||
...super.actionMenuProps,
|
||||
loadExtraPrintItems: () => [
|
||||
{
|
||||
key: "extra_print_key",
|
||||
description: "Extra Print Item",
|
||||
class: "o_menu_item",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
registry.category("views").add("extra_print", {
|
||||
...listView,
|
||||
Controller: ExtraPrintController,
|
||||
});
|
||||
await mountView({
|
||||
resModel: "foo",
|
||||
type: "list",
|
||||
arch: `<list js_class="extra_print"><field name="value"/></list>`,
|
||||
actionMenus: {
|
||||
action: [],
|
||||
print: printItems,
|
||||
},
|
||||
});
|
||||
|
||||
expect.verifySteps([
|
||||
"/web/webclient/translations",
|
||||
"/web/webclient/load_menus",
|
||||
"get_views",
|
||||
"web_search_read",
|
||||
"has_group",
|
||||
]);
|
||||
|
||||
// select all records
|
||||
await contains(`thead .o_list_record_selector input`).click();
|
||||
expect(`div.o_control_panel .o_cp_action_menus`).toHaveCount(1);
|
||||
expect(queryAllTexts(`div.o_control_panel .o_cp_action_menus .dropdown-toggle`)).toEqual([
|
||||
"Print",
|
||||
"Actions",
|
||||
]);
|
||||
|
||||
// select Print dropdown
|
||||
await contains(`.o_cp_action_menus .dropdown-toggle:eq(0)`).click();
|
||||
expect(`.o-dropdown--menu .o-dropdown-item`).toHaveCount(4);
|
||||
expect(queryAllTexts(`.o-dropdown--menu .o-dropdown-item`)).toEqual([
|
||||
"Extra Print Item",
|
||||
"Some Report always visible",
|
||||
"Some Report with domain 1",
|
||||
"Some Report with domain 2",
|
||||
]);
|
||||
|
||||
// the last RPC call to retrieve print items only happens when the dropdown is clicked
|
||||
expect.verifySteps(["get_valid_action_reports"]);
|
||||
|
||||
// select only the record that satisfies domain 1
|
||||
await contains(`.o_data_row:eq(1) input`).click();
|
||||
await contains(`.o_cp_action_menus .dropdown-toggle:eq(0)`).click();
|
||||
expect(`.o-dropdown--menu .o-dropdown-item`).toHaveCount(3);
|
||||
expect(queryAllTexts(`.o-dropdown--menu .o-dropdown-item`)).toEqual([
|
||||
"Extra Print Item",
|
||||
"Some Report always visible",
|
||||
"Some Report with domain 1",
|
||||
]);
|
||||
|
||||
expect.verifySteps(["get_valid_action_reports"]);
|
||||
});
|
||||
|
||||
test("static action items are properly ordered and styled", async () => {
|
||||
await mountView({
|
||||
type: "list",
|
||||
resModel: "foo",
|
||||
actionMenus: {
|
||||
action: [],
|
||||
print: [],
|
||||
},
|
||||
loadActionMenus: true,
|
||||
arch: /* xml */ `
|
||||
<list>
|
||||
<field name="value"/>
|
||||
</list>
|
||||
`,
|
||||
});
|
||||
// select all records
|
||||
await contains(`thead .o_list_record_selector input`).click();
|
||||
expect(`div.o_control_panel .o_cp_action_menus .dropdown-toggle`).toHaveCount(1);
|
||||
await contains(`div.o_control_panel .o_cp_action_menus .dropdown-toggle`).click();
|
||||
|
||||
expect(queryAllTexts(`.o_menu_item`)).toEqual(["Export", "Duplicate", "Delete"]);
|
||||
expect(`.o_menu_item:last`).toHaveClass("text-danger");
|
||||
});
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { getFixture, patchDate, patchWithCleanup } from "@web/../tests/helpers/utils";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { ControlPanel } from "@web/search/control_panel/control_panel";
|
||||
import {
|
||||
getFacetTexts,
|
||||
makeWithSearch,
|
||||
removeFacet,
|
||||
setupControlPanelServiceRegistry,
|
||||
toggleComparisonMenu,
|
||||
toggleFilterMenu,
|
||||
toggleMenuItem,
|
||||
toggleMenuItemOption,
|
||||
} from "./helpers";
|
||||
|
||||
let target;
|
||||
let serverData;
|
||||
QUnit.module("Search", (hooks) => {
|
||||
hooks.beforeEach(async () => {
|
||||
serverData = {
|
||||
models: {
|
||||
foo: {
|
||||
fields: {
|
||||
birthday: { string: "Birthday", type: "date", store: true, sortable: true },
|
||||
date_field: { string: "Date", type: "date", store: true, sortable: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
views: {
|
||||
"foo,false,search": `
|
||||
<search>
|
||||
<filter name="birthday" date="birthday"/>
|
||||
<filter name="date_field" date="date_field"/>
|
||||
</search>
|
||||
`,
|
||||
},
|
||||
};
|
||||
setupControlPanelServiceRegistry();
|
||||
patchWithCleanup(browser, {
|
||||
setTimeout: (fn) => fn(),
|
||||
clearTimeout: () => {},
|
||||
});
|
||||
target = getFixture();
|
||||
});
|
||||
|
||||
QUnit.module("Comparison");
|
||||
|
||||
QUnit.test("simple rendering", async function (assert) {
|
||||
patchDate(1997, 0, 9, 12, 0, 0);
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchMenuTypes: ["filter", "comparison"],
|
||||
searchViewId: false,
|
||||
});
|
||||
assert.containsOnce(target, ".dropdown.o_filter_menu");
|
||||
assert.containsNone(target, ".dropdown.o_comparison_menu");
|
||||
await toggleFilterMenu(target);
|
||||
await toggleMenuItem(target, "Birthday");
|
||||
await toggleMenuItemOption(target, "Birthday", "January");
|
||||
assert.containsOnce(target, "div.o_comparison_menu > button i.fa.fa-adjust");
|
||||
assert.strictEqual(
|
||||
target
|
||||
.querySelector("div.o_comparison_menu > button span")
|
||||
.innerText.trim()
|
||||
.toUpperCase() /** @todo why do I need to upperCase */,
|
||||
"COMPARISON"
|
||||
);
|
||||
await toggleComparisonMenu(target);
|
||||
assert.containsN(target, ".o_comparison_menu .dropdown-item", 2);
|
||||
assert.containsN(target, ".o_comparison_menu .dropdown-item[role=menuitemcheckbox]", 2);
|
||||
const comparisonOptions = [...target.querySelectorAll(".o_comparison_menu .dropdown-item")];
|
||||
assert.deepEqual(
|
||||
comparisonOptions.map((e) => e.innerText.trim()),
|
||||
["Birthday: Previous Period", "Birthday: Previous Year"]
|
||||
);
|
||||
assert.deepEqual(
|
||||
comparisonOptions.map((e) => e.ariaChecked),
|
||||
["false", "false"]
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("activate a comparison works", async function (assert) {
|
||||
patchDate(1997, 0, 9, 12, 0, 0);
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchMenuTypes: ["filter", "comparison"],
|
||||
searchViewId: false,
|
||||
});
|
||||
await toggleFilterMenu(target);
|
||||
await toggleMenuItem(target, "Birthday");
|
||||
await toggleMenuItemOption(target, "Birthday", "January");
|
||||
await toggleComparisonMenu(target);
|
||||
await toggleMenuItem(target, "Birthday: Previous Period");
|
||||
assert.deepEqual(getFacetTexts(target), [
|
||||
"Birthday: January 1997",
|
||||
"Birthday: Previous Period",
|
||||
]);
|
||||
await toggleFilterMenu(target);
|
||||
await toggleMenuItem(target, "Date");
|
||||
await toggleMenuItemOption(target, "Date", "December");
|
||||
await toggleComparisonMenu(target);
|
||||
await toggleMenuItem(target, "Date: Previous Year");
|
||||
assert.deepEqual(getFacetTexts(target), [
|
||||
["Birthday: January 1997", "Date: December 1996"].join("or"),
|
||||
"Date: Previous Year",
|
||||
]);
|
||||
await toggleFilterMenu(target);
|
||||
await toggleMenuItem(target, "Date");
|
||||
await toggleMenuItemOption(target, "Date", "1996");
|
||||
assert.deepEqual(getFacetTexts(target), ["Birthday: January 1997"]);
|
||||
await toggleComparisonMenu(target);
|
||||
await toggleMenuItem(target, "Birthday: Previous Year");
|
||||
assert.containsN(target, ".o_comparison_menu .dropdown-item", 2);
|
||||
assert.containsN(target, ".o_comparison_menu .dropdown-item[role=menuitemcheckbox]", 2);
|
||||
const comparisonOptions = [...target.querySelectorAll(".o_comparison_menu .dropdown-item")];
|
||||
assert.deepEqual(
|
||||
comparisonOptions.map((e) => e.innerText.trim()),
|
||||
["Birthday: Previous Period", "Birthday: Previous Year"]
|
||||
);
|
||||
assert.deepEqual(
|
||||
comparisonOptions.map((e) => e.ariaChecked),
|
||||
["false", "true"]
|
||||
);
|
||||
assert.deepEqual(getFacetTexts(target), [
|
||||
"Birthday: January 1997",
|
||||
"Birthday: Previous Year",
|
||||
]);
|
||||
await removeFacet(target);
|
||||
assert.deepEqual(getFacetTexts(target), []);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { click, getFixture, nextTick } from "@web/../tests/helpers/utils";
|
||||
import { ControlPanel } from "@web/search/control_panel/control_panel";
|
||||
import { makeWithSearch, setupControlPanelServiceRegistry } from "./helpers";
|
||||
|
||||
let target;
|
||||
let serverData;
|
||||
QUnit.module("Search", (hooks) => {
|
||||
hooks.beforeEach(async () => {
|
||||
serverData = {
|
||||
models: {
|
||||
foo: {
|
||||
fields: {},
|
||||
},
|
||||
},
|
||||
views: {
|
||||
"foo,false,search": `<search/>`,
|
||||
},
|
||||
};
|
||||
setupControlPanelServiceRegistry();
|
||||
target = getFixture();
|
||||
});
|
||||
|
||||
QUnit.module("ControlPanel");
|
||||
|
||||
QUnit.test("simple rendering", async (assert) => {
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
componentProps: {
|
||||
display: {
|
||||
"top-right": false,
|
||||
},
|
||||
},
|
||||
searchMenuTypes: [],
|
||||
});
|
||||
|
||||
assert.containsOnce(target, ".o_cp_top");
|
||||
assert.containsOnce(target, ".o_cp_top_left");
|
||||
assert.strictEqual(target.querySelector(".o_cp_top_right").innerHTML, "");
|
||||
assert.containsOnce(target, ".o_cp_bottom");
|
||||
assert.containsOnce(target, ".o_cp_bottom_left");
|
||||
assert.containsOnce(target, ".o_cp_bottom_right");
|
||||
|
||||
assert.containsNone(target, ".o_cp_switch_buttons");
|
||||
|
||||
assert.containsOnce(target, ".breadcrumb");
|
||||
});
|
||||
|
||||
QUnit.test("breadcrumbs", async (assert) => {
|
||||
const controlPanel = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
config: {
|
||||
breadcrumbs: [
|
||||
{ jsId: "controller_7", name: "Previous" },
|
||||
{ jsId: "controller_9", name: "Current" },
|
||||
],
|
||||
},
|
||||
searchMenuTypes: [],
|
||||
});
|
||||
|
||||
assert.containsN(target, ".breadcrumb li.breadcrumb-item", 2);
|
||||
const breadcrumbItems = target.querySelectorAll("li.breadcrumb-item");
|
||||
assert.strictEqual(breadcrumbItems[0].innerText, "Previous");
|
||||
assert.hasClass(breadcrumbItems[1], "active");
|
||||
assert.strictEqual(breadcrumbItems[1].innerText, "Current");
|
||||
|
||||
controlPanel.env.services.action.restore = (jsId) => {
|
||||
assert.step(jsId);
|
||||
};
|
||||
|
||||
await click(breadcrumbItems[0]);
|
||||
assert.verifySteps(["controller_7"]);
|
||||
});
|
||||
|
||||
QUnit.test("view switcher", async (assert) => {
|
||||
const controlPanel = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
config: {
|
||||
viewSwitcherEntries: [
|
||||
{
|
||||
type: "list",
|
||||
active: true,
|
||||
icon: "oi-view-list",
|
||||
name: "List",
|
||||
accessKey: "l",
|
||||
},
|
||||
{ type: "kanban", icon: "oi-view-kanban", name: "Kanban", accessKey: "k" },
|
||||
],
|
||||
},
|
||||
searchMenuTypes: [],
|
||||
});
|
||||
|
||||
assert.containsOnce(target, ".o_cp_switch_buttons");
|
||||
assert.containsN(target, ".o_switch_view", 2);
|
||||
const views = target.querySelectorAll(".o_switch_view");
|
||||
|
||||
assert.strictEqual(views[0].getAttribute("data-tooltip"), "List");
|
||||
assert.strictEqual(views[0].getAttribute("data-hotkey"), "l");
|
||||
assert.hasClass(views[0], "active");
|
||||
assert.strictEqual(views[1].getAttribute("data-tooltip"), "Kanban");
|
||||
assert.strictEqual(views[1].getAttribute("data-hotkey"), "k");
|
||||
assert.hasClass(views[1], "oi-view-kanban");
|
||||
|
||||
controlPanel.env.services.action.switchView = (viewType) => {
|
||||
assert.step(viewType);
|
||||
};
|
||||
|
||||
await click(views[1]);
|
||||
assert.verifySteps(["kanban"]);
|
||||
});
|
||||
|
||||
QUnit.test("pager", async (assert) => {
|
||||
const pagerProps = {
|
||||
offset: 0,
|
||||
limit: 10,
|
||||
total: 50,
|
||||
onUpdate: () => {},
|
||||
};
|
||||
|
||||
const controlPanel = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
config: {
|
||||
pagerProps: pagerProps,
|
||||
},
|
||||
searchMenuTypes: [],
|
||||
});
|
||||
assert.containsOnce(target, ".o_pager");
|
||||
|
||||
pagerProps.total = 0;
|
||||
controlPanel.render();
|
||||
await nextTick();
|
||||
assert.containsNone(target, ".o_pager");
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,295 @@
|
|||
import { expect, test, getFixture } from "@odoo/hoot";
|
||||
import { click, press, keyDown, keyUp, queryAll, queryFirst } from "@odoo/hoot-dom";
|
||||
import { animationFrame } from "@odoo/hoot-mock";
|
||||
import { reactive } from "@odoo/owl";
|
||||
import {
|
||||
contains,
|
||||
defineModels,
|
||||
fields,
|
||||
getService,
|
||||
models,
|
||||
mountWithCleanup,
|
||||
mountWithSearch,
|
||||
onRpc,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { ControlPanel } from "@web/search/control_panel/control_panel";
|
||||
import { WebClient } from "@web/webclient/webclient";
|
||||
|
||||
class Foo extends models.Model {
|
||||
_views = {
|
||||
kanban: `<kanban><t t-name="card"></t></kanban>`,
|
||||
};
|
||||
}
|
||||
defineModels([Foo]);
|
||||
|
||||
test("simple rendering", async () => {
|
||||
await mountWithSearch(ControlPanel, { resModel: "foo" });
|
||||
|
||||
expect(`.o_control_panel_breadcrumbs`).toHaveCount(1);
|
||||
expect(`.o_control_panel_actions`).toHaveCount(1);
|
||||
expect(`.o_control_panel_actions > *`).toHaveCount(0);
|
||||
expect(`.o_control_panel_navigation`).toHaveCount(1);
|
||||
expect(`.o_control_panel_navigation > *`).toHaveCount(0);
|
||||
expect(`.o_cp_switch_buttons`).toHaveCount(0);
|
||||
expect(`.o_breadcrumb`).toHaveCount(1);
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("breadcrumbs", async () => {
|
||||
await mountWithSearch(
|
||||
ControlPanel,
|
||||
{ resModel: "foo" },
|
||||
{
|
||||
breadcrumbs: [
|
||||
{
|
||||
jsId: "controller_7",
|
||||
name: "Previous",
|
||||
onSelected: () => expect.step("controller_7"),
|
||||
},
|
||||
{
|
||||
jsId: "controller_9",
|
||||
name: "Current",
|
||||
onSelected: () => expect.step("controller_9"),
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
const breadcrumbItems = queryAll(`.o_breadcrumb li.breadcrumb-item, .o_breadcrumb .active`);
|
||||
expect(breadcrumbItems).toHaveCount(2);
|
||||
expect(breadcrumbItems[0]).toHaveText("Previous");
|
||||
expect(breadcrumbItems[1]).toHaveText("Current");
|
||||
expect(breadcrumbItems[1]).toHaveClass("active");
|
||||
|
||||
await click(breadcrumbItems[0]);
|
||||
expect.verifySteps(["controller_7"]);
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("view switcher", async () => {
|
||||
await mountWithSearch(
|
||||
ControlPanel,
|
||||
{ resModel: "foo" },
|
||||
{
|
||||
viewSwitcherEntries: [
|
||||
{ type: "list", active: true, icon: "oi-view-list", name: "List" },
|
||||
{ type: "kanban", icon: "oi-view-kanban", name: "Kanban" },
|
||||
],
|
||||
}
|
||||
);
|
||||
expect(`.o_control_panel_navigation .o_cp_switch_buttons`).toHaveCount(1);
|
||||
expect(`.o_switch_view`).toHaveCount(2);
|
||||
|
||||
const views = queryAll`.o_switch_view`;
|
||||
expect(views[0]).toHaveAttribute("data-tooltip", "List");
|
||||
expect(views[0]).toHaveClass("active");
|
||||
expect(`.o_switch_view:eq(0) .oi-view-list`).toHaveCount(1);
|
||||
expect(views[1]).toHaveAttribute("data-tooltip", "Kanban");
|
||||
expect(views[1]).not.toHaveClass("active");
|
||||
expect(`.o_switch_view:eq(1) .oi-view-kanban`).toHaveCount(1);
|
||||
|
||||
getService("action").switchView = (viewType) => expect.step(viewType);
|
||||
await click(views[1]);
|
||||
expect.verifySteps(["kanban"]);
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("view switcher (middle click)", async () => {
|
||||
await mountWithSearch(
|
||||
ControlPanel,
|
||||
{ resModel: "foo" },
|
||||
{
|
||||
viewSwitcherEntries: [
|
||||
{ type: "list", active: true, icon: "oi-view-list", name: "List" },
|
||||
{ type: "kanban", icon: "oi-view-kanban", name: "Kanban" },
|
||||
],
|
||||
}
|
||||
);
|
||||
expect(`.o_control_panel_navigation .o_cp_switch_buttons`).toHaveCount(1);
|
||||
expect(`.o_switch_view`).toHaveCount(2);
|
||||
|
||||
getService("action").switchView = (viewType, props, options) =>
|
||||
expect.step(`${viewType} -- ${JSON.stringify(props)} -- ${JSON.stringify(options)}`);
|
||||
|
||||
await contains(".o_switch_view.o_kanban").click({ ctrlKey: true });
|
||||
expect.verifySteps([`kanban -- {} -- {"newWindow":true}`]);
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("views aria labels", async () => {
|
||||
await mountWithSearch(
|
||||
ControlPanel,
|
||||
{ resModel: "foo" },
|
||||
{
|
||||
viewSwitcherEntries: [
|
||||
{ type: "list", active: true, icon: "oi-view-list", name: "List" },
|
||||
{ type: "kanban", icon: "oi-view-kanban", name: "Kanban" },
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
const views = queryAll`.o_switch_view`;
|
||||
expect(views[0]).toHaveAttribute("aria-label", "List View");
|
||||
expect(views[1]).toHaveAttribute("aria-label", "Kanban View");
|
||||
});
|
||||
|
||||
test.tags("mobile");
|
||||
test("view switcher on mobile", async () => {
|
||||
await mountWithSearch(
|
||||
ControlPanel,
|
||||
{ resModel: "foo" },
|
||||
{
|
||||
viewSwitcherEntries: [
|
||||
{ type: "list", active: true, icon: "oi-view-list", name: "List" },
|
||||
{ type: "kanban", icon: "oi-view-kanban", name: "Kanban" },
|
||||
],
|
||||
}
|
||||
);
|
||||
expect(`.o_control_panel_navigation .o_cp_switch_buttons`).toHaveCount(1);
|
||||
|
||||
await click(".o_control_panel_navigation .o_cp_switch_buttons .dropdown-toggle");
|
||||
await animationFrame();
|
||||
|
||||
expect(`.dropdown-item`).toHaveCount(2);
|
||||
|
||||
const views = queryAll`.dropdown-item`;
|
||||
expect(views[0]).toHaveText("List");
|
||||
expect(views[0]).toHaveClass("selected");
|
||||
expect(queryAll(`.oi-view-list`, { root: views[0] })).toHaveCount(1);
|
||||
expect(views[1]).toHaveText("Kanban");
|
||||
expect(views[1]).not.toHaveClass("selected");
|
||||
expect(queryAll(`.oi-view-kanban`, { root: views[1] })).toHaveCount(1);
|
||||
|
||||
getService("action").switchView = (viewType) => expect.step(viewType);
|
||||
await click(views[1]);
|
||||
expect.verifySteps(["kanban"]);
|
||||
});
|
||||
|
||||
test("pager", async () => {
|
||||
const pagerProps = reactive({
|
||||
offset: 0,
|
||||
limit: 10,
|
||||
total: 50,
|
||||
onUpdate: () => {},
|
||||
});
|
||||
|
||||
await mountWithSearch(ControlPanel, { resModel: "foo" }, { pagerProps });
|
||||
expect(`.o_pager`).toHaveCount(1);
|
||||
|
||||
pagerProps.total = 0;
|
||||
await animationFrame();
|
||||
expect(`.o_pager`).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("view switcher hotkey cycles through views", async () => {
|
||||
onRpc("has_group", () => true);
|
||||
|
||||
await mountWithCleanup(WebClient);
|
||||
await getService("action").doAction({
|
||||
res_model: "foo",
|
||||
type: "ir.actions.act_window",
|
||||
views: [
|
||||
[false, "list"],
|
||||
[false, "kanban"],
|
||||
],
|
||||
});
|
||||
expect(`.o_list_view`).toHaveCount(1);
|
||||
|
||||
await press(["alt", "shift", "v"]);
|
||||
await animationFrame();
|
||||
expect(`.o_kanban_view`).toHaveCount(1);
|
||||
|
||||
await press(["alt", "shift", "v"]);
|
||||
await animationFrame();
|
||||
expect(`.o_list_view`).toHaveCount(1);
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("hotkey overlay not overlapped by active view button", async () => {
|
||||
onRpc("has_group", () => true);
|
||||
|
||||
await mountWithCleanup(WebClient);
|
||||
await getService("action").doAction({
|
||||
res_model: "foo",
|
||||
type: "ir.actions.act_window",
|
||||
views: [
|
||||
[false, "list"],
|
||||
[false, "kanban"],
|
||||
],
|
||||
});
|
||||
|
||||
await keyDown("alt");
|
||||
expect(`.o_cp_switch_buttons .o_web_hotkey_overlay`).toHaveCount(1);
|
||||
expect(`.o_switch_view.active`).toHaveCount(1);
|
||||
|
||||
const hotkeyZIndex = Number(
|
||||
getComputedStyle(queryFirst(`.o_cp_switch_buttons .o_web_hotkey_overlay`)).zIndex
|
||||
);
|
||||
const buttonZIndex = Number(getComputedStyle(queryFirst(`.o_switch_view.active`)).zIndex);
|
||||
|
||||
expect(hotkeyZIndex).toBeGreaterThan(buttonZIndex);
|
||||
|
||||
await keyUp("alt");
|
||||
expect(`.o_cp_switch_buttons .o_web_hotkey_overlay`).toHaveCount(0);
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("control panel layout buttons in dialog", async () => {
|
||||
onRpc("has_group", () => true);
|
||||
Foo._fields.char = fields.Char();
|
||||
Foo._records = [
|
||||
{
|
||||
char: "a",
|
||||
},
|
||||
{
|
||||
char: "b",
|
||||
},
|
||||
];
|
||||
Foo._views["list"] = `<list editable="top"><field name="char"/></list>`;
|
||||
|
||||
await mountWithCleanup(WebClient);
|
||||
await getService("action").doAction({
|
||||
res_model: "foo",
|
||||
type: "ir.actions.act_window",
|
||||
target: "new",
|
||||
views: [[false, "list"]],
|
||||
});
|
||||
expect(`.o_list_view`).toHaveCount(1);
|
||||
await contains(".o_data_cell").click();
|
||||
expect(".modal-footer button:visible").toHaveCount(2);
|
||||
expect(".o_control_panel_main_buttons button").toHaveCount(0, {
|
||||
message: "layout buttons are not replicated in the control panel when inside a dialog",
|
||||
});
|
||||
});
|
||||
|
||||
test.tags("mobile");
|
||||
test("Control panel is shown/hide on top when scrolling", async () => {
|
||||
await mountWithSearch(
|
||||
ControlPanel,
|
||||
{ resModel: "foo" },
|
||||
{
|
||||
viewSwitcherEntries: [
|
||||
{ type: "list", active: true, icon: "oi-view-list", name: "List" },
|
||||
{ type: "kanban", icon: "oi-view-kanban", name: "Kanban" },
|
||||
],
|
||||
}
|
||||
);
|
||||
const contentHeight = 200;
|
||||
const sampleContent = document.createElement("div");
|
||||
sampleContent.style.minHeight = `${2 * contentHeight}px`;
|
||||
const target = getFixture();
|
||||
target.appendChild(sampleContent);
|
||||
target.style.maxHeight = `${contentHeight}px`;
|
||||
target.style.overflow = "auto";
|
||||
target.scrollTo({ top: 50 });
|
||||
await animationFrame();
|
||||
expect(".o_control_panel").toHaveClass("o_mobile_sticky", {
|
||||
message: "control panel becomes sticky when the target is not on top",
|
||||
});
|
||||
target.scrollTo({ top: -50 });
|
||||
await animationFrame();
|
||||
expect(".o_control_panel").not.toHaveClass("o_mobile_sticky", {
|
||||
message: "control panel is not sticky anymore",
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,224 @@
|
|||
import { after, expect, test } from "@odoo/hoot";
|
||||
import { Component, xml } from "@odoo/owl";
|
||||
import {
|
||||
defineModels,
|
||||
editFavoriteName,
|
||||
editSearch,
|
||||
fields,
|
||||
getFacetTexts,
|
||||
mockService,
|
||||
models,
|
||||
mountWithSearch,
|
||||
onRpc,
|
||||
saveFavorite,
|
||||
saveAndEditFavorite,
|
||||
toggleSaveFavorite,
|
||||
toggleSearchBarMenu,
|
||||
validateSearch,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { useSetupAction } from "@web/search/action_hook";
|
||||
import { SearchBar } from "@web/search/search_bar/search_bar";
|
||||
import { SearchBarMenu } from "@web/search/search_bar_menu/search_bar_menu";
|
||||
import { rpcBus } from "@web/core/network/rpc";
|
||||
|
||||
class Foo extends models.Model {
|
||||
bar = fields.Many2one({ relation: "partner" });
|
||||
birthday = fields.Date();
|
||||
date_field = fields.Date();
|
||||
float_field = fields.Float();
|
||||
foo = fields.Char();
|
||||
}
|
||||
|
||||
class Partner extends models.Model {}
|
||||
|
||||
defineModels([Foo, Partner]);
|
||||
|
||||
test("simple rendering", async () => {
|
||||
await mountWithSearch(
|
||||
SearchBar,
|
||||
{
|
||||
resModel: "foo",
|
||||
searchMenuTypes: ["favorite"],
|
||||
searchViewId: false,
|
||||
},
|
||||
{
|
||||
getDisplayName: () => "Action Name",
|
||||
}
|
||||
);
|
||||
|
||||
await toggleSearchBarMenu();
|
||||
await toggleSaveFavorite();
|
||||
expect(`.o_add_favorite + .o_accordion_values input[type="text"]`).toHaveValue("Action Name");
|
||||
expect(`.o_add_favorite + .o_accordion_values input[type="checkbox"]`).toHaveCount(1);
|
||||
expect(`.o_add_favorite + .o_accordion_values .form-check label`).toHaveText("Default filter");
|
||||
});
|
||||
|
||||
test("save filter", async () => {
|
||||
class TestComponent extends Component {
|
||||
static components = { SearchBarMenu };
|
||||
static template = xml`<div><SearchBarMenu/></div>`;
|
||||
static props = ["*"];
|
||||
setup() {
|
||||
useSetupAction({
|
||||
getContext: () => ({ someKey: "foo" }),
|
||||
});
|
||||
}
|
||||
}
|
||||
onRpc("create_filter", ({ args, route }) => {
|
||||
expect.step(route);
|
||||
const irFilter = args[0];
|
||||
expect(irFilter.context).toEqual({ group_by: [], someKey: "foo" });
|
||||
return [7]; // fake serverSideId
|
||||
});
|
||||
|
||||
await mountWithSearch(TestComponent, {
|
||||
resModel: "foo",
|
||||
context: { someOtherKey: "bar" }, // should not end up in filter's context
|
||||
searchViewId: false,
|
||||
});
|
||||
const clearCacheListener = () => expect.step("CLEAR-CACHES");
|
||||
rpcBus.addEventListener("CLEAR-CACHES", clearCacheListener);
|
||||
after(() => rpcBus.removeEventListener("CLEAR-CACHES", clearCacheListener));
|
||||
expect.verifySteps([]);
|
||||
|
||||
await toggleSearchBarMenu();
|
||||
await toggleSaveFavorite();
|
||||
await editFavoriteName("aaa");
|
||||
await saveFavorite();
|
||||
expect.verifySteps(["/web/dataset/call_kw/ir.filters/create_filter", "CLEAR-CACHES"]);
|
||||
});
|
||||
|
||||
test("save and edit filter", async () => {
|
||||
class TestComponent extends Component {
|
||||
static components = { SearchBarMenu };
|
||||
static template = xml`<div><SearchBarMenu/></div>`;
|
||||
static props = ["*"];
|
||||
setup() {
|
||||
useSetupAction({
|
||||
getContext: () => ({ someKey: "foo" }),
|
||||
});
|
||||
}
|
||||
}
|
||||
onRpc("create_filter", ({ args, route }) => {
|
||||
expect.step(route);
|
||||
const irFilter = args[0];
|
||||
expect(irFilter.context).toEqual({ group_by: [], someKey: "foo" });
|
||||
return [7]; // fake serverSideId
|
||||
});
|
||||
mockService("action", {
|
||||
doAction(action) {
|
||||
expect(action).toEqual({
|
||||
context: {
|
||||
form_view_ref: "base.ir_filters_view_edit_form",
|
||||
},
|
||||
res_id: 7,
|
||||
res_model: "ir.filters",
|
||||
type: "ir.actions.act_window",
|
||||
views: [[false, "form"]],
|
||||
});
|
||||
expect.step("Edit favorite");
|
||||
},
|
||||
});
|
||||
|
||||
await mountWithSearch(TestComponent, {
|
||||
resModel: "foo",
|
||||
context: { someOtherKey: "bar" }, // should not end up in filter's context
|
||||
searchViewId: false,
|
||||
});
|
||||
const clearCacheListener = () => expect.step("CLEAR-CACHES");
|
||||
rpcBus.addEventListener("CLEAR-CACHES", clearCacheListener);
|
||||
after(() => rpcBus.removeEventListener("CLEAR-CACHES", clearCacheListener));
|
||||
expect.verifySteps([]);
|
||||
|
||||
await toggleSearchBarMenu();
|
||||
await toggleSaveFavorite();
|
||||
await editFavoriteName("aaa");
|
||||
await saveAndEditFavorite();
|
||||
expect.verifySteps([
|
||||
"/web/dataset/call_kw/ir.filters/create_filter",
|
||||
"CLEAR-CACHES",
|
||||
"Edit favorite",
|
||||
]);
|
||||
});
|
||||
|
||||
test("dynamic filters are saved dynamic", async () => {
|
||||
onRpc("create_filter", ({ args, route }) => {
|
||||
expect.step(route);
|
||||
const irFilter = args[0];
|
||||
expect(irFilter.domain).toBe(
|
||||
`[("date_field", ">=", (context_today() + relativedelta()).strftime("%Y-%m-%d"))]`
|
||||
);
|
||||
return [7]; // fake serverSideId
|
||||
});
|
||||
|
||||
await mountWithSearch(SearchBar, {
|
||||
resModel: "foo",
|
||||
context: { search_default_filter: 1 },
|
||||
searchMenuTypes: ["filter", "favorite"],
|
||||
searchViewId: false,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Filter" name="filter" domain="[('date_field', '>=', (context_today() + relativedelta()).strftime('%Y-%m-%d'))]"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
expect(getFacetTexts()).toEqual(["Filter"]);
|
||||
|
||||
await toggleSearchBarMenu();
|
||||
await toggleSaveFavorite();
|
||||
await editFavoriteName("My favorite");
|
||||
await saveFavorite();
|
||||
expect(getFacetTexts()).toEqual(["My favorite"]);
|
||||
expect.verifySteps(["/web/dataset/call_kw/ir.filters/create_filter"]);
|
||||
});
|
||||
|
||||
test("save filters created via autocompletion works", async () => {
|
||||
onRpc("create_filter", ({ args, route }) => {
|
||||
expect.step(route);
|
||||
const irFilter = args[0];
|
||||
expect(irFilter.domain).toBe(`[("foo", "ilike", "a")]`);
|
||||
return [7]; // fake serverSideId
|
||||
});
|
||||
|
||||
await mountWithSearch(SearchBar, {
|
||||
resModel: "foo",
|
||||
searchMenuTypes: ["favorite"],
|
||||
searchViewId: false,
|
||||
searchViewArch: `<search><field name="foo"/></search>`,
|
||||
});
|
||||
expect(getFacetTexts()).toEqual([]);
|
||||
|
||||
await editSearch("a");
|
||||
await validateSearch();
|
||||
expect(getFacetTexts()).toEqual(["Foo\na"]);
|
||||
|
||||
await toggleSearchBarMenu();
|
||||
await toggleSaveFavorite();
|
||||
await editFavoriteName("My favorite");
|
||||
await saveFavorite();
|
||||
expect(getFacetTexts()).toEqual(["My favorite"]);
|
||||
expect.verifySteps(["/web/dataset/call_kw/ir.filters/create_filter"]);
|
||||
});
|
||||
|
||||
test("undefined name for filter shows notification and not error", async () => {
|
||||
mockService("notification", {
|
||||
add(message, options) {
|
||||
expect.step("notification");
|
||||
expect(message).toBe("A name for your favorite filter is required.");
|
||||
expect(options).toEqual({ type: "danger" });
|
||||
},
|
||||
});
|
||||
|
||||
onRpc("create_filter", () => [7]); // fake serverSideId
|
||||
|
||||
await mountWithSearch(SearchBarMenu, {
|
||||
resModel: "foo",
|
||||
searchViewId: false,
|
||||
});
|
||||
|
||||
await toggleSearchBarMenu();
|
||||
await toggleSaveFavorite();
|
||||
await saveFavorite();
|
||||
expect.verifySteps(["notification"]);
|
||||
});
|
||||
|
|
@ -1,562 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { getFixture, patchWithCleanup, triggerEvent } from "@web/../tests/helpers/utils";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { ControlPanel } from "@web/search/control_panel/control_panel";
|
||||
import { FavoriteMenu } from "@web/search/favorite_menu/favorite_menu";
|
||||
import { useSetupAction } from "@web/webclient/actions/action_hook";
|
||||
import {
|
||||
editFavoriteName,
|
||||
editSearch,
|
||||
getFacetTexts,
|
||||
makeWithSearch,
|
||||
saveFavorite,
|
||||
setupControlPanelFavoriteMenuRegistry,
|
||||
setupControlPanelServiceRegistry,
|
||||
toggleFavoriteMenu,
|
||||
toggleSaveFavorite,
|
||||
validateSearch,
|
||||
} from "./helpers";
|
||||
|
||||
import { Component, xml } from "@odoo/owl";
|
||||
const serviceRegistry = registry.category("services");
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} target
|
||||
*/
|
||||
async function toggleDefaultCheckBox(target) {
|
||||
const checkbox = target.querySelector("input[type='checkbox']");
|
||||
checkbox.checked = !checkbox.checked;
|
||||
await triggerEvent(checkbox, null, "change");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} target
|
||||
*/
|
||||
async function toggleShareCheckBox(target) {
|
||||
const checkbox = target.querySelectorAll("input[type='checkbox']")[1];
|
||||
checkbox.checked = !checkbox.checked;
|
||||
await triggerEvent(checkbox, null, "change");
|
||||
}
|
||||
|
||||
let target;
|
||||
let serverData;
|
||||
QUnit.module("Search", (hooks) => {
|
||||
hooks.beforeEach(async () => {
|
||||
serverData = {
|
||||
models: {
|
||||
foo: {
|
||||
fields: {
|
||||
bar: { string: "Bar", type: "many2one", relation: "partner" },
|
||||
birthday: { string: "Birthday", type: "date", store: true, sortable: true },
|
||||
date_field: { string: "Date", type: "date", store: true, sortable: true },
|
||||
float_field: { string: "Float", type: "float", group_operator: "sum" },
|
||||
foo: { string: "Foo", type: "char", store: true, sortable: true },
|
||||
},
|
||||
records: {},
|
||||
},
|
||||
},
|
||||
views: {
|
||||
"foo,false,search": `<search/>`,
|
||||
},
|
||||
};
|
||||
setupControlPanelFavoriteMenuRegistry();
|
||||
setupControlPanelServiceRegistry();
|
||||
patchWithCleanup(browser, {
|
||||
setTimeout: (fn) => fn(),
|
||||
clearTimeout: () => {},
|
||||
});
|
||||
|
||||
target = getFixture();
|
||||
});
|
||||
|
||||
QUnit.module("CustomFavoriteItem");
|
||||
|
||||
QUnit.test("simple rendering", async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchMenuTypes: ["favorite"],
|
||||
searchViewId: false,
|
||||
config: {
|
||||
getDisplayName: () => "Action Name",
|
||||
},
|
||||
});
|
||||
|
||||
await toggleFavoriteMenu(target);
|
||||
await toggleSaveFavorite(target);
|
||||
assert.strictEqual(
|
||||
target.querySelector('.o_add_favorite input[type="text"]').value,
|
||||
"Action Name"
|
||||
);
|
||||
assert.containsN(target, '.o_add_favorite .form-check input[type="checkbox"]', 2);
|
||||
const labelEls = target.querySelectorAll(".o_add_favorite .form-check label");
|
||||
assert.deepEqual(
|
||||
[...labelEls].map((e) => e.innerText.trim()),
|
||||
["Use by default", "Share with all users"]
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("favorites use by default and share are exclusive", async function (assert) {
|
||||
assert.expect(11);
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchMenuTypes: ["favorite"],
|
||||
searchViewId: false,
|
||||
});
|
||||
|
||||
await toggleFavoriteMenu(target);
|
||||
await toggleSaveFavorite(target);
|
||||
const checkboxes = target.querySelectorAll('input[type="checkbox"]');
|
||||
|
||||
assert.strictEqual(checkboxes.length, 2, "2 checkboxes are present");
|
||||
|
||||
assert.notOk(checkboxes[0].checked, "Start: None of the checkboxes are checked (1)");
|
||||
assert.notOk(checkboxes[1].checked, "Start: None of the checkboxes are checked (2)");
|
||||
|
||||
await toggleDefaultCheckBox(target);
|
||||
|
||||
assert.ok(checkboxes[0].checked, "The first checkbox is checked");
|
||||
assert.notOk(checkboxes[1].checked, "The second checkbox is not checked");
|
||||
|
||||
await toggleShareCheckBox(target);
|
||||
|
||||
assert.notOk(
|
||||
checkboxes[0].checked,
|
||||
"Clicking on the second checkbox checks it, and unchecks the first (1)"
|
||||
);
|
||||
assert.ok(
|
||||
checkboxes[1].checked,
|
||||
"Clicking on the second checkbox checks it, and unchecks the first (2)"
|
||||
);
|
||||
|
||||
await toggleDefaultCheckBox(target);
|
||||
|
||||
assert.ok(
|
||||
checkboxes[0].checked,
|
||||
"Clicking on the first checkbox checks it, and unchecks the second (1)"
|
||||
);
|
||||
assert.notOk(
|
||||
checkboxes[1].checked,
|
||||
"Clicking on the first checkbox checks it, and unchecks the second (2)"
|
||||
);
|
||||
|
||||
await toggleDefaultCheckBox(target);
|
||||
|
||||
assert.notOk(checkboxes[0].checked, "End: None of the checkboxes are checked (1)");
|
||||
assert.notOk(checkboxes[1].checked, "End: None of the checkboxes are checked (2)");
|
||||
});
|
||||
|
||||
QUnit.test("save filter", async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
class TestComponent extends Component {
|
||||
setup() {
|
||||
useSetupAction({
|
||||
getContext: () => {
|
||||
return { someKey: "foo" };
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
TestComponent.components = { FavoriteMenu };
|
||||
TestComponent.template = xml`<div><FavoriteMenu/></div>`;
|
||||
|
||||
const comp = await makeWithSearch({
|
||||
serverData,
|
||||
mockRPC: (_, args) => {
|
||||
if (args.model === "ir.filters" && args.method === "create_or_replace") {
|
||||
const irFilter = args.args[0];
|
||||
assert.deepEqual(irFilter.context, { group_by: [], someKey: "foo" });
|
||||
return 7; // fake serverSideId
|
||||
}
|
||||
},
|
||||
resModel: "foo",
|
||||
context: { someOtherKey: "bar" }, // should not end up in filter's context
|
||||
Component: TestComponent,
|
||||
searchViewId: false,
|
||||
});
|
||||
comp.env.bus.on("CLEAR-CACHES", comp, () => assert.step("CLEAR-CACHES"));
|
||||
|
||||
assert.verifySteps([]);
|
||||
|
||||
await toggleFavoriteMenu(target);
|
||||
await toggleSaveFavorite(target);
|
||||
await editFavoriteName(target, "aaa");
|
||||
await saveFavorite(target);
|
||||
|
||||
assert.verifySteps(["CLEAR-CACHES"]);
|
||||
});
|
||||
|
||||
QUnit.test("dynamic filters are saved dynamic", async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
mockRPC: (_, args) => {
|
||||
if (args.model === "ir.filters" && args.method === "create_or_replace") {
|
||||
const irFilter = args.args[0];
|
||||
assert.deepEqual(
|
||||
irFilter.domain,
|
||||
'[("date_field", ">=", (context_today() + relativedelta()).strftime("%Y-%m-%d"))]'
|
||||
);
|
||||
return 7; // fake serverSideId
|
||||
}
|
||||
},
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchMenuTypes: ["favorite"],
|
||||
searchViewId: false,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Filter" name="filter" domain="[('date_field', '>=', (context_today() + relativedelta()).strftime('%Y-%m-%d'))]"/>
|
||||
</search>
|
||||
`,
|
||||
context: { search_default_filter: 1 },
|
||||
});
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), ["Filter"]);
|
||||
|
||||
await toggleFavoriteMenu(target);
|
||||
await toggleSaveFavorite(target);
|
||||
await editFavoriteName(target, "My favorite");
|
||||
await saveFavorite(target);
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), ["My favorite"]);
|
||||
});
|
||||
|
||||
QUnit.test("save filters created via autocompletion works", async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
mockRPC: (_, args) => {
|
||||
if (args.model === "ir.filters" && args.method === "create_or_replace") {
|
||||
const irFilter = args.args[0];
|
||||
assert.deepEqual(irFilter.domain, '[("foo", "ilike", "a")]');
|
||||
return 7; // fake serverSideId
|
||||
}
|
||||
},
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchMenuTypes: ["favorite"],
|
||||
searchViewId: false,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<field name="foo"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), []);
|
||||
|
||||
await editSearch(target, "a");
|
||||
await validateSearch(target);
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), ["Foo\na"]);
|
||||
|
||||
await toggleFavoriteMenu(target);
|
||||
await toggleSaveFavorite(target);
|
||||
await editFavoriteName(target, "My favorite");
|
||||
await saveFavorite(target);
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), ["My favorite"]);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"favorites have unique descriptions (the submenus of the favorite menu are correctly updated)",
|
||||
async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
serviceRegistry.add(
|
||||
"notification",
|
||||
{
|
||||
start() {
|
||||
return {
|
||||
add(message, options) {
|
||||
assert.strictEqual(
|
||||
message,
|
||||
"A filter with same name already exists."
|
||||
);
|
||||
assert.deepEqual(options, { type: "danger" });
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{ force: true }
|
||||
);
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
mockRPC: (route, args) => {
|
||||
if (args.model === "ir.filters" && args.method === "create_or_replace") {
|
||||
const irFilter = args.args[0];
|
||||
assert.deepEqual(irFilter, {
|
||||
action_id: false,
|
||||
context: { group_by: [] },
|
||||
domain: "[]",
|
||||
is_default: false,
|
||||
model_id: "foo",
|
||||
name: "My favorite 2",
|
||||
sort: "[]",
|
||||
user_id: 7,
|
||||
});
|
||||
return 2; // serverSideId
|
||||
}
|
||||
},
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchMenuTypes: ["favorite"],
|
||||
searchViewId: false,
|
||||
irFilters: [
|
||||
{
|
||||
context: "{}",
|
||||
domain: "[]",
|
||||
id: 1,
|
||||
is_default: false,
|
||||
name: "My favorite",
|
||||
sort: "[]",
|
||||
user_id: [2, "Mitchell Admin"],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await toggleFavoriteMenu(target);
|
||||
await toggleSaveFavorite(target);
|
||||
|
||||
// first try: should fail
|
||||
await editFavoriteName(target, "My favorite");
|
||||
await saveFavorite(target);
|
||||
|
||||
// second try: should succeed
|
||||
await editFavoriteName(target, "My favorite 2");
|
||||
await saveFavorite(target);
|
||||
|
||||
// third try: should fail
|
||||
await editFavoriteName(target, "My favorite 2");
|
||||
await saveFavorite(target);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"undefined name for filter shows notification and not error",
|
||||
async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
serviceRegistry.add(
|
||||
"notification",
|
||||
{
|
||||
start() {
|
||||
return {
|
||||
add(message, options) {
|
||||
assert.strictEqual(
|
||||
message,
|
||||
"A name for your favorite filter is required.",
|
||||
"The notification should match: A name for your favorite filter is required."
|
||||
);
|
||||
assert.deepEqual(options, { type: "danger" });
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{ force: true }
|
||||
);
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
mockRPC: (_, args) => {
|
||||
if (args.model === "ir.filters" && args.method === "create_or_replace") {
|
||||
return 7; // fake serverSideId
|
||||
}
|
||||
},
|
||||
resModel: "foo",
|
||||
Component: FavoriteMenu,
|
||||
searchViewId: false,
|
||||
});
|
||||
|
||||
await toggleFavoriteMenu(target);
|
||||
await toggleSaveFavorite(target);
|
||||
await saveFavorite(target);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("add favorite with enter which already exists", async function (assert) {
|
||||
serviceRegistry.add(
|
||||
"notification",
|
||||
{
|
||||
start() {
|
||||
return {
|
||||
add(message, options) {
|
||||
assert.strictEqual(message, "A filter with same name already exists.");
|
||||
assert.deepEqual(options, { type: "danger" });
|
||||
assert.step("warning dialog");
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{ force: true }
|
||||
);
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchMenuTypes: ["favorite"],
|
||||
searchViewId: false,
|
||||
config: {
|
||||
displayName: "Action Name",
|
||||
},
|
||||
irFilters: [
|
||||
{
|
||||
context: "{}",
|
||||
domain: "[]",
|
||||
id: 1,
|
||||
is_default: false,
|
||||
name: "My favorite",
|
||||
sort: "[]",
|
||||
user_id: [2, "Mitchell Admin"],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await toggleFavoriteMenu(target);
|
||||
await toggleSaveFavorite(target);
|
||||
await editFavoriteName(target, "My favorite");
|
||||
triggerEvent(
|
||||
target,
|
||||
`.o_favorite_menu .o_add_favorite .dropdown-menu input[type="text"]`,
|
||||
"keydown",
|
||||
{ key: "Enter" }
|
||||
);
|
||||
assert.verifySteps(["warning dialog"]);
|
||||
});
|
||||
|
||||
QUnit.skip("save search filter in modal", async function (assert) {
|
||||
/** @todo I don't know yet how to convert this test */
|
||||
// assert.expect(5);
|
||||
// serverData.models = {
|
||||
// partner: {
|
||||
// fields: {
|
||||
// date_field: {
|
||||
// string: "Date",
|
||||
// type: "date",
|
||||
// store: true,
|
||||
// sortable: true,
|
||||
// searchable: true,
|
||||
// },
|
||||
// birthday: { string: "Birthday", type: "date", store: true, sortable: true },
|
||||
// foo: { string: "Foo", type: "char", store: true, sortable: true },
|
||||
// bar: { string: "Bar", type: "many2one", relation: "partner" },
|
||||
// float_field: { string: "Float", type: "float", group_operator: "sum" },
|
||||
// },
|
||||
// records: [
|
||||
// {
|
||||
// id: 1,
|
||||
// display_name: "First record",
|
||||
// foo: "yop",
|
||||
// bar: 2,
|
||||
// date_field: "2017-01-25",
|
||||
// birthday: "1983-07-15",
|
||||
// float_field: 1,
|
||||
// },
|
||||
// {
|
||||
// id: 2,
|
||||
// display_name: "Second record",
|
||||
// foo: "blip",
|
||||
// bar: 1,
|
||||
// date_field: "2017-01-24",
|
||||
// birthday: "1982-06-04",
|
||||
// float_field: 2,
|
||||
// },
|
||||
// {
|
||||
// id: 3,
|
||||
// display_name: "Third record",
|
||||
// foo: "gnap",
|
||||
// bar: 1,
|
||||
// date_field: "2017-01-13",
|
||||
// birthday: "1985-09-13",
|
||||
// float_field: 1.618,
|
||||
// },
|
||||
// {
|
||||
// id: 4,
|
||||
// display_name: "Fourth record",
|
||||
// foo: "plop",
|
||||
// bar: 2,
|
||||
// date_field: "2017-02-25",
|
||||
// birthday: "1983-05-05",
|
||||
// float_field: -1,
|
||||
// },
|
||||
// {
|
||||
// id: 5,
|
||||
// display_name: "Fifth record",
|
||||
// foo: "zoup",
|
||||
// bar: 2,
|
||||
// date_field: "2016-01-25",
|
||||
// birthday: "1800-01-01",
|
||||
// float_field: 13,
|
||||
// },
|
||||
// { id: 7, display_name: "Partner 6" },
|
||||
// { id: 8, display_name: "Partner 7" },
|
||||
// { id: 9, display_name: "Partner 8" },
|
||||
// { id: 10, display_name: "Partner 9" },
|
||||
// ],
|
||||
// },
|
||||
// };
|
||||
// const form = await createView({
|
||||
// arch: `
|
||||
// <form string="Partners">
|
||||
// <sheet>
|
||||
// <group>
|
||||
// <field name="bar"/>
|
||||
// </group>
|
||||
// </sheet>
|
||||
// </form>`,
|
||||
// archs: {
|
||||
// "partner,false,list": '<tree><field name="display_name"/></tree>',
|
||||
// "partner,false,search": '<search><field name="date_field"/></search>',
|
||||
// },
|
||||
// data,
|
||||
// model: "partner",
|
||||
// res_id: 1,
|
||||
// View: FormView,
|
||||
// env: {
|
||||
// dataManager: {
|
||||
// create_filter(filter) {
|
||||
// assert.strictEqual(
|
||||
// filter.name,
|
||||
// "Awesome Test Customer Filter",
|
||||
// "filter name should be correct"
|
||||
// );
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
// await testUtils.form.clickEdit(form);
|
||||
// await testUtils.fields.many2one.clickOpenDropdown("bar");
|
||||
// await testUtils.fields.many2one.clickItem("bar", "Search");
|
||||
// assert.containsN(document.body, "tr.o_data_row", 9, "should display 9 records");
|
||||
// await toggleFilterMenu(".modal");
|
||||
// await toggleAddCustomFilter(".modal");
|
||||
// assert.strictEqual(
|
||||
// document.querySelector(".o_filter_condition select.o_generator_menu_field").value,
|
||||
// "date_field",
|
||||
// "date field should be selected"
|
||||
// );
|
||||
// await applyFilter(".modal");
|
||||
// assert.containsNone(document.body, "tr.o_data_row", "should display 0 records");
|
||||
// // Save this search
|
||||
// await toggleFavoriteMenu(".modal");
|
||||
// await toggleSaveFavorite(".modal");
|
||||
// const filterNameInput = document.querySelector('.o_add_favorite input[type="text"]');
|
||||
// assert.isVisible(filterNameInput, "should display an input field for the filter name");
|
||||
// await testUtils.fields.editInput(filterNameInput, "Awesome Test Customer Filter");
|
||||
// await click(document.querySelector(".o_add_favorite button.btn-primary"));
|
||||
// form.destroy();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,863 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import {
|
||||
click,
|
||||
getFixture,
|
||||
patchDate,
|
||||
patchTimeZone,
|
||||
patchWithCleanup,
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { localization } from "@web/core/l10n/localization";
|
||||
import { ControlPanel } from "@web/search/control_panel/control_panel";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import {
|
||||
addCondition,
|
||||
applyFilter,
|
||||
editConditionField,
|
||||
editConditionOperator,
|
||||
editConditionValue,
|
||||
getFacetTexts,
|
||||
isItemSelected,
|
||||
makeWithSearch,
|
||||
removeFacet,
|
||||
setupControlPanelServiceRegistry,
|
||||
toggleAddCustomFilter,
|
||||
toggleFilterMenu,
|
||||
toggleMenuItem,
|
||||
} from "./helpers";
|
||||
|
||||
function getDomain(controlPanel) {
|
||||
return controlPanel.env.searchModel.domain;
|
||||
}
|
||||
|
||||
let target;
|
||||
let serverData;
|
||||
QUnit.module("Search", (hooks) => {
|
||||
hooks.beforeEach(async () => {
|
||||
serverData = {
|
||||
models: {
|
||||
foo: {
|
||||
fields: {
|
||||
date_field: {
|
||||
name: "date_field",
|
||||
string: "A date",
|
||||
type: "date",
|
||||
searchable: true,
|
||||
},
|
||||
date_time_field: {
|
||||
name: "date_time_field",
|
||||
string: "DateTime",
|
||||
type: "datetime",
|
||||
searchable: true,
|
||||
},
|
||||
boolean_field: {
|
||||
name: "boolean_field",
|
||||
string: "Boolean Field",
|
||||
type: "boolean",
|
||||
default: true,
|
||||
searchable: true,
|
||||
},
|
||||
char_field: {
|
||||
name: "char_field",
|
||||
string: "Char Field",
|
||||
type: "char",
|
||||
default: "foo",
|
||||
trim: true,
|
||||
searchable: true,
|
||||
},
|
||||
float_field: {
|
||||
name: "float_field",
|
||||
string: "Floaty McFloatface",
|
||||
type: "float",
|
||||
digits: [999, 1],
|
||||
searchable: true,
|
||||
},
|
||||
color: {
|
||||
name: "color",
|
||||
string: "Color",
|
||||
type: "selection",
|
||||
selection: [
|
||||
["black", "Black"],
|
||||
["white", "White"],
|
||||
],
|
||||
searchable: true,
|
||||
},
|
||||
image: {
|
||||
name: "image",
|
||||
string: "Binary field",
|
||||
type: "binary",
|
||||
searchable: true,
|
||||
},
|
||||
json_field: {
|
||||
name: "json_field",
|
||||
string: "Jason",
|
||||
type: "json",
|
||||
searchable: true,
|
||||
},
|
||||
},
|
||||
records: {},
|
||||
},
|
||||
},
|
||||
views: {
|
||||
"foo,false,search": `<search/>`,
|
||||
},
|
||||
};
|
||||
setupControlPanelServiceRegistry();
|
||||
patchWithCleanup(browser, {
|
||||
setTimeout: (fn) => fn(),
|
||||
clearTimeout: () => {},
|
||||
});
|
||||
target = getFixture();
|
||||
});
|
||||
|
||||
QUnit.module("CustomFilterItem");
|
||||
|
||||
QUnit.test("basic rendering", async function (assert) {
|
||||
assert.expect(14);
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchMenuTypes: ["filter"],
|
||||
});
|
||||
|
||||
await toggleFilterMenu(target);
|
||||
|
||||
const customFilterItem = target.querySelector(".o_add_custom_filter_menu");
|
||||
|
||||
assert.strictEqual(customFilterItem.innerText.trim(), "Add Custom Filter");
|
||||
|
||||
await toggleAddCustomFilter(target);
|
||||
|
||||
// Single condition
|
||||
assert.containsOnce(customFilterItem, ".o_filter_condition");
|
||||
assert.containsOnce(
|
||||
customFilterItem,
|
||||
".o_filter_condition > select.o_generator_menu_field"
|
||||
);
|
||||
assert.containsOnce(
|
||||
customFilterItem,
|
||||
".o_filter_condition > select.o_generator_menu_operator"
|
||||
);
|
||||
assert.containsOnce(customFilterItem, ".o_filter_condition > span.o_generator_menu_value");
|
||||
assert.containsNone(customFilterItem, ".o_filter_condition .o_or_filter");
|
||||
assert.containsNone(customFilterItem, ".o_filter_condition .o_generator_menu_delete");
|
||||
|
||||
// no deletion allowed on single condition
|
||||
assert.containsNone(customFilterItem, ".o_filter_condition > i.o_generator_menu_delete");
|
||||
|
||||
// Buttons
|
||||
assert.containsOnce(customFilterItem, "button.o_apply_filter");
|
||||
assert.containsOnce(customFilterItem, "button.o_add_condition");
|
||||
|
||||
assert.containsOnce(customFilterItem, ".o_filter_condition");
|
||||
|
||||
await click(customFilterItem, "button.o_add_condition");
|
||||
|
||||
assert.containsN(customFilterItem, ".o_filter_condition", 2);
|
||||
assert.containsOnce(customFilterItem, ".o_filter_condition .o_or_filter");
|
||||
assert.containsN(customFilterItem, ".o_filter_condition .o_generator_menu_delete", 2);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
'should have Date and ID field proposed in that order in "Add custom Filter" submenu',
|
||||
async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchMenuTypes: ["filter"],
|
||||
searchViewFields: {
|
||||
date_field: {
|
||||
name: "date_field",
|
||||
string: "Date",
|
||||
type: "date",
|
||||
store: true,
|
||||
sortable: true,
|
||||
searchable: true,
|
||||
},
|
||||
foo: { string: "Foo", type: "char", store: true, sortable: true },
|
||||
},
|
||||
});
|
||||
|
||||
await toggleFilterMenu(target);
|
||||
await toggleAddCustomFilter(target);
|
||||
const optionEls = target.querySelectorAll(
|
||||
".o_filter_condition > select.o_generator_menu_field option"
|
||||
);
|
||||
assert.strictEqual(optionEls[0].innerText.trim(), "Date");
|
||||
assert.strictEqual(optionEls[1].innerText.trim(), "ID");
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("deactivate a new custom filter works", async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
patchDate(2020, 1, 5, 12, 20, 0);
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchMenuTypes: ["filter"],
|
||||
searchViewFields: {
|
||||
date_field: {
|
||||
name: "date_field",
|
||||
string: "Date",
|
||||
type: "date",
|
||||
store: true,
|
||||
sortable: true,
|
||||
searchable: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await toggleFilterMenu(target);
|
||||
await toggleAddCustomFilter(target);
|
||||
await applyFilter(target);
|
||||
|
||||
assert.ok(isItemSelected(target, 'Date is equal to "02/05/2020"'));
|
||||
assert.deepEqual(getFacetTexts(target), ['Date is equal to "02/05/2020"']);
|
||||
|
||||
await toggleMenuItem(target, 'Date is equal to "02/05/2020"');
|
||||
|
||||
assert.notOk(isItemSelected(target, 'Date is equal to "02/05/2020"'));
|
||||
assert.deepEqual(getFacetTexts(target), []);
|
||||
});
|
||||
|
||||
QUnit.test("custom OR filter presets new condition from preceding", async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchMenuTypes: ["filter"],
|
||||
});
|
||||
|
||||
await toggleFilterMenu(target);
|
||||
await toggleAddCustomFilter(target);
|
||||
|
||||
// Retrieve second selectable values for field and operator dropdowns
|
||||
const secondFieldName = target.querySelector(
|
||||
".o_generator_menu_field option:nth-of-type(2)"
|
||||
).value;
|
||||
const secondOperator = target.querySelector(
|
||||
".o_generator_menu_operator option:nth-of-type(2)"
|
||||
).value;
|
||||
|
||||
// Check if they really exist…
|
||||
assert.ok(!!secondFieldName);
|
||||
assert.ok(!!secondOperator);
|
||||
|
||||
// Add first filter condition
|
||||
await editConditionField(target, 0, secondFieldName);
|
||||
await editConditionOperator(target, 0, secondOperator);
|
||||
|
||||
// Add a second conditon on the filter being created
|
||||
await addCondition(target);
|
||||
|
||||
// Check the defaults for field and operator dropdowns
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_filter_condition:nth-of-type(2) .o_generator_menu_field")
|
||||
.value,
|
||||
secondFieldName
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_filter_condition:nth-of-type(2) .o_generator_menu_operator")
|
||||
.value,
|
||||
secondOperator
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("add a custom filter works", async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const controlPanel = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchMenuTypes: ["filter"],
|
||||
searchViewFields: {},
|
||||
});
|
||||
|
||||
await toggleFilterMenu(target);
|
||||
await toggleAddCustomFilter(target);
|
||||
// choose ID field in 'Add Custom filter' menu and value 1
|
||||
await editConditionField(target, 0, "id");
|
||||
await editConditionValue(target, 0, 1);
|
||||
|
||||
await applyFilter(target);
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), ['ID is "1"']);
|
||||
assert.deepEqual(getDomain(controlPanel), [["id", "=", 1]]);
|
||||
});
|
||||
|
||||
QUnit.test("adding a simple filter works", async function (assert) {
|
||||
assert.expect(11);
|
||||
|
||||
const controlPanel = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchMenuTypes: ["filter"],
|
||||
searchViewFields: {
|
||||
boolean_field: {
|
||||
name: "boolean_field",
|
||||
string: "Boolean Field",
|
||||
type: "boolean",
|
||||
default: true,
|
||||
searchable: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await toggleFilterMenu(target);
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), []);
|
||||
assert.deepEqual(getDomain(controlPanel), []);
|
||||
|
||||
assert.containsNone(target, ".o_menu_item");
|
||||
assert.containsOnce(target, ".o_add_custom_filter_menu button.dropdown-toggle");
|
||||
// the 'Add Custom Filter' menu should be closed;
|
||||
assert.containsNone(target, ".o_add_custom_filter_menu .dropdown-menu");
|
||||
|
||||
await toggleAddCustomFilter(target);
|
||||
// the 'Add Custom Filter' menu should be open;
|
||||
assert.containsOnce(target, ".o_add_custom_filter_menu .dropdown-menu");
|
||||
|
||||
await applyFilter(target);
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), ["Boolean Field is Yes"]);
|
||||
assert.deepEqual(getDomain(controlPanel), [["boolean_field", "=", true]]);
|
||||
|
||||
assert.containsOnce(target, ".o_menu_item");
|
||||
assert.containsOnce(target, ".o_add_custom_filter_menu button.dropdown-toggle");
|
||||
// the 'Add Custom Filter' menu should still be opened;
|
||||
assert.containsOnce(target, ".o_add_custom_filter_menu .dropdown-menu");
|
||||
});
|
||||
|
||||
QUnit.test("binary field is available", async function (assert) {
|
||||
assert.expect(11);
|
||||
|
||||
const controlPanel = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchMenuTypes: ["filter"],
|
||||
searchViewFields: {
|
||||
image: {
|
||||
name: "image",
|
||||
string: "Binary Field",
|
||||
type: "binary",
|
||||
default: true,
|
||||
searchable: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await toggleFilterMenu(target);
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), []);
|
||||
assert.deepEqual(getDomain(controlPanel), []);
|
||||
|
||||
assert.containsNone(target, ".o_menu_item");
|
||||
assert.containsOnce(target, ".o_add_custom_filter_menu button.dropdown-toggle");
|
||||
// the 'Add Custom Filter' menu should be closed;
|
||||
assert.containsNone(target, ".o_add_custom_filter_menu .dropdown-menu");
|
||||
|
||||
await toggleAddCustomFilter(target);
|
||||
// the 'Add Custom Filter' menu should be open;
|
||||
assert.containsOnce(target, ".o_add_custom_filter_menu .dropdown-menu");
|
||||
|
||||
await applyFilter(target);
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), ["Binary Field is set"]);
|
||||
assert.deepEqual(getDomain(controlPanel), [["image", "!=", false]]);
|
||||
|
||||
assert.containsOnce(target, ".o_menu_item");
|
||||
assert.containsOnce(target, ".o_add_custom_filter_menu button.dropdown-toggle");
|
||||
// the 'Add Custom Filter' menu should still be opened;
|
||||
assert.containsOnce(target, ".o_add_custom_filter_menu .dropdown-menu");
|
||||
});
|
||||
|
||||
QUnit.test("json field is available", async function (assert) {
|
||||
assert.expect(10);
|
||||
|
||||
const controlPanel = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchMenuTypes: ["filter"],
|
||||
searchViewFields: {
|
||||
json_field: {
|
||||
name: "json_field",
|
||||
string: "Json",
|
||||
type: "json",
|
||||
default: "{'1': 50}",
|
||||
searchable: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await toggleFilterMenu(target);
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), []);
|
||||
assert.deepEqual(getDomain(controlPanel), []);
|
||||
|
||||
assert.containsNone(target, ".o_menu_item");
|
||||
assert.containsOnce(target, ".o_add_custom_filter_menu button.dropdown-toggle");
|
||||
// the 'Add Custom Filter' menu should be closed;
|
||||
assert.containsNone(target, ".o_add_custom_filter_menu .dropdown-menu");
|
||||
|
||||
await toggleAddCustomFilter(target);
|
||||
// the 'Add Custom Filter' menu should be open;
|
||||
assert.containsOnce(target, ".o_add_custom_filter_menu .dropdown-menu");
|
||||
|
||||
await editConditionField(target, 0, "json_field");
|
||||
await applyFilter(target);
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), ['Json contains ""']);
|
||||
assert.deepEqual(getDomain(controlPanel), [["json_field", "ilike", ""]]);
|
||||
|
||||
await removeFacet(target);
|
||||
await toggleFilterMenu(target);
|
||||
await toggleAddCustomFilter(target);
|
||||
await editConditionField(target, 0, "json_field");
|
||||
await editConditionOperator(target, 0, "!=");
|
||||
await applyFilter(target);
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), ['Json is not equal to ""']);
|
||||
assert.deepEqual(getDomain(controlPanel), [["json_field", "!=", ""]]);
|
||||
});
|
||||
|
||||
QUnit.test("selection field: default and updated value", async function (assert) {
|
||||
assert.expect(11);
|
||||
|
||||
const controlPanel = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchMenuTypes: ["filter"],
|
||||
});
|
||||
|
||||
// Default value
|
||||
await toggleFilterMenu(target);
|
||||
|
||||
assert.containsN(target, ".o_menu_item", 0);
|
||||
assert.containsN(target, ".dropdown-divider", 0);
|
||||
|
||||
await toggleAddCustomFilter(target);
|
||||
await editConditionField(target, 0, "color");
|
||||
await applyFilter(target);
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), ['Color is "Black"']);
|
||||
assert.deepEqual(getDomain(controlPanel), [["color", "=", "black"]]);
|
||||
|
||||
assert.containsN(target, ".o_menu_item", 1);
|
||||
assert.containsN(target, ".dropdown-divider", 1);
|
||||
|
||||
// deactivate custom filter
|
||||
await removeFacet(target);
|
||||
|
||||
// Updated value
|
||||
await toggleFilterMenu(target);
|
||||
await toggleAddCustomFilter(target);
|
||||
await editConditionField(target, 0, "color");
|
||||
await editConditionValue(target, 0, "white");
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_generator_menu_value input,.o_generator_menu_value select")
|
||||
.value,
|
||||
"white"
|
||||
);
|
||||
await applyFilter(target);
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), ['Color is "White"']);
|
||||
assert.deepEqual(getDomain(controlPanel), [["color", "=", "white"]]);
|
||||
|
||||
assert.containsN(target, ".o_menu_item", 2);
|
||||
assert.containsN(target, ".dropdown-divider", 2);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"commit search with an extended proposition with field char does not cause a crash",
|
||||
async function (assert) {
|
||||
assert.expect(12);
|
||||
|
||||
const controlPanel = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchMenuTypes: ["filter"],
|
||||
searchViewFields: {
|
||||
many2one_field: {
|
||||
name: "many2one_field",
|
||||
string: "AAA",
|
||||
type: "many2one",
|
||||
searchable: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const steps = [
|
||||
{
|
||||
value: `a`,
|
||||
domain: [["many2one_field", "ilike", `a`]],
|
||||
facetContent: `AAA contains "a"`,
|
||||
},
|
||||
{
|
||||
value: `"a"`,
|
||||
domain: [["many2one_field", "ilike", `"a"`]],
|
||||
facetContent: `AAA contains ""a""`,
|
||||
},
|
||||
{
|
||||
value: `'a'`,
|
||||
domain: [["many2one_field", "ilike", `'a'`]],
|
||||
facetContent: `AAA contains "'a'"`,
|
||||
},
|
||||
{
|
||||
value: `'`,
|
||||
domain: [["many2one_field", "ilike", `'`]],
|
||||
facetContent: `AAA contains "'"`,
|
||||
},
|
||||
{
|
||||
value: `"`,
|
||||
domain: [["many2one_field", "ilike", `"`]],
|
||||
facetContent: `AAA contains """`,
|
||||
},
|
||||
{
|
||||
value: `\\`,
|
||||
domain: [["many2one_field", "ilike", `\\`]],
|
||||
facetContent: `AAA contains "\\"`,
|
||||
},
|
||||
];
|
||||
|
||||
for (const step of steps) {
|
||||
await toggleFilterMenu(target);
|
||||
await toggleAddCustomFilter(target);
|
||||
await editConditionValue(target, 0, step.value);
|
||||
await applyFilter(target);
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), [step.facetContent]);
|
||||
assert.deepEqual(getDomain(controlPanel), step.domain);
|
||||
|
||||
await removeFacet(target);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("custom filter date with equal operator", async function (assert) {
|
||||
patchTimeZone(-240);
|
||||
patchDate(2017, 1, 22, 12, 30, 0);
|
||||
|
||||
const controlPanel = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchMenuTypes: ["filter"],
|
||||
});
|
||||
|
||||
await toggleFilterMenu(target);
|
||||
await toggleAddCustomFilter(target);
|
||||
|
||||
await editConditionField(target, 0, "date_field");
|
||||
await editConditionOperator(target, 0, "=");
|
||||
await editConditionValue(target, 0, "01/01/2017");
|
||||
await applyFilter(target);
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), ['A date is equal to "01/01/2017"']);
|
||||
assert.deepEqual(getDomain(controlPanel), [["date_field", "=", "2017-01-01"]]);
|
||||
});
|
||||
|
||||
QUnit.test("custom filter datetime with equal operator", async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
patchTimeZone(-240);
|
||||
patchDate(2017, 1, 22, 12, 30, 0);
|
||||
|
||||
const controlPanel = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchMenuTypes: ["filter"],
|
||||
});
|
||||
|
||||
await toggleFilterMenu(target);
|
||||
await toggleAddCustomFilter(target);
|
||||
|
||||
await editConditionField(target, 0, "date_time_field");
|
||||
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_generator_menu_field").value,
|
||||
"date_time_field"
|
||||
);
|
||||
assert.strictEqual(target.querySelector(".o_generator_menu_operator").value, "between");
|
||||
assert.deepEqual(
|
||||
[...target.querySelectorAll(".o_generator_menu_value input[type=text]")].map(
|
||||
(v) => v.value
|
||||
),
|
||||
["02/22/2017 00:00:00", "02/22/2017 23:59:59"]
|
||||
);
|
||||
|
||||
await editConditionOperator(target, 0, "=");
|
||||
await editConditionValue(target, 0, "02/22/2017 11:00:00"); // in TZ
|
||||
await applyFilter(target);
|
||||
|
||||
assert.deepEqual(
|
||||
getFacetTexts(target),
|
||||
['DateTime is equal to "02/22/2017 11:00:00"'],
|
||||
"description should be in localized format"
|
||||
);
|
||||
assert.deepEqual(
|
||||
getDomain(controlPanel),
|
||||
[["date_time_field", "=", "2017-02-22 15:00:00"]],
|
||||
"domain should be in UTC format"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("custom filter datetime between operator", async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
patchTimeZone(-240);
|
||||
patchDate(2017, 1, 22, 12, 30, 0);
|
||||
|
||||
const controlPanel = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchMenuTypes: ["filter"],
|
||||
});
|
||||
|
||||
await toggleFilterMenu(target);
|
||||
await toggleAddCustomFilter(target);
|
||||
await editConditionField(target, 0, "date_time_field");
|
||||
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_generator_menu_field").value,
|
||||
"date_time_field"
|
||||
);
|
||||
assert.strictEqual(target.querySelector(".o_generator_menu_operator").value, "between");
|
||||
assert.deepEqual(
|
||||
[...target.querySelectorAll(".o_generator_menu_value input[type=text]")].map(
|
||||
(v) => v.value
|
||||
),
|
||||
["02/22/2017 00:00:00", "02/22/2017 23:59:59"]
|
||||
);
|
||||
|
||||
await editConditionValue(target, 0, "02/22/2017 11:00:00", 0); // in TZ
|
||||
await editConditionValue(target, 0, "02-22-2017 17:00:00", 1); // in TZ
|
||||
await applyFilter(target);
|
||||
|
||||
assert.deepEqual(
|
||||
getFacetTexts(target),
|
||||
['DateTime is between "02/22/2017 11:00:00 and 02/22/2017 17:00:00"'],
|
||||
"description should be in localized format"
|
||||
);
|
||||
assert.deepEqual(
|
||||
getDomain(controlPanel),
|
||||
[
|
||||
"&",
|
||||
["date_time_field", ">=", "2017-02-22 15:00:00"],
|
||||
["date_time_field", "<=", "2017-02-22 21:00:00"],
|
||||
],
|
||||
"domain should be in UTC format"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("input value parsing", async function (assert) {
|
||||
assert.expect(7);
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchMenuTypes: ["filter"],
|
||||
});
|
||||
|
||||
await toggleFilterMenu(target);
|
||||
await toggleAddCustomFilter(target);
|
||||
await addCondition(target);
|
||||
|
||||
await editConditionField(target, 0, "float_field");
|
||||
await editConditionField(target, 1, "id");
|
||||
|
||||
const [floatInput, idInput] = target.querySelectorAll(".o_generator_menu_value .o_input");
|
||||
|
||||
// Default values
|
||||
await editConditionValue(target, 0, "0.0");
|
||||
assert.strictEqual(floatInput.value, "0.0");
|
||||
|
||||
await editConditionValue(target, 1, "0");
|
||||
assert.strictEqual(idInput.value, "0");
|
||||
|
||||
// Float parsing
|
||||
await editConditionValue(target, 0, "4.2");
|
||||
assert.strictEqual(floatInput.value, "4.2");
|
||||
|
||||
await editConditionValue(target, 0, "DefinitelyValidFloat");
|
||||
// "DefinitelyValidFloat" cannot be entered in a input type number so that the input value is reset to 0
|
||||
assert.strictEqual(floatInput.value, "4.2");
|
||||
|
||||
// Number parsing
|
||||
await editConditionValue(target, 1, "4");
|
||||
assert.strictEqual(idInput.value, "4");
|
||||
|
||||
await editConditionValue(target, 1, "4.2");
|
||||
assert.strictEqual(idInput.value, "4");
|
||||
|
||||
await editConditionValue(target, 1, "DefinitelyValidID");
|
||||
// "DefinitelyValidID" cannot be entered in a input type number so that the input value is reset to 0
|
||||
assert.strictEqual(idInput.value, "0");
|
||||
});
|
||||
|
||||
QUnit.test("input value parsing with language", async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchMenuTypes: ["filter"],
|
||||
});
|
||||
|
||||
// Needs to be done after services have been started
|
||||
patchWithCleanup(localization, {
|
||||
decimalPoint: ",",
|
||||
thousandsSep: "",
|
||||
grouping: [3, 0],
|
||||
});
|
||||
|
||||
await toggleFilterMenu(target);
|
||||
await toggleAddCustomFilter(target);
|
||||
|
||||
await editConditionField(target, 0, "float_field");
|
||||
|
||||
const [floatInput] = target.querySelectorAll(".o_generator_menu_value .o_input");
|
||||
|
||||
// Default values
|
||||
assert.strictEqual(floatInput.value, "0,0");
|
||||
|
||||
// Float parsing
|
||||
await editConditionValue(target, 0, "4,");
|
||||
assert.strictEqual(floatInput.value, "4,0");
|
||||
|
||||
await editConditionValue(target, 0, "4,2");
|
||||
assert.strictEqual(floatInput.value, "4,2");
|
||||
|
||||
await editConditionValue(target, 0, "4,2,");
|
||||
assert.strictEqual(floatInput.value, "42,0"); // because of the en localization fallback in parsers.
|
||||
|
||||
await editConditionValue(target, 0, "DefinitelyValidFloat");
|
||||
|
||||
// The input here is a string, resulting in a parsing error instead of 0
|
||||
assert.strictEqual(floatInput.value, "42,0");
|
||||
});
|
||||
|
||||
QUnit.test("add custom filter with multiple values", async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const controlPanel = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchMenuTypes: ["filter"],
|
||||
});
|
||||
|
||||
await toggleFilterMenu(target);
|
||||
await toggleAddCustomFilter(target);
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
await addCondition(target);
|
||||
}
|
||||
|
||||
await editConditionField(target, 0, "date_field");
|
||||
await editConditionValue(target, 0, "01/09/1997");
|
||||
|
||||
await editConditionField(target, 1, "boolean_field");
|
||||
await editConditionOperator(target, 1, "!=");
|
||||
|
||||
await editConditionField(target, 2, "char_field");
|
||||
await editConditionValue(target, 2, "I will be deleted anyway");
|
||||
|
||||
await editConditionField(target, 3, "float_field");
|
||||
await editConditionValue(target, 3, 7.2);
|
||||
|
||||
await editConditionField(target, 4, "id");
|
||||
await editConditionValue(target, 4, 9);
|
||||
|
||||
const thirdcondition = target.querySelectorAll(".o_filter_condition")[2];
|
||||
|
||||
await click(thirdcondition, ".o_generator_menu_delete");
|
||||
await applyFilter(target);
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), [
|
||||
[
|
||||
'A date is equal to "01/09/1997"',
|
||||
"Boolean Field is No",
|
||||
'Floaty McFloatface is equal to "7.2"',
|
||||
'ID is "9"',
|
||||
].join("or"),
|
||||
]);
|
||||
assert.deepEqual(getDomain(controlPanel), [
|
||||
"|",
|
||||
["date_field", "=", "1997-01-09"],
|
||||
"|",
|
||||
["boolean_field", "!=", true],
|
||||
"|",
|
||||
["float_field", "=", 7.2],
|
||||
["id", "=", 9],
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("delete button is visible", async function (assert) {
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchMenuTypes: ["filter"],
|
||||
});
|
||||
|
||||
await toggleFilterMenu(target);
|
||||
await toggleAddCustomFilter(target);
|
||||
|
||||
assert.containsNone(
|
||||
target,
|
||||
".o_generator_menu_delete",
|
||||
"There is no delete button by default"
|
||||
);
|
||||
|
||||
await addCondition(target);
|
||||
assert.containsN(
|
||||
target,
|
||||
".o_generator_menu_delete",
|
||||
2,
|
||||
"A delete button has been added to each condition"
|
||||
);
|
||||
assert.containsN(
|
||||
target,
|
||||
"i.o_generator_menu_delete.fa-trash-o",
|
||||
2,
|
||||
"The delete button is shown as a trash icon"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
import { expect, test } from "@odoo/hoot";
|
||||
import { queryAllTexts } from "@odoo/hoot-dom";
|
||||
import {
|
||||
defineModels,
|
||||
fields,
|
||||
getFacetTexts,
|
||||
isItemSelected,
|
||||
isOptionSelected,
|
||||
models,
|
||||
mountWithSearch,
|
||||
selectGroup,
|
||||
toggleMenuItem,
|
||||
toggleSearchBarMenu,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { SearchBar } from "@web/search/search_bar/search_bar";
|
||||
|
||||
class Foo extends models.Model {
|
||||
bar = fields.Many2one({ relation: "partner", groupable: false });
|
||||
birthday = fields.Date();
|
||||
date = fields.Date();
|
||||
float = fields.Float({ groupable: false });
|
||||
foo = fields.Char();
|
||||
}
|
||||
|
||||
class Partner extends models.Model {}
|
||||
|
||||
defineModels([Foo, Partner]);
|
||||
|
||||
test(`simple rendering`, async () => {
|
||||
await mountWithSearch(SearchBar, {
|
||||
resModel: "foo",
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
});
|
||||
|
||||
await toggleSearchBarMenu();
|
||||
expect(`.o_group_by_menu option[disabled]`).toHaveText(`Custom Group`);
|
||||
expect(queryAllTexts`.o_add_custom_group_menu option:not([disabled])`).toEqual([
|
||||
"Birthday",
|
||||
"Created on",
|
||||
"Date",
|
||||
"Display name",
|
||||
"Foo",
|
||||
"Last Modified on",
|
||||
]);
|
||||
});
|
||||
|
||||
test(`the ID field should not be proposed in "Custom Group" menu`, async () => {
|
||||
await mountWithSearch(SearchBar, {
|
||||
resModel: "foo",
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
searchViewFields: {
|
||||
foo: { string: "Foo", type: "char", store: true, sortable: true, groupable: true },
|
||||
id: { string: "ID", type: "integer", sortable: true, groupable: true },
|
||||
},
|
||||
});
|
||||
|
||||
await toggleSearchBarMenu();
|
||||
expect(queryAllTexts`.o_add_custom_group_menu option:not([disabled])`).toEqual(["Foo"]);
|
||||
});
|
||||
|
||||
test(`stored many2many should be proposed in "Custom Group" menu`, async () => {
|
||||
await mountWithSearch(SearchBar, {
|
||||
resModel: "foo",
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
searchViewFields: {
|
||||
char_a: {
|
||||
string: "Char A",
|
||||
type: "char",
|
||||
store: true,
|
||||
sortable: true,
|
||||
groupable: true,
|
||||
},
|
||||
m2m_no_stored: { string: "M2M Not Stored", type: "many2many" },
|
||||
m2m_stored: {
|
||||
string: "M2M Stored",
|
||||
type: "many2many",
|
||||
store: true,
|
||||
groupable: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await toggleSearchBarMenu();
|
||||
expect(queryAllTexts`.o_add_custom_group_menu option:not([disabled])`).toEqual([
|
||||
"Char A",
|
||||
"M2M Stored",
|
||||
]);
|
||||
});
|
||||
|
||||
test(`add a date field in "Custom Group" activate a groupby with global default option "month"`, async () => {
|
||||
const component = await mountWithSearch(SearchBar, {
|
||||
resModel: "foo",
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
searchViewFields: {
|
||||
date: {
|
||||
string: "Date",
|
||||
type: "date",
|
||||
store: true,
|
||||
sortable: true,
|
||||
groupable: true,
|
||||
},
|
||||
id: { sortable: true, string: "ID", type: "integer", groupable: true },
|
||||
},
|
||||
});
|
||||
|
||||
await toggleSearchBarMenu();
|
||||
expect(component.env.searchModel.groupBy).toEqual([]);
|
||||
expect(`.o_add_custom_group_menu`).toHaveCount(1); // Custom Group
|
||||
|
||||
await selectGroup("date");
|
||||
expect(component.env.searchModel.groupBy).toEqual(["date:month"]);
|
||||
expect(getFacetTexts()).toEqual(["Date: Month"]);
|
||||
expect(isItemSelected("Date")).toBe(true);
|
||||
|
||||
await toggleMenuItem("Date");
|
||||
expect(isOptionSelected("Date", "Month")).toBe(true);
|
||||
});
|
||||
|
||||
test(`click on add custom group toggle group selector`, async () => {
|
||||
await mountWithSearch(SearchBar, {
|
||||
resModel: "foo",
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
searchViewFields: {
|
||||
date: {
|
||||
sortable: true,
|
||||
name: "date",
|
||||
string: "Super Date",
|
||||
type: "date",
|
||||
groupable: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await toggleSearchBarMenu();
|
||||
expect(`.o_add_custom_group_menu option[disabled]`).toHaveText("Custom Group");
|
||||
|
||||
// Single select node with a single option
|
||||
expect(`.o_add_custom_group_menu option:not([disabled])`).toHaveCount(1);
|
||||
expect(`.o_add_custom_group_menu option:not([disabled])`).toHaveText("Super Date");
|
||||
});
|
||||
|
||||
test(`select a field name in Custom Group menu properly trigger the corresponding field`, async () => {
|
||||
await mountWithSearch(SearchBar, {
|
||||
resModel: "foo",
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
searchViewFields: {
|
||||
candle_light: {
|
||||
sortable: true,
|
||||
groupable: true,
|
||||
string: "Candlelight",
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await toggleSearchBarMenu();
|
||||
await selectGroup("candle_light");
|
||||
expect(`.o_group_by_menu .o_menu_item`).toHaveCount(2);
|
||||
expect(`.o_add_custom_group_menu`).toHaveCount(1);
|
||||
expect(getFacetTexts()).toEqual(["Candlelight"]);
|
||||
});
|
||||
|
|
@ -1,244 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { ControlPanel } from "@web/search/control_panel/control_panel";
|
||||
import { getFixture, patchWithCleanup } from "../helpers/utils";
|
||||
import {
|
||||
applyGroup,
|
||||
getFacetTexts,
|
||||
isItemSelected,
|
||||
isOptionSelected,
|
||||
makeWithSearch,
|
||||
setupControlPanelServiceRegistry,
|
||||
toggleAddCustomGroup,
|
||||
toggleGroupByMenu,
|
||||
toggleMenuItem,
|
||||
} from "./helpers";
|
||||
|
||||
let target;
|
||||
let serverData;
|
||||
QUnit.module("Search", (hooks) => {
|
||||
hooks.beforeEach(async () => {
|
||||
serverData = {
|
||||
models: {
|
||||
foo: {
|
||||
fields: {
|
||||
bar: { string: "Bar", type: "many2one", relation: "partner" },
|
||||
birthday: { string: "Birthday", type: "date", store: true, sortable: true },
|
||||
date_field: { string: "Date", type: "date", store: true, sortable: true },
|
||||
float_field: { string: "Float", type: "float", group_operator: "sum" },
|
||||
foo: { string: "Foo", type: "char", store: true, sortable: true },
|
||||
},
|
||||
records: {},
|
||||
},
|
||||
},
|
||||
views: {
|
||||
"foo,false,search": `<search/>`,
|
||||
},
|
||||
};
|
||||
setupControlPanelServiceRegistry();
|
||||
patchWithCleanup(browser, {
|
||||
setTimeout: (fn) => fn(),
|
||||
clearTimeout: () => {},
|
||||
});
|
||||
target = getFixture();
|
||||
});
|
||||
|
||||
QUnit.module("CustomGroupByItem");
|
||||
|
||||
QUnit.test("simple rendering", async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
});
|
||||
|
||||
await toggleGroupByMenu(target);
|
||||
|
||||
const customGroupByItem = target.querySelector(".o_add_custom_group_menu");
|
||||
assert.strictEqual(customGroupByItem.innerText.trim(), "Add Custom Group");
|
||||
|
||||
assert.containsOnce(customGroupByItem, "button.dropdown-toggle");
|
||||
assert.containsNone(customGroupByItem, ".dropdown-menu");
|
||||
|
||||
await toggleAddCustomGroup(target);
|
||||
|
||||
assert.containsOnce(customGroupByItem, ".dropdown-menu");
|
||||
|
||||
assert.deepEqual(
|
||||
[...target.querySelectorAll(".o_add_custom_group_menu select option")].map(
|
||||
(el) => el.innerText
|
||||
),
|
||||
["Birthday", "Date", "Foo"]
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
'the ID field should not be proposed in "Add Custom Group" menu',
|
||||
async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
searchViewFields: {
|
||||
foo: { string: "Foo", type: "char", store: true, sortable: true },
|
||||
id: { sortable: true, string: "ID", type: "integer" },
|
||||
},
|
||||
});
|
||||
|
||||
await toggleGroupByMenu(target);
|
||||
await toggleAddCustomGroup(target);
|
||||
|
||||
assert.deepEqual(
|
||||
[
|
||||
...target.querySelectorAll(
|
||||
".o_add_custom_group_menu .dropdown-menu select option"
|
||||
),
|
||||
].map((el) => el.innerText),
|
||||
["Foo"]
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
'stored many2many should be proposed in "Add Custom Group" menu',
|
||||
async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
searchViewFields: {
|
||||
char_a: { string: "Char A", type: "char", store: true, sortable: true },
|
||||
m2m_no_stored: { string: "M2M Not Stored", type: "many2many" },
|
||||
m2m_stored: {
|
||||
string: "M2M Stored",
|
||||
type: "many2many",
|
||||
store: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await toggleGroupByMenu(target);
|
||||
await toggleAddCustomGroup(target);
|
||||
|
||||
assert.deepEqual(
|
||||
[
|
||||
...target.querySelectorAll(
|
||||
".o_add_custom_group_menu .dropdown-menu select option"
|
||||
),
|
||||
].map((el) => el.innerText),
|
||||
["Char A", "M2M Stored"]
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
'add a date field in "Add Custom Group" activate a groupby with global default option "month"',
|
||||
async function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
const controlPanel = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
searchViewFields: {
|
||||
date_field: { string: "Date", type: "date", store: true, sortable: true },
|
||||
id: { sortable: true, string: "ID", type: "integer" },
|
||||
},
|
||||
});
|
||||
await toggleGroupByMenu(target);
|
||||
|
||||
assert.deepEqual(controlPanel.env.searchModel.groupBy, []);
|
||||
assert.containsNone(target, ".o_menu_item");
|
||||
|
||||
await toggleAddCustomGroup(target);
|
||||
await applyGroup(target);
|
||||
|
||||
assert.deepEqual(controlPanel.env.searchModel.groupBy, ["date_field:month"]);
|
||||
assert.deepEqual(getFacetTexts(target), ["Date: Month"]);
|
||||
assert.ok(isItemSelected(target, "Date"));
|
||||
|
||||
await toggleMenuItem(target, "Date");
|
||||
|
||||
assert.ok(isOptionSelected(target, "Date", "Month"));
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("click on add custom group toggle group selector", async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewFields: {
|
||||
date: { sortable: true, name: "date", string: "Super Date", type: "date" },
|
||||
},
|
||||
});
|
||||
|
||||
await toggleGroupByMenu(target);
|
||||
|
||||
const addCustomGroupMenu = target.querySelector(".o_add_custom_group_menu");
|
||||
|
||||
assert.strictEqual(addCustomGroupMenu.innerText.trim(), "Add Custom Group");
|
||||
|
||||
await toggleAddCustomGroup(target);
|
||||
|
||||
// Single select node with a single option
|
||||
assert.containsOnce(target, ".o_add_custom_group_menu .dropdown-menu select");
|
||||
assert.strictEqual(
|
||||
target
|
||||
.querySelector(".o_add_custom_group_menu .dropdown-menu select option")
|
||||
.innerText.trim(),
|
||||
"Super Date"
|
||||
);
|
||||
|
||||
// Button apply
|
||||
assert.containsOnce(target, ".o_add_custom_group_menu .dropdown-menu .btn");
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"select a field name in Add Custom Group menu properly trigger the corresponding field",
|
||||
async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewFields: {
|
||||
candle_light: {
|
||||
sortable: true,
|
||||
string: "Candlelight",
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await toggleGroupByMenu(target);
|
||||
await toggleAddCustomGroup(target);
|
||||
await applyGroup(target);
|
||||
|
||||
assert.containsOnce(target, ".o_group_by_menu .o_menu_item");
|
||||
assert.containsOnce(target, ".o_add_custom_group_menu .dropdown-toggle");
|
||||
assert.containsOnce(target, ".o_add_custom_group_menu .dropdown-menu");
|
||||
assert.deepEqual(getFacetTexts(target), ["Candlelight"]);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
@ -1,391 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { click, getFixture, patchDate } from "@web/../tests/helpers/utils";
|
||||
import { createWebClient, doAction } from "@web/../tests/webclient/helpers";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { ControlPanel } from "@web/search/control_panel/control_panel";
|
||||
import { FavoriteMenu } from "@web/search/favorite_menu/favorite_menu";
|
||||
import { SearchBar } from "@web/search/search_bar/search_bar";
|
||||
import {
|
||||
deleteFavorite,
|
||||
getFacetTexts,
|
||||
isItemSelected,
|
||||
makeWithSearch,
|
||||
setupControlPanelFavoriteMenuRegistry,
|
||||
setupControlPanelServiceRegistry,
|
||||
toggleComparisonMenu,
|
||||
toggleFavoriteMenu,
|
||||
toggleMenuItem,
|
||||
} from "@web/../tests/search/helpers";
|
||||
|
||||
import { Component, onWillUpdateProps, xml } from "@odoo/owl";
|
||||
const viewRegistry = registry.category("views");
|
||||
const favoriteMenuRegistry = registry.category("favoriteMenu");
|
||||
|
||||
function getDomain(comp) {
|
||||
return comp.env.searchModel.domain;
|
||||
}
|
||||
|
||||
let serverData;
|
||||
let target;
|
||||
QUnit.module("Search", (hooks) => {
|
||||
hooks.beforeEach(async () => {
|
||||
target = getFixture();
|
||||
serverData = {
|
||||
models: {
|
||||
foo: {
|
||||
fields: {
|
||||
bar: { string: "Bar", type: "many2one", relation: "partner" },
|
||||
birthday: { string: "Birthday", type: "date", store: true, sortable: true },
|
||||
date_field: { string: "Date", type: "date", store: true, sortable: true },
|
||||
float_field: { string: "Float", type: "float", group_operator: "sum" },
|
||||
foo: { string: "Foo", type: "char", store: true, sortable: true },
|
||||
},
|
||||
records: {},
|
||||
},
|
||||
},
|
||||
views: {
|
||||
"foo,false,search": `<search/>`,
|
||||
},
|
||||
};
|
||||
setupControlPanelFavoriteMenuRegistry();
|
||||
setupControlPanelServiceRegistry();
|
||||
});
|
||||
|
||||
QUnit.module("FavoriteMenu");
|
||||
|
||||
QUnit.test(
|
||||
"simple rendering with no favorite (without ability to save)",
|
||||
async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
favoriteMenuRegistry.remove("custom-favorite-item");
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchMenuTypes: ["favorite"],
|
||||
searchViewId: false,
|
||||
config: {
|
||||
getDisplayName: () => "Action Name",
|
||||
},
|
||||
});
|
||||
|
||||
assert.containsOnce(target, "div.o_favorite_menu > button i.fa.fa-star");
|
||||
assert.strictEqual(
|
||||
target
|
||||
.querySelector("div.o_favorite_menu > button span")
|
||||
.innerText.trim()
|
||||
.toUpperCase(),
|
||||
"FAVORITES"
|
||||
);
|
||||
|
||||
await toggleFavoriteMenu(target);
|
||||
assert.containsOnce(
|
||||
target,
|
||||
"div.o_favorite_menu > .dropdown-menu",
|
||||
"the menu should be opened"
|
||||
);
|
||||
assert.containsNone(target, ".dropdown-menu *", "the menu should be empty");
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("simple rendering with no favorite", async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchMenuTypes: ["favorite"],
|
||||
searchViewId: false,
|
||||
config: {
|
||||
getDisplayName: () => "Action Name",
|
||||
},
|
||||
});
|
||||
|
||||
assert.containsOnce(target, "div.o_favorite_menu > button i.fa.fa-star");
|
||||
assert.strictEqual(
|
||||
target
|
||||
.querySelector("div.o_favorite_menu > button span")
|
||||
.innerText.trim()
|
||||
.toUpperCase(),
|
||||
"FAVORITES"
|
||||
);
|
||||
|
||||
await toggleFavoriteMenu(target);
|
||||
assert.containsOnce(
|
||||
target,
|
||||
"div.o_favorite_menu > .dropdown-menu",
|
||||
"the menu should be opened"
|
||||
);
|
||||
assert.containsNone(target, ".dropdown-menu .dropdown-divider");
|
||||
assert.containsOnce(target, ".dropdown-menu .o_add_favorite");
|
||||
});
|
||||
|
||||
QUnit.test("delete an active favorite", async function (assert) {
|
||||
assert.expect(14);
|
||||
|
||||
class ToyController extends Component {
|
||||
setup() {
|
||||
assert.deepEqual(this.props.domain, [["foo", "=", "qsdf"]]);
|
||||
onWillUpdateProps((nextProps) => {
|
||||
assert.deepEqual(nextProps.domain, []);
|
||||
});
|
||||
}
|
||||
}
|
||||
ToyController.components = { FavoriteMenu, SearchBar };
|
||||
ToyController.template = xml`
|
||||
<div>
|
||||
<SearchBar/>
|
||||
<FavoriteMenu/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
viewRegistry.add("toy", {
|
||||
type: "toy",
|
||||
display_name: "Toy",
|
||||
Controller: ToyController,
|
||||
});
|
||||
|
||||
serverData.views["foo,false,toy"] = `<toy />`;
|
||||
serverData.models.foo.filters = [
|
||||
{
|
||||
context: "{}",
|
||||
domain: "[['foo', '=', 'qsdf']]",
|
||||
id: 7,
|
||||
is_default: true,
|
||||
name: "My favorite",
|
||||
sort: "[]",
|
||||
user_id: [2, "Mitchell Admin"],
|
||||
},
|
||||
];
|
||||
|
||||
const webClient = await createWebClient({
|
||||
serverData,
|
||||
mockRPC: async (_, args) => {
|
||||
if (args.model === "ir.filters" && args.method === "unlink") {
|
||||
assert.step("deleteFavorite");
|
||||
return { result: true }; // mocked unlink result
|
||||
}
|
||||
},
|
||||
});
|
||||
webClient.env.bus.on("CLEAR-CACHES", webClient, () => assert.step("CLEAR-CACHES"));
|
||||
await doAction(webClient, {
|
||||
name: "Action",
|
||||
res_model: "foo",
|
||||
type: "ir.actions.act_window",
|
||||
views: [[false, "toy"]],
|
||||
});
|
||||
|
||||
await toggleFavoriteMenu(target);
|
||||
const favorite = target.querySelector(".o_favorite_menu .dropdown-item");
|
||||
assert.equal(favorite.innerText, "My favorite");
|
||||
assert.deepEqual(favorite.getAttribute("role"), "menuitemcheckbox");
|
||||
assert.deepEqual(favorite.ariaChecked, "true");
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), ["My favorite"]);
|
||||
assert.hasClass(target.querySelector(".o_favorite_menu .o_menu_item"), "selected");
|
||||
|
||||
await deleteFavorite(target, 0);
|
||||
|
||||
assert.verifySteps([]);
|
||||
|
||||
await click(document.querySelector("div.o_dialog footer button"));
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), []);
|
||||
assert.containsNone(target, ".o_favorite_menu .o_menu_item");
|
||||
assert.containsOnce(target, ".o_favorite_menu .o_add_favorite");
|
||||
assert.verifySteps(["deleteFavorite", "CLEAR-CACHES"]);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"default favorite is not activated if activateFavorite is set to false",
|
||||
async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
const controlPanel = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchMenuTypes: ["favorite"],
|
||||
searchViewId: false,
|
||||
irFilters: [
|
||||
{
|
||||
context: "{}",
|
||||
domain: "[('foo', '=', 'a')]",
|
||||
id: 7,
|
||||
is_default: true,
|
||||
name: "My favorite",
|
||||
sort: "[]",
|
||||
user_id: [2, "Mitchell Admin"],
|
||||
},
|
||||
],
|
||||
activateFavorite: false,
|
||||
});
|
||||
|
||||
await toggleFavoriteMenu(target);
|
||||
|
||||
assert.notOk(isItemSelected(target, "My favorite"));
|
||||
assert.deepEqual(getDomain(controlPanel), []);
|
||||
assert.deepEqual(getFacetTexts(target), []);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
'toggle favorite correctly clears filter, groupbys, comparison and field "options"',
|
||||
async function (assert) {
|
||||
patchDate(2019, 6, 31, 13, 43, 0);
|
||||
|
||||
const controlPanel = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchMenuTypes: ["filter", "groupBy", "comparison", "favorite"],
|
||||
searchViewId: false,
|
||||
irFilters: [
|
||||
{
|
||||
context: `
|
||||
{
|
||||
"group_by": ["foo"],
|
||||
"comparison": {
|
||||
"favorite comparison content": "bla bla..."
|
||||
},
|
||||
}
|
||||
`,
|
||||
domain: "['!', ['foo', '=', 'qsdf']]",
|
||||
id: 7,
|
||||
is_default: false,
|
||||
name: "My favorite",
|
||||
sort: "[]",
|
||||
user_id: [2, "Mitchell Admin"],
|
||||
},
|
||||
],
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<field string="Foo" name="foo"/>
|
||||
<filter string="Date Field Filter" name="positive" date="date_field" default_period="this_year"/>
|
||||
<filter string="Date Field Groupby" name="coolName" context="{'group_by': 'date_field'}"/>
|
||||
</search>
|
||||
`,
|
||||
context: {
|
||||
search_default_positive: true,
|
||||
search_default_coolName: true,
|
||||
search_default_foo: "a",
|
||||
},
|
||||
});
|
||||
|
||||
let domain = controlPanel.env.searchModel.domain;
|
||||
let groupBy = controlPanel.env.searchModel.groupBy;
|
||||
let comparison = controlPanel.env.searchModel.getFullComparison();
|
||||
|
||||
assert.deepEqual(domain, [
|
||||
"&",
|
||||
["foo", "ilike", "a"],
|
||||
"&",
|
||||
["date_field", ">=", "2019-01-01"],
|
||||
["date_field", "<=", "2019-12-31"],
|
||||
]);
|
||||
assert.deepEqual(groupBy, ["date_field:month"]);
|
||||
assert.deepEqual(comparison, null);
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), [
|
||||
"Foo\na",
|
||||
"Date Field Filter: 2019",
|
||||
"Date Field Groupby: Month",
|
||||
]);
|
||||
|
||||
// activate a comparison
|
||||
await toggleComparisonMenu(target);
|
||||
await toggleMenuItem(target, "Date Field Filter: Previous Period");
|
||||
|
||||
domain = controlPanel.env.searchModel.domain;
|
||||
groupBy = controlPanel.env.searchModel.groupBy;
|
||||
comparison = controlPanel.env.searchModel.getFullComparison();
|
||||
|
||||
assert.deepEqual(domain, [["foo", "ilike", "a"]]);
|
||||
assert.deepEqual(groupBy, ["date_field:month"]);
|
||||
assert.deepEqual(comparison, {
|
||||
comparisonId: "previous_period",
|
||||
comparisonRange: [
|
||||
"&",
|
||||
["date_field", ">=", "2018-01-01"],
|
||||
["date_field", "<=", "2018-12-31"],
|
||||
],
|
||||
comparisonRangeDescription: "2018",
|
||||
fieldDescription: "Date Field Filter",
|
||||
fieldName: "date_field",
|
||||
range: [
|
||||
"&",
|
||||
["date_field", ">=", "2019-01-01"],
|
||||
["date_field", "<=", "2019-12-31"],
|
||||
],
|
||||
rangeDescription: "2019",
|
||||
});
|
||||
|
||||
// activate the unique existing favorite
|
||||
await toggleFavoriteMenu(target);
|
||||
const favorite = target.querySelector(".o_favorite_menu .dropdown-item");
|
||||
assert.equal(favorite.innerText, "My favorite");
|
||||
assert.deepEqual(favorite.getAttribute("role"), "menuitemcheckbox");
|
||||
assert.deepEqual(favorite.ariaChecked, "false");
|
||||
await toggleMenuItem(target, 0);
|
||||
assert.deepEqual(favorite.ariaChecked, "true");
|
||||
|
||||
domain = controlPanel.env.searchModel.domain;
|
||||
groupBy = controlPanel.env.searchModel.groupBy;
|
||||
comparison = controlPanel.env.searchModel.getFullComparison();
|
||||
|
||||
assert.deepEqual(domain, ["!", ["foo", "=", "qsdf"]]);
|
||||
assert.deepEqual(groupBy, ["foo"]);
|
||||
assert.deepEqual(comparison, {
|
||||
"favorite comparison content": "bla bla...",
|
||||
});
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), ["My favorite"]);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.skip("modal loads saved search filters", async function (assert) {
|
||||
/** @todo I don't know yet how to convert this test */
|
||||
// assert.expect(1);
|
||||
// const data = {
|
||||
// partner: {
|
||||
// fields: {
|
||||
// bar: { string: "Bar", type: "many2one", relation: "partner" },
|
||||
// },
|
||||
// // 10 records so that the Search button shows
|
||||
// records: Array.apply(null, Array(10)).map(function (_, i) {
|
||||
// return { id: i, display_name: "Record " + i, bar: 1 };
|
||||
// }),
|
||||
// },
|
||||
// };
|
||||
// const form = await createView({
|
||||
// arch: `
|
||||
// <form string="Partners">
|
||||
// <sheet>
|
||||
// <group>
|
||||
// <field name="bar"/>
|
||||
// </group>
|
||||
// </sheet>
|
||||
// </form>`,
|
||||
// data,
|
||||
// model: "partner",
|
||||
// res_id: 1,
|
||||
// View: FormView,
|
||||
// interceptsPropagate: {
|
||||
// load_views: function (ev) {
|
||||
// assert.ok(
|
||||
// ev.data.options.load_filters,
|
||||
// "opening dialog should load the filters"
|
||||
// );
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
// await testUtils.form.clickEdit(form);
|
||||
// await testUtils.fields.many2one.clickOpenDropdown("bar");
|
||||
// await testUtils.fields.many2one.clickItem("bar", "Search");
|
||||
// form.destroy();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,587 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { getFixture, patchDate, patchWithCleanup } from "@web/../tests/helpers/utils";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { ControlPanel } from "@web/search/control_panel/control_panel";
|
||||
import {
|
||||
getFacetTexts,
|
||||
isItemSelected,
|
||||
isOptionSelected,
|
||||
makeWithSearch,
|
||||
setupControlPanelServiceRegistry,
|
||||
toggleFilterMenu,
|
||||
toggleMenuItem,
|
||||
toggleMenuItemOption,
|
||||
} from "./helpers";
|
||||
|
||||
function getDomain(controlPanel) {
|
||||
return controlPanel.env.searchModel.domain;
|
||||
}
|
||||
|
||||
function getContext(controlPanel) {
|
||||
return controlPanel.env.searchModel.context;
|
||||
}
|
||||
|
||||
let target;
|
||||
let serverData;
|
||||
QUnit.module("Search", (hooks) => {
|
||||
hooks.beforeEach(async () => {
|
||||
serverData = {
|
||||
models: {
|
||||
foo: {
|
||||
fields: {
|
||||
date_field: {
|
||||
string: "Date",
|
||||
type: "date",
|
||||
store: true,
|
||||
sortable: true,
|
||||
searchable: true,
|
||||
},
|
||||
foo: { string: "Foo", type: "char", store: true, sortable: true },
|
||||
},
|
||||
records: {},
|
||||
},
|
||||
},
|
||||
views: {
|
||||
"foo,false,search": `<search/>`,
|
||||
},
|
||||
};
|
||||
setupControlPanelServiceRegistry();
|
||||
patchWithCleanup(browser, {
|
||||
setTimeout: (fn) => fn(),
|
||||
clearTimeout: () => {},
|
||||
});
|
||||
target = getFixture();
|
||||
});
|
||||
|
||||
QUnit.module("FilterMenu");
|
||||
|
||||
QUnit.test("simple rendering with no filter", async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchMenuTypes: ["filter"],
|
||||
});
|
||||
|
||||
await toggleFilterMenu(target);
|
||||
assert.containsNone(target, ".o_menu_item");
|
||||
assert.containsNone(target, ".dropdown-divider");
|
||||
assert.containsOnce(target, ".o_add_custom_filter_menu");
|
||||
});
|
||||
|
||||
QUnit.test("simple rendering with a single filter", async function (assert) {
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchMenuTypes: ["filter"],
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Foo" name="foo" domain="[]"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
|
||||
await toggleFilterMenu(target);
|
||||
assert.containsOnce(target, ".o_menu_item");
|
||||
assert.containsOnce(target, ".o_menu_item[role=menuitemcheckbox]");
|
||||
assert.deepEqual(target.querySelector(".o_menu_item").ariaChecked, "false");
|
||||
assert.containsOnce(target, ".dropdown-divider");
|
||||
assert.containsOnce(target, ".o_add_custom_filter_menu");
|
||||
});
|
||||
|
||||
QUnit.test('toggle a "simple" filter in filter menu works', async function (assert) {
|
||||
const controlPanel = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchMenuTypes: ["filter"],
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Foo" name="foo" domain="[('foo', '=', 'qsdf')]"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
|
||||
await toggleFilterMenu(target);
|
||||
assert.deepEqual(getFacetTexts(target), []);
|
||||
assert.notOk(isItemSelected(target, "Foo"));
|
||||
assert.deepEqual(getDomain(controlPanel), []);
|
||||
assert.containsOnce(target, ".o_menu_item[role=menuitemcheckbox]");
|
||||
assert.deepEqual(target.querySelector(".o_menu_item").ariaChecked, "false");
|
||||
|
||||
await toggleMenuItem(target, "Foo");
|
||||
assert.deepEqual(target.querySelector(".o_menu_item").ariaChecked, "true");
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), ["Foo"]);
|
||||
assert.containsOnce(
|
||||
target.querySelector(".o_searchview .o_searchview_facet"),
|
||||
"span.fa.fa-filter.o_searchview_facet_label"
|
||||
);
|
||||
assert.ok(isItemSelected(target, "Foo"));
|
||||
assert.deepEqual(getDomain(controlPanel), [["foo", "=", "qsdf"]]);
|
||||
|
||||
await toggleMenuItem(target, "Foo");
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), []);
|
||||
assert.notOk(isItemSelected(target, "Foo"));
|
||||
assert.deepEqual(getDomain(controlPanel), []);
|
||||
});
|
||||
|
||||
QUnit.test("filter by a date field using period works", async function (assert) {
|
||||
assert.expect(57);
|
||||
|
||||
patchDate(2017, 2, 22, 1, 0, 0);
|
||||
|
||||
const controlPanel = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchMenuTypes: ["filter"],
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Date" name="date_field" date="date_field"/>
|
||||
</search>
|
||||
`,
|
||||
context: { search_default_date_field: 1 },
|
||||
});
|
||||
|
||||
await toggleFilterMenu(target);
|
||||
await toggleMenuItem(target, "Date");
|
||||
|
||||
const optionEls = target.querySelectorAll(".dropdown .o_item_option");
|
||||
|
||||
// default filter should be activated with the global default period 'this_month'
|
||||
assert.deepEqual(getDomain(controlPanel), [
|
||||
"&",
|
||||
["date_field", ">=", "2017-03-01"],
|
||||
["date_field", "<=", "2017-03-31"],
|
||||
]);
|
||||
assert.ok(isItemSelected(target, "Date"));
|
||||
assert.ok(isOptionSelected(target, "Date", "March"));
|
||||
|
||||
// check option descriptions
|
||||
const optionDescriptions = [...optionEls].map((e) => e.innerText.trim());
|
||||
const expectedDescriptions = [
|
||||
"March",
|
||||
"February",
|
||||
"January",
|
||||
"Q4",
|
||||
"Q3",
|
||||
"Q2",
|
||||
"Q1",
|
||||
"2017",
|
||||
"2016",
|
||||
"2015",
|
||||
];
|
||||
assert.deepEqual(optionDescriptions, expectedDescriptions);
|
||||
|
||||
// check generated domains
|
||||
const steps = [
|
||||
{
|
||||
toggledOption: "March",
|
||||
resultingFacet: "Date: 2017",
|
||||
selectedoptions: ["2017"],
|
||||
domain: [
|
||||
"&",
|
||||
["date_field", ">=", "2017-01-01"],
|
||||
["date_field", "<=", "2017-12-31"],
|
||||
],
|
||||
},
|
||||
{
|
||||
toggledOption: "February",
|
||||
resultingFacet: "Date: February 2017",
|
||||
selectedoptions: ["February", "2017"],
|
||||
domain: [
|
||||
"&",
|
||||
["date_field", ">=", "2017-02-01"],
|
||||
["date_field", "<=", "2017-02-28"],
|
||||
],
|
||||
},
|
||||
{
|
||||
toggledOption: "February",
|
||||
resultingFacet: "Date: 2017",
|
||||
selectedoptions: ["2017"],
|
||||
domain: [
|
||||
"&",
|
||||
["date_field", ">=", "2017-01-01"],
|
||||
["date_field", "<=", "2017-12-31"],
|
||||
],
|
||||
},
|
||||
{
|
||||
toggledOption: "January",
|
||||
resultingFacet: "Date: January 2017",
|
||||
selectedoptions: ["January", "2017"],
|
||||
domain: [
|
||||
"&",
|
||||
["date_field", ">=", "2017-01-01"],
|
||||
["date_field", "<=", "2017-01-31"],
|
||||
],
|
||||
},
|
||||
{
|
||||
toggledOption: "Q4",
|
||||
resultingFacet: "Date: January 2017/Q4 2017",
|
||||
selectedoptions: ["January", "Q4", "2017"],
|
||||
domain: [
|
||||
"|",
|
||||
"&",
|
||||
["date_field", ">=", "2017-01-01"],
|
||||
["date_field", "<=", "2017-01-31"],
|
||||
"&",
|
||||
["date_field", ">=", "2017-10-01"],
|
||||
["date_field", "<=", "2017-12-31"],
|
||||
],
|
||||
},
|
||||
{
|
||||
toggledOption: "January",
|
||||
resultingFacet: "Date: Q4 2017",
|
||||
selectedoptions: ["Q4", "2017"],
|
||||
domain: [
|
||||
"&",
|
||||
["date_field", ">=", "2017-10-01"],
|
||||
["date_field", "<=", "2017-12-31"],
|
||||
],
|
||||
},
|
||||
{
|
||||
toggledOption: "Q4",
|
||||
resultingFacet: "Date: 2017",
|
||||
selectedoptions: ["2017"],
|
||||
domain: [
|
||||
"&",
|
||||
["date_field", ">=", "2017-01-01"],
|
||||
["date_field", "<=", "2017-12-31"],
|
||||
],
|
||||
},
|
||||
{
|
||||
toggledOption: "Q1",
|
||||
resultingFacet: "Date: Q1 2017",
|
||||
selectedoptions: ["Q1", "2017"],
|
||||
domain: [
|
||||
"&",
|
||||
["date_field", ">=", "2017-01-01"],
|
||||
["date_field", "<=", "2017-03-31"],
|
||||
],
|
||||
},
|
||||
{
|
||||
toggledOption: "Q1",
|
||||
resultingFacet: "Date: 2017",
|
||||
selectedoptions: ["2017"],
|
||||
domain: [
|
||||
"&",
|
||||
["date_field", ">=", "2017-01-01"],
|
||||
["date_field", "<=", "2017-12-31"],
|
||||
],
|
||||
},
|
||||
{
|
||||
toggledOption: "2017",
|
||||
selectedoptions: [],
|
||||
domain: [],
|
||||
},
|
||||
{
|
||||
toggledOption: "2017",
|
||||
resultingFacet: "Date: 2017",
|
||||
selectedoptions: ["2017"],
|
||||
domain: [
|
||||
"&",
|
||||
["date_field", ">=", "2017-01-01"],
|
||||
["date_field", "<=", "2017-12-31"],
|
||||
],
|
||||
},
|
||||
{
|
||||
toggledOption: "2016",
|
||||
resultingFacet: "Date: 2016/2017",
|
||||
selectedoptions: ["2017", "2016"],
|
||||
domain: [
|
||||
"|",
|
||||
"&",
|
||||
["date_field", ">=", "2016-01-01"],
|
||||
["date_field", "<=", "2016-12-31"],
|
||||
"&",
|
||||
["date_field", ">=", "2017-01-01"],
|
||||
["date_field", "<=", "2017-12-31"],
|
||||
],
|
||||
},
|
||||
{
|
||||
toggledOption: "2015",
|
||||
resultingFacet: "Date: 2015/2016/2017",
|
||||
selectedoptions: ["2017", "2016", "2015"],
|
||||
domain: [
|
||||
"|",
|
||||
"&",
|
||||
["date_field", ">=", "2015-01-01"],
|
||||
["date_field", "<=", "2015-12-31"],
|
||||
"|",
|
||||
"&",
|
||||
["date_field", ">=", "2016-01-01"],
|
||||
["date_field", "<=", "2016-12-31"],
|
||||
"&",
|
||||
["date_field", ">=", "2017-01-01"],
|
||||
["date_field", "<=", "2017-12-31"],
|
||||
],
|
||||
},
|
||||
{
|
||||
toggledOption: "March",
|
||||
resultingFacet: "Date: March 2015/March 2016/March 2017",
|
||||
selectedoptions: ["March", "2017", "2016", "2015"],
|
||||
domain: [
|
||||
"|",
|
||||
"&",
|
||||
["date_field", ">=", "2015-03-01"],
|
||||
["date_field", "<=", "2015-03-31"],
|
||||
"|",
|
||||
"&",
|
||||
["date_field", ">=", "2016-03-01"],
|
||||
["date_field", "<=", "2016-03-31"],
|
||||
"&",
|
||||
["date_field", ">=", "2017-03-01"],
|
||||
["date_field", "<=", "2017-03-31"],
|
||||
],
|
||||
},
|
||||
];
|
||||
for (const s of steps) {
|
||||
await toggleMenuItemOption(target, "Date", s.toggledOption);
|
||||
assert.deepEqual(getDomain(controlPanel), s.domain);
|
||||
if (s.resultingFacet) {
|
||||
assert.deepEqual(getFacetTexts(target), [s.resultingFacet]);
|
||||
} else {
|
||||
assert.deepEqual(getFacetTexts(target), []);
|
||||
}
|
||||
s.selectedoptions.forEach((option) => {
|
||||
assert.ok(
|
||||
isOptionSelected(target, "Date", option),
|
||||
`at step ${steps.indexOf(s) + 1}, ${option} should be selected`
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"filter by a date field using period works even in January",
|
||||
async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
patchDate(2017, 0, 7, 3, 0, 0);
|
||||
|
||||
const controlPanel = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchMenuTypes: ["filter"],
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Date" name="some_filter" date="date_field" default_period="last_month"/>
|
||||
</search>
|
||||
`,
|
||||
context: { search_default_some_filter: 1 },
|
||||
});
|
||||
|
||||
assert.deepEqual(getDomain(controlPanel), [
|
||||
"&",
|
||||
["date_field", ">=", "2016-12-01"],
|
||||
["date_field", "<=", "2016-12-31"],
|
||||
]);
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), ["Date: December 2016"]);
|
||||
|
||||
await toggleFilterMenu(target);
|
||||
await toggleMenuItem(target, "Date");
|
||||
|
||||
assert.ok(isItemSelected(target, "Date"));
|
||||
assert.ok(isOptionSelected(target, "Date", "December"));
|
||||
assert.ok(isOptionSelected(target, "Date", "2016"));
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("`context` key in <filter> is used", async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const controlPanel = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchMenuTypes: ["filter"],
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Filter" name="some_filter" domain="[]" context="{'coucou_1': 1}"/>
|
||||
</search>
|
||||
`,
|
||||
context: { search_default_some_filter: 1 },
|
||||
});
|
||||
|
||||
assert.deepEqual(getContext(controlPanel), {
|
||||
coucou_1: 1,
|
||||
lang: "en",
|
||||
tz: "taht",
|
||||
uid: 7,
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test("Filter with JSON-parsable domain works", async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const xml_domain = "[["foo","=","Gently Weeps"]]";
|
||||
|
||||
const controlPanel = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchMenuTypes: ["filter"],
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Foo" name="gently_weeps" domain="${xml_domain}"/>
|
||||
</search>
|
||||
`,
|
||||
context: { search_default_gently_weeps: 1 },
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
getDomain(controlPanel),
|
||||
[["foo", "=", "Gently Weeps"]],
|
||||
"A JSON parsable xml domain should be handled just like any other"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("filter with date attribute set as search_default", async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
patchDate(2019, 6, 31, 13, 43, 0);
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchMenuTypes: ["filter"],
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Date" name="date_field" date="date_field" default_period="last_month"/>
|
||||
</search>
|
||||
`,
|
||||
context: { search_default_date_field: true },
|
||||
});
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), ["Date: June 2019"]);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"filter with multiple values in default_period date attribute set as search_default",
|
||||
async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
patchDate(2019, 6, 31, 13, 43, 0);
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchMenuTypes: ["filter"],
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Date" name="date_field" date="date_field" default_period="this_year,last_year"/>
|
||||
</search>
|
||||
`,
|
||||
context: { search_default_date_field: true },
|
||||
});
|
||||
|
||||
await toggleFilterMenu(target);
|
||||
await toggleMenuItem(target, "Date");
|
||||
|
||||
assert.ok(isItemSelected(target, "Date"));
|
||||
assert.ok(isOptionSelected(target, "Date", "2019"));
|
||||
assert.ok(isOptionSelected(target, "Date", "2018"));
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("filter domains are correcly combined by OR and AND", async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const controlPanel = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchMenuTypes: ["filter"],
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Filter Group 1" name="f_1_g1" domain="[['foo', '=', 'f1_g1']]"/>
|
||||
<separator/>
|
||||
<filter string="Filter 1 Group 2" name="f1_g2" domain="[['foo', '=', 'f1_g2']]"/>
|
||||
<filter string="Filter 2 GROUP 2" name="f2_g2" domain="[['foo', '=', 'f2_g2']]"/>
|
||||
</search>
|
||||
`,
|
||||
context: {
|
||||
search_default_f_1_g1: true,
|
||||
search_default_f1_g2: true,
|
||||
search_default_f2_g2: true,
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepEqual(getDomain(controlPanel), [
|
||||
"&",
|
||||
["foo", "=", "f1_g1"],
|
||||
"|",
|
||||
["foo", "=", "f1_g2"],
|
||||
["foo", "=", "f2_g2"],
|
||||
]);
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), [
|
||||
"Filter Group 1",
|
||||
"Filter 1 Group 2orFilter 2 GROUP 2",
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("arch order of groups of filters preserved", async function (assert) {
|
||||
assert.expect(12);
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchMenuTypes: ["filter"],
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="1" name="coolName1" date="date_field"/>
|
||||
<separator/>
|
||||
<filter string="2" name="coolName2" date="date_field"/>
|
||||
<separator/>
|
||||
<filter string="3" name="coolName3" domain="[]"/>
|
||||
<separator/>
|
||||
<filter string="4" name="coolName4" domain="[]"/>
|
||||
<separator/>
|
||||
<filter string="5" name="coolName5" domain="[]"/>
|
||||
<separator/>
|
||||
<filter string="6" name="coolName6" domain="[]"/>
|
||||
<separator/>
|
||||
<filter string="7" name="coolName7" domain="[]"/>
|
||||
<separator/>
|
||||
<filter string="8" name="coolName8" domain="[]"/>
|
||||
<separator/>
|
||||
<filter string="9" name="coolName9" domain="[]"/>
|
||||
<separator/>
|
||||
<filter string="10" name="coolName10" domain="[]"/>
|
||||
<separator/>
|
||||
<filter string="11" name="coolName11" domain="[]"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
|
||||
await toggleFilterMenu(target);
|
||||
assert.containsN(target, ".o_filter_menu .o_menu_item", 11);
|
||||
|
||||
const menuItemEls = target.querySelectorAll(".o_filter_menu .o_menu_item");
|
||||
[...menuItemEls].forEach((e, index) => {
|
||||
assert.strictEqual(e.innerText.trim(), String(index + 1));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
import { describe, expect, test } from "@odoo/hoot";
|
||||
|
||||
import { DEFAULT_INTERVAL } from "@web/search/utils/dates";
|
||||
import { getGroupBy } from "@web/search/utils/group_by";
|
||||
|
||||
const fields = {
|
||||
display_name: { string: "Displayed name", type: "char" },
|
||||
foo: {
|
||||
string: "Foo",
|
||||
type: "char",
|
||||
default: "My little Foo Value",
|
||||
store: true,
|
||||
sortable: true,
|
||||
},
|
||||
date_field: { string: "Date", type: "date", store: true, sortable: true },
|
||||
float_field: { string: "Float", type: "float" },
|
||||
bar: { string: "Bar", type: "many2one", relation: "partner" },
|
||||
};
|
||||
|
||||
describe("Without field validation", () => {
|
||||
test("simple valid group by", async () => {
|
||||
let groupBy = getGroupBy("display_name");
|
||||
expect(groupBy.fieldName).toBe("display_name");
|
||||
expect(groupBy.interval).toBe(null);
|
||||
expect(groupBy.spec).toBe("display_name");
|
||||
|
||||
groupBy = getGroupBy("display_name:quarter");
|
||||
expect(groupBy.fieldName).toBe("display_name");
|
||||
expect(groupBy.interval).toBe("quarter");
|
||||
expect(groupBy.spec).toBe("display_name:quarter");
|
||||
});
|
||||
|
||||
test("simple invalid group by", async () => {
|
||||
expect(() => getGroupBy(":day")).toThrow();
|
||||
expect(() => getGroupBy("diay_name:yar")).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("With field validation", () => {
|
||||
test("simple valid group by", async () => {
|
||||
const groupBy = getGroupBy("display_name", fields);
|
||||
expect(groupBy.fieldName).toBe("display_name");
|
||||
expect(groupBy.interval).toBe(null);
|
||||
expect(groupBy.spec).toBe("display_name");
|
||||
});
|
||||
|
||||
test("simple invalid group by", async () => {
|
||||
expect(() => getGroupBy("", fields)).toThrow();
|
||||
expect(() => getGroupBy("display_name:day", fields)).toThrow();
|
||||
expect(() => getGroupBy("diay_name:year", fields)).toThrow();
|
||||
expect(() => getGroupBy("diay_name:yar", fields)).toThrow();
|
||||
});
|
||||
|
||||
test("simple valid date group by", async () => {
|
||||
let groupBy = getGroupBy("date_field:year", fields);
|
||||
expect(groupBy.fieldName).toBe("date_field");
|
||||
expect(groupBy.interval).toBe("year");
|
||||
expect(groupBy.spec).toBe("date_field:year");
|
||||
|
||||
groupBy = getGroupBy("date_field", fields);
|
||||
expect(groupBy.fieldName).toBe("date_field");
|
||||
expect(groupBy.interval).toBe(DEFAULT_INTERVAL);
|
||||
expect(groupBy.spec).toBe(`date_field:${DEFAULT_INTERVAL}`);
|
||||
});
|
||||
|
||||
test("simple invalid date group by", async () => {
|
||||
expect(() => getGroupBy("date_field:yar", fields)).toThrow();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,534 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { ControlPanel } from "@web/search/control_panel/control_panel";
|
||||
import { getFixture, patchWithCleanup } from "../helpers/utils";
|
||||
import {
|
||||
getFacetTexts,
|
||||
isItemSelected,
|
||||
isOptionSelected,
|
||||
makeWithSearch,
|
||||
removeFacet,
|
||||
setupControlPanelServiceRegistry,
|
||||
toggleGroupByMenu,
|
||||
toggleMenuItem,
|
||||
toggleMenuItemOption,
|
||||
} from "./helpers";
|
||||
|
||||
let target;
|
||||
let serverData;
|
||||
QUnit.module("Search", (hooks) => {
|
||||
hooks.beforeEach(async () => {
|
||||
serverData = {
|
||||
models: {
|
||||
foo: {
|
||||
fields: {
|
||||
bar: { string: "Bar", type: "many2one", relation: "partner" },
|
||||
birthday: { string: "Birthday", type: "date", store: true, sortable: true },
|
||||
date_field: { string: "Date", type: "date", store: true, sortable: true },
|
||||
float_field: { string: "Float", type: "float", group_operator: "sum" },
|
||||
foo: { string: "Foo", type: "char", store: true, sortable: true },
|
||||
},
|
||||
records: {},
|
||||
},
|
||||
},
|
||||
views: {
|
||||
"foo,false,search": `<search/>`,
|
||||
},
|
||||
};
|
||||
setupControlPanelServiceRegistry();
|
||||
patchWithCleanup(browser, {
|
||||
setTimeout: (fn) => fn(),
|
||||
clearTimeout: () => {},
|
||||
});
|
||||
target = getFixture();
|
||||
});
|
||||
|
||||
QUnit.module("GroupByMenu");
|
||||
|
||||
QUnit.test(
|
||||
"simple rendering with neither groupbys nor groupable fields",
|
||||
async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
searchViewFields: {},
|
||||
});
|
||||
|
||||
await toggleGroupByMenu(target);
|
||||
|
||||
assert.containsNone(target, ".o_menu_item");
|
||||
assert.containsNone(target, ".dropdown-divider");
|
||||
assert.containsNone(target, ".o_add_custom_group_menu");
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("simple rendering with no groupby", async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
});
|
||||
|
||||
await toggleGroupByMenu(target);
|
||||
|
||||
assert.containsNone(target, ".o_menu_item");
|
||||
assert.containsNone(target, ".dropdown-divider");
|
||||
assert.containsOnce(target, ".o_add_custom_group_menu");
|
||||
});
|
||||
|
||||
QUnit.test("simple rendering with a single groupby", async function (assert) {
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Foo" name="group_by_foo" context="{'group_by': 'foo'}"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
|
||||
await toggleGroupByMenu(target);
|
||||
|
||||
assert.containsOnce(target, ".o_menu_item");
|
||||
const menuItem = target.querySelector(".o_menu_item");
|
||||
assert.strictEqual(menuItem.innerText.trim(), "Foo");
|
||||
assert.strictEqual(menuItem.getAttribute("role"), "menuitemcheckbox");
|
||||
assert.strictEqual(menuItem.ariaChecked, "false");
|
||||
assert.containsOnce(target, ".dropdown-divider");
|
||||
assert.containsOnce(target, ".o_add_custom_group_menu");
|
||||
});
|
||||
|
||||
QUnit.test('toggle a "simple" groupby in groupby menu works', async function (assert) {
|
||||
const controlPanel = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Foo" name="group_by_foo" context="{'group_by': 'foo'}"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
|
||||
await toggleGroupByMenu(target);
|
||||
|
||||
assert.deepEqual(controlPanel.env.searchModel.groupBy, []);
|
||||
assert.deepEqual(getFacetTexts(target), []);
|
||||
assert.notOk(isItemSelected(target, "Foo"));
|
||||
const menuItem = target.querySelector(".o_menu_item");
|
||||
assert.strictEqual(menuItem.innerText.trim(), "Foo");
|
||||
assert.strictEqual(menuItem.getAttribute("role"), "menuitemcheckbox");
|
||||
assert.strictEqual(menuItem.ariaChecked, "false");
|
||||
await toggleMenuItem(target, "Foo");
|
||||
assert.strictEqual(menuItem.ariaChecked, "true");
|
||||
|
||||
assert.deepEqual(controlPanel.env.searchModel.groupBy, ["foo"]);
|
||||
assert.deepEqual(getFacetTexts(target), ["Foo"]);
|
||||
assert.containsOnce(
|
||||
target.querySelector(".o_searchview .o_searchview_facet"),
|
||||
"span.oi.oi-group.o_searchview_facet_label"
|
||||
);
|
||||
assert.ok(isItemSelected(target, "Foo"));
|
||||
|
||||
await toggleMenuItem(target, "Foo");
|
||||
|
||||
assert.deepEqual(controlPanel.env.searchModel.groupBy, []);
|
||||
assert.deepEqual(getFacetTexts(target), []);
|
||||
assert.notOk(isItemSelected(target, "Foo"));
|
||||
});
|
||||
|
||||
QUnit.test('toggle a "simple" groupby quickly does not crash', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Foo" name="group_by_foo" context="{'group_by': 'foo'}"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
|
||||
await toggleGroupByMenu(target);
|
||||
|
||||
toggleMenuItem(target, "Foo");
|
||||
toggleMenuItem(target, "Foo");
|
||||
|
||||
assert.ok(true);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
'remove a "Group By" facet properly unchecks groupbys in groupby menu',
|
||||
async function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
const controlPanel = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Foo" name="group_by_foo" context="{'group_by': 'foo'}"/>
|
||||
</search>
|
||||
`,
|
||||
context: { search_default_group_by_foo: 1 },
|
||||
});
|
||||
|
||||
await toggleGroupByMenu(target);
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), ["Foo"]);
|
||||
assert.deepEqual(controlPanel.env.searchModel.groupBy, ["foo"]);
|
||||
assert.ok(isItemSelected(target, "Foo"));
|
||||
|
||||
await removeFacet(target, "Foo");
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), []);
|
||||
assert.deepEqual(controlPanel.env.searchModel.groupBy, []);
|
||||
|
||||
await toggleGroupByMenu(target);
|
||||
|
||||
assert.notOk(isItemSelected(target, "Foo"));
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("group by a date field using interval works", async function (assert) {
|
||||
assert.expect(21);
|
||||
|
||||
const controlPanel = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Date" name="date" context="{'group_by': 'date_field:week'}"/>
|
||||
</search>
|
||||
`,
|
||||
context: { search_default_date: 1 },
|
||||
});
|
||||
|
||||
await toggleGroupByMenu(target);
|
||||
|
||||
assert.deepEqual(controlPanel.env.searchModel.groupBy, ["date_field:week"]);
|
||||
|
||||
await toggleMenuItem(target, "Date");
|
||||
|
||||
assert.ok(isOptionSelected(target, "Date", "Week"));
|
||||
|
||||
assert.deepEqual(
|
||||
[...target.querySelectorAll(".o_item_option")].map((el) => el.innerText),
|
||||
["Year", "Quarter", "Month", "Week", "Day"]
|
||||
);
|
||||
|
||||
const steps = [
|
||||
{
|
||||
description: "Year",
|
||||
facetTexts: ["Date: Year>Date: Week"],
|
||||
selectedoptions: ["Year", "Week"],
|
||||
groupBy: ["date_field:year", "date_field:week"],
|
||||
},
|
||||
{
|
||||
description: "Month",
|
||||
facetTexts: ["Date: Year>Date: Month>Date: Week"],
|
||||
selectedoptions: ["Year", "Month", "Week"],
|
||||
groupBy: ["date_field:year", "date_field:month", "date_field:week"],
|
||||
},
|
||||
{
|
||||
description: "Week",
|
||||
facetTexts: ["Date: Year>Date: Month"],
|
||||
selectedoptions: ["Year", "Month"],
|
||||
groupBy: ["date_field:year", "date_field:month"],
|
||||
},
|
||||
{
|
||||
description: "Month",
|
||||
facetTexts: ["Date: Year"],
|
||||
selectedoptions: ["Year"],
|
||||
groupBy: ["date_field:year"],
|
||||
},
|
||||
{ description: "Year", facetTexts: [], selectedoptions: [], groupBy: [] },
|
||||
];
|
||||
for (const s of steps) {
|
||||
await toggleMenuItemOption(target, "Date", s.description);
|
||||
|
||||
assert.deepEqual(controlPanel.env.searchModel.groupBy, s.groupBy);
|
||||
assert.deepEqual(getFacetTexts(target), s.facetTexts);
|
||||
s.selectedoptions.forEach((description) => {
|
||||
assert.ok(isOptionSelected(target, "Date", description));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test("interval options are correctly grouped and ordered", async function (assert) {
|
||||
assert.expect(8);
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Bar" name="bar" context="{'group_by': 'bar'}"/>
|
||||
<filter string="Date" name="date" context="{'group_by': 'date_field'}"/>
|
||||
<filter string="Foo" name="foo" context="{'group_by': 'foo'}"/>
|
||||
</search>
|
||||
`,
|
||||
context: { search_default_bar: 1 },
|
||||
});
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), ["Bar"]);
|
||||
|
||||
// open menu 'Group By'
|
||||
await toggleGroupByMenu(target);
|
||||
|
||||
// Open the groupby 'Date'
|
||||
await toggleMenuItem(target, "Date");
|
||||
// select option 'week'
|
||||
await toggleMenuItemOption(target, "Date", "Week");
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), ["Bar>Date: Week"]);
|
||||
|
||||
// select option 'day'
|
||||
await toggleMenuItemOption(target, "Date", "Day");
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), ["Bar>Date: Week>Date: Day"]);
|
||||
|
||||
// select option 'year'
|
||||
await toggleMenuItemOption(target, "Date", "Year");
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), ["Bar>Date: Year>Date: Week>Date: Day"]);
|
||||
|
||||
// select 'Foo'
|
||||
await toggleMenuItem(target, "Foo");
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), ["Bar>Date: Year>Date: Week>Date: Day>Foo"]);
|
||||
|
||||
// select option 'quarter'
|
||||
await toggleMenuItem(target, "Date");
|
||||
await toggleMenuItemOption(target, "Date", "Quarter");
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), [
|
||||
"Bar>Date: Year>Date: Quarter>Date: Week>Date: Day>Foo",
|
||||
]);
|
||||
|
||||
// unselect 'Bar'
|
||||
await toggleMenuItem(target, "Bar");
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), [
|
||||
"Date: Year>Date: Quarter>Date: Week>Date: Day>Foo",
|
||||
]);
|
||||
|
||||
// unselect option 'week'
|
||||
await toggleMenuItem(target, "Date");
|
||||
await toggleMenuItemOption(target, "Date", "Week");
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), ["Date: Year>Date: Quarter>Date: Day>Foo"]);
|
||||
});
|
||||
|
||||
QUnit.test("default groupbys can be ordered", async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const controlPanel = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Birthday" name="birthday" context="{'group_by': 'birthday'}"/>
|
||||
<filter string="Date" name="date" context="{'group_by': 'date_field:week'}"/>
|
||||
</search>
|
||||
`,
|
||||
context: { search_default_birthday: 2, search_default_date: 1 },
|
||||
});
|
||||
|
||||
// the default groupbys should be activated in the right order
|
||||
assert.deepEqual(controlPanel.env.searchModel.groupBy, [
|
||||
"date_field:week",
|
||||
"birthday:month",
|
||||
]);
|
||||
assert.deepEqual(getFacetTexts(target), ["Date: Week>Birthday: Month"]);
|
||||
});
|
||||
|
||||
QUnit.test("a separator in groupbys does not cause problems", async function (assert) {
|
||||
assert.expect(23);
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Date" name="coolName" context="{'group_by': 'date_field'}"/>
|
||||
<separator/>
|
||||
<filter string="Bar" name="superName" context="{'group_by': 'bar'}"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
|
||||
await toggleGroupByMenu(target);
|
||||
await toggleMenuItem(target, "Date");
|
||||
await toggleMenuItemOption(target, "Date", "Day");
|
||||
|
||||
assert.ok(isItemSelected(target, "Date"));
|
||||
assert.notOk(isItemSelected(target, "Bar"));
|
||||
assert.ok(isOptionSelected(target, "Date", "Day"), "selected");
|
||||
assert.deepEqual(getFacetTexts(target), ["Date: Day"]);
|
||||
|
||||
await toggleMenuItem(target, "Bar");
|
||||
await toggleMenuItem(target, "Date");
|
||||
|
||||
assert.ok(isItemSelected(target, "Date"));
|
||||
assert.ok(isItemSelected(target, "Bar"));
|
||||
assert.ok(isOptionSelected(target, "Date", "Day"), "selected");
|
||||
assert.deepEqual(getFacetTexts(target), ["Date: Day>Bar"]);
|
||||
|
||||
await toggleMenuItemOption(target, "Date", "Quarter");
|
||||
|
||||
assert.ok(isItemSelected(target, "Date"));
|
||||
assert.ok(isItemSelected(target, "Bar"));
|
||||
assert.ok(isOptionSelected(target, "Date", "Quarter"), "selected");
|
||||
assert.ok(isOptionSelected(target, "Date", "Day"), "selected");
|
||||
assert.deepEqual(getFacetTexts(target), ["Date: Quarter>Date: Day>Bar"]);
|
||||
|
||||
await toggleMenuItem(target, "Bar");
|
||||
await toggleMenuItem(target, "Date");
|
||||
|
||||
assert.ok(isItemSelected(target, "Date"));
|
||||
assert.notOk(isItemSelected(target, "Bar"));
|
||||
assert.ok(isOptionSelected(target, "Date", "Quarter"), "selected");
|
||||
assert.ok(isOptionSelected(target, "Date", "Day"), "selected");
|
||||
assert.deepEqual(getFacetTexts(target), ["Date: Quarter>Date: Day"]);
|
||||
|
||||
await removeFacet(target);
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), []);
|
||||
|
||||
await toggleGroupByMenu(target);
|
||||
await toggleMenuItem(target, "Date");
|
||||
|
||||
assert.notOk(isItemSelected(target, "Date"));
|
||||
assert.notOk(isItemSelected(target, "Bar"));
|
||||
assert.notOk(isOptionSelected(target, "Date", "Quarter"), "selected");
|
||||
assert.notOk(isOptionSelected(target, "Date", "Day"), "selected");
|
||||
});
|
||||
|
||||
QUnit.test("falsy search default groupbys are not activated", async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const controlPanel = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Birthday" name="birthday" context="{'group_by': 'birthday'}"/>
|
||||
<filter string="Date" name="date" context="{'group_by': 'foo'}"/>
|
||||
</search>
|
||||
`,
|
||||
context: { search_default_birthday: false, search_default_foo: 0 },
|
||||
});
|
||||
|
||||
assert.deepEqual(controlPanel.env.searchModel.groupBy, []);
|
||||
assert.deepEqual(getFacetTexts(target), []);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"Custom group by menu is displayed when hideCustomGroupBy is not set",
|
||||
async function (assert) {
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Birthday" name="birthday" context="{'group_by': 'birthday'}"/>
|
||||
<filter string="Date" name="date" context="{'group_by': 'foo'}"/>
|
||||
</search>
|
||||
`,
|
||||
searchMenuTypes: ["groupBy"],
|
||||
});
|
||||
|
||||
await toggleGroupByMenu(target);
|
||||
|
||||
assert.containsOnce(target, ".o_add_custom_group_menu");
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"Custom group by menu is displayed when hideCustomGroupBy is false",
|
||||
async function (assert) {
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Birthday" name="birthday" context="{'group_by': 'birthday'}"/>
|
||||
<filter string="Date" name="date" context="{'group_by': 'foo'}"/>
|
||||
</search>
|
||||
`,
|
||||
hideCustomGroupBy: false,
|
||||
searchMenuTypes: ["groupBy"],
|
||||
});
|
||||
|
||||
await toggleGroupByMenu(target);
|
||||
|
||||
assert.containsOnce(target, ".o_add_custom_group_menu");
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"Custom group by menu is displayed when hideCustomGroupBy is true",
|
||||
async function (assert) {
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Birthday" name="birthday" context="{'group_by': 'birthday'}"/>
|
||||
<filter string="Date" name="date" context="{'group_by': 'foo'}"/>
|
||||
</search>
|
||||
`,
|
||||
hideCustomGroupBy: true,
|
||||
searchMenuTypes: ["groupBy"],
|
||||
});
|
||||
|
||||
await toggleGroupByMenu(target);
|
||||
|
||||
assert.containsNone(target, ".o_add_custom_group_menu");
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { getGroupBy } from "@web/search/utils/group_by";
|
||||
import { DEFAULT_INTERVAL } from "@web/search/utils/dates";
|
||||
|
||||
const fields = {
|
||||
display_name: { string: "Displayed name", type: "char" },
|
||||
foo: {
|
||||
string: "Foo",
|
||||
type: "char",
|
||||
default: "My little Foo Value",
|
||||
store: true,
|
||||
sortable: true,
|
||||
},
|
||||
date_field: { string: "Date", type: "date", store: true, sortable: true },
|
||||
float_field: { string: "Float", type: "float" },
|
||||
bar: { string: "Bar", type: "many2one", relation: "partner" },
|
||||
};
|
||||
|
||||
QUnit.module("GroupBy Class", {}, () => {
|
||||
QUnit.module("Without field validation");
|
||||
QUnit.test("simple valid group by", async function (assert) {
|
||||
assert.expect(6);
|
||||
let groupBy = getGroupBy("display_name");
|
||||
assert.strictEqual(groupBy.fieldName, "display_name");
|
||||
assert.strictEqual(groupBy.interval, null);
|
||||
assert.strictEqual(groupBy.spec, "display_name");
|
||||
groupBy = getGroupBy("display_name:quarter");
|
||||
assert.strictEqual(groupBy.fieldName, "display_name");
|
||||
assert.strictEqual(groupBy.interval, "quarter");
|
||||
assert.strictEqual(groupBy.spec, "display_name:quarter");
|
||||
});
|
||||
QUnit.test("simple invalid group by", async function (assert) {
|
||||
assert.expect(3);
|
||||
try {
|
||||
getGroupBy(":day");
|
||||
} catch (_e) {
|
||||
assert.step("Error 1");
|
||||
}
|
||||
try {
|
||||
getGroupBy("diay_name:yar");
|
||||
} catch (_e) {
|
||||
assert.step("Error 2");
|
||||
}
|
||||
assert.verifySteps(["Error 1", "Error 2"]);
|
||||
});
|
||||
QUnit.module("With field validation");
|
||||
QUnit.test("simple valid group by", async function (assert) {
|
||||
assert.expect(3);
|
||||
const groupBy = getGroupBy("display_name", fields);
|
||||
assert.strictEqual(groupBy.fieldName, "display_name");
|
||||
assert.strictEqual(groupBy.interval, null);
|
||||
assert.strictEqual(groupBy.spec, "display_name");
|
||||
});
|
||||
QUnit.test("simple invalid group by", async function (assert) {
|
||||
assert.expect(5);
|
||||
try {
|
||||
getGroupBy("", fields);
|
||||
} catch (_e) {
|
||||
assert.step("Error 1");
|
||||
}
|
||||
try {
|
||||
getGroupBy("display_name:day", fields);
|
||||
} catch (_e) {
|
||||
assert.step("Error 2");
|
||||
}
|
||||
try {
|
||||
getGroupBy("diay_name:year", fields);
|
||||
} catch (_e) {
|
||||
assert.step("Error 3");
|
||||
}
|
||||
try {
|
||||
getGroupBy("diay_name:yar", fields);
|
||||
} catch (_e) {
|
||||
assert.step("Error 4");
|
||||
}
|
||||
assert.verifySteps(["Error 1", "Error 2", "Error 3", "Error 4"]);
|
||||
});
|
||||
QUnit.test("simple valid date group by", async function (assert) {
|
||||
assert.expect(6);
|
||||
let groupBy = getGroupBy("date_field:year", fields);
|
||||
assert.strictEqual(groupBy.fieldName, "date_field");
|
||||
assert.strictEqual(groupBy.interval, "year");
|
||||
assert.strictEqual(groupBy.spec, "date_field:year");
|
||||
groupBy = getGroupBy("date_field", fields);
|
||||
assert.strictEqual(groupBy.fieldName, "date_field");
|
||||
assert.strictEqual(groupBy.interval, DEFAULT_INTERVAL);
|
||||
assert.strictEqual(groupBy.spec, `date_field:${DEFAULT_INTERVAL}`);
|
||||
});
|
||||
QUnit.test("simple invalid date group by", async function (assert) {
|
||||
assert.expect(2);
|
||||
try {
|
||||
getGroupBy("date_field:yar", fields);
|
||||
} catch (_e) {
|
||||
assert.step("Error");
|
||||
}
|
||||
assert.verifySteps(["Error"]);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,336 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { makeTestEnv } from "@web/../tests/helpers/mock_env";
|
||||
import {
|
||||
click,
|
||||
editInput,
|
||||
getFixture,
|
||||
mount,
|
||||
mouseEnter,
|
||||
triggerEvent,
|
||||
triggerEvents,
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { hotkeyService } from "@web/core/hotkeys/hotkey_service";
|
||||
import { notificationService } from "@web/core/notifications/notification_service";
|
||||
import { ormService } from "@web/core/orm_service";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { CustomFavoriteItem } from "@web/search/favorite_menu/custom_favorite_item";
|
||||
import { WithSearch } from "@web/search/with_search/with_search";
|
||||
import { getDefaultConfig } from "@web/views/view";
|
||||
import { viewService } from "@web/views/view_service";
|
||||
import { actionService } from "@web/webclient/actions/action_service";
|
||||
import { dialogService } from "@web/core/dialog/dialog_service";
|
||||
import { MainComponentsContainer } from "@web/core/main_components_container";
|
||||
|
||||
import { Component, xml } from "@odoo/owl";
|
||||
const serviceRegistry = registry.category("services");
|
||||
const favoriteMenuRegistry = registry.category("favoriteMenu");
|
||||
|
||||
export function setupControlPanelServiceRegistry() {
|
||||
serviceRegistry.add("action", actionService);
|
||||
serviceRegistry.add("hotkey", hotkeyService);
|
||||
serviceRegistry.add("notification", notificationService);
|
||||
serviceRegistry.add("orm", ormService);
|
||||
serviceRegistry.add("view", viewService);
|
||||
serviceRegistry.add("dialog", dialogService);
|
||||
}
|
||||
|
||||
export function setupControlPanelFavoriteMenuRegistry() {
|
||||
favoriteMenuRegistry.add(
|
||||
"custom-favorite-item",
|
||||
{ Component: CustomFavoriteItem, groupNumber: 3 },
|
||||
{ sequence: 0 }
|
||||
);
|
||||
}
|
||||
|
||||
export async function makeWithSearch(params) {
|
||||
const props = { ...params };
|
||||
|
||||
const serverData = props.serverData || undefined;
|
||||
const mockRPC = props.mockRPC || undefined;
|
||||
const config = {
|
||||
...getDefaultConfig(),
|
||||
...props.config,
|
||||
};
|
||||
|
||||
delete props.serverData;
|
||||
delete props.mockRPC;
|
||||
delete props.config;
|
||||
const componentProps = props.componentProps || {};
|
||||
delete props.componentProps;
|
||||
delete props.Component;
|
||||
|
||||
class Parent extends Component {
|
||||
setup() {
|
||||
this.withSearchProps = props;
|
||||
this.componentProps = componentProps;
|
||||
}
|
||||
getDisplay(display) {
|
||||
return Object.assign({}, display, componentProps.display);
|
||||
}
|
||||
}
|
||||
Parent.template = xml`
|
||||
<WithSearch t-props="withSearchProps" t-slot-scope="search">
|
||||
<Component
|
||||
t-props="componentProps"
|
||||
context="search.context"
|
||||
domain="search.domain"
|
||||
groupBy="search.groupBy"
|
||||
orderBy="search.orderBy"
|
||||
comparison="search.comparison"
|
||||
display="getDisplay(search.display)"/>
|
||||
</WithSearch>
|
||||
<MainComponentsContainer />
|
||||
`;
|
||||
Parent.components = { Component: params.Component, WithSearch, MainComponentsContainer };
|
||||
|
||||
const env = await makeTestEnv({ serverData, mockRPC });
|
||||
const searchEnv = Object.assign(Object.create(env), { config });
|
||||
const parent = await mount(Parent, getFixture(), { env: searchEnv, props });
|
||||
const parentNode = parent.__owl__;
|
||||
const withSearchNode = getUniqueChild(parentNode);
|
||||
const componentNode = getUniqueChild(withSearchNode);
|
||||
const component = componentNode.component;
|
||||
return component;
|
||||
}
|
||||
|
||||
function getUniqueChild(node) {
|
||||
return Object.values(node.children)[0];
|
||||
}
|
||||
|
||||
function getNode(target) {
|
||||
return target instanceof Component ? target.el : target;
|
||||
}
|
||||
|
||||
function findItem(target, selector, finder = 0) {
|
||||
const el = getNode(target);
|
||||
const elems = [...el.querySelectorAll(selector)];
|
||||
if (Number.isInteger(finder)) {
|
||||
return elems[finder];
|
||||
}
|
||||
return elems.find((el) => el.innerText.trim().toLowerCase() === String(finder).toLowerCase());
|
||||
}
|
||||
|
||||
/** Menu (generic) */
|
||||
|
||||
export async function toggleMenu(el, menuFinder) {
|
||||
const menu = findItem(el, `.dropdown button.dropdown-toggle`, menuFinder);
|
||||
await click(menu);
|
||||
}
|
||||
|
||||
export async function toggleMenuItem(el, itemFinder) {
|
||||
const item = findItem(el, `.o_menu_item`, itemFinder);
|
||||
if (item.classList.contains("dropdown-toggle")) {
|
||||
await mouseEnter(item);
|
||||
} else {
|
||||
await click(item);
|
||||
}
|
||||
}
|
||||
|
||||
export async function toggleMenuItemOption(el, itemFinder, optionFinder) {
|
||||
const item = findItem(el, `.o_menu_item`, itemFinder);
|
||||
const option = findItem(item.parentNode, ".o_item_option", optionFinder);
|
||||
if (option.classList.contains("dropdown-toggle")) {
|
||||
await mouseEnter(option);
|
||||
} else {
|
||||
await click(option);
|
||||
}
|
||||
}
|
||||
|
||||
export function isItemSelected(el, itemFinder) {
|
||||
const item = findItem(el, `.o_menu_item`, itemFinder);
|
||||
return item.classList.contains("selected");
|
||||
}
|
||||
|
||||
export function isOptionSelected(el, itemFinder, optionFinder) {
|
||||
const item = findItem(el, `.o_menu_item`, itemFinder);
|
||||
const option = findItem(item.parentNode, ".o_item_option", optionFinder);
|
||||
return option.classList.contains("selected");
|
||||
}
|
||||
|
||||
export function getMenuItemTexts(target) {
|
||||
const el = getNode(target);
|
||||
return [...el.querySelectorAll(`.dropdown-menu .o_menu_item`)].map((e) => e.innerText.trim());
|
||||
}
|
||||
|
||||
export function getButtons(el) {
|
||||
return [...el.querySelector(`div.o_cp_bottom div.o_cp_buttons`).children];
|
||||
}
|
||||
|
||||
/** Filter menu */
|
||||
|
||||
export async function toggleFilterMenu(el) {
|
||||
await click(findItem(el, `.o_filter_menu button.dropdown-toggle`));
|
||||
}
|
||||
|
||||
export async function toggleAddCustomFilter(el) {
|
||||
await mouseEnter(findItem(el, `.o_add_custom_filter_menu .dropdown-toggle`));
|
||||
}
|
||||
|
||||
export async function editConditionField(el, index, fieldName) {
|
||||
const condition = findItem(el, `.o_filter_condition`, index);
|
||||
const select = findItem(condition, "select", 0);
|
||||
select.value = fieldName;
|
||||
await triggerEvent(select, null, "change");
|
||||
}
|
||||
|
||||
export async function editConditionOperator(el, index, operator) {
|
||||
const condition = findItem(el, `.o_filter_condition`, index);
|
||||
const select = findItem(condition, "select", 1);
|
||||
select.value = operator;
|
||||
await triggerEvent(select, null, "change");
|
||||
}
|
||||
|
||||
export async function editConditionValue(el, index, value, valueIndex = 0) {
|
||||
const condition = findItem(el, `.o_filter_condition`, index);
|
||||
const target = findItem(
|
||||
condition,
|
||||
".o_generator_menu_value input:not([type=hidden]),.o_generator_menu_value select",
|
||||
valueIndex
|
||||
);
|
||||
target.value = value;
|
||||
await triggerEvent(target, null, "change");
|
||||
}
|
||||
|
||||
export async function applyFilter(el) {
|
||||
await click(findItem(el, `.o_add_custom_filter_menu .dropdown-menu button.o_apply_filter`));
|
||||
}
|
||||
|
||||
export async function addCondition(el) {
|
||||
await click(findItem(el, `.o_add_custom_filter_menu .dropdown-menu button.o_add_condition`));
|
||||
}
|
||||
|
||||
export async function removeCondition(el, index) {
|
||||
const condition = findItem(el, `.o_filter_condition`, index);
|
||||
await click(findItem(condition, ".o_generator_menu_delete"));
|
||||
}
|
||||
|
||||
/** Group by menu */
|
||||
|
||||
export async function toggleGroupByMenu(el) {
|
||||
await click(findItem(el, `.o_group_by_menu .dropdown-toggle`));
|
||||
}
|
||||
|
||||
export async function toggleAddCustomGroup(el) {
|
||||
await mouseEnter(findItem(el, `.o_add_custom_group_menu .dropdown-toggle`));
|
||||
}
|
||||
|
||||
export async function selectGroup(el, fieldName) {
|
||||
const select = findItem(el, `.o_add_custom_group_menu .dropdown-menu select`);
|
||||
select.value = fieldName;
|
||||
await triggerEvent(select, null, "change");
|
||||
}
|
||||
|
||||
export async function applyGroup(el) {
|
||||
await click(findItem(el, `.o_add_custom_group_menu .dropdown-menu .btn`));
|
||||
}
|
||||
|
||||
export async function groupByMenu(el, fieldName) {
|
||||
await toggleGroupByMenu(el);
|
||||
await toggleAddCustomGroup(el);
|
||||
await selectGroup(el, fieldName);
|
||||
await applyGroup(el);
|
||||
}
|
||||
|
||||
/** Favorite menu */
|
||||
|
||||
export async function toggleFavoriteMenu(el) {
|
||||
await click(findItem(el, `.o_favorite_menu .dropdown-toggle`));
|
||||
}
|
||||
|
||||
export async function deleteFavorite(el, favoriteFinder) {
|
||||
const favorite = findItem(el, `.o_favorite_menu .o_menu_item`, favoriteFinder);
|
||||
await click(findItem(favorite, "i.fa-trash-o"));
|
||||
}
|
||||
|
||||
export async function toggleSaveFavorite(el) {
|
||||
await mouseEnter(findItem(el, `.o_favorite_menu .o_add_favorite .dropdown-toggle`));
|
||||
}
|
||||
|
||||
export async function editFavoriteName(el, name) {
|
||||
const input = findItem(
|
||||
el,
|
||||
`.o_favorite_menu .o_add_favorite .dropdown-menu input[type="text"]`
|
||||
);
|
||||
input.value = name;
|
||||
await triggerEvents(input, null, ["input", "change"]);
|
||||
}
|
||||
|
||||
export async function saveFavorite(el) {
|
||||
await click(findItem(el, `.o_favorite_menu .o_add_favorite .dropdown-menu button`));
|
||||
}
|
||||
|
||||
/** Comparison menu */
|
||||
|
||||
export async function toggleComparisonMenu(el) {
|
||||
await click(findItem(el, `.o_comparison_menu button.dropdown-toggle`));
|
||||
}
|
||||
|
||||
/** Search bar */
|
||||
|
||||
export function getFacetTexts(target) {
|
||||
const el = getNode(target);
|
||||
return [...el.querySelectorAll(`div.o_searchview_facet`)].map((facet) =>
|
||||
facet.innerText.trim()
|
||||
);
|
||||
}
|
||||
|
||||
export async function removeFacet(el, facetFinder = 0) {
|
||||
const facet = findItem(el, `div.o_searchview_facet`, facetFinder);
|
||||
await click(facet.querySelector("i.o_facet_remove"));
|
||||
}
|
||||
|
||||
export async function editSearch(el, value) {
|
||||
const input = findItem(el, `.o_searchview input`);
|
||||
input.value = value;
|
||||
await triggerEvent(input, null, "input");
|
||||
}
|
||||
|
||||
export async function validateSearch(el) {
|
||||
const input = findItem(el, `.o_searchview input`);
|
||||
await triggerEvent(input, null, "keydown", { key: "Enter" });
|
||||
}
|
||||
|
||||
/** Switch View */
|
||||
|
||||
export async function switchView(el, viewType) {
|
||||
await click(findItem(el, `button.o_switch_view.o_${viewType}`));
|
||||
}
|
||||
|
||||
/** Pager */
|
||||
|
||||
export function getPagerValue(el) {
|
||||
const valueEl = findItem(el, ".o_pager .o_pager_value");
|
||||
return valueEl.innerText.trim().split("-").map(Number);
|
||||
}
|
||||
|
||||
export function getPagerLimit(el) {
|
||||
const limitEl = findItem(el, ".o_pager .o_pager_limit");
|
||||
return Number(limitEl.innerText.trim());
|
||||
}
|
||||
|
||||
export async function pagerNext(el) {
|
||||
await click(findItem(el, ".o_pager button.o_pager_next"));
|
||||
}
|
||||
|
||||
export async function pagerPrevious(el) {
|
||||
await click(findItem(el, ".o_pager button.o_pager_previous"));
|
||||
}
|
||||
|
||||
export async function editPager(el, value) {
|
||||
await click(findItem(el, ".o_pager .o_pager_value"));
|
||||
await editInput(getNode(el), ".o_pager .o_pager_value.o_input", value);
|
||||
}
|
||||
|
||||
/////////////////////////////////////
|
||||
// Action Menu
|
||||
/////////////////////////////////////
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @param {string} [menuFinder="Action"]
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export async function toggleActionMenu(el, menuFinder = "Action") {
|
||||
const dropdown = findItem(el, `.o_cp_action_menus button`, menuFinder);
|
||||
await click(dropdown);
|
||||
}
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
import { expect, test } from "@odoo/hoot";
|
||||
import { Component, useState, xml } from "@odoo/owl";
|
||||
import {
|
||||
defineModels,
|
||||
getPagerLimit,
|
||||
getPagerValue,
|
||||
models,
|
||||
mountWithSearch,
|
||||
pagerNext,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { ControlPanel } from "@web/search/control_panel/control_panel";
|
||||
import { usePager } from "@web/search/pager_hook";
|
||||
import { animationFrame } from "@odoo/hoot-mock";
|
||||
|
||||
class Foo extends models.Model {}
|
||||
|
||||
defineModels([Foo]);
|
||||
|
||||
test("pager is correctly displayed", async () => {
|
||||
class TestComponent extends Component {
|
||||
static components = { ControlPanel };
|
||||
static template = xml`<ControlPanel />`;
|
||||
static props = ["*"];
|
||||
setup() {
|
||||
usePager(() => ({
|
||||
offset: 0,
|
||||
limit: 10,
|
||||
total: 50,
|
||||
onUpdate: () => {},
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
await mountWithSearch(TestComponent, {
|
||||
resModel: "foo",
|
||||
searchMenuTypes: [],
|
||||
});
|
||||
expect(`.o_pager`).toHaveCount(1);
|
||||
expect(".o_pager button.o_pager_next").toHaveCount(1);
|
||||
expect(".o_pager button.o_pager_previous").toHaveCount(1);
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("pager is correctly displayed on desktop", async () => {
|
||||
class TestComponent extends Component {
|
||||
static components = { ControlPanel };
|
||||
static template = xml`<ControlPanel />`;
|
||||
static props = ["*"];
|
||||
setup() {
|
||||
usePager(() => ({
|
||||
offset: 0,
|
||||
limit: 10,
|
||||
total: 50,
|
||||
onUpdate: () => {},
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
await mountWithSearch(TestComponent, {
|
||||
resModel: "foo",
|
||||
searchMenuTypes: [],
|
||||
});
|
||||
expect(`.o_pager`).toHaveCount(1);
|
||||
expect(getPagerValue()).toEqual([1, 10]);
|
||||
expect(getPagerLimit()).toBe(50);
|
||||
});
|
||||
|
||||
test("pager is correctly updated", async () => {
|
||||
class TestComponent extends Component {
|
||||
static components = { ControlPanel };
|
||||
static template = xml`<ControlPanel />`;
|
||||
static props = ["*"];
|
||||
setup() {
|
||||
this.state = useState({ offset: 0, limit: 10 });
|
||||
usePager(() => ({
|
||||
offset: this.state.offset,
|
||||
limit: this.state.limit,
|
||||
total: 50,
|
||||
onUpdate: (newState) => {
|
||||
Object.assign(this.state, newState);
|
||||
},
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
const component = await mountWithSearch(TestComponent, {
|
||||
resModel: "foo",
|
||||
searchMenuTypes: [],
|
||||
});
|
||||
expect(`.o_pager`).toHaveCount(1);
|
||||
expect(component.state).toEqual({ offset: 0, limit: 10 });
|
||||
|
||||
await pagerNext();
|
||||
expect(`.o_pager`).toHaveCount(1);
|
||||
expect(component.state).toEqual({ offset: 10, limit: 10 });
|
||||
|
||||
component.state.offset = 20;
|
||||
await animationFrame();
|
||||
expect(`.o_pager`).toHaveCount(1);
|
||||
expect(component.state).toEqual({ offset: 20, limit: 10 });
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("pager is correctly updated on desktop", async () => {
|
||||
class TestComponent extends Component {
|
||||
static components = { ControlPanel };
|
||||
static template = xml`<ControlPanel />`;
|
||||
static props = ["*"];
|
||||
setup() {
|
||||
this.state = useState({ offset: 0, limit: 10 });
|
||||
usePager(() => ({
|
||||
offset: this.state.offset,
|
||||
limit: this.state.limit,
|
||||
total: 50,
|
||||
onUpdate: (newState) => {
|
||||
Object.assign(this.state, newState);
|
||||
},
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
const component = await mountWithSearch(TestComponent, {
|
||||
resModel: "foo",
|
||||
searchMenuTypes: [],
|
||||
});
|
||||
expect(`.o_pager`).toHaveCount(1);
|
||||
expect(getPagerValue()).toEqual([1, 10]);
|
||||
expect(getPagerLimit()).toBe(50);
|
||||
|
||||
await pagerNext();
|
||||
expect(`.o_pager`).toHaveCount(1);
|
||||
expect(getPagerValue()).toEqual([11, 20]);
|
||||
expect(getPagerLimit()).toBe(50);
|
||||
|
||||
component.state.offset = 20;
|
||||
await animationFrame();
|
||||
expect(`.o_pager`).toHaveCount(1);
|
||||
expect(getPagerValue()).toEqual([21, 30]);
|
||||
expect(getPagerLimit()).toBe(50);
|
||||
});
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { ControlPanel } from "@web/search/control_panel/control_panel";
|
||||
import { usePager } from "@web/search/pager_hook";
|
||||
import { click, getFixture, nextTick } from "../helpers/utils";
|
||||
import { makeWithSearch, setupControlPanelServiceRegistry } from "./helpers";
|
||||
|
||||
import { Component, useState, xml } from "@odoo/owl";
|
||||
|
||||
let target;
|
||||
let serverData;
|
||||
QUnit.module("Search", (hooks) => {
|
||||
hooks.beforeEach(async () => {
|
||||
serverData = {
|
||||
models: {
|
||||
foo: {
|
||||
fields: {},
|
||||
},
|
||||
},
|
||||
views: {
|
||||
"foo,false,search": `<search/>`,
|
||||
},
|
||||
};
|
||||
setupControlPanelServiceRegistry();
|
||||
target = getFixture();
|
||||
});
|
||||
|
||||
QUnit.module("usePager");
|
||||
|
||||
QUnit.test("pager is correctly displayed", async (assert) => {
|
||||
class TestComponent extends Component {
|
||||
setup() {
|
||||
usePager(() => ({
|
||||
offset: 0,
|
||||
limit: 10,
|
||||
total: 50,
|
||||
onUpdate: () => {},
|
||||
}));
|
||||
}
|
||||
}
|
||||
TestComponent.components = { ControlPanel };
|
||||
TestComponent.template = xml`<ControlPanel />`;
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: TestComponent,
|
||||
searchMenuTypes: [],
|
||||
});
|
||||
|
||||
assert.containsOnce(target, ".o_pager");
|
||||
assert.strictEqual(
|
||||
target.querySelector(`.o_pager_counter .o_pager_value`).textContent.trim(),
|
||||
"1-10"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(`.o_pager_counter span.o_pager_limit`).textContent.trim(),
|
||||
"50"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("pager is correctly updated", async (assert) => {
|
||||
class TestComponent extends Component {
|
||||
setup() {
|
||||
this.state = useState({ offset: 0, limit: 10 });
|
||||
usePager(() => ({
|
||||
offset: this.state.offset,
|
||||
limit: this.state.limit,
|
||||
total: 50,
|
||||
onUpdate: (newState) => {
|
||||
Object.assign(this.state, newState);
|
||||
},
|
||||
}));
|
||||
}
|
||||
}
|
||||
TestComponent.components = { ControlPanel };
|
||||
TestComponent.template = xml`<ControlPanel />`;
|
||||
|
||||
const comp = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: TestComponent,
|
||||
searchMenuTypes: [],
|
||||
});
|
||||
|
||||
assert.containsOnce(target, ".o_pager");
|
||||
assert.strictEqual(
|
||||
target.querySelector(`.o_pager_counter .o_pager_value`).textContent.trim(),
|
||||
"1-10"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(`.o_pager_counter span.o_pager_limit`).textContent.trim(),
|
||||
"50"
|
||||
);
|
||||
assert.deepEqual(comp.state, {
|
||||
offset: 0,
|
||||
limit: 10,
|
||||
});
|
||||
|
||||
await click(target.querySelector(`.o_pager button.o_pager_next`));
|
||||
|
||||
assert.containsOnce(target, ".o_pager");
|
||||
assert.strictEqual(
|
||||
target.querySelector(`.o_pager_counter .o_pager_value`).textContent.trim(),
|
||||
"11-20"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(`.o_pager_counter span.o_pager_limit`).textContent.trim(),
|
||||
"50"
|
||||
);
|
||||
assert.deepEqual(comp.state, {
|
||||
offset: 10,
|
||||
limit: 10,
|
||||
});
|
||||
|
||||
comp.state.offset = 20;
|
||||
await nextTick();
|
||||
|
||||
assert.containsOnce(target, ".o_pager");
|
||||
assert.strictEqual(
|
||||
target.querySelector(`.o_pager_counter .o_pager_value`).textContent.trim(),
|
||||
"21-30"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(`.o_pager_counter span.o_pager_limit`).textContent.trim(),
|
||||
"50"
|
||||
);
|
||||
assert.deepEqual(comp.state, {
|
||||
offset: 20,
|
||||
limit: 10,
|
||||
});
|
||||
});
|
||||
});
|
||||
1929
odoo-bringout-oca-ocb-web/web/static/tests/search/search_bar.test.js
Normal file
1929
odoo-bringout-oca-ocb-web/web/static/tests/search/search_bar.test.js
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,267 @@
|
|||
import { after, expect, test } from "@odoo/hoot";
|
||||
import { queryFirst } from "@odoo/hoot-dom";
|
||||
import { mockDate } from "@odoo/hoot-mock";
|
||||
import { editValue } from "@web/../tests/core/tree_editor/condition_tree_editor_test_helpers";
|
||||
import {
|
||||
contains,
|
||||
editFavorite,
|
||||
getFacetTexts,
|
||||
isItemSelected,
|
||||
mockService,
|
||||
mountWithSearch,
|
||||
onRpc,
|
||||
toggleMenuItem,
|
||||
toggleSearchBarMenu,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
import { defineSearchBarModels } from "./models";
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { SearchBar } from "@web/search/search_bar/search_bar";
|
||||
import { SearchBarMenu } from "@web/search/search_bar_menu/search_bar_menu";
|
||||
|
||||
const favoriteMenuRegistry = registry.category("favoriteMenu");
|
||||
|
||||
defineSearchBarModels();
|
||||
|
||||
test("simple rendering with no favorite (without ability to save)", async () => {
|
||||
const registryItem = favoriteMenuRegistry.content["custom-favorite-item"];
|
||||
favoriteMenuRegistry.remove("custom-favorite-item");
|
||||
after(() => {
|
||||
favoriteMenuRegistry.add("custom-favorite-item", registryItem[1], {
|
||||
sequence: registryItem[1],
|
||||
});
|
||||
});
|
||||
|
||||
await mountWithSearch(
|
||||
SearchBarMenu,
|
||||
{
|
||||
resModel: "foo",
|
||||
searchMenuTypes: ["favorite"],
|
||||
searchViewId: false,
|
||||
},
|
||||
{ getDisplayName: () => "Action Name" }
|
||||
);
|
||||
|
||||
await toggleSearchBarMenu();
|
||||
expect(`.o_favorite_menu .fa.fa-star`).toHaveCount(1);
|
||||
expect(`.o_favorite_menu .o_dropdown_title`).toHaveText(/^favorites$/i);
|
||||
expect(`.o_favorite_menu`).toHaveCount(1);
|
||||
expect(`.o_favorite_menu .o_menu_item`).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("simple rendering with no favorite", async () => {
|
||||
await mountWithSearch(
|
||||
SearchBarMenu,
|
||||
{
|
||||
resModel: "foo",
|
||||
searchMenuTypes: ["favorite"],
|
||||
searchViewId: false,
|
||||
},
|
||||
{
|
||||
getDisplayName: () => "Action Name",
|
||||
}
|
||||
);
|
||||
|
||||
await toggleSearchBarMenu();
|
||||
expect(`.o_favorite_menu .fa.fa-star`).toHaveCount(1);
|
||||
expect(`.o_favorite_menu .o_dropdown_title`).toHaveText(/^favorites$/i);
|
||||
expect(`.o_favorite_menu`).toHaveCount(1);
|
||||
expect(`.o_favorite_menu .dropdown-divider`).toHaveCount(0);
|
||||
expect(`.o_favorite_menu .o_add_favorite`).toHaveCount(1);
|
||||
});
|
||||
|
||||
test("edit an active favorite", async () => {
|
||||
const irFilters = [
|
||||
{
|
||||
context: "{}",
|
||||
domain: "[['foo', '=', 'qsdf']]",
|
||||
id: 7,
|
||||
is_default: true,
|
||||
name: "My favorite",
|
||||
sort: "[]",
|
||||
user_ids: [2],
|
||||
},
|
||||
];
|
||||
mockService("action", {
|
||||
doAction(action) {
|
||||
expect.step("edit favorite");
|
||||
expect(action).toEqual({
|
||||
context: {
|
||||
form_view_ref: "base.ir_filters_view_edit_form",
|
||||
},
|
||||
type: "ir.actions.act_window",
|
||||
res_model: "ir.filters",
|
||||
views: [[false, "form"]],
|
||||
res_id: 7,
|
||||
});
|
||||
},
|
||||
});
|
||||
await mountWithSearch(SearchBar, {
|
||||
resModel: "foo",
|
||||
searchMenuTypes: ["favorite"],
|
||||
searchViewId: false,
|
||||
searchViewArch: `<search/>`,
|
||||
irFilters,
|
||||
});
|
||||
expect(getFacetTexts()).toEqual(["My favorite"]);
|
||||
|
||||
await toggleSearchBarMenu();
|
||||
const favorite = queryFirst`.o_favorite_menu .dropdown-item`;
|
||||
expect(favorite).toHaveText("My favorite");
|
||||
expect(favorite).toHaveAttribute("role", "menuitemcheckbox");
|
||||
expect(favorite).toHaveProperty("ariaChecked", "true");
|
||||
expect(getFacetTexts()).toEqual(["My favorite"]);
|
||||
expect(queryFirst`.o_favorite_menu .o_menu_item`).toHaveClass("selected");
|
||||
|
||||
await editFavorite("My favorite");
|
||||
expect.verifySteps(["edit favorite"]);
|
||||
});
|
||||
|
||||
test("default favorite is not activated if activateFavorite is set to false", async () => {
|
||||
const searchBarMenu = await mountWithSearch(SearchBarMenu, {
|
||||
resModel: "foo",
|
||||
searchMenuTypes: ["favorite"],
|
||||
searchViewId: false,
|
||||
irFilters: [
|
||||
{
|
||||
context: "{}",
|
||||
domain: "[('foo', '=', 'a')]",
|
||||
id: 7,
|
||||
is_default: true,
|
||||
name: "My favorite",
|
||||
sort: "[]",
|
||||
user_ids: [2],
|
||||
},
|
||||
],
|
||||
activateFavorite: false,
|
||||
});
|
||||
await toggleSearchBarMenu();
|
||||
expect(isItemSelected("My favorite")).toBe(false);
|
||||
expect(searchBarMenu.env.searchModel.domain).toEqual([]);
|
||||
expect(getFacetTexts()).toEqual([]);
|
||||
});
|
||||
|
||||
test(`toggle favorite correctly clears filter, groupbys and field "options"`, async () => {
|
||||
mockDate("2019-07-31T13:43:00");
|
||||
|
||||
const searchBar = await mountWithSearch(SearchBar, {
|
||||
resModel: "foo",
|
||||
searchMenuTypes: ["filter", "groupBy", "favorite"],
|
||||
searchViewId: false,
|
||||
irFilters: [
|
||||
{
|
||||
context: `{"group_by": ["foo"]}`,
|
||||
domain: "['!', ['foo', '=', 'qsdf']]",
|
||||
id: 7,
|
||||
is_default: false,
|
||||
name: "My favorite",
|
||||
sort: "[]",
|
||||
user_ids: [2],
|
||||
},
|
||||
],
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<field string="Foo" name="foo"/>
|
||||
<filter string="Date Field Filter" name="positive" date="date_field" default_period="year"/>
|
||||
<filter string="Date Field Groupby" name="coolName" context="{'group_by': 'date_field'}"/>
|
||||
</search>
|
||||
`,
|
||||
context: {
|
||||
search_default_positive: true,
|
||||
search_default_coolName: true,
|
||||
search_default_foo: "a",
|
||||
},
|
||||
});
|
||||
expect(searchBar.env.searchModel.domain).toEqual([
|
||||
"&",
|
||||
["foo", "ilike", "a"],
|
||||
"&",
|
||||
["date_field", ">=", "2019-01-01"],
|
||||
["date_field", "<=", "2019-12-31"],
|
||||
]);
|
||||
expect(searchBar.env.searchModel.groupBy).toEqual(["date_field:month"]);
|
||||
expect(getFacetTexts()).toEqual([
|
||||
"Foo\na",
|
||||
"Date Field Filter: 2019",
|
||||
"Date Field Groupby: Month",
|
||||
]);
|
||||
|
||||
// activate the unique existing favorite
|
||||
await toggleSearchBarMenu();
|
||||
const favorite = queryFirst`.o_favorite_menu .dropdown-item`;
|
||||
expect(favorite).toHaveText("My favorite");
|
||||
expect(favorite).toHaveAttribute("role", "menuitemcheckbox");
|
||||
expect(favorite).toHaveProperty("ariaChecked", "false");
|
||||
|
||||
await toggleMenuItem("My favorite");
|
||||
expect(favorite).toHaveProperty("ariaChecked", "true");
|
||||
expect(searchBar.env.searchModel.domain).toEqual(["!", ["foo", "=", "qsdf"]]);
|
||||
expect(searchBar.env.searchModel.groupBy).toEqual(["foo"]);
|
||||
expect(getFacetTexts()).toEqual(["My favorite"]);
|
||||
});
|
||||
|
||||
test("edit a favorite with a groupby", async () => {
|
||||
const irFilters = [
|
||||
{
|
||||
context: "{ 'some_key': 'some_value', 'group_by': ['bar'] }",
|
||||
domain: "[('foo', 'ilike', 'abc')]",
|
||||
id: 1,
|
||||
is_default: true,
|
||||
name: "My favorite",
|
||||
sort: "[]",
|
||||
user_ids: [2],
|
||||
},
|
||||
];
|
||||
|
||||
onRpc("/web/domain/validate", () => true);
|
||||
await mountWithSearch(SearchBar, {
|
||||
resModel: "foo",
|
||||
searchMenuTypes: ["groupBy"], // we need it to have facet (see facets getter in search_model)
|
||||
searchViewId: false,
|
||||
searchViewArch: `<search/>`,
|
||||
irFilters,
|
||||
});
|
||||
expect(getFacetTexts()).toEqual(["My favorite"]);
|
||||
|
||||
await toggleSearchBarMenu();
|
||||
expect(`.o_group_by_menu .o_menu_item:not(.o_add_custom_group_menu)`).toHaveCount(0);
|
||||
|
||||
await contains(`.o_searchview_facet_label`).click();
|
||||
expect(`.modal`).toHaveCount(1);
|
||||
|
||||
await editValue("abcde");
|
||||
await contains(`.modal footer button`).click();
|
||||
expect(`.modal`).toHaveCount(0);
|
||||
expect(getFacetTexts()).toEqual(["Foo contains abcde", "Bar"]);
|
||||
|
||||
await toggleSearchBarMenu();
|
||||
expect(`.o_group_by_menu .o_menu_item:not(.o_add_custom_group_menu)`).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("shared favorites are partially shown if there is more than 4", async () => {
|
||||
const irFilters = [];
|
||||
for (let i = 1; i < 6; i++) {
|
||||
irFilters.push({
|
||||
context: "{}",
|
||||
domain: "[('foo', '=', 'a')]",
|
||||
id: i,
|
||||
is_default: false,
|
||||
name: "My favorite" + i,
|
||||
sort: "[]",
|
||||
user_ids: [],
|
||||
});
|
||||
}
|
||||
await mountWithSearch(SearchBarMenu, {
|
||||
resModel: "foo",
|
||||
searchMenuTypes: ["favorite"],
|
||||
searchViewId: false,
|
||||
irFilters,
|
||||
activateFavorite: false,
|
||||
});
|
||||
await toggleSearchBarMenu();
|
||||
expect(".o_favorite_menu .o_favorite_item").toHaveCount(3);
|
||||
expect(".o_favorite_menu .o_expand_shared_favorites").toHaveCount(1);
|
||||
await contains(".o_favorite_menu .o_expand_shared_favorites").click();
|
||||
expect(".o_favorite_menu .o_expand_shared_favorites").toHaveCount(0);
|
||||
expect(".o_favorite_menu .o_favorite_item").toHaveCount(5);
|
||||
});
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,382 @@
|
|||
import { expect, test } from "@odoo/hoot";
|
||||
import { queryAllTexts, queryFirst } from "@odoo/hoot-dom";
|
||||
import {
|
||||
contains,
|
||||
getFacetTexts,
|
||||
isItemSelected,
|
||||
isOptionSelected,
|
||||
mountWithSearch,
|
||||
removeFacet,
|
||||
toggleMenuItem,
|
||||
toggleMenuItemOption,
|
||||
toggleSearchBarMenu,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
import { defineSearchBarModels } from "./models";
|
||||
|
||||
import { animationFrame } from "@odoo/hoot-mock";
|
||||
import { SearchBar } from "@web/search/search_bar/search_bar";
|
||||
import { SearchBarMenu } from "@web/search/search_bar_menu/search_bar_menu";
|
||||
|
||||
defineSearchBarModels();
|
||||
|
||||
test("simple rendering with neither groupbys nor groupable fields", async () => {
|
||||
await mountWithSearch(SearchBarMenu, {
|
||||
resModel: "foo",
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewArch: `<search />`,
|
||||
searchViewId: false,
|
||||
searchViewFields: {},
|
||||
});
|
||||
await toggleSearchBarMenu();
|
||||
expect(`.o_menu_item`).toHaveCount(0);
|
||||
expect(`.dropdown-divider`).toHaveCount(0);
|
||||
expect(`.o_add_custom_group_menu`).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("simple rendering with no groupby", async () => {
|
||||
await mountWithSearch(SearchBarMenu, {
|
||||
resModel: "foo",
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
});
|
||||
await toggleSearchBarMenu();
|
||||
expect(`.o_menu_item`).toHaveCount(1);
|
||||
expect(`.dropdown-divider`).toHaveCount(0);
|
||||
expect(`.o_add_custom_group_menu`).toHaveCount(1);
|
||||
});
|
||||
|
||||
test("simple rendering with a single groupby", async () => {
|
||||
await mountWithSearch(SearchBarMenu, {
|
||||
resModel: "foo",
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Foo" name="group_by_foo" context="{'group_by': 'foo'}"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
await toggleSearchBarMenu();
|
||||
|
||||
expect(`.o_menu_item`).toHaveCount(2);
|
||||
const menuItem = queryFirst`.o_menu_item`;
|
||||
expect(menuItem).toHaveText("Foo");
|
||||
expect(menuItem).toHaveAttribute("role", "menuitemcheckbox");
|
||||
expect(menuItem).toHaveProperty("ariaChecked", "false");
|
||||
expect(".dropdown-divider").toHaveCount(1);
|
||||
expect(".o_add_custom_group_menu").toHaveCount(1);
|
||||
});
|
||||
|
||||
test(`toggle a "simple" groupby in groupby menu works`, async () => {
|
||||
const searchBar = await mountWithSearch(SearchBar, {
|
||||
resModel: "foo",
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Foo" name="group_by_foo" context="{'group_by': 'foo'}"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
await toggleSearchBarMenu();
|
||||
|
||||
expect(searchBar.env.searchModel.groupBy).toEqual([]);
|
||||
expect(getFacetTexts()).toEqual([]);
|
||||
expect(isItemSelected("Foo")).toBe(false);
|
||||
|
||||
const menuItem = queryFirst`.o_menu_item`;
|
||||
expect(menuItem).toHaveText("Foo");
|
||||
expect(menuItem).toHaveAttribute("role", "menuitemcheckbox");
|
||||
expect(menuItem).toHaveProperty("ariaChecked", "false");
|
||||
|
||||
await toggleMenuItem("Foo");
|
||||
expect(menuItem).toHaveProperty("ariaChecked", "true");
|
||||
expect(searchBar.env.searchModel.groupBy).toEqual(["foo"]);
|
||||
expect(getFacetTexts()).toEqual(["Foo"]);
|
||||
expect(`.o_searchview .o_searchview_facet .o_searchview_facet_label`).toHaveCount(1);
|
||||
expect(isItemSelected("Foo")).toBe(true);
|
||||
|
||||
await toggleMenuItem("Foo");
|
||||
expect(searchBar.env.searchModel.groupBy).toEqual([]);
|
||||
expect(getFacetTexts()).toEqual([]);
|
||||
expect(isItemSelected("Foo")).toBe(false);
|
||||
});
|
||||
|
||||
test(`toggle a "simple" groupby quickly does not crash`, async () => {
|
||||
await mountWithSearch(SearchBarMenu, {
|
||||
resModel: "foo",
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Foo" name="group_by_foo" context="{'group_by': 'foo'}"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
await toggleSearchBarMenu();
|
||||
|
||||
await toggleMenuItem("Foo");
|
||||
await toggleMenuItem("Foo");
|
||||
await animationFrame();
|
||||
|
||||
expect(isItemSelected("Foo")).toBe(false);
|
||||
});
|
||||
|
||||
test(`remove a "Group By" facet properly unchecks groupbys in groupby menu`, async () => {
|
||||
const searchBar = await mountWithSearch(SearchBar, {
|
||||
resModel: "foo",
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Foo" name="group_by_foo" context="{'group_by': 'foo'}"/>
|
||||
</search>
|
||||
`,
|
||||
context: { search_default_group_by_foo: 1 },
|
||||
});
|
||||
await toggleSearchBarMenu();
|
||||
expect(getFacetTexts()).toEqual(["Foo"]);
|
||||
expect(searchBar.env.searchModel.groupBy).toEqual(["foo"]);
|
||||
expect(isItemSelected("Foo")).toBe(true);
|
||||
|
||||
await removeFacet("Foo");
|
||||
expect(getFacetTexts()).toEqual([]);
|
||||
expect(searchBar.env.searchModel.groupBy).toEqual([]);
|
||||
|
||||
await toggleSearchBarMenu();
|
||||
expect(isItemSelected("Foo")).toBe(false);
|
||||
});
|
||||
|
||||
test("group by a date field using interval works", async () => {
|
||||
const searchBar = await mountWithSearch(SearchBar, {
|
||||
resModel: "foo",
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Date" name="date" context="{'group_by': 'date_field:week'}"/>
|
||||
</search>
|
||||
`,
|
||||
context: { search_default_date: 1 },
|
||||
});
|
||||
await toggleSearchBarMenu();
|
||||
expect(searchBar.env.searchModel.groupBy).toEqual(["date_field:week"]);
|
||||
|
||||
await toggleMenuItem("Date");
|
||||
expect(isOptionSelected("Date", "Week")).toBe(true);
|
||||
expect(queryAllTexts`.o_item_option`).toEqual(["Year", "Quarter", "Month", "Week", "Day"]);
|
||||
|
||||
await toggleMenuItemOption("Date", "Year");
|
||||
expect(searchBar.env.searchModel.groupBy).toEqual(["date_field:year", "date_field:week"]);
|
||||
expect(getFacetTexts()).toEqual(["Date: Year\n>\nDate: Week"]);
|
||||
expect(isOptionSelected("Date", "Year")).toBe(true);
|
||||
expect(isOptionSelected("Date", "Week")).toBe(true);
|
||||
|
||||
await toggleMenuItemOption("Date", "Month");
|
||||
expect(searchBar.env.searchModel.groupBy).toEqual([
|
||||
"date_field:year",
|
||||
"date_field:month",
|
||||
"date_field:week",
|
||||
]);
|
||||
expect(getFacetTexts()).toEqual(["Date: Year\n>\nDate: Month\n>\nDate: Week"]);
|
||||
expect(isOptionSelected("Date", "Year")).toBe(true);
|
||||
expect(isOptionSelected("Date", "Month")).toBe(true);
|
||||
expect(isOptionSelected("Date", "Week")).toBe(true);
|
||||
|
||||
await toggleMenuItemOption("Date", "Week");
|
||||
expect(searchBar.env.searchModel.groupBy).toEqual(["date_field:year", "date_field:month"]);
|
||||
expect(getFacetTexts()).toEqual(["Date: Year\n>\nDate: Month"]);
|
||||
expect(isOptionSelected("Date", "Year")).toBe(true);
|
||||
expect(isOptionSelected("Date", "Month")).toBe(true);
|
||||
|
||||
await toggleMenuItemOption("Date", "Month");
|
||||
expect(searchBar.env.searchModel.groupBy).toEqual(["date_field:year"]);
|
||||
expect(getFacetTexts()).toEqual(["Date: Year"]);
|
||||
expect(isOptionSelected("Date", "Year")).toBe(true);
|
||||
|
||||
await toggleMenuItemOption("Date", "Year");
|
||||
expect(searchBar.env.searchModel.groupBy).toEqual([]);
|
||||
expect(getFacetTexts()).toEqual([]);
|
||||
});
|
||||
|
||||
test("interval options are correctly grouped and ordered", async () => {
|
||||
await mountWithSearch(SearchBar, {
|
||||
resModel: "foo",
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Bar" name="bar" context="{'group_by': 'bar'}"/>
|
||||
<filter string="Date" name="date" context="{'group_by': 'date_field'}"/>
|
||||
<filter string="Foo" name="foo" context="{'group_by': 'foo'}"/>
|
||||
</search>
|
||||
`,
|
||||
context: { search_default_bar: 1 },
|
||||
});
|
||||
expect(getFacetTexts()).toEqual(["Bar"]);
|
||||
|
||||
await toggleSearchBarMenu();
|
||||
await toggleMenuItem("Date");
|
||||
await toggleMenuItemOption("Date", "Week");
|
||||
expect(getFacetTexts()).toEqual(["Bar\n>\nDate: Week"]);
|
||||
|
||||
await toggleMenuItemOption("Date", "Day");
|
||||
expect(getFacetTexts()).toEqual(["Bar\n>\nDate: Week\n>\nDate: Day"]);
|
||||
|
||||
await toggleMenuItemOption("Date", "Year");
|
||||
expect(getFacetTexts()).toEqual(["Bar\n>\nDate: Year\n>\nDate: Week\n>\nDate: Day"]);
|
||||
|
||||
await toggleMenuItem("Foo");
|
||||
expect(getFacetTexts()).toEqual(["Bar\n>\nDate: Year\n>\nDate: Week\n>\nDate: Day\n>\nFoo"]);
|
||||
|
||||
await toggleMenuItemOption("Date", "Quarter");
|
||||
expect(getFacetTexts()).toEqual([
|
||||
"Bar\n>\nDate: Year\n>\nDate: Quarter\n>\nDate: Week\n>\nDate: Day\n>\nFoo",
|
||||
]);
|
||||
|
||||
await toggleMenuItem("Bar");
|
||||
expect(getFacetTexts()).toEqual([
|
||||
"Date: Year\n>\nDate: Quarter\n>\nDate: Week\n>\nDate: Day\n>\nFoo",
|
||||
]);
|
||||
|
||||
await toggleMenuItemOption("Date", "Week");
|
||||
expect(getFacetTexts()).toEqual(["Date: Year\n>\nDate: Quarter\n>\nDate: Day\n>\nFoo"]);
|
||||
});
|
||||
|
||||
test("default groupbys can be ordered", async () => {
|
||||
const searchBar = await mountWithSearch(SearchBar, {
|
||||
resModel: "foo",
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Birthday" name="birthday" context="{'group_by': 'birthday'}"/>
|
||||
<filter string="Date" name="date" context="{'group_by': 'date_field:week'}"/>
|
||||
</search>
|
||||
`,
|
||||
context: { search_default_birthday: 2, search_default_date: 1 },
|
||||
});
|
||||
|
||||
// the default groupbys should be activated in the right order
|
||||
expect(searchBar.env.searchModel.groupBy).toEqual(["date_field:week", "birthday:month"]);
|
||||
expect(getFacetTexts()).toEqual(["Date: Week\n>\nBirthday: Month"]);
|
||||
});
|
||||
|
||||
test("a separator in groupbys does not cause problems", async () => {
|
||||
await mountWithSearch(SearchBar, {
|
||||
resModel: "foo",
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Date" name="coolName" context="{'group_by': 'date_field'}"/>
|
||||
<separator/>
|
||||
<filter string="Bar" name="superName" context="{'group_by': 'bar'}"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
await toggleSearchBarMenu();
|
||||
await toggleMenuItem("Date");
|
||||
await toggleMenuItemOption("Date", "Day");
|
||||
expect(isItemSelected("Date")).toBe(true);
|
||||
expect(isItemSelected("Bar")).toBe(false);
|
||||
expect(isOptionSelected("Date", "Day")).toBe(true);
|
||||
expect(getFacetTexts()).toEqual(["Date: Day"]);
|
||||
|
||||
await toggleMenuItem("Bar");
|
||||
expect(isItemSelected("Date")).toBe(true);
|
||||
expect(isItemSelected("Bar")).toBe(true);
|
||||
expect(isOptionSelected("Date", "Day")).toBe(true);
|
||||
expect(getFacetTexts()).toEqual(["Date: Day\n>\nBar"]);
|
||||
|
||||
await toggleMenuItemOption("Date", "Quarter");
|
||||
expect(isItemSelected("Date")).toBe(true);
|
||||
expect(isItemSelected("Bar")).toBe(true);
|
||||
expect(isOptionSelected("Date", "Quarter")).toBe(true);
|
||||
expect(isOptionSelected("Date", "Day")).toBe(true);
|
||||
expect(getFacetTexts()).toEqual(["Date: Quarter\n>\nDate: Day\n>\nBar"]);
|
||||
|
||||
await toggleMenuItem("Bar");
|
||||
expect(isItemSelected("Date")).toBe(true);
|
||||
expect(isItemSelected("Bar")).toBe(false);
|
||||
expect(isOptionSelected("Date", "Quarter")).toBe(true);
|
||||
expect(isOptionSelected("Date", "Day")).toBe(true);
|
||||
expect(getFacetTexts()).toEqual(["Date: Quarter\n>\nDate: Day"]);
|
||||
|
||||
await contains(`.o_facet_remove`).click();
|
||||
expect(getFacetTexts()).toEqual([]);
|
||||
|
||||
await toggleSearchBarMenu();
|
||||
await toggleMenuItem("Date");
|
||||
expect(isItemSelected("Date")).toBe(false);
|
||||
expect(isItemSelected("Bar")).toBe(false);
|
||||
expect(isOptionSelected("Date", "Quarter")).toBe(false);
|
||||
expect(isOptionSelected("Date", "Day")).toBe(false);
|
||||
});
|
||||
|
||||
test("falsy search default groupbys are not activated", async () => {
|
||||
const searchBar = await mountWithSearch(SearchBar, {
|
||||
resModel: "foo",
|
||||
searchMenuTypes: ["groupBy"],
|
||||
searchViewId: false,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Birthday" name="birthday" context="{'group_by': 'birthday'}"/>
|
||||
<filter string="Date" name="date" context="{'group_by': 'foo'}"/>
|
||||
</search>
|
||||
`,
|
||||
context: { search_default_birthday: false, search_default_foo: 0 },
|
||||
});
|
||||
expect(searchBar.env.searchModel.groupBy).toEqual([]);
|
||||
expect(getFacetTexts()).toEqual([]);
|
||||
});
|
||||
|
||||
test("Custom group by menu is displayed when hideCustomGroupBy is not set", async () => {
|
||||
await mountWithSearch(SearchBarMenu, {
|
||||
resModel: "foo",
|
||||
searchViewId: false,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Birthday" name="birthday" context="{'group_by': 'birthday'}"/>
|
||||
<filter string="Date" name="date" context="{'group_by': 'foo'}"/>
|
||||
</search>
|
||||
`,
|
||||
searchMenuTypes: ["groupBy"],
|
||||
});
|
||||
await toggleSearchBarMenu();
|
||||
expect(`.o_add_custom_group_menu`).toHaveCount(1);
|
||||
});
|
||||
|
||||
test("Custom group by menu is displayed when hideCustomGroupBy is false", async () => {
|
||||
await mountWithSearch(SearchBarMenu, {
|
||||
resModel: "foo",
|
||||
searchViewId: false,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Birthday" name="birthday" context="{'group_by': 'birthday'}"/>
|
||||
<filter string="Date" name="date" context="{'group_by': 'foo'}"/>
|
||||
</search>
|
||||
`,
|
||||
hideCustomGroupBy: false,
|
||||
searchMenuTypes: ["groupBy"],
|
||||
});
|
||||
await toggleSearchBarMenu();
|
||||
expect(`.o_add_custom_group_menu`).toHaveCount(1);
|
||||
});
|
||||
|
||||
test("Custom group by menu is displayed when hideCustomGroupBy is true", async () => {
|
||||
await mountWithSearch(SearchBarMenu, {
|
||||
resModel: "foo",
|
||||
searchViewId: false,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter string="Birthday" name="birthday" context="{'group_by': 'birthday'}"/>
|
||||
<filter string="Date" name="date" context="{'group_by': 'foo'}"/>
|
||||
</search>
|
||||
`,
|
||||
hideCustomGroupBy: true,
|
||||
searchMenuTypes: ["groupBy"],
|
||||
});
|
||||
await toggleSearchBarMenu();
|
||||
expect(`.o_add_custom_group_menu`).toHaveCount(0);
|
||||
});
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
import { defineModels, fields, models } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class Foo extends models.Model {
|
||||
bar = fields.Many2one({ relation: "partner" });
|
||||
foo = fields.Char();
|
||||
birthday = fields.Date();
|
||||
date_field = fields.Date({ string: "Date" });
|
||||
parent_id = fields.Many2one({ string: "Parent", relation: "parent.model" });
|
||||
properties = fields.Properties({
|
||||
definition_record: "parent_id",
|
||||
definition_record_field: "properties_definition",
|
||||
});
|
||||
|
||||
_views = {
|
||||
search: `
|
||||
<search>
|
||||
<filter name="birthday" date="birthday"/>
|
||||
<filter name="date_field" date="date_field"/>
|
||||
</search>
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
||||
export class Partner extends models.Model {
|
||||
name = fields.Char();
|
||||
}
|
||||
|
||||
export class ParentModel extends models.Model {
|
||||
_name = "parent.model";
|
||||
|
||||
name = fields.Char();
|
||||
properties_definition = fields.PropertiesDefinition();
|
||||
}
|
||||
|
||||
export function defineSearchBarModels() {
|
||||
defineModels([Foo, Partner, ParentModel]);
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,980 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { patchDate, patchWithCleanup } from "@web/../tests/helpers/utils";
|
||||
import { makeWithSearch, setupControlPanelServiceRegistry } from "./helpers";
|
||||
|
||||
import { Component, xml } from "@odoo/owl";
|
||||
|
||||
class TestComponent extends Component {}
|
||||
TestComponent.template = xml`<div class="o_test_component"/>`;
|
||||
|
||||
async function makeSearchModel(params) {
|
||||
const component = await makeWithSearch({
|
||||
Component: TestComponent,
|
||||
resModel: "foo",
|
||||
searchViewId: false,
|
||||
...params,
|
||||
});
|
||||
return component.env.searchModel;
|
||||
}
|
||||
|
||||
function sanitizeSearchItems(model) {
|
||||
// We should not access searchItems but there is a problem with getSearchItems:
|
||||
// comparisons are not sent back in some cases
|
||||
const searchItems = Object.values(model.searchItems);
|
||||
return searchItems.map((searchItem) => {
|
||||
const copy = Object.assign({}, searchItem);
|
||||
delete copy.groupId;
|
||||
delete copy.groupNumber;
|
||||
delete copy.id;
|
||||
return copy;
|
||||
});
|
||||
}
|
||||
|
||||
let serverData;
|
||||
QUnit.module("Search", (hooks) => {
|
||||
hooks.beforeEach(() => {
|
||||
serverData = {
|
||||
models: {
|
||||
foo: {
|
||||
fields: {
|
||||
display_name: { string: "Displayed name", type: "char" },
|
||||
foo: {
|
||||
string: "Foo",
|
||||
type: "char",
|
||||
default: "My little Foo Value",
|
||||
store: true,
|
||||
sortable: true,
|
||||
},
|
||||
date_field: { string: "Date", type: "date", store: true, sortable: true },
|
||||
float_field: { string: "Float", type: "float" },
|
||||
bar: { string: "Bar", type: "many2one", relation: "partner" },
|
||||
},
|
||||
records: [],
|
||||
},
|
||||
partner: {
|
||||
fields: {
|
||||
foo: { string: "Foo", type: "char" },
|
||||
bar: { string: "Bar", type: "boolean" },
|
||||
int_field: { string: "Int Field", type: "integer", group_operator: "sum" },
|
||||
company_id: { string: "company", type: "many2one", relation: "company" },
|
||||
company_ids: {
|
||||
string: "Companies",
|
||||
type: "many2many",
|
||||
relation: "company",
|
||||
},
|
||||
category_id: { string: "category", type: "many2one", relation: "category" },
|
||||
state: {
|
||||
string: "State",
|
||||
type: "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,
|
||||
},
|
||||
],
|
||||
},
|
||||
company: {
|
||||
fields: {
|
||||
name: { string: "Display Name", type: "char" },
|
||||
parent_id: {
|
||||
string: "Parent company",
|
||||
type: "many2one",
|
||||
relation: "company",
|
||||
},
|
||||
category_id: { string: "Category", type: "many2one", relation: "category" },
|
||||
},
|
||||
records: [
|
||||
{ id: 3, name: "asustek", category_id: 6 },
|
||||
{ id: 5, name: "agrolait", category_id: 7 },
|
||||
],
|
||||
},
|
||||
category: {
|
||||
fields: {
|
||||
name: { string: "Category Name", type: "char" },
|
||||
},
|
||||
records: [
|
||||
{ id: 6, name: "gold" },
|
||||
{ id: 7, name: "silver" },
|
||||
],
|
||||
},
|
||||
},
|
||||
views: {
|
||||
"foo,false,search": `<search/>`,
|
||||
"partner,false,search": `<search/>`,
|
||||
},
|
||||
};
|
||||
setupControlPanelServiceRegistry();
|
||||
});
|
||||
|
||||
QUnit.module("SearchModel");
|
||||
|
||||
QUnit.test("parsing empty arch", async function (assert) {
|
||||
assert.expect(1);
|
||||
const model = await makeSearchModel({ serverData });
|
||||
assert.deepEqual(sanitizeSearchItems(model), []);
|
||||
});
|
||||
|
||||
QUnit.test("parsing one field tag", async function (assert) {
|
||||
assert.expect(1);
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<field name="bar"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
assert.deepEqual(sanitizeSearchItems(model), [
|
||||
{
|
||||
description: "Bar",
|
||||
fieldName: "bar",
|
||||
fieldType: "many2one",
|
||||
type: "field",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("parsing one separator tag", async function (assert) {
|
||||
assert.expect(1);
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<separator/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
assert.deepEqual(sanitizeSearchItems(model), []);
|
||||
});
|
||||
|
||||
QUnit.test("parsing one separator tag and one field tag", async function (assert) {
|
||||
assert.expect(1);
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<separator/>
|
||||
<field name="bar"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
assert.deepEqual(sanitizeSearchItems(model), [
|
||||
{
|
||||
description: "Bar",
|
||||
fieldName: "bar",
|
||||
fieldType: "many2one",
|
||||
type: "field",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("parsing one filter tag", async function (assert) {
|
||||
assert.expect(1);
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter name="filter" string="Hello" domain="[]"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
assert.deepEqual(sanitizeSearchItems(model), [
|
||||
{
|
||||
description: "Hello",
|
||||
domain: "[]",
|
||||
name: "filter",
|
||||
type: "filter",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"parsing one filter tag with default_period date attribute",
|
||||
async function (assert) {
|
||||
assert.expect(1);
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter name="date_filter" string="Date" date="date_field" default_period="this_year,last_year"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
const dateFilterId = model.getSearchItems((f) => f.type === "dateFilter")[0].id;
|
||||
assert.deepEqual(sanitizeSearchItems(model), [
|
||||
{
|
||||
defaultGeneratorIds: ["this_year", "last_year"],
|
||||
description: "Date",
|
||||
fieldName: "date_field",
|
||||
fieldType: "date",
|
||||
type: "dateFilter",
|
||||
name: "date_filter",
|
||||
},
|
||||
{
|
||||
comparisonOptionId: "previous_period",
|
||||
dateFilterId,
|
||||
description: "Date: Previous Period",
|
||||
type: "comparison",
|
||||
},
|
||||
{
|
||||
comparisonOptionId: "previous_year",
|
||||
dateFilterId,
|
||||
description: "Date: Previous Year",
|
||||
type: "comparison",
|
||||
},
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("parsing one filter tag with date attribute ", async function (assert) {
|
||||
assert.expect(1);
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter name="date_filter" string="Date" date="date_field"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
const dateFilterId = model.getSearchItems((f) => f.type === "dateFilter")[0].id;
|
||||
assert.deepEqual(sanitizeSearchItems(model), [
|
||||
{
|
||||
defaultGeneratorIds: ["this_month"],
|
||||
description: "Date",
|
||||
fieldName: "date_field",
|
||||
fieldType: "date",
|
||||
name: "date_filter",
|
||||
type: "dateFilter",
|
||||
},
|
||||
{
|
||||
comparisonOptionId: "previous_period",
|
||||
dateFilterId,
|
||||
description: "Date: Previous Period",
|
||||
type: "comparison",
|
||||
},
|
||||
{
|
||||
comparisonOptionId: "previous_year",
|
||||
dateFilterId,
|
||||
description: "Date: Previous Year",
|
||||
type: "comparison",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("parsing one groupBy tag", async function (assert) {
|
||||
assert.expect(1);
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter name="groupby" string="Hi" context="{ 'group_by': 'date_field:day'}"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
assert.deepEqual(sanitizeSearchItems(model), [
|
||||
{
|
||||
defaultIntervalId: "day",
|
||||
description: "Hi",
|
||||
fieldName: "date_field",
|
||||
fieldType: "date",
|
||||
name: "groupby",
|
||||
type: "dateGroupBy",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("parsing two filter tags", async function (assert) {
|
||||
assert.expect(1);
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter name="filter_1" string="Hello One" domain="[]"/>
|
||||
<filter name="filter_2" string="Hello Two" domain="[('bar', '=', 3)]"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
assert.deepEqual(sanitizeSearchItems(model), [
|
||||
{
|
||||
description: "Hello One",
|
||||
domain: "[]",
|
||||
name: "filter_1",
|
||||
type: "filter",
|
||||
},
|
||||
{
|
||||
description: "Hello Two",
|
||||
domain: "[('bar', '=', 3)]",
|
||||
name: "filter_2",
|
||||
type: "filter",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("parsing two filter tags separated by a separator", async function (assert) {
|
||||
assert.expect(1);
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter name="filter_1" string="Hello One" domain="[]"/>
|
||||
<separator/>
|
||||
<filter name="filter_2" string="Hello Two" domain="[('bar', '=', 3)]"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
assert.deepEqual(sanitizeSearchItems(model), [
|
||||
{
|
||||
description: "Hello One",
|
||||
domain: "[]",
|
||||
name: "filter_1",
|
||||
type: "filter",
|
||||
},
|
||||
{
|
||||
description: "Hello Two",
|
||||
domain: "[('bar', '=', 3)]",
|
||||
name: "filter_2",
|
||||
type: "filter",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("parsing one filter tag and one field", async function (assert) {
|
||||
assert.expect(1);
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter name="filter" string="Hello" domain="[]"/>
|
||||
<field name="bar"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
assert.deepEqual(sanitizeSearchItems(model), [
|
||||
{
|
||||
description: "Hello",
|
||||
domain: "[]",
|
||||
name: "filter",
|
||||
type: "filter",
|
||||
},
|
||||
{
|
||||
description: "Bar",
|
||||
fieldName: "bar",
|
||||
fieldType: "many2one",
|
||||
type: "field",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("parsing two field tags", async function (assert) {
|
||||
assert.expect(1);
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<field name="foo"/>
|
||||
<field name="bar"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
assert.deepEqual(sanitizeSearchItems(model), [
|
||||
{
|
||||
description: "Foo",
|
||||
fieldName: "foo",
|
||||
fieldType: "char",
|
||||
type: "field",
|
||||
},
|
||||
{
|
||||
description: "Bar",
|
||||
fieldName: "bar",
|
||||
fieldType: "many2one",
|
||||
type: "field",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("parsing a searchpanel tag", async function (assert) {
|
||||
assert.expect(1);
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<searchpanel/>
|
||||
</search>
|
||||
`,
|
||||
config: { viewType: "kanban" },
|
||||
});
|
||||
assert.deepEqual(model.getSections(), []);
|
||||
});
|
||||
|
||||
QUnit.test("parsing a searchpanel field select one", async function (assert) {
|
||||
assert.expect(1);
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<searchpanel>
|
||||
<field name="company_id"/>
|
||||
</searchpanel>
|
||||
</search>
|
||||
`,
|
||||
resModel: "partner",
|
||||
config: { viewType: "kanban" },
|
||||
});
|
||||
const sections = model.getSections();
|
||||
for (const section of sections) {
|
||||
section.values = [...section.values];
|
||||
}
|
||||
assert.deepEqual(sections, [
|
||||
{
|
||||
activeValueId: false,
|
||||
color: null,
|
||||
description: "company",
|
||||
empty: false,
|
||||
enableCounters: false,
|
||||
expand: false,
|
||||
fieldName: "company_id",
|
||||
hierarchize: true,
|
||||
icon: "fa-folder",
|
||||
id: 1,
|
||||
limit: 200,
|
||||
parentField: "parent_id",
|
||||
rootIds: [false, 3, 5],
|
||||
type: "category",
|
||||
values: [
|
||||
[
|
||||
false,
|
||||
{
|
||||
bold: true,
|
||||
childrenIds: [],
|
||||
display_name: "All",
|
||||
id: false,
|
||||
parentId: false,
|
||||
},
|
||||
],
|
||||
[
|
||||
3,
|
||||
{
|
||||
childrenIds: [],
|
||||
display_name: "asustek",
|
||||
id: 3,
|
||||
parentId: false,
|
||||
parent_id: false,
|
||||
},
|
||||
],
|
||||
[
|
||||
5,
|
||||
{
|
||||
childrenIds: [],
|
||||
display_name: "agrolait",
|
||||
id: 5,
|
||||
parentId: false,
|
||||
parent_id: false,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("parsing a searchpanel field select multi", async function (assert) {
|
||||
assert.expect(1);
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<searchpanel>
|
||||
<field name="company_id" select="multi"/>
|
||||
</searchpanel>
|
||||
</search>
|
||||
`,
|
||||
resModel: "partner",
|
||||
config: { viewType: "kanban" },
|
||||
});
|
||||
const sections = model.getSections();
|
||||
for (const section of sections) {
|
||||
section.values = [...section.values];
|
||||
}
|
||||
assert.deepEqual(sections, [
|
||||
{
|
||||
color: null,
|
||||
description: "company",
|
||||
domain: "[]",
|
||||
empty: false,
|
||||
enableCounters: false,
|
||||
expand: false,
|
||||
fieldName: "company_id",
|
||||
groupBy: null,
|
||||
icon: "fa-filter",
|
||||
id: 1,
|
||||
limit: 200,
|
||||
type: "filter",
|
||||
values: [
|
||||
[
|
||||
3,
|
||||
{
|
||||
checked: false,
|
||||
display_name: "asustek",
|
||||
id: 3,
|
||||
},
|
||||
],
|
||||
[
|
||||
5,
|
||||
{
|
||||
checked: false,
|
||||
display_name: "agrolait",
|
||||
id: 5,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("parsing a filter and a dateFilter", async function (assert) {
|
||||
assert.expect(1);
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter name="filter" string="Filter" domain="[['foo', '=', 'a']]"/>
|
||||
<filter name="date_filter" string="Date" date="date_field"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
const groupNumbers = model.getSearchItems(() => true).map((i) => i.groupNumber);
|
||||
assert.deepEqual(groupNumbers, [1, 1]);
|
||||
});
|
||||
|
||||
QUnit.test("parsing a groupBy and a dateGroupBy", async function (assert) {
|
||||
assert.expect(1);
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter name="group_by" context="{ 'group_by': 'foo'}"/>
|
||||
<filter name="date_groupBy" string="DateGroupBy" context="{'group_by': 'date_field:day'}"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
const groupNumbers = model.getSearchItems(() => true).map((i) => i.groupNumber);
|
||||
assert.deepEqual(groupNumbers, [1, 1]);
|
||||
});
|
||||
|
||||
QUnit.test("parsing a filter and a groupBy", async function (assert) {
|
||||
assert.expect(1);
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter name="filter" string="Filter" domain="[['foo', '=', 'a']]"/>
|
||||
<filter name="group_by" context="{ 'group_by': 'foo'}"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
const groupNumbers = model.getSearchItems(() => true).map((i) => i.groupNumber);
|
||||
assert.deepEqual(groupNumbers, [1, 2]);
|
||||
});
|
||||
|
||||
QUnit.test("parsing a groupBy and a filter", async function (assert) {
|
||||
assert.expect(1);
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter name="group_by" context="{ 'group_by': 'foo'}"/>
|
||||
<filter name="filter" string="Filter" domain="[['foo', '=', 'a']]"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
const groupNumbers = model.getSearchItems(() => true).map((i) => i.groupNumber);
|
||||
assert.deepEqual(groupNumbers, [2, 1]);
|
||||
});
|
||||
|
||||
QUnit.test("process search default group by", async function (assert) {
|
||||
assert.expect(1);
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter name="group_by" context="{ 'group_by': 'foo'}"/>
|
||||
</search>
|
||||
`,
|
||||
context: { search_default_group_by: 14 },
|
||||
});
|
||||
assert.deepEqual(sanitizeSearchItems(model), [
|
||||
{
|
||||
defaultRank: 14,
|
||||
description: "Foo",
|
||||
fieldName: "foo",
|
||||
fieldType: "char",
|
||||
name: "group_by",
|
||||
type: "groupBy",
|
||||
isDefault: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("process and toggle a field with a context to evaluate", async function (assert) {
|
||||
assert.expect(2);
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<field name="foo" context="{ 'a': self }"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
assert.deepEqual(sanitizeSearchItems(model), [
|
||||
{
|
||||
context: "{ 'a': self }",
|
||||
description: "Foo",
|
||||
fieldName: "foo",
|
||||
fieldType: "char",
|
||||
type: "field",
|
||||
},
|
||||
]);
|
||||
model.addAutoCompletionValues(1, { label: "7", operator: "=", value: 7 });
|
||||
assert.deepEqual(model.context, { a: [7], lang: "en", tz: "taht", uid: 7 });
|
||||
});
|
||||
|
||||
QUnit.test("process favorite filters", async function (assert) {
|
||||
assert.expect(1);
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
irFilters: [
|
||||
{
|
||||
user_id: [2, "Mitchell Admin"],
|
||||
name: "Sorted filter",
|
||||
id: 5,
|
||||
context: `{"group_by":["foo","bar"]}`,
|
||||
sort: '["foo", "-bar"]',
|
||||
domain: "[('user_id', '=', uid)]",
|
||||
is_default: false,
|
||||
model_id: "res.partner",
|
||||
action_id: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
assert.deepEqual(sanitizeSearchItems(model), [
|
||||
{
|
||||
context: {},
|
||||
description: "Sorted filter",
|
||||
domain: "[('user_id', '=', uid)]",
|
||||
groupBys: ["foo", "bar"],
|
||||
orderBy: [
|
||||
{
|
||||
asc: true,
|
||||
name: "foo",
|
||||
},
|
||||
{
|
||||
asc: false,
|
||||
name: "bar",
|
||||
},
|
||||
],
|
||||
removable: true,
|
||||
serverSideId: 5,
|
||||
type: "favorite",
|
||||
userId: 2,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("process dynamic filters", async function (assert) {
|
||||
assert.expect(1);
|
||||
assert.expect(1);
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
dynamicFilters: [
|
||||
{
|
||||
description: "Quick search",
|
||||
domain: [["id", "in", [1, 3, 4]]],
|
||||
},
|
||||
],
|
||||
});
|
||||
assert.deepEqual(sanitizeSearchItems(model), [
|
||||
{
|
||||
description: "Quick search",
|
||||
domain: [["id", "in", [1, 3, 4]]],
|
||||
isDefault: true,
|
||||
type: "filter",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("process a dynamic filter with a isDefault key to false", async function (assert) {
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
dynamicFilters: [
|
||||
{
|
||||
description: "Quick search",
|
||||
domain: [],
|
||||
is_default: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
assert.deepEqual(sanitizeSearchItems(model), [
|
||||
{
|
||||
description: "Quick search",
|
||||
domain: [],
|
||||
isDefault: false,
|
||||
type: "filter",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("toggle a filter", async function (assert) {
|
||||
assert.expect(1);
|
||||
assert.expect(3);
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter name="filter" string="Filter" domain="[['foo', '=', 'a']]"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
const filterId = Object.keys(model.searchItems).map((key) => Number(key))[0];
|
||||
assert.deepEqual([], model.domain);
|
||||
model.toggleSearchItem(filterId);
|
||||
assert.deepEqual([["foo", "=", "a"]], model.domain);
|
||||
model.toggleSearchItem(filterId);
|
||||
assert.deepEqual([], model.domain);
|
||||
});
|
||||
|
||||
QUnit.test("toggle a date filter", async function (assert) {
|
||||
assert.expect(3);
|
||||
patchDate(2019, 0, 6, 15, 0, 0);
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter name="date_filter" date="date_field" string="DateFilter"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
const filterId = Object.keys(model.searchItems).map((key) => Number(key))[0];
|
||||
model.toggleDateFilter(filterId);
|
||||
assert.deepEqual(
|
||||
["&", ["date_field", ">=", "2019-01-01"], ["date_field", "<=", "2019-01-31"]],
|
||||
model.domain
|
||||
);
|
||||
model.toggleDateFilter(filterId, "first_quarter");
|
||||
assert.deepEqual(
|
||||
[
|
||||
"|",
|
||||
"&",
|
||||
["date_field", ">=", "2019-01-01"],
|
||||
["date_field", "<=", "2019-01-31"],
|
||||
"&",
|
||||
["date_field", ">=", "2019-01-01"],
|
||||
["date_field", "<=", "2019-03-31"],
|
||||
],
|
||||
model.domain
|
||||
);
|
||||
model.toggleDateFilter(filterId, "this_year");
|
||||
assert.deepEqual([], model.domain);
|
||||
});
|
||||
|
||||
QUnit.test("toggle a groupBy", async function (assert) {
|
||||
assert.expect(3);
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter name="groupBy" string="GroupBy" context="{'group_by': 'foo'}"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
const filterId = Object.keys(model.searchItems).map((key) => Number(key))[0];
|
||||
assert.deepEqual(model.groupBy, []);
|
||||
model.toggleSearchItem(filterId);
|
||||
assert.deepEqual(model.groupBy, ["foo"]);
|
||||
model.toggleSearchItem(filterId);
|
||||
assert.deepEqual(model.groupBy, []);
|
||||
});
|
||||
|
||||
QUnit.test("toggle a date groupBy", async function (assert) {
|
||||
assert.expect(5);
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter name="date_groupBy" string="DateGroupBy" context="{'group_by': 'date_field:day'}"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
const filterId = Object.keys(model.searchItems).map((key) => Number(key))[0];
|
||||
assert.deepEqual(model.groupBy, []);
|
||||
model.toggleDateGroupBy(filterId);
|
||||
assert.deepEqual(model.groupBy, ["date_field:day"]);
|
||||
model.toggleDateGroupBy(filterId, "week");
|
||||
assert.deepEqual(model.groupBy, ["date_field:week", "date_field:day"]);
|
||||
model.toggleDateGroupBy(filterId);
|
||||
assert.deepEqual(model.groupBy, ["date_field:week"]);
|
||||
model.toggleDateGroupBy(filterId, "week");
|
||||
assert.deepEqual(model.groupBy, []);
|
||||
});
|
||||
|
||||
QUnit.test("create a new groupBy", async function (assert) {
|
||||
assert.expect(2);
|
||||
const model = await makeSearchModel({ serverData });
|
||||
model.createNewGroupBy("foo");
|
||||
assert.deepEqual(sanitizeSearchItems(model), [
|
||||
{
|
||||
custom: true,
|
||||
description: "Foo",
|
||||
fieldName: "foo",
|
||||
fieldType: "char",
|
||||
type: "groupBy",
|
||||
},
|
||||
]);
|
||||
assert.deepEqual(model.groupBy, ["foo"]);
|
||||
});
|
||||
|
||||
QUnit.test("create a new dateGroupBy", async function (assert) {
|
||||
assert.expect(2);
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter name="foo" string="Foo" context="{'group_by': 'foo'}"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
model.createNewGroupBy("date_field");
|
||||
assert.deepEqual(sanitizeSearchItems(model), [
|
||||
{
|
||||
description: "Foo",
|
||||
fieldName: "foo",
|
||||
fieldType: "char",
|
||||
name: "foo",
|
||||
type: "groupBy",
|
||||
},
|
||||
{
|
||||
custom: true,
|
||||
defaultIntervalId: "month",
|
||||
description: "Date",
|
||||
fieldName: "date_field",
|
||||
fieldType: "date",
|
||||
type: "dateGroupBy",
|
||||
},
|
||||
]);
|
||||
assert.deepEqual(model.groupBy, ["date_field:month"]);
|
||||
});
|
||||
|
||||
QUnit.test("dynamic domains evaluation", async function (assert) {
|
||||
patchDate(2021, 8, 17, 10, 0, 0);
|
||||
|
||||
patchWithCleanup(Date.prototype, {
|
||||
getTimezoneOffset() {
|
||||
return -120;
|
||||
},
|
||||
});
|
||||
|
||||
const searchViewArch = `
|
||||
<search>
|
||||
<filter name="filter_0" domain="[('datetime', '=', (datetime.datetime.combine(context_today(), datetime.time(0,0,0)).to_utc()))]"/>
|
||||
<filter name="filter_1" domain="[('date', '=', context_today() + relativedelta(days=-365))]"/>
|
||||
<filter name="filter_2" domain="[('create_date', '>', (context_today() - datetime.timedelta(days=1)).strftime('%Y-%m-%d'))]"/>
|
||||
<filter name="filter_3" domain="[('date_deadline', '<', current_date)]"/>
|
||||
</search>
|
||||
`;
|
||||
|
||||
const evaluatedDomains = [
|
||||
[["datetime", "=", "2021-09-16 22:00:00"]],
|
||||
[["date", "=", "2020-09-17"]],
|
||||
[["create_date", ">", "2021-09-16"]],
|
||||
[["date_deadline", "<", "2021-09-17"]],
|
||||
];
|
||||
|
||||
const model = await makeSearchModel({ serverData, searchViewArch });
|
||||
|
||||
for (let i = 0; i < evaluatedDomains.length; i++) {
|
||||
model.toggleSearchItem(i + 1);
|
||||
assert.deepEqual(model.domain, evaluatedDomains[i]);
|
||||
model.toggleSearchItem(i + 1);
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test("dynamic domains evaluation using global context", async function (assert) {
|
||||
const searchViewArch = `
|
||||
<search>
|
||||
<filter name="filter" domain="[('date_deadline', '<', context.get('my_date'))]"/>
|
||||
</search>
|
||||
`;
|
||||
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
searchViewArch,
|
||||
context: {
|
||||
my_date: "2021-09-17",
|
||||
},
|
||||
});
|
||||
|
||||
model.toggleSearchItem(1);
|
||||
assert.deepEqual(model.domain, [["date_deadline", "<", "2021-09-17"]]);
|
||||
});
|
||||
|
||||
QUnit.test("no search items created for search panel sections", async function (assert) {
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<searchpanel>
|
||||
<field name="company_id"/>
|
||||
<field name="company_id" select="multi"/>
|
||||
</searchpanel>
|
||||
</search>
|
||||
`,
|
||||
resModel: "partner",
|
||||
config: { viewType: "kanban" },
|
||||
});
|
||||
const sections = model.getSections();
|
||||
assert.strictEqual(sections.length, 2);
|
||||
assert.deepEqual(sanitizeSearchItems(model), []);
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,136 @@
|
|||
import { describe, expect, test } from "@odoo/hoot";
|
||||
import { queryAllTexts } from "@odoo/hoot-dom";
|
||||
import { Component, xml } from "@odoo/owl";
|
||||
import {
|
||||
contains,
|
||||
defineModels,
|
||||
fields,
|
||||
models,
|
||||
mountWithSearch,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { SearchPanel } from "@web/search/search_panel/search_panel";
|
||||
|
||||
class Partner extends models.Model {
|
||||
name = fields.Char();
|
||||
foo = fields.Char();
|
||||
bar = fields.Boolean();
|
||||
int_field = fields.Integer({ string: "Int Field", aggregator: "sum" });
|
||||
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,
|
||||
state: "abc",
|
||||
category_id: 6,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
bar: true,
|
||||
foo: "blip",
|
||||
int_field: 2,
|
||||
state: "def",
|
||||
category_id: 7,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
bar: true,
|
||||
foo: "gnap",
|
||||
int_field: 4,
|
||||
state: "ghi",
|
||||
category_id: 7,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
bar: false,
|
||||
foo: "blip",
|
||||
int_field: 8,
|
||||
state: "ghi",
|
||||
category_id: 7,
|
||||
},
|
||||
];
|
||||
_views = {
|
||||
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="category_id" expand="1"/>
|
||||
</searchpanel>
|
||||
</search>
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
||||
class Category extends models.Model {
|
||||
name = fields.Char({ string: "Category Name" });
|
||||
|
||||
_records = [
|
||||
{ id: 6, name: "gold" },
|
||||
{ id: 7, name: "silver" },
|
||||
];
|
||||
}
|
||||
|
||||
defineModels([Partner, Category]);
|
||||
|
||||
describe.current.tags("mobile");
|
||||
|
||||
test("basic search panel rendering", async () => {
|
||||
class Parent extends Component {
|
||||
static components = { SearchPanel };
|
||||
static template = xml`<SearchPanel/>`;
|
||||
static props = ["*"];
|
||||
}
|
||||
|
||||
await mountWithSearch(Parent, {
|
||||
resModel: "partner",
|
||||
searchViewId: false,
|
||||
});
|
||||
|
||||
expect(".o_search_panel .o-dropdown").toHaveCount(1);
|
||||
expect(".o_search_panel .o-dropdown").toHaveText("category");
|
||||
|
||||
await contains(".o_search_panel .o-dropdown").click();
|
||||
expect(".o_search_panel_section.o_search_panel_category").toHaveCount(1);
|
||||
expect(".o_search_panel_category_value").toHaveCount(3);
|
||||
expect(queryAllTexts(".o_search_panel_field li")).toEqual(["All", "gold", "silver"]);
|
||||
|
||||
await contains(".o_search_panel_category_value:nth-of-type(2) header").click();
|
||||
expect(".o_search_panel .o-dropdown").toHaveText("gold");
|
||||
expect(".o_search_panel a").toHaveCount(1);
|
||||
|
||||
await contains(".o_search_panel a").click();
|
||||
expect(".o_search_panel .o-dropdown").toHaveText("category");
|
||||
});
|
||||
|
||||
test("Dropdown closes on category selection", async () => {
|
||||
class Parent extends Component {
|
||||
static components = { SearchPanel };
|
||||
static template = xml`<SearchPanel/>`;
|
||||
static props = ["*"];
|
||||
}
|
||||
|
||||
await mountWithSearch(Parent, {
|
||||
resModel: "partner",
|
||||
searchViewId: false,
|
||||
});
|
||||
|
||||
expect(".o-dropdown--menu").toHaveCount(0);
|
||||
await contains(".o_search_panel .o-dropdown").click();
|
||||
expect(".o-dropdown--menu").toHaveCount(1);
|
||||
|
||||
await contains(".o_search_panel_category_value:nth-of-type(2) header").click();
|
||||
expect(".o-dropdown--menu").toHaveCount(0);
|
||||
});
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,307 @@
|
|||
import { beforeEach, describe, expect, test } from "@odoo/hoot";
|
||||
import { mockDate, mockTimeZone } from "@odoo/hoot-mock";
|
||||
import {
|
||||
allowTranslations,
|
||||
patchTranslations,
|
||||
patchWithCleanup,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { Domain } from "@web/core/domain";
|
||||
import { localization } from "@web/core/l10n/localization";
|
||||
import { constructDateDomain } from "@web/search/utils/dates";
|
||||
|
||||
describe.current.tags("headless");
|
||||
|
||||
const dateSearchItem = {
|
||||
fieldName: "date_field",
|
||||
fieldType: "date",
|
||||
optionsParams: {
|
||||
customOptions: [],
|
||||
endMonth: 0,
|
||||
endYear: 0,
|
||||
startMonth: -2,
|
||||
startYear: -2,
|
||||
},
|
||||
type: "dateFilter",
|
||||
};
|
||||
const dateTimeSearchItem = {
|
||||
...dateSearchItem,
|
||||
fieldType: "datetime",
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockTimeZone(0);
|
||||
patchWithCleanup(localization, { direction: "ltr" });
|
||||
allowTranslations();
|
||||
});
|
||||
|
||||
test("construct simple domain based on date field (no comparisonOptionId)", () => {
|
||||
mockDate("2020-06-01T13:00:00");
|
||||
const referenceMoment = luxon.DateTime.local();
|
||||
|
||||
let domain = constructDateDomain(referenceMoment, dateSearchItem, []);
|
||||
expect(domain).toEqual({
|
||||
domain: new Domain(`[]`),
|
||||
description: "",
|
||||
});
|
||||
|
||||
domain = constructDateDomain(referenceMoment, dateSearchItem, ["month", "year"]);
|
||||
expect(domain).toEqual({
|
||||
domain: new Domain(
|
||||
`["&", ("date_field", ">=", "2020-06-01"), ("date_field", "<=", "2020-06-30")]`
|
||||
),
|
||||
description: "June 2020",
|
||||
});
|
||||
|
||||
domain = constructDateDomain(referenceMoment, dateSearchItem, ["second_quarter", "year"]);
|
||||
expect(domain).toEqual({
|
||||
domain: new Domain(
|
||||
`["&", ("date_field", ">=", "2020-04-01"), ("date_field", "<=", "2020-06-30")]`
|
||||
),
|
||||
description: "Q2 2020",
|
||||
});
|
||||
|
||||
domain = constructDateDomain(referenceMoment, dateSearchItem, ["year"]);
|
||||
expect(domain).toEqual({
|
||||
domain: new Domain(
|
||||
`["&", ("date_field", ">=", "2020-01-01"), ("date_field", "<=", "2020-12-31")]`
|
||||
),
|
||||
description: "2020",
|
||||
});
|
||||
});
|
||||
|
||||
test("construct simple domain based on date field (no comparisonOptionId) - UTC+2", () => {
|
||||
mockTimeZone(2);
|
||||
mockDate("2020-06-01T00:00:00");
|
||||
const referenceMoment = luxon.DateTime.local();
|
||||
|
||||
let domain = constructDateDomain(referenceMoment, dateSearchItem, []);
|
||||
expect(domain).toEqual({
|
||||
domain: new Domain(`[]`),
|
||||
description: "",
|
||||
});
|
||||
|
||||
domain = constructDateDomain(referenceMoment, dateSearchItem, ["month", "year"]);
|
||||
expect(domain).toEqual({
|
||||
domain: new Domain(
|
||||
`["&", ("date_field", ">=", "2020-06-01"), ("date_field", "<=", "2020-06-30")]`
|
||||
),
|
||||
description: "June 2020",
|
||||
});
|
||||
|
||||
domain = constructDateDomain(referenceMoment, dateSearchItem, ["second_quarter", "year"]);
|
||||
expect(domain).toEqual({
|
||||
domain: new Domain(
|
||||
`["&", ("date_field", ">=", "2020-04-01"), ("date_field", "<=", "2020-06-30")]`
|
||||
),
|
||||
description: "Q2 2020",
|
||||
});
|
||||
|
||||
domain = constructDateDomain(referenceMoment, dateSearchItem, ["year"]);
|
||||
expect(domain).toEqual({
|
||||
domain: new Domain(
|
||||
`["&", ("date_field", ">=", "2020-01-01"), ("date_field", "<=", "2020-12-31")]`
|
||||
),
|
||||
description: "2020",
|
||||
});
|
||||
});
|
||||
|
||||
test("construct simple domain based on datetime field (no comparisonOptionId)", () => {
|
||||
mockDate("2020-06-01T13:00:00");
|
||||
const referenceMoment = luxon.DateTime.local();
|
||||
|
||||
let domain = constructDateDomain(referenceMoment, dateTimeSearchItem, ["month", "year"]);
|
||||
expect(domain).toEqual({
|
||||
domain: new Domain(
|
||||
`["&", ("date_field", ">=", "2020-06-01 00:00:00"), ("date_field", "<=", "2020-06-30 23:59:59")]`
|
||||
),
|
||||
description: "June 2020",
|
||||
});
|
||||
|
||||
domain = constructDateDomain(referenceMoment, dateTimeSearchItem, ["second_quarter", "year"]);
|
||||
expect(domain).toEqual({
|
||||
domain: new Domain(
|
||||
`["&", ("date_field", ">=", "2020-04-01 00:00:00"), ("date_field", "<=", "2020-06-30 23:59:59")]`
|
||||
),
|
||||
description: "Q2 2020",
|
||||
});
|
||||
|
||||
domain = constructDateDomain(referenceMoment, dateTimeSearchItem, ["year"]);
|
||||
expect(domain).toEqual({
|
||||
domain: new Domain(
|
||||
`["&", ("date_field", ">=", "2020-01-01 00:00:00"), ("date_field", "<=", "2020-12-31 23:59:59")]`
|
||||
),
|
||||
description: "2020",
|
||||
});
|
||||
});
|
||||
|
||||
test("construct simple domain based on datetime field (no comparisonOptionId) - UTC+2", () => {
|
||||
mockTimeZone(2);
|
||||
mockDate("2020-06-01T00:00:00");
|
||||
const referenceMoment = luxon.DateTime.local();
|
||||
|
||||
let domain = constructDateDomain(referenceMoment, dateTimeSearchItem, ["month", "year"]);
|
||||
expect(domain).toEqual({
|
||||
domain: new Domain(
|
||||
`["&", ("date_field", ">=", "2020-05-31 22:00:00"), ("date_field", "<=", "2020-06-30 21:59:59")]`
|
||||
),
|
||||
description: "June 2020",
|
||||
});
|
||||
|
||||
domain = constructDateDomain(referenceMoment, dateTimeSearchItem, ["second_quarter", "year"]);
|
||||
expect(domain).toEqual({
|
||||
domain: new Domain(
|
||||
`["&", ("date_field", ">=", "2020-03-31 22:00:00"), ("date_field", "<=", "2020-06-30 21:59:59")]`
|
||||
),
|
||||
description: "Q2 2020",
|
||||
});
|
||||
|
||||
domain = constructDateDomain(referenceMoment, dateTimeSearchItem, ["year"]);
|
||||
expect(domain).toEqual({
|
||||
domain: new Domain(
|
||||
`["&", ("date_field", ">=", "2019-12-31 22:00:00"), ("date_field", "<=", "2020-12-31 21:59:59")]`
|
||||
),
|
||||
description: "2020",
|
||||
});
|
||||
});
|
||||
|
||||
test("construct domain based on date field (no comparisonOptionId)", () => {
|
||||
mockDate("2020-01-01T12:00:00");
|
||||
const referenceMoment = luxon.DateTime.local();
|
||||
|
||||
let domain = constructDateDomain(referenceMoment, dateSearchItem, [
|
||||
"month",
|
||||
"first_quarter",
|
||||
"year",
|
||||
]);
|
||||
expect(domain).toEqual({
|
||||
domain: new Domain(
|
||||
"[" +
|
||||
`"|", ` +
|
||||
`"&", ("date_field", ">=", "2020-01-01"), ("date_field", "<=", "2020-01-31"), ` +
|
||||
`"&", ("date_field", ">=", "2020-01-01"), ("date_field", "<=", "2020-03-31")` +
|
||||
"]"
|
||||
),
|
||||
description: "January 2020/Q1 2020",
|
||||
});
|
||||
|
||||
domain = constructDateDomain(referenceMoment, dateSearchItem, [
|
||||
"second_quarter",
|
||||
"year",
|
||||
"year-1",
|
||||
]);
|
||||
expect(domain).toEqual({
|
||||
domain: new Domain(
|
||||
"[" +
|
||||
`"|", ` +
|
||||
`"&", ("date_field", ">=", "2019-04-01"), ("date_field", "<=", "2019-06-30"), ` +
|
||||
`"&", ("date_field", ">=", "2020-04-01"), ("date_field", "<=", "2020-06-30")` +
|
||||
"]"
|
||||
),
|
||||
description: "Q2 2019/Q2 2020",
|
||||
});
|
||||
|
||||
domain = constructDateDomain(referenceMoment, dateSearchItem, ["year", "month", "month-2"]);
|
||||
expect(domain).toEqual({
|
||||
domain: new Domain(
|
||||
"[" +
|
||||
`"|", ` +
|
||||
`"&", ("date_field", ">=", "2020-01-01"), ("date_field", "<=", "2020-01-31"), ` +
|
||||
`"&", ("date_field", ">=", "2020-11-01"), ("date_field", "<=", "2020-11-30")` +
|
||||
"]"
|
||||
),
|
||||
description: "January 2020/November 2020",
|
||||
});
|
||||
});
|
||||
|
||||
test("construct domain based on datetime field (no comparisonOptionId)", () => {
|
||||
mockDate("2020-01-01T12:00:00");
|
||||
const referenceMoment = luxon.DateTime.local();
|
||||
|
||||
let domain = constructDateDomain(referenceMoment, dateTimeSearchItem, [
|
||||
"month",
|
||||
"first_quarter",
|
||||
"year",
|
||||
]);
|
||||
expect(domain).toEqual({
|
||||
domain: new Domain(
|
||||
"[" +
|
||||
`"|", ` +
|
||||
`"&", ("date_field", ">=", "2020-01-01 00:00:00"), ("date_field", "<=", "2020-01-31 23:59:59"), ` +
|
||||
`"&", ("date_field", ">=", "2020-01-01 00:00:00"), ("date_field", "<=", "2020-03-31 23:59:59")` +
|
||||
"]"
|
||||
),
|
||||
description: "January 2020/Q1 2020",
|
||||
});
|
||||
|
||||
domain = constructDateDomain(referenceMoment, dateTimeSearchItem, [
|
||||
"second_quarter",
|
||||
"year",
|
||||
"year-1",
|
||||
]);
|
||||
expect(domain).toEqual({
|
||||
domain: new Domain(
|
||||
"[" +
|
||||
`"|", ` +
|
||||
`"&", ("date_field", ">=", "2019-04-01 00:00:00"), ("date_field", "<=", "2019-06-30 23:59:59"), ` +
|
||||
`"&", ("date_field", ">=", "2020-04-01 00:00:00"), ("date_field", "<=", "2020-06-30 23:59:59")` +
|
||||
"]"
|
||||
),
|
||||
description: "Q2 2019/Q2 2020",
|
||||
});
|
||||
|
||||
domain = constructDateDomain(referenceMoment, dateTimeSearchItem, ["year", "month", "month-2"]);
|
||||
expect(domain).toEqual({
|
||||
domain: new Domain(
|
||||
"[" +
|
||||
`"|", ` +
|
||||
`"&", ("date_field", ">=", "2020-01-01 00:00:00"), ("date_field", "<=", "2020-01-31 23:59:59"), ` +
|
||||
`"&", ("date_field", ">=", "2020-11-01 00:00:00"), ("date_field", "<=", "2020-11-30 23:59:59")` +
|
||||
"]"
|
||||
),
|
||||
description: "January 2020/November 2020",
|
||||
});
|
||||
});
|
||||
|
||||
test("Quarter option: custom translation", async () => {
|
||||
mockDate("2020-06-01T13:00:00");
|
||||
const referenceMoment = luxon.DateTime.local().setLocale("en");
|
||||
patchTranslations({ web: { Q2: "Deuxième trimestre de l'an de grâce" } });
|
||||
|
||||
const domain = constructDateDomain(referenceMoment, dateSearchItem, ["second_quarter", "year"]);
|
||||
expect(domain).toEqual({
|
||||
domain: new Domain(
|
||||
`["&", ("date_field", ">=", "2020-04-01"), ("date_field", "<=", "2020-06-30")]`
|
||||
),
|
||||
description: "Deuxième trimestre de l'an de grâce 2020",
|
||||
});
|
||||
});
|
||||
|
||||
test("Quarter option: right to left", async () => {
|
||||
mockDate("2020-06-01T13:00:00");
|
||||
const referenceMoment = luxon.DateTime.local().setLocale("en");
|
||||
patchWithCleanup(localization, { direction: "rtl" });
|
||||
|
||||
const domain = constructDateDomain(referenceMoment, dateSearchItem, ["second_quarter", "year"]);
|
||||
expect(domain).toEqual({
|
||||
domain: new Domain(
|
||||
`["&", ("date_field", ">=", "2020-04-01"), ("date_field", "<=", "2020-06-30")]`
|
||||
),
|
||||
description: "2020 Q2",
|
||||
});
|
||||
});
|
||||
|
||||
test("Quarter option: custom translation and right to left", async () => {
|
||||
mockDate("2020-06-01T13:00:00");
|
||||
const referenceMoment = luxon.DateTime.local().setLocale("en");
|
||||
patchWithCleanup(localization, { direction: "rtl" });
|
||||
patchTranslations({ web: { Q2: "2e Trimestre" } });
|
||||
|
||||
const domain = constructDateDomain(referenceMoment, dateSearchItem, ["second_quarter", "year"]);
|
||||
expect(domain).toEqual({
|
||||
domain: new Domain(
|
||||
`["&", ("date_field", ">=", "2020-04-01"), ("date_field", "<=", "2020-06-30")]`
|
||||
),
|
||||
description: "2020 2e Trimestre",
|
||||
});
|
||||
});
|
||||
|
|
@ -1,593 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { constructDateDomain } from "@web/search/utils/dates";
|
||||
import { defaultLocalization } from "@web/../tests/helpers/mock_services";
|
||||
import { Domain } from "@web/core/domain";
|
||||
import { localization } from "@web/core/l10n/localization";
|
||||
import { patch, unpatch } from "@web/core/utils/patch";
|
||||
import { patchDate } from "@web/../tests/helpers/utils";
|
||||
import { registerCleanup } from "@web/../tests/helpers/cleanup";
|
||||
import { translatedTerms } from "@web/core/l10n/translation";
|
||||
|
||||
const { DateTime } = luxon;
|
||||
|
||||
function patchTimeZone(offset) {
|
||||
const fixedZone = new luxon.FixedOffsetZone.instance(offset);
|
||||
const originalZone = luxon.Settings.defaultZone;
|
||||
luxon.Settings.defaultZone = fixedZone.name;
|
||||
registerCleanup(() => {
|
||||
luxon.Settings.defaultZone = originalZone;
|
||||
});
|
||||
}
|
||||
|
||||
QUnit.module("Search", () => {
|
||||
QUnit.module("SearchUtils", {
|
||||
beforeEach() {
|
||||
patchTimeZone(0);
|
||||
},
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"construct simple domain based on date field (no comparisonOptionId)",
|
||||
function (assert) {
|
||||
patchDate(2020, 5, 1, 13, 0, 0);
|
||||
const referenceMoment = DateTime.local();
|
||||
assert.deepEqual(constructDateDomain(referenceMoment, "date_field", "date", []), {
|
||||
domain: new Domain(`[]`),
|
||||
description: "",
|
||||
});
|
||||
assert.deepEqual(
|
||||
constructDateDomain(referenceMoment, "date_field", "date", [
|
||||
"this_month",
|
||||
"this_year",
|
||||
]),
|
||||
{
|
||||
domain: new Domain(
|
||||
`["&", ("date_field", ">=", "2020-06-01"), ("date_field", "<=", "2020-06-30")]`
|
||||
),
|
||||
description: "June 2020",
|
||||
}
|
||||
);
|
||||
assert.deepEqual(
|
||||
constructDateDomain(referenceMoment, "date_field", "date", [
|
||||
"second_quarter",
|
||||
"this_year",
|
||||
]),
|
||||
{
|
||||
domain: new Domain(
|
||||
`["&", ("date_field", ">=", "2020-04-01"), ("date_field", "<=", "2020-06-30")]`
|
||||
),
|
||||
description: "Q2 2020",
|
||||
}
|
||||
);
|
||||
assert.deepEqual(
|
||||
constructDateDomain(referenceMoment, "date_field", "date", ["this_year"]),
|
||||
{
|
||||
domain: new Domain(
|
||||
`["&", ("date_field", ">=", "2020-01-01"), ("date_field", "<=", "2020-12-31")]`
|
||||
),
|
||||
description: "2020",
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"construct simple domain based on date field (no comparisonOptionId) - UTC+2",
|
||||
(assert) => {
|
||||
patchTimeZone(120);
|
||||
patchDate(2020, 5, 1, 0, 0, 0);
|
||||
const referenceMoment = DateTime.local();
|
||||
assert.deepEqual(constructDateDomain(referenceMoment, "date_field", "date", []), {
|
||||
domain: new Domain(`[]`),
|
||||
description: "",
|
||||
});
|
||||
assert.deepEqual(
|
||||
constructDateDomain(referenceMoment, "date_field", "date", [
|
||||
"this_month",
|
||||
"this_year",
|
||||
]),
|
||||
{
|
||||
domain: new Domain(
|
||||
`["&", ("date_field", ">=", "2020-06-01"), ("date_field", "<=", "2020-06-30")]`
|
||||
),
|
||||
description: "June 2020",
|
||||
}
|
||||
);
|
||||
assert.deepEqual(
|
||||
constructDateDomain(referenceMoment, "date_field", "date", [
|
||||
"second_quarter",
|
||||
"this_year",
|
||||
]),
|
||||
{
|
||||
domain: new Domain(
|
||||
`["&", ("date_field", ">=", "2020-04-01"), ("date_field", "<=", "2020-06-30")]`
|
||||
),
|
||||
description: "Q2 2020",
|
||||
}
|
||||
);
|
||||
assert.deepEqual(
|
||||
constructDateDomain(referenceMoment, "date_field", "date", ["this_year"]),
|
||||
{
|
||||
domain: new Domain(
|
||||
`["&", ("date_field", ">=", "2020-01-01"), ("date_field", "<=", "2020-12-31")]`
|
||||
),
|
||||
description: "2020",
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"construct simple domain based on datetime field (no comparisonOptionId)",
|
||||
function (assert) {
|
||||
patchDate(2020, 5, 1, 13, 0, 0);
|
||||
const referenceMoment = DateTime.local();
|
||||
assert.deepEqual(
|
||||
constructDateDomain(referenceMoment, "date_field", "datetime", [
|
||||
"this_month",
|
||||
"this_year",
|
||||
]),
|
||||
{
|
||||
domain: new Domain(
|
||||
`["&", ("date_field", ">=", "2020-06-01 00:00:00"), ("date_field", "<=", "2020-06-30 23:59:59")]`
|
||||
),
|
||||
description: "June 2020",
|
||||
}
|
||||
);
|
||||
assert.deepEqual(
|
||||
constructDateDomain(referenceMoment, "date_field", "datetime", [
|
||||
"second_quarter",
|
||||
"this_year",
|
||||
]),
|
||||
{
|
||||
domain: new Domain(
|
||||
`["&", ("date_field", ">=", "2020-04-01 00:00:00"), ("date_field", "<=", "2020-06-30 23:59:59")]`
|
||||
),
|
||||
description: "Q2 2020",
|
||||
}
|
||||
);
|
||||
assert.deepEqual(
|
||||
constructDateDomain(referenceMoment, "date_field", "datetime", ["this_year"]),
|
||||
{
|
||||
domain: new Domain(
|
||||
`["&", ("date_field", ">=", "2020-01-01 00:00:00"), ("date_field", "<=", "2020-12-31 23:59:59")]`
|
||||
),
|
||||
description: "2020",
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"construct simple domain based on datetime field (no comparisonOptionId) - UTC+2",
|
||||
(assert) => {
|
||||
patchTimeZone(120);
|
||||
patchDate(2020, 5, 1, 0, 0, 0);
|
||||
const referenceMoment = DateTime.local();
|
||||
assert.deepEqual(
|
||||
constructDateDomain(referenceMoment, "date_field", "datetime", [
|
||||
"this_month",
|
||||
"this_year",
|
||||
]),
|
||||
{
|
||||
domain: new Domain(
|
||||
`["&", ("date_field", ">=", "2020-05-31 22:00:00"), ("date_field", "<=", "2020-06-30 21:59:59")]`
|
||||
),
|
||||
description: "June 2020",
|
||||
}
|
||||
);
|
||||
assert.deepEqual(
|
||||
constructDateDomain(referenceMoment, "date_field", "datetime", [
|
||||
"second_quarter",
|
||||
"this_year",
|
||||
]),
|
||||
{
|
||||
domain: new Domain(
|
||||
`["&", ("date_field", ">=", "2020-03-31 22:00:00"), ("date_field", "<=", "2020-06-30 21:59:59")]`
|
||||
),
|
||||
description: "Q2 2020",
|
||||
}
|
||||
);
|
||||
assert.deepEqual(
|
||||
constructDateDomain(referenceMoment, "date_field", "datetime", ["this_year"]),
|
||||
{
|
||||
domain: new Domain(
|
||||
`["&", ("date_field", ">=", "2019-12-31 22:00:00"), ("date_field", "<=", "2020-12-31 21:59:59")]`
|
||||
),
|
||||
description: "2020",
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("construct domain based on date field (no comparisonOptionId)", function (assert) {
|
||||
patchDate(2020, 0, 1, 12, 0, 0);
|
||||
const referenceMoment = DateTime.local();
|
||||
assert.deepEqual(
|
||||
constructDateDomain(referenceMoment, "date_field", "date", [
|
||||
"this_month",
|
||||
"first_quarter",
|
||||
"this_year",
|
||||
]),
|
||||
{
|
||||
domain: new Domain(
|
||||
"[" +
|
||||
`"|", ` +
|
||||
`"&", ("date_field", ">=", "2020-01-01"), ("date_field", "<=", "2020-01-31"), ` +
|
||||
`"&", ("date_field", ">=", "2020-01-01"), ("date_field", "<=", "2020-03-31")` +
|
||||
"]"
|
||||
),
|
||||
description: "January 2020/Q1 2020",
|
||||
}
|
||||
);
|
||||
assert.deepEqual(
|
||||
constructDateDomain(referenceMoment, "date_field", "date", [
|
||||
"second_quarter",
|
||||
"this_year",
|
||||
"last_year",
|
||||
]),
|
||||
{
|
||||
domain: new Domain(
|
||||
"[" +
|
||||
`"|", ` +
|
||||
`"&", ("date_field", ">=", "2019-04-01"), ("date_field", "<=", "2019-06-30"), ` +
|
||||
`"&", ("date_field", ">=", "2020-04-01"), ("date_field", "<=", "2020-06-30")` +
|
||||
"]"
|
||||
),
|
||||
description: "Q2 2019/Q2 2020",
|
||||
}
|
||||
);
|
||||
assert.deepEqual(
|
||||
constructDateDomain(referenceMoment, "date_field", "date", [
|
||||
"this_year",
|
||||
"this_month",
|
||||
"antepenultimate_month",
|
||||
]),
|
||||
{
|
||||
domain: new Domain(
|
||||
"[" +
|
||||
`"|", ` +
|
||||
`"&", ("date_field", ">=", "2020-01-01"), ("date_field", "<=", "2020-01-31"), ` +
|
||||
`"&", ("date_field", ">=", "2020-11-01"), ("date_field", "<=", "2020-11-30")` +
|
||||
"]"
|
||||
),
|
||||
description: "January 2020/November 2020",
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"construct domain based on datetime field (no comparisonOptionId)",
|
||||
function (assert) {
|
||||
patchDate(2020, 0, 1, 12, 0, 0);
|
||||
const referenceMoment = DateTime.local();
|
||||
assert.deepEqual(
|
||||
constructDateDomain(referenceMoment, "date_field", "datetime", [
|
||||
"this_month",
|
||||
"first_quarter",
|
||||
"this_year",
|
||||
]),
|
||||
{
|
||||
domain: new Domain(
|
||||
"[" +
|
||||
`"|", ` +
|
||||
`"&", ("date_field", ">=", "2020-01-01 00:00:00"), ("date_field", "<=", "2020-01-31 23:59:59"), ` +
|
||||
`"&", ("date_field", ">=", "2020-01-01 00:00:00"), ("date_field", "<=", "2020-03-31 23:59:59")` +
|
||||
"]"
|
||||
),
|
||||
description: "January 2020/Q1 2020",
|
||||
}
|
||||
);
|
||||
assert.deepEqual(
|
||||
constructDateDomain(referenceMoment, "date_field", "datetime", [
|
||||
"second_quarter",
|
||||
"this_year",
|
||||
"last_year",
|
||||
]),
|
||||
{
|
||||
domain: new Domain(
|
||||
"[" +
|
||||
`"|", ` +
|
||||
`"&", ("date_field", ">=", "2019-04-01 00:00:00"), ("date_field", "<=", "2019-06-30 23:59:59"), ` +
|
||||
`"&", ("date_field", ">=", "2020-04-01 00:00:00"), ("date_field", "<=", "2020-06-30 23:59:59")` +
|
||||
"]"
|
||||
),
|
||||
description: "Q2 2019/Q2 2020",
|
||||
}
|
||||
);
|
||||
assert.deepEqual(
|
||||
constructDateDomain(referenceMoment, "date_field", "datetime", [
|
||||
"this_year",
|
||||
"this_month",
|
||||
"antepenultimate_month",
|
||||
]),
|
||||
{
|
||||
domain: new Domain(
|
||||
"[" +
|
||||
`"|", ` +
|
||||
`"&", ("date_field", ">=", "2020-01-01 00:00:00"), ("date_field", "<=", "2020-01-31 23:59:59"), ` +
|
||||
`"&", ("date_field", ">=", "2020-11-01 00:00:00"), ("date_field", "<=", "2020-11-30 23:59:59")` +
|
||||
"]"
|
||||
),
|
||||
description: "January 2020/November 2020",
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
'construct comparison domain based on date field and option "previous_period"',
|
||||
function (assert) {
|
||||
patchDate(2020, 0, 1, 12, 0, 0);
|
||||
const referenceMoment = DateTime.local();
|
||||
assert.deepEqual(
|
||||
constructDateDomain(
|
||||
referenceMoment,
|
||||
"date_field",
|
||||
"date",
|
||||
["this_month", "first_quarter", "this_year"],
|
||||
"previous_period"
|
||||
),
|
||||
{
|
||||
domain: new Domain(
|
||||
"[" +
|
||||
`"|", ` +
|
||||
`"&", ("date_field", ">=", "2019-10-01"), ("date_field", "<=", "2019-10-31"), ` +
|
||||
`"|", ` +
|
||||
`"&", ("date_field", ">=", "2019-11-01"), ("date_field", "<=", "2019-11-30"), ` +
|
||||
`"&", ("date_field", ">=", "2019-12-01"), ("date_field", "<=", "2019-12-31")` +
|
||||
"]"
|
||||
),
|
||||
description: "October 2019/November 2019/December 2019",
|
||||
}
|
||||
);
|
||||
assert.deepEqual(
|
||||
constructDateDomain(
|
||||
referenceMoment,
|
||||
"date_field",
|
||||
"date",
|
||||
["second_quarter", "this_year", "last_year"],
|
||||
"previous_period"
|
||||
),
|
||||
{
|
||||
domain: new Domain(
|
||||
"[" +
|
||||
`"|", ` +
|
||||
`"&", ("date_field", ">=", "2018-01-01"), ("date_field", "<=", "2018-03-31"), ` +
|
||||
`"&", ("date_field", ">=", "2019-01-01"), ("date_field", "<=", "2019-03-31")` +
|
||||
"]"
|
||||
),
|
||||
description: "Q1 2018/Q1 2019",
|
||||
}
|
||||
);
|
||||
assert.deepEqual(
|
||||
constructDateDomain(
|
||||
referenceMoment,
|
||||
"date_field",
|
||||
"date",
|
||||
["this_year", "antepenultimate_year", "this_month", "antepenultimate_month"],
|
||||
"previous_period"
|
||||
),
|
||||
{
|
||||
domain: new Domain(
|
||||
"[" +
|
||||
`"|", ` +
|
||||
`"&", ("date_field", ">=", "2015-02-01"), ("date_field", "<=", "2015-02-28"), ` +
|
||||
`"|", ` +
|
||||
`"&", ("date_field", ">=", "2015-12-01"), ("date_field", "<=", "2015-12-31"), ` +
|
||||
`"|", ` +
|
||||
`"&", ("date_field", ">=", "2017-02-01"), ("date_field", "<=", "2017-02-28"), ` +
|
||||
`"&", ("date_field", ">=", "2017-12-01"), ("date_field", "<=", "2017-12-31")` +
|
||||
"]"
|
||||
),
|
||||
description: "February 2015/December 2015/February 2017/December 2017",
|
||||
}
|
||||
);
|
||||
assert.deepEqual(
|
||||
constructDateDomain(
|
||||
referenceMoment,
|
||||
"date_field",
|
||||
"date",
|
||||
["this_year", "last_year"],
|
||||
"previous_period"
|
||||
),
|
||||
{
|
||||
domain: new Domain(
|
||||
"[" +
|
||||
`"|", ` +
|
||||
`"&", ("date_field", ">=", "2017-01-01"), ("date_field", "<=", "2017-12-31"), ` +
|
||||
`"&", ("date_field", ">=", "2018-01-01"), ("date_field", "<=", "2018-12-31")` +
|
||||
"]"
|
||||
),
|
||||
description: "2017/2018",
|
||||
}
|
||||
);
|
||||
assert.deepEqual(
|
||||
constructDateDomain(
|
||||
referenceMoment,
|
||||
"date_field",
|
||||
"date",
|
||||
["second_quarter", "third_quarter", "last_year"],
|
||||
"previous_period"
|
||||
),
|
||||
{
|
||||
domain: new Domain(
|
||||
"[" +
|
||||
`"|", ` +
|
||||
`"&", ("date_field", ">=", "2018-10-01"), ("date_field", "<=", "2018-12-31"), ` +
|
||||
`"&", ("date_field", ">=", "2019-01-01"), ("date_field", "<=", "2019-03-31")` +
|
||||
"]"
|
||||
),
|
||||
description: "Q4 2018/Q1 2019",
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
'construct comparison domain based on datetime field and option "previous_year"',
|
||||
function (assert) {
|
||||
patchDate(2020, 5, 1, 13, 0, 0);
|
||||
const referenceMoment = DateTime.local();
|
||||
assert.deepEqual(
|
||||
constructDateDomain(
|
||||
referenceMoment,
|
||||
"date_field",
|
||||
"datetime",
|
||||
["this_month", "first_quarter", "this_year"],
|
||||
"previous_year"
|
||||
),
|
||||
{
|
||||
domain: new Domain(
|
||||
"[" +
|
||||
`"|", ` +
|
||||
`"&", ("date_field", ">=", "2019-06-01 00:00:00"), ("date_field", "<=", "2019-06-30 23:59:59"), ` +
|
||||
`"&", ("date_field", ">=", "2019-01-01 00:00:00"), ("date_field", "<=", "2019-03-31 23:59:59")` +
|
||||
"]"
|
||||
),
|
||||
description: "June 2019/Q1 2019",
|
||||
}
|
||||
);
|
||||
assert.deepEqual(
|
||||
constructDateDomain(
|
||||
referenceMoment,
|
||||
"date_field",
|
||||
"datetime",
|
||||
["second_quarter", "this_year", "last_year"],
|
||||
"previous_year"
|
||||
),
|
||||
{
|
||||
domain: new Domain(
|
||||
"[" +
|
||||
`"|", ` +
|
||||
`"&", ("date_field", ">=", "2018-04-01 00:00:00"), ("date_field", "<=", "2018-06-30 23:59:59"), ` +
|
||||
`"&", ("date_field", ">=", "2019-04-01 00:00:00"), ("date_field", "<=", "2019-06-30 23:59:59")` +
|
||||
"]"
|
||||
),
|
||||
description: "Q2 2018/Q2 2019",
|
||||
}
|
||||
);
|
||||
assert.deepEqual(
|
||||
constructDateDomain(
|
||||
referenceMoment,
|
||||
"date_field",
|
||||
"datetime",
|
||||
["this_year", "antepenultimate_year", "this_month", "antepenultimate_month"],
|
||||
"previous_year"
|
||||
),
|
||||
{
|
||||
domain: new Domain(
|
||||
"[" +
|
||||
`"|", ` +
|
||||
`"&", ("date_field", ">=", "2017-04-01 00:00:00"), ("date_field", "<=", "2017-04-30 23:59:59"), ` +
|
||||
`"|", ` +
|
||||
`"&", ("date_field", ">=", "2017-06-01 00:00:00"), ("date_field", "<=", "2017-06-30 23:59:59"), ` +
|
||||
`"|", ` +
|
||||
`"&", ("date_field", ">=", "2019-04-01 00:00:00"), ("date_field", "<=", "2019-04-30 23:59:59"), ` +
|
||||
`"&", ("date_field", ">=", "2019-06-01 00:00:00"), ("date_field", "<=", "2019-06-30 23:59:59")` +
|
||||
"]"
|
||||
),
|
||||
description: "April 2017/June 2017/April 2019/June 2019",
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("Quarter option: custom translation", async function (assert) {
|
||||
patchDate(2020, 5, 1, 13, 0, 0);
|
||||
const referenceMoment = DateTime.local().setLocale("en");
|
||||
patch(translatedTerms, "add_translations", { Q2: "Deuxième trimestre de l'an de grâce" });
|
||||
assert.deepEqual(
|
||||
constructDateDomain(referenceMoment, "date_field", "date", [
|
||||
"second_quarter",
|
||||
"this_year",
|
||||
]),
|
||||
{
|
||||
domain: new Domain(
|
||||
`["&", ("date_field", ">=", "2020-04-01"), ("date_field", "<=", "2020-06-30")]`
|
||||
),
|
||||
description: "Deuxième trimestre de l'an de grâce 2020",
|
||||
},
|
||||
"Quarter term should be translated"
|
||||
);
|
||||
unpatch(translatedTerms, "add_translations");
|
||||
});
|
||||
|
||||
QUnit.test("Quarter option: right to left", async function (assert) {
|
||||
patchDate(2020, 5, 1, 13, 0, 0);
|
||||
const referenceMoment = DateTime.local().setLocale("en");
|
||||
patch(
|
||||
localization,
|
||||
"rtl_localization",
|
||||
Object.assign({}, defaultLocalization, { direction: "rtl" })
|
||||
);
|
||||
assert.deepEqual(
|
||||
constructDateDomain(referenceMoment, "date_field", "date", [
|
||||
"second_quarter",
|
||||
"this_year",
|
||||
]),
|
||||
{
|
||||
domain: new Domain(
|
||||
`["&", ("date_field", ">=", "2020-04-01"), ("date_field", "<=", "2020-06-30")]`
|
||||
),
|
||||
description: "2020 Q2",
|
||||
},
|
||||
"Notation should be right to left"
|
||||
);
|
||||
unpatch(localization, "rtl_localization");
|
||||
});
|
||||
|
||||
QUnit.test("Quarter option: custom translation and right to left", async function (assert) {
|
||||
patchDate(2020, 5, 1, 13, 0, 0);
|
||||
const referenceMoment = DateTime.local().setLocale("en");
|
||||
patch(
|
||||
localization,
|
||||
"rtl_localization",
|
||||
Object.assign({}, defaultLocalization, { direction: "rtl" })
|
||||
);
|
||||
patch(translatedTerms, "add_translations", { Q2: "2e Trimestre" });
|
||||
assert.deepEqual(
|
||||
constructDateDomain(referenceMoment, "date_field", "date", [
|
||||
"second_quarter",
|
||||
"this_year",
|
||||
]),
|
||||
{
|
||||
domain: new Domain(
|
||||
`["&", ("date_field", ">=", "2020-04-01"), ("date_field", "<=", "2020-06-30")]`
|
||||
),
|
||||
description: "2020 2e Trimestre",
|
||||
},
|
||||
"Quarter term should be translated and notation should be right to left"
|
||||
);
|
||||
unpatch(localization, "rtl_localization");
|
||||
unpatch(translatedTerms, "add_translations");
|
||||
});
|
||||
|
||||
QUnit.skip(
|
||||
"Moment.js localization does not affect formatted domain dates",
|
||||
async function (assert) {
|
||||
patchDate(2020, 5, 1, 13, 0, 0);
|
||||
const initialLocale = moment.locale();
|
||||
moment.defineLocale("addoneForTest", {
|
||||
postformat: function (string) {
|
||||
return string.replace(/\d/g, (match) => (1 + parseInt(match)) % 10);
|
||||
},
|
||||
});
|
||||
const referenceMoment = moment().locale("addoneForTest");
|
||||
assert.deepEqual(
|
||||
constructDateDomain(referenceMoment, "date_field", "date", [
|
||||
"this_month",
|
||||
"this_year",
|
||||
]),
|
||||
{
|
||||
domain: `["&", ["date_field", ">=", "2020-06-01"], ["date_field", "<=", "2020-06-30"]]`,
|
||||
description: "June 3131",
|
||||
},
|
||||
"Numbers in domain should not use addoneForTest locale"
|
||||
);
|
||||
moment.locale(initialLocale);
|
||||
moment.updateLocale("addoneForTest", null);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
@ -0,0 +1,347 @@
|
|||
import { expect, test } from "@odoo/hoot";
|
||||
import { animationFrame } from "@odoo/hoot-mock";
|
||||
import { Component, onWillStart, onWillUpdateProps, useState, useSubEnv, xml } from "@odoo/owl";
|
||||
import {
|
||||
defineModels,
|
||||
fields,
|
||||
getMenuItemTexts,
|
||||
models,
|
||||
mountWithCleanup,
|
||||
mountWithSearch,
|
||||
onRpc,
|
||||
toggleMenuItem,
|
||||
toggleSearchBarMenu,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { SearchBarMenu } from "@web/search/search_bar_menu/search_bar_menu";
|
||||
import { WithSearch } from "@web/search/with_search/with_search";
|
||||
|
||||
class Animal extends models.Model {
|
||||
name = fields.Char();
|
||||
birthday = fields.Date({ groupable: false });
|
||||
type = fields.Selection({
|
||||
groupable: false,
|
||||
selection: [
|
||||
["omnivorous", "Omnivorous"],
|
||||
["herbivorous", "Herbivorous"],
|
||||
["carnivorous", "Carnivorous"],
|
||||
],
|
||||
});
|
||||
|
||||
_views = {
|
||||
[["search", 1]]: `
|
||||
<search>
|
||||
<filter name="filter" string="True domain" domain="[(1, '=', 1)]"/>
|
||||
<filter name="group_by" context="{ 'group_by': 'name' }"/>
|
||||
</search>
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
||||
defineModels([Animal]);
|
||||
|
||||
test("simple rendering", async () => {
|
||||
class TestComponent extends Component {
|
||||
static props = ["*"];
|
||||
static template = xml`<div class="o_test_component">Test component content</div>`;
|
||||
}
|
||||
|
||||
await mountWithSearch(TestComponent, {
|
||||
resModel: "animal",
|
||||
});
|
||||
expect(".o_test_component").toHaveCount(1);
|
||||
expect(".o_test_component").toHaveText("Test component content");
|
||||
});
|
||||
|
||||
test("search model in sub env", async () => {
|
||||
class TestComponent extends Component {
|
||||
static props = ["*"];
|
||||
static template = xml`<div class="o_test_component">Test component content</div>`;
|
||||
}
|
||||
|
||||
const component = await mountWithSearch(TestComponent, {
|
||||
resModel: "animal",
|
||||
});
|
||||
expect(component.env.searchModel).not.toBeEmpty();
|
||||
});
|
||||
|
||||
test("search query props are passed as props to concrete component", async () => {
|
||||
class TestComponent extends Component {
|
||||
static props = ["*"];
|
||||
static template = xml`<div class="o_test_component">Test component content</div>`;
|
||||
|
||||
setup() {
|
||||
expect.step("setup");
|
||||
const { context, domain, groupBy, orderBy } = this.props;
|
||||
expect(context).toEqual({
|
||||
allowed_company_ids: [1],
|
||||
lang: "en",
|
||||
tz: "taht",
|
||||
uid: 7,
|
||||
key: "val",
|
||||
});
|
||||
expect(domain).toEqual([[0, "=", 1]]);
|
||||
expect(groupBy).toEqual(["birthday"]);
|
||||
expect(orderBy).toEqual([{ name: "bar", asc: true }]);
|
||||
}
|
||||
}
|
||||
|
||||
await mountWithSearch(TestComponent, {
|
||||
resModel: "animal",
|
||||
domain: [[0, "=", 1]],
|
||||
groupBy: ["birthday"],
|
||||
context: { key: "val" },
|
||||
orderBy: [{ name: "bar", asc: true }],
|
||||
});
|
||||
expect.verifySteps(["setup"]);
|
||||
});
|
||||
|
||||
test("do not load search view description by default", async () => {
|
||||
class TestComponent extends Component {
|
||||
static props = ["*"];
|
||||
static template = xml`<div class="o_test_component">Test component content</div>`;
|
||||
}
|
||||
|
||||
onRpc("get_views", ({ method }) => {
|
||||
expect.step(method);
|
||||
throw new Error("No get_views should be done");
|
||||
});
|
||||
await mountWithSearch(TestComponent, {
|
||||
resModel: "animal",
|
||||
});
|
||||
expect.verifySteps([]);
|
||||
});
|
||||
|
||||
test("load search view description if not provided and loadSearchView=true", async () => {
|
||||
class TestComponent extends Component {
|
||||
static props = ["*"];
|
||||
static template = xml`<div class="o_test_component">Test component content</div>`;
|
||||
}
|
||||
|
||||
onRpc("get_views", ({ method, kwargs }) => {
|
||||
expect.step(method);
|
||||
delete kwargs.options.mobile;
|
||||
expect(kwargs).toMatchObject({
|
||||
options: {
|
||||
action_id: false,
|
||||
load_filters: false,
|
||||
toolbar: false,
|
||||
embedded_action_id: false,
|
||||
embedded_parent_res_id: false,
|
||||
},
|
||||
views: [[false, "search"]],
|
||||
});
|
||||
});
|
||||
await mountWithSearch(TestComponent, {
|
||||
resModel: "animal",
|
||||
searchViewId: false,
|
||||
});
|
||||
expect.verifySteps(["get_views"]);
|
||||
});
|
||||
|
||||
test("do not load the search view description if provided even if loadSearchView=true", async () => {
|
||||
class TestComponent extends Component {
|
||||
static props = ["*"];
|
||||
static template = xml`<div class="o_test_component">Test component content</div>`;
|
||||
}
|
||||
|
||||
onRpc("get_views", ({ method }) => {
|
||||
expect.step(method);
|
||||
throw new Error("No get_views should be done");
|
||||
});
|
||||
await mountWithSearch(TestComponent, {
|
||||
resModel: "animal",
|
||||
searchViewArch: "<search/>",
|
||||
searchViewFields: {},
|
||||
searchViewId: false,
|
||||
});
|
||||
expect.verifySteps([]);
|
||||
});
|
||||
|
||||
test("load view description if it is not complete and loadSearchView=true", async () => {
|
||||
class TestComponent extends Component {
|
||||
static props = ["*"];
|
||||
static template = xml`<div class="o_test_component">Test component content</div>`;
|
||||
}
|
||||
|
||||
onRpc("get_views", ({ method, kwargs }) => {
|
||||
expect.step(method);
|
||||
delete kwargs.options.mobile;
|
||||
expect(kwargs.options).toEqual({
|
||||
action_id: false,
|
||||
load_filters: true,
|
||||
toolbar: false,
|
||||
embedded_action_id: false,
|
||||
embedded_parent_res_id: false,
|
||||
});
|
||||
});
|
||||
await mountWithSearch(TestComponent, {
|
||||
resModel: "animal",
|
||||
searchViewArch: "<search/>",
|
||||
searchViewFields: {},
|
||||
searchViewId: true,
|
||||
loadIrFilters: true,
|
||||
});
|
||||
expect.verifySteps(["get_views"]);
|
||||
});
|
||||
|
||||
test("load view description with given id if it is not provided and loadSearchView=true", async () => {
|
||||
class TestComponent extends Component {
|
||||
static props = ["*"];
|
||||
static components = { SearchBarMenu };
|
||||
static template = xml`<div class="o_test_component"><SearchBarMenu/></div>`;
|
||||
}
|
||||
|
||||
onRpc("get_views", ({ method, kwargs }) => {
|
||||
expect.step(method);
|
||||
expect(kwargs.views).toEqual([[1, "search"]]);
|
||||
});
|
||||
await mountWithSearch(TestComponent, {
|
||||
resModel: "animal",
|
||||
searchViewId: 1,
|
||||
});
|
||||
expect.verifySteps(["get_views"]);
|
||||
|
||||
await toggleSearchBarMenu();
|
||||
expect(getMenuItemTexts()).toEqual([
|
||||
"True domain",
|
||||
"Custom Filter...",
|
||||
"Name",
|
||||
"Custom Group\nCreated on\nDisplay name\nLast Modified on\nName",
|
||||
"Save current search",
|
||||
]);
|
||||
});
|
||||
|
||||
test("toggle a filter render the underlying component with an updated domain", async () => {
|
||||
class TestComponent extends Component {
|
||||
static props = ["*"];
|
||||
static components = { SearchBarMenu };
|
||||
static template = xml`<div class="o_test_component"><SearchBarMenu/></div>`;
|
||||
|
||||
setup() {
|
||||
onWillStart(() => {
|
||||
expect.step("willStart");
|
||||
expect(this.props.domain).toEqual([]);
|
||||
});
|
||||
onWillUpdateProps((nextProps) => {
|
||||
expect.step("willUpdateProps");
|
||||
expect(nextProps.domain).toEqual([[1, "=", 1]]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await mountWithSearch(TestComponent, {
|
||||
resModel: "animal",
|
||||
searchViewId: 1,
|
||||
});
|
||||
expect.verifySteps(["willStart"]);
|
||||
|
||||
await toggleSearchBarMenu();
|
||||
await toggleMenuItem("True domain");
|
||||
expect.verifySteps(["willUpdateProps"]);
|
||||
});
|
||||
|
||||
test("react to prop 'domain' changes", async () => {
|
||||
class TestComponent extends Component {
|
||||
static props = ["*"];
|
||||
static template = xml`<div class="o_test_component">Test component content</div>`;
|
||||
|
||||
setup() {
|
||||
onWillStart(() => {
|
||||
expect.step("willStart");
|
||||
expect(this.props.domain).toEqual([["type", "=", "carnivorous"]]);
|
||||
});
|
||||
onWillUpdateProps((nextProps) => {
|
||||
expect.step("willUpdateProps");
|
||||
expect(nextProps.domain).toEqual([["type", "=", "herbivorous"]]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static props = ["*"];
|
||||
static template = xml`
|
||||
<WithSearch t-props="searchState" t-slot-scope="search">
|
||||
<TestComponent domain="search.domain"/>
|
||||
</WithSearch>
|
||||
`;
|
||||
static components = { WithSearch, TestComponent };
|
||||
setup() {
|
||||
useSubEnv({ config: {} });
|
||||
this.searchState = useState({
|
||||
resModel: "animal",
|
||||
domain: [["type", "=", "carnivorous"]],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const parent = await mountWithCleanup(Parent);
|
||||
expect.verifySteps(["willStart"]);
|
||||
|
||||
parent.searchState.domain = [["type", "=", "herbivorous"]];
|
||||
await animationFrame();
|
||||
expect.verifySteps(["willUpdateProps"]);
|
||||
});
|
||||
|
||||
test("search defaults are removed from context at reload", async function () {
|
||||
const context = {
|
||||
search_default_x: true,
|
||||
searchpanel_default_y: true,
|
||||
};
|
||||
|
||||
class TestComponent extends Component {
|
||||
static template = xml`<div class="o_test_component">Test component content</div>`;
|
||||
static props = { context: Object };
|
||||
setup() {
|
||||
onWillStart(() => {
|
||||
expect.step("willStart");
|
||||
expect(this.props.context).toEqual({
|
||||
lang: "en",
|
||||
tz: "taht",
|
||||
uid: 7,
|
||||
allowed_company_ids: [1],
|
||||
});
|
||||
});
|
||||
onWillUpdateProps((nextProps) => {
|
||||
expect.step("willUpdateProps");
|
||||
expect(nextProps.context).toEqual({
|
||||
lang: "en",
|
||||
tz: "taht",
|
||||
uid: 7,
|
||||
allowed_company_ids: [1],
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static props = ["*"];
|
||||
static template = xml`
|
||||
<WithSearch t-props="searchState" t-slot-scope="search">
|
||||
<TestComponent
|
||||
context="search.context"
|
||||
/>
|
||||
</WithSearch>
|
||||
`;
|
||||
static components = { WithSearch, TestComponent };
|
||||
setup() {
|
||||
useSubEnv({ config: {} });
|
||||
this.searchState = useState({
|
||||
resModel: "animal",
|
||||
domain: [["type", "=", "carnivorous"]],
|
||||
context,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const parent = await mountWithCleanup(Parent);
|
||||
expect.verifySteps(["willStart"]);
|
||||
|
||||
expect(parent.searchState.context).toEqual(context);
|
||||
|
||||
parent.searchState.domain = [["type", "=", "herbivorous"]];
|
||||
|
||||
await animationFrame();
|
||||
expect.verifySteps(["willUpdateProps"]);
|
||||
expect(parent.searchState.context).toEqual(context);
|
||||
});
|
||||
|
|
@ -1,391 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { makeTestEnv } from "@web/../tests/helpers/mock_env";
|
||||
import { getFixture, nextTick } from "@web/../tests/helpers/utils";
|
||||
import { FilterMenu } from "@web/search/filter_menu/filter_menu";
|
||||
import { GroupByMenu } from "@web/search/group_by_menu/group_by_menu";
|
||||
import { WithSearch } from "@web/search/with_search/with_search";
|
||||
import { mount } from "../helpers/utils";
|
||||
import {
|
||||
getMenuItemTexts,
|
||||
makeWithSearch,
|
||||
setupControlPanelServiceRegistry,
|
||||
toggleFilterMenu,
|
||||
toggleGroupByMenu,
|
||||
toggleMenuItem,
|
||||
} from "./helpers";
|
||||
|
||||
import { Component, onWillUpdateProps, onWillStart, useState, xml } from "@odoo/owl";
|
||||
|
||||
let target;
|
||||
let serverData;
|
||||
|
||||
QUnit.module("Search", (hooks) => {
|
||||
hooks.beforeEach(async () => {
|
||||
serverData = {
|
||||
models: {
|
||||
animal: {
|
||||
fields: {
|
||||
birthday: { string: "Birthday", type: "date", store: true },
|
||||
type: {
|
||||
string: "Type",
|
||||
type: "selection",
|
||||
selection: [
|
||||
["omnivorous", "Omnivorous"],
|
||||
["herbivorous", "Herbivorous"],
|
||||
["carnivorous", "Carnivorous"],
|
||||
],
|
||||
store: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
views: {
|
||||
"animal,false,search": `<search/>`,
|
||||
"animal,1,search": `
|
||||
<search>
|
||||
<filter name="filter" string="True domain" domain="[(1, '=', 1)]"/>
|
||||
<filter name="group_by" context="{ 'group_by': 'name' }"/>
|
||||
</search>
|
||||
`,
|
||||
},
|
||||
};
|
||||
setupControlPanelServiceRegistry();
|
||||
target = getFixture();
|
||||
});
|
||||
|
||||
QUnit.module("WithSearch");
|
||||
|
||||
QUnit.test("simple rendering", async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
class TestComponent extends Component {}
|
||||
TestComponent.template = xml`<div class="o_test_component">Test component content</div>`;
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "animal",
|
||||
Component: TestComponent,
|
||||
});
|
||||
assert.containsOnce(target, ".o_test_component");
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_test_component").innerText,
|
||||
"Test component content"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("search model in sub env", async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
class TestComponent extends Component {}
|
||||
TestComponent.template = xml`<div class="o_test_component">Test component content</div>`;
|
||||
|
||||
const component = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "animal",
|
||||
Component: TestComponent,
|
||||
});
|
||||
assert.ok(component.env.searchModel);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"search query props are passed as props to concrete component",
|
||||
async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
class TestComponent extends Component {
|
||||
setup() {
|
||||
const { context, domain, groupBy, orderBy } = this.props;
|
||||
assert.deepEqual(context, {
|
||||
lang: "en",
|
||||
tz: "taht",
|
||||
uid: 7,
|
||||
key: "val",
|
||||
});
|
||||
assert.deepEqual(domain, [[0, "=", 1]]);
|
||||
assert.deepEqual(groupBy, ["birthday"]);
|
||||
assert.deepEqual(orderBy, [{ name: "bar", asc: true }]);
|
||||
}
|
||||
}
|
||||
TestComponent.template = xml`<div class="o_test_component">Test component content</div>`;
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "animal",
|
||||
Component: TestComponent,
|
||||
domain: [[0, "=", 1]],
|
||||
groupBy: ["birthday"],
|
||||
context: { key: "val" },
|
||||
orderBy: [{ name: "bar", asc: true }],
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("do not load search view description by default", async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
class TestComponent extends Component {}
|
||||
TestComponent.template = xml`<div class="o_test_component">Test component content</div>`;
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
mockRPC: function (_, args) {
|
||||
if (args.method === "get_views") {
|
||||
throw new Error("No get_views should be done");
|
||||
}
|
||||
},
|
||||
resModel: "animal",
|
||||
Component: TestComponent,
|
||||
});
|
||||
|
||||
assert.ok(true);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"load search view description if not provided and loadSearchView=true",
|
||||
async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
class TestComponent extends Component {}
|
||||
TestComponent.template = xml`<div class="o_test_component">Test component content</div>`;
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
mockRPC: function (_, args) {
|
||||
if (args.method === "get_views") {
|
||||
assert.deepEqual(args.kwargs, {
|
||||
context: {
|
||||
lang: "en",
|
||||
tz: "taht",
|
||||
uid: 7,
|
||||
},
|
||||
options: {
|
||||
action_id: false,
|
||||
load_filters: false,
|
||||
toolbar: false,
|
||||
},
|
||||
views: [[false, "search"]],
|
||||
});
|
||||
}
|
||||
},
|
||||
resModel: "animal",
|
||||
Component: TestComponent,
|
||||
searchViewId: false,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"do not load the search view description if provided even if loadSearchView=true",
|
||||
async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
class TestComponent extends Component {}
|
||||
TestComponent.template = xml`<div class="o_test_component">Test component content</div>`;
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
mockRPC: function (_, args) {
|
||||
if (args.method === "get_views") {
|
||||
throw new Error("No get_views should be done");
|
||||
}
|
||||
},
|
||||
resModel: "animal",
|
||||
Component: TestComponent,
|
||||
searchViewArch: "<search/>",
|
||||
searchViewFields: {},
|
||||
searchViewId: false,
|
||||
});
|
||||
assert.ok(true);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"load view description if it is not complete and loadSearchView=true",
|
||||
async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
class TestComponent extends Component {}
|
||||
TestComponent.template = xml`<div class="o_test_component">Test component content</div>`;
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
mockRPC: function (_, args) {
|
||||
if (args.method === "get_views") {
|
||||
assert.deepEqual(args.kwargs.options, {
|
||||
action_id: false,
|
||||
load_filters: true,
|
||||
toolbar: false,
|
||||
});
|
||||
}
|
||||
},
|
||||
resModel: "animal",
|
||||
Component: TestComponent,
|
||||
searchViewArch: "<search/>",
|
||||
searchViewFields: {},
|
||||
searchViewId: true,
|
||||
loadIrFilters: true,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"load view description with given id if it is not provided and loadSearchView=true",
|
||||
async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
class TestComponent extends Component {}
|
||||
TestComponent.components = { FilterMenu, GroupByMenu };
|
||||
TestComponent.template = xml`
|
||||
<div class="o_test_component">
|
||||
<FilterMenu/>
|
||||
<GroupByMenu/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
mockRPC: function (_, args) {
|
||||
if (args.method === "get_views") {
|
||||
assert.deepEqual(args.kwargs.views, [[1, "search"]]);
|
||||
}
|
||||
},
|
||||
resModel: "animal",
|
||||
Component: TestComponent,
|
||||
searchViewId: 1,
|
||||
});
|
||||
await toggleFilterMenu(target);
|
||||
await assert.ok(getMenuItemTexts(target), ["True Domain"]);
|
||||
|
||||
await toggleGroupByMenu(target);
|
||||
await assert.ok(getMenuItemTexts(target), ["Name"]);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"toggle a filter render the underlying component with an updated domain",
|
||||
async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
class TestComponent extends Component {
|
||||
setup() {
|
||||
owl.onWillStart(() => {
|
||||
assert.deepEqual(this.props.domain, []);
|
||||
});
|
||||
owl.onWillUpdateProps((nextProps) => {
|
||||
assert.deepEqual(nextProps.domain, [[1, "=", 1]]);
|
||||
});
|
||||
}
|
||||
}
|
||||
TestComponent.components = { FilterMenu };
|
||||
TestComponent.template = xml`
|
||||
<div class="o_test_component">
|
||||
<FilterMenu/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "animal",
|
||||
Component: TestComponent,
|
||||
searchViewId: 1,
|
||||
});
|
||||
await toggleFilterMenu(target);
|
||||
await toggleMenuItem(target, "True domain");
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("react to prop 'domain' changes", async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
class TestComponent extends Component {
|
||||
setup() {
|
||||
onWillStart(() => {
|
||||
assert.deepEqual(this.props.domain, [["type", "=", "carnivorous"]]);
|
||||
});
|
||||
onWillUpdateProps((nextProps) => {
|
||||
assert.deepEqual(nextProps.domain, [["type", "=", "herbivorous"]]);
|
||||
});
|
||||
}
|
||||
}
|
||||
TestComponent.template = xml`<div class="o_test_component">Test component content</div>`;
|
||||
|
||||
const env = await makeTestEnv(serverData);
|
||||
const target = getFixture();
|
||||
|
||||
class Parent extends Component {
|
||||
setup() {
|
||||
owl.useSubEnv({ config: {} });
|
||||
this.searchState = useState({
|
||||
resModel: "animal",
|
||||
domain: [["type", "=", "carnivorous"]],
|
||||
});
|
||||
}
|
||||
}
|
||||
Parent.template = xml`
|
||||
<WithSearch t-props="searchState" t-slot-scope="search">
|
||||
<TestComponent
|
||||
domain="search.domain"
|
||||
/>
|
||||
</WithSearch>
|
||||
`;
|
||||
Parent.components = { WithSearch, TestComponent };
|
||||
|
||||
const parent = await mount(Parent, target, { env });
|
||||
parent.searchState.domain = [["type", "=", "herbivorous"]];
|
||||
|
||||
await nextTick();
|
||||
});
|
||||
|
||||
QUnit.test("search defaults are removed from context at reload", async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
const context = {
|
||||
search_default_x: true,
|
||||
searchpanel_default_y: true,
|
||||
};
|
||||
|
||||
class TestComponent extends Component {
|
||||
setup() {
|
||||
onWillStart(() => {
|
||||
assert.deepEqual(this.props.context, { lang: "en", tz: "taht", uid: 7 });
|
||||
});
|
||||
onWillUpdateProps((nextProps) => {
|
||||
assert.deepEqual(nextProps.context, { lang: "en", tz: "taht", uid: 7 });
|
||||
});
|
||||
}
|
||||
}
|
||||
TestComponent.template = xml`<div class="o_test_component">Test component content</div>`;
|
||||
TestComponent.props = { context: Object };
|
||||
|
||||
const env = await makeTestEnv(serverData);
|
||||
const target = getFixture();
|
||||
|
||||
class Parent extends Component {
|
||||
setup() {
|
||||
owl.useSubEnv({ config: {} });
|
||||
this.searchState = useState({
|
||||
resModel: "animal",
|
||||
domain: [["type", "=", "carnivorous"]],
|
||||
context,
|
||||
});
|
||||
}
|
||||
}
|
||||
Parent.template = xml`
|
||||
<WithSearch t-props="searchState" t-slot-scope="search">
|
||||
<TestComponent
|
||||
context="search.context"
|
||||
/>
|
||||
</WithSearch>
|
||||
`;
|
||||
Parent.components = { WithSearch, TestComponent };
|
||||
|
||||
const parent = await mount(Parent, target, { env });
|
||||
assert.deepEqual(parent.searchState.context, context);
|
||||
|
||||
parent.searchState.domain = [["type", "=", "herbivorous"]];
|
||||
|
||||
await nextTick();
|
||||
assert.deepEqual(parent.searchState.context, context);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue