vanilla 17.0

This commit is contained in:
Ernad Husremovic 2025-10-08 10:47:08 +02:00
parent d72e748793
commit a9bcec8e91
1986 changed files with 1613876 additions and 568976 deletions

View file

@ -1,136 +0,0 @@
/** @odoo-module **/
import { getFixture, patchDate, patchWithCleanup } from "@web/../tests/helpers/utils";
import { browser } from "@web/core/browser/browser";
import { ControlPanel } from "@web/search/control_panel/control_panel";
import {
getFacetTexts,
makeWithSearch,
removeFacet,
setupControlPanelServiceRegistry,
toggleComparisonMenu,
toggleFilterMenu,
toggleMenuItem,
toggleMenuItemOption,
} from "./helpers";
let target;
let serverData;
QUnit.module("Search", (hooks) => {
hooks.beforeEach(async () => {
serverData = {
models: {
foo: {
fields: {
birthday: { string: "Birthday", type: "date", store: true, sortable: true },
date_field: { string: "Date", type: "date", store: true, sortable: true },
},
},
},
views: {
"foo,false,search": `
<search>
<filter name="birthday" date="birthday"/>
<filter name="date_field" date="date_field"/>
</search>
`,
},
};
setupControlPanelServiceRegistry();
patchWithCleanup(browser, {
setTimeout: (fn) => fn(),
clearTimeout: () => {},
});
target = getFixture();
});
QUnit.module("Comparison");
QUnit.test("simple rendering", async function (assert) {
patchDate(1997, 0, 9, 12, 0, 0);
await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchMenuTypes: ["filter", "comparison"],
searchViewId: false,
});
assert.containsOnce(target, ".dropdown.o_filter_menu");
assert.containsNone(target, ".dropdown.o_comparison_menu");
await toggleFilterMenu(target);
await toggleMenuItem(target, "Birthday");
await toggleMenuItemOption(target, "Birthday", "January");
assert.containsOnce(target, "div.o_comparison_menu > button i.fa.fa-adjust");
assert.strictEqual(
target
.querySelector("div.o_comparison_menu > button span")
.innerText.trim()
.toUpperCase() /** @todo why do I need to upperCase */,
"COMPARISON"
);
await toggleComparisonMenu(target);
assert.containsN(target, ".o_comparison_menu .dropdown-item", 2);
assert.containsN(target, ".o_comparison_menu .dropdown-item[role=menuitemcheckbox]", 2);
const comparisonOptions = [...target.querySelectorAll(".o_comparison_menu .dropdown-item")];
assert.deepEqual(
comparisonOptions.map((e) => e.innerText.trim()),
["Birthday: Previous Period", "Birthday: Previous Year"]
);
assert.deepEqual(
comparisonOptions.map((e) => e.ariaChecked),
["false", "false"]
);
});
QUnit.test("activate a comparison works", async function (assert) {
patchDate(1997, 0, 9, 12, 0, 0);
await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchMenuTypes: ["filter", "comparison"],
searchViewId: false,
});
await toggleFilterMenu(target);
await toggleMenuItem(target, "Birthday");
await toggleMenuItemOption(target, "Birthday", "January");
await toggleComparisonMenu(target);
await toggleMenuItem(target, "Birthday: Previous Period");
assert.deepEqual(getFacetTexts(target), [
"Birthday: January 1997",
"Birthday: Previous Period",
]);
await toggleFilterMenu(target);
await toggleMenuItem(target, "Date");
await toggleMenuItemOption(target, "Date", "December");
await toggleComparisonMenu(target);
await toggleMenuItem(target, "Date: Previous Year");
assert.deepEqual(getFacetTexts(target), [
["Birthday: January 1997", "Date: December 1996"].join("or"),
"Date: Previous Year",
]);
await toggleFilterMenu(target);
await toggleMenuItem(target, "Date");
await toggleMenuItemOption(target, "Date", "1996");
assert.deepEqual(getFacetTexts(target), ["Birthday: January 1997"]);
await toggleComparisonMenu(target);
await toggleMenuItem(target, "Birthday: Previous Year");
assert.containsN(target, ".o_comparison_menu .dropdown-item", 2);
assert.containsN(target, ".o_comparison_menu .dropdown-item[role=menuitemcheckbox]", 2);
const comparisonOptions = [...target.querySelectorAll(".o_comparison_menu .dropdown-item")];
assert.deepEqual(
comparisonOptions.map((e) => e.innerText.trim()),
["Birthday: Previous Period", "Birthday: Previous Year"]
);
assert.deepEqual(
comparisonOptions.map((e) => e.ariaChecked),
["false", "true"]
);
assert.deepEqual(getFacetTexts(target), [
"Birthday: January 1997",
"Birthday: Previous Year",
]);
await removeFacet(target);
assert.deepEqual(getFacetTexts(target), []);
});
});

View file

