1 | ;
|
2 | /**
|
3 | * @module Core
|
4 | */
|
5 | Object.defineProperty(exports, "__esModule", { value: true });
|
6 | exports.Group = void 0;
|
7 | /*
|
8 | * japa
|
9 | *
|
10 | * (c) Harminder Virk <virk@adonisjs.com>
|
11 | *
|
12 | * For the full copyright and license information, please view the LICENSE
|
13 | * file that was distributed with this source code.
|
14 | */
|
15 | const Hook_1 = require("../Hook");
|
16 | const Test_1 = require("../Test");
|
17 | const Emitter_1 = require("../Emitter");
|
18 | const Contracts_1 = require("../Contracts");
|
19 | /**
|
20 | * Group holds `n` number of tests to be executed. Groups also allows
|
21 | * defining hooks to be called before and after each test and the
|
22 | * group itself.
|
23 | */
|
24 | class Group {
|
25 | constructor(title, _resolveTestFn, _resolveHookFn, _options) {
|
26 | this.title = title;
|
27 | this._resolveTestFn = _resolveTestFn;
|
28 | this._resolveHookFn = _resolveHookFn;
|
29 | this._options = _options;
|
30 | this._hooks = {
|
31 | before: [],
|
32 | after: [],
|
33 | beforeEach: [],
|
34 | afterEach: [],
|
35 | };
|
36 | /**
|
37 | * The test error (if any)
|
38 | */
|
39 | this._error = null;
|
40 | /**
|
41 | * Has test been executed
|
42 | */
|
43 | this._completed = false;
|
44 | /**
|
45 | * An array of tests related to the group. They are mutated by the
|
46 | * run method to filter and keep only the one's that matches
|
47 | * the grep filter.
|
48 | */
|
49 | this._tests = [];
|
50 | /**
|
51 | * Storing whether the group has any failing tests or
|
52 | * not.
|
53 | */
|
54 | this._hasFailingTests = false;
|
55 | /**
|
56 | * Is there a cherry picked test using the `only` property
|
57 | * or not?
|
58 | */
|
59 | this._hasCherryPickedTest = false;
|
60 | }
|
61 | /**
|
62 | * Returns a boolean telling if group or any of the tests inside
|
63 | * the group has errors.
|
64 | */
|
65 | get hasErrors() {
|
66 | return this._hasFailingTests || !!this._error;
|
67 | }
|
68 | /**
|
69 | * Filter tests if grep value is defined
|
70 | */
|
71 | _filterTests() {
|
72 | if (!this._options.grep) {
|
73 | return;
|
74 | }
|
75 | const filteredTests = this._tests.filter((test) => this._options.grep.test(test.title));
|
76 | this._tests = filteredTests;
|
77 | }
|
78 | /**
|
79 | * Run a hook and if it raises error, then we will
|
80 | * set the completed flag to true, along with the
|
81 | * error.
|
82 | */
|
83 | async _runHook(fn) {
|
84 | try {
|
85 | await fn.run();
|
86 | }
|
87 | catch (error) {
|
88 | this._completed = true;
|
89 | this._error = error;
|
90 | }
|
91 | }
|
92 | /**
|
93 | * Runs a single test along side with it's hooks.
|
94 | */
|
95 | async _runTest(test) {
|
96 | /**
|
97 | * Run beforeEach hooks
|
98 | */
|
99 | for (let hook of this._hooks.beforeEach) {
|
100 | if (this._completed) {
|
101 | break;
|
102 | }
|
103 | await this._runHook(hook);
|
104 | }
|
105 | /**
|
106 | * Return early if completed is set to true (happens when any hook throws error)
|
107 | */
|
108 | if (this._completed) {
|
109 | return;
|
110 | }
|
111 | /**
|
112 | * Otherwise run the test
|
113 | */
|
114 | await test.run();
|
115 | /**
|
116 | * Setting flag to true when any one test has failed. This helps
|
117 | * in telling runner to exit process with the correct status.
|
118 | */
|
119 | const testFailed = test.toJSON().status === Contracts_1.ITestStatus.FAILED;
|
120 | if (!this._hasFailingTests && testFailed) {
|
121 | this._hasFailingTests = true;
|
122 | }
|
123 | /**
|
124 | * Mark group as completed when bail is set to true and
|
125 | * test has failed
|
126 | */
|
127 | if (this._options.bail && testFailed) {
|
128 | this._completed = true;
|
129 | return;
|
130 | }
|
131 | /**
|
132 | * Run all after each hooks
|
133 | */
|
134 | for (let hook of this._hooks.afterEach) {
|
135 | if (this._completed) {
|
136 | break;
|
137 | }
|
138 | await this._runHook(hook);
|
139 | }
|
140 | }
|
141 | /**
|
142 | * Runs all the tests one by one and also executes
|
143 | * the beforeEach and afterEach hooks
|
144 | */
|
145 | async _runTests() {
|
146 | /**
|
147 | * Run all the tests in sequence. If any hook beforeEach or afterEach
|
148 | * hook fails, it will set `complete = true` and then we break out
|
149 | * of the loop, since if hooks are failing, then there is no
|
150 | * point is running tests.
|
151 | */
|
152 | for (let test of this._tests) {
|
153 | if (this._completed) {
|
154 | break;
|
155 | }
|
156 | await this._runTest(test);
|
157 | }
|
158 | }
|
159 | /**
|
160 | * Returns the JSON report for the group. The output of this
|
161 | * method is emitted as an event.
|
162 | */
|
163 | toJSON() {
|
164 | let status = Contracts_1.IGroupStatus.PENDING;
|
165 | if (this._completed && this._error) {
|
166 | status = Contracts_1.IGroupStatus.FAILED;
|
167 | }
|
168 | else if (this._completed) {
|
169 | status = Contracts_1.IGroupStatus.PASSED;
|
170 | }
|
171 | return {
|
172 | title: this.title,
|
173 | status: status,
|
174 | error: this._error,
|
175 | };
|
176 | }
|
177 | /**
|
178 | * Define timeout for all the tests inside the group. Still
|
179 | * each test can override it's own timeout.
|
180 | */
|
181 | timeout(duration) {
|
182 | if (typeof (duration) !== 'number') {
|
183 | throw new Error('"group.timeout" expects a valid integer');
|
184 | }
|
185 | if (this._tests.length) {
|
186 | throw new Error('group.timeout must be called before defining the tests');
|
187 | }
|
188 | this._timeout = duration;
|
189 | return this;
|
190 | }
|
191 | /**
|
192 | * Create a new test as part of this group.
|
193 | */
|
194 | test(title, callback, testOptions) {
|
195 | if (!title.trim()) {
|
196 | throw new Error('test title cannot be empty');
|
197 | }
|
198 | testOptions = Object.assign({
|
199 | regression: false,
|
200 | skip: false,
|
201 | skipInCI: false,
|
202 | runInCI: false,
|
203 | }, testOptions);
|
204 | /**
|
205 | * Using group timeout as a priority over runner timeout
|
206 | */
|
207 | testOptions.timeout = this._timeout !== undefined ? this._timeout : this._options.timeout;
|
208 | const test = new Test_1.Test(title, this._resolveTestFn, callback, testOptions);
|
209 | /**
|
210 | * Do not track test when a test has been cherry picked earlier
|
211 | */
|
212 | if (this._hasCherryPickedTest) {
|
213 | return test;
|
214 | }
|
215 | /**
|
216 | * Remove all existing tests, when a test has a `.only` property
|
217 | * set to true.
|
218 | */
|
219 | if (testOptions.only === true) {
|
220 | this._hasCherryPickedTest = true;
|
221 | this._tests = [];
|
222 | }
|
223 | this._tests.push(test);
|
224 | return test;
|
225 | }
|
226 | /**
|
227 | * Add before hook to be executed before the group starts
|
228 | * executing tests.
|
229 | */
|
230 | before(cb) {
|
231 | if (typeof (cb) !== 'function') {
|
232 | throw new Error('"group.before" expects callback to be a valid function');
|
233 | }
|
234 | this._hooks.before.push(new Hook_1.Hook(this._resolveHookFn, cb, 'before'));
|
235 | return this;
|
236 | }
|
237 | /**
|
238 | * Add after hook to be executed after the group has executed
|
239 | * all the tests.
|
240 | */
|
241 | after(cb) {
|
242 | if (typeof (cb) !== 'function') {
|
243 | throw new Error('"group.after" expects callback to be a valid function');
|
244 | }
|
245 | this._hooks.after.push(new Hook_1.Hook(this._resolveHookFn, cb, 'after'));
|
246 | return this;
|
247 | }
|
248 | /**
|
249 | * Add before each hook to be execute before each test
|
250 | */
|
251 | beforeEach(cb) {
|
252 | if (typeof (cb) !== 'function') {
|
253 | throw new Error('"group.beforeEach" expects callback to be a valid function');
|
254 | }
|
255 | this._hooks.beforeEach.push(new Hook_1.Hook(this._resolveHookFn, cb, 'beforeEach'));
|
256 | return this;
|
257 | }
|
258 | /**
|
259 | * Add after each hook to be execute before each test
|
260 | */
|
261 | afterEach(cb) {
|
262 | if (typeof (cb) !== 'function') {
|
263 | throw new Error('"group.afterEach" expects callback to be a valid function');
|
264 | }
|
265 | this._hooks.afterEach.push(new Hook_1.Hook(this._resolveHookFn, cb, 'afterEach'));
|
266 | return this;
|
267 | }
|
268 | /**
|
269 | * Run the group with it's hooks and all tests. Shouldn't be called
|
270 | * by the end user and Japa itself will call this method
|
271 | */
|
272 | async run() {
|
273 | this._filterTests();
|
274 | /**
|
275 | * Return early when no tests are defined
|
276 | */
|
277 | if (!this._tests.length) {
|
278 | this._completed = true;
|
279 | return;
|
280 | }
|
281 | Emitter_1.emitter.emit(Contracts_1.IEvents.GROUPSTARTED, this.toJSON());
|
282 | /**
|
283 | * Run all before hooks for the group
|
284 | */
|
285 | for (let hook of this._hooks.before) {
|
286 | if (this._completed) {
|
287 | break;
|
288 | }
|
289 | await this._runHook(hook);
|
290 | }
|
291 | /**
|
292 | * Run the tests, if complete flag is not set to true. It is
|
293 | * set to true, when any before hooks fail
|
294 | */
|
295 | if (!this._completed) {
|
296 | await this._runTests();
|
297 | }
|
298 | /**
|
299 | * Run all after hooks
|
300 | */
|
301 | for (let hook of this._hooks.after) {
|
302 | if (this._completed) {
|
303 | break;
|
304 | }
|
305 | await this._runHook(hook);
|
306 | }
|
307 | this._completed = true;
|
308 | Emitter_1.emitter.emit(Contracts_1.IEvents.GROUPCOMPLETED, this.toJSON());
|
309 | }
|
310 | }
|
311 | exports.Group = Group;
|