mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-19 14:52:06 +02:00
Initial commit: Core packages
This commit is contained in:
commit
12c29a983b
9512 changed files with 8379910 additions and 0 deletions
|
|
@ -0,0 +1,136 @@
|
|||
/** @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), []);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
/** @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,562 @@
|
|||
/** @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();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,863 @@
|
|||
/** @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,244 @@
|
|||
/** @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"]);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
@ -0,0 +1,391 @@
|
|||
/** @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();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,587 @@
|
|||
/** @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,534 @@
|
|||
/** @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");
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
/** @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"]);
|
||||
});
|
||||
});
|
||||
336
odoo-bringout-oca-ocb-web/web/static/tests/search/helpers.js
Normal file
336
odoo-bringout-oca-ocb-web/web/static/tests/search/helpers.js
Normal file
|
|
@ -0,0 +1,336 @@
|
|||
/** @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,133 @@
|
|||
/** @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,
|
||||
});
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,980 @@
|
|||
/** @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,593 @@
|
|||
/** @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,391 @@
|
|||
/** @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