@ -37,16 +37,15 @@ QUnit.module("Search", (hooks) => {
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.containsOnce(target, ".o_control_panel_breadcrumbs");
assert.containsOnce(target, ".o_control_panel_actions");
assert.strictEqual(target.querySelector(".o_control_panel_actions").innerHTML, "");
assert.containsOnce(target, ".o_control_panel_navigation");
assert.strictEqual(target.querySelector(".o_control_panel_navigation").innerHTML, "");
assert.containsNone(target, ".o_cp_switch_buttons");
assert.containsOnce(target, ".breadcrumb");
assert.containsOnce(target, ".o_breadcrumb");
});
QUnit.test("breadcrumbs", async (assert) => {
@ -63,8 +62,9 @@ QUnit.module("Search", (hooks) => {
searchMenuTypes: [],
});
assert.containsN(target, ".breadcrumb li.breadcrumb-item", 2);
const breadcrumbItems = target.querySelectorAll("li.breadcrumb-item");
const breadcrumbsSelector = ".o_breadcrumb li.breadcrumb-item, .o_breadcrumb .active";
assert.containsN(target, breadcrumbsSelector, 2);
const breadcrumbItems = target.querySelectorAll(breadcrumbsSelector);
assert.strictEqual(breadcrumbItems[0].innerText, "Previous");
assert.hasClass(breadcrumbItems[1], "active");
assert.strictEqual(breadcrumbItems[1].innerText, "Current");
@ -84,29 +84,25 @@ QUnit.module("Search", (hooks) => {
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" },
{ type: "list", active: true, icon: "oi-view-list", name: "List" },
{ type: "kanban", icon: "oi-view-kanban", name: "Kanban" },
],
},
searchMenuTypes: [],
});
assert.containsOnce(target, ".o_cp_switch_buttons");
assert.containsOnce(
target,
".o_control_panel_navigation .d-xl-inline-flex.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.containsOnce(views[0], ".oi-view-list");
assert.strictEqual(views[1].getAttribute("data-tooltip"), "Kanban");
assert.strictEqual(views[1].getAttribute("data-hotkey"), "k");
assert.hasClass(views[1], "oi-view-kanban");
assert.containsOnce(views[1], ".oi-view-kanban");
controlPanel.env.services.action.switchView = (viewType) => {
assert.step(viewType);

View file

@ -3,8 +3,7 @@
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 { SearchBarMenu } from "@web/search/search_bar_menu/search_bar_menu";
import { useSetupAction } from "@web/webclient/actions/action_hook";
import {
editFavoriteName,
@ -14,12 +13,13 @@ import {
saveFavorite,
setupControlPanelFavoriteMenuRegistry,
setupControlPanelServiceRegistry,
toggleFavoriteMenu,
toggleSaveFavorite,
toggleSearchBarMenu,
validateSearch,
} from "./helpers";
import { Component, xml } from "@odoo/owl";
import { SearchBar } from "@web/search/search_bar/search_bar";
const serviceRegistry = registry.category("services");
/**
@ -79,7 +79,7 @@ QUnit.module("Search", (hooks) => {
await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
Component: SearchBar,
searchMenuTypes: ["favorite"],
searchViewId: false,
config: {
@ -87,17 +87,23 @@ QUnit.module("Search", (hooks) => {
},
});
await toggleFavoriteMenu(target);
await toggleSearchBarMenu(target);
await toggleSaveFavorite(target);
assert.strictEqual(
target.querySelector('.o_add_favorite input[type="text"]').value,
target.querySelector('.o_add_favorite + .o_accordion_values 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.containsN(
target,
'.o_add_favorite + .o_accordion_values .form-check input[type="checkbox"]',
2
);
const labelEls = target.querySelectorAll(
".o_add_favorite + .o_accordion_values .form-check label"
);
assert.deepEqual(
[...labelEls].map((e) => e.innerText.trim()),
["Use by default", "Share with all users"]
["Default filter", "Shared"]
);
});
@ -107,12 +113,12 @@ QUnit.module("Search", (hooks) => {
await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
Component: SearchBar,
searchMenuTypes: ["favorite"],
searchViewId: false,
});
await toggleFavoriteMenu(target);
await toggleSearchBarMenu(target);
await toggleSaveFavorite(target);
const checkboxes = target.querySelectorAll('input[type="checkbox"]');
@ -166,8 +172,8 @@ QUnit.module("Search", (hooks) => {
});
}
}
TestComponent.components = { FavoriteMenu };
TestComponent.template = xml`<div><FavoriteMenu/></div>`;
TestComponent.components = { SearchBarMenu };
TestComponent.template = xml`<div><SearchBarMenu/></div>`;
const comp = await makeWithSearch({
serverData,
@ -183,11 +189,11 @@ QUnit.module("Search", (hooks) => {
Component: TestComponent,
searchViewId: false,
});
comp.env.bus.on("CLEAR-CACHES", comp, () => assert.step("CLEAR-CACHES"));
comp.env.bus.addEventListener("CLEAR-CACHES", () => assert.step("CLEAR-CACHES"));
assert.verifySteps([]);
await toggleFavoriteMenu(target);
await toggleSearchBarMenu(target);
await toggleSaveFavorite(target);
await editFavoriteName(target, "aaa");
await saveFavorite(target);
@ -211,8 +217,8 @@ QUnit.module("Search", (hooks) => {
}
},
resModel: "foo",
Component: ControlPanel,
searchMenuTypes: ["favorite"],
Component: SearchBar,
searchMenuTypes: ["filter", "favorite"],
searchViewId: false,
searchViewArch: `
<search>
@ -224,7 +230,7 @@ QUnit.module("Search", (hooks) => {
assert.deepEqual(getFacetTexts(target), ["Filter"]);
await toggleFavoriteMenu(target);
await toggleSearchBarMenu(target);
await toggleSaveFavorite(target);
await editFavoriteName(target, "My favorite");
await saveFavorite(target);
@ -245,7 +251,7 @@ QUnit.module("Search", (hooks) => {
}
},
resModel: "foo",
Component: ControlPanel,
Component: SearchBar,
searchMenuTypes: ["favorite"],
searchViewId: false,
searchViewArch: `
@ -262,7 +268,7 @@ QUnit.module("Search", (hooks) => {
assert.deepEqual(getFacetTexts(target), ["Foo\na"]);
await toggleFavoriteMenu(target);
await toggleSearchBarMenu(target);
await toggleSaveFavorite(target);
await editFavoriteName(target, "My favorite");
await saveFavorite(target);
@ -312,7 +318,7 @@ QUnit.module("Search", (hooks) => {
}
},
resModel: "foo",
Component: ControlPanel,
Component: SearchBar,
searchMenuTypes: ["favorite"],
searchViewId: false,
irFilters: [
@ -328,7 +334,7 @@ QUnit.module("Search", (hooks) => {
],
});
await toggleFavoriteMenu(target);
await toggleSearchBarMenu(target);
await toggleSaveFavorite(target);
// first try: should fail
@ -377,11 +383,11 @@ QUnit.module("Search", (hooks) => {
}
},
resModel: "foo",
Component: FavoriteMenu,
Component: SearchBarMenu,
searchViewId: false,
});
await toggleFavoriteMenu(target);
await toggleSearchBarMenu(target);
await toggleSaveFavorite(target);
await saveFavorite(target);
}
@ -406,12 +412,8 @@ QUnit.module("Search", (hooks) => {
await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchMenuTypes: ["favorite"],
Component: SearchBarMenu,
searchViewId: false,
config: {
displayName: "Action Name",
},
irFilters: [
{
context: "{}",
@ -425,15 +427,10 @@ QUnit.module("Search", (hooks) => {
],
});
await toggleFavoriteMenu(target);
await toggleSearchBarMenu(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" }
);
triggerEvent(target, `.o_favorite_menu input[type="text"]`, "keydown", { key: "Enter" });
assert.verifySteps(["warning dialog"]);
});
@ -551,7 +548,7 @@ QUnit.module("Search", (hooks) => {
// await applyFilter(".modal");
// assert.containsNone(document.body, "tr.o_data_row", "should display 0 records");
// // Save this search
// await toggleFavoriteMenu(".modal");
// await toggleSearchBarMenu(".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");

View file

@ -1,863 +0,0 @@
/** @odoo-module **/
import {
click,
getFixture,
patchDate,
patchTimeZone,
patchWithCleanup,
} from "@web/../tests/helpers/utils";
import { localization } from "@web/core/l10n/localization";
import { ControlPanel } from "@web/search/control_panel/control_panel";
import { browser } from "@web/core/browser/browser";
import {
addCondition,
applyFilter,
editConditionField,
editConditionOperator,
editConditionValue,
getFacetTexts,
isItemSelected,
makeWithSearch,
removeFacet,
setupControlPanelServiceRegistry,
toggleAddCustomFilter,
toggleFilterMenu,
toggleMenuItem,
} from "./helpers";
function getDomain(controlPanel) {
return controlPanel.env.searchModel.domain;
}
let target;
let serverData;
QUnit.module("Search", (hooks) => {
hooks.beforeEach(async () => {
serverData = {
models: {
foo: {
fields: {
date_field: {
name: "date_field",
string: "A date",
type: "date",
searchable: true,
},
date_time_field: {
name: "date_time_field",
string: "DateTime",
type: "datetime",
searchable: true,
},
boolean_field: {
name: "boolean_field",
string: "Boolean Field",
type: "boolean",
default: true,
searchable: true,
},
char_field: {
name: "char_field",
string: "Char Field",
type: "char",
default: "foo",
trim: true,
searchable: true,
},
float_field: {
name: "float_field",
string: "Floaty McFloatface",
type: "float",
digits: [999, 1],
searchable: true,
},
color: {
name: "color",
string: "Color",
type: "selection",
selection: [
["black", "Black"],
["white", "White"],
],
searchable: true,
},
image: {
name: "image",
string: "Binary field",
type: "binary",
searchable: true,
},
json_field: {
name: "json_field",
string: "Jason",
type: "json",
searchable: true,
},
},
records: {},
},
},
views: {
"foo,false,search": `<search/>`,
},
};
setupControlPanelServiceRegistry();
patchWithCleanup(browser, {
setTimeout: (fn) => fn(),
clearTimeout: () => {},
});
target = getFixture();
});
QUnit.module("CustomFilterItem");
QUnit.test("basic rendering", async function (assert) {
assert.expect(14);
await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchViewId: false,
searchMenuTypes: ["filter"],
});
await toggleFilterMenu(target);
const customFilterItem = target.querySelector(".o_add_custom_filter_menu");
assert.strictEqual(customFilterItem.innerText.trim(), "Add Custom Filter");
await toggleAddCustomFilter(target);
// Single condition
assert.containsOnce(customFilterItem, ".o_filter_condition");
assert.containsOnce(
customFilterItem,
".o_filter_condition > select.o_generator_menu_field"
);
assert.containsOnce(
customFilterItem,
".o_filter_condition > select.o_generator_menu_operator"
);
assert.containsOnce(customFilterItem, ".o_filter_condition > span.o_generator_menu_value");
assert.containsNone(customFilterItem, ".o_filter_condition .o_or_filter");
assert.containsNone(customFilterItem, ".o_filter_condition .o_generator_menu_delete");
// no deletion allowed on single condition
assert.containsNone(customFilterItem, ".o_filter_condition > i.o_generator_menu_delete");
// Buttons
assert.containsOnce(customFilterItem, "button.o_apply_filter");
assert.containsOnce(customFilterItem, "button.o_add_condition");
assert.containsOnce(customFilterItem, ".o_filter_condition");
await click(customFilterItem, "button.o_add_condition");
assert.containsN(customFilterItem, ".o_filter_condition", 2);
assert.containsOnce(customFilterItem, ".o_filter_condition .o_or_filter");
assert.containsN(customFilterItem, ".o_filter_condition .o_generator_menu_delete", 2);
});
QUnit.test(
'should have Date and ID field proposed in that order in "Add custom Filter" submenu',
async function (assert) {
assert.expect(2);
await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchViewId: false,
searchMenuTypes: ["filter"],
searchViewFields: {
date_field: {
name: "date_field",
string: "Date",
type: "date",
store: true,
sortable: true,
searchable: true,
},
foo: { string: "Foo", type: "char", store: true, sortable: true },
},
});
await toggleFilterMenu(target);
await toggleAddCustomFilter(target);
const optionEls = target.querySelectorAll(
".o_filter_condition > select.o_generator_menu_field option"
);
assert.strictEqual(optionEls[0].innerText.trim(), "Date");
assert.strictEqual(optionEls[1].innerText.trim(), "ID");
}
);
QUnit.test("deactivate a new custom filter works", async function (assert) {
assert.expect(4);
patchDate(2020, 1, 5, 12, 20, 0);
await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchViewId: false,
searchMenuTypes: ["filter"],
searchViewFields: {
date_field: {
name: "date_field",
string: "Date",
type: "date",
store: true,
sortable: true,
searchable: true,
},
},
});
await toggleFilterMenu(target);
await toggleAddCustomFilter(target);
await applyFilter(target);
assert.ok(isItemSelected(target, 'Date is equal to "02/05/2020"'));
assert.deepEqual(getFacetTexts(target), ['Date is equal to "02/05/2020"']);
await toggleMenuItem(target, 'Date is equal to "02/05/2020"');
assert.notOk(isItemSelected(target, 'Date is equal to "02/05/2020"'));
assert.deepEqual(getFacetTexts(target), []);
});
QUnit.test("custom OR filter presets new condition from preceding", async function (assert) {
assert.expect(4);
await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchViewId: false,
searchMenuTypes: ["filter"],
});
await toggleFilterMenu(target);
await toggleAddCustomFilter(target);
// Retrieve second selectable values for field and operator dropdowns
const secondFieldName = target.querySelector(
".o_generator_menu_field option:nth-of-type(2)"
).value;
const secondOperator = target.querySelector(
".o_generator_menu_operator option:nth-of-type(2)"
).value;
// Check if they really exist…
assert.ok(!!secondFieldName);
assert.ok(!!secondOperator);
// Add first filter condition
await editConditionField(target, 0, secondFieldName);
await editConditionOperator(target, 0, secondOperator);
// Add a second conditon on the filter being created
await addCondition(target);
// Check the defaults for field and operator dropdowns
assert.strictEqual(
target.querySelector(".o_filter_condition:nth-of-type(2) .o_generator_menu_field")
.value,
secondFieldName
);
assert.strictEqual(
target.querySelector(".o_filter_condition:nth-of-type(2) .o_generator_menu_operator")
.value,
secondOperator
);
});
QUnit.test("add a custom filter works", async function (assert) {
assert.expect(2);
const controlPanel = await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchViewId: false,
searchMenuTypes: ["filter"],
searchViewFields: {},
});
await toggleFilterMenu(target);
await toggleAddCustomFilter(target);
// choose ID field in 'Add Custom filter' menu and value 1
await editConditionField(target, 0, "id");
await editConditionValue(target, 0, 1);
await applyFilter(target);
assert.deepEqual(getFacetTexts(target), ['ID is "1"']);
assert.deepEqual(getDomain(controlPanel), [["id", "=", 1]]);
});
QUnit.test("adding a simple filter works", async function (assert) {
assert.expect(11);
const controlPanel = await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchViewId: false,
searchMenuTypes: ["filter"],
searchViewFields: {
boolean_field: {
name: "boolean_field",
string: "Boolean Field",
type: "boolean",
default: true,
searchable: true,
},
},
});
await toggleFilterMenu(target);
assert.deepEqual(getFacetTexts(target), []);
assert.deepEqual(getDomain(controlPanel), []);
assert.containsNone(target, ".o_menu_item");
assert.containsOnce(target, ".o_add_custom_filter_menu button.dropdown-toggle");
// the 'Add Custom Filter' menu should be closed;
assert.containsNone(target, ".o_add_custom_filter_menu .dropdown-menu");
await toggleAddCustomFilter(target);
// the 'Add Custom Filter' menu should be open;
assert.containsOnce(target, ".o_add_custom_filter_menu .dropdown-menu");
await applyFilter(target);
assert.deepEqual(getFacetTexts(target), ["Boolean Field is Yes"]);
assert.deepEqual(getDomain(controlPanel), [["boolean_field", "=", true]]);
assert.containsOnce(target, ".o_menu_item");
assert.containsOnce(target, ".o_add_custom_filter_menu button.dropdown-toggle");
// the 'Add Custom Filter' menu should still be opened;
assert.containsOnce(target, ".o_add_custom_filter_menu .dropdown-menu");
});
QUnit.test("binary field is available", async function (assert) {
assert.expect(11);
const controlPanel = await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchViewId: false,
searchMenuTypes: ["filter"],
searchViewFields: {
image: {
name: "image",
string: "Binary Field",
type: "binary",
default: true,
searchable: true,
},
},
});
await toggleFilterMenu(target);
assert.deepEqual(getFacetTexts(target), []);
assert.deepEqual(getDomain(controlPanel), []);
assert.containsNone(target, ".o_menu_item");
assert.containsOnce(target, ".o_add_custom_filter_menu button.dropdown-toggle");
// the 'Add Custom Filter' menu should be closed;
assert.containsNone(target, ".o_add_custom_filter_menu .dropdown-menu");
await toggleAddCustomFilter(target);
// the 'Add Custom Filter' menu should be open;
assert.containsOnce(target, ".o_add_custom_filter_menu .dropdown-menu");
await applyFilter(target);
assert.deepEqual(getFacetTexts(target), ["Binary Field is set"]);
assert.deepEqual(getDomain(controlPanel), [["image", "!=", false]]);
assert.containsOnce(target, ".o_menu_item");
assert.containsOnce(target, ".o_add_custom_filter_menu button.dropdown-toggle");
// the 'Add Custom Filter' menu should still be opened;
assert.containsOnce(target, ".o_add_custom_filter_menu .dropdown-menu");
});
QUnit.test("json field is available", async function (assert) {
assert.expect(10);
const controlPanel = await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchViewId: false,
searchMenuTypes: ["filter"],
searchViewFields: {
json_field: {
name: "json_field",
string: "Json",
type: "json",
default: "{'1': 50}",
searchable: true,
},
},
});
await toggleFilterMenu(target);
assert.deepEqual(getFacetTexts(target), []);
assert.deepEqual(getDomain(controlPanel), []);
assert.containsNone(target, ".o_menu_item");
assert.containsOnce(target, ".o_add_custom_filter_menu button.dropdown-toggle");
// the 'Add Custom Filter' menu should be closed;
assert.containsNone(target, ".o_add_custom_filter_menu .dropdown-menu");
await toggleAddCustomFilter(target);
// the 'Add Custom Filter' menu should be open;
assert.containsOnce(target, ".o_add_custom_filter_menu .dropdown-menu");
await editConditionField(target, 0, "json_field");
await applyFilter(target);
assert.deepEqual(getFacetTexts(target), ['Json contains ""']);
assert.deepEqual(getDomain(controlPanel), [["json_field", "ilike", ""]]);
await removeFacet(target);
await toggleFilterMenu(target);
await toggleAddCustomFilter(target);
await editConditionField(target, 0, "json_field");
await editConditionOperator(target, 0, "!=");
await applyFilter(target);
assert.deepEqual(getFacetTexts(target), ['Json is not equal to ""']);
assert.deepEqual(getDomain(controlPanel), [["json_field", "!=", ""]]);
});
QUnit.test("selection field: default and updated value", async function (assert) {
assert.expect(11);
const controlPanel = await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchViewId: false,
searchMenuTypes: ["filter"],
});
// Default value
await toggleFilterMenu(target);
assert.containsN(target, ".o_menu_item", 0);
assert.containsN(target, ".dropdown-divider", 0);
await toggleAddCustomFilter(target);
await editConditionField(target, 0, "color");
await applyFilter(target);
assert.deepEqual(getFacetTexts(target), ['Color is "Black"']);
assert.deepEqual(getDomain(controlPanel), [["color", "=", "black"]]);
assert.containsN(target, ".o_menu_item", 1);
assert.containsN(target, ".dropdown-divider", 1);
// deactivate custom filter
await removeFacet(target);
// Updated value
await toggleFilterMenu(target);
await toggleAddCustomFilter(target);
await editConditionField(target, 0, "color");
await editConditionValue(target, 0, "white");
assert.strictEqual(
target.querySelector(".o_generator_menu_value input,.o_generator_menu_value select")
.value,
"white"
);
await applyFilter(target);
assert.deepEqual(getFacetTexts(target), ['Color is "White"']);
assert.deepEqual(getDomain(controlPanel), [["color", "=", "white"]]);
assert.containsN(target, ".o_menu_item", 2);
assert.containsN(target, ".dropdown-divider", 2);
});
QUnit.test(
"commit search with an extended proposition with field char does not cause a crash",
async function (assert) {
assert.expect(12);
const controlPanel = await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchViewId: false,
searchMenuTypes: ["filter"],
searchViewFields: {
many2one_field: {
name: "many2one_field",
string: "AAA",
type: "many2one",
searchable: true,
},
},
});
const steps = [
{
value: `a`,
domain: [["many2one_field", "ilike", `a`]],
facetContent: `AAA contains "a"`,
},
{
value: `"a"`,
domain: [["many2one_field", "ilike", `"a"`]],
facetContent: `AAA contains ""a""`,
},
{
value: `'a'`,
domain: [["many2one_field", "ilike", `'a'`]],
facetContent: `AAA contains "'a'"`,
},
{
value: `'`,
domain: [["many2one_field", "ilike", `'`]],
facetContent: `AAA contains "'"`,
},
{
value: `"`,
domain: [["many2one_field", "ilike", `"`]],
facetContent: `AAA contains """`,
},
{
value: `\\`,
domain: [["many2one_field", "ilike", `\\`]],
facetContent: `AAA contains "\\"`,
},
];
for (const step of steps) {
await toggleFilterMenu(target);
await toggleAddCustomFilter(target);
await editConditionValue(target, 0, step.value);
await applyFilter(target);
assert.deepEqual(getFacetTexts(target), [step.facetContent]);
assert.deepEqual(getDomain(controlPanel), step.domain);
await removeFacet(target);
}
}
);
QUnit.test("custom filter date with equal operator", async function (assert) {
patchTimeZone(-240);
patchDate(2017, 1, 22, 12, 30, 0);
const controlPanel = await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchViewId: false,
searchMenuTypes: ["filter"],
});
await toggleFilterMenu(target);
await toggleAddCustomFilter(target);
await editConditionField(target, 0, "date_field");
await editConditionOperator(target, 0, "=");
await editConditionValue(target, 0, "01/01/2017");
await applyFilter(target);
assert.deepEqual(getFacetTexts(target), ['A date is equal to "01/01/2017"']);
assert.deepEqual(getDomain(controlPanel), [["date_field", "=", "2017-01-01"]]);
});
QUnit.test("custom filter datetime with equal operator", async function (assert) {
assert.expect(5);
patchTimeZone(-240);
patchDate(2017, 1, 22, 12, 30, 0);
const controlPanel = await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchViewId: false,
searchMenuTypes: ["filter"],
});
await toggleFilterMenu(target);
await toggleAddCustomFilter(target);
await editConditionField(target, 0, "date_time_field");
assert.strictEqual(
target.querySelector(".o_generator_menu_field").value,
"date_time_field"
);
assert.strictEqual(target.querySelector(".o_generator_menu_operator").value, "between");
assert.deepEqual(
[...target.querySelectorAll(".o_generator_menu_value input[type=text]")].map(
(v) => v.value
),
["02/22/2017 00:00:00", "02/22/2017 23:59:59"]
);
await editConditionOperator(target, 0, "=");
await editConditionValue(target, 0, "02/22/2017 11:00:00"); // in TZ
await applyFilter(target);
assert.deepEqual(
getFacetTexts(target),
['DateTime is equal to "02/22/2017 11:00:00"'],
"description should be in localized format"
);
assert.deepEqual(
getDomain(controlPanel),
[["date_time_field", "=", "2017-02-22 15:00:00"]],
"domain should be in UTC format"
);
});
QUnit.test("custom filter datetime between operator", async function (assert) {
assert.expect(5);
patchTimeZone(-240);
patchDate(2017, 1, 22, 12, 30, 0);
const controlPanel = await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchViewId: false,
searchMenuTypes: ["filter"],
});
await toggleFilterMenu(target);
await toggleAddCustomFilter(target);
await editConditionField(target, 0, "date_time_field");
assert.strictEqual(
target.querySelector(".o_generator_menu_field").value,
"date_time_field"
);
assert.strictEqual(target.querySelector(".o_generator_menu_operator").value, "between");
assert.deepEqual(
[...target.querySelectorAll(".o_generator_menu_value input[type=text]")].map(
(v) => v.value
),
["02/22/2017 00:00:00", "02/22/2017 23:59:59"]
);
await editConditionValue(target, 0, "02/22/2017 11:00:00", 0); // in TZ
await editConditionValue(target, 0, "02-22-2017 17:00:00", 1); // in TZ
await applyFilter(target);
assert.deepEqual(
getFacetTexts(target),
['DateTime is between "02/22/2017 11:00:00 and 02/22/2017 17:00:00"'],
"description should be in localized format"
);
assert.deepEqual(
getDomain(controlPanel),
[
"&",
["date_time_field", ">=", "2017-02-22 15:00:00"],
["date_time_field", "<=", "2017-02-22 21:00:00"],
],
"domain should be in UTC format"
);
});
QUnit.test("input value parsing", async function (assert) {
assert.expect(7);
await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchViewId: false,
searchMenuTypes: ["filter"],
});
await toggleFilterMenu(target);
await toggleAddCustomFilter(target);
await addCondition(target);
await editConditionField(target, 0, "float_field");
await editConditionField(target, 1, "id");
const [floatInput, idInput] = target.querySelectorAll(".o_generator_menu_value .o_input");
// Default values
await editConditionValue(target, 0, "0.0");
assert.strictEqual(floatInput.value, "0.0");
await editConditionValue(target, 1, "0");
assert.strictEqual(idInput.value, "0");
// Float parsing
await editConditionValue(target, 0, "4.2");
assert.strictEqual(floatInput.value, "4.2");
await editConditionValue(target, 0, "DefinitelyValidFloat");
// "DefinitelyValidFloat" cannot be entered in a input type number so that the input value is reset to 0
assert.strictEqual(floatInput.value, "4.2");
// Number parsing
await editConditionValue(target, 1, "4");
assert.strictEqual(idInput.value, "4");
await editConditionValue(target, 1, "4.2");
assert.strictEqual(idInput.value, "4");
await editConditionValue(target, 1, "DefinitelyValidID");
// "DefinitelyValidID" cannot be entered in a input type number so that the input value is reset to 0
assert.strictEqual(idInput.value, "0");
});
QUnit.test("input value parsing with language", async function (assert) {
assert.expect(5);
await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchViewId: false,
searchMenuTypes: ["filter"],
});
// Needs to be done after services have been started
patchWithCleanup(localization, {
decimalPoint: ",",
thousandsSep: "",
grouping: [3, 0],
});
await toggleFilterMenu(target);
await toggleAddCustomFilter(target);
await editConditionField(target, 0, "float_field");
const [floatInput] = target.querySelectorAll(".o_generator_menu_value .o_input");
// Default values
assert.strictEqual(floatInput.value, "0,0");
// Float parsing
await editConditionValue(target, 0, "4,");
assert.strictEqual(floatInput.value, "4,0");
await editConditionValue(target, 0, "4,2");
assert.strictEqual(floatInput.value, "4,2");
await editConditionValue(target, 0, "4,2,");
assert.strictEqual(floatInput.value, "42,0"); // because of the en localization fallback in parsers.
await editConditionValue(target, 0, "DefinitelyValidFloat");
// The input here is a string, resulting in a parsing error instead of 0
assert.strictEqual(floatInput.value, "42,0");
});
QUnit.test("add custom filter with multiple values", async function (assert) {
assert.expect(2);
const controlPanel = await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchViewId: false,
searchMenuTypes: ["filter"],
});
await toggleFilterMenu(target);
await toggleAddCustomFilter(target);
for (let i = 0; i < 4; i++) {
await addCondition(target);
}
await editConditionField(target, 0, "date_field");
await editConditionValue(target, 0, "01/09/1997");
await editConditionField(target, 1, "boolean_field");
await editConditionOperator(target, 1, "!=");
await editConditionField(target, 2, "char_field");
await editConditionValue(target, 2, "I will be deleted anyway");
await editConditionField(target, 3, "float_field");
await editConditionValue(target, 3, 7.2);
await editConditionField(target, 4, "id");
await editConditionValue(target, 4, 9);
const thirdcondition = target.querySelectorAll(".o_filter_condition")[2];
await click(thirdcondition, ".o_generator_menu_delete");
await applyFilter(target);
assert.deepEqual(getFacetTexts(target), [
[
'A date is equal to "01/09/1997"',
"Boolean Field is No",
'Floaty McFloatface is equal to "7.2"',
'ID is "9"',
].join("or"),
]);
assert.deepEqual(getDomain(controlPanel), [
"|",
["date_field", "=", "1997-01-09"],
"|",
["boolean_field", "!=", true],
"|",
["float_field", "=", 7.2],
["id", "=", 9],
]);
});
QUnit.test("delete button is visible", async function (assert) {
await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchViewId: false,
searchMenuTypes: ["filter"],
});
await toggleFilterMenu(target);
await toggleAddCustomFilter(target);
assert.containsNone(
target,
".o_generator_menu_delete",
"There is no delete button by default"
);
await addCondition(target);
assert.containsN(
target,
".o_generator_menu_delete",
2,
"A delete button has been added to each condition"
);
assert.containsN(
target,
"i.o_generator_menu_delete.fa-trash-o",
2,
"The delete button is shown as a trash icon"
);
});
});

