Files
odoo_source/addons/bus/static/tests/bus_tests.js
T
Julien Mougenot db63ff2259 [REF] web: Owl imports adaptation
In the next version of Owl, all exported terms are directly available
from the top level `owl` object. This commit aims to adapt existing
imports to this new system. This is done by importing any Owl property
used in files at the top, right after the `import` or `require`
statements.

closes odoo/odoo#82736

Related: odoo/enterprise#23609
Signed-off-by: Géry Debongnie <ged@odoo.com>
2022-01-20 11:03:01 +00:00

437 lines
15 KiB
JavaScript

odoo.define('web.bus_tests', function (require) {
"use strict";
var BusService = require('bus.BusService');
var CrossTabBus = require('bus.CrossTab');
var AbstractStorageService = require('web.AbstractStorageService');
var RamStorage = require('web.RamStorage');
var testUtils = require('web.test_utils');
var Widget = require('web.Widget');
const LegacyRegistry = require("web.Registry");
const { ConnectionLostError } = require("@web/core/network/rpc_service");
const { patchWithCleanup, nextTick } = require("@web/../tests/helpers/utils");
const { createWebClient } = require('@web/../tests/webclient/helpers');
const { Component } = owl;
var LocalStorageServiceMock;
BusService = BusService.extend({
TAB_HEARTBEAT_PERIOD: 10,
MASTER_TAB_HEARTBEAT_PERIOD: 1,
});
QUnit.module('Bus', {
beforeEach: function () {
LocalStorageServiceMock = AbstractStorageService.extend({storage: new RamStorage()});
},
}, function () {
QUnit.test('notifications received from the longpolling channel', async function (assert) {
assert.expect(6);
var pollPromise = testUtils.makeTestPromise();
var parent = new Widget();
await testUtils.mock.addMockEnvironment(parent, {
data: {},
services: {
bus_service: BusService,
local_storage: LocalStorageServiceMock,
},
mockRPC: function (route, args) {
if (route === '/longpolling/poll') {
assert.step(route + ' - ' + args.channels.join(','));
pollPromise = testUtils.makeTestPromise();
pollPromise.abort = (function () {
this.reject({message: "XmlHttpRequestError abort"}, $.Event());
}).bind(pollPromise);
return pollPromise;
}
return this._super.apply(this, arguments);
}
});
var widget = new Widget(parent);
await widget.appendTo($('#qunit-fixture'));
widget.call('bus_service', 'onNotification', this, function (notifications) {
assert.step('notification - ' + notifications.toString());
});
widget.call('bus_service', 'addChannel', 'lambda');
pollPromise.resolve([{
message: 'beta',
}]);
await testUtils.nextTick();
pollPromise.resolve([{
message: 'epsilon',
}]);
await testUtils.nextTick();
assert.verifySteps([
'/longpolling/poll - lambda',
'notification - beta',
'/longpolling/poll - lambda',
'notification - epsilon',
'/longpolling/poll - lambda',
]);
parent.destroy();
});
QUnit.test('longpolling restarts when connection is lost', async function (assert) {
assert.expect(4);
const legacyRegistry = new LegacyRegistry();
legacyRegistry.add("bus_service", BusService);
legacyRegistry.add("local_storage", LocalStorageServiceMock);
const oldSetTimeout = window.setTimeout;
patchWithCleanup(
window,
{
setTimeout: callback => oldSetTimeout(callback, 0)
},
{ pure: true },
)
let busService;
let rpcCount = 0;
// Using createWebclient to get the compatibility layer between the old services and the new
await createWebClient({
mockRPC(route) {
if (route === '/longpolling/poll') {
rpcCount++;
assert.step(`polling ${rpcCount}`);
if (rpcCount == 1) {
return Promise.reject(new ConnectionLostError());
}
assert.equal(rpcCount, 2, "Should not be called after stopPolling");
busService.stopPolling();
return Promise.reject(new ConnectionLostError());
}
},
legacyParams: { serviceRegistry: legacyRegistry },
});
busService = Component.env.services.bus_service;
busService.startPolling();
// Give longpolling bus a tick to try to restart polling
await nextTick();
assert.verifySteps([
"polling 1",
"polling 2",
]);
});
QUnit.test('provide notification ID of 0 by default', async function (assert) {
// This test is important in order to ensure that we provide the correct
// sentinel value 0 when we are not aware of the last notification ID
// that we have received. We cannot provide an ID of -1, otherwise it
// may likely be handled incorrectly (before this test was written,
// it was providing -1 to the server, which in return sent every stored
// notifications related to this user).
assert.expect(3);
// Simulate no ID of last notification in the local storage
testUtils.mock.patch(LocalStorageServiceMock, {
getItem: function (key) {
if (key === 'last_ts') {
return 0;
}
return this._super.apply(this, arguments);
},
});
var pollPromise = testUtils.makeTestPromise();
var parent = new Widget();
await testUtils.mock.addMockEnvironment(parent, {
data: {},
services: {
bus_service: BusService,
local_storage: LocalStorageServiceMock,
},
mockRPC: function (route, args) {
if (route === '/longpolling/poll') {
assert.step(route);
assert.strictEqual(args.last, 0,
"provided last notification ID should be 0");
pollPromise = testUtils.makeTestPromise();
pollPromise.abort = (function () {
this.reject({message: "XmlHttpRequestError abort"}, $.Event());
}).bind(pollPromise);
return pollPromise;
}
return this._super.apply(this, arguments);
}
});
var widget = new Widget(parent);
await widget.appendTo($('#qunit-fixture'));
// trigger longpolling poll RPC
widget.call('bus_service', 'addChannel', 'lambda');
assert.verifySteps(['/longpolling/poll']);
testUtils.mock.unpatch(LocalStorageServiceMock);
parent.destroy();
});
QUnit.test('cross tab bus share message from a channel', async function (assert) {
assert.expect(5);
// master
var pollPromiseMaster = testUtils.makeTestPromise();
var parentMaster = new Widget();
await testUtils.mock.addMockEnvironment(parentMaster, {
data: {},
services: {
bus_service: BusService,
local_storage: LocalStorageServiceMock,
},
mockRPC: function (route, args) {
if (route === '/longpolling/poll') {
assert.step('master' + ' - ' + route + ' - ' + args.channels.join(','));
pollPromiseMaster = testUtils.makeTestPromise();
pollPromiseMaster.abort = (function () {
this.reject({message: "XmlHttpRequestError abort"}, $.Event());
}).bind(pollPromiseMaster);
return pollPromiseMaster;
}
return this._super.apply(this, arguments);
}
});
var master = new Widget(parentMaster);
await master.appendTo($('#qunit-fixture'));
master.call('bus_service', 'onNotification', master, function (notifications) {
assert.step('master - notification - ' + notifications.toString());
});
master.call('bus_service', 'addChannel', 'lambda');
// slave
await testUtils.nextTick();
var parentSlave = new Widget();
await testUtils.mock.addMockEnvironment(parentSlave, {
data: {},
services: {
bus_service: BusService,
local_storage: LocalStorageServiceMock,
},
mockRPC: function (route, args) {
if (route === '/longpolling/poll') {
throw new Error("Can not use the longpolling of the slave client");
}
return this._super.apply(this, arguments);
}
});
var slave = new Widget(parentSlave);
await slave.appendTo($('#qunit-fixture'));
slave.call('bus_service', 'onNotification', slave, function (notifications) {
assert.step('slave - notification - ' + notifications.toString());
});
slave.call('bus_service', 'addChannel', 'lambda');
pollPromiseMaster.resolve([{
id: 1,
channel: 'lambda',
message: 'beta',
}]);
await testUtils.nextTick();
assert.verifySteps([
'master - /longpolling/poll - lambda',
'master - notification - beta',
'slave - notification - beta',
'master - /longpolling/poll - lambda',
]);
parentMaster.destroy();
parentSlave.destroy();
});
QUnit.test('cross tab bus elect new master on master unload', async function (assert) {
assert.expect(8);
// master
var pollPromiseMaster = testUtils.makeTestPromise();
var parentMaster = new Widget();
await testUtils.mock.addMockEnvironment(parentMaster, {
data: {},
services: {
bus_service: BusService,
local_storage: LocalStorageServiceMock,
},
mockRPC: function (route, args) {
if (route === '/longpolling/poll') {
assert.step('master - ' + route + ' - ' + args.channels.join(','));
pollPromiseMaster = testUtils.makeTestPromise();
pollPromiseMaster.abort = (function () {
this.reject({message: "XmlHttpRequestError abort"}, $.Event());
}).bind(pollPromiseMaster);
return pollPromiseMaster;
}
return this._super.apply(this, arguments);
}
});
var master = new Widget(parentMaster);
await master.appendTo($('#qunit-fixture'));
master.call('bus_service', 'onNotification', master, function (notifications) {
assert.step('master - notification - ' + notifications.toString());
});
master.call('bus_service', 'addChannel', 'lambda');
// slave
await testUtils.nextTick();
var parentSlave = new Widget();
var pollPromiseSlave = testUtils.makeTestPromise();
await testUtils.mock.addMockEnvironment(parentSlave, {
data: {},
services: {
bus_service: BusService,
local_storage: LocalStorageServiceMock,
},
mockRPC: function (route, args) {
if (route === '/longpolling/poll') {
assert.step('slave - ' + route + ' - ' + args.channels.join(','));
pollPromiseSlave = testUtils.makeTestPromise();
pollPromiseSlave.abort = (function () {
this.reject({message: "XmlHttpRequestError abort"}, $.Event());
}).bind(pollPromiseSlave);
return pollPromiseSlave;
}
return this._super.apply(this, arguments);
}
});
var slave = new Widget(parentSlave);
await slave.appendTo($('#qunit-fixture'));
slave.call('bus_service', 'onNotification', slave, function (notifications) {
assert.step('slave - notification - ' + notifications.toString());
});
slave.call('bus_service', 'addChannel', 'lambda');
pollPromiseMaster.resolve([{
id: 1,
channel: 'lambda',
message: 'beta',
}]);
await testUtils.nextTick();
// simulate unloading master
master.call('bus_service', '_onUnload');
pollPromiseSlave.resolve([{
id: 2,
channel: 'lambda',
message: 'gamma',
}]);
await testUtils.nextTick();
assert.verifySteps([
'master - /longpolling/poll - lambda',
'master - notification - beta',
'slave - notification - beta',
'master - /longpolling/poll - lambda',
'slave - /longpolling/poll - lambda',
'slave - notification - gamma',
'slave - /longpolling/poll - lambda',
]);
parentMaster.destroy();
parentSlave.destroy();
});
QUnit.test('two tabs calling addChannel simultaneously', async function (assert) {
assert.expect(5);
let id = 1;
testUtils.mock.patch(CrossTabBus, {
init: function () {
this._super.apply(this, arguments);
this.__tabId__ = id++;
},
addChannel: function (channel) {
assert.step('Tab ' + this.__tabId__ + ': addChannel ' + channel);
this._super.apply(this, arguments);
},
deleteChannel: function (channel) {
assert.step('Tab ' + this.__tabId__ + ': deleteChannel ' + channel);
this._super.apply(this, arguments);
},
});
let pollPromise;
const parentTab1 = new Widget();
await testUtils.mock.addMockEnvironment(parentTab1, {
data: {},
services: {
local_storage: LocalStorageServiceMock,
},
mockRPC: function (route) {
if (route === '/longpolling/poll') {
pollPromise = testUtils.makeTestPromise();
pollPromise.abort = (function () {
this.reject({message: "XmlHttpRequestError abort"}, $.Event());
}).bind(pollPromise);
return pollPromise;
}
return this._super.apply(this, arguments);
}
});
const parentTab2 = new Widget();
await testUtils.mock.addMockEnvironment(parentTab2, {
data: {},
services: {
local_storage: LocalStorageServiceMock,
},
mockRPC: function (route) {
if (route === '/longpolling/poll') {
pollPromise = testUtils.makeTestPromise();
pollPromise.abort = (function () {
this.reject({message: "XmlHttpRequestError abort"}, $.Event());
}).bind(pollPromise);
return pollPromise;
}
return this._super.apply(this, arguments);
}
});
const tab1 = new CrossTabBus(parentTab1);
const tab2 = new CrossTabBus(parentTab2);
tab1.addChannel("alpha");
tab2.addChannel("alpha");
tab1.addChannel("beta");
tab2.addChannel("beta");
assert.verifySteps([
"Tab 1: addChannel alpha",
"Tab 2: addChannel alpha",
"Tab 1: addChannel beta",
"Tab 2: addChannel beta",
]);
testUtils.mock.unpatch(CrossTabBus);
parentTab1.destroy();
parentTab2.destroy();
});
});
});