Files
test/web_map/static/tests/map_view/map_view_tests.js
2023-04-14 17:42:23 +08:00

2539 lines
97 KiB
JavaScript

/** @odoo-module **/
import { MapModel } from "@web_map/map_view/map_model";
import { makeView } from "@web/../tests/views/helpers";
import {
setupControlPanelServiceRegistry,
toggleFilterMenu,
toggleMenuItem,
toggleGroupByMenu,
toggleMenuItemOption,
} from "@web/../tests/search/helpers";
import { registry } from "@web/core/registry";
import { dialogService } from "@web/core/dialog/dialog_service";
import {
click,
getFixture,
makeDeferred,
nextTick,
patchWithCleanup,
destroy,
findChildren,
} from "@web/../tests/helpers/utils";
import { session } from "@web/session";
import { browser } from "@web/core/browser/browser";
import {
makeFakeHTTPService,
makeFakeLocalizationService,
} from "@web/../tests/helpers/mock_services";
import { MapRenderer } from "@web_map/map_view/map_renderer";
const serviceRegistry = registry.category("services");
let serverData;
let target;
const MAP_BOX_TOKEN = "token";
function findMapRenderer(map) {
return findChildren(map, (c) => c.component instanceof MapRenderer).component;
}
QUnit.module("Views", (hooks) => {
hooks.beforeEach(() => {
target = getFixture();
const models = {
"project.task": {
fields: {
display_name: { string: "name", type: "char" },
sequence: { string: "sequence", type: "integer" },
partner_id: {
string: "partner",
type: "many2one",
relation: "res.partner",
},
another_partner_id: {
string: "another relation",
type: "many2one",
relation: "res.partner}",
},
partner_ids: {
string: "Partners",
type: "one2many",
comodel_name: "res.partner",
relation: "res.partner",
relation_field: "task_id",
},
},
records: [{ id: 1, display_name: "project", partner_id: 1 }],
oneRecord: {
records: [{ id: 1, display_name: "Foo", partner_id: [1] }],
length: 1,
},
twoRecords: {
records: [
{ id: 1, display_name: "FooProject", sequence: 1, partner_id: [1] },
{ id: 2, display_name: "BarProject", sequence: 2, partner_id: [2] },
],
length: 2,
},
threeRecords: {
records: [
{
id: 1,
display_name: "FooProject",
sequence: 1,
partner_id: [1],
partner_ids: [1, 2],
},
{
id: 2,
display_name: "BarProject",
sequence: 2,
partner_id: [2],
partner_ids: [1, 3],
},
{
id: 3,
display_name: "FooBarProject",
sequence: 3,
partner_id: [1],
partner_ids: [1],
},
],
length: 3,
},
twoRecordOnePartner: {
records: [
{ id: 1, display_name: "FooProject", partner_id: [1] },
{ id: 2, display_name: "BarProject", partner_id: [1] },
],
length: 2,
},
noRecord: {
records: [],
length: 0,
},
recordWithouthPartner: {
records: [{ id: 1, display_name: "Foo", partner_id: [] }],
length: 1,
},
anotherPartnerId: {
records: [{ id: 1, display_name: "FooProject", another_partner_id: [1] }],
length: 1,
},
},
"res.partner": {
fields: {
name: { string: "Customer", type: "char" },
partner_latitude: { string: "Latitude", type: "float" },
partner_longitude: { string: "Longitude", type: "float" },
contact_address_complete: { string: "Address", type: "char" },
task_ids: {
string: "Task",
type: "one2many",
relation: "project.task",
relation_field: "partner_id",
},
sequence: { string: "sequence", type: "integer" },
},
records: [
{
id: 1,
name: "Foo",
partner_latitude: 10.0,
partner_longitude: 10.5,
contact_address_complete: "Chaussée de Namur 40, 1367, Ramillies",
sequence: 1,
},
{
id: 2,
name: "Foo",
partner_latitude: 10.0,
partner_longitude: 10.5,
contact_address_complete: "Chaussée de Namur 40, 1367, Ramillies",
sequence: 3,
},
{
id: 3,
name: "Bar",
partner_latitude: 11.0,
partner_longitude: 11.5,
contact_address_complete: "Chaussée de Wavre 50, 1367, Ramillies",
sequence: 4,
},
],
methods: {
update_latitude_longitude() {
return Promise.resolve();
},
},
coordinatesNoAddress: [
{
id: 1,
name: "Foo",
partner_latitude: 10.0,
partner_longitude: 10.5,
},
],
oneLocatedRecord: [
{
id: 1,
name: "Foo",
partner_latitude: 10.0,
partner_longitude: 10.5,
contact_address_complete: "Chaussée de Namur 40, 1367, Ramillies",
sequence: 1,
},
],
wrongCoordinatesNoAddress: [
{
id: 1,
name: "Foo",
partner_latitude: 10000.0,
partner_longitude: 100000.5,
},
],
noCoordinatesGoodAddress: [
{
id: 1,
name: "Foo",
partner_latitude: 0,
partner_longitude: 0,
contact_address_complete: "Chaussée de Namur 40, 1367, Ramillies",
},
],
emptyRecords: [],
twoRecordsAddressNoCoordinates: [
{
id: 2,
name: "Foo",
contact_address_complete: "Chaussée de Namur 40, 1367, Ramillies",
sequence: 3,
},
{
id: 1,
name: "Bar",
contact_address_complete: "Chaussée de Louvain 94, 5310 Éghezée",
sequence: 1,
},
],
twoRecordsAddressCoordinates: [
{
id: 2,
name: "Foo",
partner_latitude: 10.0,
partner_longitude: 10.5,
contact_address_complete: "Chaussée de Namur 40, 1367, Ramillies",
sequence: 3,
},
{
id: 1,
name: "Bar",
partner_latitude: 10.0,
partner_longitude: 10.5,
contact_address_complete: "Chaussée de Louvain 94, 5310 Éghezée",
sequence: 1,
},
],
twoRecordsOneUnlocated: [
{
id: 1,
name: "Foo",
contact_address_complete: "Chaussée de Namur 40, 1367, Ramillies",
sequence: 3,
},
{
id: 2,
name: "Bar",
},
],
unlocatedRecords: [{ id: 1, name: "Foo" }],
noCoordinatesWrongAddress: [
{
id: 1,
name: "Foo",
contact_address_complete: "Cfezfezfefes",
},
],
},
};
serverData = { models };
setupControlPanelServiceRegistry();
serviceRegistry.add("dialog", dialogService);
serviceRegistry.add("localization", makeFakeLocalizationService());
serviceRegistry.add("http", makeFakeHTTPService());
patchWithCleanup(browser, {
setTimeout: (fn) => fn(),
clearTimeout: () => {},
});
patchWithCleanup(MapModel, {
// set delay to 0 as _fetchCoordinatesFromAddressOSM is mocked
COORDINATE_FETCH_DELAY: 0,
});
patchWithCleanup(MapModel.prototype, {
_fetchCoordinatesFromAddressMB(metaData, data, record) {
if (metaData.mapBoxToken !== MAP_BOX_TOKEN) {
return Promise.reject({ status: 401 });
}
const coordinates = [];
coordinates[0] = 10.0;
coordinates[1] = 10.5;
const geometry = { coordinates };
const features = [];
features[0] = { geometry };
const successResponse = { features };
const failResponse = { features: [] };
switch (record.contact_address_complete) {
case "Cfezfezfefes":
return Promise.resolve(failResponse);
case "":
return Promise.resolve(failResponse);
}
return Promise.resolve(successResponse);
},
_fetchCoordinatesFromAddressOSM(metaData, data, record) {
const coordinates = [];
coordinates[0] = { lat: 10.0, lon: 10.5 };
switch (record.contact_address_complete) {
case "Cfezfezfefes":
return Promise.resolve([]);
case "":
return Promise.resolve([]);
}
return Promise.resolve(coordinates);
},
_fetchRoute(metaData, data) {
if (metaData.mapBoxToken !== MAP_BOX_TOKEN) {
return Promise.reject({ status: 401 });
}
const legs = [];
for (let i = 1; i < data.records.length; i++) {
const coordinates = [];
coordinates[0] = [10, 10.5];
coordinates[1] = [10, 10.6];
const geometry = { coordinates };
const steps = [];
steps[0] = { geometry };
legs.push({ steps: steps });
}
const routes = [];
routes[0] = { legs };
return Promise.resolve({ routes });
},
_notifyFetchedCoordinate(metaData, data) {
// do not notify in tests as coords fetching is " synchronous "
},
_openStreetMapAPI(metaData, data) {
// return promise to wait for it
return this._openStreetMapAPIAsync(metaData, data);
},
});
});
QUnit.module("MapView");
//--------------------------------------------------------------------------
// Testing data fetching
//--------------------------------------------------------------------------
/**
* data: no record
* Should have no record
* Should have no marker
* Should have no route
*/
QUnit.test("Create a view with no record", async function (assert) {
assert.expect(8);
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
const map = await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `
<map res_partner="partner_id" routing="1">
<field name="name" string="Project"/>
</map>
`,
async mockRPC(route, { model, kwargs }) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
assert.strictEqual(
model,
"project.task",
"The model should be project.task"
);
assert.strictEqual(kwargs.fields[0], "partner_id");
assert.strictEqual(kwargs.fields[1], "display_name");
return serverData.models["project.task"].noRecord;
case "/web/dataset/call_kw/res.partner/search_read":
assert.ok(
false,
"Should not search_read the partners if there are no partner"
);
}
},
});
assert.strictEqual(
target.querySelector("a.btn.btn-primary").href,
"https://www.google.com/maps/dir/?api=1",
"The link's URL should not contain any coordinates"
);
assert.strictEqual(
map.model.metaData.resPartnerField,
"partner_id",
"the resPartnerField should be set"
);
assert.strictEqual(map.model.data.records.length, 0, "There should be no records");
assert.containsNone(target, "div.leaflet-marker-icon", "No marker should be on a the map.");
assert.containsNone(
target.querySelector(".leaflet-overlay-pane"),
"path",
"No route should be shown"
);
});
/**
* data: one record that has no partner linked to it
* The record should be kept and displayed in the list of records in gray (no clickable)
* should have no marker
* Should have no route
*/
QUnit.test("Create a view with one record that has no partner", async function (assert) {
assert.expect(5);
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
const map = await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].recordWithouthPartner;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].emptyRecords;
}
},
});
assert.strictEqual(map.model.data.records.length, 1, "There should be 1 records");
assert.containsNone(target, "div.leaflet-marker-icon", "No marker should be on a the map.");
assert.containsNone(
target.querySelector(".leaflet-overlay-pane"),
"path",
"No route should be shown"
);
assert.containsOnce(
target,
".o-map-renderer--pin-list-container .o-map-renderer--pin-list-details li"
);
assert.containsOnce(
target,
".o-map-renderer--pin-list-container .o-map-renderer--pin-list-details li span"
);
});
/**
* data: one record that has a partner which has coordinates but no address
* One record
* One marker
* no route
*/
QUnit.test(
"Create a view with one record and a partner located by coordinates",
async function (assert) {
assert.expect(3);
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
const map = await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].oneRecord;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].coordinatesNoAddress;
}
},
});
assert.strictEqual(map.model.data.records.length, 1, "There should be one records");
assert.containsOnce(
target,
"div.leaflet-marker-icon",
"There should be one marker on the map"
);
assert.containsNone(
target.querySelector(".leaflet-overlay-pane"),
"path",
"There should be no route on the map"
);
}
);
/**
* data: one record linked to one partner with no address and wrong coordinates
* api: MapBox
* record should be kept and displayed in the list
* no route
* no marker
*/
QUnit.test(
"Create view with one record linked to a partner with wrong coordinates with MB",
async function (assert) {
assert.expect(5);
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
const map = await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].oneRecord;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].wrongCoordinatesNoAddress;
}
},
});
assert.strictEqual(map.model.data.records.length, 1, "There should be one records");
assert.containsNone(
target,
"div.leaflet-marker-icon",
"There should be np marker on the map"
);
assert.containsNone(
target.querySelector(".leaflet-overlay-pane"),
"path",
"There should be no route on the map"
);
assert.containsOnce(
target,
".o-map-renderer--pin-list-container .o-map-renderer--pin-list-details li"
);
assert.containsOnce(
target,
".o-map-renderer--pin-list-container .o-map-renderer--pin-list-details li span"
);
}
);
/**
* data: one record linked to one partner with no address and wrong coordinates
* api: OpenStreet Map
* record should be kept
* no route
* no marker
*/
QUnit.test(
"Create view with one record linked to a partner with wrong coordinates with OSM",
async function (assert) {
assert.expect(3);
const map = await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].oneRecord;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].wrongCoordinatesNoAddress;
}
},
});
assert.strictEqual(map.model.data.records.length, 1, "There should be one records");
assert.containsNone(
target,
"div.leaflet-marker-icon",
"There should be no marker on the map"
);
assert.containsNone(
target.querySelector(".leaflet-overlay-pane"),
"path",
"There should be no route on the map"
);
}
);
/**
* data: one record linked to one partner with no coordinates and good address
* api: OpenStreet Map
* caching RPC called, assert good args
* one record
* no route
*/
QUnit.test(
"Create View with one record linked to a partner with no coordinates and right address OSM",
async function (assert) {
assert.expect(7);
const map = await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1" />`,
async mockRPC(route, { args, method, model }) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].oneRecord;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].noCoordinatesGoodAddress;
case "/web/dataset/call_kw/res.partner/update_latitude_longitude":
assert.strictEqual(
model,
"res.partner",
'The model should be "res.partner"'
);
assert.strictEqual(method, "update_latitude_longitude");
assert.strictEqual(
args[0].length,
1,
"There should be one record needing caching"
);
assert.strictEqual(args[0][0].id, 1, "The records's id should be 1");
}
},
});
assert.strictEqual(map.model.data.records.length, 1, "There should be one records");
assert.containsOnce(
target,
"div.leaflet-marker-icon",
"There should be one marker on the map"
);
assert.containsNone(
target.querySelector(".leaflet-overlay-pane"),
"path",
"There should be no route on the map"
);
}
);
/**
* data: one record linked to one partner with no coordinates and good address
* api: MapBox
* caching RPC called, assert good args
* one record
* no route
*/
QUnit.test(
"Create View with one record linked to a partner with no coordinates and right address MB",
async function (assert) {
assert.expect(7);
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
const map = await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1" />`,
async mockRPC(route, { args, method, model }) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].oneRecord;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].noCoordinatesGoodAddress;
case "/web/dataset/call_kw/res.partner/update_latitude_longitude":
assert.strictEqual(
model,
"res.partner",
'The model should be "res.partner"'
);
assert.strictEqual(method, "update_latitude_longitude");
assert.strictEqual(
args[0].length,
1,
"There should be one record needing caching"
);
assert.strictEqual(args[0][0].id, 1, "The records's id should be 1");
}
},
});
assert.strictEqual(map.model.data.records.length, 1, "There should be one records");
assert.containsOnce(
target,
"div.leaflet-marker-icon",
"There should be one marker on the map"
);
assert.containsNone(
target.querySelector(".leaflet-overlay-pane"),
"path",
"There should be no route on the map"
);
}
);
/**
* data: one record linked to a partner with no coordinates and no address
* api: MapBox
* 1 record
* no route
* no marker
*/
QUnit.test("Create view with no located record", async function (assert) {
assert.expect(3);
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
const map = await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].oneRecord;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].unlocatedRecords;
}
},
});
assert.strictEqual(map.model.data.records.length, 1, "There should be one records");
assert.containsNone(target, "div.leaflet-marker-icon", "No marker should be on a the map.");
assert.containsNone(
target.querySelector(".leaflet-overlay-pane"),
"path",
"No route should be shown"
);
});
/**
* data: one record linked to a partner with no coordinates and no address
* api: OSM
* one record
* no route
* no marker
*/
QUnit.test("Create view with no located record OSM", async function (assert) {
assert.expect(3);
const map = await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].oneRecord;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].unlocatedRecords;
}
},
});
assert.strictEqual(map.model.data.records.length, 1, "There should be one records");
assert.containsNone(target, "div.leaflet-marker-icon", "No marker should be on a the map.");
assert.containsNone(
target.querySelector(".leaflet-overlay-pane"),
"path",
"No route should be shown"
);
});
/**
* data: one record linked to a partner with no coordinates and wrong address
* api: OSM
* one record
* no route
* no marker
*/
QUnit.test("Create view with no badly located record OSM", async function (assert) {
assert.expect(3);
const map = await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].oneRecord;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].noCoordinatesWrongAddress;
}
},
});
assert.strictEqual(map.model.data.records.length, 1, "There should be one records");
assert.containsNone(target, "div.leaflet-marker-icon", "No marker should be on a the map.");
assert.containsNone(
target.querySelector(".leaflet-overlay-pane"),
"path",
"No route should be shown"
);
});
/**
* data: one record linked to a partner with no coordinates and wrong address
* api: mapbox
* one record
* no route
* no marker
*/
QUnit.test("Create view with no badly located record MB", async function (assert) {
assert.expect(3);
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
const map = await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].oneRecord;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].noCoordinatesWrongAddress;
}
},
});
assert.strictEqual(map.model.data.records.length, 1, "There should be one records");
assert.containsNone(target, "div.leaflet-marker-icon", "No marker should be on a the map.");
assert.containsNone(
target.querySelector(".leaflet-overlay-pane"),
"path",
"No route should be shown"
);
});
/**
* data: 2 records linked to the same partner
* 2 records
* 2 markers
* no route
* same partner object
* 1 caching request
*/
QUnit.test("Create a view with two located records same partner", async function (assert) {
assert.expect(4);
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
const map = await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].twoRecordOnePartner;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].oneLocatedRecord;
}
},
});
assert.strictEqual(map.model.data.records.length, 2, "There should be 2 records");
assert.strictEqual(
target.querySelector("div.leaflet-marker-icon .o-map-renderer--marker-badge")
.textContent,
"2",
"There should be a marker for two records"
);
assert.containsOnce(
target.querySelector(".leaflet-overlay-pane"),
"path",
"There should be one route showing"
);
assert.equal(
map.model.data.records[0].partner,
map.model.data.records[1].partner,
"The records should have the same partner object as a property"
);
});
/**
* data: 2 records linked to differnet partners
* 2 records
* 1 route
* different partner object.
* 2 caching
*/
QUnit.test(
"Create a a view with two located records different partner",
async function (assert) {
assert.expect(5);
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
const map = await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1" />`,
async mockRPC(route, { args }) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].twoRecords;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].twoRecordsAddressNoCoordinates;
case "/web/dataset/call_kw/res.partner/update_latitude_longitude":
assert.strictEqual(
args[0].length,
2,
"Should have 2 record needing caching"
);
}
},
});
assert.strictEqual(map.model.data.records.length, 2, "There should be 2 records");
assert.strictEqual(
target.querySelector("div.leaflet-marker-icon .o-map-renderer--marker-badge")
.textContent,
"2",
"There should be a marker for two records"
);
assert.containsOnce(
target.querySelector(".leaflet-overlay-pane"),
"path",
"There should be one route showing"
);
assert.notEqual(
map.model.data.records[0].partner,
map.model.data.records[1].partner,
"The records should have the same partner object as a property"
);
}
);
/**
* data: 2 valid res.partner records
* test the case where the model is res.partner and the "res.partner" field is the id
* should have 2 records,
* 2 markers
* no route
*/
QUnit.test("Create a view with res.partner", async function (assert) {
assert.expect(8);
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
serverData.models["res.partner"].recordsPrimary = {
records: [
{
id: 2,
name: "Foo",
contact_address_complete: "Chaussée de Namur 40, 1367, Ramillies",
sequence: 3,
},
{
id: 1,
name: "FooBar",
contact_address_complete: "Chaussée de Louvain 94, 5310 Éghezée",
sequence: 1,
},
],
length: 2,
};
const map = await makeView({
serverData,
type: "map",
resModel: "res.partner",
arch: `<map res_partner="id" />`,
async mockRPC(route, { kwargs, model }) {
switch (route) {
case "/web/dataset/call_kw/res.partner/web_search_read":
assert.strictEqual(model, "res.partner", "The model should be res.partner");
assert.strictEqual(kwargs.fields[0], "id");
return serverData.models["res.partner"].recordsPrimary;
case "/web/dataset/call_kw/res.partner/search_read":
assert.strictEqual(
model,
"res.partner",
"The model should be res.partner as well"
);
assert.strictEqual(kwargs.domain[1][2][0], 2);
assert.strictEqual(kwargs.domain[1][2][1], 1);
return serverData.models["res.partner"].twoRecordsAddressNoCoordinates;
}
},
});
assert.strictEqual(map.model.data.records.length, 2, "There should be two records");
assert.strictEqual(
target.querySelector("div.leaflet-marker-icon .o-map-renderer--marker-badge")
.textContent,
"2",
"There should be a marker for two records"
);
assert.containsNone(
target.querySelector(".leaflet-overlay-pane"),
"path",
"There should be no route showing"
);
});
/**
* data: 3 records linked to one located partner and one unlocated
* test if only the 2 located records are displayed
*/
QUnit.test("Create a view with 2 located records and 1 unlocated", async function (assert) {
assert.expect(4);
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
const map = await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].threeRecords;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].twoRecordsOneUnlocated;
}
},
});
assert.strictEqual(map.model.data.records.length, 3);
assert.strictEqual(map.model.data.records[0].partner.id, 1, "The partner's id should be 1");
assert.strictEqual(map.model.data.records[1].partner.id, 2, "The partner's id should be 2");
assert.strictEqual(map.model.data.records[2].partner.id, 1, "The partner's id should be 1");
});
QUnit.test("Change load limit", async function (assert) {
assert.expect(2);
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
serverData.models["project.task"].records =
serverData.models["project.task"].threeRecords.records;
serverData.models["res.partner"].records =
serverData.models["res.partner"].twoRecordsAddressCoordinates;
await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" limit="2" />`,
});
assert.strictEqual(
target.querySelector(`.o_pager_counter .o_pager_value`).textContent.trim(),
"1-2"
);
assert.strictEqual(
target.querySelector(`.o_pager_counter span.o_pager_limit`).innerText.trim(),
"3"
);
});
//--------------------------------------------------------------------------
// Renderer testing
//--------------------------------------------------------------------------
QUnit.test("Google Maps redirection", async function (assert) {
assert.expect(2);
await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id"></map>`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].twoRecords;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].twoRecordsAddressNoCoordinates;
}
},
});
assert.strictEqual(
target.querySelector("a.btn.btn-primary").href,
"https://www.google.com/maps/dir/?api=1&waypoints=10,10.5",
"The link's URL should contain the right sets of coordinates"
);
await click(target, ".leaflet-marker-icon");
assert.strictEqual(
target.querySelector("div.leaflet-popup a.btn.btn-primary").href,
"https://www.google.com/maps/dir/?api=1&destination=10,10.5",
"The link's URL should the right set of coordinates"
);
});
QUnit.test("Google Maps redirection (with routing = true)", async function (assert) {
assert.expect(2);
await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1"></map>`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].twoRecords;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].twoRecordsAddressNoCoordinates;
}
},
});
assert.strictEqual(
target.querySelector("a.btn.btn-primary").href,
"https://www.google.com/maps/dir/?api=1&destination=10,10.5",
"The link's URL should contain the right sets of coordinates"
);
await click(target, ".leaflet-marker-icon");
assert.strictEqual(
target.querySelector("div.leaflet-popup a.btn.btn-primary").href,
"https://www.google.com/maps/dir/?api=1&destination=10,10.5",
"The link's URL should the right set of coordinates"
);
});
QUnit.test("Unicity of coordinates in Google Maps url", async function (assert) {
assert.expect(2);
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].twoRecordOnePartner;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].twoRecordsAddressNoCoordinates;
}
},
});
assert.strictEqual(
target.querySelector("a.btn.btn-primary").href,
"https://www.google.com/maps/dir/?api=1&waypoints=10.5,10",
"The link's URL should contain unqiue sets of coordinates"
);
await click(target, ".leaflet-marker-icon");
assert.strictEqual(
target.querySelector("div.leaflet-popup a.btn.btn-primary").href,
"https://www.google.com/maps/dir/?api=1&destination=10.5,10",
"The link's URL should only contain unqiue sets of coordinates"
);
});
QUnit.test("test the position of pin", async function (assert) {
assert.expect(4);
const map = await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].twoRecords;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].twoRecordsAddressNoCoordinates;
}
},
});
assert.containsOnce(target, ".o-map-renderer--marker", "Should have one marker created");
assert.strictEqual(
target.querySelector("div.leaflet-marker-icon .o-map-renderer--marker-badge")
.textContent,
"2",
"There should be a marker for two records"
);
const renderer = findMapRenderer(map);
assert.strictEqual(
renderer.markers[0].getLatLng().lat,
10,
"The latitude should be the same as the record"
);
assert.strictEqual(
renderer.markers[0].getLatLng().lng,
10.5,
"The longitude should be the same as the record"
);
});
/**
* data: two located records
* Create an empty map
*/
QUnit.test("Create of a empty map", async function (assert) {
assert.expect(6);
const map = await makeView({
serverData,
type: "map",
resModel: "res.partner",
arch: `<map />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].twoRecords;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].twoRecordsAddressNoCoordinates;
}
},
});
assert.notOk(map.model.metaData.resPartnerField, "the resPartnerField should not be set");
assert.hasClass(target.querySelector(".o_map_view"), "o_view_controller");
assert.containsOnce(target, ".leaflet-map-pane", "If the map exists this div should exist");
assert.ok(
target.querySelector(".leaflet-pane .leaflet-tile-pane").children.length,
"The map tiles should have been happened to the DOM"
);
// if element o-map-renderer--container has class leaflet-container then
// the map is mounted
assert.hasClass(
target.querySelector(".o-map-renderer--container"),
"leaflet-container",
"the map should be in the DOM"
);
assert.strictEqual(
target.querySelector(".leaflet-overlay-pane").children.length,
0,
"Should have no showing route"
);
});
/**
* two located records
* without routing or default_order
* normal marker icon
* test the click on them
*/
QUnit.test("Create view with normal marker icons", async function (assert) {
assert.expect(6);
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
const map = await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].twoRecords;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].twoRecordsAddressNoCoordinates;
}
},
});
assert.notOk(map.model.metaData.numbering, "the numbering option should not be enabled");
assert.notOk(map.model.metaData.routing, "The routing option should not be enabled");
assert.containsOnce(target, ".leaflet-marker-icon", "There should be 1 marker");
assert.containsNone(
target.querySelector(".leaflet-overlay-pane"),
"path",
"There should be no route showing"
);
await click(target, ".leaflet-marker-icon");
assert.strictEqual(
target.querySelector(".leaflet-popup-pane").children.length,
1,
"Should have one showing popup"
);
await click(target, "div.leaflet-container");
// wait for the popup's destruction which takes a certain time...
for (let i = 0; i < 15; i++) {
await nextTick();
}
assert.strictEqual(
target.querySelector(".leaflet-popup-pane").children.length,
0,
"Should not have any showing popup"
);
});
/**
* two located records
* with default_order
* no numbered icon
* test click on them
* asserts that the rpc receive the right parameters
*/
QUnit.test("Create a view with default_order", async function (assert) {
assert.expect(7);
const map = await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" default_order="name" />`,
async mockRPC(route, { kwargs }) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
assert.deepEqual(
kwargs.order,
"name ASC",
"The sorting order should be on the field name in a ascendant way"
);
return serverData.models["project.task"].twoRecords;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].twoRecordsAddressNoCoordinates;
}
},
});
assert.notOk(map.model.metaData.numbering, "the numbering option should not be enabled");
assert.notOk(map.model.metaData.routing, "The routing option should not be enabled");
assert.containsOnce(target, "div.leaflet-marker-icon", "There should be 1 marker");
assert.strictEqual(
target.querySelector("div.leaflet-marker-icon .o-map-renderer--marker-badge")
.textContent,
"2",
"There should be a marker for two records"
);
assert.strictEqual(
target.querySelector(".leaflet-popup-pane").children.length,
0,
"Should have no showing popup"
);
await click(target, "div.leaflet-marker-icon");
assert.strictEqual(
target.querySelector(".leaflet-popup-pane").children.length,
1,
"Should have one showing popup"
);
});
/**
* two locted records
* with routing enabled
* numbered icon
* test click on route
*/
QUnit.test("Create a view with routing", async function (assert) {
assert.expect(9);
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
const map = await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].twoRecords;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].twoRecordsAddressNoCoordinates;
}
},
});
assert.ok(map.model.metaData.numbering, "The numbering option should be enabled");
assert.ok(map.model.metaData.routing, "The routing option should be enabled");
assert.strictEqual(
map.model.data.numberOfLocatedRecords,
2,
"Should have 2 located Records"
);
assert.strictEqual(map.model.data.routes.length, 1, "Should have 1 computed route");
assert.strictEqual(
target.querySelector("div.leaflet-marker-icon .o-map-renderer--marker-badge")
.textContent,
"2",
"There should be a marker for two records"
);
assert.strictEqual(
target.querySelector("path.leaflet-interactive").getAttribute("stroke"),
"blue",
"The route should be blue if it has not been clicked"
);
assert.strictEqual(
target.querySelector("path.leaflet-interactive").getAttribute("stroke-opacity"),
"0.3",
"The opacity of the polyline should be 0.3"
);
// bypass the click helper because the element isn't visible
target
.querySelector("path.leaflet-interactive")
.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
await nextTick();
assert.strictEqual(
target.querySelector("path.leaflet-interactive").getAttribute("stroke"),
"darkblue",
"The route should be darkblue after being clicked"
);
assert.strictEqual(
target.querySelector("path.leaflet-interactive").getAttribute("stroke-opacity"),
"1",
"The opacity of the polyline should be 1"
);
});
QUnit.test("Create a view with routingError", async function (assert) {
assert.expect(1);
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
patchWithCleanup(MapModel.prototype, {
async _maxBoxAPI(metaData, data) {
data.routingError = "this is test warning";
data.routes = [];
},
});
await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].twoRecords;
case "/web/dataset/call_kw/res.partner/search_read":
return [];
}
},
});
assert.containsOnce(
target,
".o-map-renderer > .o-map-renderer--alert",
"should have alert"
);
});
/**
* routing with token and one located record
* No route
*/
QUnit.test("create a view with routing and one located record", async function (assert) {
assert.expect(2);
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
const map = await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].oneRecord;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].oneLocatedRecord;
}
},
});
assert.ok(map.model.metaData.routing, "The routing option should be enabled");
assert.strictEqual(map.model.data.routes.length, 0, "Should have no computed route");
});
/**
* no mapbox token
* assert that the view uses the right api and routes
*/
QUnit.test("CreateView with empty mapbox token setting", async function (assert) {
assert.expect(2);
const map = await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].recordWithouthPartner;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].emptyRecords;
}
},
});
assert.strictEqual(
map.model.metaData.mapBoxToken,
"",
"The token should be an empty string"
);
assert.notOk(map.model.data.useMapBoxAPI, "model should not use mapbox");
});
/**
* wrong mapbox token
* assert that the view uses the openstreetmap api
*/
QUnit.test("Create a view with wrong map box setting", async function (assert) {
assert.expect(2);
patchWithCleanup(session, { map_box_token: "vrve" });
const map = await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].twoRecords;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].twoRecordsAddressNoCoordinates;
}
},
});
assert.strictEqual(map.model.metaData.mapBoxToken, "vrve", "The token should be kept");
assert.notOk(map.model.data.useMapBoxAPI, "model should not use mapbox");
});
/**
* wrong mapbox token fails at catch at route computing
*/
QUnit.test(
"create a view with wrong map box setting and located records",
async function (assert) {
assert.expect(2);
patchWithCleanup(session, { map_box_token: "frezfre" });
const map = await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].twoRecords;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].twoRecordsAddressCoordinates;
}
},
});
assert.strictEqual(
map.model.metaData.mapBoxToken,
"frezfre",
"The token should be kept"
);
assert.notOk(map.model.data.useMapBoxAPI, "model should not use mapbox");
}
);
/**
* create view with right map box token
* assert that the view uses the map box api
*/
QUnit.test("Create a view with the right map box token", async function (assert) {
assert.expect(2);
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
const map = await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].recordWithouthPartner;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].emptyRecords;
}
},
});
assert.strictEqual(
map.model.metaData.mapBoxToken,
"token",
"The token should be the right token"
);
assert.ok(map.model.data.useMapBoxAPI, "model should use mapbox");
});
/**
* data: two located records
*/
QUnit.test(
"Click on pin shows popup, click on another shuts the first and open the other",
async function (assert) {
assert.expect(3);
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].twoRecords;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].twoRecordsAddressNoCoordinates;
}
},
});
assert.notOk(
target.querySelector(".leaflet-pane .leaflet-popup-pane").children.length,
"The popup div should be empty"
);
await click(target, "div.leaflet-marker-icon");
assert.strictEqual(
target.querySelector(".leaflet-popup-pane").children.length,
1,
"The popup div should contain one element"
);
// bypass the click helper because the element isn't visible
target
.querySelector(".leaflet-map-pane")
.dispatchEvent(new MouseEvent("click", { bubbles: true }));
await nextTick();
// wait for the popup's destruction which takes a certain time...
for (let i = 0; i < 15; i++) {
await nextTick();
}
assert.notOk(
target.querySelector(".leaflet-pane .leaflet-popup-pane").children.length,
"The popup div should be empty"
);
}
);
/**
* data: two located records
* asserts that all the records are shown on the map
*/
QUnit.test("assert that all the records are shown on the map", async function (assert) {
assert.expect(3);
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].twoRecords;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].twoRecordsAddressNoCoordinates;
}
},
});
const mapX = target.querySelector(".leaflet-map-pane")._leaflet_pos.x;
const mapY = target.querySelector(".leaflet-map-pane")._leaflet_pos.y;
assert.ok(
mapX - target.querySelector("div.leaflet-marker-icon")._leaflet_pos.x < 0,
"If the marker is currently shown on the map, the subtraction of latitude should be under 0"
);
assert.ok(mapY - target.querySelector("div.leaflet-marker-icon")._leaflet_pos.y < 0);
assert.strictEqual(
target.querySelector("div.leaflet-marker-icon .o-map-renderer--marker-badge")
.textContent,
"2",
"There should be a marker for two records"
);
});
/**
* data: two located records
* asserts that the right fields are shown in the popup
*/
QUnit.test("Content of the marker popup with one field", async function (assert) {
assert.expect(5);
serverData.views = {
"project.task,false,form": "<form/>",
};
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
const map = await makeView({
config: { views: [[false, "form"]] },
serverData,
type: "map",
resModel: "project.task",
arch: `
<map res_partner="partner_id" routing="1" hide_name="1" hide_address="1">
<field name="display_name" string="Name" />
</map>
`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].oneRecord;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].twoRecordsAddressCoordinates;
}
},
});
assert.strictEqual(map.model.metaData.fieldNamesMarkerPopup[0].fieldName, "display_name");
await click(target, "div.leaflet-marker-icon");
assert.strictEqual(
map.model.metaData.fieldNamesMarkerPopup.length,
1,
"fieldsMarkerPopup should contain one field"
);
assert.containsOnce(target, "tbody tr", "The popup should have one field");
assert.strictEqual(
target.querySelector("tbody tr").textContent,
"NameFoo",
"Field row's text should be 'Name Foo'"
);
assert.strictEqual(
target.querySelector(".o-map-renderer--popup-buttons").children.length,
3,
"The popup should contain 2 buttons and one divider"
);
});
/**
* data: two located records
* asserts that no field is shown in popup
*/
QUnit.test("Content of the marker with no field", async function (assert) {
assert.expect(2);
serverData.views = {
"project.task,false,form": "<form/>",
};
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
await makeView({
config: { views: [[false, "form"]] },
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1" hide_name="1" hide_address="1" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].twoRecords;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].twoRecordsAddressNoCoordinates;
}
},
});
await click(target, "div.leaflet-marker-icon");
assert.strictEqual(
target.querySelector("tbody").children.length,
0,
"The popup should have only the button"
);
assert.strictEqual(
target.querySelector(".o-map-renderer--popup-buttons").children.length,
3,
"The popup should contain 2 buttons and one divider"
);
});
QUnit.test("Attribute: hide_name", async function (assert) {
assert.expect(2);
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1" hide_name="1" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].twoRecords;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].twoRecordsAddressCoordinates;
}
},
});
await click(target, "div.leaflet-marker-icon");
assert.containsOnce(target, "tbody > tr", "The popup should have one field");
assert.strictEqual(
target
.querySelector("tbody tr .o-map-renderer--popup-table-content-name")
.textContent.trim(),
"Address",
"The popup should have address field"
);
});
QUnit.test("Render partner address field in popup", async function (assert) {
assert.expect(3);
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1" hide_name="1" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].oneRecord;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].oneLocatedRecord;
}
},
});
await click(target, "div.leaflet-marker-icon");
assert.containsOnce(target, "tbody tr", "The popup should have one field");
assert.strictEqual(
target
.querySelector("tbody tr .o-map-renderer--popup-table-content-name")
.textContent.trim(),
"Address",
"The popup should have address field"
);
assert.strictEqual(
target
.querySelector("tbody tr .o-map-renderer--popup-table-content-value")
.textContent.trim(),
"Chaussée de Namur 40, 1367, Ramillies",
"The popup should have correct address"
);
});
QUnit.test("Hide partner address field in popup", async function (assert) {
assert.expect(3);
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1" hide_address="1" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].oneRecord;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].oneLocatedRecord;
}
},
});
await click(target, "div.leaflet-marker-icon");
assert.containsOnce(target, "tbody tr", "The popup should have one field");
assert.strictEqual(
target
.querySelector("tbody tr .o-map-renderer--popup-table-content-name")
.textContent.trim(),
"Name",
"The popup should have name field"
);
assert.strictEqual(
target
.querySelector("tbody tr .o-map-renderer--popup-table-content-value")
.textContent.trim(),
"Foo",
"The popup should have correct address"
);
});
QUnit.test("Handle records of same co-ordinates in marker", async function (assert) {
assert.expect(4);
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].twoRecords;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].twoRecordsAddressCoordinates;
}
},
});
assert.containsOnce(target, "div.leaflet-marker-icon", "There should be a one marker");
assert.strictEqual(
target.querySelector("div.leaflet-marker-icon .o-map-renderer--marker-badge")
.textContent,
"2",
"There should be a marker for two records"
);
await click(target, "div.leaflet-marker-icon");
assert.containsOnce(target, "tbody tr", "The popup should have one field");
assert.strictEqual(
target
.querySelector("tbody tr .o-map-renderer--popup-table-content-name")
.textContent.trim(),
"Address",
"The popup should have address field"
);
});
QUnit.test("Pager", async function (assert) {
assert.expect(4);
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return {
length: 101,
records: Array.from({ length: 101 }, (_, index) => {
return {
id: index,
name: "project",
partner_id: [index],
};
}),
};
case "/web/dataset/call_kw/res.partner/search_read":
return Array.from({ length: 101 }, (_, index) => ({
id: index,
name: "Foo",
partner_latitude: 10.0,
partner_longitude: 10.5,
}));
}
},
});
assert.containsOnce(target, ".o_pager");
assert.strictEqual(
target.querySelector(`.o_pager_counter .o_pager_value`).textContent.trim(),
"1-80",
"current pager value should be 1-20"
);
assert.strictEqual(
target.querySelector(`.o_pager_counter span.o_pager_limit`).innerText.trim(),
"101",
"current pager limit should be 21"
);
await click(target.querySelector(`.o_pager button.o_pager_next`));
assert.strictEqual(
target.querySelector(`.o_pager_counter .o_pager_value`).textContent.trim(),
"81-101",
"pager value should be 21-40"
);
});
QUnit.test("New domain", async function (assert) {
assert.expect(13);
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
serverData.models["project.task"].records = [
{ id: 1, name: "FooProject", sequence: 1, partner_id: 1 },
{ id: 2, name: "BarProject", sequence: 2, partner_id: 2 },
];
const map = await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].twoRecordsAddressCoordinates;
}
},
});
assert.strictEqual(map.model.data.records.length, 2, "There should be 2 records");
assert.containsOnce(
target.querySelector(".leaflet-overlay-pane"),
"path",
"There should be one route displayed"
);
assert.strictEqual(
target.querySelector("div.leaflet-marker-icon .o-map-renderer--marker-badge")
.textContent,
"2",
"There should be a marker for two records"
);
map.env.searchModel.setDomainParts({
test: {
domain: [["name", "=", "FooProject"]],
},
});
await nextTick();
assert.strictEqual(map.model.data.records.length, 1, "There should be 1 record");
assert.containsNone(
target.querySelector(".leaflet-overlay-pane"),
"path",
"There should be no route on the map"
);
assert.containsOnce(
target,
"div.leaflet-marker-icon",
"There should be 1 marker on the map"
);
map.env.searchModel.setDomainParts({
test: {
domain: [["name", "=", "Foofezfezf"]],
},
});
await nextTick();
assert.strictEqual(map.model.data.records.length, 0, "There should be no record");
assert.containsNone(
target.querySelector(".leaflet-overlay-pane"),
"path",
"There should be no route on the map"
);
assert.containsNone(
target,
"div.leaflet-marker-icon",
"There should be 0 marker on the map"
);
map.env.searchModel.setDomainParts({
test: {
domain: [["name", "like", "Project"]],
},
});
await nextTick();
assert.strictEqual(map.model.data.records.length, 2, "There should be 2 record");
assert.containsOnce(
target.querySelector(".leaflet-overlay-pane"),
"path",
"There should be 1 route on the map"
);
assert.containsOnce(
target,
"div.leaflet-marker-icon",
"There should be 1 marker on the map"
);
assert.strictEqual(
target.querySelector("div.leaflet-marker-icon .o-map-renderer--marker-badge")
.textContent,
"2",
"There should be a marker for two records"
);
});
QUnit.test("Toggle grouped pin lists", async function (assert) {
assert.expect(13);
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
const records = serverData.models["project.task"].threeRecords;
const partners = serverData.models["res.partner"].twoRecordsAddressCoordinates;
for (const record of records.records) {
// add name on partner_id to have name_get like value
record.partner_id.push(partners.find((x) => x.id === record.partner_id[0]).name);
}
await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return records;
case "/web/dataset/call_kw/res.partner/search_read":
return partners;
}
},
groupBy: ["partner_id"],
});
assert.containsN(
target,
".o-map-renderer--pin-list-group-header",
2,
"Should have 2 groups"
);
const groupHeaders = Array.from(
target.querySelectorAll(".o-map-renderer--pin-list-group-header")
);
assert.deepEqual(
groupHeaders.map((gh) => gh.innerText),
["Bar", "Foo"]
);
assert.containsN(target, ".o-map-renderer--pin-list-details", 2);
assert.containsN(target, ".o-map-renderer--pin-list-details li", 3);
let details = Array.from(target.querySelectorAll(".o-map-renderer--pin-list-details"));
assert.deepEqual(
details.map((d) => d.innerText),
["FooProject\nFooBarProject", "BarProject"]
);
await click(target.querySelectorAll(".o-map-renderer--pin-list-group-header")[1]);
assert.containsN(
target,
".o-map-renderer--pin-list-group-header",
2,
"Should still have 2 groups"
);
assert.containsOnce(target, ".o-map-renderer--pin-list-details");
assert.containsN(target, ".o-map-renderer--pin-list-details li", 2);
details = Array.from(target.querySelectorAll(".o-map-renderer--pin-list-details"));
assert.deepEqual(
details.map((d) => d.innerText),
["FooProject\nFooBarProject"]
);
await click(target.querySelectorAll(".o-map-renderer--pin-list-group-header")[0]);
assert.containsNone(target, ".o-map-renderer--pin-list-details");
await click(target.querySelectorAll(".o-map-renderer--pin-list-group-header")[1]);
assert.containsOnce(target, ".o-map-renderer--pin-list-details");
assert.containsOnce(target, ".o-map-renderer--pin-list-details li");
details = Array.from(target.querySelectorAll(".o-map-renderer--pin-list-details"));
assert.deepEqual(
details.map((d) => d.innerText),
["BarProject"]
);
});
QUnit.test("Toggle grouped one2many pin lists", async function (assert) {
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
const records = serverData.models["project.task"].threeRecords;
const partners = serverData.models["res.partner"].records;
for (const record of records.records) {
// add name on partner_id to have name_get like value
record.partner_id.push(partners.find((x) => x.id === record.partner_id[0]).name);
}
await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id"/>`,
groupBy: ["partner_ids"],
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return records;
case "/web/dataset/call_kw/res.partner/search_read":
return partners;
}
},
});
assert.containsN(
target,
".o-map-renderer--pin-list-group-header",
3,
"Should have 3 groups"
);
const groupHeaders = Array.from(
target.querySelectorAll(".o-map-renderer--pin-list-group-header")
);
assert.deepEqual(
groupHeaders.map((gh) => gh.innerText),
["Foo", "Foo", "Bar"]
);
assert.containsN(target, ".o-map-renderer--pin-list-details", 3);
assert.containsN(target, ".o-map-renderer--pin-list-details li", 5);
const details = Array.from(target.querySelectorAll(".o-map-renderer--pin-list-details"));
assert.deepEqual(
details.map((d) => d.innerText),
["FooProject\nBarProject\nFooBarProject", "FooProject", "BarProject"]
);
assert.containsN(target, ".leaflet-marker-icon", 3);
});
QUnit.test("Check groupBy on datetime field", async function (assert) {
assert.expect(1);
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
serverData.models["project.task"].fields["scheduled_date"] = {
string: "Schedule date",
type: "datetime",
};
serverData.models["project.task"].records = [
{ id: 1, name: "FooProject", sequence: 1, partner_id: 1, scheduled_date: false },
];
await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].twoRecordsAddressCoordinates;
}
},
searchViewId: false,
searchViewArch: `
<search>
<group expand='0' string='Group By'>
<filter string="scheduled_date" name="scheduled_date" context="{'group_by': 'scheduled_date'}"/>
</group>
</search>
`,
});
assert.containsNone(
target,
".o-map-renderer--pin-list-group-header",
"Should not have any groups"
);
await toggleGroupByMenu(target);
// don't throw an error when grouping a field with a false value
await toggleMenuItem(target, "scheduled_date");
await toggleMenuItemOption(target, "scheduled_date", "year");
});
QUnit.test("Change groupBy", async function (assert) {
assert.expect(13);
patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN });
const records = serverData.models["project.task"].threeRecords;
const partners = serverData.models["res.partner"].twoRecordsAddressCoordinates;
for (const record of records.records) {
// add name on partner_id to have name_get like value
record.partner_id.push(partners.find((x) => x.id === record.partner_id[0]).name);
}
await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return records;
case "/web/dataset/call_kw/res.partner/search_read":
return partners;
}
},
searchViewId: false,
searchViewArch: `
<search>
<filter string="Partner" name="partner_id" context="{'group_by': 'partner_id'}"/>
<filter string="Name" name="display_name" context="{'group_by': 'display_name'}"/>
</search>
`,
});
assert.containsNone(
target,
".o-map-renderer--pin-list-group-header",
"Should not have any groups"
);
await toggleGroupByMenu(target);
await toggleMenuItem(target, "Partner");
assert.containsN(
target,
".o-map-renderer--pin-list-group-header",
2,
"Should have 2 groups"
);
let groupHeaders = Array.from(
target.querySelectorAll(".o-map-renderer--pin-list-group-header")
);
assert.deepEqual(
groupHeaders.map((gh) => gh.innerText),
["Bar", "Foo"]
);
// Groups should be loaded too
assert.containsN(target, ".o-map-renderer--pin-list-details li", 3);
let details = Array.from(target.querySelectorAll(".o-map-renderer--pin-list-details"));
assert.deepEqual(
details.map((d) => d.innerText),
["FooProject\nFooBarProject", "BarProject"]
);
await toggleMenuItem(target, "Name");
groupHeaders = Array.from(
target.querySelectorAll(".o-map-renderer--pin-list-group-header")
);
assert.deepEqual(
groupHeaders.map((gh) => gh.innerText),
["Bar", "Foo"],
"Should not have changed"
);
details = Array.from(target.querySelectorAll(".o-map-renderer--pin-list-details"));
assert.deepEqual(
details.map((d) => d.innerText),
["FooProject\nFooBarProject", "BarProject"]
);
await toggleMenuItem(target, "Partner");
assert.containsN(
target,
".o-map-renderer--pin-list-group-header",
3,
"Should have 3 groups"
);
groupHeaders = Array.from(
target.querySelectorAll(".o-map-renderer--pin-list-group-header")
);
assert.deepEqual(
groupHeaders.map((gh) => gh.innerText),
["FooProject", "BarProject", "FooBarProject"]
);
details = Array.from(target.querySelectorAll(".o-map-renderer--pin-list-details"));
assert.deepEqual(
details.map((d) => d.innerText),
["FooProject", "BarProject", "FooBarProject"]
);
assert.containsOnce(target.querySelectorAll(".o-map-renderer--pin-list-details")[0], "li");
assert.containsOnce(target.querySelectorAll(".o-map-renderer--pin-list-details")[1], "li");
assert.containsOnce(target.querySelectorAll(".o-map-renderer--pin-list-details")[2], "li");
});
//--------------------------------------------------------------------------
// Controller testing
//--------------------------------------------------------------------------
QUnit.test("Click on open button switches to form view", async function (assert) {
assert.expect(7);
serviceRegistry.add(
"action",
{
start() {
return {
switchView(name, info) {
assert.step("switchView");
assert.strictEqual(name, "form", "The view switched to should be form");
assert.strictEqual(info.resId, 1, "The record's id should be 1");
assert.strictEqual(
info.mode,
"readonly",
"The mode should be readonly"
);
assert.strictEqual(
info.model,
"project.task",
"The form view should be on the 'project.task' model"
);
},
};
},
},
{ force: true }
);
serverData.views = {
"project.task,false,form": "<form/>",
};
await makeView({
config: { views: [[false, "form"]] },
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1" />`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].oneRecord;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].oneLocatedRecord;
}
},
});
await click(target, "div.leaflet-marker-icon");
assert.containsOnce(
target,
"div.leaflet-popup-pane button.btn.btn-primary.o-map-renderer--popup-buttons-open",
"The button should be present in the dom"
);
await click(
target,
"div.leaflet-popup-pane button.btn.btn-primary.o-map-renderer--popup-buttons-open"
);
assert.verifySteps(["switchView"]);
});
QUnit.test("Test the lack of open button", async function (assert) {
assert.expect(1);
await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id"></map>`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].oneRecord;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].oneLocatedRecord;
}
},
});
await click(target, "div.leaflet-marker-icon");
assert.containsNone(
target,
"div.leaflet-popup-pane button.btn.btn-primary.o-map-renderer--popup-buttons-open",
"The button should not be present in the dom"
);
});
QUnit.test(
"attribute panel_title on the arch should display in the pin list",
async function (assert) {
assert.expect(1);
await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" panel_title="AAAAAAAAAAAAAAAAA"></map>`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].oneRecord;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].oneLocatedRecord;
}
},
});
assert.strictEqual(
target.querySelector(".o-map-renderer--pin-list-container .o_pin_list_header span")
.textContent,
"AAAAAAAAAAAAAAAAA"
);
}
);
QUnit.test(
"Test using a field other than partner_id for the map view",
async function (assert) {
assert.expect(1);
await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="another_partner_id"></map>`,
async mockRPC(route) {
switch (route) {
case "/web/dataset/call_kw/project.task/web_search_read":
return serverData.models["project.task"].anotherPartnerId;
case "/web/dataset/call_kw/res.partner/search_read":
return serverData.models["res.partner"].oneLocatedRecord;
}
},
});
await click(target, "div.leaflet-marker-icon");
assert.containsNone(
target,
"div.leaflet-popup-pane button.btn.btn-primary.o-map-renderer--popup-buttons-open",
"The button should not be present in the dom"
);
}
);
QUnit.test("Check Google Maps URL is updating on domain change", async function (assert) {
assert.expect(2);
serverData.models["project.task"].records = [
{ id: 1, name: "FooProject", sequence: 1, partner_id: 2 },
{ id: 2, name: "BarProject", sequence: 2, partner_id: 3 },
];
await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id"/>`,
searchViewArch: `
<search>
<filter name="some_filter" string="FooProject only" domain="[['name', '=', 'FooProject']]"/>
</search>`,
});
assert.strictEqual(
target.querySelector("a.btn.btn-primary").href,
"https://www.google.com/maps/dir/?api=1&waypoints=10,10.5|11,11.5",
"The link's URL initially should contain the coordinates for all records"
);
//apply domain and check that the Google Maps URL on the button reflects the changes
await toggleFilterMenu(target);
await toggleMenuItem(target, "FooProject only");
assert.strictEqual(
target.querySelector("a.btn.btn-primary").href,
"https://www.google.com/maps/dir/?api=1&waypoints=10,10.5",
"The link's URL after domain is applied should only contain coordinates for filtered records"
);
});
QUnit.test("Check Google Maps URL (routing and multiple records)", async function (assert) {
assert.expect(1);
serverData.models["project.task"].records = [
{ id: 1, name: "FooProject", sequence: 1, partner_id: 2 },
{ id: 2, name: "BarProject", sequence: 2, partner_id: 3 },
];
await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1"/>`,
});
assert.strictEqual(
target.querySelector("a.btn.btn-primary").href,
"https://www.google.com/maps/dir/?api=1&destination=11,11.5&waypoints=10,10.5",
"The link's URL initially should contain the coordinates for all records"
);
});
QUnit.test("Do not notify if unmounted after fetching coordinate", async function (assert) {
const def = makeDeferred();
patchWithCleanup(MapModel.prototype, {
_fetchCoordinatesFromAddressOSM() {
return def;
},
_notifyFetchedCoordinate() {
assert.step("notify");
},
});
const map = await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1"/>`,
});
destroy(map);
def.resolve();
await nextTick();
assert.verifySteps([]);
});
QUnit.test("Do not fetch if unmounted after waiting interval", async function (assert) {
const def = makeDeferred();
patchWithCleanup(browser, {
setTimeout(fn) {
def.then(fn);
},
});
patchWithCleanup(MapModel.prototype, {
async _fetchCoordinatesFromAddressOSM() {
assert.step("fetch");
return [];
},
_notifyFetchedCoordinate() {
assert.step("notify");
},
});
const map = await makeView({
serverData,
type: "map",
resModel: "project.task",
arch: `<map res_partner="partner_id" routing="1"/>`,
});
destroy(map);
def.resolve();
await nextTick();
assert.verifySteps([]);
});
});