[IMP] web: use original router service in tests
Before this commit, the router service used in tests was almost entirely mocked and some of the mocked features didn't correspond to their original counterparts. Now, the actual router service is used in tests and the browser object is mocked instead, allowing to correctly test the routing features.
This commit is contained in:
committed by
Aaron Bohy
parent
36e03d1b98
commit
d2985c033e
@@ -25,6 +25,7 @@ export const browser = Object.assign({}, owl.browser, {
|
||||
removeEventListener: window.removeEventListener.bind(window),
|
||||
console: window.console,
|
||||
location: window.location,
|
||||
history: window.history,
|
||||
navigator: navigator,
|
||||
open: window.open.bind(window),
|
||||
XMLHttpRequest: window.XMLHttpRequest,
|
||||
|
||||
@@ -5,6 +5,11 @@ import { shallowEqual } from "../utils/objects";
|
||||
import { objectToUrlEncodedString } from "../utils/urls";
|
||||
import { browser } from "./browser";
|
||||
|
||||
/**
|
||||
* @typedef {{ [key: string]: string }} Query
|
||||
* @typedef {{ [key: string]: any }} Route
|
||||
*/
|
||||
|
||||
function parseString(str) {
|
||||
const parts = str.split("&");
|
||||
const result = {};
|
||||
@@ -16,139 +21,161 @@ function parseString(str) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* For each push request (replaceState or pushState), filterout keys that have been locked before
|
||||
* overrides locked keys that are explicitly re-locked or unlocked
|
||||
* registers keys in "hash" in "lockedKeys" according to the "lock" Boolean
|
||||
*
|
||||
* @param {Set<string>} lockedKeys A set containing all keys that were locked
|
||||
* @param {Query} hash An Object representing the pushed url hash
|
||||
* @param {Query} currentHash The current hash compare against
|
||||
* @param {Object} [options={}] Whether to lock all hash keys in "hash" to prevent them from being changed afterwards
|
||||
* @param {Boolean} [options.lock] Whether to lock all hash keys in "hash" to prevent them from being changed afterwards
|
||||
* @return {Query} The resulting "hash" where previous locking has been applied
|
||||
*/
|
||||
function applyLocking(lockedKeys, hash, currentHash, options = {}) {
|
||||
const newHash = {};
|
||||
for (const key in hash) {
|
||||
if ("lock" in options) {
|
||||
options.lock ? lockedKeys.add(key) : lockedKeys.delete(key);
|
||||
} else if (lockedKeys.has(key)) {
|
||||
// forbid implicit override of key
|
||||
continue;
|
||||
}
|
||||
newHash[key] = hash[key];
|
||||
}
|
||||
for (const key in currentHash) {
|
||||
if (lockedKeys.has(key) && !(key in newHash)) {
|
||||
newHash[key] = currentHash[key];
|
||||
}
|
||||
}
|
||||
return newHash;
|
||||
}
|
||||
|
||||
function computeNewRoute(hash, replace, currentRoute) {
|
||||
if (!replace) {
|
||||
hash = Object.assign({}, currentRoute.hash, hash);
|
||||
}
|
||||
hash = sanitizeHash(hash);
|
||||
if (!shallowEqual(currentRoute.hash, hash)) {
|
||||
return Object.assign({}, currentRoute, { hash });
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function sanitizeHash(hash) {
|
||||
return Object.fromEntries(Object.entries(hash).filter(([, v]) => v !== undefined));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} hash
|
||||
* @returns {any}
|
||||
*/
|
||||
export function parseHash(hash) {
|
||||
return hash === "#" || hash === "" ? {} : parseString(hash.slice(1));
|
||||
return hash && hash !== "#" ? parseString(hash.slice(1)) : {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} search
|
||||
* @returns {any}
|
||||
*/
|
||||
export function parseSearchQuery(search) {
|
||||
return search === "" ? {} : parseString(search.slice(1));
|
||||
return search ? parseString(search.slice(1)) : {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ [key: string]: any }} route
|
||||
* @returns
|
||||
*/
|
||||
export function routeToUrl(route) {
|
||||
const search = objectToUrlEncodedString(route.search);
|
||||
const hash = objectToUrlEncodedString(route.hash);
|
||||
return route.pathname + (search ? "?" + search : "") + (hash ? "#" + hash : "");
|
||||
}
|
||||
|
||||
export function redirect(env, url, wait) {
|
||||
const load = () => browser.location.assign(url);
|
||||
async function redirect(env, url, wait = false) {
|
||||
if (wait) {
|
||||
const wait_server = function () {
|
||||
env.services
|
||||
.rpc("/web/webclient/version_info", {})
|
||||
.then(load)
|
||||
.catch(() => browser.setTimeout(wait_server, 250));
|
||||
};
|
||||
browser.setTimeout(wait_server, 1000);
|
||||
} else {
|
||||
load();
|
||||
await new Promise((resolve) => {
|
||||
const waitForServer = (delay) => {
|
||||
browser.setTimeout(async () => {
|
||||
env.services
|
||||
.rpc("/web/webclient/version_info", {})
|
||||
.then(resolve)
|
||||
.catch(() => waitForServer(250));
|
||||
}, delay);
|
||||
};
|
||||
waitForServer(1000);
|
||||
});
|
||||
}
|
||||
browser.location.assign(url);
|
||||
}
|
||||
|
||||
function getRoute() {
|
||||
const { pathname, search, hash } = window.location;
|
||||
const { pathname, search, hash } = browser.location;
|
||||
const searchQuery = parseSearchQuery(search);
|
||||
const hashQuery = parseHash(hash);
|
||||
return { pathname, search: searchQuery, hash: hashQuery };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {function} getCurrent function that returns the current route
|
||||
* @returns {function} function to compute the next hash
|
||||
*/
|
||||
export function makePreProcessQuery(getCurrent) {
|
||||
const lockedKeys = new Set();
|
||||
return (hash) => {
|
||||
const newHash = {};
|
||||
Object.keys(hash).forEach((key) => {
|
||||
if (lockedKeys.has(key)) {
|
||||
return;
|
||||
}
|
||||
const k = key.split(" ");
|
||||
let value;
|
||||
if (k.length === 2) {
|
||||
value = hash[key];
|
||||
key = k[1];
|
||||
if (k[0] === "lock") {
|
||||
lockedKeys.add(key);
|
||||
} else if (k[0] === "unlock") {
|
||||
lockedKeys.delete(key);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
newHash[key] = value || hash[key];
|
||||
});
|
||||
const current = getCurrent();
|
||||
Object.keys(current.hash).forEach((key) => {
|
||||
if (lockedKeys.has(key) && !(key in newHash)) {
|
||||
newHash[key] = current.hash[key];
|
||||
}
|
||||
});
|
||||
return newHash;
|
||||
};
|
||||
}
|
||||
|
||||
function makeRouter(env) {
|
||||
let bus = env.bus;
|
||||
const bus = env.bus;
|
||||
const lockedKeys = new Set();
|
||||
let current = getRoute();
|
||||
let pushTimeout;
|
||||
browser.addEventListener("hashchange", () => {
|
||||
current = getRoute();
|
||||
bus.trigger("ROUTE_CHANGE");
|
||||
});
|
||||
|
||||
function doPush(mode = "push", route) {
|
||||
if (!shallowEqual(route.hash, current.hash)) {
|
||||
const url = location.origin + routeToUrl(route);
|
||||
if (mode === "push") {
|
||||
window.history.pushState({}, url, url);
|
||||
} else {
|
||||
window.history.replaceState({}, url, url);
|
||||
/**
|
||||
* @param {string} mode
|
||||
* @returns {(hash: string, options: any) => any}
|
||||
*/
|
||||
function makeDebouncedPush(mode) {
|
||||
let allPushArgs = [];
|
||||
function doPush() {
|
||||
// Aggregates push/replace state arguments
|
||||
const replace = allPushArgs.some(([, options]) => options && options.replace);
|
||||
const newHash = allPushArgs.reduce((finalHash, [hash, options]) => {
|
||||
hash = applyLocking(lockedKeys, hash, current.hash, options);
|
||||
if (finalHash) {
|
||||
hash = applyLocking(lockedKeys, hash, finalHash, options);
|
||||
}
|
||||
return Object.assign(finalHash || {}, hash);
|
||||
}, null);
|
||||
// Calculates new route based on aggregated hash and options
|
||||
const newRoute = computeNewRoute(newHash, replace, current);
|
||||
if (!newRoute) {
|
||||
return;
|
||||
}
|
||||
// If the route changed: pushes or replaces browser state
|
||||
const url = browser.location.origin + routeToUrl(newRoute);
|
||||
if (mode === "push") {
|
||||
browser.history.pushState({}, "", url);
|
||||
} else {
|
||||
browser.history.replaceState({}, "", url);
|
||||
}
|
||||
current = getRoute();
|
||||
}
|
||||
current = getRoute();
|
||||
return function pushOrReplaceState(hash, options) {
|
||||
allPushArgs.push([hash, options]);
|
||||
browser.clearTimeout(pushTimeout);
|
||||
pushTimeout = browser.setTimeout(() => {
|
||||
doPush();
|
||||
pushTimeout = null;
|
||||
allPushArgs = [];
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function getCurrent() {
|
||||
return current;
|
||||
}
|
||||
|
||||
const preProcessQuery = makePreProcessQuery(getCurrent);
|
||||
return {
|
||||
get current() {
|
||||
return getCurrent();
|
||||
return current;
|
||||
},
|
||||
pushState: makePushState(getCurrent, doPush.bind(null, "push"), preProcessQuery),
|
||||
replaceState: makePushState(getCurrent, doPush.bind(null, "replace"), preProcessQuery),
|
||||
pushState: makeDebouncedPush("push"),
|
||||
replaceState: makeDebouncedPush("replace"),
|
||||
redirect: (url, wait) => redirect(env, url, wait),
|
||||
};
|
||||
}
|
||||
|
||||
export function makePushState(getCurrent, doPush, preProcessQuery) {
|
||||
let _replace = false;
|
||||
let timeoutId;
|
||||
let tempHash;
|
||||
return (hash, replace = false) => {
|
||||
clearTimeout(timeoutId);
|
||||
hash = preProcessQuery(hash);
|
||||
_replace = _replace || replace;
|
||||
tempHash = Object.assign(tempHash || {}, hash);
|
||||
timeoutId = setTimeout(() => {
|
||||
tempHash = sanitizeHash(tempHash);
|
||||
const current = getCurrent();
|
||||
if (!_replace) {
|
||||
tempHash = Object.assign({}, current.hash, tempHash);
|
||||
}
|
||||
const route = Object.assign({}, current, { hash: tempHash });
|
||||
doPush(route);
|
||||
tempHash = undefined;
|
||||
timeoutId = undefined;
|
||||
_replace = false;
|
||||
});
|
||||
cancelPushes: () => browser.clearTimeout(pushTimeout),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -161,7 +188,7 @@ export const routerService = {
|
||||
export function objectToQuery(obj) {
|
||||
const query = {};
|
||||
Object.entries(obj).forEach(([k, v]) => {
|
||||
query[k] = v ? `${v}` : v;
|
||||
query[k] = v ? String(v) : v;
|
||||
});
|
||||
return query;
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ export const userService = {
|
||||
};
|
||||
|
||||
const stringCIds = allowedCompanies.join(",");
|
||||
router.replaceState({ "lock cids": stringCIds });
|
||||
router.replaceState({ cids: stringCIds }, { lock: true });
|
||||
cookie.setCookie("cids", stringCIds);
|
||||
|
||||
const setCompanies = makeSetCompanies(() => allowedCompanies);
|
||||
@@ -126,7 +126,7 @@ export const userService = {
|
||||
showEffect,
|
||||
setCompanies: (mode, companyId) => {
|
||||
const nextCompanyIds = setCompanies(mode, companyId).join(",");
|
||||
router.pushState({ "lock cids": nextCompanyIds });
|
||||
router.pushState({ cids: nextCompanyIds }, { lock: true });
|
||||
cookie.setCookie("cids", nextCompanyIds);
|
||||
browser.setTimeout(() => window.location.reload()); // history.pushState is a little async
|
||||
},
|
||||
|
||||
@@ -1162,7 +1162,7 @@ function makeActionManager(env) {
|
||||
const newState = {};
|
||||
const action = controller.action;
|
||||
if (action.id) {
|
||||
newState.action = `${action.id}`;
|
||||
newState.action = action.id;
|
||||
} else if (action.type === "ir.actions.client") {
|
||||
newState.action = action.tag;
|
||||
}
|
||||
@@ -1182,7 +1182,7 @@ function makeActionManager(env) {
|
||||
newState.view_type = props.type;
|
||||
newState.id = props.recordId ? `${props.recordId}` : undefined;
|
||||
}
|
||||
env.services.router.pushState(newState, true);
|
||||
env.services.router.pushState(newState, { replace: true });
|
||||
}
|
||||
return {
|
||||
doAction,
|
||||
|
||||
@@ -62,9 +62,7 @@ function makeMenus(env, menusData, fetchLoadMenus) {
|
||||
env.bus.trigger("MENUS:APP-CHANGED");
|
||||
// FIXME: lock API: maybe do something like
|
||||
// pushState({menu_id: ...}, { lock: true}); ?
|
||||
env.services.router.pushState({
|
||||
"lock menu_id": `${menu.id}`,
|
||||
});
|
||||
env.services.router.pushState({ menu_id: menu.id }, { lock: true });
|
||||
}
|
||||
},
|
||||
async reload() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="web.UserMenu.ShortCutsDialog">
|
||||
<t t-name="web.UserMenu.ShortCutsDialog" owl="1">
|
||||
<Dialog title="props.title">
|
||||
<t t-call="web.UserMenu.shortcutsTable"/>
|
||||
</Dialog>
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import {
|
||||
parseHash,
|
||||
parseSearchQuery,
|
||||
redirect,
|
||||
routeToUrl,
|
||||
} from "@web/core/browser/router_service";
|
||||
import { patch, unpatch } from "@web/core/utils/patch";
|
||||
import { parseHash, parseSearchQuery, routeToUrl } from "@web/core/browser/router_service";
|
||||
import { makeTestEnv } from "../helpers/mock_env";
|
||||
import { nextTick } from "../helpers/utils";
|
||||
import { makeFakeRouterService } from "../helpers/mock_services";
|
||||
import { nextTick, patchWithCleanup } from "../helpers/utils";
|
||||
|
||||
async function createRouter(params = {}) {
|
||||
const env = params.env || {};
|
||||
env.bus = env.bus || new owl.core.EventBus();
|
||||
const router = await makeFakeRouterService(params).start(env);
|
||||
return router;
|
||||
}
|
||||
|
||||
QUnit.module("Router");
|
||||
|
||||
@@ -76,47 +78,179 @@ QUnit.test("can parse URI encoded strings", (assert) => {
|
||||
QUnit.test("routeToUrl encodes URI compatible strings", (assert) => {
|
||||
const route = { pathname: "/asf", search: {}, hash: {} };
|
||||
assert.strictEqual(routeToUrl(route), "/asf");
|
||||
|
||||
route.search = { a: "11", g: "summer wine" };
|
||||
assert.strictEqual(routeToUrl(route), "/asf?a=11&g=summer%20wine");
|
||||
|
||||
route.hash = { b: "2", c: "", e: "kloug,gloubi" };
|
||||
assert.strictEqual(routeToUrl(route), "/asf?a=11&g=summer%20wine#b=2&c&e=kloug%2Cgloubi");
|
||||
});
|
||||
|
||||
QUnit.test("can redirect an URL", async (assert) => {
|
||||
patch(browser, "test.location", {
|
||||
location: {
|
||||
assign(url) {
|
||||
assert.step(url);
|
||||
},
|
||||
},
|
||||
setTimeout: (handler, delay) => {
|
||||
patchWithCleanup(browser, {
|
||||
setTimeout(handler, delay) {
|
||||
handler();
|
||||
assert.step(String(delay));
|
||||
assert.step(`timeout: ${delay}`);
|
||||
},
|
||||
});
|
||||
let firstCheckServer = true;
|
||||
const env = await makeTestEnv({
|
||||
browser,
|
||||
mockRPC(...args) {
|
||||
if (args[0] === "/web/webclient/version_info") {
|
||||
async mockRPC(route) {
|
||||
if (route === "/web/webclient/version_info") {
|
||||
if (firstCheckServer) {
|
||||
firstCheckServer = false;
|
||||
return;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
},
|
||||
});
|
||||
redirect(env, "/my/test/url");
|
||||
assert.verifySteps(["/my/test/url"]);
|
||||
redirect(env, "/my/test/url/2", true);
|
||||
const onRedirect = (url) => assert.step(url);
|
||||
const router = await createRouter({ env, onRedirect });
|
||||
|
||||
router.redirect("/my/test/url");
|
||||
await nextTick();
|
||||
assert.verifySteps(["1000", "250", "/my/test/url/2"]);
|
||||
unpatch(browser, "test.location");
|
||||
assert.verifySteps(["/my/test/url"]);
|
||||
|
||||
router.redirect("/my/test/url/2", true);
|
||||
await nextTick();
|
||||
assert.verifySteps(["timeout: 1000", "timeout: 250", "/my/test/url/2"]);
|
||||
});
|
||||
|
||||
// TODO: test
|
||||
// - push/replaceState requests occuring in the same tick are aggregated
|
||||
// - we don't push twice the same url (different key orders)
|
||||
// - undefined keys are not pushed in the url
|
||||
// - lock stuff
|
||||
QUnit.module("Router: Push state");
|
||||
|
||||
QUnit.test("can push in same timeout", async (assert) => {
|
||||
const router = await createRouter();
|
||||
|
||||
assert.deepEqual(router.current.hash, {});
|
||||
|
||||
router.pushState({ k1: 2 });
|
||||
assert.deepEqual(router.current.hash, {});
|
||||
|
||||
router.pushState({ k1: 3 });
|
||||
assert.deepEqual(router.current.hash, {});
|
||||
await nextTick();
|
||||
assert.deepEqual(router.current.hash, { k1: 3 });
|
||||
});
|
||||
|
||||
QUnit.test("can lock keys", async (assert) => {
|
||||
const router = await createRouter();
|
||||
|
||||
router.pushState({ k1: 2 }, { lock: true });
|
||||
await nextTick();
|
||||
assert.deepEqual(router.current.hash, { k1: 2 });
|
||||
|
||||
router.pushState({ k1: 3 });
|
||||
await nextTick();
|
||||
assert.deepEqual(router.current.hash, { k1: 2 });
|
||||
|
||||
router.pushState({ k1: 4 }, { lock: true });
|
||||
await nextTick();
|
||||
assert.deepEqual(router.current.hash, { k1: 4 });
|
||||
|
||||
router.pushState({ k1: 3 });
|
||||
await nextTick();
|
||||
assert.deepEqual(router.current.hash, { k1: 4 });
|
||||
});
|
||||
|
||||
QUnit.test("can re-lock keys in same final call", async (assert) => {
|
||||
const router = await createRouter();
|
||||
|
||||
router.pushState({ k1: 2 }, { lock: true });
|
||||
await nextTick();
|
||||
router.pushState({ k1: 1 }, { lock: true });
|
||||
router.pushState({ k1: 4 });
|
||||
await nextTick();
|
||||
assert.deepEqual(router.current.hash, { k1: 1 });
|
||||
});
|
||||
|
||||
QUnit.test("can unlock keys", async (assert) => {
|
||||
const router = await createRouter();
|
||||
|
||||
router.pushState({ k1: 2 }, { lock: true });
|
||||
await nextTick();
|
||||
assert.deepEqual(router.current.hash, { k1: 2 });
|
||||
|
||||
router.pushState({ k1: 3 });
|
||||
await nextTick();
|
||||
assert.deepEqual(router.current.hash, { k1: 2 });
|
||||
|
||||
router.pushState({ k1: 4 }, { lock: false });
|
||||
await nextTick();
|
||||
assert.deepEqual(router.current.hash, { k1: 4 });
|
||||
|
||||
router.pushState({ k1: 3 });
|
||||
await nextTick();
|
||||
assert.deepEqual(router.current.hash, { k1: 3 });
|
||||
});
|
||||
|
||||
QUnit.test("can replace hash", async (assert) => {
|
||||
const router = await createRouter();
|
||||
|
||||
router.pushState({ k1: 2 });
|
||||
await nextTick();
|
||||
assert.deepEqual(router.current.hash, { k1: 2 });
|
||||
|
||||
router.pushState({ k2: 3 }, { replace: true });
|
||||
await nextTick();
|
||||
assert.deepEqual(router.current.hash, { k2: 3 });
|
||||
});
|
||||
|
||||
QUnit.test("can replace hash with locked keys", async (assert) => {
|
||||
const router = await createRouter();
|
||||
|
||||
router.pushState({ k1: 2 }, { lock: true });
|
||||
await nextTick();
|
||||
assert.deepEqual(router.current.hash, { k1: 2 });
|
||||
|
||||
router.pushState({ k2: 3 }, { replace: true });
|
||||
await nextTick();
|
||||
assert.deepEqual(router.current.hash, { k1: 2, k2: 3 });
|
||||
});
|
||||
|
||||
QUnit.test("can merge hash", async (assert) => {
|
||||
const router = await createRouter();
|
||||
|
||||
router.pushState({ k1: 2 });
|
||||
await nextTick();
|
||||
assert.deepEqual(router.current.hash, { k1: 2 });
|
||||
|
||||
router.pushState({ k2: 3 });
|
||||
await nextTick();
|
||||
assert.deepEqual(router.current.hash, { k1: 2, k2: 3 });
|
||||
});
|
||||
|
||||
QUnit.test("undefined keys are not pushed", async (assert) => {
|
||||
const onPushState = () => assert.step("pushed state");
|
||||
const router = await createRouter({ onPushState });
|
||||
|
||||
router.pushState({ k1: undefined });
|
||||
await nextTick();
|
||||
assert.verifySteps([]);
|
||||
assert.deepEqual(router.current.hash, {});
|
||||
});
|
||||
|
||||
QUnit.test("undefined keys destroy previous non locked keys", async (assert) => {
|
||||
const router = await createRouter();
|
||||
|
||||
router.pushState({ k1: 1 });
|
||||
await nextTick();
|
||||
assert.deepEqual(router.current.hash, { k1: 1 });
|
||||
|
||||
router.pushState({ k1: undefined });
|
||||
await nextTick();
|
||||
assert.deepEqual(router.current.hash, {});
|
||||
});
|
||||
|
||||
QUnit.test("do not re-push when hash is same", async (assert) => {
|
||||
const onPushState = () => assert.step("pushed state");
|
||||
const router = await createRouter({ onPushState });
|
||||
|
||||
router.pushState({ k1: 1, k2: 2 });
|
||||
await nextTick();
|
||||
assert.verifySteps(["pushed state"]);
|
||||
|
||||
router.pushState({ k2: 2, k1: 1 });
|
||||
await nextTick();
|
||||
assert.verifySteps([]);
|
||||
});
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { makePreProcessQuery, makePushState, routeToUrl } from "@web/core/browser/router_service";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { routerService } from "@web/core/browser/router_service";
|
||||
import { localization } from "@web/core/l10n/localization";
|
||||
import { translatedTerms } from "@web/core/l10n/translation";
|
||||
import { rpcService } from "@web/core/network/rpc_service";
|
||||
import { SIZES } from "@web/core/ui_service";
|
||||
import { computeAllowedCompanyIds, makeSetCompanies } from "@web/core/user_service";
|
||||
import { effectService } from "@web/webclient/effects/effect_service";
|
||||
import { objectToUrlEncodedString } from "../../src/core/utils/urls";
|
||||
import { registerCleanup } from "./cleanup";
|
||||
import { patchWithCleanup } from "./utils";
|
||||
|
||||
const { Component } = owl;
|
||||
@@ -27,7 +30,7 @@ export const defaultLocalization = {
|
||||
weekStart: 7,
|
||||
};
|
||||
|
||||
export function makeFakeLocalizationService(config) {
|
||||
export function makeFakeLocalizationService() {
|
||||
patchWithCleanup(localization, defaultLocalization);
|
||||
|
||||
return {
|
||||
@@ -49,7 +52,7 @@ export function makeFakeUserService(values) {
|
||||
const { uid, name, username, is_admin, user_companies, partner_id, user_context } = sessionInfo;
|
||||
return {
|
||||
name: "user",
|
||||
start(env) {
|
||||
start() {
|
||||
let allowedCompanies = computeAllowedCompanyIds();
|
||||
const setCompanies = makeSetCompanies(() => allowedCompanies);
|
||||
const context = {
|
||||
@@ -170,64 +173,70 @@ export function makeMockFetch(mockRPC) {
|
||||
};
|
||||
}
|
||||
|
||||
function stripUndefinedQueryKey(query) {
|
||||
const keyValArray = Array.from(Object.entries(query)).filter(([k, v]) => v !== undefined);
|
||||
// transform to Object.fromEntries in es > 2019
|
||||
const newObj = {};
|
||||
keyValArray.forEach(([k, v]) => {
|
||||
newObj[k] = v;
|
||||
export function makeMockLocation() {
|
||||
const locationLink = Object.assign(document.createElement("a"), {
|
||||
href: window.location.origin + window.location.pathname,
|
||||
assign(url) {
|
||||
this.href = url;
|
||||
},
|
||||
});
|
||||
return new Proxy(locationLink, {
|
||||
get(target, p) {
|
||||
return target[p];
|
||||
},
|
||||
set(target, p, value) {
|
||||
target[p] = value;
|
||||
if (p === "hash") {
|
||||
window.dispatchEvent(new HashChangeEvent("hashchange"));
|
||||
}
|
||||
return true;
|
||||
},
|
||||
});
|
||||
return newObj;
|
||||
}
|
||||
|
||||
function getRoute(route) {
|
||||
route.hash = stripUndefinedQueryKey(route.hash);
|
||||
route.search = stripUndefinedQueryKey(route.search);
|
||||
return route;
|
||||
}
|
||||
|
||||
export function makeFakeRouterService(params) {
|
||||
let _current = {
|
||||
pathname: "test.wowl",
|
||||
search: {},
|
||||
hash: {},
|
||||
};
|
||||
if (params && params.initialRoute) {
|
||||
Object.assign(_current, params.initialRoute);
|
||||
}
|
||||
let current = getRoute(_current);
|
||||
return {
|
||||
start(env) {
|
||||
function loadState(hash) {
|
||||
current.hash = hash;
|
||||
env.bus.trigger("ROUTE_CHANGE");
|
||||
}
|
||||
env.bus.on("test:hashchange", null, loadState);
|
||||
function getCurrent() {
|
||||
return current;
|
||||
}
|
||||
function doPush(mode = "push", route) {
|
||||
const oldUrl = routeToUrl(current);
|
||||
const newRoute = getRoute(route);
|
||||
const newUrl = routeToUrl(newRoute);
|
||||
if (params && params.onPushState && oldUrl !== newUrl) {
|
||||
params.onPushState(mode, route.hash);
|
||||
/**
|
||||
* @param {Object} [params={}]
|
||||
* @param {Object} [params.initialRoute] initial route object
|
||||
* @param {Object} [params.onPushState] hook on the "pushState" method
|
||||
* @param {Object} [params.onRedirect] hook on the "redirect" method
|
||||
* @returns {RouterService}
|
||||
*/
|
||||
export function makeFakeRouterService(params = {}) {
|
||||
const mockLocation = makeMockLocation();
|
||||
Object.assign(mockLocation, params.initialRoute);
|
||||
patchWithCleanup(browser, {
|
||||
location: mockLocation,
|
||||
history: {
|
||||
pushState(state, title, url) {
|
||||
mockLocation.assign(url);
|
||||
if (params.onPushState) {
|
||||
params.onPushState(url);
|
||||
}
|
||||
current = newRoute;
|
||||
}
|
||||
const preProcessQuery = makePreProcessQuery(getCurrent);
|
||||
return {
|
||||
get current() {
|
||||
return getCurrent();
|
||||
},
|
||||
replaceState(state, title, url) {
|
||||
mockLocation.assign(url);
|
||||
if (params.onReplaceState) {
|
||||
params.onReplaceState(url);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
return {
|
||||
start({ bus }) {
|
||||
const router = routerService.start(...arguments);
|
||||
bus.on("test:hashchange", null, (hash) => {
|
||||
mockLocation.hash = objectToUrlEncodedString(hash);
|
||||
});
|
||||
registerCleanup(router.cancelPushes);
|
||||
patchWithCleanup(router, {
|
||||
async redirect() {
|
||||
await this._super(...arguments);
|
||||
if (params.onRedirect) {
|
||||
params.onRedirect(...arguments);
|
||||
}
|
||||
},
|
||||
pushState: makePushState(getCurrent, doPush.bind(null, "push"), preProcessQuery),
|
||||
replaceState: makePushState(
|
||||
getCurrent,
|
||||
doPush.bind(null, "replace"),
|
||||
preProcessQuery
|
||||
),
|
||||
redirect: (params && params.redirect) || (() => {}),
|
||||
};
|
||||
});
|
||||
return router;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -275,7 +284,7 @@ export const fakeCookieService = {
|
||||
get current() {
|
||||
return cookie;
|
||||
},
|
||||
setCookie(key, value, ttl) {
|
||||
setCookie(key, value) {
|
||||
if (value !== undefined) {
|
||||
cookie[key] = value;
|
||||
}
|
||||
|
||||
+11
-10
@@ -1,12 +1,11 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { patch, unpatch } from "@web/core/utils/patch";
|
||||
import * as LegacyRegistry from "web.Registry";
|
||||
import core from "web.core";
|
||||
import AbstractAction from "web.AbstractAction";
|
||||
import core from "web.core";
|
||||
import * as LegacyRegistry from "web.Registry";
|
||||
import { registerCleanup } from "../../helpers/cleanup";
|
||||
import { nextTick } from "../../helpers/utils";
|
||||
import { nextTick, patchWithCleanup } from "../../helpers/utils";
|
||||
import { createWebClient, doAction, getActionManagerTestConfig } from "../../webclient/actions/helpers";
|
||||
|
||||
let testConfig;
|
||||
@@ -26,11 +25,14 @@ QUnit.module("Service Provider Adapter Notification", (hooks) => {
|
||||
assert.expect(8);
|
||||
let notifId;
|
||||
let timeoutCB;
|
||||
patch(browser, "mock.settimeout.cb", {
|
||||
setTimeout: (cb, t) => {
|
||||
timeoutCB = cb;
|
||||
assert.step("time: " + t);
|
||||
return 1;
|
||||
patchWithCleanup(browser, {
|
||||
setTimeout: (cb, delay) => {
|
||||
if (!delay) {
|
||||
return; // Timeouts from router service
|
||||
}
|
||||
timeoutCB = cb;
|
||||
assert.step("time: " + delay);
|
||||
return 1;
|
||||
},
|
||||
});
|
||||
const NotifyAction = AbstractAction.extend({
|
||||
@@ -75,7 +77,6 @@ QUnit.module("Service Provider Adapter Notification", (hooks) => {
|
||||
await nextTick();
|
||||
assert.containsNone(document.body, ".o_notification");
|
||||
assert.verifySteps(["time: 3000"]);
|
||||
unpatch(browser, "mock.settimeout.cb");
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@@ -58,14 +58,14 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
);
|
||||
assert.strictEqual(webClient.el.querySelector(".o_menu_brand").textContent, "App2");
|
||||
assert.deepEqual(webClient.env.services.router.current.hash, {
|
||||
action: "1001",
|
||||
menu_id: "2",
|
||||
action: 1001,
|
||||
menu_id: 2,
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test("initial loading with action id", async (assert) => {
|
||||
assert.expect(4);
|
||||
const hash = { action: 1001 };
|
||||
const hash = "#action=1001";
|
||||
serviceRegistry.add("router", makeFakeRouterService({ initialRoute: { hash } }), {
|
||||
force: true,
|
||||
});
|
||||
@@ -82,7 +82,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
|
||||
QUnit.test("initial loading with action tag", async (assert) => {
|
||||
assert.expect(3);
|
||||
const hash = { action: "__test__client__action__" };
|
||||
const hash = "#action=__test__client__action__";
|
||||
serviceRegistry.add("router", makeFakeRouterService({ initialRoute: { hash } }), {
|
||||
force: true,
|
||||
});
|
||||
@@ -112,11 +112,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
|
||||
QUnit.test("correctly sends additional context", async (assert) => {
|
||||
assert.expect(1);
|
||||
const hash = {
|
||||
action: 1001,
|
||||
active_id: 4,
|
||||
active_ids: "4,8",
|
||||
};
|
||||
const hash = "#action=1001&active_id=4&active_ids=4,8";
|
||||
serviceRegistry.add("router", makeFakeRouterService({ initialRoute: { hash } }), {
|
||||
force: true,
|
||||
});
|
||||
@@ -248,7 +244,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
1: { id: 1, children: [], name: "App1", appID: 1, actionID: 1001, xmlid: "menu_1" },
|
||||
2: { id: 2, children: [], name: "App2", appID: 2, actionID: 1002, xmlid: "menu_2" },
|
||||
};
|
||||
const hash = { id: 2, model: "partner" };
|
||||
const hash = "#id=2&model=partner";
|
||||
serviceRegistry.add("router", makeFakeRouterService({ initialRoute: { hash } }), {
|
||||
force: true,
|
||||
});
|
||||
@@ -533,7 +529,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
await loadState(webClient, { action: 3 });
|
||||
currentHash = webClient.env.services.router.current.hash;
|
||||
assert.deepEqual(currentHash, {
|
||||
action: "3",
|
||||
action: 3,
|
||||
model: "partner",
|
||||
view_type: "list",
|
||||
});
|
||||
@@ -542,8 +538,8 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
await legacyExtraNextTick();
|
||||
currentHash = webClient.env.services.router.current.hash;
|
||||
assert.deepEqual(currentHash, {
|
||||
action: "3",
|
||||
id: "1",
|
||||
action: 3,
|
||||
id: 1,
|
||||
model: "partner",
|
||||
view_type: "form",
|
||||
});
|
||||
@@ -585,7 +581,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
await loadState(webClient, { action: 9 });
|
||||
currentHash = webClient.env.services.router.current.hash;
|
||||
assert.deepEqual(currentHash, {
|
||||
action: "9",
|
||||
action: 9,
|
||||
});
|
||||
assert.verifySteps([], "should not push the loaded state");
|
||||
await testUtils.dom.click($(webClient.el).find("#client_action_button"));
|
||||
@@ -593,14 +589,14 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
assert.verifySteps(["push_state"], "should push the state of it changes afterwards");
|
||||
currentHash = webClient.env.services.router.current.hash;
|
||||
assert.deepEqual(currentHash, {
|
||||
action: "9",
|
||||
action: 9,
|
||||
someValue: "X",
|
||||
});
|
||||
delete core.action_registry.map.ClientAction;
|
||||
});
|
||||
|
||||
QUnit.test("change a param of an ir.actions.client in the url", async function (assert) {
|
||||
assert.expect(13);
|
||||
assert.expect(12);
|
||||
const ClientAction = AbstractAction.extend({
|
||||
hasControlPanel: true,
|
||||
init: function (parent, action) {
|
||||
@@ -622,8 +618,8 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
serviceRegistry.add(
|
||||
"router",
|
||||
makeFakeRouterService({
|
||||
onPushState(mode) {
|
||||
assert.step(`push_state ${mode}`);
|
||||
onPushState() {
|
||||
assert.step("push_state");
|
||||
},
|
||||
}),
|
||||
{ force: true }
|
||||
@@ -634,10 +630,10 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
assert.deepEqual(currentHash, {});
|
||||
// execute the client action
|
||||
await doAction(webClient, 9);
|
||||
assert.verifySteps(["start", "push_state push"]);
|
||||
assert.verifySteps(["start", "push_state"]);
|
||||
currentHash = webClient.env.services.router.current.hash;
|
||||
assert.deepEqual(currentHash, {
|
||||
action: "9",
|
||||
action: 9,
|
||||
a: "default value",
|
||||
});
|
||||
assert.strictEqual(
|
||||
@@ -656,10 +652,10 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
action: 9,
|
||||
a: "new value",
|
||||
});
|
||||
assert.verifySteps(["start", "push_state push"]);
|
||||
assert.verifySteps(["start"]); // No push state since the hash hasn't changed
|
||||
currentHash = webClient.env.services.router.current.hash;
|
||||
assert.deepEqual(currentHash, {
|
||||
action: "9",
|
||||
action: 9,
|
||||
a: "new value",
|
||||
});
|
||||
assert.strictEqual(
|
||||
@@ -783,15 +779,10 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
root: { id: "root", children: [1], name: "root", appID: "root" },
|
||||
1: { id: 1, children: [], name: "App1", appID: 1, actionID: 1 },
|
||||
};
|
||||
serviceRegistry.add(
|
||||
"router",
|
||||
makeFakeRouterService({
|
||||
initialRoute: {
|
||||
hash: { home: 1 },
|
||||
},
|
||||
}),
|
||||
{ force: true }
|
||||
);
|
||||
const hash = "#home=1";
|
||||
serviceRegistry.add("router", makeFakeRouterService({ initialRoute: { hash } }), {
|
||||
force: true,
|
||||
});
|
||||
const mockRPC = async function (route) {
|
||||
assert.step(route);
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import testUtils from "web.test_utils";
|
||||
import core from "web.core";
|
||||
import Widget from "web.Widget";
|
||||
import AbstractAction from "web.AbstractAction";
|
||||
import core from "web.core";
|
||||
import testUtils from "web.test_utils";
|
||||
import Widget from "web.Widget";
|
||||
import { makeTestEnv } from "../../helpers/mock_env";
|
||||
import { legacyExtraNextTick } from "../../helpers/utils";
|
||||
import { createWebClient, doAction, getActionManagerTestConfig } from "./helpers";
|
||||
@@ -75,7 +75,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
QUnit.test("action in handler registry", async (assert) => {
|
||||
assert.expect(2);
|
||||
const env = await makeTestEnv(testConfig);
|
||||
actionHandlersRegistry.add("ir.action_in_handler_registry", ({ env, action, options }) =>
|
||||
actionHandlersRegistry.add("ir.action_in_handler_registry", ({ action }) =>
|
||||
assert.step(action.type)
|
||||
);
|
||||
await doAction(env, {
|
||||
@@ -145,7 +145,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
QUnit.test("action cache: additionalContext is respected", async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
const mockRPC = async (route, args) => {
|
||||
const mockRPC = async (route) => {
|
||||
if (route === "/web/action/load") {
|
||||
assert.step("server loaded");
|
||||
}
|
||||
@@ -326,7 +326,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
const mockRPC = async function (route, args) {
|
||||
const mockRPC = async function (route) {
|
||||
if (route === "/web/dataset/search_read") {
|
||||
await Promise.resolve(def);
|
||||
}
|
||||
@@ -378,7 +378,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
action: "Partners Action 4",
|
||||
});
|
||||
currentHash = webClient.env.services.router.current.hash;
|
||||
assert.deepEqual(currentHash, { action: "4", model: "partner", view_type: "kanban" });
|
||||
assert.deepEqual(currentHash, { action: 4, model: "partner", view_type: "kanban" });
|
||||
await doAction(webClient, 8);
|
||||
currentTitle = webClient.env.services.title.getParts();
|
||||
assert.deepEqual(currentTitle, {
|
||||
@@ -386,7 +386,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
action: "Favorite Ponies",
|
||||
});
|
||||
currentHash = webClient.env.services.router.current.hash;
|
||||
assert.deepEqual(currentHash, { action: "8", model: "pony", view_type: "list" });
|
||||
assert.deepEqual(currentHash, { action: 8, model: "pony", view_type: "list" });
|
||||
await testUtils.dom.click($(webClient.el).find("tr.o_data_row:first"));
|
||||
await legacyExtraNextTick();
|
||||
currentTitle = webClient.env.services.title.getParts();
|
||||
@@ -395,7 +395,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
action: "Twilight Sparkle",
|
||||
});
|
||||
currentHash = webClient.env.services.router.current.hash;
|
||||
assert.deepEqual(currentHash, { action: "8", id: "4", model: "pony", view_type: "form" });
|
||||
assert.deepEqual(currentHash, { action: 8, id: 4, model: "pony", view_type: "form" });
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
|
||||
@@ -30,8 +30,8 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
await nextTick();
|
||||
await nextTick();
|
||||
urlState = webClient.env.services.router.current;
|
||||
assert.strictEqual(urlState.hash.action, "1002");
|
||||
assert.strictEqual(urlState.hash.menu_id, "2");
|
||||
assert.strictEqual(urlState.hash.action, 1002);
|
||||
assert.strictEqual(urlState.hash.menu_id, 2);
|
||||
assert.strictEqual(
|
||||
webClient.el.querySelector(".test_client_action").textContent.trim(),
|
||||
"ClientAction_Id 2"
|
||||
@@ -49,8 +49,8 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
await nextTick();
|
||||
await nextTick();
|
||||
urlState = webClient.env.services.router.current;
|
||||
assert.strictEqual(urlState.hash.action, "1002");
|
||||
assert.strictEqual(urlState.hash.menu_id, "2");
|
||||
assert.strictEqual(urlState.hash.action, 1002);
|
||||
assert.strictEqual(urlState.hash.menu_id, 2);
|
||||
assert.strictEqual(
|
||||
webClient.el.querySelector(".test_client_action").textContent.trim(),
|
||||
"ClientAction_Id 2"
|
||||
@@ -58,8 +58,8 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
assert.strictEqual(webClient.el.querySelector(".o_menu_brand").textContent, "App2");
|
||||
await doAction(webClient, 1001, { clearBreadcrumbs: true });
|
||||
urlState = webClient.env.services.router.current;
|
||||
assert.strictEqual(urlState.hash.action, "1001");
|
||||
assert.strictEqual(urlState.hash.menu_id, "2");
|
||||
assert.strictEqual(urlState.hash.action, 1001);
|
||||
assert.strictEqual(urlState.hash.menu_id, 2);
|
||||
assert.strictEqual(
|
||||
webClient.el.querySelector(".test_client_action").textContent.trim(),
|
||||
"ClientAction_Id 1"
|
||||
@@ -122,7 +122,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
assert.strictEqual(urlState.hash.arbitrary, "actionPushed");
|
||||
await doAction(webClient, 1001);
|
||||
urlState = webClient.env.services.router.current;
|
||||
assert.strictEqual(urlState.hash.action, "1001");
|
||||
assert.strictEqual(urlState.hash.action, 1001);
|
||||
assert.strictEqual(urlState.hash.arbitrary, undefined);
|
||||
});
|
||||
|
||||
@@ -152,8 +152,8 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
await nextTick();
|
||||
await nextTick();
|
||||
urlState = webClient.env.services.router.current;
|
||||
assert.strictEqual(urlState.hash.action, "1002");
|
||||
assert.strictEqual(urlState.hash.menu_id, "2");
|
||||
assert.strictEqual(urlState.hash.action, 1002);
|
||||
assert.strictEqual(urlState.hash.menu_id, 2);
|
||||
});
|
||||
|
||||
QUnit.test("action in target new do not push state", async (assert) => {
|
||||
@@ -162,7 +162,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
serviceRegistry.add(
|
||||
"router",
|
||||
makeFakeRouterService({
|
||||
onPushState(mode, newState) {
|
||||
onPushState() {
|
||||
throw new Error("should not push state");
|
||||
},
|
||||
}),
|
||||
@@ -178,30 +178,30 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
const webClient = await createWebClient({ testConfig });
|
||||
await doAction(webClient, 4);
|
||||
assert.deepEqual(webClient.env.services.router.current.hash, {
|
||||
action: "4",
|
||||
action: 4,
|
||||
model: "partner",
|
||||
view_type: "kanban",
|
||||
});
|
||||
await doAction(webClient, 8);
|
||||
assert.deepEqual(webClient.env.services.router.current.hash, {
|
||||
action: "8",
|
||||
action: 8,
|
||||
model: "pony",
|
||||
view_type: "list",
|
||||
});
|
||||
await testUtils.dom.click($(webClient.el).find("tr.o_data_row:first"));
|
||||
await legacyExtraNextTick();
|
||||
assert.deepEqual(webClient.env.services.router.current.hash, {
|
||||
action: "8",
|
||||
action: 8,
|
||||
model: "pony",
|
||||
view_type: "form",
|
||||
id: "4",
|
||||
id: 4,
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test("push state after action is loaded, not before", async function (assert) {
|
||||
assert.expect(2);
|
||||
const def = testUtils.makeTestPromise();
|
||||
const mockRPC = async function (route, args) {
|
||||
const mockRPC = async function (route) {
|
||||
if (route === "/web/dataset/search_read") {
|
||||
await def;
|
||||
}
|
||||
@@ -215,7 +215,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
await testUtils.nextTick();
|
||||
await legacyExtraNextTick();
|
||||
assert.deepEqual(webClient.env.services.router.current.hash, {
|
||||
action: "4",
|
||||
action: 4,
|
||||
model: "partner",
|
||||
view_type: "kanban",
|
||||
});
|
||||
@@ -231,7 +231,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
const webClient = await createWebClient({ testConfig, mockRPC });
|
||||
await doAction(webClient, 8);
|
||||
assert.deepEqual(webClient.env.services.router.current.hash, {
|
||||
action: "8",
|
||||
action: 8,
|
||||
model: "pony",
|
||||
view_type: "list",
|
||||
});
|
||||
@@ -240,7 +240,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
// we make sure here that the list view is still in the dom
|
||||
assert.containsOnce(webClient, ".o_list_view", "there should still be a list view in dom");
|
||||
assert.deepEqual(webClient.env.services.router.current.hash, {
|
||||
action: "8",
|
||||
action: 8,
|
||||
model: "pony",
|
||||
view_type: "list",
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
serviceRegistry.add(
|
||||
"router",
|
||||
makeFakeRouterService({
|
||||
redirect: (url) => {
|
||||
onRedirect(url) {
|
||||
assert.step(url);
|
||||
},
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user