@serenity-js/core 3.27.0 → 3.29.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,646 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Numeric = void 0;
4
+ const tiny_types_1 = require("tiny-types");
5
+ const Question_1 = require("../Question");
6
+ const tag_functions_1 = require("./tag-functions");
7
+ /**
8
+ * Provides methods to perform calculations on numeric values returned by other [questions](https://serenity-js.org/api/core/class/Question/).
9
+ *
10
+ * ## Example
11
+ *
12
+ * The examples in this section demonstrate interacting with an HTML widget representing a price list.
13
+ *
14
+ * ```html
15
+ * <ul id="price-list">
16
+ * <li class="item">
17
+ * <span class="name">apples</span>
18
+ * <span class="quantity">5</span>
19
+ * <span class="price">£2.25</span>
20
+ * </li>
21
+ * <li class="item">
22
+ * <span class="name">apricots</span>
23
+ * <span class="quantity">4</span>
24
+ * <span class="price">£3.70</span>
25
+ * </li>
26
+ * <li class="item">
27
+ * <span class="name">bananas</span>
28
+ * <span class="quantity">2</span>
29
+ * <span class="price">£1.50</span>
30
+ * </li>
31
+ * </ul>
32
+ * ```
33
+ *
34
+ * ### Lean Page Objects
35
+ *
36
+ * To represent our example HTML widget,
37
+ * we follow the [Lean Page Objects pattern](https://serenity-js.org/handbook/web-testing/page-objects-pattern/)
38
+ * to define the `PriceList` and `PriceListItem` classes
39
+ * and use the Serenity/JS [Page Element Query Language (PEQL)](https://serenity-js.org/handbook/web-testing/page-element-query-language/)
40
+ * to identify the elements of interest.
41
+ *
42
+ * ```ts
43
+ * // ui/price-list.ts
44
+ *
45
+ * import { PageElement, PageElements, By } from '@serenity-js/web'
46
+ *
47
+ * export class PriceList {
48
+ * static container = () =>
49
+ * PageElement.located(By.id('price-list'))
50
+ * .describedAs('price list')
51
+ *
52
+ * static items = () =>
53
+ * PageElements.located(PriceListItem.containerSelector())
54
+ * .of(this.container())
55
+ * .describedAs('items')
56
+ * }
57
+ *
58
+ * export class PriceListItem {
59
+ * static containerSelector = () =>
60
+ * By.css('.item')
61
+ *
62
+ * static container = () =>
63
+ * PageElement.located(this.containerSelector())
64
+ * .describedAs('item')
65
+ *
66
+ * static name = () =>
67
+ * PageElement.located(By.css('.name'))
68
+ * .of(this.container())
69
+ * .describedAs('name')
70
+ *
71
+ * static quantity = () =>
72
+ * PageElement.located(By.css('.quantity'))
73
+ * .of(this.container())
74
+ * .describedAs('quantity')
75
+ *
76
+ * static price = () =>
77
+ * PageElement.located(By.css('.price'))
78
+ * .of(this.container())
79
+ * .describedAs('price')
80
+ * }
81
+ * ```
82
+ *
83
+ * @group Questions
84
+ */
85
+ class Numeric {
86
+ /**
87
+ * Returns a [`Question`](https://serenity-js.org/api/core/class/Question/) that sums up the values provided
88
+ * and throws if any of the values is not a `number`.
89
+ *
90
+ * #### Adding static numbers
91
+ *
92
+ * The most basic example of using the `Numeric.sum` method is to add up a few numbers.
93
+ *
94
+ * ```ts
95
+ * import { actorCalled, Numeric } from '@serenity-js/core'
96
+ * import { Ensure, equals } from '@serenity-js/assertions'
97
+ *
98
+ * await actorCalled('Zoé').attemptsTo(
99
+ * Ensure.that(
100
+ * Numeric.sum(1, 2, 3),
101
+ * equals(6),
102
+ * )
103
+ * )
104
+ * ```
105
+ *
106
+ * The numbers can be provided individually, as an array, or as a combination of both.
107
+ *
108
+ * ```ts
109
+ * import { actorCalled, Numeric } from '@serenity-js/core'
110
+ * import { Ensure, equals } from '@serenity-js/assertions'
111
+ *
112
+ * await actorCalled('Zoé').attemptsTo(
113
+ * Ensure.that(
114
+ * Numeric.sum([ 1, 2 ], 3, [ 4, 5 ]),
115
+ * equals(15),
116
+ * )
117
+ * )
118
+ * ```
119
+ *
120
+ * #### Adding numbers returned by other questions
121
+ *
122
+ * Apart from adding static numbers, you can also add numbers returned by other questions.
123
+ * This is particularly useful when you need to calculate the sum of numbers extracted from a list of UI elements
124
+ * or from an API response.
125
+ *
126
+ * ```ts
127
+ * import { actorCalled, Numeric, Question } from '@serenity-js/core'
128
+ * import { Ensure, equals } from '@serenity-js/assertions'
129
+ *
130
+ * const myNumber = () =>
131
+ * Question.about('my number', actor => 42)
132
+ *
133
+ * const myArrayOfNumbers = () =>
134
+ * Question.about('my array of numbers', actor => [ 1, 2, 3 ])
135
+ *
136
+ * const myObjectWithNumbers = () =>
137
+ * Question.about('my object with numbers', actor => ({ a: 1, b: 2, c: 3 }))
138
+ *
139
+ * await actorCalled('Zoé').attemptsTo(
140
+ * Ensure.that(
141
+ * Numeric.sum(
142
+ * myNumber(), // a question returning a number
143
+ * myArrayOfNumbers(), // a question returning an array of numbers
144
+ * ),
145
+ * equals(48),
146
+ * ),
147
+ * Ensure.that(
148
+ * Numeric.sum(
149
+ * myObjectWithNumbers() // a question returning an object with numbers
150
+ * .as(Object.values), // from which we extract the numeric values
151
+ * ),
152
+ * equals(6),
153
+ * ),
154
+ * )
155
+ * ```
156
+ *
157
+ * Of course, you can also mix and match static numbers with numbers returned by questions.
158
+ *
159
+ * ```ts
160
+ * import { actorCalled, Numeric, Question } from '@serenity-js/core'
161
+ * import { Ensure, equals } from '@serenity-js/assertions'
162
+ *
163
+ * const myObjectWithNumbers = () =>
164
+ * Question.about('my object with numbers', actor => ({ a: 1, b: 2, c: 3 }))
165
+ *
166
+ * await actorCalled('Zoé').attemptsTo(
167
+ * Ensure.that(
168
+ * Numeric.sum(
169
+ * myObjectWithNumbers().as(Object.values),
170
+ * [ 4, 5 ],
171
+ * 6,
172
+ * ),
173
+ * equals(21),
174
+ * ),
175
+ * )
176
+ * ```
177
+ *
178
+ * #### Adding numbers extracted from a UI
179
+ *
180
+ * To add numbers extracted from a UI:
181
+ * - use the [`PageElement`](https://serenity-js.org/api/web/class/PageElement) and [`PageElements`](https://serenity-js.org/api/web/class/PageElements) classes to identify the elements of interest,
182
+ * - use the [`Text.of`](https://serenity-js.org/api/web/class/Text/) or [`Text.ofAll`](https://serenity-js.org/api/web/class/Text/) questions to extract the text of the element or elements,
183
+ * - and then interpret this text as number using either the [`.as(Number)`](https://serenity-js.org/api/core/class/Question/#as) mapping function,
184
+ * or the [`Numeric.intValue()`](https://serenity-js.org/api/core/class/Numeric/#intValue) or [`Numeric.floatValue()`](https://serenity-js.org/api/core/class/Numeric/#floatValue) meta-questions.
185
+ *
186
+ * For example, we could calculate the sum of quantities of items in our example price list by specifying each element explicitly
187
+ * and mapping its text to [`Number`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number):
188
+ *
189
+ * ```ts
190
+ * import { actorCalled, Numeric } from '@serenity-js/core'
191
+ * import { Ensure, equals } from '@serenity-js/assertions'
192
+ * import { Text } from '@serenity-js/web'
193
+ * import { PriceList, PriceListItem } from './ui/price-list'
194
+ *
195
+ * await actorCalled('Zoé').attemptsTo(
196
+ * Ensure.that(
197
+ * Numeric.sum(
198
+ * Text.of(PriceListItem.quantity().of(PriceList.items().first())).as(Number),
199
+ * Text.of(PriceListItem.quantity().of(PriceList.items().at(1))).as(Number),
200
+ * Text.of(PriceListItem.quantity().of(PriceList.items().last())).as(Number),
201
+ * ),
202
+ * equals(11),
203
+ * ),
204
+ * )
205
+ * ```
206
+ *
207
+ * A more elegant approach would be to use the Serenity/JS [Page Element Query Language (PEQL)](https://serenity-js.org/handbook/web-testing/page-element-query-language/#mapping-page-elements-in-a-collection)
208
+ * to map each item in the collection to its quantity and then calculate the sum.
209
+ *
210
+ * ```ts
211
+ * import { actorCalled, Numeric } from '@serenity-js/core'
212
+ * import { Ensure, equals } from '@serenity-js/assertions'
213
+ * import { Text } from '@serenity-js/web'
214
+ * import { PriceList, PriceListItem } from './ui/price-list'
215
+ *
216
+ * await actorCalled('Zoé').attemptsTo(
217
+ * Ensure.that(
218
+ * Numeric.sum(
219
+ * PriceList.items() // get all the li.item elements
220
+ * .eachMappedTo(
221
+ * Text.of(PriceListItem.quantity()) // extract the text of the .quantity element
222
+ * )
223
+ * .eachMappedTo( // interpret the quantity as an integer value
224
+ * Numeric.intValue(),
225
+ * ),
226
+ * ),
227
+ * equals(11), // 5 apples, 4 apricots, 2 bananas
228
+ * )
229
+ * )
230
+ * ```
231
+ *
232
+ * Using PEQL allows us to express the intent more concisely and, for example,
233
+ * introduce helper functions that limit the scope of the operation to a subset of elements.
234
+ *
235
+ * ```ts
236
+ * import { actorCalled, Expectation, Numeric, the } from '@serenity-js/core'
237
+ * import { Ensure, equals, startsWith } from '@serenity-js/assertions'
238
+ * import { PriceList, PriceListItem } from './ui/price-list'
239
+ *
240
+ * const quantitiesOfItemsWhichName = (expectation: Expectation<string>) =>
241
+ * PriceList.items() // get all the li.item elements
242
+ * .where( // leave only those which name matches the expectation
243
+ * Text.of(PriceListItem.name()),
244
+ * expectation
245
+ * )
246
+ * .eachMappedTo(
247
+ * Text.of(PriceListItem.quantity()) // extract the text of the .quantity element
248
+ * )
249
+ * .eachMappedTo( // interpret the quantity as an integer value
250
+ * Numeric.intValue(),
251
+ * )
252
+ * .describedAs(the`quantities of items which name does ${ expectation }`)
253
+ *
254
+ * await actorCalled('Zoé').attemptsTo(
255
+ * Ensure.that(
256
+ * Numeric.sum(
257
+ * quantitiesOfItemsWhichName(startsWith('ap')), // apples and apricots
258
+ * ),
259
+ * equals(9), // 5 apples, 4 apricots
260
+ * )
261
+ * )
262
+ * ```
263
+ *
264
+ * #### Learn more
265
+ *
266
+ * View the [`Numeric` API documentation](https://serenity-js.org/api/core/class/Numeric) for more details
267
+ * and examples.
268
+ *
269
+ * @param values
270
+ */
271
+ static sum(...values) {
272
+ return Question_1.Question.about((0, tag_functions_1.the) `the sum of ${values}`, async (actor) => {
273
+ const numbers = await actor.answer(this.flatten(values, (0, tiny_types_1.isNumber)()));
274
+ return numbers.sort()
275
+ .reduce((acc, current) => acc + current, 0);
276
+ });
277
+ }
278
+ /**
279
+ * Returns a [`Question`](https://serenity-js.org/api/core/class/Question/) that calculates the difference between
280
+ * two numbers and throws if any of the values is not a `number`.
281
+ *
282
+ * #### Subtracting numbers
283
+ *
284
+ * ```ts
285
+ * import { actorCalled, Numeric } from '@serenity-js/core'
286
+ * import { Ensure, equals } from '@serenity-js/assertions'
287
+ * import { Text } from '@serenity-js/web'
288
+ * import { PriceList, PriceListItem } from './ui/price-list'
289
+ *
290
+ * await actorCalled('Zoé').attemptsTo(
291
+ * Ensure.that(
292
+ * Numeric.difference(
293
+ * Text.of(PriceListItem.quantity().of(PriceList.items().first())).as(Number), // 5 (apples)
294
+ * 2, // - 2
295
+ * ),
296
+ * equals(3),
297
+ * ),
298
+ * )
299
+ * ```
300
+ *
301
+ * #### Learn more
302
+ *
303
+ * View the [`Numeric` API documentation](https://serenity-js.org/api/core/class/Numeric) for more details
304
+ * and examples.
305
+ *
306
+ * @param minuend
307
+ * @param subtrahend
308
+ */
309
+ static difference(minuend, subtrahend) {
310
+ return Question_1.Question.about((0, tag_functions_1.the) `the difference between ${minuend} and ${subtrahend}`, async (actor) => {
311
+ const minuendValue = await actor.answer(minuend);
312
+ const subtrahendValue = await actor.answer(subtrahend);
313
+ return (0, tiny_types_1.ensure)(this.descriptionOf(minuendValue), minuendValue, (0, tiny_types_1.isNumber)())
314
+ - (0, tiny_types_1.ensure)(this.descriptionOf(subtrahendValue), subtrahendValue, (0, tiny_types_1.isNumber)());
315
+ });
316
+ }
317
+ /**
318
+ * Returns a [`MetaQuestion`](https://serenity-js.org/api/core/interface/MetaQuestion/) that calculates
319
+ * the ceiling of a number and throws if the value is not a `number`.
320
+ *
321
+ * #### Calculating the ceiling of a number
322
+ *
323
+ * ```ts
324
+ * import { actorCalled, Numeric } from '@serenity-js/core'
325
+ * import { Ensure, equals } from '@serenity-js/assertions'
326
+ * import { Text } from '@serenity-js/web'
327
+ * import { PriceList, PriceListItem } from './ui/price-list'
328
+ *
329
+ * await actorCalled('Zoé').attemptsTo(
330
+ * Ensure.that(
331
+ * Numeric.ceiling().of(
332
+ * Text.of(PriceListItem.price().of(PriceList.items().first())) // '£2.25' (apples)
333
+ * .replace('£', '') // '2.25'
334
+ * .as(Number.parseFloat), // 2.25
335
+ * ),
336
+ * equals(3),
337
+ * ),
338
+ * )
339
+ * ```
340
+ *
341
+ * #### Learn more
342
+ *
343
+ * View the [`Numeric` API documentation](https://serenity-js.org/api/core/class/Numeric) for more details
344
+ * and examples.
345
+ */
346
+ static ceiling() {
347
+ return {
348
+ of: (value) => Question_1.Question.about((0, tag_functions_1.the) `the ceiling of ${value}`, async (actor) => {
349
+ const answer = await actor.answer(value);
350
+ return Math.ceil((0, tiny_types_1.ensure)(this.descriptionOf(answer), answer, (0, tiny_types_1.isNumber)()));
351
+ }),
352
+ };
353
+ }
354
+ /**
355
+ * Returns a [`MetaQuestion`](https://serenity-js.org/api/core/interface/MetaQuestion/) that calculates
356
+ * the floor of a number and throws if the value is not a `number`.
357
+ *
358
+ * #### Calculating the floor of a number
359
+ *
360
+ * ```ts
361
+ * import { actorCalled, Numeric } from '@serenity-js/core'
362
+ * import { Ensure, equals } from '@serenity-js/assertions'
363
+ * import { Text } from '@serenity-js/web'
364
+ * import { PriceList, PriceListItem } from './ui/price-list'
365
+ *
366
+ * await actorCalled('Zoé').attemptsTo(
367
+ * Ensure.that(
368
+ * Numeric.floor().of(
369
+ * Text.of(PriceListItem.price().of(PriceList.items().first())) // '£2.25' (apples)
370
+ * .replace('£', '') // '2.25'
371
+ * .as(Number.parseFloat), // 2.25
372
+ * ),
373
+ * equals(2),
374
+ * ),
375
+ * )
376
+ * ```
377
+ *
378
+ * #### Learn more
379
+ *
380
+ * View the [`Numeric` API documentation](https://serenity-js.org/api/core/class/Numeric) for more details
381
+ * and examples.
382
+ */
383
+ static floor() {
384
+ return {
385
+ of: (value) => Question_1.Question.about((0, tag_functions_1.the) `the floor of ${value}`, async (actor) => {
386
+ const answer = await actor.answer(value);
387
+ return Math.floor((0, tiny_types_1.ensure)(this.descriptionOf(answer), answer, (0, tiny_types_1.isNumber)()));
388
+ }),
389
+ };
390
+ }
391
+ /**
392
+ * Returns a [`Question`](https://serenity-js.org/api/core/class/Question/) that calculates
393
+ * the maximum value in the lists of numbers provided and throws if any of the values is not a `number`.
394
+ *
395
+ * #### Calculating the maximum value
396
+ *
397
+ * ```ts
398
+ * import { actorCalled, Numeric } from '@serenity-js/core'
399
+ * import { Ensure, equals } from '@serenity-js/assertions'
400
+ * import { Text } from '@serenity-js/web'
401
+ * import { PriceList, PriceListItem } from './ui/price-list'
402
+ *
403
+ * await actorCalled('Zoé').attemptsTo(
404
+ * Ensure.that(
405
+ * Numeric.max(
406
+ * PriceList.items() // get all the li.item elements
407
+ * .eachMappedTo(
408
+ * Text.of(PriceListItem.quantity()) // extract the text of the .quantity element
409
+ * )
410
+ * .eachMappedTo( // interpret the quantity as an integer value
411
+ * Numeric.intValue(),
412
+ * ),
413
+ * ),
414
+ * equals(5), // 5 (apples)
415
+ * )
416
+ * )
417
+ * ```
418
+ *
419
+ * #### Learn more
420
+ *
421
+ * View the [`Numeric` API documentation](https://serenity-js.org/api/core/class/Numeric) for more details
422
+ * and examples.
423
+ *
424
+ * @param values
425
+ */
426
+ static max(...values) {
427
+ return Question_1.Question.about((0, tag_functions_1.the) `the max of ${values}`, async (actor) => {
428
+ const numbers = await actor.answer(this.flatten(values, (0, tiny_types_1.isNumber)()));
429
+ return numbers.sort().pop();
430
+ });
431
+ }
432
+ /**
433
+ * Returns a [`Question`](https://serenity-js.org/api/core/class/Question/) that calculates
434
+ * the minimum value in the lists of numbers provided and throws if any of the values is not a `number`.
435
+ *
436
+ * #### Calculating the minimum value
437
+ *
438
+ * ```ts
439
+ * import { actorCalled, Numeric } from '@serenity-js/core'
440
+ * import { Ensure, equals } from '@serenity-js/assertions'
441
+ * import { Text } from '@serenity-js/web'
442
+ * import { PriceList, PriceListItem } from './ui/price-list'
443
+ *
444
+ * await actorCalled('Zoé').attemptsTo(
445
+ * Ensure.that(
446
+ * Numeric.min(
447
+ * PriceList.items() // get all the li.item elements
448
+ * .eachMappedTo(
449
+ * Text.of(PriceListItem.quantity()) // extract the text of the .quantity element
450
+ * )
451
+ * .eachMappedTo( // interpret the quantity as an integer value
452
+ * Numeric.intValue(),
453
+ * ),
454
+ * ),
455
+ * equals(2), // 2 (bananas)
456
+ * )
457
+ * )
458
+ * ```
459
+ *
460
+ * #### Learn more
461
+ *
462
+ * View the [`Numeric` API documentation](https://serenity-js.org/api/core/class/Numeric) for more details
463
+ * and examples.
464
+ *
465
+ * @param values
466
+ */
467
+ static min(...values) {
468
+ return Question_1.Question.about((0, tag_functions_1.the) `the min of ${values}`, async (actor) => {
469
+ const numbers = await actor.answer(this.flatten(values, (0, tiny_types_1.isNumber)()));
470
+ return numbers.sort().shift();
471
+ });
472
+ }
473
+ /**
474
+ * Returns a [`MetaQuestion`](https://serenity-js.org/api/core/interface/MetaQuestion/) that parses a string `value`
475
+ * and returns an integer of the specified `base`.
476
+ * Leading whitespace in the value to parse argument is ignored.
477
+ *
478
+ * #### Parsing a string as an integer
479
+ *
480
+ * ```ts
481
+ * import { actorCalled, Numeric } from '@serenity-js/core'
482
+ * import { Ensure, equals } from '@serenity-js/assertions'
483
+ * import { Text } from '@serenity-js/web'
484
+ * import { PriceList, PriceListItem } from './ui/price-list'
485
+ *
486
+ * await actorCalled('Zoé').attemptsTo(
487
+ * Ensure.that(
488
+ * Numeric.intValue().of(
489
+ * Text.of( // '5' (apples)
490
+ * PriceListItem.quantity().of(
491
+ * PriceList.items().first()
492
+ * )
493
+ * )
494
+ * ),
495
+ * equals(5),
496
+ * ),
497
+ * )
498
+ * ```
499
+ *
500
+ * #### Learn more
501
+ *
502
+ * View the [`Numeric` API documentation](https://serenity-js.org/api/core/class/Numeric) for more details
503
+ * and examples.
504
+ *
505
+ * @param base
506
+ * An integer between 2 and 36 that represents the base in mathematical numeral systems of the string.
507
+ * If base is undefined or 0, it is assumed to be 10 except when the number begins with the code unit pairs 0x or 0X, in which case a radix of 16 is assumed.
508
+ */
509
+ static intValue(base) {
510
+ return {
511
+ /**
512
+ * @param value
513
+ * The value to parse, coerced to a string. Leading whitespace in this argument is ignored.
514
+ */
515
+ of: (value) => Question_1.Question.about((0, tag_functions_1.the) `the integer value of ${value}`, async (actor) => {
516
+ const description = this.descriptionOf(value);
517
+ const stringValue = (0, tiny_types_1.ensure)(description, await actor.answer(value), (0, tiny_types_1.isString)());
518
+ const maybeBase = await actor.answer(base);
519
+ const radix = maybeBase !== undefined && maybeBase !== null
520
+ ? (0, tiny_types_1.ensure)(`base ${this.descriptionOf(base)}`, maybeBase, (0, tiny_types_1.isNumber)())
521
+ : undefined;
522
+ const parsed = Number.parseInt(stringValue, radix);
523
+ if (Number.isNaN(parsed)) {
524
+ throw new TypeError(`Parsing ${description} as an integer value returned a NaN`);
525
+ }
526
+ return parsed;
527
+ }),
528
+ };
529
+ }
530
+ /**
531
+ * Returns a [`MetaQuestion`](https://serenity-js.org/api/core/interface/MetaQuestion/) that parses a string `value`
532
+ * and returns a [`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt).
533
+ * Leading whitespace in the value to parse argument is ignored.
534
+ *
535
+ * #### Parsing a string as a bigint
536
+ *
537
+ * ```ts
538
+ * import { actorCalled, Numeric } from '@serenity-js/core'
539
+ * import { Ensure, equals } from '@serenity-js/assertions'
540
+ * import { Text } from '@serenity-js/web'
541
+ * import { PriceList, PriceListItem } from './ui/price-list'
542
+ *
543
+ * await actorCalled('Zoé').attemptsTo(
544
+ * Ensure.that(
545
+ * Numeric.bigIntValue().of(
546
+ * Text.of( // '5' (apples)
547
+ * PriceListItem.quantity().of(PriceList.items().first())
548
+ * )
549
+ * ),
550
+ * equals(BigInt('5')),
551
+ * ),
552
+ * )
553
+ * ```
554
+ *
555
+ * #### Learn more
556
+ *
557
+ * View the [`Numeric` API documentation](https://serenity-js.org/api/core/class/Numeric) for more details
558
+ * and examples.
559
+ */
560
+ static bigIntValue() {
561
+ return {
562
+ /**
563
+ * @param value
564
+ * The value to parse, coerced to a string. Leading whitespace in this argument is ignored.
565
+ */
566
+ of: (value) => Question_1.Question.about((0, tag_functions_1.the) `the bigint value of ${value}`, async (actor) => {
567
+ const description = this.descriptionOf(value);
568
+ const stringValue = (0, tiny_types_1.ensure)(description, await actor.answer(value), (0, tiny_types_1.isString)());
569
+ try {
570
+ return BigInt(stringValue);
571
+ }
572
+ catch (error) {
573
+ throw new TypeError(`Parsing ${description} as a bigint value returned an error: ${error.message || error}`);
574
+ }
575
+ }),
576
+ };
577
+ }
578
+ /**
579
+ * Returns a [`MetaQuestion`](https://serenity-js.org/api/core/interface/MetaQuestion/) that parses a string `value`
580
+ * and returns a floating-point number.
581
+ *
582
+ * #### Parsing a string as a floating point number
583
+ *
584
+ * ```ts
585
+ * import { actorCalled, Numeric } from '@serenity-js/core'
586
+ * import { Ensure, equals } from '@serenity-js/assertions'
587
+ * import { Text } from '@serenity-js/web'
588
+ * import { PriceList, PriceListItem } from './ui/price-list'
589
+ *
590
+ * await actorCalled('Zoé').attemptsTo(
591
+ * Ensure.that(
592
+ * Numeric.floatValue().of(
593
+ * Text.of( // '£2.25'
594
+ * PriceListItem.price().of(PriceList.items().first())
595
+ * ).replace('£', '') // '2.25'
596
+ * ),
597
+ * equals(2.25),
598
+ * ),
599
+ * )
600
+ * ```
601
+ *
602
+ * #### Learn more
603
+ *
604
+ * View the [`Numeric` API documentation](https://serenity-js.org/api/core/class/Numeric) for more details
605
+ * and examples.
606
+ */
607
+ static floatValue() {
608
+ return {
609
+ /**
610
+ * @param value
611
+ * The value to parse, coerced to a string. Leading whitespace in this argument is ignored.
612
+ */
613
+ of: (value) => Question_1.Question.about((0, tag_functions_1.the) `the float value of ${value}`, async (actor) => {
614
+ const description = this.descriptionOf(value);
615
+ const maybeNumber = (0, tiny_types_1.ensure)(description, await actor.answer(value), (0, tiny_types_1.isString)());
616
+ const parsed = Number.parseFloat(maybeNumber);
617
+ if (Number.isNaN(parsed)) {
618
+ throw new TypeError(`Parsing ${description} as a float value returned a NaN`);
619
+ }
620
+ return parsed;
621
+ }),
622
+ };
623
+ }
624
+ static flatten(items, ...predicates) {
625
+ return Question_1.Question.about('flatten', async (actor) => {
626
+ const result = [];
627
+ for (const item of items) {
628
+ const valueOrValues = await actor.answer(item);
629
+ const values = Array.isArray(valueOrValues)
630
+ ? valueOrValues
631
+ : [valueOrValues];
632
+ const valuesOfCorrectType = values.map(value => (0, tiny_types_1.ensure)(this.descriptionOf(value), value, ...predicates));
633
+ result.push(...valuesOfCorrectType);
634
+ }
635
+ return result;
636
+ });
637
+ }
638
+ static descriptionOf(value) {
639
+ if (value === undefined) {
640
+ return 'undefined';
641
+ }
642
+ return Question_1.Question.formattedValue().of(value).toString();
643
+ }
644
+ }
645
+ exports.Numeric = Numeric;
646
+ //# sourceMappingURL=Numeric.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Numeric.js","sourceRoot":"","sources":["../../../src/screenplay/questions/Numeric.ts"],"names":[],"mappings":"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"}
@@ -8,6 +8,7 @@ export * from './expectations';
8
8
  export * from './List';
9
9
  export * from './Masked';
10
10
  export * from './MetaQuestion';
11
+ export * from './Numeric';
11
12
  export * from './tag-functions';
12
13
  export * from './Unanswered';
13
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/screenplay/questions/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC;AAC9B,cAAc,gCAAgC,CAAC;AAC/C,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/screenplay/questions/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC;AAC9B,cAAc,gCAAgC,CAAC;AAC/C,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC"}
@@ -24,6 +24,7 @@ __exportStar(require("./expectations"), exports);
24
24
  __exportStar(require("./List"), exports);
25
25
  __exportStar(require("./Masked"), exports);
26
26
  __exportStar(require("./MetaQuestion"), exports);
27
+ __exportStar(require("./Numeric"), exports);
27
28
  __exportStar(require("./tag-functions"), exports);
28
29
  __exportStar(require("./Unanswered"), exports);
29
30
  //# sourceMappingURL=index.js.map