View file

@ -1,19 +1,18 @@
/** @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 { getFixture, getNodesTextContent, patchWithCleanup } from "../helpers/utils";
import {
applyGroup,
getFacetTexts,
isItemSelected,
isOptionSelected,
makeWithSearch,
selectGroup,
setupControlPanelServiceRegistry,
toggleAddCustomGroup,
toggleGroupByMenu,
toggleMenuItem,
toggleSearchBarMenu,
} from "./helpers";
import { SearchBar } from "@web/search/search_bar/search_bar";
let target;
let serverData;
@ -47,31 +46,26 @@ QUnit.module("Search", (hooks) => {
QUnit.module("CustomGroupByItem");
QUnit.test("simple rendering", async function (assert) {
assert.expect(5);
assert.expect(2);
await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
Component: SearchBar,
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");
await toggleSearchBarMenu(target);
const groupByMenu = target.querySelector(".o_group_by_menu");
assert.strictEqual(
groupByMenu.querySelector(".o_group_by_menu option[disabled]").innerText.trim(),
"Add Custom Group"
);
assert.deepEqual(
[...target.querySelectorAll(".o_add_custom_group_menu select option")].map(
(el) => el.innerText
getNodesTextContent(
target.querySelectorAll(".o_add_custom_group_menu option:not([disabled])")
),
["Birthday", "Date", "Foo"]
);
@ -85,7 +79,7 @@ QUnit.module("Search", (hooks) => {
await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
Component: SearchBar,
searchMenuTypes: ["groupBy"],
searchViewId: false,
searchViewFields: {
@ -94,17 +88,11 @@ QUnit.module("Search", (hooks) => {
},
});
await toggleGroupByMenu(target);
await toggleAddCustomGroup(target);
assert.deepEqual(
[
...target.querySelectorAll(
".o_add_custom_group_menu .dropdown-menu select option"
),
].map((el) => el.innerText),
["Foo"]
);
await toggleSearchBarMenu(target);
const optionDescriptions = [
...target.querySelectorAll(".o_add_custom_group_menu option:not([disabled])"),
].map((option) => option.innerText.trim());
assert.deepEqual(optionDescriptions, ["Foo"]);
}
);
@ -116,7 +104,7 @@ QUnit.module("Search", (hooks) => {
await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
Component: SearchBar,
searchMenuTypes: ["groupBy"],
searchViewId: false,
searchViewFields: {
@ -130,17 +118,11 @@ QUnit.module("Search", (hooks) => {
},
});
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"]
);
await toggleSearchBarMenu(target);
const optionDescriptions = [
...target.querySelectorAll(".o_add_custom_group_menu option:not([disabled])"),
].map((option) => option.innerText.trim());
assert.deepEqual(optionDescriptions, ["Char A", "M2M Stored"]);
}
);
@ -152,7 +134,7 @@ QUnit.module("Search", (hooks) => {
const controlPanel = await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
Component: SearchBar,
searchMenuTypes: ["groupBy"],
searchViewId: false,
searchViewFields: {
@ -160,13 +142,12 @@ QUnit.module("Search", (hooks) => {
id: { sortable: true, string: "ID", type: "integer" },
},
});
await toggleGroupByMenu(target);
await toggleSearchBarMenu(target);
assert.deepEqual(controlPanel.env.searchModel.groupBy, []);
assert.containsNone(target, ".o_menu_item");
assert.containsOnce(target, ".o_add_custom_group_menu"); //Add Custom Group
await toggleAddCustomGroup(target);
await applyGroup(target);
await selectGroup(target, "date_field");
assert.deepEqual(controlPanel.env.searchModel.groupBy, ["date_field:month"]);
assert.deepEqual(getFacetTexts(target), ["Date: Month"]);
@ -179,48 +160,44 @@ QUnit.module("Search", (hooks) => {
);
QUnit.test("click on add custom group toggle group selector", async function (assert) {
assert.expect(4);
assert.expect(3);
await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
Component: SearchBar,
searchMenuTypes: ["groupBy"],
searchViewFields: {
date: { sortable: true, name: "date", string: "Super Date", type: "date" },
},
});
await toggleGroupByMenu(target);
await toggleSearchBarMenu(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"
addCustomGroupMenu.querySelector("option[disabled]").innerText.trim(),
"Add Custom Group"
);
// Button apply
assert.containsOnce(target, ".o_add_custom_group_menu .dropdown-menu .btn");
// Single select node with a single option
assert.containsOnce(target, ".o_add_custom_group_menu option:not([disabled])");
assert.deepEqual(
target.querySelector(".o_add_custom_group_menu option:not([disabled])").textContent,
"Super Date"
);
});
QUnit.test(
"select a field name in Add Custom Group menu properly trigger the corresponding field",
async function (assert) {
assert.expect(4);
assert.expect(3);
await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
Component: SearchBar,
searchMenuTypes: ["groupBy"],
searchViewFields: {
candle_light: {
@ -231,13 +208,11 @@ QUnit.module("Search", (hooks) => {
},
});
await toggleGroupByMenu(target);
await toggleAddCustomGroup(target);
await applyGroup(target);
await toggleSearchBarMenu(target);
await selectGroup(target, "candle_light");
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.containsN(target, ".o_group_by_menu .o_menu_item", 2);
assert.containsOnce(target, ".o_add_custom_group_menu");
assert.deepEqual(getFacetTexts(target), ["Candlelight"]);
}
);

View file

@ -1,391 +0,0 @@
/** @odoo-module **/
import { click, getFixture, patchDate } from "@web/../tests/helpers/utils";
import { createWebClient, doAction } from "@web/../tests/webclient/helpers";
import { registry } from "@web/core/registry";
import { ControlPanel } from "@web/search/control_panel/control_panel";
import { FavoriteMenu } from "@web/search/favorite_menu/favorite_menu";
import { SearchBar } from "@web/search/search_bar/search_bar";
import {
deleteFavorite,
getFacetTexts,
isItemSelected,
makeWithSearch,
setupControlPanelFavoriteMenuRegistry,
setupControlPanelServiceRegistry,
toggleComparisonMenu,
toggleFavoriteMenu,
toggleMenuItem,
} from "@web/../tests/search/helpers";
import { Component, onWillUpdateProps, xml } from "@odoo/owl";
const viewRegistry = registry.category("views");
const favoriteMenuRegistry = registry.category("favoriteMenu");
function getDomain(comp) {
return comp.env.searchModel.domain;
}
let serverData;
let target;
QUnit.module("Search", (hooks) => {
hooks.beforeEach(async () => {
target = getFixture();
serverData = {
models: {
foo: {
fields: {
bar: { string: "Bar", type: "many2one", relation: "partner" },
birthday: { string: "Birthday", type: "date", store: true, sortable: true },
date_field: { string: "Date", type: "date", store: true, sortable: true },
float_field: { string: "Float", type: "float", group_operator: "sum" },
foo: { string: "Foo", type: "char", store: true, sortable: true },
},
records: {},
},
},
views: {
"foo,false,search": `<search/>`,
},
};
setupControlPanelFavoriteMenuRegistry();
setupControlPanelServiceRegistry();
});
QUnit.module("FavoriteMenu");
QUnit.test(
"simple rendering with no favorite (without ability to save)",
async function (assert) {
assert.expect(4);
favoriteMenuRegistry.remove("custom-favorite-item");
await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchMenuTypes: ["favorite"],
searchViewId: false,
config: {
getDisplayName: () => "Action Name",
},
});
assert.containsOnce(target, "div.o_favorite_menu > button i.fa.fa-star");
assert.strictEqual(
target
.querySelector("div.o_favorite_menu > button span")
.innerText.trim()
.toUpperCase(),
"FAVORITES"
);
await toggleFavoriteMenu(target);
assert.containsOnce(
target,
"div.o_favorite_menu > .dropdown-menu",
"the menu should be opened"
);
assert.containsNone(target, ".dropdown-menu *", "the menu should be empty");
}
);
QUnit.test("simple rendering with no favorite", async function (assert) {
assert.expect(5);
await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchMenuTypes: ["favorite"],
searchViewId: false,
config: {
getDisplayName: () => "Action Name",
},
});
assert.containsOnce(target, "div.o_favorite_menu > button i.fa.fa-star");
assert.strictEqual(
target
.querySelector("div.o_favorite_menu > button span")
.innerText.trim()
.toUpperCase(),
"FAVORITES"
);
await toggleFavoriteMenu(target);
assert.containsOnce(
target,
"div.o_favorite_menu > .dropdown-menu",
"the menu should be opened"
);
assert.containsNone(target, ".dropdown-menu .dropdown-divider");
assert.containsOnce(target, ".dropdown-menu .o_add_favorite");
});
QUnit.test("delete an active favorite", async function (assert) {
assert.expect(14);
class ToyController extends Component {
setup() {
assert.deepEqual(this.props.domain, [["foo", "=", "qsdf"]]);
onWillUpdateProps((nextProps) => {
assert.deepEqual(nextProps.domain, []);
});
}
}
ToyController.components = { FavoriteMenu, SearchBar };
ToyController.template = xml`
<div>
<SearchBar/>
<FavoriteMenu/>
</div>
`;
viewRegistry.add("toy", {
type: "toy",
display_name: "Toy",
Controller: ToyController,
});
serverData.views["foo,false,toy"] = `<toy />`;
serverData.models.foo.filters = [
{
context: "{}",
domain: "[['foo', '=', 'qsdf']]",
id: 7,
is_default: true,
name: "My favorite",
sort: "[]",
user_id: [2, "Mitchell Admin"],
},
];
const webClient = await createWebClient({
serverData,
mockRPC: async (_, args) => {
if (args.model === "ir.filters" && args.method === "unlink") {
assert.step("deleteFavorite");
return { result: true }; // mocked unlink result
}
},
});
webClient.env.bus.on("CLEAR-CACHES", webClient, () => assert.step("CLEAR-CACHES"));
await doAction(webClient, {
name: "Action",
res_model: "foo",
type: "ir.actions.act_window",
views: [[false, "toy"]],
});
await toggleFavoriteMenu(target);
const favorite = target.querySelector(".o_favorite_menu .dropdown-item");
assert.equal(favorite.innerText, "My favorite");
assert.deepEqual(favorite.getAttribute("role"), "menuitemcheckbox");
assert.deepEqual(favorite.ariaChecked, "true");
assert.deepEqual(getFacetTexts(target), ["My favorite"]);
assert.hasClass(target.querySelector(".o_favorite_menu .o_menu_item"), "selected");
await deleteFavorite(target, 0);
assert.verifySteps([]);
await click(document.querySelector("div.o_dialog footer button"));
assert.deepEqual(getFacetTexts(target), []);
assert.containsNone(target, ".o_favorite_menu .o_menu_item");
assert.containsOnce(target, ".o_favorite_menu .o_add_favorite");
assert.verifySteps(["deleteFavorite", "CLEAR-CACHES"]);
});
QUnit.test(
"default favorite is not activated if activateFavorite is set to false",
async function (assert) {
assert.expect(3);
const controlPanel = await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchMenuTypes: ["favorite"],
searchViewId: false,
irFilters: [
{
context: "{}",
domain: "[('foo', '=', 'a')]",
id: 7,
is_default: true,
name: "My favorite",
sort: "[]",
user_id: [2, "Mitchell Admin"],
},
],
activateFavorite: false,
});
await toggleFavoriteMenu(target);
assert.notOk(isItemSelected(target, "My favorite"));
assert.deepEqual(getDomain(controlPanel), []);
assert.deepEqual(getFacetTexts(target), []);
}
);
QUnit.test(
'toggle favorite correctly clears filter, groupbys, comparison and field "options"',
async function (assert) {
patchDate(2019, 6, 31, 13, 43, 0);
const controlPanel = await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchMenuTypes: ["filter", "groupBy", "comparison", "favorite"],
searchViewId: false,
irFilters: [
{
context: `
{
"group_by": ["foo"],
"comparison": {
"favorite comparison content": "bla bla..."
},
}
`,
domain: "['!', ['foo', '=', 'qsdf']]",
id: 7,
is_default: false,
name: "My favorite",
sort: "[]",
user_id: [2, "Mitchell Admin"],
},
],
searchViewArch: `
<search>
<field string="Foo" name="foo"/>
<filter string="Date Field Filter" name="positive" date="date_field" default_period="this_year"/>
<filter string="Date Field Groupby" name="coolName" context="{'group_by': 'date_field'}"/>
</search>
`,
context: {
search_default_positive: true,
search_default_coolName: true,
search_default_foo: "a",
},
});
let domain = controlPanel.env.searchModel.domain;
let groupBy = controlPanel.env.searchModel.groupBy;
let comparison = controlPanel.env.searchModel.getFullComparison();
assert.deepEqual(domain, [
"&",
["foo", "ilike", "a"],
"&",
["date_field", ">=", "2019-01-01"],
["date_field", "<=", "2019-12-31"],
]);
assert.deepEqual(groupBy, ["date_field:month"]);
assert.deepEqual(comparison, null);
assert.deepEqual(getFacetTexts(target), [
"Foo\na",
"Date Field Filter: 2019",
"Date Field Groupby: Month",
]);
// activate a comparison
await toggleComparisonMenu(target);
await toggleMenuItem(target, "Date Field Filter: Previous Period");
domain = controlPanel.env.searchModel.domain;
groupBy = controlPanel.env.searchModel.groupBy;
comparison = controlPanel.env.searchModel.getFullComparison();
assert.deepEqual(domain, [["foo", "ilike", "a"]]);
assert.deepEqual(groupBy, ["date_field:month"]);
assert.deepEqual(comparison, {
comparisonId: "previous_period",
comparisonRange: [
"&",
["date_field", ">=", "2018-01-01"],
["date_field", "<=", "2018-12-31"],
],
comparisonRangeDescription: "2018",
fieldDescription: "Date Field Filter",
fieldName: "date_field",
range: [
"&",
["date_field", ">=", "2019-01-01"],
["date_field", "<=", "2019-12-31"],
],
rangeDescription: "2019",
});
// activate the unique existing favorite
await toggleFavoriteMenu(target);
const favorite = target.querySelector(".o_favorite_menu .dropdown-item");
assert.equal(favorite.innerText, "My favorite");
assert.deepEqual(favorite.getAttribute("role"), "menuitemcheckbox");
assert.deepEqual(favorite.ariaChecked, "false");
await toggleMenuItem(target, 0);
assert.deepEqual(favorite.ariaChecked, "true");
domain = controlPanel.env.searchModel.domain;
groupBy = controlPanel.env.searchModel.groupBy;
comparison = controlPanel.env.searchModel.getFullComparison();
assert.deepEqual(domain, ["!", ["foo", "=", "qsdf"]]);
assert.deepEqual(groupBy, ["foo"]);
assert.deepEqual(comparison, {
"favorite comparison content": "bla bla...",
});
assert.deepEqual(getFacetTexts(target), ["My favorite"]);
}
);
QUnit.skip("modal loads saved search filters", async function (assert) {
/** @todo I don't know yet how to convert this test */
// assert.expect(1);
// const data = {
// partner: {
// fields: {
// bar: { string: "Bar", type: "many2one", relation: "partner" },
// },
// // 10 records so that the Search button shows
// records: Array.apply(null, Array(10)).map(function (_, i) {
// return { id: i, display_name: "Record " + i, bar: 1 };
// }),
// },
// };
// const form = await createView({
// arch: `
// <form string="Partners">
// <sheet>
// <group>
// <field name="bar"/>
// </group>
// </sheet>
// </form>`,
// data,
// model: "partner",
// res_id: 1,
// View: FormView,
// interceptsPropagate: {
// load_views: function (ev) {
// assert.ok(
// ev.data.options.load_filters,
// "opening dialog should load the filters"
// );
// },
// },
// });
// await testUtils.form.clickEdit(form);
// await testUtils.fields.many2one.clickOpenDropdown("bar");
// await testUtils.fields.many2one.clickItem("bar", "Search");
// form.destroy();
});
});

