1 | // Licensed to the Software Freedom Conservancy (SFC) under one
|
2 | // or more contributor license agreements. See the NOTICE file
|
3 | // distributed with this work for additional information
|
4 | // regarding copyright ownership. The SFC licenses this file
|
5 | // to you under the Apache License, Version 2.0 (the
|
6 | // "License"); you may not use this file except in compliance
|
7 | // with the License. You may obtain a copy of the License at
|
8 | //
|
9 | // http://www.apache.org/licenses/LICENSE-2.0
|
10 | //
|
11 | // Unless required by applicable law or agreed to in writing,
|
12 | // software distributed under the License is distributed on an
|
13 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14 | // KIND, either express or implied. See the License for the
|
15 | // specific language governing permissions and limitations
|
16 | // under the License.
|
17 |
|
18 | /**
|
19 | * @fileoverview Defines a library that simplifies writing assertions against
|
20 | * promised values.
|
21 | *
|
22 | * > <hr>
|
23 | * > __NOTE:__ This module is considered experimental and is subject to
|
24 | * > change, or removal, at any time!
|
25 | * > <hr>
|
26 | *
|
27 | * Sample usage:
|
28 | *
|
29 | * var driver = new webdriver.Builder().build();
|
30 | * driver.get('http://www.google.com');
|
31 | *
|
32 | * assert(driver.getTitle()).equalTo('Google');
|
33 | */
|
34 |
|
35 | ;
|
36 |
|
37 | const assert = require('assert');
|
38 |
|
39 | function trueType(v) {
|
40 | if (v === null) {
|
41 | return 'null';
|
42 | }
|
43 |
|
44 | let type = typeof v;
|
45 | if (type === 'object') {
|
46 | if (Array.isArray(v)) {
|
47 | type = 'array';
|
48 | }
|
49 | }
|
50 | return type;
|
51 | }
|
52 |
|
53 |
|
54 | function checkType(v, want) {
|
55 | let got = trueType(v);
|
56 | if (got !== want) {
|
57 | throw new TypeError('require ' + want + ', but got ' + got);
|
58 | }
|
59 | return v;
|
60 | }
|
61 |
|
62 | const checkNumber = v => checkType(v, 'number');
|
63 | const checkFunction = v => checkType(v, 'function');
|
64 | const checkString = v => checkType(v, 'string');
|
65 |
|
66 | const isFunction = v => trueType(v) === 'function';
|
67 | const isNumber = v => trueType(v) === 'number';
|
68 | const isObject = v => trueType(v) === 'object';
|
69 | const isString = v => trueType(v) === 'string';
|
70 |
|
71 |
|
72 | function describe(value) {
|
73 | let ret;
|
74 | try {
|
75 | ret = `<${String(value)}>`;
|
76 | } catch (e) {
|
77 | ret = `<toString failed: ${e.message}>`;
|
78 | }
|
79 |
|
80 | if (null !== value && void(0) !== value) {
|
81 | ret += ` (${trueType(value)})`;
|
82 | }
|
83 |
|
84 | return ret;
|
85 | }
|
86 |
|
87 |
|
88 | function evaluate(value, predicate) {
|
89 | if (isObject(value) && isFunction(value.then)) {
|
90 | return value.then(predicate);
|
91 | }
|
92 | predicate(value);
|
93 | }
|
94 |
|
95 |
|
96 | /**
|
97 | * @private
|
98 | */
|
99 | class Assertion {
|
100 | /**
|
101 | * @param {?} subject The subject of this assertion.
|
102 | * @param {boolean=} opt_invert Whether to invert any assertions performed by
|
103 | * this instance.
|
104 | */
|
105 | constructor(subject, opt_invert) {
|
106 | /** @private {?} */
|
107 | this.subject_ = subject;
|
108 | /** @private {boolean} */
|
109 | this.invert_ = !!opt_invert;
|
110 | }
|
111 |
|
112 | /**
|
113 | * @param {number} expected The minimum permissible value (inclusive).
|
114 | * @param {string=} opt_message An optional failure message.
|
115 | * @return {(Promise|undefined)} The result of this assertion, if the subject
|
116 | * is a promised-value. Otherwise, the assertion is performed immediately
|
117 | * and nothing is returned.
|
118 | */
|
119 | atLeast(expected, opt_message) {
|
120 | checkNumber(expected);
|
121 | return evaluate(this.subject_, function(actual) {
|
122 | if (!isNumber(actual) || actual < expected) {
|
123 | assert.fail(actual, expected, opt_message, '>=');
|
124 | }
|
125 | });
|
126 | }
|
127 |
|
128 | /**
|
129 | * @param {number} expected The maximum permissible value (inclusive).
|
130 | * @param {string=} opt_message An optional failure message.
|
131 | * @return {(Promise|undefined)} The result of this assertion, if the subject
|
132 | * is a promised-value. Otherwise, the assertion is performed immediately
|
133 | * and nothing is returned.
|
134 | */
|
135 | atMost(expected, opt_message) {
|
136 | checkNumber(expected);
|
137 | return evaluate(this.subject_, function (actual) {
|
138 | if (!isNumber(actual) || actual > expected) {
|
139 | assert.fail(actual, expected, opt_message, '<=');
|
140 | }
|
141 | });
|
142 | }
|
143 |
|
144 | /**
|
145 | * @param {number} expected The maximum permissible value (exclusive).
|
146 | * @param {string=} opt_message An optional failure message.
|
147 | * @return {(Promise|undefined)} The result of this assertion, if the subject
|
148 | * is a promised-value. Otherwise, the assertion is performed immediately
|
149 | * and nothing is returned.
|
150 | */
|
151 | greaterThan(expected, opt_message) {
|
152 | checkNumber(expected);
|
153 | return evaluate(this.subject_, function(actual) {
|
154 | if (!isNumber(actual) || actual <= expected) {
|
155 | assert.fail(actual, expected, opt_message, '>');
|
156 | }
|
157 | });
|
158 | }
|
159 |
|
160 | /**
|
161 | * @param {number} expected The minimum permissible value (exclusive).
|
162 | * @param {string=} opt_message An optional failure message.
|
163 | * @return {(Promise|undefined)} The result of this assertion, if the subject
|
164 | * is a promised-value. Otherwise, the assertion is performed immediately
|
165 | * and nothing is returned.
|
166 | */
|
167 | lessThan(expected, opt_message) {
|
168 | checkNumber(expected);
|
169 | return evaluate(this.subject_, function (actual) {
|
170 | if (!isNumber(actual) || actual >= expected) {
|
171 | assert.fail(actual, expected, opt_message, '<');
|
172 | }
|
173 | });
|
174 | }
|
175 |
|
176 | /**
|
177 | * @param {number} expected The desired value.
|
178 | * @param {number} epsilon The maximum distance from the desired value.
|
179 | * @param {string=} opt_message An optional failure message.
|
180 | * @return {(Promise|undefined)} The result of this assertion, if the subject
|
181 | * is a promised-value. Otherwise, the assertion is performed immediately
|
182 | * and nothing is returned.
|
183 | */
|
184 | closeTo(expected, epsilon, opt_message) {
|
185 | checkNumber(expected);
|
186 | checkNumber(epsilon);
|
187 | return evaluate(this.subject_, function(actual) {
|
188 | checkNumber(actual);
|
189 | if (Math.abs(expected - actual) > epsilon) {
|
190 | assert.fail(opt_message || `${actual} === ${expected} (± ${epsilon})`);
|
191 | }
|
192 | });
|
193 | }
|
194 |
|
195 | /**
|
196 | * @param {function(new: ?)} ctor The exptected type's constructor.
|
197 | * @param {string=} opt_message An optional failure message.
|
198 | * @return {(Promise|undefined)} The result of this assertion, if the subject
|
199 | * is a promised-value. Otherwise, the assertion is performed immediately
|
200 | * and nothing is returned.
|
201 | */
|
202 | instanceOf(ctor, opt_message) {
|
203 | checkFunction(ctor);
|
204 | return evaluate(this.subject_, function(actual) {
|
205 | if (!(actual instanceof ctor)) {
|
206 | assert.fail(
|
207 | opt_message
|
208 | || `${describe(actual)} instanceof ${ctor.name || ctor}`);
|
209 | }
|
210 | });
|
211 | }
|
212 |
|
213 | /**
|
214 | * @param {string=} opt_message An optional failure message.
|
215 | * @return {(Promise|undefined)} The result of this assertion, if the subject
|
216 | * is a promised-value. Otherwise, the assertion is performed immediately
|
217 | * and nothing is returned.
|
218 | */
|
219 | isNull(opt_message) {
|
220 | return this.isEqualTo(null);
|
221 | }
|
222 |
|
223 | /**
|
224 | * @param {string=} opt_message An optional failure message.
|
225 | * @return {(Promise|undefined)} The result of this assertion, if the subject
|
226 | * is a promised-value. Otherwise, the assertion is performed immediately
|
227 | * and nothing is returned.
|
228 | */
|
229 | isUndefined(opt_message) {
|
230 | return this.isEqualTo(void(0));
|
231 | }
|
232 |
|
233 | /**
|
234 | * Ensures the subject of this assertion is either a string or array
|
235 | * containing the given `value`.
|
236 | *
|
237 | * @param {?} value The value expected to be contained within the subject.
|
238 | * @param {string=} opt_message An optional failure message.
|
239 | * @return {(Promise|undefined)} The result of this assertion, if the subject
|
240 | * is a promised-value. Otherwise, the assertion is performed immediately
|
241 | * and nothing is returned.
|
242 | */
|
243 | contains(value, opt_message) {
|
244 | return evaluate(this.subject_, function(actual) {
|
245 | if (actual instanceof Map || actual instanceof Set) {
|
246 | assert.ok(actual.has(value), opt_message || `${actual}.has(${value})`);
|
247 | } else if (Array.isArray(actual) || isString(actual)) {
|
248 | assert.ok(
|
249 | actual.indexOf(value) !== -1,
|
250 | opt_message || `${actual}.indexOf(${value}) !== -1`);
|
251 | } else {
|
252 | assert.fail(
|
253 | `Expected an array, map, set, or string: got ${describe(actual)}`);
|
254 | }
|
255 | });
|
256 | }
|
257 |
|
258 | /**
|
259 | * @param {string} str The expected suffix.
|
260 | * @param {string=} opt_message An optional failure message.
|
261 | * @return {(Promise|undefined)} The result of this assertion, if the subject
|
262 | * is a promised-value. Otherwise, the assertion is performed immediately
|
263 | * and nothing is returned.
|
264 | */
|
265 | endsWith(str, opt_message) {
|
266 | checkString(str);
|
267 | return evaluate(this.subject_, function(actual) {
|
268 | if (!isString(actual) || !actual.endsWith(str)) {
|
269 | assert.fail(actual, str, 'ends with');
|
270 | }
|
271 | });
|
272 | }
|
273 |
|
274 | /**
|
275 | * @param {string} str The expected prefix.
|
276 | * @param {string=} opt_message An optional failure message.
|
277 | * @return {(Promise|undefined)} The result of this assertion, if the subject
|
278 | * is a promised-value. Otherwise, the assertion is performed immediately
|
279 | * and nothing is returned.
|
280 | */
|
281 | startsWith(str, opt_message) {
|
282 | checkString(str);
|
283 | return evaluate(this.subject_, function(actual) {
|
284 | if (!isString(actual) || !actual.startsWith(str)) {
|
285 | assert.fail(actual, str, 'starts with');
|
286 | }
|
287 | });
|
288 | }
|
289 |
|
290 | /**
|
291 | * @param {!RegExp} regex The regex the subject is expected to match.
|
292 | * @param {string=} opt_message An optional failure message.
|
293 | * @return {(Promise|undefined)} The result of this assertion, if the subject
|
294 | * is a promised-value. Otherwise, the assertion is performed immediately
|
295 | * and nothing is returned.
|
296 | */
|
297 | matches(regex, opt_message) {
|
298 | if (!(regex instanceof RegExp)) {
|
299 | throw TypeError(`Not a RegExp: ${describe(regex)}`);
|
300 | }
|
301 | return evaluate(this.subject_, function(actual) {
|
302 | if (!isString(actual) || !regex.test(actual)) {
|
303 | let message = opt_message
|
304 | || `Expected a string matching ${regex}, got ${describe(actual)}`;
|
305 | assert.fail(actual, regex, message);
|
306 | }
|
307 | });
|
308 | }
|
309 |
|
310 | /**
|
311 | * @param {?} value The unexpected value.
|
312 | * @param {string=} opt_message An optional failure message.
|
313 | * @return {(Promise|undefined)} The result of this assertion, if the subject
|
314 | * is a promised-value. Otherwise, the assertion is performed immediately
|
315 | * and nothing is returned.
|
316 | */
|
317 | notEqualTo(value, opt_message) {
|
318 | return evaluate(this.subject_, function(actual) {
|
319 | assert.notStrictEqual(actual, value, opt_message);
|
320 | });
|
321 | }
|
322 |
|
323 | /** An alias for {@link #isEqualTo}. */
|
324 | equalTo(value, opt_message) {
|
325 | return this.isEqualTo(value, opt_message);
|
326 | }
|
327 |
|
328 | /** An alias for {@link #isEqualTo}. */
|
329 | equals(value, opt_message) {
|
330 | return this.isEqualTo(value, opt_message);
|
331 | }
|
332 |
|
333 | /**
|
334 | * @param {?} value The expected value.
|
335 | * @param {string=} opt_message An optional failure message.
|
336 | * @return {(Promise|undefined)} The result of this assertion, if the subject
|
337 | * is a promised-value. Otherwise, the assertion is performed immediately
|
338 | * and nothing is returned.
|
339 | */
|
340 | isEqualTo(value, opt_message) {
|
341 | return evaluate(this.subject_, function(actual) {
|
342 | assert.strictEqual(actual, value, opt_message);
|
343 | });
|
344 | }
|
345 |
|
346 | /**
|
347 | * @param {string=} opt_message An optional failure message.
|
348 | * @return {(Promise|undefined)} The result of this assertion, if the subject
|
349 | * is a promised-value. Otherwise, the assertion is performed immediately
|
350 | * and nothing is returned.
|
351 | */
|
352 | isTrue(opt_message) {
|
353 | return this.isEqualTo(true, opt_message);
|
354 | }
|
355 |
|
356 | /**
|
357 | * @param {string=} opt_message An optional failure message.
|
358 | * @return {(Promise|undefined)} The result of this assertion, if the subject
|
359 | * is a promised-value. Otherwise, the assertion is performed immediately
|
360 | * and nothing is returned.
|
361 | */
|
362 | isFalse(opt_message) {
|
363 | return this.isEqualTo(false, opt_message);
|
364 | }
|
365 | }
|
366 |
|
367 |
|
368 | // PUBLIC API
|
369 |
|
370 |
|
371 | /**
|
372 | * Creates an assertion about the given `value`.
|
373 | * @return {!Assertion} the new assertion.
|
374 | */
|
375 | module.exports = function assertThat(value) {
|
376 | return new Assertion(value);
|
377 | };
|
378 | module.exports.Assertion = Assertion; // Exported to help generated docs
|