1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
|
odoo.define('point_of_sale.tour.utils', function (require) {
'use strict';
const config = require('web.config');
/**
* USAGE
* -----
*
* ```
* const { startSteps, getSteps, createTourMethods } = require('point_of_sale.utils');
* const { Other } = require('point_of_sale.tour.OtherMethods');
*
* // 1. Define classes Do, Check and Execute having methods that
* // each return array of tour steps.
* class Do {
* click() {
* return [{ content: 'click button', trigger: '.button' }];
* }
* }
* class Check {
* isHighligted() {
* return [{ content: 'button is highlighted', trigger: '.button.highlight', run: () => {} }];
* }
* }
* // Notice that Execute has access to methods defined in Do and Check classes
* // Also, we can compose steps from other module.
* class Execute {
* complexSteps() {
* return [...this._do.click(), ...this._check.isHighlighted(), ...Other._exec.complicatedSteps()];
* }
* }
*
* // 2. Instantiate these class definitions using `createTourMethods`.
* // The returned object gives access to the defined methods above
* // thru the do, check and exec properties.
* // - do gives access to the methods defined in Do class
* // - check gives access to the methods defined in Check class
* // - exec gives access to the methods defined in Execute class
* const Screen = createTourMethods('Screen', Do, Check, Execute);
*
* // 3. Call `startSteps` to start empty steps.
* startSteps();
*
* // 4. Call the tour methods to populate the steps created by `startSteps`.
* Screen.do.click(); // return of this method call is added to steps created by startSteps
* Screen.check.isHighlighted() // same as above
* Screen.exec.complexSteps() // same as above
*
* // 5. Call `getSteps` which returns the generated tour steps.
* const steps = getSteps();
* ```
*/
let steps = [];
function startSteps() {
// always start by waiting for loading to finish
steps = [
{
content: 'wait for loading to finish',
trigger: 'body:not(:has(.loader))',
run: function () {},
},
];
}
function getSteps() {
return steps;
}
// this is the method decorator
// when the method is called, the generated steps are added
// to steps
const methodProxyHandler = {
apply(target, thisArg, args) {
const res = target.call(thisArg, ...args);
if (config.isDebug()) {
// This step is added before the real steps.
// Very useful when debugging because we know which
// method call failed and what were the parameters.
const constructor = thisArg.constructor.name.split(' ')[1];
const methodName = target.name.split(' ')[1];
const argList = args
.map((a) => (typeof a === 'string' ? `'${a}'` : `${a}`))
.join(', ');
steps.push({
content: `DOING "${constructor}.${methodName}(${argList})"`,
trigger: '.pos',
run: () => {},
});
}
steps.push(...res);
return res;
},
};
// we proxy get of the method to decorate the method call
const proxyHandler = {
get(target, key) {
const method = target[key];
if (!method) {
throw new Error(`Tour method '${key}' is not available.`);
}
return new Proxy(method.bind(target), methodProxyHandler);
},
};
/**
* Creates an object with `do`, `check` and `exec` properties which are instances of
* the given `Do`, `Check` and `Execute` classes, respectively. Calling methods
* automatically adds the returned steps to the steps created by `startSteps`.
*
* There are however underscored version (_do, _check, _exec).
* Calling methods thru the underscored version does not automatically
* add the returned steps to the current steps array. Useful when composing
* steps from other methods.
*
* @param {String} name
* @param {Function} Do class containing methods which return array of tour steps
* @param {Function} Check similar to Do class but the steps are mainly for checking
* @param {Function} Execute class containing methods which return array of tour steps
* but has access to methods of Do and Check classes via .do and .check,
* respectively. Here, we define methods that return tour steps based
* on the combination of steps from Do and Check.
*/
function createTourMethods(name, Do, Check = class {}, Execute = class {}) {
Object.defineProperty(Do, 'name', { value: `${name}.do` });
Object.defineProperty(Check, 'name', { value: `${name}.check` });
Object.defineProperty(Execute, 'name', {
value: `${name}.exec`,
});
const methods = { do: new Do(), check: new Check(), exec: new Execute() };
// Allow Execute to have access to methods defined in Do and Check
// via do and exec, respectively.
methods.exec._do = methods.do;
methods.exec._check = methods.check;
return {
Do,
Check,
Execute,
[name]: {
do: new Proxy(methods.do, proxyHandler),
check: new Proxy(methods.check, proxyHandler),
exec: new Proxy(methods.exec, proxyHandler),
_do: methods.do,
_check: methods.check,
_exec: methods.exec,
},
};
}
return { startSteps, getSteps, createTourMethods };
});
|