View file

@ -1,587 +0,0 @@
/** @odoo-module **/
import { getFixture, patchDate, patchWithCleanup } from "@web/../tests/helpers/utils";
import { browser } from "@web/core/browser/browser";
import { ControlPanel } from "@web/search/control_panel/control_panel";
import {
getFacetTexts,
isItemSelected,
isOptionSelected,
makeWithSearch,
setupControlPanelServiceRegistry,
toggleFilterMenu,
toggleMenuItem,
toggleMenuItemOption,
} from "./helpers";
function getDomain(controlPanel) {
return controlPanel.env.searchModel.domain;
}
function getContext(controlPanel) {
return controlPanel.env.searchModel.context;
}
let target;
let serverData;
QUnit.module("Search", (hooks) => {
hooks.beforeEach(async () => {
serverData = {
models: {
foo: {
fields: {
date_field: {
string: "Date",
type: "date",
store: true,
sortable: true,
searchable: true,
},
foo: { string: "Foo", type: "char", store: true, sortable: true },
},
records: {},
},
},
views: {
"foo,false,search": `<search/>`,
},
};
setupControlPanelServiceRegistry();
patchWithCleanup(browser, {
setTimeout: (fn) => fn(),
clearTimeout: () => {},
});
target = getFixture();
});
QUnit.module("FilterMenu");
QUnit.test("simple rendering with no filter", async function (assert) {
assert.expect(3);
await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchMenuTypes: ["filter"],
});
await toggleFilterMenu(target);
assert.containsNone(target, ".o_menu_item");
assert.containsNone(target, ".dropdown-divider");
assert.containsOnce(target, ".o_add_custom_filter_menu");
});
QUnit.test("simple rendering with a single filter", async function (assert) {
await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchViewId: false,
searchMenuTypes: ["filter"],
searchViewArch: `
<search>
<filter string="Foo" name="foo" domain="[]"/>
</search>
`,
});
await toggleFilterMenu(target);
assert.containsOnce(target, ".o_menu_item");
assert.containsOnce(target, ".o_menu_item[role=menuitemcheckbox]");
assert.deepEqual(target.querySelector(".o_menu_item").ariaChecked, "false");
assert.containsOnce(target, ".dropdown-divider");
assert.containsOnce(target, ".o_add_custom_filter_menu");
});
QUnit.test('toggle a "simple" filter in filter menu works', async function (assert) {
const controlPanel = await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchViewId: false,
searchMenuTypes: ["filter"],
searchViewArch: `
<search>
<filter string="Foo" name="foo" domain="[('foo', '=', 'qsdf')]"/>
</search>
`,
});
await toggleFilterMenu(target);
assert.deepEqual(getFacetTexts(target), []);
assert.notOk(isItemSelected(target, "Foo"));
assert.deepEqual(getDomain(controlPanel), []);
assert.containsOnce(target, ".o_menu_item[role=menuitemcheckbox]");
assert.deepEqual(target.querySelector(".o_menu_item").ariaChecked, "false");
await toggleMenuItem(target, "Foo");
assert.deepEqual(target.querySelector(".o_menu_item").ariaChecked, "true");
assert.deepEqual(getFacetTexts(target), ["Foo"]);
assert.containsOnce(
target.querySelector(".o_searchview .o_searchview_facet"),
"span.fa.fa-filter.o_searchview_facet_label"
);
assert.ok(isItemSelected(target, "Foo"));
assert.deepEqual(getDomain(controlPanel), [["foo", "=", "qsdf"]]);
await toggleMenuItem(target, "Foo");
assert.deepEqual(getFacetTexts(target), []);
assert.notOk(isItemSelected(target, "Foo"));
assert.deepEqual(getDomain(controlPanel), []);
});
QUnit.test("filter by a date field using period works", async function (assert) {
assert.expect(57);
patchDate(2017, 2, 22, 1, 0, 0);
const controlPanel = await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchViewId: false,
searchMenuTypes: ["filter"],
searchViewArch: `
<search>
<filter string="Date" name="date_field" date="date_field"/>
</search>
`,
context: { search_default_date_field: 1 },
});
await toggleFilterMenu(target);
await toggleMenuItem(target, "Date");
const optionEls = target.querySelectorAll(".dropdown .o_item_option");
// default filter should be activated with the global default period 'this_month'
assert.deepEqual(getDomain(controlPanel), [
"&",
["date_field", ">=", "2017-03-01"],
["date_field", "<=", "2017-03-31"],
]);
assert.ok(isItemSelected(target, "Date"));
assert.ok(isOptionSelected(target, "Date", "March"));
// check option descriptions
const optionDescriptions = [...optionEls].map((e) => e.innerText.trim());
const expectedDescriptions = [
"March",
"February",
"January",
"Q4",
"Q3",
"Q2",
"Q1",
"2017",
"2016",
"2015",
];
assert.deepEqual(optionDescriptions, expectedDescriptions);
// check generated domains
const steps = [
{
toggledOption: "March",
resultingFacet: "Date: 2017",
selectedoptions: ["2017"],
domain: [
"&",
["date_field", ">=", "2017-01-01"],
["date_field", "<=", "2017-12-31"],
],
},
{
toggledOption: "February",
resultingFacet: "Date: February 2017",
selectedoptions: ["February", "2017"],
domain: [
"&",
["date_field", ">=", "2017-02-01"],
["date_field", "<=", "2017-02-28"],
],
},
{
toggledOption: "February",
resultingFacet: "Date: 2017",
selectedoptions: ["2017"],
domain: [
"&",
["date_field", ">=", "2017-01-01"],
["date_field", "<=", "2017-12-31"],
],
},
{
toggledOption: "January",
resultingFacet: "Date: January 2017",
selectedoptions: ["January", "2017"],
domain: [
"&",
["date_field", ">=", "2017-01-01"],
["date_field", "<=", "2017-01-31"],
],
},
{
toggledOption: "Q4",
resultingFacet: "Date: January 2017/Q4 2017",
selectedoptions: ["January", "Q4", "2017"],
domain: [
"|",
"&",
["date_field", ">=", "2017-01-01"],
["date_field", "<=", "2017-01-31"],
"&",
["date_field", ">=", "2017-10-01"],
["date_field", "<=", "2017-12-31"],
],
},
{
toggledOption: "January",
resultingFacet: "Date: Q4 2017",
selectedoptions: ["Q4", "2017"],
domain: [
"&",
["date_field", ">=", "2017-10-01"],
["date_field", "<=", "2017-12-31"],
],
},
{
toggledOption: "Q4",
resultingFacet: "Date: 2017",
selectedoptions: ["2017"],
domain: [
"&",
["date_field", ">=", "2017-01-01"],
["date_field", "<=", "2017-12-31"],
],
},
{
toggledOption: "Q1",
resultingFacet: "Date: Q1 2017",
selectedoptions: ["Q1", "2017"],
domain: [
"&",
["date_field", ">=", "2017-01-01"],
["date_field", "<=", "2017-03-31"],
],
},
{
toggledOption: "Q1",
resultingFacet: "Date: 2017",
selectedoptions: ["2017"],
domain: [
"&",
["date_field", ">=", "2017-01-01"],
["date_field", "<=", "2017-12-31"],
],
},
{
toggledOption: "2017",
selectedoptions: [],
domain: [],
},
{
toggledOption: "2017",
resultingFacet: "Date: 2017",
selectedoptions: ["2017"],
domain: [
"&",
["date_field", ">=", "2017-01-01"],
["date_field", "<=", "2017-12-31"],
],
},
{
toggledOption: "2016",
resultingFacet: "Date: 2016/2017",
selectedoptions: ["2017", "2016"],
domain: [
"|",
"&",
["date_field", ">=", "2016-01-01"],
["date_field", "<=", "2016-12-31"],
"&",
["date_field", ">=", "2017-01-01"],
["date_field", "<=", "2017-12-31"],
],
},
{
toggledOption: "2015",
resultingFacet: "Date: 2015/2016/2017",
selectedoptions: ["2017", "2016", "2015"],
domain: [
"|",
"&",
["date_field", ">=", "2015-01-01"],
["date_field", "<=", "2015-12-31"],
"|",
"&",
["date_field", ">=", "2016-01-01"],
["date_field", "<=", "2016-12-31"],
"&",
["date_field", ">=", "2017-01-01"],
["date_field", "<=", "2017-12-31"],
],
},
{
toggledOption: "March",
resultingFacet: "Date: March 2015/March 2016/March 2017",
selectedoptions: ["March", "2017", "2016", "2015"],
domain: [
"|",
"&",
["date_field", ">=", "2015-03-01"],
["date_field", "<=", "2015-03-31"],
"|",
"&",
["date_field", ">=", "2016-03-01"],
["date_field", "<=", "2016-03-31"],
"&",
["date_field", ">=", "2017-03-01"],
["date_field", "<=", "2017-03-31"],
],
},
];
for (const s of steps) {
await toggleMenuItemOption(target, "Date", s.toggledOption);
assert.deepEqual(getDomain(controlPanel), s.domain);
if (s.resultingFacet) {
assert.deepEqual(getFacetTexts(target), [s.resultingFacet]);
} else {
assert.deepEqual(getFacetTexts(target), []);
}
s.selectedoptions.forEach((option) => {
assert.ok(
isOptionSelected(target, "Date", option),
`at step ${steps.indexOf(s) + 1}, ${option} should be selected`
);
});
}
});
QUnit.test(
"filter by a date field using period works even in January",
async function (assert) {
assert.expect(5);
patchDate(2017, 0, 7, 3, 0, 0);
const controlPanel = await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchViewId: false,
searchMenuTypes: ["filter"],
searchViewArch: `
<search>
<filter string="Date" name="some_filter" date="date_field" default_period="last_month"/>
</search>
`,
context: { search_default_some_filter: 1 },
});
assert.deepEqual(getDomain(controlPanel), [
"&",
["date_field", ">=", "2016-12-01"],
["date_field", "<=", "2016-12-31"],
]);
assert.deepEqual(getFacetTexts(target), ["Date: December 2016"]);
await toggleFilterMenu(target);
await toggleMenuItem(target, "Date");
assert.ok(isItemSelected(target, "Date"));
assert.ok(isOptionSelected(target, "Date", "December"));
assert.ok(isOptionSelected(target, "Date", "2016"));
}
);
QUnit.test("`context` key in <filter> is used", async function (assert) {
assert.expect(1);
const controlPanel = await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchViewId: false,
searchMenuTypes: ["filter"],
searchViewArch: `
<search>
<filter string="Filter" name="some_filter" domain="[]" context="{'coucou_1': 1}"/>
</search>
`,
context: { search_default_some_filter: 1 },
});
assert.deepEqual(getContext(controlPanel), {
coucou_1: 1,
lang: "en",
tz: "taht",
uid: 7,
});
});
QUnit.test("Filter with JSON-parsable domain works", async function (assert) {
assert.expect(1);
const xml_domain = "[[&quot;foo&quot;,&quot;=&quot;,&quot;Gently Weeps&quot;]]";
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));
});
});
});

