1931 lines
68 KiB
JavaScript
1931 lines
68 KiB
JavaScript
import { describe, expect, getFixture, test } from "@odoo/hoot";
|
|
import { click, on } from "@odoo/hoot-dom";
|
|
import { tick } from "@odoo/hoot-mock";
|
|
import { patchWithCleanup } from "@web/../tests/web_test_helpers";
|
|
|
|
import { browser } from "@web/core/browser/browser";
|
|
import {
|
|
parseHash,
|
|
parseSearchQuery,
|
|
router,
|
|
routerBus,
|
|
startRouter,
|
|
stateToUrl,
|
|
urlToState,
|
|
} from "@web/core/browser/router";
|
|
import { redirect } from "@web/core/utils/urls";
|
|
|
|
const _urlToState = (url) => urlToState(new URL(url));
|
|
|
|
function createRouter(params = {}) {
|
|
if (params.onPushState) {
|
|
patchWithCleanup(browser.history, {
|
|
pushState() {
|
|
super.pushState(...arguments);
|
|
params.onPushState(...arguments);
|
|
},
|
|
});
|
|
}
|
|
if (params.onReplaceState) {
|
|
patchWithCleanup(browser.history, {
|
|
replaceState() {
|
|
super.replaceState(...arguments);
|
|
params.onReplaceState(...arguments);
|
|
},
|
|
});
|
|
}
|
|
startRouter();
|
|
}
|
|
|
|
describe.current.tags("headless");
|
|
|
|
describe("parseHash", () => {
|
|
test("can parse an empty hash", () => {
|
|
expect(parseHash("")).toEqual({});
|
|
});
|
|
|
|
test("can parse an single hash", () => {
|
|
expect(parseHash("#")).toEqual({});
|
|
});
|
|
|
|
test("can parse a hash with a single key/value pair", () => {
|
|
expect(parseHash("#action=114")).toEqual({ action: 114 });
|
|
});
|
|
|
|
test("can parse a hash with 2 key/value pairs", () => {
|
|
expect(parseHash("#action=114&active_id=mail.box_inbox")).toEqual({
|
|
action: 114,
|
|
active_id: "mail.box_inbox",
|
|
});
|
|
});
|
|
|
|
test("a missing value is encoded as an empty string", () => {
|
|
expect(parseHash("#action")).toEqual({ action: "" });
|
|
});
|
|
|
|
test("a missing value is encoded as an empty string -- 2", () => {
|
|
expect(parseHash("#action=")).toEqual({ action: "" });
|
|
});
|
|
|
|
test("can parse a realistic hash", () => {
|
|
expect(parseHash("#action=114&active_id=mail.box_inbox&menu_id=91")).toEqual({
|
|
action: 114,
|
|
active_id: "mail.box_inbox",
|
|
menu_id: 91,
|
|
});
|
|
});
|
|
|
|
test("can parse URI encoded strings", () => {
|
|
expect(parseHash("#comma=that%2Cis")).toEqual({ comma: "that,is" });
|
|
});
|
|
});
|
|
|
|
describe("parseSearchQuery", () => {
|
|
test("can parse an empty search", () => {
|
|
expect(parseSearchQuery("")).toEqual({});
|
|
});
|
|
|
|
test("can parse an simple search with no value", () => {
|
|
expect(parseSearchQuery("?a")).toEqual({ a: "" });
|
|
});
|
|
|
|
test("can parse an simple search with a value", () => {
|
|
expect(parseSearchQuery("?a=1")).toEqual({ a: 1 });
|
|
});
|
|
|
|
test("can parse an search with 2 key/value pairs", () => {
|
|
expect(parseSearchQuery("?a=1&b=2")).toEqual({ a: 1, b: 2 });
|
|
});
|
|
|
|
test("can parse URI encoded strings", () => {
|
|
expect(parseHash("#comma=that%2Cis")).toEqual({ comma: "that,is" });
|
|
});
|
|
});
|
|
|
|
describe("stateToUrl", () => {
|
|
test("encodes URI compatible strings", (assert) => {
|
|
expect(stateToUrl({})).toBe("/odoo");
|
|
expect(stateToUrl({ a: "11", b: "summer wine" })).toBe("/odoo?a=11&b=summer%20wine");
|
|
expect(stateToUrl({ b: "2", c: "", e: "kloug,gloubi" })).toBe(
|
|
"/odoo?b=2&c=&e=kloug%2Cgloubi"
|
|
);
|
|
});
|
|
|
|
test("backwards compatibility: no action stack, action encoded in path", (assert) => {
|
|
expect(stateToUrl({})).toBe("/odoo");
|
|
// action
|
|
expect(stateToUrl({ action: "some-path" })).toBe("/odoo/some-path");
|
|
expect(stateToUrl({ active_id: 5, action: "some-path" })).toBe("/odoo/5/some-path");
|
|
expect(stateToUrl({ active_id: "some-active_id", action: "some-path" })).toBe(
|
|
"/odoo/some-path?active_id=some-active_id",
|
|
{ message: "only numeric active_id are encoded in path" }
|
|
);
|
|
expect(stateToUrl({ action: "some-path", resId: 2 })).toBe("/odoo/some-path/2");
|
|
expect(stateToUrl({ action: "some-path", resId: "some-resId" })).toBe(
|
|
"/odoo/some-path?resId=some-resId",
|
|
{ message: "only numeric resId are encoded in path" }
|
|
);
|
|
expect(stateToUrl({ active_id: 5, action: "some-path", resId: 2 })).toBe(
|
|
"/odoo/5/some-path/2"
|
|
);
|
|
expect(stateToUrl({ active_id: 5, action: "some-path", resId: "new" })).toBe(
|
|
"/odoo/5/some-path/new"
|
|
);
|
|
expect(stateToUrl({ action: 1, resId: 2 })).toBe("/odoo/action-1/2", {
|
|
message: "action id instead of path/tag",
|
|
});
|
|
expect(stateToUrl({ action: "module.xml_id", resId: 2 })).toBe(
|
|
"/odoo/action-module.xml_id/2",
|
|
{ message: "action xml_id instead of path/tag" }
|
|
);
|
|
// model
|
|
expect(stateToUrl({ model: "some.model" })).toBe("/odoo/some.model");
|
|
expect(stateToUrl({ model: "some.model", resId: 2 })).toBe("/odoo/some.model/2");
|
|
expect(stateToUrl({ active_id: 5, model: "some.model" })).toBe("/odoo/5/some.model");
|
|
expect(stateToUrl({ active_id: 5, model: "some.model", resId: 2 })).toBe(
|
|
"/odoo/5/some.model/2"
|
|
);
|
|
expect(stateToUrl({ active_id: 5, model: "some.model", resId: "new" })).toBe(
|
|
"/odoo/5/some.model/new"
|
|
);
|
|
expect(stateToUrl({ active_id: 5, model: "some.model", view_type: "some_viewtype" })).toBe(
|
|
"/odoo/5/some.model?view_type=some_viewtype"
|
|
);
|
|
// edge cases
|
|
expect(
|
|
stateToUrl({ active_id: 5, action: "some-path", resId: 2, some_key: "some_value" })
|
|
).toBe("/odoo/5/some-path/2?some_key=some_value", {
|
|
message: "pieces of state unrelated to actions are added as query string",
|
|
});
|
|
expect(
|
|
stateToUrl({ active_id: 5, action: "some-path", model: "some.model", resId: 2 })
|
|
).toBe("/odoo/5/some-path/2", { message: "action has priority on model" });
|
|
expect(stateToUrl({ active_id: 5, model: "some.model", resId: 2, view_type: "list" })).toBe(
|
|
"/odoo/5/some.model/2?view_type=list",
|
|
{ message: "view_type and resId aren't incompatible" }
|
|
// Should they be? view_type will just be stripped by action_service
|
|
);
|
|
});
|
|
|
|
test("actionStack: one action", () => {
|
|
expect(stateToUrl({ actionStack: [] })).toBe("/odoo");
|
|
// action
|
|
expect(stateToUrl({ actionStack: [{ action: "some-path" }] })).toBe("/odoo/some-path");
|
|
expect(stateToUrl({ actionStack: [{ active_id: 5, action: "some-path" }] })).toBe(
|
|
"/odoo/5/some-path"
|
|
);
|
|
expect(stateToUrl({ actionStack: [{ action: "some-path", resId: 2 }] })).toBe(
|
|
"/odoo/some-path/2"
|
|
);
|
|
expect(stateToUrl({ actionStack: [{ active_id: 5, action: "some-path", resId: 2 }] })).toBe(
|
|
"/odoo/5/some-path/2"
|
|
);
|
|
expect(
|
|
stateToUrl({ actionStack: [{ active_id: 5, action: "some-path", resId: "new" }] })
|
|
).toBe("/odoo/5/some-path/new");
|
|
expect(stateToUrl({ actionStack: [{ action: 1, resId: 2 }] })).toBe("/odoo/action-1/2", {
|
|
message: "numerical action id instead of path",
|
|
});
|
|
expect(stateToUrl({ actionStack: [{ action: "module.xml_id", resId: 2 }] })).toBe(
|
|
"/odoo/action-module.xml_id/2",
|
|
{ message: "action xml_id instead of path" }
|
|
);
|
|
// model
|
|
expect(stateToUrl({ actionStack: [{ model: "some.model" }] })).toBe("/odoo/some.model");
|
|
expect(stateToUrl({ actionStack: [{ model: "some.model", resId: 2 }] })).toBe(
|
|
"/odoo/some.model/2"
|
|
);
|
|
expect(stateToUrl({ actionStack: [{ active_id: 5, model: "some.model" }] })).toBe(
|
|
"/odoo/5/some.model"
|
|
);
|
|
expect(stateToUrl({ actionStack: [{ active_id: 5, model: "some.model", resId: 2 }] })).toBe(
|
|
"/odoo/5/some.model/2"
|
|
);
|
|
expect(
|
|
stateToUrl({ actionStack: [{ active_id: 5, model: "some.model", resId: "new" }] })
|
|
).toBe("/odoo/5/some.model/new");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [{ active_id: 5, model: "some.model", view_type: "some_viewtype" }],
|
|
})
|
|
).toBe("/odoo/5/some.model", { message: "view_type is ignored in the action stack" });
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [{ active_id: 5, model: "some.model" }],
|
|
view_type: "some_viewtype",
|
|
})
|
|
).toBe("/odoo/5/some.model?view_type=some_viewtype", {
|
|
message: "view_type is added if it's on the state itself",
|
|
});
|
|
expect(
|
|
stateToUrl({ actionStack: [{ active_id: 5, model: "model_no_dot", resId: 2 }] })
|
|
).toBe("/odoo/5/m-model_no_dot/2");
|
|
// edge cases
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ active_id: 5, action: "some-path", resId: 2, some_key: "some_value" },
|
|
],
|
|
})
|
|
).toBe("/odoo/5/some-path/2", {
|
|
message: "pieces of state unrelated to actions are ignored in the actionStack",
|
|
});
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [{ active_id: 5, action: "some-path", resId: 2 }],
|
|
some_key: "some_value",
|
|
})
|
|
).toBe("/odoo/5/some-path/2?some_key=some_value", {
|
|
message:
|
|
"pieces of state unrelated to actions are added as query string even with actionStack",
|
|
});
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [{ active_id: 5, action: "some-path", model: "some.model", resId: 2 }],
|
|
})
|
|
).toBe("/odoo/5/some-path/2", { message: "action has priority on model" });
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [{ active_id: 5, model: "some.model", resId: 2 }],
|
|
view_type: "list",
|
|
})
|
|
).toBe(
|
|
"/odoo/5/some.model/2?view_type=list",
|
|
{ message: "view_type and resId aren't incompatible" }
|
|
// Should they be? view_type will just be stripped by action_service
|
|
);
|
|
});
|
|
|
|
test("actionStack: multiple actions", () => {
|
|
// different actions
|
|
expect(
|
|
stateToUrl({ actionStack: [{ action: "some-path" }, { action: "other-path" }] })
|
|
).toBe("/odoo/some-path/other-path");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [{ active_id: 5, action: "some-path" }, { action: "other-path" }],
|
|
})
|
|
).toBe("/odoo/5/some-path/other-path");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [{ action: "some-path" }, { active_id: 7, action: "other-path" }],
|
|
})
|
|
).toBe(
|
|
// On reload, this will generate a form view for the first action even though there was
|
|
// originally none. This is probably fine.
|
|
"/odoo/some-path/7/other-path"
|
|
);
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [{ action: "some-path", resId: 2 }, { action: "other-path" }],
|
|
})
|
|
).toBe(
|
|
// On reload, the second action will have an active_id even though it originally didn't
|
|
// have one. This might be a problem?
|
|
"/odoo/some-path/2/other-path"
|
|
);
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [{ action: "some-path" }, { action: "other-path", resId: 2 }],
|
|
})
|
|
).toBe(
|
|
// On reload, this will generate an action in the default multi-record view for the second
|
|
// action. This is the desired behaviour.
|
|
"/odoo/some-path/other-path/2"
|
|
);
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ active_id: 5, action: "some-path", resId: 2 },
|
|
{ action: "other-path" },
|
|
],
|
|
})
|
|
).toBe("/odoo/5/some-path/2/other-path");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ action: "some-path" },
|
|
{ active_id: 5, action: "other-path", resId: 2 },
|
|
],
|
|
})
|
|
).toBe("/odoo/some-path/5/other-path/2");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ action: "some-path" },
|
|
{ active_id: 5, action: "other-path", resId: "new" },
|
|
],
|
|
})
|
|
).toBe("/odoo/some-path/5/other-path/new");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ action: "some-path", resId: 5 },
|
|
{ active_id: 5, action: "other-path" },
|
|
],
|
|
})
|
|
).toBe("/odoo/some-path/5/other-path", {
|
|
message:
|
|
"action with resId followed by action with same value as active_id is not duplicated",
|
|
});
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ action: "some-path", resId: 5 },
|
|
{ active_id: 5, action: "other-path", resId: 2 },
|
|
],
|
|
})
|
|
).toBe("/odoo/some-path/5/other-path/2");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [{ action: 1 }, { active_id: 5, action: 6, resId: 2 }],
|
|
})
|
|
).toBe("/odoo/action-1/5/action-6/2", { message: "numerical actions" });
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ action: "module.xml_id" },
|
|
{ active_id: 5, action: "module.other_xml_id", resId: 2 },
|
|
],
|
|
})
|
|
).toBe("/odoo/action-module.xml_id/5/action-module.other_xml_id/2", {
|
|
message: "actions as xml_ids",
|
|
});
|
|
// same action twice
|
|
expect(
|
|
stateToUrl({ actionStack: [{ action: "some-path" }, { action: "some-path" }] })
|
|
).toBe("/odoo/some-path", {
|
|
message: "consolidates identical actions into one path segment",
|
|
});
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [{ active_id: 5, action: "some-path" }, { action: "some-path" }],
|
|
})
|
|
).toBe("/odoo/5/some-path/some-path", {
|
|
message: "doesn't consolidate the same action with different active_id",
|
|
});
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ action: "some-path", resId: 7 },
|
|
{ active_id: 7, action: "some-path" },
|
|
],
|
|
})
|
|
).toBe("/odoo/some-path/7/some-path", {
|
|
message:
|
|
"doesn't remove multirecord action if it follows the same action in mono-record mode",
|
|
});
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [{ action: "some-path", resId: 2 }, { action: "some-path" }],
|
|
})
|
|
).toBe("/odoo/some-path/2/some-path");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ active_id: 7, action: "some-path", resId: 7 },
|
|
{ active_id: 7, action: "some-path" },
|
|
],
|
|
})
|
|
).toBe("/odoo/7/some-path/7/some-path", {
|
|
message:
|
|
"doesn't remove multirecord action if it follows the same action in mono-record mode even if the active_id are the same",
|
|
});
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [{ action: "some-path" }, { action: "some-path", resId: 2 }],
|
|
})
|
|
).toBe("/odoo/some-path/2", {
|
|
message: "consolidates multi-record action with mono-record action",
|
|
});
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ active_id: 5, action: "some-path" },
|
|
{ active_id: 5, action: "some-path", resId: 2 },
|
|
],
|
|
})
|
|
).toBe("/odoo/5/some-path/2", {
|
|
message:
|
|
"consolidates multi-record action with mono-record action if they have the same active_id",
|
|
});
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ active_id: 5, action: "some-path", resId: 2 },
|
|
{ action: "some-path" },
|
|
],
|
|
})
|
|
).toBe("/odoo/5/some-path/2/some-path");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ action: "some-path" },
|
|
{ active_id: 5, action: "some-path", resId: 2 },
|
|
],
|
|
})
|
|
).toBe("/odoo/some-path/5/some-path/2", {
|
|
message:
|
|
"doesn't consolidate mono-record action into preceding multi-record action if active_id is not the same",
|
|
});
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ action: "some-path" },
|
|
{ active_id: 5, action: "some-path", resId: "new" },
|
|
],
|
|
})
|
|
).toBe("/odoo/some-path/5/some-path/new");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ action: "some-path", resId: 5 },
|
|
{ active_id: 5, action: "some-path" },
|
|
],
|
|
})
|
|
).toBe("/odoo/some-path/5/some-path", {
|
|
message:
|
|
"action with resId followed by action with same value as active_id is not duplicated",
|
|
});
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ action: "some-path", resId: 5 },
|
|
{ active_id: 5, action: "some-path", resId: 2 },
|
|
],
|
|
})
|
|
).toBe("/odoo/some-path/5/some-path/2", {
|
|
message: "doesn't consolidate two mono-record actions",
|
|
});
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ active_id: 5, action: "some-path", resId: 5 },
|
|
{ active_id: 5, action: "some-path", resId: 2 },
|
|
],
|
|
})
|
|
).toBe("/odoo/5/some-path/5/some-path/2", {
|
|
message: "doesn't consolidate two mono-record actions even with same active_id",
|
|
});
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [{ action: 1 }, { active_id: 5, action: 1, resId: 2 }],
|
|
})
|
|
).toBe("/odoo/action-1/5/action-1/2", { message: "numerical actions" });
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ action: "module.xml_id" },
|
|
{ active_id: 5, action: "module.xml_id", resId: 2 },
|
|
],
|
|
})
|
|
).toBe("/odoo/action-module.xml_id/5/action-module.xml_id/2", {
|
|
message: "actions as xml_ids",
|
|
});
|
|
// model
|
|
expect(
|
|
stateToUrl({ actionStack: [{ model: "some.model" }, { model: "other.model" }] })
|
|
).toBe("/odoo/some.model/other.model");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [{ active_id: 5, model: "some.model" }, { model: "other.model" }],
|
|
})
|
|
).toBe("/odoo/5/some.model/other.model");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [{ model: "some.model" }, { active_id: 7, model: "other.model" }],
|
|
})
|
|
).toBe("/odoo/some.model/7/other.model");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [{ model: "some.model", resId: 2 }, { model: "other.model" }],
|
|
})
|
|
).toBe("/odoo/some.model/2/other.model");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [{ model: "some.model" }, { model: "other.model", resId: 2 }],
|
|
})
|
|
).toBe("/odoo/some.model/other.model/2");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ active_id: 5, model: "some.model", resId: 2 },
|
|
{ model: "other.model" },
|
|
],
|
|
})
|
|
).toBe("/odoo/5/some.model/2/other.model");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ model: "some.model" },
|
|
{ active_id: 5, model: "other.model", resId: 2 },
|
|
],
|
|
})
|
|
).toBe("/odoo/some.model/5/other.model/2");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ model: "some.model" },
|
|
{ active_id: 5, model: "other.model", resId: "new" },
|
|
],
|
|
})
|
|
).toBe("/odoo/some.model/5/other.model/new");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ model: "some.model", resId: 5 },
|
|
{ active_id: 5, model: "other.model" },
|
|
],
|
|
})
|
|
).toBe("/odoo/some.model/5/other.model", {
|
|
message:
|
|
"action with resId followed by action with same value as active_id is not duplicated",
|
|
});
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ model: "some.model", resId: 5 },
|
|
{ active_id: 5, model: "other.model", resId: 2 },
|
|
],
|
|
})
|
|
).toBe("/odoo/some.model/5/other.model/2");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ model: "model_no_dot", resId: 5 },
|
|
{ active_id: 5, model: "no_dot_model", resId: 2 },
|
|
],
|
|
})
|
|
).toBe("/odoo/m-model_no_dot/5/m-no_dot_model/2");
|
|
// action + model
|
|
expect(
|
|
stateToUrl({ actionStack: [{ action: "some-path" }, { model: "some.model" }] })
|
|
).toBe("/odoo/some-path/some.model");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [{ active_id: 5, action: "some-path" }, { model: "some.model" }],
|
|
})
|
|
).toBe("/odoo/5/some-path/some.model");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [{ action: "some-path" }, { active_id: 7, model: "some.model" }],
|
|
})
|
|
).toBe("/odoo/some-path/7/some.model");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [{ action: "some-path", resId: 2 }, { model: "some.model" }],
|
|
})
|
|
).toBe("/odoo/some-path/2/some.model");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [{ action: "some-path" }, { model: "some.model", resId: 2 }],
|
|
})
|
|
).toBe("/odoo/some-path/some.model/2");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ active_id: 5, action: "some-path", resId: 2 },
|
|
{ model: "some.model" },
|
|
],
|
|
})
|
|
).toBe("/odoo/5/some-path/2/some.model");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ action: "some-path" },
|
|
{ active_id: 5, model: "some.model", resId: 2 },
|
|
],
|
|
})
|
|
).toBe("/odoo/some-path/5/some.model/2");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ action: "some-path" },
|
|
{ active_id: 5, model: "some.model", resId: "new" },
|
|
],
|
|
})
|
|
).toBe("/odoo/some-path/5/some.model/new");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ action: "some-path", resId: 5 },
|
|
{ active_id: 5, model: "some.model" },
|
|
],
|
|
})
|
|
).toBe("/odoo/some-path/5/some.model", {
|
|
message:
|
|
"action with resId followed by action with same value as active_id is not duplicated",
|
|
});
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ action: "some-path", resId: 5 },
|
|
{ active_id: 5, model: "some.model", resId: 2 },
|
|
],
|
|
})
|
|
).toBe("/odoo/some-path/5/some.model/2");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ action: 1, resId: 5 },
|
|
{ active_id: 5, model: "model_no_dot", resId: 2 },
|
|
],
|
|
})
|
|
).toBe("/odoo/action-1/5/m-model_no_dot/2");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ action: "module.xml_id", resId: 5 },
|
|
{ active_id: 5, model: "model_no_dot", resId: 2 },
|
|
],
|
|
})
|
|
).toBe("/odoo/action-module.xml_id/5/m-model_no_dot/2");
|
|
// model + action
|
|
expect(
|
|
stateToUrl({ actionStack: [{ model: "some.model" }, { action: "other-path" }] })
|
|
).toBe("/odoo/some.model/other-path");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [{ active_id: 5, model: "some.model" }, { action: "other-path" }],
|
|
})
|
|
).toBe("/odoo/5/some.model/other-path");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [{ model: "some.model" }, { active_id: 7, action: "other-path" }],
|
|
})
|
|
).toBe("/odoo/some.model/7/other-path");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [{ model: "some.model", resId: 2 }, { action: "other-path" }],
|
|
})
|
|
).toBe("/odoo/some.model/2/other-path");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [{ model: "some.model" }, { action: "other-path", resId: 2 }],
|
|
})
|
|
).toBe("/odoo/some.model/other-path/2");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ active_id: 5, model: "some.model", resId: 2 },
|
|
{ action: "other-path" },
|
|
],
|
|
})
|
|
).toBe("/odoo/5/some.model/2/other-path");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ model: "some.model" },
|
|
{ active_id: 5, action: "other-path", resId: 2 },
|
|
],
|
|
})
|
|
).toBe("/odoo/some.model/5/other-path/2");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ model: "some.model" },
|
|
{ active_id: 5, action: "other-path", resId: "new" },
|
|
],
|
|
})
|
|
).toBe("/odoo/some.model/5/other-path/new");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ model: "some.model", resId: 5 },
|
|
{ active_id: 5, action: "other-path" },
|
|
],
|
|
})
|
|
).toBe("/odoo/some.model/5/other-path", {
|
|
message:
|
|
"action with resId followed by action with same value as active_id is not duplicated",
|
|
});
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ model: "some.model", resId: 5 },
|
|
{ active_id: 5, action: "other-path", resId: 2 },
|
|
],
|
|
})
|
|
).toBe("/odoo/some.model/5/other-path/2");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ model: "model_no_dot", resId: 5 },
|
|
{ active_id: 5, action: 1, resId: 2 },
|
|
],
|
|
})
|
|
).toBe("/odoo/m-model_no_dot/5/action-1/2");
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ model: "model_no_dot", resId: 5 },
|
|
{ active_id: 5, action: "module.xml_id", resId: 2 },
|
|
],
|
|
})
|
|
).toBe("/odoo/m-model_no_dot/5/action-module.xml_id/2");
|
|
|
|
// edge cases
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ action: "some-path", resId: 5, some_key: "some_value" },
|
|
{ active_id: 5, action: "other-path", resId: 2, other_key: "other_value" },
|
|
],
|
|
})
|
|
).toBe("/odoo/some-path/5/other-path/2", {
|
|
message: "pieces of state unrelated to actions are ignored in the actionStack",
|
|
});
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ action: "some-path", resId: 5 },
|
|
{ active_id: 5, action: "other-path", resId: 2 },
|
|
],
|
|
some_key: "some_value",
|
|
})
|
|
).toBe("/odoo/some-path/5/other-path/2?some_key=some_value", {
|
|
message:
|
|
"pieces of state unrelated to actions are added as query string even with actionStack",
|
|
});
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ action: "some-path", model: "some.model", resId: 5 },
|
|
{ active_id: 5, action: "other-path", model: "other.model", resId: 2 },
|
|
],
|
|
})
|
|
).toBe("/odoo/some-path/5/other-path/2", { message: "action has priority on model" });
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ action: "some-path", resId: 5 },
|
|
{ active_id: 5, model: "some.model", resId: 2 },
|
|
],
|
|
view_type: "list",
|
|
})
|
|
).toBe("/odoo/some-path/5/some.model/2?view_type=list", {
|
|
message: "view_type and resId aren't incompatible",
|
|
});
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ action: "some-path", resId: 2 },
|
|
{ active_id: 5, action: "other-path" },
|
|
],
|
|
})
|
|
).toBe("/odoo/some-path/2/5/other-path", {
|
|
message:
|
|
"action with resId followed by action with different active_id gets both ids in a row",
|
|
});
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ model: "some.model", resId: 2 },
|
|
{ active_id: 5, model: "other.model" },
|
|
],
|
|
})
|
|
).toBe("/odoo/some.model/2/5/other.model", {
|
|
message:
|
|
"action with resId followed by action with different active_id gets both ids in a row",
|
|
});
|
|
expect(
|
|
stateToUrl({
|
|
actionStack: [
|
|
{ action: "other-path", resId: 5 },
|
|
{ active_id: 5, action: "some-path" },
|
|
{ active_id: 5, action: "some-path", resId: 2 },
|
|
],
|
|
})
|
|
).toBe("/odoo/other-path/5/some-path/2", {
|
|
message:
|
|
"active_id of last action is correctly removed even if previous action's active id is also removed because of the preceding resId",
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("urlToState", () => {
|
|
test("deserialize queryString", () => {
|
|
expect(_urlToState("/odoo?a=11&g=summer%20wine")).toEqual({
|
|
a: 11,
|
|
g: "summer wine",
|
|
});
|
|
expect(_urlToState("/odoo?g=2&c=&e=kloug%2Cgloubi")).toEqual({
|
|
g: 2,
|
|
c: "",
|
|
e: "kloug,gloubi",
|
|
});
|
|
});
|
|
|
|
test("deserialize action in legacy url form", () => {
|
|
expect(
|
|
_urlToState("/web#id=5&action=1&model=some.model&view_type=form&menu_id=137&cids=1")
|
|
).toEqual({
|
|
action: 1,
|
|
resId: 5,
|
|
cids: 1,
|
|
menu_id: 137,
|
|
model: "some.model",
|
|
actionStack: [
|
|
{
|
|
action: 1,
|
|
},
|
|
{
|
|
action: 1,
|
|
resId: 5,
|
|
},
|
|
],
|
|
});
|
|
|
|
expect(_urlToState("/web#id=5&model=some.model&view_type=form&menu_id=137&cids=1")).toEqual(
|
|
{
|
|
resId: 5,
|
|
cids: 1,
|
|
menu_id: 137,
|
|
model: "some.model",
|
|
actionStack: [
|
|
{
|
|
resId: 5,
|
|
model: "some.model",
|
|
},
|
|
],
|
|
},
|
|
{ message: "no action" }
|
|
);
|
|
});
|
|
|
|
test("deserialize single action", () => {
|
|
expect(_urlToState("")).toEqual({});
|
|
expect(_urlToState("/odoo")).toEqual({});
|
|
// action
|
|
expect(_urlToState("/odoo/some-path")).toEqual({
|
|
action: "some-path",
|
|
actionStack: [{ action: "some-path" }],
|
|
});
|
|
expect(_urlToState("/odoo/5/some-path")).toEqual({
|
|
active_id: 5,
|
|
action: "some-path",
|
|
actionStack: [{ active_id: 5, action: "some-path" }],
|
|
});
|
|
expect(_urlToState("/odoo/some-path/2")).toEqual(
|
|
{
|
|
action: "some-path",
|
|
resId: 2,
|
|
actionStack: [{ action: "some-path" }, { action: "some-path", resId: 2 }],
|
|
},
|
|
{ message: "two actions are created for action with resId" }
|
|
);
|
|
expect(_urlToState("/odoo/some-path/new")).toEqual(
|
|
{
|
|
action: "some-path",
|
|
resId: "new",
|
|
actionStack: [{ action: "some-path" }, { action: "some-path", resId: "new" }],
|
|
},
|
|
{ message: "new record" }
|
|
);
|
|
expect(_urlToState("/odoo/5/some-path/2")).toEqual({
|
|
active_id: 5,
|
|
action: "some-path",
|
|
resId: 2,
|
|
actionStack: [
|
|
{
|
|
active_id: 5,
|
|
action: "some-path",
|
|
},
|
|
{
|
|
active_id: 5,
|
|
action: "some-path",
|
|
resId: 2,
|
|
},
|
|
],
|
|
});
|
|
expect(_urlToState("/odoo/action-1/2")).toEqual({
|
|
action: 1,
|
|
resId: 2,
|
|
actionStack: [{ action: 1 }, { action: 1, resId: 2 }],
|
|
});
|
|
expect(_urlToState("/odoo/action-module.xml_id/2")).toEqual({
|
|
action: "module.xml_id",
|
|
resId: 2,
|
|
actionStack: [{ action: "module.xml_id" }, { action: "module.xml_id", resId: 2 }],
|
|
});
|
|
// model
|
|
expect(_urlToState("/odoo/some.model")).toEqual({
|
|
model: "some.model",
|
|
actionStack: [{ model: "some.model" }],
|
|
});
|
|
expect(_urlToState("/odoo/some.model/2")).toEqual(
|
|
{
|
|
model: "some.model",
|
|
resId: 2,
|
|
actionStack: [{ model: "some.model", resId: 2 }],
|
|
},
|
|
{ message: "single action is created for model with resId" }
|
|
);
|
|
expect(_urlToState("/odoo/some.model/new")).toEqual(
|
|
{
|
|
model: "some.model",
|
|
resId: "new",
|
|
actionStack: [{ model: "some.model", resId: "new" }],
|
|
},
|
|
{ message: "new record" }
|
|
);
|
|
expect(_urlToState("/odoo/5/some.model")).toEqual({
|
|
active_id: 5,
|
|
model: "some.model",
|
|
actionStack: [{ active_id: 5, model: "some.model" }],
|
|
});
|
|
expect(_urlToState("/odoo/5/some.model/2")).toEqual({
|
|
active_id: 5,
|
|
model: "some.model",
|
|
resId: 2,
|
|
actionStack: [{ active_id: 5, model: "some.model", resId: 2 }],
|
|
});
|
|
expect(_urlToState("/odoo/5/some.model?view_type=some_viewtype")).toEqual(
|
|
{
|
|
active_id: 5,
|
|
model: "some.model",
|
|
view_type: "some_viewtype",
|
|
actionStack: [{ active_id: 5, model: "some.model" }],
|
|
},
|
|
{ message: "view_type doesn't end up in the actionStack" }
|
|
);
|
|
expect(_urlToState("/odoo/m-model_no_dot/2")).toEqual({
|
|
model: "model_no_dot",
|
|
resId: 2,
|
|
actionStack: [{ model: "model_no_dot", resId: 2 }],
|
|
});
|
|
// edge cases
|
|
expect(_urlToState("/odoo/5/some-path/2?some_key=some_value")).toEqual(
|
|
{
|
|
active_id: 5,
|
|
action: "some-path",
|
|
resId: 2,
|
|
some_key: "some_value",
|
|
actionStack: [
|
|
{ active_id: 5, action: "some-path" },
|
|
{ active_id: 5, action: "some-path", resId: 2 },
|
|
],
|
|
},
|
|
{
|
|
message:
|
|
"pieces of state unrelated to actions end up on the state but not in the actionStack",
|
|
}
|
|
);
|
|
expect(_urlToState("/odoo/5/some.model/2?view_type=list")).toEqual(
|
|
{
|
|
active_id: 5,
|
|
model: "some.model",
|
|
resId: 2,
|
|
view_type: "list",
|
|
actionStack: [{ active_id: 5, model: "some.model", resId: 2 }],
|
|
},
|
|
{ message: "view_type and resId aren't incompatible" }
|
|
);
|
|
});
|
|
|
|
test("deserialize multiple actions", () => {
|
|
// action
|
|
expect(_urlToState("/odoo/some-path/other-path")).toEqual({
|
|
action: "other-path",
|
|
actionStack: [{ action: "some-path" }, { action: "other-path" }],
|
|
});
|
|
expect(_urlToState("/odoo/5/some-path/other-path")).toEqual({
|
|
action: "other-path",
|
|
actionStack: [{ active_id: 5, action: "some-path" }, { action: "other-path" }],
|
|
});
|
|
expect(_urlToState("/odoo/some-path/2/other-path")).toEqual({
|
|
action: "other-path",
|
|
active_id: 2,
|
|
actionStack: [
|
|
{ action: "some-path" },
|
|
{ action: "some-path", resId: 2 },
|
|
{ active_id: 2, action: "other-path" },
|
|
],
|
|
});
|
|
expect(_urlToState("/odoo/some-path/other-path/2")).toEqual({
|
|
action: "other-path",
|
|
resId: 2,
|
|
actionStack: [
|
|
{ action: "some-path" },
|
|
{ action: "other-path" },
|
|
{ action: "other-path", resId: 2 },
|
|
],
|
|
});
|
|
expect(_urlToState("/odoo/5/some-path/2/other-path")).toEqual({
|
|
action: "other-path",
|
|
active_id: 2,
|
|
actionStack: [
|
|
{ active_id: 5, action: "some-path" },
|
|
{ active_id: 5, action: "some-path", resId: 2 },
|
|
{ active_id: 2, action: "other-path" },
|
|
],
|
|
});
|
|
expect(_urlToState("/odoo/some-path/5/other-path/2")).toEqual({
|
|
active_id: 5,
|
|
action: "other-path",
|
|
resId: 2,
|
|
actionStack: [
|
|
{ action: "some-path" },
|
|
{ action: "some-path", resId: 5 },
|
|
{ active_id: 5, action: "other-path" },
|
|
{ active_id: 5, action: "other-path", resId: 2 },
|
|
],
|
|
});
|
|
expect(_urlToState("/odoo/some-path/5/other-path/new")).toEqual({
|
|
active_id: 5,
|
|
action: "other-path",
|
|
resId: "new",
|
|
actionStack: [
|
|
{ action: "some-path" },
|
|
{ action: "some-path", resId: 5 },
|
|
{ active_id: 5, action: "other-path" },
|
|
{ active_id: 5, action: "other-path", resId: "new" },
|
|
],
|
|
});
|
|
expect(_urlToState("/odoo/action-1/5/action-6/2")).toEqual({
|
|
active_id: 5,
|
|
action: 6,
|
|
resId: 2,
|
|
actionStack: [
|
|
{ action: 1 },
|
|
{ action: 1, resId: 5 },
|
|
{ active_id: 5, action: 6 },
|
|
{ active_id: 5, action: 6, resId: 2 },
|
|
],
|
|
});
|
|
expect(_urlToState("/odoo/action-module.xml_id/5/action-module.other_xml_id/2")).toEqual({
|
|
active_id: 5,
|
|
action: "module.other_xml_id",
|
|
resId: 2,
|
|
actionStack: [
|
|
{ action: "module.xml_id" },
|
|
{ action: "module.xml_id", resId: 5 },
|
|
{ active_id: 5, action: "module.other_xml_id" },
|
|
{ active_id: 5, action: "module.other_xml_id", resId: 2 },
|
|
],
|
|
});
|
|
// model
|
|
expect(_urlToState("/odoo/some.model/other.model")).toEqual(
|
|
{
|
|
model: "other.model",
|
|
actionStack: [{ model: "other.model" }],
|
|
},
|
|
{
|
|
message:
|
|
"model not followed by resId doesn't generate an action unless it's the last one",
|
|
}
|
|
);
|
|
expect(_urlToState("/odoo/5/some.model/other.model")).toEqual(
|
|
{
|
|
model: "other.model",
|
|
actionStack: [{ model: "other.model" }],
|
|
},
|
|
{
|
|
message:
|
|
"model not followed by resId doesn't generate an action unless it's the last one, even with an active_id",
|
|
}
|
|
);
|
|
expect(_urlToState("/odoo/some.model/7/other.model")).toEqual({
|
|
active_id: 7,
|
|
model: "other.model",
|
|
actionStack: [
|
|
{ model: "some.model", resId: 7 },
|
|
{ active_id: 7, model: "other.model" },
|
|
],
|
|
});
|
|
expect(_urlToState("/odoo/some.model/other.model/2")).toEqual({
|
|
model: "other.model",
|
|
resId: 2,
|
|
actionStack: [{ model: "other.model", resId: 2 }],
|
|
});
|
|
expect(_urlToState("/odoo/5/some.model/2/other.model")).toEqual({
|
|
active_id: 2,
|
|
model: "other.model",
|
|
actionStack: [
|
|
{ active_id: 5, model: "some.model", resId: 2 },
|
|
{ active_id: 2, model: "other.model" },
|
|
],
|
|
});
|
|
expect(_urlToState("/odoo/some.model/5/other.model/2")).toEqual({
|
|
active_id: 5,
|
|
model: "other.model",
|
|
resId: 2,
|
|
actionStack: [
|
|
{ model: "some.model", resId: 5 },
|
|
{ active_id: 5, model: "other.model", resId: 2 },
|
|
],
|
|
});
|
|
expect(_urlToState("/odoo/some.model/5/other.model/new")).toEqual({
|
|
active_id: 5,
|
|
model: "other.model",
|
|
resId: "new",
|
|
actionStack: [
|
|
{ model: "some.model", resId: 5 },
|
|
{ active_id: 5, model: "other.model", resId: "new" },
|
|
],
|
|
});
|
|
expect(_urlToState("/odoo/m-model_no_dot/5/m-no_dot_model/2")).toEqual({
|
|
active_id: 5,
|
|
model: "no_dot_model",
|
|
resId: 2,
|
|
actionStack: [
|
|
{ model: "model_no_dot", resId: 5 },
|
|
{ active_id: 5, model: "no_dot_model", resId: 2 },
|
|
],
|
|
});
|
|
// action + model
|
|
expect(_urlToState("/odoo/some-path/some.model")).toEqual({
|
|
model: "some.model",
|
|
actionStack: [{ action: "some-path" }, { model: "some.model" }],
|
|
});
|
|
expect(_urlToState("/odoo/5/some-path/some.model")).toEqual({
|
|
model: "some.model",
|
|
actionStack: [{ active_id: 5, action: "some-path" }, { model: "some.model" }],
|
|
});
|
|
expect(_urlToState("/odoo/some-path/7/some.model")).toEqual({
|
|
active_id: 7,
|
|
model: "some.model",
|
|
actionStack: [
|
|
{ action: "some-path" },
|
|
{ action: "some-path", resId: 7 },
|
|
{ active_id: 7, model: "some.model" },
|
|
],
|
|
});
|
|
expect(_urlToState("/odoo/some-path/some.model/2")).toEqual({
|
|
model: "some.model",
|
|
resId: 2,
|
|
actionStack: [{ action: "some-path" }, { model: "some.model", resId: 2 }],
|
|
});
|
|
expect(_urlToState("/odoo/5/some-path/2/some.model")).toEqual({
|
|
active_id: 2,
|
|
model: "some.model",
|
|
actionStack: [
|
|
{ active_id: 5, action: "some-path" },
|
|
{ active_id: 5, action: "some-path", resId: 2 },
|
|
{ active_id: 2, model: "some.model" },
|
|
],
|
|
});
|
|
expect(_urlToState("/odoo/some-path/5/some.model/2")).toEqual({
|
|
active_id: 5,
|
|
model: "some.model",
|
|
resId: 2,
|
|
actionStack: [
|
|
{ action: "some-path" },
|
|
{ action: "some-path", resId: 5 },
|
|
{ active_id: 5, model: "some.model", resId: 2 },
|
|
],
|
|
});
|
|
expect(_urlToState("/odoo/some-path/5/some.model/new")).toEqual({
|
|
active_id: 5,
|
|
model: "some.model",
|
|
resId: "new",
|
|
actionStack: [
|
|
{ action: "some-path" },
|
|
{ action: "some-path", resId: 5 },
|
|
{ active_id: 5, model: "some.model", resId: "new" },
|
|
],
|
|
});
|
|
expect(_urlToState("/odoo/action-1/5/m-model_no_dot/2")).toEqual({
|
|
active_id: 5,
|
|
model: "model_no_dot",
|
|
resId: 2,
|
|
actionStack: [
|
|
{ action: 1 },
|
|
{ action: 1, resId: 5 },
|
|
{ active_id: 5, model: "model_no_dot", resId: 2 },
|
|
],
|
|
});
|
|
expect(_urlToState("/odoo/action-module.xml_id/5/m-model_no_dot/2")).toEqual({
|
|
active_id: 5,
|
|
model: "model_no_dot",
|
|
resId: 2,
|
|
actionStack: [
|
|
{ action: "module.xml_id" },
|
|
{ action: "module.xml_id", resId: 5 },
|
|
{ active_id: 5, model: "model_no_dot", resId: 2 },
|
|
],
|
|
});
|
|
// model + action
|
|
expect(_urlToState("/odoo/some.model/other-path")).toEqual({
|
|
action: "other-path",
|
|
actionStack: [{ action: "other-path" }],
|
|
});
|
|
expect(_urlToState("/odoo/5/some.model/other-path")).toEqual({
|
|
action: "other-path",
|
|
actionStack: [{ action: "other-path" }],
|
|
});
|
|
expect(_urlToState("/odoo/some.model/2/other-path")).toEqual({
|
|
active_id: 2,
|
|
action: "other-path",
|
|
actionStack: [
|
|
{ model: "some.model", resId: 2 },
|
|
{ active_id: 2, action: "other-path" },
|
|
],
|
|
});
|
|
expect(_urlToState("/odoo/some.model/other-path/2")).toEqual({
|
|
action: "other-path",
|
|
resId: 2,
|
|
actionStack: [{ action: "other-path" }, { action: "other-path", resId: 2 }],
|
|
});
|
|
expect(_urlToState("/odoo/5/some.model/2/other-path")).toEqual({
|
|
active_id: 2,
|
|
action: "other-path",
|
|
actionStack: [
|
|
{ active_id: 5, model: "some.model", resId: 2 },
|
|
{ active_id: 2, action: "other-path" },
|
|
],
|
|
});
|
|
expect(_urlToState("/odoo/some.model/5/other-path/2")).toEqual({
|
|
active_id: 5,
|
|
action: "other-path",
|
|
resId: 2,
|
|
actionStack: [
|
|
{ model: "some.model", resId: 5 },
|
|
{ active_id: 5, action: "other-path" },
|
|
{ active_id: 5, action: "other-path", resId: 2 },
|
|
],
|
|
});
|
|
expect(_urlToState("/odoo/some.model/5/other-path/new")).toEqual({
|
|
active_id: 5,
|
|
action: "other-path",
|
|
resId: "new",
|
|
actionStack: [
|
|
{ model: "some.model", resId: 5 },
|
|
{ active_id: 5, action: "other-path" },
|
|
{ active_id: 5, action: "other-path", resId: "new" },
|
|
],
|
|
});
|
|
expect(_urlToState("/odoo/m-model_no_dot/5/action-1/2")).toEqual({
|
|
active_id: 5,
|
|
action: 1,
|
|
resId: 2,
|
|
actionStack: [
|
|
{ model: "model_no_dot", resId: 5 },
|
|
{ active_id: 5, action: 1 },
|
|
{ active_id: 5, action: 1, resId: 2 },
|
|
],
|
|
});
|
|
expect(_urlToState("/odoo/m-model_no_dot/5/action-module.xml_id/2")).toEqual({
|
|
active_id: 5,
|
|
action: "module.xml_id",
|
|
resId: 2,
|
|
actionStack: [
|
|
{ model: "model_no_dot", resId: 5 },
|
|
{ active_id: 5, action: "module.xml_id" },
|
|
{ active_id: 5, action: "module.xml_id", resId: 2 },
|
|
],
|
|
});
|
|
|
|
// edge cases
|
|
expect(_urlToState("/odoo/some-path/5/other-path/2?some_key=some_value")).toEqual({
|
|
active_id: 5,
|
|
action: "other-path",
|
|
resId: 2,
|
|
actionStack: [
|
|
{ action: "some-path" },
|
|
{ action: "some-path", resId: 5 },
|
|
{ active_id: 5, action: "other-path" },
|
|
{ active_id: 5, action: "other-path", resId: 2 },
|
|
],
|
|
some_key: "some_value",
|
|
});
|
|
expect(_urlToState("/odoo/some-path/5/some.model?view_type=list")).toEqual(
|
|
{
|
|
active_id: 5,
|
|
model: "some.model",
|
|
actionStack: [
|
|
{ action: "some-path" },
|
|
{ action: "some-path", resId: 5 },
|
|
{ active_id: 5, model: "some.model" },
|
|
],
|
|
view_type: "list",
|
|
},
|
|
{ message: "view_type doesn't end up in the actionStack" }
|
|
);
|
|
expect(_urlToState("/odoo/some-path/5/some.model/2?view_type=list")).toEqual(
|
|
{
|
|
active_id: 5,
|
|
model: "some.model",
|
|
resId: 2,
|
|
actionStack: [
|
|
{ action: "some-path" },
|
|
{ action: "some-path", resId: 5 },
|
|
{ active_id: 5, model: "some.model", resId: 2 },
|
|
],
|
|
view_type: "list",
|
|
},
|
|
{ message: "view_type and resId aren't incompatible" }
|
|
);
|
|
expect(_urlToState("/odoo/some-path/2/5/other-path")).toEqual(
|
|
{
|
|
active_id: 5,
|
|
action: "other-path",
|
|
actionStack: [
|
|
{ action: "some-path" },
|
|
{ action: "some-path", resId: 2 },
|
|
{ active_id: 5, action: "other-path" },
|
|
],
|
|
},
|
|
{ message: "resId immediately following active_id: action" }
|
|
);
|
|
expect(_urlToState("/odoo/some.model/2/5/other.model")).toEqual(
|
|
{
|
|
active_id: 5,
|
|
model: "other.model",
|
|
actionStack: [
|
|
{ model: "some.model", resId: 2 },
|
|
{ active_id: 5, model: "other.model" },
|
|
],
|
|
},
|
|
{ message: "resId immediately following active_id: model" }
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("pushState", () => {
|
|
test("can push in same timeout", async () => {
|
|
createRouter();
|
|
|
|
expect(router.current).toEqual({});
|
|
|
|
router.pushState({ k1: 2 });
|
|
expect(router.current).toEqual({});
|
|
|
|
router.pushState({ k1: 3 });
|
|
expect(router.current).toEqual({});
|
|
await tick();
|
|
expect(router.current).toEqual({ k1: 3 });
|
|
});
|
|
|
|
test("can push state directly", async () => {
|
|
createRouter();
|
|
|
|
expect(router.current).toEqual({});
|
|
|
|
router.pushState({ k1: 2 }, { sync: true });
|
|
expect(router.current).toEqual({ k1: 2 });
|
|
|
|
router.pushState({ k1: 3 }, { sync: true });
|
|
expect(router.current).toEqual({ k1: 3 });
|
|
|
|
router.pushState({ k1: 4 });
|
|
router.pushState({ k2: 1 }, { sync: true });
|
|
expect(router.current).toEqual({ k1: 4, k2: 1 });
|
|
});
|
|
|
|
test("can lock keys", async () => {
|
|
createRouter();
|
|
|
|
router.addLockedKey("k1");
|
|
|
|
router.replaceState({ k1: 2 });
|
|
await tick();
|
|
expect(router.current).toEqual({ k1: 2 });
|
|
|
|
router.replaceState({ k1: 3 });
|
|
await tick();
|
|
expect(router.current).toEqual({ k1: 3 });
|
|
|
|
router.replaceState({ k2: 4 });
|
|
await tick();
|
|
expect(router.current).toEqual({ k1: 3, k2: 4 });
|
|
|
|
router.replaceState({ k1: 4 });
|
|
await tick();
|
|
expect(router.current).toEqual({ k1: 4, k2: 4 });
|
|
});
|
|
|
|
test("can re-lock keys in same final call", async () => {
|
|
createRouter();
|
|
|
|
router.addLockedKey("k1");
|
|
|
|
router.pushState({ k1: 2 });
|
|
await tick();
|
|
router.pushState({ k2: 1 });
|
|
router.pushState({ k1: 4 });
|
|
await tick();
|
|
expect(router.current).toEqual({ k1: 4, k2: 1 });
|
|
});
|
|
|
|
test("can replace search state", async () => {
|
|
createRouter();
|
|
|
|
router.pushState({ k1: 2 });
|
|
await tick();
|
|
expect(router.current).toEqual({ k1: 2 });
|
|
|
|
router.pushState({ k2: 3 }, { replace: true });
|
|
await tick();
|
|
expect(router.current).toEqual({ k2: 3 });
|
|
});
|
|
|
|
test("can replace search state with locked keys", async () => {
|
|
createRouter();
|
|
|
|
router.addLockedKey("k1");
|
|
|
|
router.pushState({ k1: 2 });
|
|
await tick();
|
|
expect(router.current).toEqual({ k1: 2 });
|
|
|
|
router.pushState({ k2: 3 }, { replace: true });
|
|
await tick();
|
|
expect(router.current).toEqual({ k1: 2, k2: 3 });
|
|
});
|
|
|
|
test("can merge hash", async () => {
|
|
createRouter();
|
|
|
|
router.pushState({ k1: 2 });
|
|
await tick();
|
|
expect(router.current).toEqual({ k1: 2 });
|
|
|
|
router.pushState({ k2: 3 });
|
|
await tick();
|
|
expect(router.current).toEqual({ k1: 2, k2: 3 });
|
|
});
|
|
|
|
test("undefined keys are not pushed", async () => {
|
|
redirect("/odoo");
|
|
const onPushState = () => expect.step("pushed state");
|
|
createRouter({ onPushState });
|
|
|
|
router.pushState({ k1: undefined });
|
|
await tick();
|
|
expect.verifySteps([]);
|
|
expect(router.current).toEqual({});
|
|
});
|
|
|
|
test("undefined keys destroy previous non locked keys", async () => {
|
|
createRouter();
|
|
|
|
router.pushState({ k1: 1 });
|
|
await tick();
|
|
expect(router.current).toEqual({ k1: 1 });
|
|
|
|
router.pushState({ k1: undefined });
|
|
await tick();
|
|
expect(router.current).toEqual({});
|
|
});
|
|
|
|
test("do not re-push when hash is same", async () => {
|
|
const onPushState = () => expect.step("pushed state");
|
|
createRouter({ onPushState });
|
|
|
|
router.pushState({ k1: 1, k2: 2 });
|
|
await tick();
|
|
expect.verifySteps(["pushed state"]);
|
|
|
|
router.pushState({ k2: 2, k1: 1 });
|
|
await tick();
|
|
expect.verifySteps([]);
|
|
});
|
|
|
|
test("do not re-push when hash is same (with integers as strings)", async () => {
|
|
const onPushState = () => expect.step("pushed state");
|
|
createRouter({ onPushState });
|
|
|
|
router.pushState({ k1: 1, k2: "2" });
|
|
await tick();
|
|
expect.verifySteps(["pushed state"]);
|
|
|
|
router.pushState({ k2: 2, k1: "1" });
|
|
await tick();
|
|
expect.verifySteps([]);
|
|
});
|
|
|
|
test("pushState adds action-related keys to last entry in actionStack", async () => {
|
|
createRouter();
|
|
|
|
router.pushState({ action: 1, resId: 2, actionStack: [{ action: 1, resId: 2 }] });
|
|
await tick();
|
|
expect(router.current).toEqual({
|
|
action: 1,
|
|
resId: 2,
|
|
actionStack: [{ action: 1, resId: 2 }],
|
|
});
|
|
|
|
router.pushState({
|
|
action: 3,
|
|
resId: 4,
|
|
view_type: "form",
|
|
model: "some.model",
|
|
active_id: 5,
|
|
someKey: "someVal",
|
|
});
|
|
await tick();
|
|
expect(router.current).toEqual({
|
|
action: 3,
|
|
resId: 4,
|
|
view_type: "form",
|
|
model: "some.model",
|
|
active_id: 5,
|
|
someKey: "someVal",
|
|
actionStack: [
|
|
{
|
|
action: 3,
|
|
resId: 4,
|
|
model: "some.model",
|
|
active_id: 5,
|
|
},
|
|
],
|
|
});
|
|
});
|
|
test("can hide keys", async () => {
|
|
createRouter();
|
|
|
|
router.hideKeyFromUrl("k1");
|
|
|
|
router.pushState({ k1: 2, k2: 3 });
|
|
await tick();
|
|
expect(router.current).toEqual({ k1: 2, k2: 3 });
|
|
expect(browser.location.href).toBe("https://www.hoot.test/odoo?k2=3");
|
|
});
|
|
test("different order of keys shouldn't push a new state", async () => {
|
|
redirect("/odoo?k1=2");
|
|
createRouter({
|
|
onPushState: () => expect.step("pushState"),
|
|
});
|
|
|
|
router.addLockedKey("z");
|
|
router.addLockedKey("a");
|
|
|
|
router.pushState({ z: 1, a: 2 });
|
|
await tick();
|
|
expect.verifySteps(["pushState"]);
|
|
expect(router.current).toEqual({ a: 2, z: 1, k1: 2 });
|
|
expect(browser.location.href).toBe("https://www.hoot.test/odoo?k1=2&z=1&a=2");
|
|
|
|
router.pushState({ k1: 2 }, { replace: true });
|
|
await tick();
|
|
expect.verifySteps([]);
|
|
expect(router.current).toEqual({ a: 2, z: 1, k1: 2 });
|
|
expect(browser.location.href).toBe("https://www.hoot.test/odoo?k1=2&z=1&a=2");
|
|
});
|
|
});
|
|
|
|
describe("History", () => {
|
|
test("properly handles history.back and history.forward", async () => {
|
|
redirect("/");
|
|
on(routerBus, "ROUTE_CHANGE", () => expect.step("ROUTE_CHANGE"));
|
|
createRouter();
|
|
|
|
router.pushState({ k1: 1 });
|
|
await tick();
|
|
expect(browser.location.href).toBe("https://www.hoot.test/odoo?k1=1");
|
|
|
|
router.pushState({ k2: 2 });
|
|
await tick();
|
|
expect(browser.location.href).toBe("https://www.hoot.test/odoo?k1=1&k2=2");
|
|
|
|
router.pushState({ k3: 3 }, { replace: true });
|
|
await tick();
|
|
expect(browser.location.href).toBe("https://www.hoot.test/odoo?k3=3");
|
|
|
|
browser.history.back(); // Click on back button
|
|
await tick();
|
|
expect(browser.location.href).toBe("https://www.hoot.test/odoo?k1=1&k2=2");
|
|
|
|
router.pushState({ k4: 3 }, { replace: true }); // Click on a link
|
|
await tick();
|
|
expect(browser.location.href).toBe("https://www.hoot.test/odoo?k4=3");
|
|
|
|
browser.history.back(); // Click on back button
|
|
await tick();
|
|
expect(browser.location.href).toBe("https://www.hoot.test/odoo?k1=1&k2=2");
|
|
|
|
browser.history.forward(); // Click on forward button
|
|
await tick();
|
|
expect(browser.location.href).toBe("https://www.hoot.test/odoo?k4=3");
|
|
|
|
expect.verifySteps(["ROUTE_CHANGE", "ROUTE_CHANGE", "ROUTE_CHANGE"]);
|
|
});
|
|
|
|
test("unserialized parts of action stack are preserved when going back/forward", async () => {
|
|
redirect("/odoo");
|
|
createRouter();
|
|
expect(router.current).toEqual({});
|
|
router.pushState({
|
|
actionStack: [{ action: "some-path", displayName: "A cool display name" }],
|
|
});
|
|
await tick();
|
|
expect(browser.location.href).toBe("https://www.hoot.test/odoo/some-path");
|
|
expect(router.current).toEqual({
|
|
actionStack: [{ action: "some-path", displayName: "A cool display name" }],
|
|
});
|
|
router.pushState({
|
|
actionStack: [{ action: "other-path", displayName: "A different display name" }],
|
|
});
|
|
await tick();
|
|
expect(browser.location.href).toBe("https://www.hoot.test/odoo/other-path");
|
|
expect(router.current).toEqual({
|
|
actionStack: [{ action: "other-path", displayName: "A different display name" }],
|
|
});
|
|
browser.history.back();
|
|
await tick();
|
|
expect(browser.location.href).toBe("https://www.hoot.test/odoo/some-path");
|
|
expect(router.current).toEqual({
|
|
actionStack: [{ action: "some-path", displayName: "A cool display name" }],
|
|
});
|
|
browser.history.forward();
|
|
await tick();
|
|
expect(browser.location.href).toBe("https://www.hoot.test/odoo/other-path");
|
|
expect(router.current).toEqual({
|
|
actionStack: [{ action: "other-path", displayName: "A different display name" }],
|
|
});
|
|
});
|
|
test("properly handles history.back with hidden keys", async () => {
|
|
redirect("/");
|
|
on(routerBus, "ROUTE_CHANGE", () => expect.step("ROUTE_CHANGE"));
|
|
createRouter();
|
|
|
|
router.hideKeyFromUrl("k1");
|
|
|
|
router.pushState({ k1: 1, k2: 2 });
|
|
await tick();
|
|
expect(router.current).toEqual({ k1: 1, k2: 2 });
|
|
expect(browser.location.href).toBe("https://www.hoot.test/odoo?k2=2");
|
|
|
|
router.pushState({ k3: 3 }, { replace: true }); // Click on a link
|
|
await tick();
|
|
expect(router.current).toEqual({ k3: 3 });
|
|
expect(browser.location.href).toBe("https://www.hoot.test/odoo?k3=3");
|
|
|
|
browser.history.back(); // Click on back button
|
|
await tick();
|
|
expect(router.current).toEqual({ k1: 1, k2: 2 });
|
|
expect(browser.location.href).toBe("https://www.hoot.test/odoo?k2=2");
|
|
|
|
expect.verifySteps(["ROUTE_CHANGE"]);
|
|
});
|
|
});
|
|
|
|
describe("Retrocompatibility", () => {
|
|
test("parse an url with hash (key/values)", async () => {
|
|
Object.assign(browser.location, { pathname: "/web" });
|
|
browser.location.hash = "#a=114&k=c.e&f=1&g=91";
|
|
createRouter();
|
|
expect(browser.location.search).toBe("?a=114&k=c.e&f=1&g=91");
|
|
expect(browser.location.hash).toBe("");
|
|
expect(router.current).toEqual({ a: 114, k: "c.e", f: 1, g: 91 });
|
|
expect(browser.location.pathname).toBe("/odoo");
|
|
});
|
|
|
|
test("parse an url with hash (key/values) and query string", async () => {
|
|
Object.assign(browser.location, { pathname: "/web" });
|
|
browser.location.hash = "#g=91";
|
|
browser.location.search = "?a=114&t=c.e&f=1";
|
|
createRouter();
|
|
expect(browser.location.search).toBe("?a=114&t=c.e&f=1&g=91");
|
|
expect(browser.location.hash).toBe("");
|
|
expect(router.current).toEqual({ a: 114, t: "c.e", f: 1, g: 91 });
|
|
expect(browser.location.pathname).toBe("/odoo");
|
|
});
|
|
|
|
test("parse an url with hash (anchor link)", async () => {
|
|
redirect("/odoo#anchor");
|
|
browser.location.hash = "#anchor";
|
|
createRouter();
|
|
expect(browser.location.search).toBe("");
|
|
expect(browser.location.hash).toBe("#anchor");
|
|
expect(browser.location.pathname).toBe("/odoo");
|
|
expect(router.current).toEqual({});
|
|
});
|
|
|
|
test("parse an url with hash (anchor link) and query string", async () => {
|
|
redirect("/odoo?a=114&g=c.e&f=1#anchor");
|
|
browser.location.hash = "#anchor";
|
|
browser.location.search = "?a=114&g=c.e&f=1";
|
|
createRouter();
|
|
expect(browser.location.search).toBe("?a=114&g=c.e&f=1");
|
|
expect(browser.location.hash).toBe("#anchor");
|
|
expect(router.current).toEqual({ a: 114, g: "c.e", f: 1 });
|
|
expect(browser.location.pathname).toBe("/odoo");
|
|
});
|
|
});
|
|
|
|
describe("internal links", () => {
|
|
test("click on internal link does a loadState instead of a full reload", async () => {
|
|
redirect("/odoo");
|
|
createRouter({ onPushState: () => expect.step("pushState") });
|
|
const fixture = getFixture();
|
|
const link = document.createElement("a");
|
|
link.href = "/odoo/some-action/2";
|
|
fixture.appendChild(link);
|
|
|
|
expect(router.current).toEqual({});
|
|
|
|
let defaultPrevented;
|
|
browser.addEventListener("click", (ev) => {
|
|
expect.step("click");
|
|
defaultPrevented = ev.defaultPrevented;
|
|
ev.preventDefault();
|
|
});
|
|
await click("a");
|
|
await tick();
|
|
expect.verifySteps(["click"]);
|
|
expect(router.current).toEqual({
|
|
action: "some-action",
|
|
actionStack: [
|
|
{
|
|
action: "some-action",
|
|
},
|
|
{
|
|
action: "some-action",
|
|
resId: 2,
|
|
},
|
|
],
|
|
resId: 2,
|
|
});
|
|
expect(defaultPrevented).toBe(true);
|
|
});
|
|
|
|
test("click on internal link with children does a loadState instead of a full reload", async () => {
|
|
redirect("/odoo");
|
|
createRouter({ onPushState: () => expect.step("pushState") });
|
|
const fixture = getFixture();
|
|
const link = document.createElement("a");
|
|
const span = document.createElement("span");
|
|
link.appendChild(span);
|
|
link.href = "/odoo/some-action/2";
|
|
fixture.appendChild(link);
|
|
|
|
expect(router.current).toEqual({});
|
|
|
|
let defaultPrevented;
|
|
browser.addEventListener("click", (ev) => {
|
|
expect.step("click");
|
|
defaultPrevented = ev.defaultPrevented;
|
|
ev.preventDefault();
|
|
});
|
|
await click("span");
|
|
await tick();
|
|
expect.verifySteps(["click"]);
|
|
expect(router.current).toEqual({
|
|
action: "some-action",
|
|
actionStack: [
|
|
{
|
|
action: "some-action",
|
|
},
|
|
{
|
|
action: "some-action",
|
|
resId: 2,
|
|
},
|
|
],
|
|
resId: 2,
|
|
});
|
|
expect(defaultPrevented).toBe(true);
|
|
});
|
|
|
|
test("click on internal link with different protocol does a loadState", async () => {
|
|
redirect("/odoo");
|
|
createRouter({ onPushState: () => expect.step("pushState") });
|
|
const fixture = getFixture();
|
|
const link = document.createElement("a");
|
|
link.href = "http://" + browser.location.host + "/odoo/some-action/2";
|
|
fixture.appendChild(link);
|
|
|
|
expect(router.current).toEqual({});
|
|
expect(browser.location.protocol).not.toBe(link.protocol, {
|
|
message:
|
|
"should have different protocols between the current location and the clicked link",
|
|
});
|
|
|
|
let defaultPrevented;
|
|
browser.addEventListener("click", (ev) => {
|
|
expect.step("click");
|
|
defaultPrevented = ev.defaultPrevented;
|
|
ev.preventDefault();
|
|
});
|
|
await click("a");
|
|
await tick();
|
|
expect.verifySteps(["click"]);
|
|
expect(router.current).toEqual({
|
|
action: "some-action",
|
|
actionStack: [
|
|
{
|
|
action: "some-action",
|
|
},
|
|
{
|
|
action: "some-action",
|
|
resId: 2,
|
|
},
|
|
],
|
|
resId: 2,
|
|
});
|
|
expect(defaultPrevented).toBe(true);
|
|
});
|
|
|
|
test("click on internal link with hash (key/values)", async () => {
|
|
redirect("/odoo");
|
|
createRouter({
|
|
onPushState: () => expect.step("pushState"),
|
|
onReplaceState: () => expect.step("replaceState"),
|
|
});
|
|
const fixture = getFixture();
|
|
const link = document.createElement("a");
|
|
link.href = "/odoo/1/action-114/22";
|
|
fixture.appendChild(link);
|
|
|
|
expect(router.current).toEqual({});
|
|
|
|
let defaultPrevented;
|
|
browser.addEventListener("click", (ev) => {
|
|
expect.step("click");
|
|
defaultPrevented = ev.defaultPrevented;
|
|
ev.preventDefault();
|
|
});
|
|
await click("a");
|
|
await tick();
|
|
expect.verifySteps(["click"]);
|
|
expect(router.current).toEqual({
|
|
action: 114,
|
|
active_id: 1,
|
|
actionStack: [
|
|
{
|
|
active_id: 1,
|
|
action: 114,
|
|
},
|
|
{
|
|
active_id: 1,
|
|
resId: 22,
|
|
action: 114,
|
|
},
|
|
],
|
|
resId: 22,
|
|
});
|
|
expect(defaultPrevented).toBe(true);
|
|
});
|
|
|
|
test("click on internal link with hash (anchor)", async () => {
|
|
redirect("/odoo");
|
|
createRouter({
|
|
onPushState: (_data, _unused, url) => {
|
|
expect.step("pushState: " + url);
|
|
},
|
|
onReplaceState: () => expect.step("replaceState"),
|
|
});
|
|
const fixture = getFixture();
|
|
const link = document.createElement("a");
|
|
link.href = "/odoo/1/action-114/22#anchorId";
|
|
fixture.appendChild(link);
|
|
|
|
expect(router.current).toEqual({});
|
|
|
|
let defaultPrevented;
|
|
browser.addEventListener("click", (ev) => {
|
|
expect.step("click");
|
|
defaultPrevented = ev.defaultPrevented;
|
|
ev.preventDefault();
|
|
});
|
|
await click("a");
|
|
await tick();
|
|
expect.verifySteps([
|
|
"pushState: https://www.hoot.test/odoo/1/action-114/22#anchorId",
|
|
"click",
|
|
]);
|
|
expect(router.current).toEqual({
|
|
action: 114,
|
|
active_id: 1,
|
|
actionStack: [
|
|
{
|
|
active_id: 1,
|
|
action: 114,
|
|
},
|
|
{
|
|
active_id: 1,
|
|
resId: 22,
|
|
action: 114,
|
|
},
|
|
],
|
|
resId: 22,
|
|
});
|
|
expect(defaultPrevented).toBe(true);
|
|
});
|
|
|
|
test("click on internal link with target _blank doesn't do a loadState", async () => {
|
|
redirect("/odoo");
|
|
createRouter({ onPushState: () => expect.step("pushState") });
|
|
const fixture = getFixture();
|
|
const link = document.createElement("a");
|
|
link.href = "/odoo/some-action/2";
|
|
link.target = "_blank";
|
|
fixture.appendChild(link);
|
|
|
|
expect(router.current).toEqual({});
|
|
|
|
let defaultPrevented;
|
|
link.addEventListener("click", (ev) => {
|
|
expect.step("click");
|
|
defaultPrevented = ev.defaultPrevented;
|
|
ev.preventDefault();
|
|
});
|
|
await click("a");
|
|
await tick();
|
|
expect.verifySteps(["click"]);
|
|
expect(router.current).toEqual({});
|
|
expect(defaultPrevented).toBe(false);
|
|
});
|
|
});
|