[IMP] web_tour: add a sequence option to tours

Before this commit, tours were activated in a non deterministic way and there was
no way to control the order.

Now there is a `sequence` key in the options dict (i.e. the second parameters of
`tour.register`) which will determine the tour sequence order, the lowest being
first and going upward. Unsequenced tours will behave as before with no particular
order.

Task 2191192
This commit is contained in:
Julien Mougenot
2020-03-27 12:09:27 +00:00
parent f9bf408a82
commit d316c6630b
3 changed files with 94 additions and 2 deletions
+17 -2
View File
@@ -51,6 +51,9 @@ return core.Class.extend(mixins.EventDispatcherMixin, ServicesMixin, {
* the url to load when manually running the tour
* @param {boolean} [options.rainbowMan=true]
* whether or not the rainbowman must be shown at the end of the tour
* @param {boolean} [options.sequence=1000]
* priority sequence of the tour (lowest is first, tours with the same
* sequence will be executed in a non deterministic order).
* @param {Promise} [options.wait_for]
* indicates when the tour can be started
* @param {Object[]} steps - steps' descriptions, each step being an object
@@ -71,6 +74,7 @@ return core.Class.extend(mixins.EventDispatcherMixin, ServicesMixin, {
steps: steps,
url: options.url,
rainbowMan: options.rainbowMan === undefined ? true : !!options.rainbowMan,
sequence: options.sequence || 1000,
test: options.test,
wait_for: options.wait_for || Promise.resolve(),
};
@@ -113,7 +117,7 @@ return core.Class.extend(mixins.EventDispatcherMixin, ServicesMixin, {
_register: function (do_update, tour, name) {
if (tour.ready) return Promise.resolve();
var tour_is_consumed = _.contains(this.consumed_tours, name);
const tour_is_consumed = this._isTourConsumed(name);
return tour.wait_for.then((function () {
tour.current_step = parseInt(local_storage.getItem(get_step_key(name))) || 0;
@@ -198,7 +202,10 @@ return core.Class.extend(mixins.EventDispatcherMixin, ServicesMixin, {
self._check_for_tooltip(self.active_tooltips[tour_name], tour_name);
});
} else {
for (var tourName in this.active_tooltips) {
const sortedTooltips = Object.keys(this.active_tooltips).sort(
(a, b) => this.tours[a].sequence - this.tours[b].sequence
);
for (const tourName of sortedTooltips) {
var tip = this.active_tooltips[tourName];
var activated = this._check_for_tooltip(tip, tourName);
if (activated) {
@@ -342,6 +349,14 @@ return core.Class.extend(mixins.EventDispatcherMixin, ServicesMixin, {
}
this.active_tooltips[tour_name] = tour.steps[tour.current_step];
},
/**
* @private
* @param {string} tourName
* @returns {boolean}
*/
_isTourConsumed(tourName) {
return this.consumed_tours.includes(tourName);
},
_consume_tour: function (tour_name, error) {
delete this.active_tooltips[tour_name];
//display rainbow at the end of any tour
@@ -0,0 +1,71 @@
odoo.define('web_tour.tour_manager_tests', function (require) {
"use strict";
const TourManager = require('web_tour.TourManager');
const testUtils = require('web.test_utils');
/**
* Create a widget and a TourManager instance with a list of given Tour objects.
* @see TourManager.register() for more details on the Tours registry system.
* @param {Object} params
* @param {string[]} [params.consumed_tours]
* @param {boolean} [params.debug]
* @param {string} params.template inner HTML content of the widget
* @param {Object[]} params.tours { {string} name, {Object} option, {Object[]} steps }
*/
async function createTourManager({ consumed_tours, debug, template, tours }) {
const parent = testUtils.createParent({ debug });
const tourManager = new TourManager(parent, consumed_tours);
tourManager.running_step_delay = 0;
for (const { name, options, steps } of tours) {
tourManager.register(name, options, steps);
}
const _destroy = tourManager.destroy;
tourManager.destroy = function () {
tourManager.destroy = _destroy;
parent.destroy();
};
await parent.prependTo(testUtils.prepareTarget(debug));
parent.el.innerHTML = template;
testUtils.mock.patch(TourManager, {
// Since the `tour_disable.js` script automatically sets tours as consumed
// as soon as they are registered, we override the "is consumed" to
// assert that the tour is in the `consumed_tours` param key.
_isTourConsumed: name => (consumed_tours || []).includes(name),
});
await tourManager._register_all(true);
// Wait for possible tooltips to be loaded and appended.
await testUtils.nextTick();
return tourManager;
}
QUnit.module("Tours", {
afterEach() {
testUtils.mock.unpatch(TourManager);
},
}, function () {
QUnit.module("Tour manager");
QUnit.test("Tours sequence", async function (assert) {
assert.expect(2);
const tourManager = await createTourManager({
template: `
<button class="btn anchor">Anchor</button>`,
tours: [
{ name: "Tour 1", options: { sequence: 10 }, steps: [{ trigger: '.anchor' }] },
{ name: "Tour 2", options: {}, steps: [{ trigger: '.anchor' }] },
{ name: "Tour 3", options: { sequence: 5 }, steps: [{ trigger: '.anchor', content: "Oui" }] },
],
});
assert.containsOnce(document.body, '.o_tooltip');
const tooltip = document.querySelector('.o_tooltip_content');
assert.strictEqual(tooltip.innerHTML.trim(), "Oui",
"content should be that of the third tour");
tourManager.destroy();
});
});
});
+6
View File
@@ -25,5 +25,11 @@
<script type="text/javascript" src="/web_tour/static/src/js/public/tour_manager.js"/>
</xpath>
</template>
<template id="web_tour.qunit_suite" name="tour tests" inherit_id="web.qunit_suite">
<xpath expr="//script[last()]" position="after">
<script type="text/javascript" src="/web_tour/static/tests/tour_manager_tests.js"/>
</xpath>
</template>
</data>
</odoo>