View file

@ -1,534 +0,0 @@
/** @odoo-module **/
import { browser } from "@web/core/browser/browser";
import { ControlPanel } from "@web/search/control_panel/control_panel";
import { getFixture, patchWithCleanup } from "../helpers/utils";
import {
getFacetTexts,
isItemSelected,
isOptionSelected,
makeWithSearch,
removeFacet,
setupControlPanelServiceRegistry,
toggleGroupByMenu,
toggleMenuItem,
toggleMenuItemOption,
} from "./helpers";
let target;
let serverData;
QUnit.module("Search", (hooks) => {
hooks.beforeEach(async () => {
serverData = {
models: {
foo: {
fields: {
bar: { string: "Bar", type: "many2one", relation: "partner" },
birthday: { string: "Birthday", type: "date", store: true, sortable: true },
date_field: { string: "Date", type: "date", store: true, sortable: true },
float_field: { string: "Float", type: "float", group_operator: "sum" },
foo: { string: "Foo", type: "char", store: true, sortable: true },
},
records: {},
},
},
views: {
"foo,false,search": `<search/>`,
},
};
setupControlPanelServiceRegistry();
patchWithCleanup(browser, {
setTimeout: (fn) => fn(),
clearTimeout: () => {},
});
target = getFixture();
});
QUnit.module("GroupByMenu");
QUnit.test(
"simple rendering with neither groupbys nor groupable fields",
async function (assert) {
assert.expect(3);
await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchMenuTypes: ["groupBy"],
searchViewId: false,
searchViewFields: {},
});
await toggleGroupByMenu(target);
assert.containsNone(target, ".o_menu_item");
assert.containsNone(target, ".dropdown-divider");
assert.containsNone(target, ".o_add_custom_group_menu");
}
);
QUnit.test("simple rendering with no groupby", async function (assert) {
assert.expect(3);
await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchMenuTypes: ["groupBy"],
searchViewId: false,
});
await toggleGroupByMenu(target);
assert.containsNone(target, ".o_menu_item");
assert.containsNone(target, ".dropdown-divider");
assert.containsOnce(target, ".o_add_custom_group_menu");
});
QUnit.test("simple rendering with a single groupby", async function (assert) {
await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchMenuTypes: ["groupBy"],
searchViewId: false,
searchViewArch: `
<search>
<filter string="Foo" name="group_by_foo" context="{'group_by': 'foo'}"/>
</search>
`,
});
await toggleGroupByMenu(target);
assert.containsOnce(target, ".o_menu_item");
const menuItem = target.querySelector(".o_menu_item");
assert.strictEqual(menuItem.innerText.trim(), "Foo");
assert.strictEqual(menuItem.getAttribute("role"), "menuitemcheckbox");
assert.strictEqual(menuItem.ariaChecked, "false");
assert.containsOnce(target, ".dropdown-divider");
assert.containsOnce(target, ".o_add_custom_group_menu");
});
QUnit.test('toggle a "simple" groupby in groupby menu works', async function (assert) {
const controlPanel = await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchMenuTypes: ["groupBy"],
searchViewId: false,
searchViewArch: `
<search>
<filter string="Foo" name="group_by_foo" context="{'group_by': 'foo'}"/>
</search>
`,
});
await toggleGroupByMenu(target);
assert.deepEqual(controlPanel.env.searchModel.groupBy, []);
assert.deepEqual(getFacetTexts(target), []);
assert.notOk(isItemSelected(target, "Foo"));
const menuItem = target.querySelector(".o_menu_item");
assert.strictEqual(menuItem.innerText.trim(), "Foo");
assert.strictEqual(menuItem.getAttribute("role"), "menuitemcheckbox");
assert.strictEqual(menuItem.ariaChecked, "false");
await toggleMenuItem(target, "Foo");
assert.strictEqual(menuItem.ariaChecked, "true");
assert.deepEqual(controlPanel.env.searchModel.groupBy, ["foo"]);
assert.deepEqual(getFacetTexts(target), ["Foo"]);
assert.containsOnce(
target.querySelector(".o_searchview .o_searchview_facet"),
"span.oi.oi-group.o_searchview_facet_label"
);
assert.ok(isItemSelected(target, "Foo"));
await toggleMenuItem(target, "Foo");
assert.deepEqual(controlPanel.env.searchModel.groupBy, []);
assert.deepEqual(getFacetTexts(target), []);
assert.notOk(isItemSelected(target, "Foo"));
});
QUnit.test('toggle a "simple" groupby quickly does not crash', async function (assert) {
assert.expect(1);
await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchMenuTypes: ["groupBy"],
searchViewId: false,
searchViewArch: `
<search>
<filter string="Foo" name="group_by_foo" context="{'group_by': 'foo'}"/>
</search>
`,
});
await toggleGroupByMenu(target);
toggleMenuItem(target, "Foo");
toggleMenuItem(target, "Foo");
assert.ok(true);
});
QUnit.test(
'remove a "Group By" facet properly unchecks groupbys in groupby menu',
async function (assert) {
assert.expect(6);
const controlPanel = await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchMenuTypes: ["groupBy"],
searchViewId: false,
searchViewArch: `
<search>
<filter string="Foo" name="group_by_foo" context="{'group_by': 'foo'}"/>
</search>
`,
context: { search_default_group_by_foo: 1 },
});
await toggleGroupByMenu(target);
assert.deepEqual(getFacetTexts(target), ["Foo"]);
assert.deepEqual(controlPanel.env.searchModel.groupBy, ["foo"]);
assert.ok(isItemSelected(target, "Foo"));
await removeFacet(target, "Foo");
assert.deepEqual(getFacetTexts(target), []);
assert.deepEqual(controlPanel.env.searchModel.groupBy, []);
await toggleGroupByMenu(target);
assert.notOk(isItemSelected(target, "Foo"));
}
);
QUnit.test("group by a date field using interval works", async function (assert) {
assert.expect(21);
const controlPanel = await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchMenuTypes: ["groupBy"],
searchViewId: false,
searchViewArch: `
<search>
<filter string="Date" name="date" context="{'group_by': 'date_field:week'}"/>
</search>
`,
context: { search_default_date: 1 },
});
await toggleGroupByMenu(target);
assert.deepEqual(controlPanel.env.searchModel.groupBy, ["date_field:week"]);
await toggleMenuItem(target, "Date");
assert.ok(isOptionSelected(target, "Date", "Week"));
assert.deepEqual(
[...target.querySelectorAll(".o_item_option")].map((el) => el.innerText),
["Year", "Quarter", "Month", "Week", "Day"]
);
const steps = [
{
description: "Year",
facetTexts: ["Date: Year>Date: Week"],
selectedoptions: ["Year", "Week"],
groupBy: ["date_field:year", "date_field:week"],
},
{
description: "Month",
facetTexts: ["Date: Year>Date: Month>Date: Week"],
selectedoptions: ["Year", "Month", "Week"],
groupBy: ["date_field:year", "date_field:month", "date_field:week"],
},
{
description: "Week",
facetTexts: ["Date: Year>Date: Month"],
selectedoptions: ["Year", "Month"],
groupBy: ["date_field:year", "date_field:month"],
},
{
description: "Month",
facetTexts: ["Date: Year"],
selectedoptions: ["Year"],
groupBy: ["date_field:year"],
},
{ description: "Year", facetTexts: [], selectedoptions: [], groupBy: [] },
];
for (const s of steps) {
await toggleMenuItemOption(target, "Date", s.description);
assert.deepEqual(controlPanel.env.searchModel.groupBy, s.groupBy);
assert.deepEqual(getFacetTexts(target), s.facetTexts);
s.selectedoptions.forEach((description) => {
assert.ok(isOptionSelected(target, "Date", description));
});
}
});
QUnit.test("interval options are correctly grouped and ordered", async function (assert) {
assert.expect(8);
await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchMenuTypes: ["groupBy"],
searchViewId: false,
searchViewArch: `
<search>
<filter string="Bar" name="bar" context="{'group_by': 'bar'}"/>
<filter string="Date" name="date" context="{'group_by': 'date_field'}"/>
<filter string="Foo" name="foo" context="{'group_by': 'foo'}"/>
</search>
`,
context: { search_default_bar: 1 },
});
assert.deepEqual(getFacetTexts(target), ["Bar"]);
// open menu 'Group By'
await toggleGroupByMenu(target);
// Open the groupby 'Date'
await toggleMenuItem(target, "Date");
// select option 'week'
await toggleMenuItemOption(target, "Date", "Week");
assert.deepEqual(getFacetTexts(target), ["Bar>Date: Week"]);
// select option 'day'
await toggleMenuItemOption(target, "Date", "Day");
assert.deepEqual(getFacetTexts(target), ["Bar>Date: Week>Date: Day"]);
// select option 'year'
await toggleMenuItemOption(target, "Date", "Year");
assert.deepEqual(getFacetTexts(target), ["Bar>Date: Year>Date: Week>Date: Day"]);
// select 'Foo'
await toggleMenuItem(target, "Foo");
assert.deepEqual(getFacetTexts(target), ["Bar>Date: Year>Date: Week>Date: Day>Foo"]);
// select option 'quarter'
await toggleMenuItem(target, "Date");
await toggleMenuItemOption(target, "Date", "Quarter");
assert.deepEqual(getFacetTexts(target), [
"Bar>Date: Year>Date: Quarter>Date: Week>Date: Day>Foo",
]);
// unselect 'Bar'
await toggleMenuItem(target, "Bar");
assert.deepEqual(getFacetTexts(target), [
"Date: Year>Date: Quarter>Date: Week>Date: Day>Foo",
]);
// unselect option 'week'
await toggleMenuItem(target, "Date");
await toggleMenuItemOption(target, "Date", "Week");
assert.deepEqual(getFacetTexts(target), ["Date: Year>Date: Quarter>Date: Day>Foo"]);
});
QUnit.test("default groupbys can be ordered", async function (assert) {
assert.expect(2);
const controlPanel = await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchMenuTypes: ["groupBy"],
searchViewId: false,
searchViewArch: `
<search>
<filter string="Birthday" name="birthday" context="{'group_by': 'birthday'}"/>
<filter string="Date" name="date" context="{'group_by': 'date_field:week'}"/>
</search>
`,
context: { search_default_birthday: 2, search_default_date: 1 },
});
// the default groupbys should be activated in the right order
assert.deepEqual(controlPanel.env.searchModel.groupBy, [
"date_field:week",
"birthday:month",
]);
assert.deepEqual(getFacetTexts(target), ["Date: Week>Birthday: Month"]);
});
QUnit.test("a separator in groupbys does not cause problems", async function (assert) {
assert.expect(23);
await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchMenuTypes: ["groupBy"],
searchViewId: false,
searchViewArch: `
<search>
<filter string="Date" name="coolName" context="{'group_by': 'date_field'}"/>
<separator/>
<filter string="Bar" name="superName" context="{'group_by': 'bar'}"/>
</search>
`,
});
await toggleGroupByMenu(target);
await toggleMenuItem(target, "Date");
await toggleMenuItemOption(target, "Date", "Day");
assert.ok(isItemSelected(target, "Date"));
assert.notOk(isItemSelected(target, "Bar"));
assert.ok(isOptionSelected(target, "Date", "Day"), "selected");
assert.deepEqual(getFacetTexts(target), ["Date: Day"]);
await toggleMenuItem(target, "Bar");
await toggleMenuItem(target, "Date");
assert.ok(isItemSelected(target, "Date"));
assert.ok(isItemSelected(target, "Bar"));
assert.ok(isOptionSelected(target, "Date", "Day"), "selected");
assert.deepEqual(getFacetTexts(target), ["Date: Day>Bar"]);
await toggleMenuItemOption(target, "Date", "Quarter");
assert.ok(isItemSelected(target, "Date"));
assert.ok(isItemSelected(target, "Bar"));
assert.ok(isOptionSelected(target, "Date", "Quarter"), "selected");
assert.ok(isOptionSelected(target, "Date", "Day"), "selected");
assert.deepEqual(getFacetTexts(target), ["Date: Quarter>Date: Day>Bar"]);
await toggleMenuItem(target, "Bar");
await toggleMenuItem(target, "Date");
assert.ok(isItemSelected(target, "Date"));
assert.notOk(isItemSelected(target, "Bar"));
assert.ok(isOptionSelected(target, "Date", "Quarter"), "selected");
assert.ok(isOptionSelected(target, "Date", "Day"), "selected");
assert.deepEqual(getFacetTexts(target), ["Date: Quarter>Date: Day"]);
await removeFacet(target);
assert.deepEqual(getFacetTexts(target), []);
await toggleGroupByMenu(target);
await toggleMenuItem(target, "Date");
assert.notOk(isItemSelected(target, "Date"));
assert.notOk(isItemSelected(target, "Bar"));
assert.notOk(isOptionSelected(target, "Date", "Quarter"), "selected");
assert.notOk(isOptionSelected(target, "Date", "Day"), "selected");
});
QUnit.test("falsy search default groupbys are not activated", async function (assert) {
assert.expect(2);
const controlPanel = await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchMenuTypes: ["groupBy"],
searchViewId: false,
searchViewArch: `
<search>
<filter string="Birthday" name="birthday" context="{'group_by': 'birthday'}"/>
<filter string="Date" name="date" context="{'group_by': 'foo'}"/>
</search>
`,
context: { search_default_birthday: false, search_default_foo: 0 },
});
assert.deepEqual(controlPanel.env.searchModel.groupBy, []);
assert.deepEqual(getFacetTexts(target), []);
});
QUnit.test(
"Custom group by menu is displayed when hideCustomGroupBy is not set",
async function (assert) {
await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchViewId: false,
searchViewArch: `
<search>
<filter string="Birthday" name="birthday" context="{'group_by': 'birthday'}"/>
<filter string="Date" name="date" context="{'group_by': 'foo'}"/>
</search>
`,
searchMenuTypes: ["groupBy"],
});
await toggleGroupByMenu(target);
assert.containsOnce(target, ".o_add_custom_group_menu");
}
);
QUnit.test(
"Custom group by menu is displayed when hideCustomGroupBy is false",
async function (assert) {
await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchViewId: false,
searchViewArch: `
<search>
<filter string="Birthday" name="birthday" context="{'group_by': 'birthday'}"/>
<filter string="Date" name="date" context="{'group_by': 'foo'}"/>
</search>
`,
hideCustomGroupBy: false,
searchMenuTypes: ["groupBy"],
});
await toggleGroupByMenu(target);
assert.containsOnce(target, ".o_add_custom_group_menu");
}
);
QUnit.test(
"Custom group by menu is displayed when hideCustomGroupBy is true",
async function (assert) {
await makeWithSearch({
serverData,
resModel: "foo",
Component: ControlPanel,
searchViewId: false,
searchViewArch: `
<search>
<filter string="Birthday" name="birthday" context="{'group_by': 'birthday'}"/>
<filter string="Date" name="date" context="{'group_by': 'foo'}"/>
</search>
`,
hideCustomGroupBy: true,
searchMenuTypes: ["groupBy"],
});
await toggleGroupByMenu(target);
assert.containsNone(target, ".o_add_custom_group_menu");
}
);
});

