[LINT] web
closes odoo/odoo#99295 Related: odoo/enterprise#30900 Signed-off-by: Samuel Degueldre <sad@odoo.com>
This commit is contained in:
@@ -379,13 +379,13 @@
|
||||
function findCycle(jobs) {
|
||||
// build dependency graph
|
||||
const dependencyGraph = new Map();
|
||||
for (let job of jobs) {
|
||||
for (const job of jobs) {
|
||||
dependencyGraph.set(job.name, job.dependencies);
|
||||
}
|
||||
|
||||
// helpers
|
||||
function visitJobs(jobs, visited = new Set()) {
|
||||
for (let job of jobs) {
|
||||
for (const job of jobs) {
|
||||
const result = visitJob(job, visited);
|
||||
if (result) {
|
||||
return result;
|
||||
|
||||
@@ -14,7 +14,7 @@ const COOKIE_TTL = 24 * 60 * 60 * 365;
|
||||
function parseCookieString(str) {
|
||||
const cookie = {};
|
||||
const parts = str.split("; ");
|
||||
for (let part of parts) {
|
||||
for (const part of parts) {
|
||||
const [key, value] = part.split("=");
|
||||
cookie[key] = value || "";
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ function cast(value) {
|
||||
function parseString(str) {
|
||||
const parts = str.split("&");
|
||||
const result = {};
|
||||
for (let part of parts) {
|
||||
for (const part of parts) {
|
||||
const [key, value] = part.split("=");
|
||||
const decoded = decodeURIComponent(value || "");
|
||||
result[key] = cast(decoded);
|
||||
|
||||
@@ -53,7 +53,7 @@ ColorList.template = "web.ColorList";
|
||||
ColorList.defaultProps = {
|
||||
forceExpanded: false,
|
||||
isExpanded: false,
|
||||
}
|
||||
};
|
||||
ColorList.props = {
|
||||
canToggle: { type: Boolean, optional: true },
|
||||
colors: Array,
|
||||
|
||||
@@ -94,7 +94,7 @@ const profilingService = {
|
||||
await setProfiling({ profile: !state.isEnabled });
|
||||
},
|
||||
async toggleCollector(collector) {
|
||||
let nextCollectors = state.collectors.slice();
|
||||
const nextCollectors = state.collectors.slice();
|
||||
const index = nextCollectors.indexOf(collector);
|
||||
if (index >= 0) {
|
||||
nextCollectors.splice(index, 1);
|
||||
|
||||
@@ -49,7 +49,7 @@ export class DomainSelectorLeafNode extends Component {
|
||||
return registry.category("domain_selector/fields").get(type, null);
|
||||
}
|
||||
getOperatorInfo(operator) {
|
||||
let op = this.getFieldComponent(this.fieldInfo.type)
|
||||
const op = this.getFieldComponent(this.fieldInfo.type)
|
||||
.getOperators()
|
||||
.find((op) =>
|
||||
op.matches({
|
||||
|
||||
@@ -73,7 +73,9 @@ export class RPCErrorDialog extends ErrorDialog {
|
||||
return;
|
||||
}
|
||||
// Fall back to a name based on the error type.
|
||||
if (!this.props.type) return;
|
||||
if (!this.props.type) {
|
||||
return;
|
||||
}
|
||||
switch (this.props.type) {
|
||||
case "server":
|
||||
this.title = this.env._t("Odoo Server Error");
|
||||
|
||||
@@ -88,7 +88,7 @@ export const errorService = {
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (let handler of registry.category("error_handlers").getAll()) {
|
||||
for (const handler of registry.category("error_handlers").getAll()) {
|
||||
if (handler(env, error, originalError)) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ export const localizationService = {
|
||||
|
||||
// FIXME We flatten the result of the python route.
|
||||
// Eventually, we want a new python route to return directly the good result.
|
||||
let terms = {};
|
||||
const terms = {};
|
||||
for (const addon of Object.keys(modules)) {
|
||||
for (const message of modules[addon].messages) {
|
||||
terms[message.id] = message.string;
|
||||
|
||||
@@ -44,7 +44,7 @@ class Macro {
|
||||
return;
|
||||
}
|
||||
const step = this.steps[this.currentIndex];
|
||||
let trigger = step.trigger;
|
||||
const trigger = step.trigger;
|
||||
if (trigger) {
|
||||
let el = null;
|
||||
if (typeof trigger === "function") {
|
||||
@@ -175,20 +175,20 @@ export class MacroEngine {
|
||||
}
|
||||
|
||||
advanceMacros() {
|
||||
let toDelete = [];
|
||||
for (let macro of this.macros) {
|
||||
const toDelete = [];
|
||||
for (const macro of this.macros) {
|
||||
macro.advance();
|
||||
if (macro.isComplete) {
|
||||
toDelete.push(macro);
|
||||
}
|
||||
}
|
||||
if (toDelete.length) {
|
||||
for (let macro of toDelete) {
|
||||
for (const macro of toDelete) {
|
||||
this.macros.delete(macro);
|
||||
}
|
||||
// recompute current interval, because it may need to be increased
|
||||
this.interval = Infinity;
|
||||
for (let macro of this.macros) {
|
||||
for (const macro of this.macros) {
|
||||
this.interval = Math.min(this.interval, macro.interval);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,9 @@ export class ModelSelector extends Component {
|
||||
if (!this.props.models) {
|
||||
this.models = await this._fetchAvailableModels();
|
||||
} else {
|
||||
this.models = await this.orm.call("ir.model", "display_name_for", [this.props.models]);
|
||||
this.models = await this.orm.call("ir.model", "display_name_for", [
|
||||
this.props.models,
|
||||
]);
|
||||
}
|
||||
|
||||
this.models = this.models.map((record) => ({
|
||||
@@ -73,13 +75,10 @@ export class ModelSelector extends Component {
|
||||
* Fetch the list of the models that can be
|
||||
* selected for the relational properties.
|
||||
*/
|
||||
async _fetchAvailableModels() {
|
||||
const result = await this.orm.call(
|
||||
'ir.model',
|
||||
'get_available_models',
|
||||
);
|
||||
async _fetchAvailableModels() {
|
||||
const result = await this.orm.call("ir.model", "get_available_models");
|
||||
return result || [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ModelSelector.template = "web.ModelSelector";
|
||||
|
||||
@@ -111,7 +111,7 @@ function ord2ymd(n) {
|
||||
};
|
||||
}
|
||||
|
||||
let leapyear = n1 === 3 && (n4 !== 24 || n100 == 3);
|
||||
const leapyear = n1 === 3 && (n4 !== 24 || n100 == 3);
|
||||
assert(leapyear == isLeap(year));
|
||||
let month = (n + 50) >> 5;
|
||||
let preceding = DAYS_BEFORE_MONTH[month] + (month > 2 && leapyear ? 1 : 0);
|
||||
@@ -176,7 +176,7 @@ function tmxxx(year, month, day, hour, minute, second, microsecond) {
|
||||
// for a datetime object, but we don't care about that here).
|
||||
// If day is out of bounds, what to do is arguable, but at least the
|
||||
// method here is principled and explainable.
|
||||
let dim = daysInMonth(year, month);
|
||||
const dim = daysInMonth(year, month);
|
||||
if (day < 1 || day > dim) {
|
||||
// Move day-1 days from the first of the month. First try to
|
||||
// get off cheap if we're only one day out of range (adjustments
|
||||
@@ -198,7 +198,7 @@ function tmxxx(year, month, day, hour, minute, second, microsecond) {
|
||||
++year;
|
||||
}
|
||||
} else {
|
||||
let r = ord2ymd(ymd2ord(year, month, 1) + (day - 1));
|
||||
const r = ord2ymd(ymd2ord(year, month, 1) + (day - 1));
|
||||
year = r.year;
|
||||
month = r.month;
|
||||
day = r.day;
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
PyTimeDelta,
|
||||
} from "./py_date";
|
||||
import { PY_DICT, toPyDict } from "./py_utils";
|
||||
import { parseArgs } from './py_parser';
|
||||
import { parseArgs } from "./py_parser";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Types
|
||||
@@ -201,7 +201,7 @@ function applyBinaryOp(ast, context) {
|
||||
}
|
||||
return left - right;
|
||||
}
|
||||
case "*":
|
||||
case "*": {
|
||||
const timeDeltaOnLeft = left instanceof PyTimeDelta;
|
||||
const timeDeltaOnRight = right instanceof PyTimeDelta;
|
||||
if (timeDeltaOnLeft || timeDeltaOnRight) {
|
||||
@@ -211,6 +211,7 @@ function applyBinaryOp(ast, context) {
|
||||
}
|
||||
|
||||
return left * right;
|
||||
}
|
||||
case "/":
|
||||
return left / right;
|
||||
case "%":
|
||||
@@ -303,28 +304,30 @@ export function evaluate(ast, context = {}) {
|
||||
return applyUnaryOp(ast, evalContext);
|
||||
case 7 /* BinaryOperator */:
|
||||
return applyBinaryOp(ast, evalContext);
|
||||
case 14 /* BooleanOperator */:
|
||||
case 14 /* BooleanOperator */: {
|
||||
const left = _evaluate(ast.left);
|
||||
if (ast.op === "and") {
|
||||
return isTrue(left) ? _evaluate(ast.right) : left;
|
||||
} else {
|
||||
return isTrue(left) ? left : _evaluate(ast.right);
|
||||
}
|
||||
}
|
||||
case 4 /* List */:
|
||||
case 10 /* Tuple */:
|
||||
return ast.value.map(_evaluate);
|
||||
case 11 /* Dictionary */:
|
||||
case 11 /* Dictionary */: {
|
||||
const dict = {};
|
||||
for (let key in ast.value) {
|
||||
for (const key in ast.value) {
|
||||
dict[key] = _evaluate(ast.value[key]);
|
||||
}
|
||||
dicts.add(dict);
|
||||
return dict;
|
||||
case 8 /* FunctionCall */:
|
||||
}
|
||||
case 8 /* FunctionCall */: {
|
||||
const fnValue = _evaluate(ast.fn);
|
||||
const args = ast.args.map(_evaluate);
|
||||
const kwargs = {};
|
||||
for (let kwarg in ast.kwargs) {
|
||||
for (const kwarg in ast.kwargs) {
|
||||
kwargs[kwarg] = _evaluate(ast.kwargs[kwarg]);
|
||||
}
|
||||
if (
|
||||
@@ -337,6 +340,7 @@ export function evaluate(ast, context = {}) {
|
||||
return fnValue.create(...args, kwargs);
|
||||
}
|
||||
return fnValue(...args, kwargs);
|
||||
}
|
||||
case 12 /* Lookup */: {
|
||||
const dict = _evaluate(ast.target);
|
||||
const key = _evaluate(ast.key);
|
||||
|
||||
@@ -153,7 +153,7 @@ function parsePrefix(current, tokens) {
|
||||
op: current.value,
|
||||
right: _parse(tokens, 50),
|
||||
};
|
||||
case "(":
|
||||
case "(": {
|
||||
const content = [];
|
||||
let isTuple = false;
|
||||
while (tokens[0] && !isSymbol(tokens[0], ")")) {
|
||||
@@ -175,7 +175,8 @@ function parsePrefix(current, tokens) {
|
||||
tokens.shift();
|
||||
isTuple = isTuple || content.length === 0;
|
||||
return isTuple ? { type: 10 /* Tuple */, value: content } : content[0];
|
||||
case "[":
|
||||
}
|
||||
case "[": {
|
||||
const value = [];
|
||||
while (tokens[0] && !isSymbol(tokens[0], "]")) {
|
||||
value.push(_parse(tokens, 0));
|
||||
@@ -192,6 +193,7 @@ function parsePrefix(current, tokens) {
|
||||
}
|
||||
tokens.shift();
|
||||
return { type: 4 /* List */, value };
|
||||
}
|
||||
case "{": {
|
||||
const dict = {};
|
||||
while (tokens[0] && !isSymbol(tokens[0], "}")) {
|
||||
@@ -279,7 +281,7 @@ function parseInfix(left, current, tokens) {
|
||||
return op;
|
||||
}
|
||||
switch (current.value) {
|
||||
case "(":
|
||||
case "(": {
|
||||
// function call
|
||||
const args = [];
|
||||
const kwargs = {};
|
||||
@@ -299,6 +301,7 @@ function parseInfix(left, current, tokens) {
|
||||
}
|
||||
tokens.shift();
|
||||
return { type: 8 /* FunctionCall */, fn: left, args, kwargs };
|
||||
}
|
||||
case "=":
|
||||
if (left.type === 5 /* Name */) {
|
||||
return {
|
||||
@@ -307,6 +310,7 @@ function parseInfix(left, current, tokens) {
|
||||
value: _parse(tokens, 10),
|
||||
};
|
||||
}
|
||||
break;
|
||||
case "[": {
|
||||
// lookup in dictionary
|
||||
const key = _parse(tokens);
|
||||
@@ -379,7 +383,7 @@ export function parseArgs(args, spec) {
|
||||
const last = args[args.length - 1];
|
||||
const unnamedArgs = typeof last === "object" ? args.slice(0, -1) : args;
|
||||
const kwargs = typeof last === "object" ? last : {};
|
||||
for (let [index, val] of unnamedArgs.entries()) {
|
||||
for (const [index, val] of unnamedArgs.entries()) {
|
||||
kwargs[spec[index]] = val;
|
||||
}
|
||||
return kwargs;
|
||||
|
||||
@@ -60,7 +60,7 @@ const directMap = {
|
||||
* @returns {string}
|
||||
*/
|
||||
function decodeStringLiteral(str, unicode) {
|
||||
let out = [];
|
||||
const out = [];
|
||||
let code;
|
||||
for (var i = 0; i < str.length; ++i) {
|
||||
if (str[i] !== "\\") {
|
||||
@@ -239,7 +239,7 @@ const strip = new RegExp("^" + Whitespace);
|
||||
*/
|
||||
export function tokenize(str) {
|
||||
const tokens = [];
|
||||
let max = str.length;
|
||||
const max = str.length;
|
||||
let start = 0;
|
||||
let end = 0;
|
||||
// /g flag makes repeated exec() have memory
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { bp } from "./py_parser";
|
||||
import { PyDate, PyDateTime } from './py_date';
|
||||
import { PyDate, PyDateTime } from "./py_date";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Types
|
||||
@@ -40,7 +40,7 @@ export function toPyValue(value) {
|
||||
return { type: 1, value };
|
||||
} else {
|
||||
const content = {};
|
||||
for (let key in value) {
|
||||
for (const key in value) {
|
||||
content[key] = toPyValue(value[key]);
|
||||
}
|
||||
return { type: 11 /* Dictionary */, value: content };
|
||||
@@ -79,7 +79,7 @@ export function formatAST(ast, lbp = 0) {
|
||||
}
|
||||
case 11 /* Dictionary */: {
|
||||
const pairs = [];
|
||||
for (let k in ast.value) {
|
||||
for (const k in ast.value) {
|
||||
pairs.push(`"${k}": ${formatAST(ast.value[k])}`);
|
||||
}
|
||||
return `{` + pairs.join(", ") + `}`;
|
||||
@@ -105,7 +105,7 @@ export function formatAST(ast, lbp = 0) {
|
||||
case 8 /* FunctionCall */: {
|
||||
const args = ast.args.map(formatAST);
|
||||
const kwargs = [];
|
||||
for (let kwarg in ast.kwargs) {
|
||||
for (const kwarg in ast.kwargs) {
|
||||
kwargs.push(`${kwarg} = ${formatAST(ast.kwargs[kwarg])}`);
|
||||
}
|
||||
const argStr = args.concat(kwargs).join(", ");
|
||||
|
||||
@@ -16,7 +16,7 @@ import { unaccent } from "./strings";
|
||||
function match(pattern, str) {
|
||||
let totalScore = 0;
|
||||
let currentScore = 0;
|
||||
let len = str.length;
|
||||
const len = str.length;
|
||||
let patternIndex = 0;
|
||||
|
||||
pattern = unaccent(pattern, false);
|
||||
|
||||
@@ -102,7 +102,7 @@ async function _startServices(env, toStart) {
|
||||
let service = null;
|
||||
const proms = [];
|
||||
while ((service = findNext())) {
|
||||
let name = service.name;
|
||||
const name = service.name;
|
||||
toStart.delete(service);
|
||||
const entries = (service.dependencies || []).map((dep) => [dep, services[dep]]);
|
||||
const dependencies = Object.fromEntries(entries);
|
||||
@@ -159,7 +159,7 @@ async function _startServices(env, toStart) {
|
||||
}
|
||||
|
||||
function findNext() {
|
||||
for (let s of toStart) {
|
||||
for (const s of toStart) {
|
||||
if (s.dependencies) {
|
||||
if (s.dependencies.every((d) => d in services)) {
|
||||
return s;
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from "./utils";
|
||||
import { makeLegacyActionManagerService } from "./backend_utils";
|
||||
import * as AbstractService from "web.AbstractService";
|
||||
import * as legacyEnv from "web.env";
|
||||
import legacyEnv from "web.env";
|
||||
import * as session from "web.session";
|
||||
import * as makeLegacyWebClientService from "web.pseudo_web_client";
|
||||
|
||||
@@ -46,7 +46,7 @@ export const legacySetupProm = new Promise((resolve) => {
|
||||
const legacyCommandService = makeLegacyCommandService(legacyEnv);
|
||||
serviceRegistry.add("legacy_command", legacyCommandService);
|
||||
serviceRegistry.add("legacy_dropdown", makeLegacyDropdownService(legacyEnv));
|
||||
const wowlToLegacyServiceMappers = registry.category('wowlToLegacyServiceMappers').getEntries();
|
||||
const wowlToLegacyServiceMappers = registry.category("wowlToLegacyServiceMappers").getEntries();
|
||||
for (const [legacyServiceName, wowlToLegacyServiceMapper] of wowlToLegacyServiceMappers) {
|
||||
serviceRegistry.add(legacyServiceName, wowlToLegacyServiceMapper(legacyEnv));
|
||||
}
|
||||
|
||||
@@ -1,50 +1,55 @@
|
||||
$(function() {
|
||||
$(function () {
|
||||
// Little eye
|
||||
$('body').on('mousedown', '.o_little_eye', function (ev) {
|
||||
$(ev.target).closest('.input-group').find('.form-control').prop("type",
|
||||
(i, old) => { return old === "text" ? "password" : "text"; }
|
||||
);
|
||||
$("body").on("mousedown", ".o_little_eye", function (ev) {
|
||||
$(ev.target)
|
||||
.closest(".input-group")
|
||||
.find(".form-control")
|
||||
.prop("type", (i, old) => {
|
||||
return old === "text" ? "password" : "text";
|
||||
});
|
||||
});
|
||||
// db modal
|
||||
$('body').on('click', '.o_database_action', function (ev) {
|
||||
$("body").on("click", ".o_database_action", function (ev) {
|
||||
ev.preventDefault();
|
||||
var db = $(ev.currentTarget).data('db');
|
||||
var target = $(ev.currentTarget).data('bsTarget');
|
||||
$(target).find('input[name=name]').val(db);
|
||||
const db = $(ev.currentTarget).data("db");
|
||||
const target = $(ev.currentTarget).data("bsTarget");
|
||||
$(target).find("input[name=name]").val(db);
|
||||
$(target).modal("show");
|
||||
});
|
||||
// close modal on submit
|
||||
$('.modal').on('submit', 'form', function (ev) {
|
||||
var form = $(this).closest('form')[0];
|
||||
$(".modal").on("submit", "form", function (ev) {
|
||||
const form = $(this).closest("form")[0];
|
||||
if (form && form.checkValidity && !form.checkValidity()) {
|
||||
return;
|
||||
}
|
||||
var modal = $(this).parentsUntil('body', '.modal');
|
||||
if (modal.hasClass('o_database_backup')) {
|
||||
$(modal).modal('hide');
|
||||
if (!$('.alert-backup-long').length) {
|
||||
$('.list-group').before("<div class='alert alert-info alert-backup-long'>The backup may take some time before being ready</div>");
|
||||
const modal = $(this).parentsUntil("body", ".modal");
|
||||
if (modal.hasClass("o_database_backup")) {
|
||||
$(modal).modal("hide");
|
||||
if (!$(".alert-backup-long").length) {
|
||||
$(".list-group").before(
|
||||
"<div class='alert alert-info alert-backup-long'>The backup may take some time before being ready</div>"
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// generate a random master password
|
||||
// removed l1O0 to avoid confusions
|
||||
var charset = "abcdefghijkmnpqrstuvwxyz23456789";
|
||||
var password = "";
|
||||
for (var i = 0, n = charset.length; i < 12; ++i) {
|
||||
const charset = "abcdefghijkmnpqrstuvwxyz23456789";
|
||||
let password = "";
|
||||
for (let i = 0, n = charset.length; i < 12; ++i) {
|
||||
password += charset.charAt(Math.floor(Math.random() * n));
|
||||
if (i === 3 || i === 7) {
|
||||
password += "-";
|
||||
}
|
||||
}
|
||||
var master_pwds = document.getElementsByClassName("generated_master_pwd");
|
||||
for (var i=0, len=master_pwds.length|0; i<len; i=i+1|0) {
|
||||
const master_pwds = document.getElementsByClassName("generated_master_pwd");
|
||||
for (let i = 0, len = master_pwds.length | 0; i < len; i = (i + 1) | 0) {
|
||||
master_pwds[i].innerText = password;
|
||||
}
|
||||
var master_pwd_inputs = document.getElementsByClassName("generated_master_pwd_input");
|
||||
for (var i=0, len=master_pwd_inputs.length|0; i<len; i=i+1|0) {
|
||||
const master_pwd_inputs = document.getElementsByClassName("generated_master_pwd_input");
|
||||
for (let i = 0, len = master_pwd_inputs.length | 0; i < len; i = (i + 1) | 0) {
|
||||
master_pwd_inputs[i].value = password;
|
||||
master_pwd_inputs[i].setAttribute('autocomplete', 'new-password');
|
||||
master_pwd_inputs[i].setAttribute("autocomplete", "new-password");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ odooExceptionTitleMap.forEach((title, exceptionName) => {
|
||||
registry.category("error_notifications").add(exceptionName, {
|
||||
title: title,
|
||||
type: "warning",
|
||||
sticky: true
|
||||
sticky: true,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,9 +20,9 @@ const sessionExpired = {
|
||||
{
|
||||
text: _t("Ok"),
|
||||
click: () => window.location.reload(true),
|
||||
close: true
|
||||
}
|
||||
]
|
||||
close: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
registry
|
||||
@@ -33,5 +33,5 @@ registry
|
||||
title: _t("Request timeout"),
|
||||
message: _t(
|
||||
"The operation was interrupted. This usually means that the current operation is taking too much time."
|
||||
)
|
||||
),
|
||||
});
|
||||
|
||||
@@ -210,7 +210,9 @@ export class SearchArchParser extends XMLParser {
|
||||
preSearchItem.fieldType = this.fields[fieldName].type;
|
||||
preSearchItem.defaultGeneratorIds = [DEFAULT_PERIOD];
|
||||
if (node.hasAttribute("default_period")) {
|
||||
preSearchItem.defaultGeneratorIds = node.getAttribute("default_period").split(',');
|
||||
preSearchItem.defaultGeneratorIds = node
|
||||
.getAttribute("default_period")
|
||||
.split(",");
|
||||
}
|
||||
} else {
|
||||
let stringRepr = "[]";
|
||||
|
||||
@@ -192,7 +192,7 @@ export class SearchBar extends Component {
|
||||
limit: 8,
|
||||
name: query.trim(),
|
||||
});
|
||||
let subItems = [];
|
||||
const subItems = [];
|
||||
if (options.length) {
|
||||
const operator = searchItem.operator || "=";
|
||||
for (const [value, label] of options) {
|
||||
|
||||
@@ -683,7 +683,7 @@ export class SearchModel extends EventBus {
|
||||
}
|
||||
|
||||
getDomainPart(partName) {
|
||||
let part = this.domainParts[partName] || null;
|
||||
const part = this.domainParts[partName] || null;
|
||||
if (part) {
|
||||
return deepCopy(part);
|
||||
}
|
||||
@@ -908,7 +908,9 @@ export class SearchModel extends EventBus {
|
||||
if (!yearSelected(this._getSelectedGeneratorIds(searchItemId))) {
|
||||
// Here we add 'this_year' as options if no option of type
|
||||
// year is already selected.
|
||||
const { defaultYearId } = this.optionGenerators.find((o) => o.id === generatorId);
|
||||
const { defaultYearId } = this.optionGenerators.find(
|
||||
(o) => o.id === generatorId
|
||||
);
|
||||
this.query.push({ searchItemId, generatorId: defaultYearId });
|
||||
}
|
||||
}
|
||||
@@ -1702,7 +1704,7 @@ export class SearchModel extends EventBus {
|
||||
const groups = [];
|
||||
for (const preGroup of preGroups) {
|
||||
const { queryElements, id } = preGroup;
|
||||
let activeItems = [];
|
||||
const activeItems = [];
|
||||
for (const queryElem of queryElements) {
|
||||
const { searchItemId } = queryElem;
|
||||
let activeItem = activeItems.find(({ searchItemId: id }) => id === searchItemId);
|
||||
|
||||
@@ -215,8 +215,8 @@ export function constructDateRange(params) {
|
||||
.plus(plusParam || {})
|
||||
.setZone("utc", { keepLocalTime: true });
|
||||
// compute domain
|
||||
let leftDate = date.startOf(granularity);
|
||||
let rightDate = date.endOf(granularity);
|
||||
const leftDate = date.startOf(granularity);
|
||||
const rightDate = date.endOf(granularity);
|
||||
let leftBound;
|
||||
let rightBound;
|
||||
if (fieldType === "date") {
|
||||
@@ -258,7 +258,7 @@ export function getComparisonParams(referenceMoment, selectedOptionIds, comparis
|
||||
if (comparisonOption.plusParam) {
|
||||
return [comparisonOption.plusParam, selectedOptions];
|
||||
}
|
||||
let plusParam = {};
|
||||
const plusParam = {};
|
||||
let globalGranularity = "year";
|
||||
if (selectedOptions.month) {
|
||||
globalGranularity = "month";
|
||||
@@ -335,11 +335,12 @@ export function getPeriodOptions(referenceMoment) {
|
||||
defaultYear = referenceMoment.set(option.setParam).year;
|
||||
break;
|
||||
case "month":
|
||||
case "year":
|
||||
case "year": {
|
||||
const date = referenceMoment.plus(option.plusParam);
|
||||
description = date.toFormat(option.format);
|
||||
defaultYear = date.year;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const setParam = getSetParam(option, referenceMoment);
|
||||
options.push({ id, groupNumber, description, defaultYear, setParam });
|
||||
|
||||
@@ -474,9 +474,10 @@ export class Record extends DataPoint {
|
||||
if (!value) {
|
||||
return true;
|
||||
}
|
||||
return value.every((propertyDefinition) =>
|
||||
!propertyDefinition.id ||
|
||||
(propertyDefinition.string && propertyDefinition.string.length)
|
||||
return value.every(
|
||||
(propertyDefinition) =>
|
||||
!propertyDefinition.id ||
|
||||
(propertyDefinition.string && propertyDefinition.string.length)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,11 +12,9 @@ export class CopyButton extends Component {
|
||||
}
|
||||
|
||||
showTooltip() {
|
||||
const closeTooltip = this.popover.add(
|
||||
this.button.el,
|
||||
Tooltip,
|
||||
{ tooltip: this.props.successText },
|
||||
);
|
||||
const closeTooltip = this.popover.add(this.button.el, Tooltip, {
|
||||
tooltip: this.props.successText,
|
||||
});
|
||||
browser.setTimeout(() => {
|
||||
closeTooltip();
|
||||
}, 800);
|
||||
|
||||
@@ -47,7 +47,9 @@ export class FileUploader extends Component {
|
||||
* @param {Event} ev
|
||||
*/
|
||||
async onFileChange(ev) {
|
||||
if (!ev.target.files.length) return;
|
||||
if (!ev.target.files.length) {
|
||||
return;
|
||||
}
|
||||
for (const file of ev.target.files) {
|
||||
if (file.size > this.maxUploadSize) {
|
||||
this.notification.add(
|
||||
|
||||
+1
-1
@@ -95,7 +95,7 @@ export class JournalDashboardGraphField extends Component {
|
||||
this.data[0].values.forEach(function (pt) {
|
||||
data.push(pt.value);
|
||||
labels.push(pt.label);
|
||||
let color =
|
||||
const color =
|
||||
pt.type === "past" ? "#ccbdc8" : pt.type === "future" ? "#a5d8d7" : "#ebebeb";
|
||||
backgroundColor.push(color);
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ const { useRef, useEffect } = owl;
|
||||
export function useNumpadDecimal() {
|
||||
const decimalPoint = localization.decimalPoint;
|
||||
const listeners = [];
|
||||
let ref = useRef("numpadDecimal");
|
||||
const ref = useRef("numpadDecimal");
|
||||
const handler = (ev) => {
|
||||
if (
|
||||
!([".", ","].includes(ev.key) && ev.code === "NumpadDecimal") ||
|
||||
|
||||
@@ -13,14 +13,7 @@ import { usePopover } from "@web/core/popover/popover_hook";
|
||||
import { sprintf } from "@web/core/utils/strings";
|
||||
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
|
||||
|
||||
const {
|
||||
Component,
|
||||
useRef,
|
||||
useState,
|
||||
useEffect,
|
||||
onWillStart,
|
||||
onWillUpdateProps,
|
||||
} = owl;
|
||||
const { Component, useRef, useState, useEffect, onWillStart, onWillUpdateProps } = owl;
|
||||
|
||||
export class PropertiesField extends Component {
|
||||
setup() {
|
||||
@@ -57,8 +50,7 @@ export class PropertiesField extends Component {
|
||||
if (this.openLastPropertyDefinition) {
|
||||
this.openLastPropertyDefinition = null;
|
||||
const propertiesList = this.propertiesList;
|
||||
const lastPropertyName =
|
||||
propertiesList[propertiesList.length - 1].name;
|
||||
const lastPropertyName = propertiesList[propertiesList.length - 1].name;
|
||||
const labels = this.propertiesRef.el.querySelectorAll(
|
||||
`.o_property_field[property-name="${lastPropertyName}"] .o_field_property_open_popover`
|
||||
);
|
||||
@@ -91,12 +83,8 @@ export class PropertiesField extends Component {
|
||||
* @returns {array}
|
||||
*/
|
||||
get propertiesList() {
|
||||
const propertiesValues = JSON.parse(
|
||||
JSON.stringify(this.props.value || [])
|
||||
);
|
||||
return propertiesValues.filter(
|
||||
(definition) => !definition.definition_deleted
|
||||
);
|
||||
const propertiesValues = JSON.parse(JSON.stringify(this.props.value || []));
|
||||
return propertiesValues.filter((definition) => !definition.definition_deleted);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,9 +164,7 @@ export class PropertiesField extends Component {
|
||||
*/
|
||||
onPropertyValueChange(propertyName, propertyValue) {
|
||||
const propertiesValues = this.propertiesList;
|
||||
propertiesValues.find(
|
||||
(property) => property.name === propertyName
|
||||
).value = propertyValue;
|
||||
propertiesValues.find((property) => property.name === propertyName).value = propertyValue;
|
||||
this.props.update(propertiesValues);
|
||||
}
|
||||
|
||||
@@ -203,8 +189,7 @@ export class PropertiesField extends Component {
|
||||
event.currentTarget,
|
||||
PropertyDefinition,
|
||||
{
|
||||
readonly:
|
||||
this.props.readonly || !this.state.canChangeDefinition,
|
||||
readonly: this.props.readonly || !this.state.canChangeDefinition,
|
||||
canChangeDefinition: this.state.canChangeDefinition,
|
||||
propertyDefinition: this.propertiesList.find(
|
||||
(property) => property.name === propertyName
|
||||
@@ -212,8 +197,7 @@ export class PropertiesField extends Component {
|
||||
context: this.context,
|
||||
onChange: this.onPropertyDefinitionChange.bind(this),
|
||||
onDelete: () => this.onPropertyDelete(propertyName),
|
||||
onPropertyMove: (direction) =>
|
||||
this.onPropertyMove(propertyName, direction),
|
||||
onPropertyMove: (direction) => this.onPropertyMove(propertyName, direction),
|
||||
},
|
||||
{
|
||||
preventClose: this.checkPopoverClose,
|
||||
@@ -249,10 +233,7 @@ export class PropertiesField extends Component {
|
||||
) {
|
||||
// restore the original name
|
||||
propertyDefinition.name = initialValues.name;
|
||||
} else if (
|
||||
initialValues &&
|
||||
initialValues.name === propertyDefinition.name
|
||||
) {
|
||||
} else if (initialValues && initialValues.name === propertyDefinition.name) {
|
||||
// generate a new new to reset all values on other records
|
||||
// store the new generated name to be able to restore it
|
||||
// if needed
|
||||
@@ -301,32 +282,22 @@ export class PropertiesField extends Component {
|
||||
|
||||
if (
|
||||
propertiesDefinitions.length &&
|
||||
propertiesDefinitions.some(
|
||||
(prop) => !prop.string || !prop.string.length
|
||||
)
|
||||
propertiesDefinitions.some((prop) => !prop.string || !prop.string.length)
|
||||
) {
|
||||
// do not allow to add new field until we set a label on the previous one
|
||||
this.propertiesRef.el
|
||||
.closest(".o_field_properties")
|
||||
.classList.add("o_field_invalid");
|
||||
this.propertiesRef.el.closest(".o_field_properties").classList.add("o_field_invalid");
|
||||
|
||||
this.notification.add(
|
||||
_lt("Please complete your properties before adding a new one"),
|
||||
{ type: "warning" }
|
||||
);
|
||||
this.notification.add(_lt("Please complete your properties before adding a new one"), {
|
||||
type: "warning",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.propertiesRef.el
|
||||
.closest(".o_field_properties")
|
||||
.classList.remove("o_field_invalid");
|
||||
this.propertiesRef.el.closest(".o_field_properties").classList.remove("o_field_invalid");
|
||||
|
||||
propertiesDefinitions.push({
|
||||
name: uuid(),
|
||||
string: sprintf(
|
||||
_lt("Property %s"),
|
||||
propertiesDefinitions.length + 1
|
||||
),
|
||||
string: sprintf(_lt("Property %s"), propertiesDefinitions.length + 1),
|
||||
type: "char",
|
||||
definition_changed: true,
|
||||
});
|
||||
@@ -381,8 +352,7 @@ export class PropertiesField extends Component {
|
||||
const targetPosition = targetElement.getBoundingClientRect();
|
||||
const popoverPosition = popover.getBoundingClientRect();
|
||||
|
||||
popover.style.top =
|
||||
targetPosition.top - popoverPosition.height - 10 + "px";
|
||||
popover.style.top = targetPosition.top - popoverPosition.height - 10 + "px";
|
||||
popover.style.left = targetPosition.left + "px";
|
||||
popover.style.position = "absolute";
|
||||
}
|
||||
@@ -392,13 +362,10 @@ export class PropertiesField extends Component {
|
||||
* and therefor update the properties definition.
|
||||
*/
|
||||
async _checkDefinitionAccess() {
|
||||
const definitionRecordId =
|
||||
this.props.record.data[this.definitionRecordField][0];
|
||||
const definitionRecordId = this.props.record.data[this.definitionRecordField][0];
|
||||
this.parentName = this.props.record.data[this.definitionRecordField][1];
|
||||
const definitionRecordModel =
|
||||
this.props.record.fields[this.definitionRecordField].relation;
|
||||
this.parentString =
|
||||
this.props.record.fields[this.definitionRecordField].string;
|
||||
const definitionRecordModel = this.props.record.fields[this.definitionRecordField].relation;
|
||||
this.parentString = this.props.record.fields[this.definitionRecordField].string;
|
||||
|
||||
if (!definitionRecordId || !definitionRecordModel) {
|
||||
return;
|
||||
@@ -425,7 +392,7 @@ export class PropertiesField extends Component {
|
||||
// initial properties values, if the type or the model changed, the
|
||||
// name will be regenerated in order to reset the value on the children
|
||||
this.initialValues = {};
|
||||
for (let propertiesValues of this.props.value || []) {
|
||||
for (const propertiesValues of this.props.value || []) {
|
||||
this.initialValues[propertiesValues.name] = {
|
||||
name: propertiesValues.name,
|
||||
type: propertiesValues.type,
|
||||
|
||||
@@ -45,9 +45,7 @@ export class PropertyDefinition extends Component {
|
||||
this._syncStateWithProps(propertyDefinition);
|
||||
|
||||
// update the state and fetch needed information
|
||||
onWillUpdateProps((newProps) =>
|
||||
this._syncStateWithProps(newProps.value)
|
||||
);
|
||||
onWillUpdateProps((newProps) => this._syncStateWithProps(newProps.value));
|
||||
|
||||
useEffect((event) => {
|
||||
// focus the property label, when we open the property definition
|
||||
@@ -56,8 +54,7 @@ export class PropertyDefinition extends Component {
|
||||
return;
|
||||
}
|
||||
this.labelFocused = true;
|
||||
const labelInput =
|
||||
this.propertyDefinitionRef.el.querySelectorAll("input")[0];
|
||||
const labelInput = this.propertyDefinitionRef.el.querySelectorAll("input")[0];
|
||||
if (labelInput) {
|
||||
labelInput.focus();
|
||||
}
|
||||
@@ -166,9 +163,7 @@ export class PropertyDefinition extends Component {
|
||||
const propertyDefinition = {
|
||||
...this.state.propertyDefinition,
|
||||
comodel: technical,
|
||||
default: modelChanged
|
||||
? false
|
||||
: this.state.propertyDefinition.default,
|
||||
default: modelChanged ? false : this.state.propertyDefinition.default,
|
||||
value: modelChanged ? false : this.state.propertyDefinition.value,
|
||||
domain: modelChanged ? false : this.state.propertyDefinition.domain,
|
||||
};
|
||||
@@ -202,9 +197,7 @@ export class PropertyDefinition extends Component {
|
||||
noCreate: true,
|
||||
multiSelect: false,
|
||||
resModel: this.state.propertyDefinition.comodel,
|
||||
domain: new Domain(
|
||||
this.state.propertyDefinition.domain || "[]"
|
||||
).toList(),
|
||||
domain: new Domain(this.state.propertyDefinition.domain || "[]").toList(),
|
||||
context: this.props.context || {},
|
||||
});
|
||||
}
|
||||
@@ -260,11 +253,7 @@ export class PropertyDefinition extends Component {
|
||||
// retrieve the model id and the model description from it's name
|
||||
// "res.partner" => (5, "Contact")
|
||||
try {
|
||||
const result = await this.orm.call(
|
||||
"ir.model",
|
||||
"display_name_for",
|
||||
[[newModel]],
|
||||
);
|
||||
const result = await this.orm.call("ir.model", "display_name_for", [[newModel]]);
|
||||
if (!result || !result.length) {
|
||||
return;
|
||||
}
|
||||
@@ -288,9 +277,7 @@ export class PropertyDefinition extends Component {
|
||||
*/
|
||||
async _updateMatchingRecordsCount() {
|
||||
if (this.state.resModel && this.state.resModel.length) {
|
||||
const domainList = new Domain(
|
||||
this.state.propertyDefinition.domain || "[]"
|
||||
).toList();
|
||||
const domainList = new Domain(this.state.propertyDefinition.domain || "[]").toList();
|
||||
|
||||
const result = await this.orm.call(
|
||||
this.state.propertyDefinition.comodel,
|
||||
|
||||
@@ -14,17 +14,14 @@ export class PropertyDefinitionSelection extends Component {
|
||||
// the new value in the props
|
||||
this.state = useState({ newOption: null });
|
||||
|
||||
this.propertyDefinitionSelectionRef = useRef(
|
||||
"propertyDefinitionSelection"
|
||||
);
|
||||
this.propertyDefinitionSelectionRef = useRef("propertyDefinitionSelection");
|
||||
this.addButtonRef = useRef("addButton");
|
||||
|
||||
useEffect(() => {
|
||||
// automatically give the focus to the new option if it is empty
|
||||
const inputs =
|
||||
this.propertyDefinitionSelectionRef.el.querySelectorAll(
|
||||
".o_field_property_selection_option input"
|
||||
);
|
||||
const inputs = this.propertyDefinitionSelectionRef.el.querySelectorAll(
|
||||
".o_field_property_selection_option input"
|
||||
);
|
||||
if (inputs && inputs.length && !inputs[inputs.length - 1].value) {
|
||||
inputs[inputs.length - 1].focus();
|
||||
}
|
||||
@@ -54,9 +51,7 @@ export class PropertyDefinitionSelection extends Component {
|
||||
*/
|
||||
get optionsVisible() {
|
||||
const options = this.options || [];
|
||||
return this.state.newOption
|
||||
? [...options, this.state.newOption]
|
||||
: options;
|
||||
return this.state.newOption ? [...options, this.state.newOption] : options;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------
|
||||
@@ -80,10 +75,7 @@ export class PropertyDefinitionSelection extends Component {
|
||||
const target = event.target;
|
||||
const newLabel = target.value;
|
||||
|
||||
if (
|
||||
this.options[optionIndex] &&
|
||||
this.options[optionIndex][1] === newLabel
|
||||
) {
|
||||
if (this.options[optionIndex] && this.options[optionIndex][1] === newLabel) {
|
||||
// do not update the props if we are already up to date
|
||||
// e.g. we pressed enter already and lost focus
|
||||
return;
|
||||
@@ -98,16 +90,10 @@ export class PropertyDefinitionSelection extends Component {
|
||||
options[optionIndex][1] = newLabel;
|
||||
}
|
||||
|
||||
const nonEmptyOptions = options.filter(
|
||||
(option) => option[1] && option[1].length
|
||||
);
|
||||
const nonEmptyOptions = options.filter((option) => option[1] && option[1].length);
|
||||
this.props.onOptionsChange(nonEmptyOptions);
|
||||
|
||||
if (
|
||||
this.state.newOption &&
|
||||
this.state.newOption[1] &&
|
||||
this.state.newOption[1].length
|
||||
) {
|
||||
if (this.state.newOption && this.state.newOption[1] && this.state.newOption[1].length) {
|
||||
// the new option has been propagated in the props
|
||||
this.state.newOption = null;
|
||||
}
|
||||
@@ -168,10 +154,7 @@ export class PropertyDefinitionSelection extends Component {
|
||||
.closest(".o_field_property_selection_option")
|
||||
.previousElementSibling.querySelector("input");
|
||||
previousInput.focus();
|
||||
} else if (
|
||||
event.key === "ArrowDown" &&
|
||||
optionIndex < this.optionsVisible.length - 1
|
||||
) {
|
||||
} else if (event.key === "ArrowDown" && optionIndex < this.optionsVisible.length - 1) {
|
||||
const nextInput = event.target
|
||||
.closest(".o_field_property_selection_option")
|
||||
.nextElementSibling.querySelector("input");
|
||||
@@ -190,9 +173,7 @@ export class PropertyDefinitionSelection extends Component {
|
||||
return;
|
||||
}
|
||||
const newValue = this.optionsVisible[optionIndex][0];
|
||||
this.props.onDefaultOptionChange(
|
||||
newValue !== this.props.default ? newValue : false
|
||||
);
|
||||
this.props.onDefaultOptionChange(newValue !== this.props.default ? newValue : false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,9 +38,7 @@ export class PropertyTags extends Component {
|
||||
|
||||
// Retrieve the tags label and color
|
||||
// ['a', 'b'] => [['a', 'A', 5], ['b', 'B', 6]]
|
||||
const value = this.props.tags.filter(
|
||||
(tag) => this.props.selectedTags.indexOf(tag[0]) >= 0
|
||||
);
|
||||
const value = this.props.tags.filter((tag) => this.props.selectedTags.indexOf(tag[0]) >= 0);
|
||||
|
||||
const canDeleteTag = !this.props.readonly && this.props.canChangeTags;
|
||||
|
||||
@@ -50,8 +48,7 @@ export class PropertyTags extends Component {
|
||||
id: tagId,
|
||||
text: tagLabel,
|
||||
colorIndex: tagColorIndex || 0,
|
||||
onClick: (event) =>
|
||||
this.onTagClick(event, tagId, tagColorIndex),
|
||||
onClick: (event) => this.onTagClick(event, tagId, tagColorIndex),
|
||||
onDelete: canDeleteTag && (() => this.onTagDelete(tagId)),
|
||||
};
|
||||
});
|
||||
@@ -94,9 +91,8 @@ export class PropertyTags extends Component {
|
||||
this.props.selectedTags.indexOf(tag[0]) < 0) &&
|
||||
(!request ||
|
||||
!request.length ||
|
||||
tag[1]
|
||||
.toLocaleLowerCase()
|
||||
.indexOf(request.toLocaleLowerCase()) >= 0)
|
||||
tag[1].toLocaleLowerCase().indexOf(request.toLocaleLowerCase()) >=
|
||||
0)
|
||||
);
|
||||
if (!tagsFiltered || !tagsFiltered.length) {
|
||||
// no result, ask the user if he want to create a new tag
|
||||
@@ -175,9 +171,7 @@ export class PropertyTags extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
const newValue = newLabel
|
||||
? newLabel.toLowerCase().replace(" ", "_")
|
||||
: "";
|
||||
const newValue = newLabel ? newLabel.toLowerCase().replace(" ", "_") : "";
|
||||
|
||||
const existingTag = this.props.tags.find((tag) => tag[0] === newValue);
|
||||
if (existingTag) {
|
||||
@@ -190,8 +184,7 @@ export class PropertyTags extends Component {
|
||||
// cycle trough colors
|
||||
const tagColor =
|
||||
this.props.tags && this.props.tags.length
|
||||
? (this.props.tags[this.props.tags.length - 1][2] + 1) %
|
||||
ColorList.COLORS.length
|
||||
? (this.props.tags[this.props.tags.length - 1][2] + 1) % ColorList.COLORS.length
|
||||
: parseInt(Math.random() * ColorList.COLORS.length);
|
||||
|
||||
const newTag = [newValue, newLabel, tagColor];
|
||||
@@ -220,9 +213,7 @@ export class PropertyTags extends Component {
|
||||
} else {
|
||||
// remove the tag from the options
|
||||
const availableTags = this.availableTags;
|
||||
this.props.onTagsChange(
|
||||
availableTags.filter((tag) => tag[0] !== deleteTag)
|
||||
);
|
||||
this.props.onTagsChange(availableTags.filter((tag) => tag[0] !== deleteTag));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,20 +5,13 @@ import { DropdownItem } from "@web/core/dropdown/dropdown_item";
|
||||
import { CheckBox } from "@web/core/checkbox/checkbox";
|
||||
import { DateTimePicker, DatePicker } from "@web/core/datepicker/datepicker";
|
||||
import { Domain } from "@web/core/domain";
|
||||
import {
|
||||
Many2XAutocomplete,
|
||||
useOpenMany2XRecord,
|
||||
} from "@web/views/fields/relational_utils";
|
||||
import { Many2XAutocomplete, useOpenMany2XRecord } from "@web/views/fields/relational_utils";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { TagsList } from "@web/views/fields/many2many_tags/tags_list";
|
||||
import { m2oTupleFromData } from "@web/views/fields/many2one/many2one_field";
|
||||
import { PropertyTags } from "./property_tags";
|
||||
import { AutoComplete } from "@web/core/autocomplete/autocomplete";
|
||||
import {
|
||||
formatFloat,
|
||||
formatInteger,
|
||||
formatMany2one,
|
||||
} from "@web/views/fields/formatters";
|
||||
import { formatFloat, formatInteger, formatMany2one } from "@web/views/fields/formatters";
|
||||
import { formatDate, formatDateTime } from "@web/core/l10n/dates";
|
||||
|
||||
const { Component } = owl;
|
||||
@@ -63,9 +56,7 @@ export class PropertyValue extends Component {
|
||||
// maybe the record display name has changed
|
||||
await record.load();
|
||||
const recordData = m2oTupleFromData(record.data);
|
||||
await this.onValueChange([
|
||||
{ id: recordData[0], name: recordData[1] },
|
||||
]);
|
||||
await this.onValueChange([{ id: recordData[0], name: recordData[1] }]);
|
||||
},
|
||||
fieldString: this.props.string,
|
||||
});
|
||||
@@ -125,13 +116,9 @@ export class PropertyValue extends Component {
|
||||
id: many2manyValue[0],
|
||||
text: many2manyValue[1],
|
||||
onClick: async () =>
|
||||
await this._openRecord(
|
||||
this.props.comodel,
|
||||
many2manyValue[0]
|
||||
),
|
||||
await this._openRecord(this.props.comodel, many2manyValue[0]),
|
||||
onDelete:
|
||||
!this.props.readonly &&
|
||||
(() => this.onMany2manyDelete(many2manyValue[0])),
|
||||
!this.props.readonly && (() => this.onMany2manyDelete(many2manyValue[0])),
|
||||
colorIndex: 0,
|
||||
};
|
||||
});
|
||||
@@ -171,9 +158,7 @@ export class PropertyValue extends Component {
|
||||
} else if (this.props.type === "date" && value) {
|
||||
return formatDate(value);
|
||||
} else if (this.props.type === "selection") {
|
||||
return this.props.selection.find(
|
||||
(option) => option[0] === value
|
||||
)[1];
|
||||
return this.props.selection.find((option) => option[0] === value)[1];
|
||||
} else if (this.props.type === "float") {
|
||||
return formatFloat(value);
|
||||
} else if (this.props.type === "integer") {
|
||||
@@ -196,9 +181,7 @@ export class PropertyValue extends Component {
|
||||
if (typeof newValue === "string") {
|
||||
newValue = DateTime.fromISO(newValue);
|
||||
}
|
||||
newValue = newValue
|
||||
.toUTC()
|
||||
.toFormat(DEFAULT_SERVER_DATETIME_FORMAT);
|
||||
newValue = newValue.toUTC().toFormat(DEFAULT_SERVER_DATETIME_FORMAT);
|
||||
} else if (this.props.type === "date") {
|
||||
if (typeof newValue === "string") {
|
||||
newValue = DateTime.fromISO(newValue);
|
||||
@@ -270,9 +253,7 @@ export class PropertyValue extends Component {
|
||||
onMany2manyDelete(many2manyId) {
|
||||
// deep copy
|
||||
const currentValue = JSON.parse(JSON.stringify(this.props.value || []));
|
||||
const newValue = currentValue.filter(
|
||||
(value) => value[0] !== many2manyId
|
||||
);
|
||||
const newValue = currentValue.filter((value) => value[0] !== many2manyId);
|
||||
this.props.onChange(newValue);
|
||||
}
|
||||
|
||||
@@ -287,12 +268,9 @@ export class PropertyValue extends Component {
|
||||
this.onValueChange(false);
|
||||
return;
|
||||
}
|
||||
const result = await this.orm.call(
|
||||
this.props.comodel,
|
||||
"name_create",
|
||||
[name],
|
||||
{ context: this.props.context }
|
||||
);
|
||||
const result = await this.orm.call(this.props.comodel, "name_create", [name], {
|
||||
context: this.props.context,
|
||||
});
|
||||
this.onValueChange([{ id: result[0], name: result[1] }]);
|
||||
}
|
||||
|
||||
@@ -307,12 +285,9 @@ export class PropertyValue extends Component {
|
||||
* @param {integer} recordId
|
||||
*/
|
||||
async _openRecord(recordModel, recordId) {
|
||||
const action = await this.orm.call(
|
||||
recordModel,
|
||||
"get_formview_action",
|
||||
[[recordId]],
|
||||
{ context: this.props.context }
|
||||
);
|
||||
const action = await this.orm.call(recordModel, "get_formview_action", [[recordId]], {
|
||||
context: this.props.context,
|
||||
});
|
||||
|
||||
this.action.doAction(action);
|
||||
}
|
||||
@@ -325,12 +300,9 @@ export class PropertyValue extends Component {
|
||||
* @returns {array} [record id, record name]
|
||||
*/
|
||||
async _nameGet(recordId) {
|
||||
const result = await this.orm.call(
|
||||
this.props.comodel,
|
||||
"name_get",
|
||||
[[recordId]],
|
||||
{ context: this.props.context }
|
||||
);
|
||||
const result = await this.orm.call(this.props.comodel, "name_get", [[recordId]], {
|
||||
context: this.props.context,
|
||||
});
|
||||
return result[0];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,7 +352,10 @@ export function useOpenMany2XRecord({
|
||||
const addDialog = useOwnedDialogs();
|
||||
const orm = useService("orm");
|
||||
|
||||
return async function openDialog({ resId = false, forceModel = null, title, context }, immediate = false) {
|
||||
return async function openDialog(
|
||||
{ resId = false, forceModel = null, title, context },
|
||||
immediate = false
|
||||
) {
|
||||
const model = forceModel || resModel;
|
||||
let viewId;
|
||||
if (resId !== false) {
|
||||
|
||||
@@ -150,7 +150,20 @@ export class FormCompiler extends ViewCompiler {
|
||||
: true,
|
||||
});
|
||||
if (child.tagName === "button" || child.children.tagName === "button") {
|
||||
child.classList.add("oe_stat_button", "btn-light", "flex-shrink-0", "mb-0", "py-0", "border-0", "border-start", "border-bottom", "rounded-0", "text-start", "text-nowrap", "text-capitalize");
|
||||
child.classList.add(
|
||||
"oe_stat_button",
|
||||
"btn-light",
|
||||
"flex-shrink-0",
|
||||
"mb-0",
|
||||
"py-0",
|
||||
"border-0",
|
||||
"border-start",
|
||||
"border-bottom",
|
||||
"rounded-0",
|
||||
"text-start",
|
||||
"text-nowrap",
|
||||
"text-capitalize"
|
||||
);
|
||||
}
|
||||
if (child.tagName === "field") {
|
||||
child.classList.add("d-inline-block", "mb-0");
|
||||
@@ -379,7 +392,8 @@ export class FormCompiler extends ViewCompiler {
|
||||
*/
|
||||
compileHeader(el, params) {
|
||||
const statusBar = createElement("div");
|
||||
statusBar.className = "o_form_statusbar position-relative d-flex justify-content-between border-bottom";
|
||||
statusBar.className =
|
||||
"o_form_statusbar position-relative d-flex justify-content-between border-bottom";
|
||||
const buttons = [];
|
||||
const others = [];
|
||||
for (const child of el.childNodes) {
|
||||
|
||||
@@ -34,10 +34,10 @@ export class FormRenderer extends Component {
|
||||
useBounceButton(useRef("compiled_view_root"), (target) => {
|
||||
return !record.isInEdition && !!target.closest(".oe_title, .o_inner_group");
|
||||
});
|
||||
this.uiService = useService('ui');
|
||||
this.uiService = useService("ui");
|
||||
this.onResize = useDebounced(this.render, 200);
|
||||
onMounted(() => browser.addEventListener('resize', this.onResize));
|
||||
onWillUnmount(() => browser.removeEventListener('resize', this.onResize));
|
||||
onMounted(() => browser.addEventListener("resize", this.onResize));
|
||||
onWillUnmount(() => browser.removeEventListener("resize", this.onResize));
|
||||
}
|
||||
|
||||
evalDomainFromRecord(record, expr) {
|
||||
|
||||
@@ -12,7 +12,7 @@ class KanbanExamplesNotebookTemplate extends Component {
|
||||
setup() {
|
||||
this.columns = [];
|
||||
const hasBullet = this.props.bullets && this.props.bullets.length;
|
||||
for (let title of this.props.columns) {
|
||||
for (const title of this.props.columns) {
|
||||
const col = { title, records: [] };
|
||||
this.columns.push(col);
|
||||
for (let i = 0; i < random(1, 5); i++) {
|
||||
|
||||
@@ -262,7 +262,12 @@ export class KanbanRenderer extends Component {
|
||||
if (!this.env.isSmall && group.isFolded) {
|
||||
classes.push("o_column_folded");
|
||||
}
|
||||
if (this.canResequenceGroups && group.value && !group.isFolded && !group.hasActiveProgressValue) {
|
||||
if (
|
||||
this.canResequenceGroups &&
|
||||
group.value &&
|
||||
!group.isFolded &&
|
||||
!group.hasActiveProgressValue
|
||||
) {
|
||||
classes.push("bg-100");
|
||||
}
|
||||
if (group.progressBars.length) {
|
||||
|
||||
@@ -1171,7 +1171,7 @@ export class ListRenderer extends Component {
|
||||
const { activeActions, cycleOnTab, list } = this.props;
|
||||
const row = cell.parentElement;
|
||||
const applyMultiEditBehavior = record && record.selected && list.model.multiEdit;
|
||||
const topReCreate = this.props.editable === 'top' && record.isNew;
|
||||
const topReCreate = this.props.editable === "top" && record.isNew;
|
||||
|
||||
if (
|
||||
applyMultiEditBehavior &&
|
||||
|
||||
@@ -24,7 +24,7 @@ export const pivotView = {
|
||||
buttonTemplate: "web.PivotView.Buttons",
|
||||
|
||||
props: (genericProps, view) => {
|
||||
let modelParams = {};
|
||||
const modelParams = {};
|
||||
if (genericProps.state) {
|
||||
modelParams.data = genericProps.state.data;
|
||||
modelParams.metaData = genericProps.state.metaData;
|
||||
|
||||
@@ -262,5 +262,5 @@ export function uuid() {
|
||||
const array = new Uint8Array(16);
|
||||
window.crypto.getRandomValues(array);
|
||||
// Uint8Array to hex
|
||||
return [...array].map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
return [...array].map((b) => b.toString(16).padStart(2, "0")).join("");
|
||||
}
|
||||
|
||||
@@ -214,8 +214,8 @@ export class View extends Component {
|
||||
actionMenus,
|
||||
} = props;
|
||||
|
||||
let loadView = !arch || (!actionMenus && loadActionMenus);
|
||||
let loadSearchView =
|
||||
const loadView = !arch || (!actionMenus && loadActionMenus);
|
||||
const loadSearchView =
|
||||
(searchViewId !== undefined && !searchViewArch) || (!irFilters && loadIrFilters);
|
||||
|
||||
let viewDescription = { viewId, resModel, type };
|
||||
@@ -322,7 +322,7 @@ export class View extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
let { noContentHelp } = props;
|
||||
const { noContentHelp } = props;
|
||||
if (noContentHelp) {
|
||||
viewProps.info.noContentHelp = noContentHelp;
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ export const viewService = {
|
||||
loadViewsOptions.mobile = true;
|
||||
}
|
||||
const { context, resModel, views } = params;
|
||||
let filteredContext = Object.fromEntries(
|
||||
const filteredContext = Object.fromEntries(
|
||||
Object.entries(context || {}).filter((k, v) => !String(k).startsWith("default_"))
|
||||
);
|
||||
const key = JSON.stringify([resModel, views, filteredContext, loadViewsOptions]);
|
||||
|
||||
@@ -14,7 +14,7 @@ export function displayNotificationAction(env, action) {
|
||||
title: params.title,
|
||||
type: params.type || "info",
|
||||
};
|
||||
let links = (params.links || []).map((link) => {
|
||||
const links = (params.links || []).map((link) => {
|
||||
return `<a href="${escape(link.url)}" target="_blank">${escape(link.label)}</a>`;
|
||||
});
|
||||
const message = owl.markup(sprintf(escape(params.message), ...links));
|
||||
|
||||
@@ -439,7 +439,7 @@
|
||||
}
|
||||
await testApp(app);
|
||||
} else {
|
||||
while (app = await getNextApp()) {
|
||||
while ((app = await getNextApp())) {
|
||||
await testApp(app);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ export const companyService = {
|
||||
} else if ("cids" in cookie.current) {
|
||||
cids = parseCompanyIds(cookie.current.cids);
|
||||
}
|
||||
let allowedCompanyIds = computeAllowedCompanyIds(cids);
|
||||
const allowedCompanyIds = computeAllowedCompanyIds(cids);
|
||||
|
||||
const stringCIds = allowedCompanyIds.join(",");
|
||||
router.replaceState({ cids: stringCIds }, { lock: true });
|
||||
|
||||
@@ -29,7 +29,7 @@ export class SettingsFormRenderer extends FormRenderer {
|
||||
}
|
||||
search(kind, value) {
|
||||
const regexp = new RegExp(escapeRegExp(this.searchState.value), "i");
|
||||
for (let x of labels[this.props.archInfo.arch]) {
|
||||
for (const x of labels[this.props.archInfo.arch]) {
|
||||
if (x[kind] === value) {
|
||||
if (regexp.test([x.label, x.groupTitle, x.groupTip].join())) {
|
||||
return true;
|
||||
|
||||
@@ -58,29 +58,28 @@ QUnit.module("Components", () => {
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("color click does not open the list if canToggle props is not given", async function (assert) {
|
||||
const selectedColorId = 0;
|
||||
await mountComponent(ColorList, {
|
||||
colors: [4, 5, 6],
|
||||
selectedColor: selectedColorId,
|
||||
onColorSelected: (colorId) => assert.step("color #" + colorId + " is selected"),
|
||||
});
|
||||
QUnit.test(
|
||||
"color click does not open the list if canToggle props is not given",
|
||||
async function (assert) {
|
||||
const selectedColorId = 0;
|
||||
await mountComponent(ColorList, {
|
||||
colors: [4, 5, 6],
|
||||
selectedColor: selectedColorId,
|
||||
onColorSelected: (colorId) => assert.step("color #" + colorId + " is selected"),
|
||||
});
|
||||
|
||||
assert.containsOnce(target, ".o_colorlist");
|
||||
assert.containsOnce(
|
||||
target,
|
||||
"button.o_colorlist_toggler",
|
||||
"only the toggler button is available"
|
||||
);
|
||||
assert.containsOnce(target, ".o_colorlist");
|
||||
assert.containsOnce(
|
||||
target,
|
||||
"button.o_colorlist_toggler",
|
||||
"only the toggler button is available"
|
||||
);
|
||||
|
||||
await click(target.querySelector(".o_colorlist button"));
|
||||
await click(target.querySelector(".o_colorlist button"));
|
||||
|
||||
assert.containsOnce(
|
||||
target,
|
||||
"button.o_colorlist_toggler",
|
||||
"button is still visible"
|
||||
);
|
||||
});
|
||||
assert.containsOnce(target, "button.o_colorlist_toggler", "button is still visible");
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("open the list of colors if canToggle props is given", async function (assert) {
|
||||
const selectedColorId = 0;
|
||||
|
||||
@@ -1518,7 +1518,7 @@ QUnit.test("generate new session id when opened", async (assert) => {
|
||||
mount(TestComponent, target, { env });
|
||||
const providers = [
|
||||
{
|
||||
provide: (env, {sessionId}) => {
|
||||
provide: (env, { sessionId }) => {
|
||||
lastSessionId = sessionId;
|
||||
return [];
|
||||
},
|
||||
@@ -1568,14 +1568,14 @@ QUnit.test("checks that href is correctly used", async (assert) => {
|
||||
],
|
||||
},
|
||||
];
|
||||
const config = {providers};
|
||||
const config = { providers };
|
||||
env.services.dialog.add(CommandPalette, {
|
||||
config,
|
||||
});
|
||||
patchWithCleanup(window, {
|
||||
open: (href) => {
|
||||
assert.step(href.toString());
|
||||
}
|
||||
},
|
||||
});
|
||||
await nextTick();
|
||||
await editSearchBar("@");
|
||||
|
||||
@@ -80,8 +80,8 @@ QUnit.module("Components", (hooks) => {
|
||||
// unpatch QUnit asap so any other errors can be caught by it
|
||||
unpatch(QUnit, "MainComponentsContainer QUnit patch");
|
||||
assert.verifySteps([
|
||||
"An error occured in the owl lifecycle (see this Error's \"cause\" property)",
|
||||
"BOOM"
|
||||
'An error occured in the owl lifecycle (see this Error\'s "cause" property)',
|
||||
"BOOM",
|
||||
]);
|
||||
|
||||
assert.equal(
|
||||
@@ -133,8 +133,8 @@ QUnit.module("Components", (hooks) => {
|
||||
// unpatch QUnit asap so any other errors can be caught by it
|
||||
unpatch(QUnit, "MainComponentsContainer QUnit patch");
|
||||
assert.verifySteps([
|
||||
"An error occured in the owl lifecycle (see this Error's \"cause\" property)",
|
||||
"BOOM"
|
||||
'An error occured in the owl lifecycle (see this Error\'s "cause" property)',
|
||||
"BOOM",
|
||||
]);
|
||||
assert.equal(
|
||||
target.querySelector(".o-main-components-container").innerHTML,
|
||||
|
||||
@@ -8,19 +8,16 @@ import { ModelSelector } from "@web/core/model_selector/model_selector";
|
||||
import { makeTestEnv } from "@web/../tests/helpers/mock_env";
|
||||
import { click, editInput, getFixture, mount, patchWithCleanup } from "@web/../tests/helpers/utils";
|
||||
|
||||
registry
|
||||
.category("mock_server")
|
||||
.add("ir.model/display_name_for", function (route, args) {
|
||||
const models = args.args[0];
|
||||
const records = this.models["ir.model"].records.filter((record) =>
|
||||
models.includes(record.model)
|
||||
);
|
||||
return records.map((record) => ({
|
||||
model: record.model,
|
||||
display_name: record.name,
|
||||
}));
|
||||
});
|
||||
|
||||
registry.category("mock_server").add("ir.model/display_name_for", function (route, args) {
|
||||
const models = args.args[0];
|
||||
const records = this.models["ir.model"].records.filter((record) =>
|
||||
models.includes(record.model)
|
||||
);
|
||||
return records.map((record) => ({
|
||||
model: record.model,
|
||||
display_name: record.name,
|
||||
}));
|
||||
});
|
||||
|
||||
const serviceRegistry = registry.category("services");
|
||||
|
||||
@@ -169,33 +166,42 @@ QUnit.test("model_selector: with more than 8 models", async function (assert) {
|
||||
assert.containsN(fixture, "li.o-autocomplete--dropdown-item", 8);
|
||||
});
|
||||
|
||||
QUnit.test("model_selector: search content is not applied when opening the autocomplete", async function (assert) {
|
||||
await mountModelSelector(["model_1", "model_2"], "_2");
|
||||
await openAutocomplete();
|
||||
assert.containsN(fixture, "li.o-autocomplete--dropdown-item", 2);
|
||||
});
|
||||
QUnit.test(
|
||||
"model_selector: search content is not applied when opening the autocomplete",
|
||||
async function (assert) {
|
||||
await mountModelSelector(["model_1", "model_2"], "_2");
|
||||
await openAutocomplete();
|
||||
assert.containsN(fixture, "li.o-autocomplete--dropdown-item", 2);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("model_selector: with search matching some records on technical name", async function (assert) {
|
||||
await mountModelSelector(["model_1", "model_2"]);
|
||||
await openAutocomplete();
|
||||
await editInput(fixture, ".o-autocomplete--input", "_2");
|
||||
assert.containsOnce(fixture, "li.o-autocomplete--dropdown-item");
|
||||
assert.strictEqual(
|
||||
fixture.querySelector("li.o-autocomplete--dropdown-item").innerText,
|
||||
"Model 2"
|
||||
);
|
||||
});
|
||||
QUnit.test(
|
||||
"model_selector: with search matching some records on technical name",
|
||||
async function (assert) {
|
||||
await mountModelSelector(["model_1", "model_2"]);
|
||||
await openAutocomplete();
|
||||
await editInput(fixture, ".o-autocomplete--input", "_2");
|
||||
assert.containsOnce(fixture, "li.o-autocomplete--dropdown-item");
|
||||
assert.strictEqual(
|
||||
fixture.querySelector("li.o-autocomplete--dropdown-item").innerText,
|
||||
"Model 2"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("model_selector: with search matching some records on business name", async function (assert) {
|
||||
await mountModelSelector(["model_1", "model_2"]);
|
||||
await openAutocomplete();
|
||||
await editInput(fixture, ".o-autocomplete--input", " 2");
|
||||
assert.containsOnce(fixture, "li.o-autocomplete--dropdown-item");
|
||||
assert.strictEqual(
|
||||
fixture.querySelector("li.o-autocomplete--dropdown-item").innerText,
|
||||
"Model 2"
|
||||
);
|
||||
});
|
||||
QUnit.test(
|
||||
"model_selector: with search matching some records on business name",
|
||||
async function (assert) {
|
||||
await mountModelSelector(["model_1", "model_2"]);
|
||||
await openAutocomplete();
|
||||
await editInput(fixture, ".o-autocomplete--input", " 2");
|
||||
assert.containsOnce(fixture, "li.o-autocomplete--dropdown-item");
|
||||
assert.strictEqual(
|
||||
fixture.querySelector("li.o-autocomplete--dropdown-item").innerText,
|
||||
"Model 2"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("model_selector: with search matching no record", async function (assert) {
|
||||
await mountModelSelector(["model_1", "model_2"]);
|
||||
|
||||
@@ -6,8 +6,7 @@ import { makeMockXHR } from "../../helpers/mock_services";
|
||||
import { ConnectionLostError, RPCError } from "@web/core/network/rpc_service";
|
||||
import { registerCleanup } from "../../helpers/cleanup";
|
||||
|
||||
QUnit.module("download", hooks => {
|
||||
|
||||
QUnit.module("download", (hooks) => {
|
||||
QUnit.test("handles connection error when behind a server", async (assert) => {
|
||||
assert.expect(1);
|
||||
|
||||
@@ -17,7 +16,7 @@ QUnit.module("download", hooks => {
|
||||
type: "text/html",
|
||||
};
|
||||
}
|
||||
let MockXHR = makeMockXHR("", send);
|
||||
const MockXHR = makeMockXHR("", send);
|
||||
|
||||
patchWithCleanup(
|
||||
browser,
|
||||
@@ -46,7 +45,7 @@ QUnit.module("download", hooks => {
|
||||
async function send() {
|
||||
return Promise.reject();
|
||||
}
|
||||
let MockXHR = makeMockXHR("", send);
|
||||
const MockXHR = makeMockXHR("", send);
|
||||
|
||||
patchWithCleanup(
|
||||
browser,
|
||||
@@ -84,12 +83,9 @@ QUnit.module("download", hooks => {
|
||||
|
||||
async function send() {
|
||||
this.status = 200;
|
||||
this.response = new Blob(
|
||||
[JSON.stringify(serverError)],
|
||||
{type: "text/html"}
|
||||
);
|
||||
this.response = new Blob([JSON.stringify(serverError)], { type: "text/html" });
|
||||
}
|
||||
let MockXHR = makeMockXHR("", send);
|
||||
const MockXHR = makeMockXHR("", send);
|
||||
|
||||
patchWithCleanup(
|
||||
browser,
|
||||
@@ -118,16 +114,13 @@ QUnit.module("download", hooks => {
|
||||
QUnit.test("handles arbitrary error", async (assert) => {
|
||||
assert.expect(3);
|
||||
|
||||
const serverError = /* xml */`<html><body><div>HTML error message</div></body></html>`;
|
||||
const serverError = /* xml */ `<html><body><div>HTML error message</div></body></html>`;
|
||||
|
||||
async function send() {
|
||||
this.status = 200;
|
||||
this.response = new Blob(
|
||||
[JSON.stringify(serverError)],
|
||||
{type: "text/html"}
|
||||
);
|
||||
this.response = new Blob([JSON.stringify(serverError)], { type: "text/html" });
|
||||
}
|
||||
let MockXHR = makeMockXHR("", send);
|
||||
const MockXHR = makeMockXHR("", send);
|
||||
|
||||
patchWithCleanup(
|
||||
browser,
|
||||
@@ -164,12 +157,9 @@ QUnit.module("download", hooks => {
|
||||
assert.ok(data.has("csrf_token"));
|
||||
|
||||
this.status = 200;
|
||||
this.response = new Blob(
|
||||
["some plain text file"],
|
||||
{type: "text/plain"}
|
||||
);
|
||||
this.response = new Blob(["some plain text file"], { type: "text/plain" });
|
||||
}
|
||||
let MockXHR = makeMockXHR("", send);
|
||||
const MockXHR = makeMockXHR("", send);
|
||||
|
||||
patchWithCleanup(
|
||||
browser,
|
||||
@@ -186,7 +176,7 @@ QUnit.module("download", hooks => {
|
||||
// This part asserts the implementation detail in question
|
||||
const downloadOnClick = (ev) => {
|
||||
const target = ev.target;
|
||||
if (target.tagName === "A" && "download" in target.attributes) {
|
||||
if (target.tagName === "A" && "download" in target.attributes) {
|
||||
ev.preventDefault();
|
||||
assert.ok(target.href.startsWith("blob:"));
|
||||
assert.step("file downloaded");
|
||||
|
||||
@@ -27,7 +27,7 @@ let isDeployed = false;
|
||||
async function testRPC(route, params) {
|
||||
let url = "";
|
||||
let request;
|
||||
let MockXHR = makeMockXHR({ test: true }, function (data) {
|
||||
const MockXHR = makeMockXHR({ test: true }, function (data) {
|
||||
request = data;
|
||||
url = this.url;
|
||||
});
|
||||
@@ -76,7 +76,7 @@ QUnit.module("RPC", {
|
||||
|
||||
QUnit.test("can perform a simple rpc", async (assert) => {
|
||||
assert.expect(4);
|
||||
let MockXHR = makeMockXHR({ result: { action_id: 123 } }, (request) => {
|
||||
const MockXHR = makeMockXHR({ result: { action_id: 123 } }, (request) => {
|
||||
assert.strictEqual(request.jsonrpc, "2.0");
|
||||
assert.strictEqual(request.method, "call");
|
||||
assert.ok(typeof request.id === "number");
|
||||
@@ -100,7 +100,7 @@ QUnit.test("trigger an error when response has 'error' key", async (assert) => {
|
||||
message: "data_message",
|
||||
},
|
||||
};
|
||||
let MockXHR = makeMockXHR({ error });
|
||||
const MockXHR = makeMockXHR({ error });
|
||||
patch(browser, "mock.xhr", { XMLHttpRequest: MockXHR }, { pure: true });
|
||||
|
||||
const env = await makeTestEnv({
|
||||
@@ -132,7 +132,7 @@ QUnit.test("rpc coming from destroyed components are left pending", async (asser
|
||||
}
|
||||
MyComponent.template = xml`<div/>`;
|
||||
const def = makeDeferred();
|
||||
let MockXHR = makeMockXHR({ result: "1" }, () => {}, def);
|
||||
const MockXHR = makeMockXHR({ result: "1" }, () => {}, def);
|
||||
patch(browser, "mock.xhr", { XMLHttpRequest: MockXHR }, { pure: true });
|
||||
|
||||
const env = await makeTestEnv({
|
||||
@@ -182,14 +182,14 @@ QUnit.test("rpc initiated from destroyed components throw exception", async (ass
|
||||
});
|
||||
|
||||
QUnit.test("check trigger RPC:REQUEST and RPC:RESPONSE for a simple rpc", async (assert) => {
|
||||
let MockXHR = makeMockXHR({ test: true }, () => 1);
|
||||
const MockXHR = makeMockXHR({ test: true }, () => 1);
|
||||
patch(browser, "mock.xhr", { XMLHttpRequest: MockXHR }, { pure: true });
|
||||
|
||||
const env = await makeTestEnv({
|
||||
serviceRegistry,
|
||||
});
|
||||
let rpcIdsRequest = [];
|
||||
let rpcIdsResponse = [];
|
||||
const rpcIdsRequest = [];
|
||||
const rpcIdsResponse = [];
|
||||
env.bus.addEventListener("RPC:REQUEST", (rpcId) => {
|
||||
rpcIdsRequest.push(rpcId);
|
||||
assert.step("RPC:REQUEST");
|
||||
@@ -217,13 +217,13 @@ QUnit.test("check trigger RPC:REQUEST and RPC:RESPONSE for a rpc with an error",
|
||||
message: "data_message",
|
||||
},
|
||||
};
|
||||
let MockXHR = makeMockXHR({ error });
|
||||
const MockXHR = makeMockXHR({ error });
|
||||
patch(browser, "mock.xhr", { XMLHttpRequest: MockXHR }, { pure: true });
|
||||
const env = await makeTestEnv({
|
||||
serviceRegistry,
|
||||
});
|
||||
let rpcIdsRequest = [];
|
||||
let rpcIdsResponse = [];
|
||||
const rpcIdsRequest = [];
|
||||
const rpcIdsResponse = [];
|
||||
env.bus.addEventListener("RPC:REQUEST", (rpcId) => {
|
||||
rpcIdsRequest.push(rpcId);
|
||||
assert.step("RPC:REQUEST");
|
||||
@@ -244,7 +244,7 @@ QUnit.test("check trigger RPC:REQUEST and RPC:RESPONSE for a rpc with an error",
|
||||
|
||||
QUnit.test("check connection aborted", async (assert) => {
|
||||
const def = makeDeferred();
|
||||
let MockXHR = makeMockXHR({}, () => {}, def);
|
||||
const MockXHR = makeMockXHR({}, () => {}, def);
|
||||
patchWithCleanup(browser, { XMLHttpRequest: MockXHR }, { pure: true });
|
||||
const env = await makeTestEnv({ serviceRegistry });
|
||||
|
||||
|
||||
@@ -270,7 +270,7 @@ QUnit.skip("can close sticky notification with wait", async (assert) => {
|
||||
const notifService = env.services.notification;
|
||||
await mount(NotificationContainer, target, { env, props });
|
||||
|
||||
let id = notifService.create("I'm a sticky notification", { sticky: true });
|
||||
const id = notifService.create("I'm a sticky notification", { sticky: true });
|
||||
await nextTick();
|
||||
assert.containsOnce(target, ".o_notification");
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { evaluateExpr, formatAST, parseExpr } from "@web/core/py_js/py";
|
||||
import { toPyValue } from "@web/core/py_js/py_utils";
|
||||
import { PyDate, PyDateTime } from '@web/core/py_js/py_date';
|
||||
import { PyDate, PyDateTime } from "@web/core/py_js/py_date";
|
||||
|
||||
QUnit.module("py", {}, () => {
|
||||
QUnit.module("formatAST");
|
||||
@@ -133,23 +133,23 @@ QUnit.module("py", {}, () => {
|
||||
assert.strictEqual(formatAST(toPyValue(null)), "None");
|
||||
});
|
||||
|
||||
QUnit.module('toPyValue');
|
||||
QUnit.module("toPyValue");
|
||||
|
||||
QUnit.test('toPyValue a string', function (assert) {
|
||||
const ast = toPyValue('test');
|
||||
QUnit.test("toPyValue a string", function (assert) {
|
||||
const ast = toPyValue("test");
|
||||
assert.strictEqual(ast.type, 1);
|
||||
assert.strictEqual(ast.value, 'test');
|
||||
assert.strictEqual(ast.value, "test");
|
||||
assert.strictEqual(formatAST(ast), '"test"');
|
||||
});
|
||||
|
||||
QUnit.test('toPyValue a number', function (assert) {
|
||||
QUnit.test("toPyValue a number", function (assert) {
|
||||
const ast = toPyValue(1);
|
||||
assert.strictEqual(ast.type, 0);
|
||||
assert.strictEqual(ast.value, 1);
|
||||
assert.strictEqual(formatAST(ast), "1");
|
||||
});
|
||||
|
||||
QUnit.test('toPyValue a boolean', function (assert) {
|
||||
QUnit.test("toPyValue a boolean", function (assert) {
|
||||
let ast = toPyValue(true);
|
||||
assert.strictEqual(ast.type, 2);
|
||||
assert.strictEqual(ast.value, true);
|
||||
@@ -161,17 +161,17 @@ QUnit.module("py", {}, () => {
|
||||
assert.strictEqual(formatAST(ast), "False");
|
||||
});
|
||||
|
||||
QUnit.test('toPyValue a object', function (assert) {
|
||||
const ast = toPyValue({a: 1});
|
||||
QUnit.test("toPyValue a object", function (assert) {
|
||||
const ast = toPyValue({ a: 1 });
|
||||
assert.strictEqual(ast.type, 11);
|
||||
assert.ok('a' in ast.value);
|
||||
assert.ok(['type', 'value'].every(prop => prop in ast.value.a));
|
||||
assert.ok("a" in ast.value);
|
||||
assert.ok(["type", "value"].every((prop) => prop in ast.value.a));
|
||||
assert.strictEqual(ast.value.a.type, 0);
|
||||
assert.strictEqual(ast.value.a.value, 1);
|
||||
assert.strictEqual(formatAST(ast), '{"a": 1}');
|
||||
});
|
||||
|
||||
QUnit.test('toPyValue a date', function (assert) {
|
||||
QUnit.test("toPyValue a date", function (assert) {
|
||||
const date = new Date(Date.UTC(2000, 0, 1));
|
||||
const ast = toPyValue(date);
|
||||
assert.strictEqual(ast.type, 1);
|
||||
@@ -180,7 +180,7 @@ QUnit.module("py", {}, () => {
|
||||
assert.strictEqual(formatAST(ast), JSON.stringify(expectedValue));
|
||||
});
|
||||
|
||||
QUnit.test('toPyValue a dateime', function (assert) {
|
||||
QUnit.test("toPyValue a dateime", function (assert) {
|
||||
const datetime = new Date(Date.UTC(2000, 0, 1, 1, 0, 0, 0));
|
||||
const ast = toPyValue(datetime);
|
||||
assert.strictEqual(ast.type, 1);
|
||||
@@ -189,7 +189,7 @@ QUnit.module("py", {}, () => {
|
||||
assert.strictEqual(formatAST(ast), JSON.stringify(expectedValue));
|
||||
});
|
||||
|
||||
QUnit.test('toPyValue a PyDate', function (assert) {
|
||||
QUnit.test("toPyValue a PyDate", function (assert) {
|
||||
const value = new PyDate(2000, 1, 1);
|
||||
const ast = toPyValue(value);
|
||||
assert.strictEqual(ast.type, 1);
|
||||
@@ -197,7 +197,7 @@ QUnit.module("py", {}, () => {
|
||||
assert.strictEqual(formatAST(ast), JSON.stringify(value));
|
||||
});
|
||||
|
||||
QUnit.test('toPyValue a PyDateTime', function (assert) {
|
||||
QUnit.test("toPyValue a PyDateTime", function (assert) {
|
||||
const value = new PyDateTime(2000, 1, 1, 1, 0, 0, 0);
|
||||
const ast = toPyValue(value);
|
||||
assert.strictEqual(ast.type, 1);
|
||||
|
||||
@@ -16,11 +16,11 @@ QUnit.test("successive calls to hasGroup", async (assert) => {
|
||||
return groups.includes(args.args[0]);
|
||||
};
|
||||
const env = await makeTestEnv({ mockRPC });
|
||||
let hasGroupX = await env.services.user.hasGroup("x");
|
||||
let hasGroupY = await env.services.user.hasGroup("y");
|
||||
const hasGroupX = await env.services.user.hasGroup("x");
|
||||
const hasGroupY = await env.services.user.hasGroup("y");
|
||||
assert.strictEqual(hasGroupX, true);
|
||||
assert.strictEqual(hasGroupY, false);
|
||||
let hasGroupXAgain = await env.services.user.hasGroup("x");
|
||||
const hasGroupXAgain = await env.services.user.hasGroup("x");
|
||||
assert.strictEqual(hasGroupXAgain, true);
|
||||
|
||||
assert.verifySteps(["res.users/has_group/x", "res.users/has_group/y"]);
|
||||
|
||||
@@ -1093,7 +1093,7 @@ QUnit.module("utils", () => {
|
||||
}
|
||||
}
|
||||
let descriptor = Object.getOwnPropertyDescriptor(BaseClass.prototype, "getter");
|
||||
let getterFn = descriptor.get;
|
||||
const getterFn = descriptor.get;
|
||||
assert.strictEqual(descriptor.configurable, true);
|
||||
assert.strictEqual(descriptor.enumerable, false);
|
||||
|
||||
|
||||
@@ -203,7 +203,10 @@ QUnit.test(
|
||||
serviceRegistry.add("b", serviceB);
|
||||
const prom = startServices(env);
|
||||
await Promise.resolve();
|
||||
await assert.rejects(prom, "Some services could not be started: b. Missing dependencies: a");
|
||||
await assert.rejects(
|
||||
prom,
|
||||
"Some services could not be started: b. Missing dependencies: a"
|
||||
);
|
||||
assert.deepEqual(env.services, {});
|
||||
|
||||
serviceRegistry.add("a", serviceA);
|
||||
|
||||
@@ -52,7 +52,11 @@ const validElements = [
|
||||
{ tagName: "DIV", attr: "class", value: "tooltip fade bs-tooltip-auto" },
|
||||
{ tagName: "DIV", attr: "class", value: "tooltip fade bs-tooltip-auto show" },
|
||||
{ tagName: "DIV", attr: "class", value: "tooltip tooltip-field-info fade bs-tooltip-auto" },
|
||||
{ tagName: "DIV", attr: "class", value: "tooltip tooltip-field-info fade bs-tooltip-auto show" },
|
||||
{
|
||||
tagName: "DIV",
|
||||
attr: "class",
|
||||
value: "tooltip tooltip-field-info fade bs-tooltip-auto show",
|
||||
},
|
||||
{ tagName: "SPAN", attr: "class", value: "select2-hidden-accessible" },
|
||||
|
||||
// Due to a Document Kanban bug (already present in 12.0)
|
||||
|
||||
@@ -11,7 +11,9 @@ odoo.define("web.SessionOverrideForTests", (require) => {
|
||||
const { patch } = require("@web/core/utils/patch");
|
||||
patch(Session.prototype, "web.SessionTestPatch", {
|
||||
async session_reload() {
|
||||
for (const key in sessionInfo) delete sessionInfo[key];
|
||||
for (const key in sessionInfo) {
|
||||
delete sessionInfo[key];
|
||||
}
|
||||
for (const key in initialSessionInfo) {
|
||||
sessionInfo[key] = initialSessionInfo[key];
|
||||
}
|
||||
@@ -22,13 +24,8 @@ odoo.define("web.SessionOverrideForTests", (require) => {
|
||||
|
||||
odoo.define("web.test_legacy", async (require) => {
|
||||
require("web.SessionOverrideForTests");
|
||||
|
||||
const legacyProm = new Promise(async (resolve) => {
|
||||
const session = require("web.session");
|
||||
await session.is_bound; // await for templates from server
|
||||
require("web.test_utils");
|
||||
resolve();
|
||||
});
|
||||
|
||||
return { legacyProm };
|
||||
require("web.test_utils");
|
||||
const session = require("web.session");
|
||||
await session.is_bound; // await for templates from server
|
||||
return { legacyProm: session.is_bound };
|
||||
});
|
||||
|
||||
@@ -100,7 +100,7 @@ export async function makeTestEnv(config = {}) {
|
||||
while (servicesToProcess.length) {
|
||||
const service = servicesToProcess.pop();
|
||||
if (service.dependencies) {
|
||||
for (let depName of service.dependencies) {
|
||||
for (const depName of service.dependencies) {
|
||||
if (depName in mocks && !serviceRegistry.contains(depName)) {
|
||||
const dep = mocks[depName]();
|
||||
serviceRegistry.add(depName, dep);
|
||||
|
||||
@@ -111,9 +111,9 @@ export class MockServer {
|
||||
console.log("%c[rpc] request " + route, "color: #66e; font-weight: bold;", args);
|
||||
args = JSON.parse(JSON.stringify(args));
|
||||
}
|
||||
let result;
|
||||
const result = await this._performRPC(route, args);
|
||||
// try {
|
||||
result = await this._performRPC(route, args);
|
||||
// const result = await this._performRPC(route, args);
|
||||
// } catch {
|
||||
// const message = result && result.message;
|
||||
// const event = result && result.event;
|
||||
@@ -196,7 +196,7 @@ export class MockServer {
|
||||
}
|
||||
|
||||
_getView(params) {
|
||||
let processedNodes = params.processedNodes || [];
|
||||
const processedNodes = params.processedNodes || [];
|
||||
const { arch, context, modelName } = params;
|
||||
const level = params.level || 0;
|
||||
const fields = deepCopy(params.fields);
|
||||
@@ -709,7 +709,7 @@ export class MockServer {
|
||||
const str = args && typeof args[0] === "string" ? args[0] : kwargs.name;
|
||||
const limit = kwargs.limit || 100;
|
||||
const domain = (args && args[1]) || kwargs.args || [];
|
||||
let { records } = this.models[model];
|
||||
const { records } = this.models[model];
|
||||
const result = [];
|
||||
for (const r of records) {
|
||||
const isInDomain = this.evaluateDomain(domain, r);
|
||||
@@ -2011,7 +2011,7 @@ export class MockServer {
|
||||
switch (field.type) {
|
||||
case "many2many":
|
||||
case "many2one": {
|
||||
let coRecords = this.models[field.relation].records;
|
||||
const coRecords = this.models[field.relation].records;
|
||||
const coField = this.getOrderByField(field.relation);
|
||||
if (field.type === "many2many") {
|
||||
// M2m use the joined list of comodel field values
|
||||
@@ -2207,7 +2207,7 @@ export class MockServer {
|
||||
|
||||
// instance of `MockServer` linked to the current test.
|
||||
let mockServer;
|
||||
QUnit.testStart(() => mockServer = undefined);
|
||||
QUnit.testStart(() => (mockServer = undefined));
|
||||
export async function makeMockServer(serverData, mockRPC) {
|
||||
serverData = serverData || {};
|
||||
if (!mockServer) {
|
||||
|
||||
@@ -329,7 +329,9 @@ export async function triggerScroll(
|
||||
const isScrollable =
|
||||
(target.scrollHeight > target.clientHeight && target.clientHeight > 0) ||
|
||||
(target.scrollWidth > target.clientWidth && target.clientWidth > 0);
|
||||
if (!isScrollable && !canPropagate) return;
|
||||
if (!isScrollable && !canPropagate) {
|
||||
return;
|
||||
}
|
||||
if (isScrollable) {
|
||||
const canScrollFrom = {
|
||||
left:
|
||||
@@ -351,7 +353,9 @@ export async function triggerScroll(
|
||||
target.scrollTo(scrollCoordinates);
|
||||
target.dispatchEvent(new UIEvent("scroll"));
|
||||
await nextTick();
|
||||
if (!canPropagate || !Object.entries(coordinates).length) return;
|
||||
if (!canPropagate || !Object.entries(coordinates).length) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
target.parentElement
|
||||
? triggerScroll(target.parentElement, coordinates)
|
||||
|
||||
@@ -124,7 +124,7 @@ QUnit.test("Burger menu closes when an action is requested", async (assert) => {
|
||||
});
|
||||
|
||||
QUnit.test("Burger menu closes when click on menu item", async (assert) => {
|
||||
serverData.actions[1].target = 'new';
|
||||
serverData.actions[1].target = "new";
|
||||
serverData.menus[1].children = [99];
|
||||
serverData.menus[99] = {
|
||||
id: 99,
|
||||
|
||||
@@ -109,7 +109,7 @@ QUnit.module("Search", (hooks) => {
|
||||
});
|
||||
|
||||
QUnit.test("pager", async (assert) => {
|
||||
let pagerProps = {
|
||||
const pagerProps = {
|
||||
offset: 0,
|
||||
limit: 10,
|
||||
total: 50,
|
||||
|
||||
@@ -471,33 +471,35 @@ QUnit.module("Search", (hooks) => {
|
||||
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);
|
||||
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);
|
||||
patchDate(2019, 6, 31, 13, 43, 0);
|
||||
|
||||
await makeWithSearch({
|
||||
serverData,
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
searchViewId: false,
|
||||
searchMenuTypes: ["filter"],
|
||||
searchViewArch: `
|
||||
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 },
|
||||
});
|
||||
context: { search_default_date_field: true },
|
||||
});
|
||||
|
||||
await toggleFilterMenu(target);
|
||||
await toggleMenuItem(target, "Date");
|
||||
await toggleFilterMenu(target);
|
||||
await toggleMenuItem(target, "Date");
|
||||
|
||||
assert.ok(isItemSelected(target, "Date"));
|
||||
assert.ok(isOptionSelected(target, "Date", "2019"));
|
||||
assert.ok(isOptionSelected(target, "Date", "2018"));
|
||||
|
||||
});
|
||||
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);
|
||||
|
||||
@@ -458,68 +458,74 @@ QUnit.module("Search", (hooks) => {
|
||||
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: `
|
||||
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"],
|
||||
});
|
||||
searchMenuTypes: ["groupBy"],
|
||||
});
|
||||
|
||||
await toggleGroupByMenu(target);
|
||||
await toggleGroupByMenu(target);
|
||||
|
||||
assert.containsOnce(target, ".o_add_custom_group_menu");
|
||||
});
|
||||
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: `
|
||||
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"],
|
||||
});
|
||||
hideCustomGroupBy: false,
|
||||
searchMenuTypes: ["groupBy"],
|
||||
});
|
||||
|
||||
await toggleGroupByMenu(target);
|
||||
await toggleGroupByMenu(target);
|
||||
|
||||
assert.containsOnce(target, ".o_add_custom_group_menu");
|
||||
});
|
||||
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: `
|
||||
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"],
|
||||
});
|
||||
hideCustomGroupBy: true,
|
||||
searchMenuTypes: ["groupBy"],
|
||||
});
|
||||
|
||||
await toggleGroupByMenu(target);
|
||||
await toggleGroupByMenu(target);
|
||||
|
||||
assert.containsNone(target, ".o_add_custom_group_menu");
|
||||
});
|
||||
assert.containsNone(target, ".o_add_custom_group_menu");
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { getGroupBy } from "@web/search/utils/group_by";
|
||||
import { DEFAULT_INTERVAL } from "@web/search/utils/dates";
|
||||
|
||||
let fields = {
|
||||
const fields = {
|
||||
display_name: { string: "Displayed name", type: "char" },
|
||||
foo: {
|
||||
string: "Foo",
|
||||
@@ -47,7 +47,7 @@ QUnit.module("GroupBy Class", {}, () => {
|
||||
QUnit.module("With field validation");
|
||||
QUnit.test("simple valid group by", async function (assert) {
|
||||
assert.expect(3);
|
||||
let groupBy = getGroupBy("display_name", fields);
|
||||
const groupBy = getGroupBy("display_name", fields);
|
||||
assert.strictEqual(groupBy.fieldName, "display_name");
|
||||
assert.strictEqual(groupBy.interval, null);
|
||||
assert.strictEqual(groupBy.spec, "display_name");
|
||||
|
||||
@@ -232,40 +232,43 @@ QUnit.module("Search", (hooks) => {
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("parsing one filter tag with default_period date attribute", async function (assert) {
|
||||
assert.expect(1);
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
searchViewArch: `
|
||||
QUnit.test(
|
||||
"parsing one filter tag with default_period date attribute",
|
||||
async function (assert) {
|
||||
assert.expect(1);
|
||||
const model = await makeSearchModel({
|
||||
serverData,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter name="date_filter" string="Date" date="date_field" default_period="this_year,last_year"/>
|
||||
</search>
|
||||
`,
|
||||
});
|
||||
const dateFilterId = model.getSearchItems((f) => f.type === "dateFilter")[0].id;
|
||||
assert.deepEqual(sanitizeSearchItems(model), [
|
||||
{
|
||||
defaultGeneratorIds: ["this_year", "last_year"],
|
||||
description: "Date",
|
||||
fieldName: "date_field",
|
||||
fieldType: "date",
|
||||
type: "dateFilter",
|
||||
name: "date_filter",
|
||||
},
|
||||
{
|
||||
comparisonOptionId: "previous_period",
|
||||
dateFilterId,
|
||||
description: "Date: Previous Period",
|
||||
type: "comparison",
|
||||
},
|
||||
{
|
||||
comparisonOptionId: "previous_year",
|
||||
dateFilterId,
|
||||
description: "Date: Previous Year",
|
||||
type: "comparison",
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
const dateFilterId = model.getSearchItems((f) => f.type === "dateFilter")[0].id;
|
||||
assert.deepEqual(sanitizeSearchItems(model), [
|
||||
{
|
||||
defaultGeneratorIds: ["this_year", "last_year"],
|
||||
description: "Date",
|
||||
fieldName: "date_field",
|
||||
fieldType: "date",
|
||||
type: "dateFilter",
|
||||
name: "date_filter",
|
||||
},
|
||||
{
|
||||
comparisonOptionId: "previous_period",
|
||||
dateFilterId,
|
||||
description: "Date: Previous Period",
|
||||
type: "comparison",
|
||||
},
|
||||
{
|
||||
comparisonOptionId: "previous_year",
|
||||
dateFilterId,
|
||||
description: "Date: Previous Year",
|
||||
type: "comparison",
|
||||
},
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("parsing one filter tag with date attribute ", async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
@@ -1118,7 +1118,7 @@ QUnit.module("Search", (hooks) => {
|
||||
</searchpanel>
|
||||
</search>`;
|
||||
|
||||
let promise = makeDeferred();
|
||||
const promise = makeDeferred();
|
||||
const { TestComponent } = makeTestComponent();
|
||||
const compPromise = makeWithSearch({
|
||||
serverData,
|
||||
@@ -1158,7 +1158,7 @@ QUnit.module("Search", (hooks) => {
|
||||
</searchpanel>
|
||||
</search>`;
|
||||
|
||||
let promise = makeDeferred();
|
||||
const promise = makeDeferred();
|
||||
const { TestComponent } = makeTestComponent();
|
||||
const compPromise = makeWithSearch({
|
||||
serverData,
|
||||
|
||||
@@ -132,59 +132,86 @@ QUnit.module("Fields", (hooks) => {
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"color picker on tree view",
|
||||
async function (assert) {
|
||||
await makeView({
|
||||
type: "list",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
QUnit.test("color picker on tree view", async function (assert) {
|
||||
await makeView({
|
||||
type: "list",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<tree>
|
||||
<field name="int_field" widget="color_picker"/>
|
||||
<field name="display_name" />
|
||||
</tree>`,
|
||||
selectRecord() {
|
||||
assert.step("record selected to open");
|
||||
},
|
||||
});
|
||||
selectRecord() {
|
||||
assert.step("record selected to open");
|
||||
},
|
||||
});
|
||||
|
||||
await click(target, ".o_field_color_picker button");
|
||||
assert.verifySteps(["record selected to open"], "the color is not editable and the record has been opened");
|
||||
}
|
||||
);
|
||||
await click(target, ".o_field_color_picker button");
|
||||
assert.verifySteps(
|
||||
["record selected to open"],
|
||||
"the color is not editable and the record has been opened"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"color picker in editable list view",
|
||||
async function (assert) {
|
||||
serverData.models.partner.records.push({
|
||||
int_field: 1
|
||||
});
|
||||
await makeView({
|
||||
type: "list",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
QUnit.test("color picker in editable list view", async function (assert) {
|
||||
serverData.models.partner.records.push({
|
||||
int_field: 1,
|
||||
});
|
||||
await makeView({
|
||||
type: "list",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<list editable="bottom">
|
||||
<field name="int_field" widget="color_picker"/>
|
||||
</list>
|
||||
`,
|
||||
});
|
||||
});
|
||||
|
||||
assert.containsOnce(target, ".o_data_row:nth-child(1) .o_field_color_picker button", "color picker list is not open by default");
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_data_row:nth-child(1) .o_field_color_picker button",
|
||||
"color picker list is not open by default"
|
||||
);
|
||||
|
||||
await click(target, ".o_data_row:nth-child(1) .o_field_color_picker button");
|
||||
assert.hasClass(target.querySelector(".o_data_row:nth-child(1)"), "o_selected_row", "first row is selected");
|
||||
assert.containsN(target, ".o_data_row:nth-child(1) .o_field_color_picker button", 12, "color picker list is open when the row is in edition");
|
||||
await click(target, ".o_data_row:nth-child(1) .o_field_color_picker button");
|
||||
assert.hasClass(
|
||||
target.querySelector(".o_data_row:nth-child(1)"),
|
||||
"o_selected_row",
|
||||
"first row is selected"
|
||||
);
|
||||
assert.containsN(
|
||||
target,
|
||||
".o_data_row:nth-child(1) .o_field_color_picker button",
|
||||
12,
|
||||
"color picker list is open when the row is in edition"
|
||||
);
|
||||
|
||||
await click(target, ".o_data_row:nth-child(1) .o_field_color_picker .o_colorlist_item_color_6");
|
||||
assert.containsN(target, ".o_data_row:nth-child(1) .o_field_color_picker button", 12, "color picker list is still open after color has been selected");
|
||||
await click(
|
||||
target,
|
||||
".o_data_row:nth-child(1) .o_field_color_picker .o_colorlist_item_color_6"
|
||||
);
|
||||
assert.containsN(
|
||||
target,
|
||||
".o_data_row:nth-child(1) .o_field_color_picker button",
|
||||
12,
|
||||
"color picker list is still open after color has been selected"
|
||||
);
|
||||
|
||||
await click(target, ".o_data_row:nth-child(2) .o_data_cell");
|
||||
assert.containsOnce(target, ".o_data_row:nth-child(1) .o_field_color_picker button", "color picker list is no longer open on the first row");
|
||||
assert.containsN(target, ".o_data_row:nth-child(2) .o_field_color_picker button", 12, "color picker list is open when the row is in edition");
|
||||
}
|
||||
);
|
||||
await click(target, ".o_data_row:nth-child(2) .o_data_cell");
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_data_row:nth-child(1) .o_field_color_picker button",
|
||||
"color picker list is no longer open on the first row"
|
||||
);
|
||||
assert.containsN(
|
||||
target,
|
||||
".o_data_row:nth-child(2) .o_field_color_picker button",
|
||||
12,
|
||||
"color picker list is open when the row is in edition"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("column widths: dont overflow color picker in list", async function (assert) {
|
||||
serverData.models.partner.fields.date_field = {
|
||||
|
||||
@@ -45,12 +45,16 @@ QUnit.module("Fields", (hooks) => {
|
||||
return {
|
||||
add(el, comp, params) {
|
||||
assert.strictEqual(el.textContent, "Copy", "button has the right text");
|
||||
assert.deepEqual(params, { tooltip: "Copied" }, "tooltip has the right parameters");
|
||||
assert.deepEqual(
|
||||
params,
|
||||
{ tooltip: "Copied" },
|
||||
"tooltip has the right parameters"
|
||||
);
|
||||
assert.step("copied tooltip");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
serviceRegistry.remove("popover");
|
||||
serviceRegistry.add("popover", fakePopoverService);
|
||||
});
|
||||
@@ -151,7 +155,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
await makeView({
|
||||
|
||||
@@ -28,7 +28,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
assert.strictEqual(formatFloat(null), "0.00");
|
||||
assert.strictEqual(formatFloat(1000000), "1,000,000.00");
|
||||
|
||||
let options = { grouping: [3, 2, -1], decimalPoint: "?", thousandsSep: "€" };
|
||||
const options = { grouping: [3, 2, -1], decimalPoint: "?", thousandsSep: "€" };
|
||||
assert.strictEqual(formatFloat(106500, options), "1€06€500?00");
|
||||
|
||||
assert.strictEqual(formatFloat(1500, { thousandsSep: "" }), "1500.00");
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import {
|
||||
click,
|
||||
editInput,
|
||||
getFixture,
|
||||
nextTick,
|
||||
triggerEvent,
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { click, editInput, getFixture, nextTick, triggerEvent } from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
|
||||
let serverData;
|
||||
@@ -161,13 +155,8 @@ QUnit.module("Fields", (hooks) => {
|
||||
const addButton = target.querySelector(".o_field_property_add button");
|
||||
assert.notOk(addButton, "The add button must not be in the view");
|
||||
|
||||
const editButton = field.querySelector(
|
||||
".o_field_property_open_popover"
|
||||
);
|
||||
assert.notOk(
|
||||
editButton,
|
||||
"The edit definition button must not be in the view"
|
||||
);
|
||||
const editButton = field.querySelector(".o_field_property_open_popover");
|
||||
assert.notOk(editButton, "The edit definition button must not be in the view");
|
||||
|
||||
const property = field.querySelector(".o_property_field_value input");
|
||||
assert.strictEqual(property.value, "char value");
|
||||
@@ -209,59 +198,36 @@ QUnit.module("Fields", (hooks) => {
|
||||
const addButton = target.querySelector(".o_field_property_add button");
|
||||
assert.ok(addButton, "The add button must be in the view");
|
||||
|
||||
const editButton = field.querySelectorAll(
|
||||
".o_field_property_open_popover"
|
||||
);
|
||||
const editButton = field.querySelectorAll(".o_field_property_open_popover");
|
||||
assert.ok(editButton, "The edit definition button must be in the view");
|
||||
|
||||
const property = field.querySelector(".o_property_field_value input");
|
||||
assert.strictEqual(property.value, "char value");
|
||||
|
||||
// Open the definition popover
|
||||
await click(
|
||||
target,
|
||||
".o_property_field:first-child .o_field_property_open_popover"
|
||||
);
|
||||
await click(target, ".o_property_field:first-child .o_field_property_open_popover");
|
||||
|
||||
const popover = target.querySelector(".o_property_field_popover");
|
||||
assert.ok(popover, "Should have opened the definition popover");
|
||||
|
||||
const label = popover.querySelector(
|
||||
".o_field_property_definition_header input"
|
||||
);
|
||||
const label = popover.querySelector(".o_field_property_definition_header input");
|
||||
assert.strictEqual(label.value, "My Char");
|
||||
|
||||
const type = popover.querySelector(
|
||||
".o_field_property_definition_type input"
|
||||
);
|
||||
const type = popover.querySelector(".o_field_property_definition_type input");
|
||||
assert.strictEqual(type.value, "Text");
|
||||
|
||||
// Change the property type to "Date & Time"
|
||||
await editInput(
|
||||
target,
|
||||
".o_field_property_definition_header input",
|
||||
"My Datetime"
|
||||
);
|
||||
await editInput(target, ".o_field_property_definition_header input", "My Datetime");
|
||||
await click(target, ".o_field_property_definition_type button");
|
||||
await click(
|
||||
target,
|
||||
".o_field_property_definition_type .dropdown-menu .dropdown-item:nth-child(6)"
|
||||
);
|
||||
assert.strictEqual(
|
||||
type.value,
|
||||
"Date & Time",
|
||||
"Should have changed the property type"
|
||||
);
|
||||
assert.strictEqual(type.value, "Date & Time", "Should have changed the property type");
|
||||
|
||||
// Choosing a date in the date picker should not close the definition popover
|
||||
await click(
|
||||
target,
|
||||
".o_field_property_definition_value .o_datepicker_input"
|
||||
);
|
||||
await click(
|
||||
document,
|
||||
".datepicker-days tr:first-child .day:nth-child(3)"
|
||||
);
|
||||
await click(target, ".o_field_property_definition_value .o_datepicker_input");
|
||||
await click(document, ".datepicker-days tr:first-child .day:nth-child(3)");
|
||||
assert.ok(
|
||||
document.querySelector(".picker-switch .fa-check"),
|
||||
"Should not close the definition popover after selecting a date"
|
||||
@@ -281,15 +247,11 @@ QUnit.module("Fields", (hooks) => {
|
||||
"My Datetime",
|
||||
"Should have updated the property label"
|
||||
);
|
||||
const datetimeComponent = field.querySelector(
|
||||
".o_property_field_value .o_datepicker"
|
||||
);
|
||||
const datetimeComponent = field.querySelector(".o_property_field_value .o_datepicker");
|
||||
assert.ok(datetimeComponent, "Should have changed the property type");
|
||||
|
||||
// Check that the value is reset (because the type changed)
|
||||
const inputValue = document.querySelector(
|
||||
".o_property_field_value input"
|
||||
);
|
||||
const inputValue = document.querySelector(".o_property_field_value input");
|
||||
assert.notOk(inputValue.value);
|
||||
|
||||
// Discard the form view and check that the properties take its old values
|
||||
@@ -347,18 +309,10 @@ QUnit.module("Fields", (hooks) => {
|
||||
const popover = target.querySelector(".o_property_field_popover");
|
||||
assert.ok(popover, "Should have opened the definition popover");
|
||||
|
||||
const label = popover.querySelector(
|
||||
".o_field_property_definition_header input"
|
||||
);
|
||||
assert.strictEqual(
|
||||
label.value,
|
||||
"Property 3",
|
||||
"Should have added a default label"
|
||||
);
|
||||
const label = popover.querySelector(".o_field_property_definition_header input");
|
||||
assert.strictEqual(label.value, "Property 3", "Should have added a default label");
|
||||
|
||||
const type = popover.querySelector(
|
||||
".o_field_property_definition_type input"
|
||||
);
|
||||
const type = popover.querySelector(".o_field_property_definition_type input");
|
||||
assert.strictEqual(type.value, "Text", "Default type must be text");
|
||||
|
||||
await closePopover(target);
|
||||
@@ -367,9 +321,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
assert.strictEqual(properties.length, 3);
|
||||
|
||||
const newProperty = properties[2];
|
||||
const newPropertyLabel = newProperty.querySelector(
|
||||
".o_field_property_label"
|
||||
);
|
||||
const newPropertyLabel = newProperty.querySelector(".o_field_property_label");
|
||||
assert.strictEqual(newPropertyLabel.innerText, "Property 3");
|
||||
});
|
||||
|
||||
@@ -405,68 +357,35 @@ QUnit.module("Fields", (hooks) => {
|
||||
const field = target.querySelector(".o_field_properties");
|
||||
assert.ok(field, "The field must be in the view");
|
||||
|
||||
const selectionValue = target.querySelector(
|
||||
".o_property_field:nth-child(2) select"
|
||||
);
|
||||
const selectionValue = target.querySelector(".o_property_field:nth-child(2) select");
|
||||
assert.ok(selectionValue);
|
||||
assert.strictEqual(selectionValue.value, "b");
|
||||
|
||||
// Edit the selection property
|
||||
await click(
|
||||
target,
|
||||
".o_property_field:nth-child(2) .o_field_property_open_popover"
|
||||
);
|
||||
await click(target, ".o_property_field:nth-child(2) .o_field_property_open_popover");
|
||||
|
||||
const popover = target.querySelector(".o_property_field_popover");
|
||||
assert.ok(popover, "Should have opened the definition popover");
|
||||
|
||||
const selectionComponent = popover.querySelector(
|
||||
".o_field_property_selection"
|
||||
);
|
||||
assert.ok(
|
||||
selectionComponent,
|
||||
"Must instantiate the selection component"
|
||||
);
|
||||
const selectionComponent = popover.querySelector(".o_field_property_selection");
|
||||
assert.ok(selectionComponent, "Must instantiate the selection component");
|
||||
|
||||
// Check the default option
|
||||
const option1 = popover.querySelector(
|
||||
".o_field_property_selection_option:nth-child(1)"
|
||||
);
|
||||
assert.notOk(
|
||||
option1.querySelector(".fa-star"),
|
||||
"Default option must be the third one"
|
||||
);
|
||||
const option2 = popover.querySelector(
|
||||
".o_field_property_selection_option:nth-child(2)"
|
||||
);
|
||||
assert.notOk(
|
||||
option2.querySelector(".fa-star"),
|
||||
"Default option must be the third one"
|
||||
);
|
||||
const option3 = popover.querySelector(
|
||||
".o_field_property_selection_option:nth-child(3)"
|
||||
);
|
||||
const option1 = popover.querySelector(".o_field_property_selection_option:nth-child(1)");
|
||||
assert.notOk(option1.querySelector(".fa-star"), "Default option must be the third one");
|
||||
const option2 = popover.querySelector(".o_field_property_selection_option:nth-child(2)");
|
||||
assert.notOk(option2.querySelector(".fa-star"), "Default option must be the third one");
|
||||
const option3 = popover.querySelector(".o_field_property_selection_option:nth-child(3)");
|
||||
assert.ok(option3);
|
||||
assert.ok(
|
||||
option3.querySelector(".fa-star"),
|
||||
"Default option must be the third one"
|
||||
);
|
||||
assert.ok(option3.querySelector(".fa-star"), "Default option must be the third one");
|
||||
|
||||
const type = popover.querySelector(
|
||||
".o_field_property_definition_type input"
|
||||
);
|
||||
const type = popover.querySelector(".o_field_property_definition_type input");
|
||||
assert.strictEqual(type.value, "Selection");
|
||||
|
||||
// Create a new selection option
|
||||
await click(target, ".o_field_property_selection .fa-plus");
|
||||
let options = popover.querySelectorAll(
|
||||
".o_field_property_selection_option"
|
||||
);
|
||||
assert.strictEqual(
|
||||
options.length,
|
||||
4,
|
||||
"Should have added the new option"
|
||||
);
|
||||
let options = popover.querySelectorAll(".o_field_property_selection_option");
|
||||
assert.strictEqual(options.length, 4, "Should have added the new option");
|
||||
assert.strictEqual(
|
||||
document.activeElement,
|
||||
options[3].querySelector("input"),
|
||||
@@ -488,14 +407,8 @@ QUnit.module("Fields", (hooks) => {
|
||||
);
|
||||
await nextTick();
|
||||
|
||||
options = popover.querySelectorAll(
|
||||
".o_field_property_selection_option"
|
||||
);
|
||||
assert.strictEqual(
|
||||
options.length,
|
||||
5,
|
||||
"Should have added the new option on Enter"
|
||||
);
|
||||
options = popover.querySelectorAll(".o_field_property_selection_option");
|
||||
assert.strictEqual(options.length, 5, "Should have added the new option on Enter");
|
||||
assert.strictEqual(
|
||||
document.activeElement,
|
||||
options[4].querySelector("input"),
|
||||
@@ -518,9 +431,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
options[3].querySelector("input"),
|
||||
"Should focus the previous option"
|
||||
);
|
||||
options = popover.querySelectorAll(
|
||||
".o_field_property_selection_option"
|
||||
);
|
||||
options = popover.querySelectorAll(".o_field_property_selection_option");
|
||||
assert.strictEqual(
|
||||
options.length,
|
||||
4,
|
||||
@@ -538,31 +449,15 @@ QUnit.module("Fields", (hooks) => {
|
||||
);
|
||||
await nextTick();
|
||||
|
||||
assert.strictEqual(
|
||||
document.activeElement,
|
||||
options[2].querySelector("input")
|
||||
);
|
||||
options = popover.querySelectorAll(
|
||||
".o_field_property_selection_option"
|
||||
);
|
||||
assert.strictEqual(document.activeElement, options[2].querySelector("input"));
|
||||
options = popover.querySelectorAll(".o_field_property_selection_option");
|
||||
assert.strictEqual(options.length, 4, "Should not remove any options");
|
||||
|
||||
// Remove the second option
|
||||
await click(
|
||||
target,
|
||||
".o_field_property_selection_option:nth-child(2) .fa-times"
|
||||
);
|
||||
options = popover.querySelectorAll(
|
||||
".o_field_property_selection_option"
|
||||
);
|
||||
assert.strictEqual(
|
||||
options.length,
|
||||
3,
|
||||
"Should have removed the second option"
|
||||
);
|
||||
const optionValues = [...options].map(
|
||||
(option) => option.querySelector("input").value
|
||||
);
|
||||
await click(target, ".o_field_property_selection_option:nth-child(2) .fa-times");
|
||||
options = popover.querySelectorAll(".o_field_property_selection_option");
|
||||
assert.strictEqual(options.length, 3, "Should have removed the second option");
|
||||
const optionValues = [...options].map((option) => option.querySelector("input").value);
|
||||
assert.deepEqual(
|
||||
optionValues,
|
||||
["A", "C", "New option"],
|
||||
@@ -599,9 +494,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
|
||||
// Return the properties labels
|
||||
const getLabels = () => {
|
||||
const labels = target.querySelectorAll(
|
||||
".o_field_properties .o_field_property_label"
|
||||
);
|
||||
const labels = target.querySelectorAll(".o_field_properties .o_field_property_label");
|
||||
return [...labels].map((label) => label.innerText);
|
||||
};
|
||||
|
||||
@@ -611,10 +504,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
assert.ok(field, "The field must be in the view");
|
||||
|
||||
// Edit the selection property
|
||||
await click(
|
||||
target,
|
||||
".o_property_field:nth-child(2) .o_field_property_open_popover"
|
||||
);
|
||||
await click(target, ".o_property_field:nth-child(2) .o_field_property_open_popover");
|
||||
|
||||
const popover = target.querySelector(".o_property_field_popover");
|
||||
assert.ok(popover, "Should have opened the definition popover");
|
||||
@@ -633,31 +523,16 @@ QUnit.module("Fields", (hooks) => {
|
||||
|
||||
// Move the property up again, should have no effect
|
||||
await click(popover, ".fa-chevron-up");
|
||||
assert.deepEqual(getLabels(), [
|
||||
"My Selection",
|
||||
"My Char",
|
||||
"My Char 3",
|
||||
"My Char 4",
|
||||
]);
|
||||
assert.deepEqual(getLabels(), ["My Selection", "My Char", "My Char 3", "My Char 4"]);
|
||||
|
||||
// Move the property down
|
||||
await click(popover, ".fa-chevron-down");
|
||||
assert.deepEqual(getLabels(), [
|
||||
"My Char",
|
||||
"My Selection",
|
||||
"My Char 3",
|
||||
"My Char 4",
|
||||
]);
|
||||
assert.deepEqual(getLabels(), ["My Char", "My Selection", "My Char 3", "My Char 4"]);
|
||||
|
||||
// Move the property at the bottom
|
||||
await click(popover, ".fa-chevron-down");
|
||||
await click(popover, ".fa-chevron-down");
|
||||
assert.deepEqual(getLabels(), [
|
||||
"My Char",
|
||||
"My Char 3",
|
||||
"My Char 4",
|
||||
"My Selection",
|
||||
]);
|
||||
assert.deepEqual(getLabels(), ["My Char", "My Char 3", "My Char 4", "My Selection"]);
|
||||
|
||||
await closePopover(target);
|
||||
|
||||
@@ -711,22 +586,15 @@ QUnit.module("Fields", (hooks) => {
|
||||
};
|
||||
|
||||
await click(target, ".o_form_button_edit");
|
||||
await click(
|
||||
target,
|
||||
".o_property_field:nth-child(2) .o_field_property_open_popover"
|
||||
);
|
||||
await click(target, ".o_property_field:nth-child(2) .o_field_property_open_popover");
|
||||
let popover = target.querySelector(".o_property_field_popover");
|
||||
// Select the tags type
|
||||
await click(popover, ".o_field_property_definition_type input");
|
||||
await nextTick();
|
||||
await click(
|
||||
popover,
|
||||
".o_field_property_definition_type .dropdown-item:nth-child(8)"
|
||||
);
|
||||
await click(popover, ".o_field_property_definition_type .dropdown-item:nth-child(8)");
|
||||
|
||||
// Create 3 tags
|
||||
const tagsInputSelector =
|
||||
".o_property_field_popover .o_field_property_dropdown_menu input";
|
||||
const tagsInputSelector = ".o_property_field_popover .o_field_property_dropdown_menu input";
|
||||
await createNewTag(tagsInputSelector, "A");
|
||||
await createNewTag(tagsInputSelector, "B");
|
||||
await createNewTag(tagsInputSelector, "C");
|
||||
@@ -735,9 +603,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
await closePopover(target);
|
||||
|
||||
// Edit the tags value
|
||||
const tagsComponent = target.querySelector(
|
||||
".o_property_field_value .o_input_dropdown"
|
||||
);
|
||||
const tagsComponent = target.querySelector(".o_property_field_value .o_input_dropdown");
|
||||
await click(target, ".o_property_field_value .o_input_dropdown input");
|
||||
|
||||
// Check that he newly created tags are available
|
||||
@@ -748,10 +614,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
);
|
||||
|
||||
// Select one tag in the list
|
||||
await click(
|
||||
target,
|
||||
".o_property_field_value .ui-menu-item:nth-child(2)"
|
||||
);
|
||||
await click(target, ".o_property_field_value .ui-menu-item:nth-child(2)");
|
||||
assert.deepEqual(
|
||||
getVisibleTags(target, ".o_property_field_value .o_tag"),
|
||||
["B"],
|
||||
@@ -768,10 +631,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
);
|
||||
|
||||
// Create a new tag from the property value component
|
||||
await createNewTag(
|
||||
".o_property_field_value .o_field_property_dropdown_menu input",
|
||||
"D"
|
||||
);
|
||||
await createNewTag(".o_property_field_value .o_field_property_dropdown_menu input", "D");
|
||||
assert.deepEqual(
|
||||
getVisibleTags(target, ".o_property_field_value .o_tag"),
|
||||
["B", "D"],
|
||||
@@ -779,18 +639,10 @@ QUnit.module("Fields", (hooks) => {
|
||||
);
|
||||
|
||||
// Re-open the popover and check that the new tag has been added in the definition
|
||||
await click(
|
||||
target,
|
||||
".o_property_field:nth-child(2) .o_field_property_open_popover"
|
||||
);
|
||||
await click(target, ".o_property_field:nth-child(2) .o_field_property_open_popover");
|
||||
await nextTick();
|
||||
popover = target.querySelector(".o_property_field_popover");
|
||||
assert.deepEqual(getVisibleTags(popover, ".o_tag"), [
|
||||
"A",
|
||||
"B",
|
||||
"C",
|
||||
"D",
|
||||
]);
|
||||
assert.deepEqual(getVisibleTags(popover, ".o_tag"), ["A", "B", "C", "D"]);
|
||||
|
||||
// Change the tag color
|
||||
await click(popover, ".o_tag:nth-child(2)");
|
||||
@@ -803,30 +655,18 @@ QUnit.module("Fields", (hooks) => {
|
||||
|
||||
// Check that the new B color has been propagated in the form view
|
||||
await closePopover(target);
|
||||
secondTag = target.querySelector(
|
||||
".o_property_field_value .o_tag:first-child"
|
||||
);
|
||||
secondTag = target.querySelector(".o_property_field_value .o_tag:first-child");
|
||||
assert.ok(
|
||||
secondTag.classList.contains("o_tag_color_11"),
|
||||
"Should have changed the tag color"
|
||||
);
|
||||
|
||||
// Open the popover and remove B from the definition
|
||||
await click(
|
||||
target,
|
||||
".o_property_field:nth-child(2) .o_field_property_open_popover"
|
||||
);
|
||||
await click(
|
||||
target,
|
||||
".o_property_field_popover .o_tag:nth-child(2) .o_delete"
|
||||
);
|
||||
await click(target, ".o_property_field:nth-child(2) .o_field_property_open_popover");
|
||||
await click(target, ".o_property_field_popover .o_tag:nth-child(2) .o_delete");
|
||||
await closePopover();
|
||||
const tags = target.querySelectorAll(".o_property_field_value .o_tag");
|
||||
assert.strictEqual(
|
||||
tags.length,
|
||||
1,
|
||||
"Should have unselected the removed tag B"
|
||||
);
|
||||
assert.strictEqual(tags.length, 1, "Should have unselected the removed tag B");
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -836,10 +676,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
async function mockRPC(route, { method, model, args, kwargs }) {
|
||||
if (method === "check_access_rights") {
|
||||
return true;
|
||||
} else if (
|
||||
method === "get_available_models" &&
|
||||
model === "ir.model"
|
||||
) {
|
||||
} else if (method === "get_available_models" && model === "ir.model") {
|
||||
return [
|
||||
{ model: "res.partner", display_name: "Partner" },
|
||||
{ model: "res.users", display_name: "User" },
|
||||
@@ -882,72 +719,38 @@ QUnit.module("Fields", (hooks) => {
|
||||
});
|
||||
|
||||
await click(target, ".o_form_button_edit");
|
||||
await click(
|
||||
target,
|
||||
".o_property_field:nth-child(2) .o_field_property_open_popover"
|
||||
);
|
||||
let popover = target.querySelector(".o_property_field_popover");
|
||||
await click(target, ".o_property_field:nth-child(2) .o_field_property_open_popover");
|
||||
const popover = target.querySelector(".o_property_field_popover");
|
||||
// Select the many2one type
|
||||
await click(popover, ".o_field_property_definition_type input");
|
||||
await nextTick();
|
||||
await click(
|
||||
popover,
|
||||
".o_field_property_definition_type .dropdown-item:nth-child(9)"
|
||||
);
|
||||
await click(popover, ".o_field_property_definition_type .dropdown-item:nth-child(9)");
|
||||
|
||||
// Choose the "User" model
|
||||
await click(popover, ".o_field_property_definition_model input");
|
||||
let models = target.querySelectorAll(
|
||||
".o_field_property_definition_model .ui-menu-item"
|
||||
);
|
||||
let models = target.querySelectorAll(".o_field_property_definition_model .ui-menu-item");
|
||||
models = [...models].map((model) => model.innerText);
|
||||
assert.deepEqual(models, ["Partner", "User"]);
|
||||
await click(
|
||||
popover,
|
||||
".o_field_property_definition_model .ui-menu-item:nth-child(2)"
|
||||
);
|
||||
await click(popover, ".o_field_property_definition_model .ui-menu-item:nth-child(2)");
|
||||
|
||||
const selectedModel = target.querySelector(
|
||||
".o_field_property_definition_model input"
|
||||
);
|
||||
assert.strictEqual(
|
||||
selectedModel.value,
|
||||
"User",
|
||||
"Should have selected the User model"
|
||||
);
|
||||
const selectedModel = target.querySelector(".o_field_property_definition_model input");
|
||||
assert.strictEqual(selectedModel.value, "User", "Should have selected the User model");
|
||||
|
||||
// Choose a many2one value
|
||||
await click(popover, ".o_field_property_definition_value input");
|
||||
await click(
|
||||
popover,
|
||||
".o_field_property_definition_value .ui-menu-item:nth-child(3)"
|
||||
);
|
||||
let selectedUser = target.querySelector(
|
||||
".o_field_property_definition_value input"
|
||||
);
|
||||
assert.strictEqual(
|
||||
selectedUser.value,
|
||||
"Eve",
|
||||
"Should have selected the third user"
|
||||
);
|
||||
await click(popover, ".o_field_property_definition_value .ui-menu-item:nth-child(3)");
|
||||
let selectedUser = target.querySelector(".o_field_property_definition_value input");
|
||||
assert.strictEqual(selectedUser.value, "Eve", "Should have selected the third user");
|
||||
|
||||
await closePopover(target);
|
||||
|
||||
// Quick create a user
|
||||
await click(
|
||||
target,
|
||||
".o_property_field:nth-child(2) .o_property_field_value input"
|
||||
);
|
||||
await editInput(
|
||||
target,
|
||||
".o_property_field:nth-child(2) input",
|
||||
"New User"
|
||||
);
|
||||
for (let i = 0; i < 50; ++i) await nextTick(); // wait until the dropdown appears
|
||||
await click(
|
||||
target,
|
||||
".o_property_field:nth-child(2) .o_m2o_dropdown_option_create"
|
||||
);
|
||||
await click(target, ".o_property_field:nth-child(2) .o_property_field_value input");
|
||||
await editInput(target, ".o_property_field:nth-child(2) input", "New User");
|
||||
for (let i = 0; i < 50; ++i) {
|
||||
await nextTick();
|
||||
} // wait until the dropdown appears
|
||||
await click(target, ".o_property_field:nth-child(2) .o_m2o_dropdown_option_create");
|
||||
selectedUser = target.querySelector(
|
||||
".o_property_field:nth-child(2) .o_property_field_value input"
|
||||
);
|
||||
@@ -966,10 +769,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
async function mockRPC(route, { method, model, args, kwargs }) {
|
||||
if (method === "check_access_rights") {
|
||||
return true;
|
||||
} else if (
|
||||
method === "get_available_models" &&
|
||||
model === "ir.model"
|
||||
) {
|
||||
} else if (method === "get_available_models" && model === "ir.model") {
|
||||
return [
|
||||
{ model: "res.partner", display_name: "Partner" },
|
||||
{ model: "res.users", display_name: "User" },
|
||||
@@ -1008,60 +808,33 @@ QUnit.module("Fields", (hooks) => {
|
||||
};
|
||||
|
||||
await click(target, ".o_form_button_edit");
|
||||
await click(
|
||||
target,
|
||||
".o_property_field:nth-child(2) .o_field_property_open_popover"
|
||||
);
|
||||
let popover = target.querySelector(".o_property_field_popover");
|
||||
await click(target, ".o_property_field:nth-child(2) .o_field_property_open_popover");
|
||||
const popover = target.querySelector(".o_property_field_popover");
|
||||
// Select the many2many type
|
||||
await click(popover, ".o_field_property_definition_type input");
|
||||
await nextTick();
|
||||
await click(
|
||||
popover,
|
||||
".o_field_property_definition_type .dropdown-item:nth-child(10)"
|
||||
);
|
||||
await click(popover, ".o_field_property_definition_type .dropdown-item:nth-child(10)");
|
||||
|
||||
// Choose the "User" model
|
||||
await click(popover, ".o_field_property_definition_model input");
|
||||
let models = target.querySelectorAll(
|
||||
".o_field_property_definition_model .ui-menu-item"
|
||||
);
|
||||
let models = target.querySelectorAll(".o_field_property_definition_model .ui-menu-item");
|
||||
models = [...models].map((model) => model.innerText);
|
||||
assert.deepEqual(models, ["Partner", "User"]);
|
||||
await click(
|
||||
popover,
|
||||
".o_field_property_definition_model .ui-menu-item:nth-child(2)"
|
||||
);
|
||||
await click(popover, ".o_field_property_definition_model .ui-menu-item:nth-child(2)");
|
||||
|
||||
const selectedModel = target.querySelector(
|
||||
".o_field_property_definition_model input"
|
||||
);
|
||||
assert.strictEqual(
|
||||
selectedModel.value,
|
||||
"User",
|
||||
"Should have selected the User model"
|
||||
);
|
||||
const selectedModel = target.querySelector(".o_field_property_definition_model input");
|
||||
assert.strictEqual(selectedModel.value, "User", "Should have selected the User model");
|
||||
|
||||
await closePopover(target);
|
||||
|
||||
// Add Eve in the list
|
||||
await click(target, ".o_property_field:nth-child(2) input");
|
||||
await click(
|
||||
target,
|
||||
".o_property_field:nth-child(2) .ui-menu-item:nth-child(3)"
|
||||
);
|
||||
assert.deepEqual(
|
||||
getSelectedUsers(),
|
||||
["Eve"],
|
||||
"Should have selected the third user"
|
||||
);
|
||||
await click(target, ".o_property_field:nth-child(2) .ui-menu-item:nth-child(3)");
|
||||
assert.deepEqual(getSelectedUsers(), ["Eve"], "Should have selected the third user");
|
||||
|
||||
// Add Bob in the list
|
||||
await click(target, ".o_property_field:nth-child(2) input");
|
||||
await click(
|
||||
target,
|
||||
".o_property_field:nth-child(2) .ui-menu-item:nth-child(2)"
|
||||
);
|
||||
await click(target, ".o_property_field:nth-child(2) .ui-menu-item:nth-child(2)");
|
||||
assert.deepEqual(
|
||||
getSelectedUsers(),
|
||||
["Eve", "Bob"],
|
||||
@@ -1069,20 +842,12 @@ QUnit.module("Fields", (hooks) => {
|
||||
);
|
||||
|
||||
// Quick create a user
|
||||
await click(
|
||||
target,
|
||||
".o_property_field:nth-child(2) .o_property_field_value input"
|
||||
);
|
||||
await editInput(
|
||||
target,
|
||||
".o_property_field:nth-child(2) input",
|
||||
"New User"
|
||||
);
|
||||
for (let i = 0; i < 50; ++i) await nextTick(); // wait until the dropdown appears
|
||||
await click(
|
||||
target,
|
||||
".o_property_field:nth-child(2) .o_m2o_dropdown_option_create"
|
||||
);
|
||||
await click(target, ".o_property_field:nth-child(2) .o_property_field_value input");
|
||||
await editInput(target, ".o_property_field:nth-child(2) input", "New User");
|
||||
for (let i = 0; i < 50; ++i) {
|
||||
await nextTick();
|
||||
} // wait until the dropdown appears
|
||||
await click(target, ".o_property_field:nth-child(2) .o_m2o_dropdown_option_create");
|
||||
assert.deepEqual(
|
||||
getSelectedUsers(),
|
||||
["Eve", "Bob", "Created:New User"],
|
||||
@@ -1090,10 +855,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
);
|
||||
|
||||
// Remove Bob from the list
|
||||
await click(
|
||||
target,
|
||||
".o_property_field:nth-child(2) .o_tag:nth-child(2) .o_delete"
|
||||
);
|
||||
await click(target, ".o_property_field:nth-child(2) .o_tag:nth-child(2) .o_delete");
|
||||
assert.deepEqual(
|
||||
getSelectedUsers(),
|
||||
["Eve", "Created:New User"],
|
||||
|
||||
@@ -364,7 +364,8 @@ QUnit.module("Fields", (hooks) => {
|
||||
await click(target.querySelectorAll(".o_notebook .nav .nav-link")[2]);
|
||||
assert.hasClass(target.querySelectorAll(".o_notebook .nav .nav-link")[2], "active");
|
||||
|
||||
height = target.querySelector(".o_field_widget[name=text_field_empty] textarea").offsetHeight;
|
||||
height = target.querySelector(".o_field_widget[name=text_field_empty] textarea")
|
||||
.offsetHeight;
|
||||
assert.strictEqual(height, 50, "empty textarea should have height of 50px");
|
||||
});
|
||||
|
||||
|
||||
@@ -2663,7 +2663,7 @@ QUnit.module("Views", (hooks) => {
|
||||
resId: 2,
|
||||
});
|
||||
|
||||
const buttons = target.querySelectorAll('.o_form_statusbar button');
|
||||
const buttons = target.querySelectorAll(".o_form_statusbar button");
|
||||
assert.strictEqual(buttons[0].attributes.name.textContent, "0");
|
||||
assert.strictEqual(buttons[1].attributes.name.textContent, "1");
|
||||
assert.strictEqual(buttons[2].attributes.name.textContent, "2");
|
||||
|
||||
@@ -53,7 +53,7 @@ QUnit.module("View service", (hooks) => {
|
||||
{
|
||||
resModel: "take.five",
|
||||
views: [[99, "list"]],
|
||||
context: {default_field_value: 1},
|
||||
context: { default_field_value: 1 },
|
||||
},
|
||||
{}
|
||||
);
|
||||
@@ -61,7 +61,7 @@ QUnit.module("View service", (hooks) => {
|
||||
{
|
||||
resModel: "take.five",
|
||||
views: [[99, "list"]],
|
||||
context: {default_field_value: 2},
|
||||
context: { default_field_value: 2 },
|
||||
},
|
||||
{}
|
||||
);
|
||||
@@ -128,7 +128,7 @@ QUnit.module("View service", (hooks) => {
|
||||
{
|
||||
resModel: "take.five",
|
||||
views: [[99, "list"]],
|
||||
context: {default_field_value: 1},
|
||||
context: { default_field_value: 1 },
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
getFixture,
|
||||
legacyExtraNextTick,
|
||||
nextTick,
|
||||
patchWithCleanup
|
||||
patchWithCleanup,
|
||||
} from "../../helpers/utils";
|
||||
import { createWebClient, doAction, getActionManagerServerData } from "./../helpers";
|
||||
|
||||
@@ -214,7 +214,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
await legacyExtraNextTick();
|
||||
readOnFirstRecordDef.reject(new Error("not working as intended"));
|
||||
await nextTick();
|
||||
assert.verifySteps(["error"])
|
||||
assert.verifySteps(["error"]);
|
||||
assert.containsOnce(target, ".o_list_view", "there should still be a list view in dom");
|
||||
// open another record, the read will not crash
|
||||
await testUtils.dom.click(
|
||||
|
||||
@@ -230,7 +230,9 @@ export async function addLegacyMockEnvironment(env, legacyParams = {}) {
|
||||
let rejectFn;
|
||||
const rpcProm = new Promise((resolve, reject) => {
|
||||
rejectFn = reject;
|
||||
rpc(...arguments).then(resolve).catch(reject);
|
||||
rpc(...arguments)
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
rpcProm.abort = () => rejectFn(new ConnectionAbortedError("XmlHttpRequestError abort"));
|
||||
return rpcProm;
|
||||
|
||||
Reference in New Issue
Block a user