/** @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: ` `, 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: ``, 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: ``, 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: ``, 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: ``, 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: ``, 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: ``, 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: ``, 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: ``, 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: ``, 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: ``, 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: ``, 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: ``, 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: ``, 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: ``, 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: ``, }); 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: ``, 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: ``, 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: ``, 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: ``, 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: ``, 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: ``, 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: ``, 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: ``, 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: ``, 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: ``, 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: ``, 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: ``, 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: ``, 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: ``, 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: ``, 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: ``, 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": "
", }; patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN }); const map = await makeView({ config: { views: [[false, "form"]] }, serverData, type: "map", resModel: "project.task", arch: ` `, 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": "", }; patchWithCleanup(session, { map_box_token: MAP_BOX_TOKEN }); await makeView({ config: { views: [[false, "form"]] }, serverData, type: "map", resModel: "project.task", arch: ``, 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: ``, 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: ``, 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: ``, 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: ``, 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: ``, 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: ``, 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: ``, 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: ``, 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: ``, async mockRPC(route) { switch (route) { case "/web/dataset/call_kw/res.partner/search_read": return serverData.models["res.partner"].twoRecordsAddressCoordinates; } }, searchViewId: false, searchViewArch: ` `, }); 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: ``, 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: ` `, }); 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": "", }; await makeView({ config: { views: [[false, "form"]] }, serverData, type: "map", resModel: "project.task", arch: ``, 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: ``, 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: ``, 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: ``, 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: ``, searchViewArch: ` `, }); 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: ``, }); 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: ``, }); 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: ``, }); destroy(map); def.resolve(); await nextTick(); assert.verifySteps([]); }); });