View file

@ -34,12 +34,12 @@ QUnit.module("GroupBy Class", {}, () => {
assert.expect(3);
try {
getGroupBy(":day");
} catch (_e) {
} catch {
assert.step("Error 1");
}
try {
getGroupBy("diay_name:yar");
} catch (_e) {
} catch {
assert.step("Error 2");
}
assert.verifySteps(["Error 1", "Error 2"]);
@ -56,22 +56,22 @@ QUnit.module("GroupBy Class", {}, () => {
assert.expect(5);
try {
getGroupBy("", fields);
} catch (_e) {
} catch {
assert.step("Error 1");
}
try {
getGroupBy("display_name:day", fields);
} catch (_e) {
} catch {
assert.step("Error 2");
}
try {
getGroupBy("diay_name:year", fields);
} catch (_e) {
} catch {
assert.step("Error 3");
}
try {
getGroupBy("diay_name:yar", fields);
} catch (_e) {
} catch {
assert.step("Error 4");
}
assert.verifySteps(["Error 1", "Error 2", "Error 3", "Error 4"]);
@ -91,7 +91,7 @@ QUnit.module("GroupBy Class", {}, () => {
assert.expect(2);
try {
getGroupBy("date_field:yar", fields);
} catch (_e) {
} catch {
assert.step("Error");
}
assert.verifySteps(["Error"]);

View file

@ -1,5 +1,6 @@
/** @odoo-module **/
import { Component, xml } from "@odoo/owl";
import { makeTestEnv } from "@web/../tests/helpers/mock_env";
import {
click,
@ -10,29 +11,38 @@ import {
triggerEvent,
triggerEvents,
} from "@web/../tests/helpers/utils";
import { commandService } from "@web/core/commands/command_service";
import { dialogService } from "@web/core/dialog/dialog_service";
import { fieldService } from "@web/core/field_service";
import { hotkeyService } from "@web/core/hotkeys/hotkey_service";
import { notificationService } from "@web/core/notifications/notification_service";
import { ormService } from "@web/core/orm_service";
import { popoverService } from "@web/core/popover/popover_service";
import { registry } from "@web/core/registry";
import { CustomFavoriteItem } from "@web/search/favorite_menu/custom_favorite_item";
import { CustomFavoriteItem } from "@web/search/custom_favorite_item/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 { nameService } from "@web/core/name_service";
import { datetimePickerService } from "@web/core/datetime/datetimepicker_service";
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("dialog", dialogService);
serviceRegistry.add("field", fieldService);
serviceRegistry.add("hotkey", hotkeyService);
serviceRegistry.add("name", nameService);
serviceRegistry.add("notification", notificationService);
serviceRegistry.add("orm", ormService);
serviceRegistry.add("popover", popoverService);
serviceRegistry.add("view", viewService);
serviceRegistry.add("dialog", dialogService);
serviceRegistry.add("command", commandService);
serviceRegistry.add("datetime_picker", datetimePickerService);
}
export function setupControlPanelFavoriteMenuRegistry() {
@ -63,22 +73,24 @@ export async function makeWithSearch(params) {
class Parent extends Component {
setup() {
this.withSearchProps = props;
this.componentProps = componentProps;
}
getDisplay(display) {
return Object.assign({}, display, componentProps.display);
getProps(search) {
const props = Object.assign({}, componentProps, {
context: search.context,
domain: search.domain,
groupBy: search.groupBy,
orderBy: search.orderBy,
comparison: search.comparison,
display: Object.assign({}, search.display, componentProps.display),
});
return filterPropsForComponent(params.Component, props);
}
}
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)"/>
<Component t-props="getProps(search)"/>
</WithSearch>
<MainComponentsContainer />
`;
@ -94,6 +106,40 @@ export async function makeWithSearch(params) {
return component;
}
/** This function is aim to be used only in the tests.
* It will filter the props that are needed by the Component.
* This is to avoid errors of props validation. This occurs for example, on ControlPanel tests.
* In production, View use WithSearch for the Controllers, and the Layout send only the props that
* need to the ControlPanel.
*
* @param {Component} Component
* @param {Object} props
* @returns {Object} filtered props
*/
function filterPropsForComponent(Component, props) {
// This if, can be removed once all the Components have the props defined
if (Component.props) {
let componentKeys = null;
if (Component.props instanceof Array) {
componentKeys = Component.props.map((x) => x.replace("?", ""));
} else {
componentKeys = Object.keys(Component.props);
}
if (componentKeys.includes("*")) {
return props;
} else {
return Object.keys(props)
.filter((k) => componentKeys.includes(k))
.reduce((o, k) => {
o[k] = props[k];
return o;
}, {});
}
} else {
return props;
}
}
function getUniqueChild(node) {
return Object.values(node.children)[0];
}
@ -102,7 +148,7 @@ function getNode(target) {
return target instanceof Component ? target.el : target;
}
function findItem(target, selector, finder = 0) {
export function findItem(target, selector, finder = 0) {
const el = getNode(target);
const elems = [...el.querySelectorAll(selector)];
if (Number.isInteger(finder)) {
@ -153,117 +199,57 @@ export function getMenuItemTexts(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];
export function getVisibleButtons(el) {
return [
...$(el).find(
[
"div.o_control_panel_breadcrumbs button:visible", // button in the breadcrumbs
"div.o_control_panel_actions button:visible", // buttons for list selection
].join(",")
),
];
}
/** 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"));
export async function openAddCustomFilterDialog(el) {
await click(findItem(el, `.o_filter_menu .o_menu_item.o_add_custom_filter`));
}
/** 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`));
el.querySelector(".o_add_custom_group_menu").value = fieldName;
await triggerEvent(el, ".o_add_custom_group_menu", "change");
}
export async function groupByMenu(el, fieldName) {
await toggleGroupByMenu(el);
await toggleAddCustomGroup(el);
await toggleSearchBarMenu(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`));
await click(findItem(el, `.o_favorite_menu .o_add_favorite`));
}
export async function editFavoriteName(el, name) {
const input = findItem(
el,
`.o_favorite_menu .o_add_favorite .dropdown-menu input[type="text"]`
`.o_favorite_menu .o_add_favorite + .o_accordion_values 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`));
await click(findItem(el, `.o_favorite_menu .o_add_favorite + .o_accordion_values button`));
}
/** Search bar */
@ -277,7 +263,7 @@ export function getFacetTexts(target) {
export async function removeFacet(el, facetFinder = 0) {
const facet = findItem(el, `div.o_searchview_facet`, facetFinder);
await click(facet.querySelector("i.o_facet_remove"));
await click(facet.querySelector(".o_facet_remove"));
}
export async function editSearch(el, value) {
@ -330,7 +316,11 @@ export async function editPager(el, value) {
* @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);
export async function toggleActionMenu(el) {
await click(el.querySelector(".o_cp_action_menus .dropdown-toggle"));
}
/** SearchBarMenu */
export async function toggleSearchBarMenu(el) {
await click(findItem(el, `.o_searchview_dropdown_toggler`));
}

File diff suppressed because it is too large Load diff

View file

@ -49,6 +49,12 @@ QUnit.module("Search", (hooks) => {
date_field: { string: "Date", type: "date", store: true, sortable: true },
float_field: { string: "Float", type: "float" },
bar: { string: "Bar", type: "many2one", relation: "partner" },
properties: {
string: "Properties",
type: "properties",
definition_record: "bar",
definition_record_field: "child_properties",
},
},
records: [],
},
@ -959,6 +965,44 @@ QUnit.module("Search", (hooks) => {
assert.deepEqual(model.domain, [["date_deadline", "<", "2021-09-17"]]);
});
QUnit.test("field tags with invisible attribute", async function (assert) {
const model = await makeSearchModel({
serverData,
searchViewArch: `
<search>
<field name="foo" invisible="context.get('abc')"/>
<field name="bar" invisible="context.get('def')"/>
<field name="float_field" invisible="1"/>
</search>
`,
context: { abc: true },
});
assert.deepEqual(
model.getSearchItems((f) => f.type === "field").map((item) => item.fieldName),
["bar"]
);
});
QUnit.test("filter tags with invisible attribute", async function (assert) {
const model = await makeSearchModel({
serverData,
searchViewArch: `
<search>
<filter name="filter1" string="Invisible ABC" domain="[]" invisible="context.get('abc')"/>
<filter name="filter2" string="Invisible DEF" domain="[]" invisible="context.get('def')"/>
<filter name="filter3" string="Always invisible" domain="[]" invisible="1"/>
</search>
`,
context: { abc: true },
});
assert.deepEqual(
model
.getSearchItems((item) => ["filter", "dateFilter"].includes(item.type))
.map((item) => item.name),
["filter2"]
);
});
QUnit.test("no search items created for search panel sections", async function (assert) {
const model = await makeSearchModel({
serverData,
@ -977,4 +1021,31 @@ QUnit.module("Search", (hooks) => {
assert.strictEqual(sections.length, 2);
assert.deepEqual(sanitizeSearchItems(model), []);
});
QUnit.test(
"a field of type 'properties' should not be accepted as a search_default",
async function (assert) {
const searchViewArch = `
<search>
<field name="properties"/>
</search>
`;
const model = await makeSearchModel({
serverData,
searchViewArch,
context: {
search_default_properties: true,
},
});
assert.deepEqual(sanitizeSearchItems(model), [
{
description: "Properties",
fieldName: "properties",
fieldType: "properties",
type: "field",
},
]);
}
);
});

View file

@ -12,16 +12,20 @@ import {
makeWithSearch,
setupControlPanelServiceRegistry,
switchView,
toggleFilterMenu,
toggleSearchBarMenu,
toggleMenuItem,
} from "@web/../tests/search/helpers";
import { createWebClient, doAction } from "@web/../tests/webclient/helpers";
import { registry } from "@web/core/registry";
import { FilterMenu } from "@web/search/filter_menu/filter_menu";
import { GroupByMenu } from "@web/search/group_by_menu/group_by_menu";
import { SearchBarMenu } from "@web/search/search_bar_menu/search_bar_menu";
import { SearchPanel } from "@web/search/search_panel/search_panel";
import { Component, xml } from "@odoo/owl";
import {
Component,
xml,
onWillStart as onWillStartOWL,
onWillUpdateProps as onWillUpdatePropsOWL,
} from "@odoo/owl";
const serviceRegistry = registry.category("services");
@ -107,13 +111,13 @@ function makeTestComponent({ onWillStart, onWillUpdateProps } = {}) {
let domain;
class TestComponent extends Component {
setup() {
owl.onWillStart(async () => {
onWillStartOWL(async () => {
if (onWillStart) {
await onWillStart();
}
domain = this.props.domain;
});
owl.onWillUpdateProps(async (nextProps) => {
onWillUpdatePropsOWL(async (nextProps) => {
if (onWillUpdateProps) {
await onWillUpdateProps();
}
@ -122,12 +126,11 @@ function makeTestComponent({ onWillStart, onWillUpdateProps } = {}) {
}
}
TestComponent.components = { FilterMenu, GroupByMenu, SearchPanel };
TestComponent.components = { SearchBarMenu, SearchPanel };
TestComponent.template = xml`
<div class="o_test_component">
<SearchPanel t-if="env.searchModel.display.searchPanel" />
<FilterMenu />
<GroupByMenu />
<SearchBarMenu />
</div>`;
return { TestComponent, getDomain: () => domain };
@ -947,9 +950,9 @@ QUnit.module("Search", (hooks) => {
// unfold agrolait
function getAgrolaitElement() {
return [
...target.querySelectorAll(".o_search_panel_category_value > header"),
].find((el) => el.innerText.includes("agrolait"));
return [...target.querySelectorAll(".o_search_panel_category_value > header")].find(
(el) => el.innerText.includes("agrolait")
);
}
await click(getAgrolaitElement());
@ -960,7 +963,7 @@ QUnit.module("Search", (hooks) => {
);
assert.containsN(target, ".o_search_panel_category_value", 5);
await toggleFilterMenu(target);
await toggleSearchBarMenu(target);
await toggleMenuItem(target, "True Domain");
assert.hasClass(
@ -987,11 +990,6 @@ QUnit.module("Search", (hooks) => {
});
await makeWithSearch({
serverData,
async mockRPC(route) {
if (route === "/web/dataset/search_read") {
await promise;
}
},
Component: TestComponent,
resModel: "partner",
searchViewId: false,
@ -1083,7 +1081,7 @@ QUnit.module("Search", (hooks) => {
// Case 2: search domain changed so we wait for the search panel once again
promise = makeDeferred();
await toggleFilterMenu(target);
await toggleSearchBarMenu(target);
await toggleMenuItem(target, 0);
assert.verifySteps([]);
@ -1285,7 +1283,7 @@ QUnit.module("Search", (hooks) => {
// trigger a reload and delay the get_filter
promise = makeDeferred();
await toggleFilterMenu(target);
await toggleSearchBarMenu(target);
await toggleMenuItem(target, 0);
assert.deepEqual(getDomain(), []);
@ -1434,7 +1432,7 @@ QUnit.module("Search", (hooks) => {
assert.verifySteps(["search_panel_select_range", "search_panel_select_multi_range"]);
// reload with another domain, so the filters should be reloaded
await toggleFilterMenu(target);
await toggleSearchBarMenu(target);
await toggleMenuItem(target, 0);
assert.verifySteps(["search_panel_select_multi_range"]);
@ -1476,7 +1474,7 @@ QUnit.module("Search", (hooks) => {
assert.verifySteps(["search_panel_select_range", "search_panel_select_multi_range"]);
// reload with another domain, so the filters should be reloaded
await toggleFilterMenu(target);
await toggleSearchBarMenu(target);
await toggleMenuItem(target, 0);
assert.verifySteps(["search_panel_select_multi_range"]);
@ -1533,7 +1531,7 @@ QUnit.module("Search", (hooks) => {
]);
// reload with another domain, so the categories 'state' and 'company_id' should be reloaded
await toggleFilterMenu(target);
await toggleSearchBarMenu(target);
await toggleMenuItem(target, 0);
assert.verifySteps(["search_panel_select_range", "state"]);
@ -1593,7 +1591,7 @@ QUnit.module("Search", (hooks) => {
assert.deepEqual(getCategoriesContent(target), ["All", "ABC", "DEF", "GHI"]);
// reload with another domain, so the category 'state' should be reloaded
await toggleFilterMenu(target);
await toggleSearchBarMenu(target);
await toggleMenuItem(target, 0);
assert.verifySteps([]);
@ -3350,7 +3348,7 @@ QUnit.module("Search", (hooks) => {
assert.verifySteps(["search_panel_select_range"]);
// select DEF in filter menu
await toggleFilterMenu(target);
await toggleSearchBarMenu(target);
await toggleMenuItem(target, "DEF");
assert.verifySteps(["search_panel_select_range"]);
@ -3401,7 +3399,7 @@ QUnit.module("Search", (hooks) => {
assert.deepEqual(getCategoriesContent(target), ["All", "ABC", "DEF", "GHI"]);
// select DEF in filter menu --> the external domain changes --> the values should be updated
await toggleFilterMenu(target);
await toggleSearchBarMenu(target);
await toggleMenuItem(target, "DEF");
assert.verifySteps(["search_panel_select_range"]);
@ -3462,4 +3460,74 @@ QUnit.module("Search", (hooks) => {
assert.verifySteps(["special_key", "special_key"]);
});
QUnit.test("Display message when no filter availible", async (assert) => {
serverData.models.partner.records = [];
serverData.models.company.records = [];
serverData.models.category.records = [];
const { TestComponent } = makeTestComponent();
await makeWithSearch({
serverData,
Component: TestComponent,
resModel: "partner",
searchViewId: false,
});
assert.containsOnce(
target,
".o_search_panel_empty_state",
"Search panel has the empty state container"
);
assert.containsN(
target,
".o_search_panel_empty_state button",
1,
"Empty state has the All button"
);
});
QUnit.test(
"Don't display empty state message when some filters are availible",
async (assert) => {
const { TestComponent } = makeTestComponent();
await makeWithSearch({
serverData,
Component: TestComponent,
resModel: "partner",
searchViewId: false,
});
assert.containsNone(
target,
".o_search_panel_empty_state",
"Search panel does not have the empty state container"
);
}
);
QUnit.test("search panel with sample data", async (assert) => {
serverData.models.partner.records = [];
serverData.views["partner,false,kanban"] = /* xml */ `
<kanban sample="1">
<templates>
<div t-name="kanban-box" class="oe_kanban_global_click">
<field name="foo"/>
</div>
</templates>
</kanban>`;
serverData.views["partner,false,list"] = /* xml */ `
<tree sample="1">
<field name="foo"/>
</tree>`;
const webclient = await createWebClient({ serverData });
await doAction(webclient, 1);
assert.deepEqual(getComputedStyle(getFilter(target, 0, "input")).pointerEvents, 'auto');
await switchView(target, "list");
assert.deepEqual(getComputedStyle(getFilter(target, 0, "input")).pointerEvents, 'auto');
});
});

View file

@ -1,25 +1,14 @@
/** @odoo-module **/
import { constructDateDomain } from "@web/search/utils/dates";
import { defaultLocalization } from "@web/../tests/helpers/mock_services";
import { patchDate, patchTimeZone, patchWithCleanup } from "@web/../tests/helpers/utils";
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";
import { constructDateDomain } from "@web/search/utils/dates";
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() {
@ -497,7 +486,7 @@ QUnit.module("Search", () => {
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" });
patchWithCleanup(translatedTerms, { Q2: "Deuxième trimestre de l'an de grâce" });
assert.deepEqual(
constructDateDomain(referenceMoment, "date_field", "date", [
"second_quarter",
@ -511,17 +500,12 @@ QUnit.module("Search", () => {
},
"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" })
);
patchWithCleanup(localization, { ...defaultLocalization, direction: "rtl" });
assert.deepEqual(
constructDateDomain(referenceMoment, "date_field", "date", [
"second_quarter",
@ -535,18 +519,13 @@ QUnit.module("Search", () => {
},
"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" });
patchWithCleanup(localization, { ...defaultLocalization, direction: "rtl" });
patchWithCleanup(translatedTerms, { Q2: "2e Trimestre" });
assert.deepEqual(
constructDateDomain(referenceMoment, "date_field", "date", [
"second_quarter",
@ -560,34 +539,5 @@ QUnit.module("Search", () => {
},
"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);
}
);
});

View file

@ -2,20 +2,18 @@
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 { SearchBarMenu } from "@web/search/search_bar_menu/search_bar_menu";
import { WithSearch } from "@web/search/with_search/with_search";
import { mount } from "../helpers/utils";
import {
getMenuItemTexts,
makeWithSearch,
setupControlPanelServiceRegistry,
toggleFilterMenu,
toggleGroupByMenu,
toggleSearchBarMenu,
toggleMenuItem,
} from "./helpers";
import { Component, onWillUpdateProps, onWillStart, useState, xml } from "@odoo/owl";
import { Component, onWillUpdateProps, onWillStart, useState, xml, useSubEnv } from "@odoo/owl";
let target;
let serverData;
@ -235,11 +233,10 @@ QUnit.module("Search", (hooks) => {
assert.expect(3);
class TestComponent extends Component {}
TestComponent.components = { FilterMenu, GroupByMenu };
TestComponent.components = { SearchBarMenu };
TestComponent.template = xml`
<div class="o_test_component">
<FilterMenu/>
<GroupByMenu/>
<SearchBarMenu/>
</div>
`;
@ -254,10 +251,10 @@ QUnit.module("Search", (hooks) => {
Component: TestComponent,
searchViewId: 1,
});
await toggleFilterMenu(target);
await toggleSearchBarMenu(target);
await assert.ok(getMenuItemTexts(target), ["True Domain"]);
await toggleGroupByMenu(target);
await toggleSearchBarMenu(target);
await assert.ok(getMenuItemTexts(target), ["Name"]);
}
);
@ -269,18 +266,18 @@ QUnit.module("Search", (hooks) => {
class TestComponent extends Component {
setup() {
owl.onWillStart(() => {
onWillStart(() => {
assert.deepEqual(this.props.domain, []);
});
owl.onWillUpdateProps((nextProps) => {
onWillUpdateProps((nextProps) => {
assert.deepEqual(nextProps.domain, [[1, "=", 1]]);
});
}
}
TestComponent.components = { FilterMenu };
TestComponent.components = { SearchBarMenu };
TestComponent.template = xml`
<div class="o_test_component">
<FilterMenu/>
<SearchBarMenu/>
</div>
`;
@ -290,7 +287,7 @@ QUnit.module("Search", (hooks) => {
Component: TestComponent,
searchViewId: 1,
});
await toggleFilterMenu(target);
await toggleSearchBarMenu(target);
await toggleMenuItem(target, "True domain");
}
);
@ -315,7 +312,7 @@ QUnit.module("Search", (hooks) => {
class Parent extends Component {
setup() {
owl.useSubEnv({ config: {} });
useSubEnv({ config: {} });
this.searchState = useState({
resModel: "animal",
domain: [["type", "=", "carnivorous"]],
@ -363,7 +360,7 @@ QUnit.module("Search", (hooks) => {
class Parent extends Component {
setup() {
owl.useSubEnv({ config: {} });
useSubEnv({ config: {} });
this.searchState = useState({
resModel: "animal",
domain: [["type", "=", "carnivorous"]],