[IMP] web: add pager to new framework
Before this commit, Pager was not implemented in the new framework. Part-of: odoo/odoo#76472
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useAutofocus } from "../utils/hooks";
|
||||
|
||||
const { Component } = owl;
|
||||
const { useExternalListener, useRef, useState } = owl.hooks;
|
||||
|
||||
/**
|
||||
* Pager
|
||||
*
|
||||
* The pager goes from 1 to total (included).
|
||||
* The current value is minimum if limit === 1 or the interval:
|
||||
* [minimum, minimum + limit[ if limit > 1].
|
||||
* The value can be manually changed by clicking on the pager value and giving
|
||||
* an input matching the pattern: min[,max] (in which the comma can be a dash
|
||||
* or a semicolon).
|
||||
* The pager also provides two buttons to quickly change the current page (next
|
||||
* or previous).
|
||||
* @extends Component
|
||||
*/
|
||||
export class Pager extends Component {
|
||||
setup() {
|
||||
this.state = useState({
|
||||
isEditing: false,
|
||||
isDisabled: false,
|
||||
});
|
||||
|
||||
this.inputRef = useRef("input");
|
||||
useExternalListener(document, "mousedown", this.onClickAway, { capture: true });
|
||||
|
||||
this.forceFocus = useAutofocus();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get minimum() {
|
||||
return this.props.offset + 1;
|
||||
}
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get maximum() {
|
||||
return Math.min(this.props.offset + this.props.limit, this.props.total);
|
||||
}
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
get value() {
|
||||
const parts = [this.minimum];
|
||||
if (this.props.limit > 1) {
|
||||
parts.push(this.maximum);
|
||||
}
|
||||
return parts.join("-");
|
||||
}
|
||||
/**
|
||||
* @returns {boolean} true iff there is only one page
|
||||
*/
|
||||
get isSinglePage() {
|
||||
return this.minimum === 1 && this.maximum === this.props.total;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {-1 | 1} direction
|
||||
*/
|
||||
navigate(direction) {
|
||||
let minimum = this.props.offset + this.props.limit * direction;
|
||||
if (minimum >= this.props.total) {
|
||||
minimum = 0;
|
||||
} else if (minimum < 0 && this.props.limit === 1) {
|
||||
minimum = this.props.total - 1;
|
||||
} else if (minimum < 0 && this.props.limit > 1) {
|
||||
minimum = this.props.total - (this.props.total % this.props.limit || this.props.limit);
|
||||
}
|
||||
|
||||
this.update(minimum, this.props.limit);
|
||||
}
|
||||
/**
|
||||
* @param {string} value
|
||||
* @returns {{ minimum: number, maximum: number }}
|
||||
*/
|
||||
parse(value) {
|
||||
let [minimum, maximum] = value.trim().split(/\s*[\-\s,;]\s*/);
|
||||
const clamp = (value) => Math.min(Math.max(value, 1), this.props.total);
|
||||
return {
|
||||
minimum: clamp(parseInt(minimum, 10)) - 1,
|
||||
maximum: clamp(maximum ? parseInt(maximum, 10) : minimum),
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @param {string} value
|
||||
*/
|
||||
setValue(value) {
|
||||
const { minimum, maximum } = this.parse(value);
|
||||
|
||||
if (!isNaN(minimum) && !isNaN(maximum) && minimum < maximum) {
|
||||
this.update(minimum, maximum - minimum);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {number} offset
|
||||
* @param {number} limit
|
||||
*/
|
||||
async update(offset, limit) {
|
||||
this.state.isDisabled = true;
|
||||
await this.props.onUpdate({ offset, limit });
|
||||
this.state.isDisabled = false;
|
||||
this.state.isEditing = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onClickAway(ev) {
|
||||
if (ev.target !== this.inputRef.el) {
|
||||
this.state.isEditing = false;
|
||||
}
|
||||
}
|
||||
onInputBlur() {
|
||||
this.state.isEditing = false;
|
||||
}
|
||||
/**
|
||||
* @param {Event} ev
|
||||
*/
|
||||
onInputChange(ev) {
|
||||
this.setValue(ev.target.value);
|
||||
if (!this.state.isDisabled) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {KeyboardEvent} ev
|
||||
*/
|
||||
onInputKeydown(ev) {
|
||||
switch (ev.key) {
|
||||
case "Enter":
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.setValue(ev.currentTarget.value);
|
||||
break;
|
||||
case "Escape":
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.state.isEditing = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
onValueClick() {
|
||||
if (this.props.isEditable && !this.state.isEditing && !this.state.isDisabled) {
|
||||
this.forceFocus();
|
||||
this.state.isEditing = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Pager.template = "web.Pager";
|
||||
|
||||
Pager.defaultProps = {
|
||||
isEditable: true,
|
||||
withAccessKey: true,
|
||||
};
|
||||
Pager.props = {
|
||||
offset: Number,
|
||||
limit: Number,
|
||||
total: Number,
|
||||
onUpdate: Function,
|
||||
isEditable: Boolean,
|
||||
withAccessKey: Boolean,
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
.o_pager {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.o_pager_counter {
|
||||
min-width: $o-statbutton-height;
|
||||
}
|
||||
|
||||
.o_pager_value {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
span.o_pager_value {
|
||||
border-bottom: 1px solid transparent;
|
||||
}
|
||||
|
||||
input.o_pager_value {
|
||||
text-align: right;
|
||||
width: 60px;
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="web.Pager" owl="1">
|
||||
<nav class="o_pager" aria-label="Pager">
|
||||
<span class="o_pager_counter mr-2" t-on-click.stop="">
|
||||
<t t-if="state.isEditing">
|
||||
<input type="text" class="o_pager_value o_input" t-ref="input" autofocus="" t-att-value="value" t-on-blur="onInputBlur" t-on-change="onInputChange" t-on-keydown.stop="onInputKeydown" />
|
||||
</t>
|
||||
<t t-else="">
|
||||
<span class="o_pager_value" t-esc="value" t-on-click="onValueClick" />
|
||||
</t>
|
||||
<span> / </span>
|
||||
<span class="o_pager_limit" t-esc="props.total" />
|
||||
</span>
|
||||
<span class="btn-group" aria-atomic="true">
|
||||
<!-- accesskeys not wanted in X2Many widgets -->
|
||||
<button type="button" class="fa fa-chevron-left btn btn-secondary o_pager_previous px-2 rounded-left" aria-label="Previous" data-tooltip="Previous" tabindex="-1" t-att-data-hotkey="props.withAccessKey ? 'p' : false" t-att-disabled="state.isDisabled or isSinglePage" t-on-click.stop="navigate(-1)" />
|
||||
<button type="button" class="fa fa-chevron-right btn btn-secondary o_pager_next px-2 rounded-right" aria-label="Next" data-tooltip="Next" tabindex="-1" t-att-data-hotkey="props.withAccessKey ? 'n' : false" t-att-disabled="state.isDisabled or isSinglePage" t-on-click.stop="navigate(1)" />
|
||||
</span>
|
||||
</nav>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -219,7 +219,7 @@ odoo.define('web.Pager', function (require) {
|
||||
validate: Function,
|
||||
withAccessKey: Boolean,
|
||||
};
|
||||
Pager.template = 'web.Pager';
|
||||
Pager.template = 'web.legacy.Pager';
|
||||
|
||||
return Pager;
|
||||
});
|
||||
|
||||
@@ -292,7 +292,7 @@
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="web.Pager" owl="1">
|
||||
<t t-name="web.legacy.Pager" owl="1">
|
||||
<nav class="o_pager" aria-label="Pager">
|
||||
<span class="o_pager_counter mr-2" t-on-click.stop="">
|
||||
<input t-if="state.editing" type="text"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { Pager } from "@web/core/pager/pager";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { ComparisonMenu } from "../comparison_menu/comparison_menu";
|
||||
import { FavoriteMenu } from "../favorite_menu/favorite_menu";
|
||||
@@ -114,5 +115,12 @@ export class ControlPanel extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
ControlPanel.components = { ComparisonMenu, FavoriteMenu, FilterMenu, GroupByMenu, SearchBar };
|
||||
ControlPanel.components = {
|
||||
ComparisonMenu,
|
||||
FavoriteMenu,
|
||||
FilterMenu,
|
||||
GroupByMenu,
|
||||
Pager,
|
||||
SearchBar,
|
||||
};
|
||||
ControlPanel.template = "web.ControlPanel";
|
||||
|
||||
@@ -78,15 +78,6 @@
|
||||
padding-left: 5px;
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
|
||||
.o_pager {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.o_pager_counter {
|
||||
min-width: $o-statbutton-height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .o_cp_switch_buttons {
|
||||
@@ -117,22 +108,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.o_pager_value {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
span.o_pager_value {
|
||||
border-bottom: 1px solid transparent;
|
||||
}
|
||||
|
||||
input.o_pager_value {
|
||||
text-align: right;
|
||||
width: 60px;
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
.o_control_panel {
|
||||
display: none;
|
||||
|
||||
@@ -28,7 +28,12 @@
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<div class="o_cp_pager"/>
|
||||
<div class="o_cp_pager" role="search">
|
||||
<t t-if="env.config.getPagerProps">
|
||||
<t t-set="pagerProps" t-value="env.config.getPagerProps()" />
|
||||
<Pager t-if="pagerProps.total > 0" t-props="pagerProps" />
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<t t-if="(env.config.viewSwitcherEntries or []).length">
|
||||
<nav class="btn-group o_cp_switch_buttons">
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
const { useEnv, useSubEnv } = owl.hooks;
|
||||
|
||||
/**
|
||||
* @typedef PagerUpdateParams
|
||||
* @property {number} offset
|
||||
* @property {number} limit
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef PagerProps
|
||||
* @property {number} offset
|
||||
* @property {number} limit
|
||||
* @property {number} total
|
||||
* @property {(params: PagerUpdateParams) => any} onUpdate
|
||||
* @property {boolean} [isEditable]
|
||||
* @property {boolean} [withAccessKey]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {() => PagerProps} getProps
|
||||
*/
|
||||
export function usePager(getProps) {
|
||||
const env = useEnv();
|
||||
|
||||
useSubEnv({
|
||||
config: {
|
||||
...env.config,
|
||||
getPagerProps: getProps,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { Pager } from "@web/core/pager/pager";
|
||||
import { registerCleanup } from "../helpers/cleanup";
|
||||
import {
|
||||
click,
|
||||
triggerEvent,
|
||||
makeDeferred,
|
||||
nextTick,
|
||||
getFixture,
|
||||
triggerEvents,
|
||||
} from "../helpers/utils";
|
||||
|
||||
const { Component, mount } = owl;
|
||||
const { useState } = owl.hooks;
|
||||
const { xml } = owl.tags;
|
||||
|
||||
class PagerController extends Component {
|
||||
setup() {
|
||||
this.state = useState({ ...this.props });
|
||||
}
|
||||
async updateProps(nextProps) {
|
||||
Object.assign(this.state, nextProps);
|
||||
await nextTick();
|
||||
}
|
||||
}
|
||||
PagerController.template = xml`<Pager t-props="state" />`;
|
||||
PagerController.components = { Pager };
|
||||
|
||||
async function makePager(props) {
|
||||
const pager = await mount(PagerController, { target: getFixture(), props });
|
||||
registerCleanup(() => {
|
||||
pager.destroy();
|
||||
});
|
||||
return pager;
|
||||
}
|
||||
|
||||
QUnit.module("Components", () => {
|
||||
QUnit.module("Pager");
|
||||
|
||||
QUnit.test("basic interactions", async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const pager = await makePager({
|
||||
offset: 0,
|
||||
limit: 4,
|
||||
total: 10,
|
||||
onUpdate(data) {
|
||||
pager.updateProps(data);
|
||||
},
|
||||
});
|
||||
assert.strictEqual(
|
||||
pager.el.querySelector(`.o_pager_counter .o_pager_value`).textContent.trim(),
|
||||
"1-4",
|
||||
"currentMinimum should be set to 1"
|
||||
);
|
||||
|
||||
await click(pager.el.querySelector(`.o_pager button.o_pager_next`));
|
||||
assert.strictEqual(
|
||||
pager.el.querySelector(`.o_pager_counter .o_pager_value`).textContent.trim(),
|
||||
"5-8",
|
||||
"currentMinimum should now be 5"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("edit the pager", async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
const pager = await makePager({
|
||||
offset: 0,
|
||||
limit: 4,
|
||||
total: 10,
|
||||
onUpdate(data) {
|
||||
pager.updateProps(data);
|
||||
},
|
||||
});
|
||||
|
||||
await click(pager.el, ".o_pager_value");
|
||||
|
||||
assert.containsOnce(pager, "input", "the pager should contain an input");
|
||||
assert.strictEqual(
|
||||
pager.el.querySelector(`.o_pager_counter .o_pager_value`).value,
|
||||
"1-4",
|
||||
"the input should have correct value"
|
||||
);
|
||||
|
||||
// change the limit
|
||||
const input = pager.el.querySelector(`.o_pager_counter input.o_pager_value`);
|
||||
input.value = "1-6";
|
||||
await triggerEvents(input, null, ["change", "blur"]);
|
||||
|
||||
assert.containsNone(pager, "input", "the pager should not contain an input anymore");
|
||||
assert.strictEqual(
|
||||
pager.el.querySelector(`.o_pager_counter .o_pager_value`).textContent.trim(),
|
||||
"1-6",
|
||||
"the limit should have been updated"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("keydown on pager with same value", async function (assert) {
|
||||
assert.expect(7);
|
||||
|
||||
const pager = await makePager({
|
||||
offset: 0,
|
||||
limit: 4,
|
||||
total: 10,
|
||||
onUpdate() {
|
||||
assert.step("pager-changed");
|
||||
},
|
||||
});
|
||||
|
||||
// Enter edit mode
|
||||
await click(pager.el, ".o_pager_value");
|
||||
|
||||
assert.containsOnce(pager.el, "input");
|
||||
assert.strictEqual(pager.el.querySelector(`.o_pager_counter .o_pager_value`).value, "1-4");
|
||||
assert.verifySteps([]);
|
||||
|
||||
// Exit edit mode
|
||||
await triggerEvent(pager.el, "input", "keydown", { key: "Enter" });
|
||||
|
||||
assert.containsNone(pager.el, "input");
|
||||
assert.strictEqual(
|
||||
pager.el.querySelector(`.o_pager_counter .o_pager_value`).textContent.trim(),
|
||||
"1-4"
|
||||
);
|
||||
assert.verifySteps(["pager-changed"]);
|
||||
});
|
||||
|
||||
QUnit.test("pager value formatting", async function (assert) {
|
||||
assert.expect(8);
|
||||
|
||||
const pager = await makePager({
|
||||
offset: 0,
|
||||
limit: 4,
|
||||
total: 10,
|
||||
onUpdate(data) {
|
||||
pager.updateProps(data);
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(
|
||||
pager.el.querySelector(`.o_pager_counter .o_pager_value`).textContent.trim(),
|
||||
"1-4",
|
||||
"Initial value should be correct"
|
||||
);
|
||||
|
||||
async function inputAndAssert(inputValue, expected, reason) {
|
||||
await click(pager.el.querySelector(`.o_pager_counter .o_pager_value`));
|
||||
const inputEl = pager.el.querySelector(`.o_pager_counter input.o_pager_value`);
|
||||
inputEl.value = inputValue;
|
||||
await triggerEvents(inputEl, null, ["change", "blur"]);
|
||||
assert.strictEqual(
|
||||
pager.el.querySelector(`.o_pager_counter .o_pager_value`).textContent.trim(),
|
||||
expected,
|
||||
`Pager value should be "${expected}" when given "${inputValue}": ${reason}`
|
||||
);
|
||||
}
|
||||
|
||||
await inputAndAssert("4-4", "4", "values are squashed when minimum = maximum");
|
||||
await inputAndAssert("1-11", "1-10", "maximum is floored to total when out of range");
|
||||
await inputAndAssert("20-15", "10", "combination of the 2 assertions above");
|
||||
await inputAndAssert("6-5", "10", "fallback to previous value when minimum > maximum");
|
||||
await inputAndAssert(
|
||||
"definitelyValidNumber",
|
||||
"10",
|
||||
"fallback to previous value if not a number"
|
||||
);
|
||||
await inputAndAssert(
|
||||
" 1 , 2 ",
|
||||
"1-2",
|
||||
"value is normalized and accepts several separators"
|
||||
);
|
||||
await inputAndAssert("3 8", "3-8", "value accepts whitespace(s) as a separator");
|
||||
});
|
||||
|
||||
QUnit.test("pager disabling", async function (assert) {
|
||||
assert.expect(9);
|
||||
|
||||
const reloadPromise = makeDeferred();
|
||||
|
||||
const pager = await makePager({
|
||||
offset: 0,
|
||||
limit: 4,
|
||||
total: 10,
|
||||
// The goal here is to test the reactivity of the pager; in a
|
||||
// typical views, we disable the pager after switching page
|
||||
// to avoid switching twice with the same action (double click).
|
||||
async onUpdate(data) {
|
||||
// 1. Simulate a (long) server action
|
||||
await reloadPromise;
|
||||
// 2. Update the view with loaded data
|
||||
pager.updateProps(data);
|
||||
},
|
||||
});
|
||||
const pagerButtons = pager.el.querySelectorAll("button");
|
||||
|
||||
// Click twice
|
||||
await click(pager.el.querySelector(`.o_pager button.o_pager_next`));
|
||||
await click(pager.el.querySelector(`.o_pager button.o_pager_next`));
|
||||
// Try to edit the pager value
|
||||
await click(pager.el, ".o_pager_value");
|
||||
|
||||
assert.strictEqual(pagerButtons.length, 2, "the two buttons should be displayed");
|
||||
assert.ok(pagerButtons[0].disabled, "'previous' is disabled");
|
||||
assert.ok(pagerButtons[1].disabled, "'next' is disabled");
|
||||
assert.strictEqual(
|
||||
pager.el.querySelector(".o_pager_value").tagName,
|
||||
"SPAN",
|
||||
"pager edition is prevented"
|
||||
);
|
||||
|
||||
// Server action is done
|
||||
reloadPromise.resolve();
|
||||
await nextTick();
|
||||
|
||||
assert.strictEqual(pagerButtons.length, 2, "the two buttons should be displayed");
|
||||
assert.notOk(pagerButtons[0].disabled, "'previous' is enabled");
|
||||
assert.notOk(pagerButtons[1].disabled, "'next' is enabled");
|
||||
assert.strictEqual(
|
||||
pager.el.querySelector(`.o_pager_counter .o_pager_value`).textContent.trim(),
|
||||
"5-8",
|
||||
"value has been updated"
|
||||
);
|
||||
|
||||
await click(pager.el, ".o_pager_value");
|
||||
|
||||
assert.strictEqual(
|
||||
pager.el.querySelector(".o_pager_value").tagName,
|
||||
"INPUT",
|
||||
"pager edition is re-enabled"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("input interaction", async function (assert) {
|
||||
const pager = await makePager({
|
||||
offset: 0,
|
||||
limit: 4,
|
||||
total: 10,
|
||||
onUpdate(data) {
|
||||
pager.updateProps(data);
|
||||
},
|
||||
});
|
||||
|
||||
await click(pager.el, ".o_pager_value");
|
||||
assert.containsOnce(pager, "input", "the pager should contain an input");
|
||||
assert.strictEqual(
|
||||
pager.el.querySelector("input"),
|
||||
document.activeElement,
|
||||
"pager input is focused"
|
||||
);
|
||||
|
||||
await triggerEvent(pager.el, null, "mousedown");
|
||||
assert.containsNone(pager, "input", "the pager should not contain an input");
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { click } from "@web/../tests/helpers/utils";
|
||||
import { click, nextTick } from "@web/../tests/helpers/utils";
|
||||
import { ControlPanel } from "@web/search/control_panel/control_panel";
|
||||
import { makeWithSearch, setupControlPanelServiceRegistry } from "./helpers";
|
||||
|
||||
@@ -108,4 +108,31 @@ QUnit.module("Search", (hooks) => {
|
||||
await click(views[1]);
|
||||
assert.verifySteps(["kanban"]);
|
||||
});
|
||||
|
||||
QUnit.test("pager", async (assert) => {
|
||||
let pagerInfo = {
|
||||
offset: 0,
|
||||
limit: 10,
|
||||
total: 50,
|
||||
onUpdate: () => {},
|
||||
};
|
||||
|
||||
const controlPanel = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
config: {
|
||||
getPagerProps() {
|
||||
return pagerInfo;
|
||||
},
|
||||
},
|
||||
searchMenuTypes: [],
|
||||
});
|
||||
assert.containsOnce(controlPanel.el, ".o_pager");
|
||||
|
||||
pagerInfo.total = 0;
|
||||
controlPanel.render();
|
||||
await nextTick();
|
||||
assert.containsNone(controlPanel.el, ".o_pager");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,7 +10,7 @@ import { viewService } from "@web/views/view_service";
|
||||
import { actionService } from "@web/webclient/actions/action_service";
|
||||
import { registerCleanup } from "../helpers/cleanup";
|
||||
import { makeTestEnv } from "../helpers/mock_env";
|
||||
import { click, getFixture, mouseEnter, triggerEvent } from "../helpers/utils";
|
||||
import { click, getFixture, mouseEnter, triggerEvent, triggerEvents } from "../helpers/utils";
|
||||
|
||||
const serviceRegistry = registry.category("services");
|
||||
const favoriteMenuRegistry = registry.category("favoriteMenu");
|
||||
@@ -252,56 +252,3 @@ export const switchView = async (el, viewType) => {
|
||||
// const dropdown = findItem(el, `.o_cp_action_menus button`, menuFinder);
|
||||
// await click(dropdown);
|
||||
// }
|
||||
/////////////////////////////////////
|
||||
// Pager
|
||||
/////////////////////////////////////
|
||||
// /**
|
||||
// * @param {EventTarget} el
|
||||
// * @returns {Promise}
|
||||
// */
|
||||
// export async function pagerPrevious(el) {
|
||||
// await click(getNode(el).querySelector(`.o_pager button.o_pager_previous`));
|
||||
// }
|
||||
// /**
|
||||
// * @param {EventTarget} el
|
||||
// * @returns {Promise}
|
||||
// */
|
||||
// export async function pagerNext(el) {
|
||||
// await click(getNode(el).querySelector(`.o_pager button.o_pager_next`));
|
||||
// }
|
||||
// /**
|
||||
// * @param {EventTarget} el
|
||||
// * @returns {string}
|
||||
// */
|
||||
// export function getPagerValue(el) {
|
||||
// const pagerValue = getNode(el).querySelector(`.o_pager_counter .o_pager_value`);
|
||||
// switch (pagerValue.tagName) {
|
||||
// case 'INPUT':
|
||||
// return pagerValue.value;
|
||||
// case 'SPAN':
|
||||
// return pagerValue.innerText.trim();
|
||||
// }
|
||||
// }
|
||||
// /**
|
||||
// * @param {EventTarget} el
|
||||
// * @returns {string}
|
||||
// */
|
||||
// export function getPagerSize(el) {
|
||||
// return getNode(el).querySelector(`.o_pager_counter span.o_pager_limit`).innerText.trim();
|
||||
// }
|
||||
// /**
|
||||
// * @param {EventTarget} el
|
||||
// * @param {string} value
|
||||
// * @returns {Promise}
|
||||
// */
|
||||
// export async function setPagerValue(el, value) {
|
||||
// let pagerValue = getNode(el).querySelector(`.o_pager_counter .o_pager_value`);
|
||||
// if (pagerValue.tagName === 'SPAN') {
|
||||
// await click(pagerValue);
|
||||
// }
|
||||
// pagerValue = getNode(el).querySelector(`.o_pager_counter input.o_pager_value`);
|
||||
// if (!pagerValue) {
|
||||
// throw new Error("Pager value is being edited and cannot be changed.");
|
||||
// }
|
||||
// await editAndTrigger(pagerValue, value, ['change', 'blur']);
|
||||
// }
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { ControlPanel } from "@web/search/control_panel/control_panel";
|
||||
import { usePager } from "@web/search/pager_hook";
|
||||
import { click } from "../helpers/utils";
|
||||
import { makeWithSearch, setupControlPanelServiceRegistry } from "./helpers";
|
||||
|
||||
const { Component } = owl;
|
||||
const { useState } = owl.hooks;
|
||||
const { xml } = owl.tags;
|
||||
|
||||
let serverData;
|
||||
QUnit.module("Search", (hooks) => {
|
||||
hooks.beforeEach(async () => {
|
||||
serverData = {
|
||||
models: {
|
||||
foo: {
|
||||
fields: {},
|
||||
},
|
||||
},
|
||||
views: {
|
||||
"foo,false,search": `<search/>`,
|
||||
},
|
||||
};
|
||||
setupControlPanelServiceRegistry();
|
||||
});
|
||||
|
||||
QUnit.module("usePager");
|
||||
|
||||
QUnit.test("pager is correctly displayed", async (assert) => {
|
||||
class TestComponent extends Component {
|
||||
setup() {
|
||||
usePager(() => ({
|
||||
offset: 0,
|
||||
limit: 10,
|
||||
total: 50,
|
||||
onUpdate: () => {},
|
||||
}));
|
||||
}
|
||||
}
|
||||
TestComponent.components = { ControlPanel };
|
||||
TestComponent.template = xml`<ControlPanel />`;
|
||||
|
||||
const comp = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: TestComponent,
|
||||
searchMenuTypes: [],
|
||||
});
|
||||
|
||||
assert.containsOnce(comp, ".o_pager");
|
||||
assert.strictEqual(
|
||||
comp.el.querySelector(`.o_pager_counter .o_pager_value`).textContent.trim(),
|
||||
"1-10"
|
||||
);
|
||||
assert.strictEqual(
|
||||
comp.el.querySelector(`.o_pager_counter span.o_pager_limit`).textContent.trim(),
|
||||
"50"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("pager is correctly updated", async (assert) => {
|
||||
class TestComponent extends Component {
|
||||
setup() {
|
||||
this.state = useState({ offset: 0, limit: 10 });
|
||||
usePager(() => ({
|
||||
offset: this.state.offset,
|
||||
limit: this.state.limit,
|
||||
total: 50,
|
||||
onUpdate: (newState) => {
|
||||
Object.assign(this.state, newState);
|
||||
},
|
||||
}));
|
||||
}
|
||||
}
|
||||
TestComponent.components = { ControlPanel };
|
||||
TestComponent.template = xml`<ControlPanel />`;
|
||||
|
||||
const comp = await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: TestComponent,
|
||||
searchMenuTypes: [],
|
||||
});
|
||||
|
||||
assert.containsOnce(comp, ".o_pager");
|
||||
assert.strictEqual(
|
||||
comp.el.querySelector(`.o_pager_counter .o_pager_value`).textContent.trim(),
|
||||
"1-10"
|
||||
);
|
||||
assert.strictEqual(
|
||||
comp.el.querySelector(`.o_pager_counter span.o_pager_limit`).textContent.trim(),
|
||||
"50"
|
||||
);
|
||||
assert.deepEqual(comp.state, {
|
||||
offset: 0,
|
||||
limit: 10,
|
||||
});
|
||||
|
||||
await click(comp.el.querySelector(`.o_pager button.o_pager_next`));
|
||||
|
||||
assert.containsOnce(comp, ".o_pager");
|
||||
assert.strictEqual(
|
||||
comp.el.querySelector(`.o_pager_counter .o_pager_value`).textContent.trim(),
|
||||
"11-20"
|
||||
);
|
||||
assert.strictEqual(
|
||||
comp.el.querySelector(`.o_pager_counter span.o_pager_limit`).textContent.trim(),
|
||||
"50"
|
||||
);
|
||||
assert.deepEqual(comp.state, {
|
||||
offset: 10,
|
||||
limit: 10,
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user