UNPKG

@serenity-js/core

Version:

The core Serenity/JS framework, providing the Screenplay Pattern interfaces, as well as the test reporting and integration infrastructure

534 lines 20.8 kB
import type { Answerable } from '../Answerable'; import type { QuestionAdapter } from '../Question'; import type { MetaQuestion } from './MetaQuestion'; /** * Provides methods to perform calculations on numeric values returned by other [questions](https://serenity-js.org/api/core/class/Question/). * * ## Example * * The examples in this section demonstrate interacting with an HTML widget representing a price list. * * ```html * <ul id="price-list"> * <li class="item"> * <span class="name">apples</span> * <span class="quantity">5</span> * <span class="price">£2.25</span> * </li> * <li class="item"> * <span class="name">apricots</span> * <span class="quantity">4</span> * <span class="price">£3.70</span> * </li> * <li class="item"> * <span class="name">bananas</span> * <span class="quantity">2</span> * <span class="price">£1.50</span> * </li> * </ul> * ``` * * ### Lean Page Objects * * To represent our example HTML widget, * we follow the [Lean Page Objects pattern](https://serenity-js.org/handbook/web-testing/page-objects-pattern/) * to define the `PriceList` and `PriceListItem` classes * and use the Serenity/JS [Page Element Query Language (PEQL)](https://serenity-js.org/handbook/web-testing/page-element-query-language/) * to identify the elements of interest. * * ```ts * // ui/price-list.ts * * import { PageElement, PageElements, By } from '@serenity-js/web' * * export class PriceList { * static container = () => * PageElement.located(By.id('price-list')) * .describedAs('price list') * * static items = () => * PageElements.located(PriceListItem.containerSelector()) * .of(this.container()) * .describedAs('items') * } * * export class PriceListItem { * static containerSelector = () => * By.css('.item') * * static container = () => * PageElement.located(this.containerSelector()) * .describedAs('item') * * static name = () => * PageElement.located(By.css('.name')) * .of(this.container()) * .describedAs('name') * * static quantity = () => * PageElement.located(By.css('.quantity')) * .of(this.container()) * .describedAs('quantity') * * static price = () => * PageElement.located(By.css('.price')) * .of(this.container()) * .describedAs('price') * } * ``` * * @group Questions */ export declare class Numeric { /** * Returns a [`Question`](https://serenity-js.org/api/core/class/Question/) that sums up the values provided * and throws if any of the values is not a `number`. * * #### Adding static numbers * * The most basic example of using the `Numeric.sum` method is to add up a few numbers. * * ```ts * import { actorCalled, Numeric } from '@serenity-js/core' * import { Ensure, equals } from '@serenity-js/assertions' * * await actorCalled('Zoé').attemptsTo( * Ensure.that( * Numeric.sum(1, 2, 3), * equals(6), * ) * ) * ``` * * The numbers can be provided individually, as an array, or as a combination of both. * * ```ts * import { actorCalled, Numeric } from '@serenity-js/core' * import { Ensure, equals } from '@serenity-js/assertions' * * await actorCalled('Zoé').attemptsTo( * Ensure.that( * Numeric.sum([ 1, 2 ], 3, [ 4, 5 ]), * equals(15), * ) * ) * ``` * * #### Adding numbers returned by other questions * * Apart from adding static numbers, you can also add numbers returned by other questions. * This is particularly useful when you need to calculate the sum of numbers extracted from a list of UI elements * or from an API response. * * ```ts * import { actorCalled, Numeric, Question } from '@serenity-js/core' * import { Ensure, equals } from '@serenity-js/assertions' * * const myNumber = () => * Question.about('my number', actor => 42) * * const myArrayOfNumbers = () => * Question.about('my array of numbers', actor => [ 1, 2, 3 ]) * * const myObjectWithNumbers = () => * Question.about('my object with numbers', actor => ({ a: 1, b: 2, c: 3 })) * * await actorCalled('Zoé').attemptsTo( * Ensure.that( * Numeric.sum( * myNumber(), // a question returning a number * myArrayOfNumbers(), // a question returning an array of numbers * ), * equals(48), * ), * Ensure.that( * Numeric.sum( * myObjectWithNumbers() // a question returning an object with numbers * .as(Object.values), // from which we extract the numeric values * ), * equals(6), * ), * ) * ``` * * Of course, you can also mix and match static numbers with numbers returned by questions. * * ```ts * import { actorCalled, Numeric, Question } from '@serenity-js/core' * import { Ensure, equals } from '@serenity-js/assertions' * * const myObjectWithNumbers = () => * Question.about('my object with numbers', actor => ({ a: 1, b: 2, c: 3 })) * * await actorCalled('Zoé').attemptsTo( * Ensure.that( * Numeric.sum( * myObjectWithNumbers().as(Object.values), * [ 4, 5 ], * 6, * ), * equals(21), * ), * ) * ``` * * #### Adding numbers extracted from a UI * * To add numbers extracted from a UI: * - 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, * - 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, * - and then interpret this text as number using either the [`.as(Number)`](https://serenity-js.org/api/core/class/Question/#as) mapping function, * 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. * * For example, we could calculate the sum of quantities of items in our example price list by specifying each element explicitly * and mapping its text to [`Number`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number): * * ```ts * import { actorCalled, Numeric } from '@serenity-js/core' * import { Ensure, equals } from '@serenity-js/assertions' * import { Text } from '@serenity-js/web' * import { PriceList, PriceListItem } from './ui/price-list' * * await actorCalled('Zoé').attemptsTo( * Ensure.that( * Numeric.sum( * Text.of(PriceListItem.quantity().of(PriceList.items().first())).as(Number), * Text.of(PriceListItem.quantity().of(PriceList.items().at(1))).as(Number), * Text.of(PriceListItem.quantity().of(PriceList.items().last())).as(Number), * ), * equals(11), * ), * ) * ``` * * 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) * to map each item in the collection to its quantity and then calculate the sum. * * ```ts * import { actorCalled, Numeric } from '@serenity-js/core' * import { Ensure, equals } from '@serenity-js/assertions' * import { Text } from '@serenity-js/web' * import { PriceList, PriceListItem } from './ui/price-list' * * await actorCalled('Zoé').attemptsTo( * Ensure.that( * Numeric.sum( * PriceList.items() // get all the li.item elements * .eachMappedTo( * Text.of(PriceListItem.quantity()) // extract the text of the .quantity element * ) * .eachMappedTo( // interpret the quantity as an integer value * Numeric.intValue(), * ), * ), * equals(11), // 5 apples, 4 apricots, 2 bananas * ) * ) * ``` * * Using PEQL allows us to express the intent more concisely and, for example, * introduce helper functions that limit the scope of the operation to a subset of elements. * * ```ts * import { actorCalled, Expectation, Numeric, the } from '@serenity-js/core' * import { Ensure, equals, startsWith } from '@serenity-js/assertions' * import { PriceList, PriceListItem } from './ui/price-list' * * const quantitiesOfItemsWhichName = (expectation: Expectation<string>) => * PriceList.items() // get all the li.item elements * .where( // leave only those which name matches the expectation * Text.of(PriceListItem.name()), * expectation * ) * .eachMappedTo( * Text.of(PriceListItem.quantity()) // extract the text of the .quantity element * ) * .eachMappedTo( // interpret the quantity as an integer value * Numeric.intValue(), * ) * .describedAs(the`quantities of items which name does ${ expectation }`) * * await actorCalled('Zoé').attemptsTo( * Ensure.that( * Numeric.sum( * quantitiesOfItemsWhichName(startsWith('ap')), // apples and apricots * ), * equals(9), // 5 apples, 4 apricots * ) * ) * ``` * * #### Learn more * * View the [`Numeric` API documentation](https://serenity-js.org/api/core/class/Numeric) for more details * and examples. * * @param values */ static sum(...values: Array<Answerable<number | number[]>>): QuestionAdapter<number>; /** * Returns a [`Question`](https://serenity-js.org/api/core/class/Question/) that calculates the difference between * two numbers and throws if any of the values is not a `number`. * * #### Subtracting numbers * * ```ts * import { actorCalled, Numeric } from '@serenity-js/core' * import { Ensure, equals } from '@serenity-js/assertions' * import { Text } from '@serenity-js/web' * import { PriceList, PriceListItem } from './ui/price-list' * * await actorCalled('Zoé').attemptsTo( * Ensure.that( * Numeric.difference( * Text.of(PriceListItem.quantity().of(PriceList.items().first())).as(Number), // 5 (apples) * 2, // - 2 * ), * equals(3), * ), * ) * ``` * * #### Learn more * * View the [`Numeric` API documentation](https://serenity-js.org/api/core/class/Numeric) for more details * and examples. * * @param minuend * @param subtrahend */ static difference(minuend: Answerable<number>, subtrahend: Answerable<number>): QuestionAdapter<number>; /** * Returns a [`MetaQuestion`](https://serenity-js.org/api/core/interface/MetaQuestion/) that calculates * the ceiling of a number and throws if the value is not a `number`. * * #### Calculating the ceiling of a number * * ```ts * import { actorCalled, Numeric } from '@serenity-js/core' * import { Ensure, equals } from '@serenity-js/assertions' * import { Text } from '@serenity-js/web' * import { PriceList, PriceListItem } from './ui/price-list' * * await actorCalled('Zoé').attemptsTo( * Ensure.that( * Numeric.ceiling().of( * Text.of(PriceListItem.price().of(PriceList.items().first())) // '£2.25' (apples) * .replace('£', '') // '2.25' * .as(Number.parseFloat), // 2.25 * ), * equals(3), * ), * ) * ``` * * #### Learn more * * View the [`Numeric` API documentation](https://serenity-js.org/api/core/class/Numeric) for more details * and examples. */ static ceiling(): MetaQuestion<number, QuestionAdapter<number>>; /** * Returns a [`MetaQuestion`](https://serenity-js.org/api/core/interface/MetaQuestion/) that calculates * the floor of a number and throws if the value is not a `number`. * * #### Calculating the floor of a number * * ```ts * import { actorCalled, Numeric } from '@serenity-js/core' * import { Ensure, equals } from '@serenity-js/assertions' * import { Text } from '@serenity-js/web' * import { PriceList, PriceListItem } from './ui/price-list' * * await actorCalled('Zoé').attemptsTo( * Ensure.that( * Numeric.floor().of( * Text.of(PriceListItem.price().of(PriceList.items().first())) // '£2.25' (apples) * .replace('£', '') // '2.25' * .as(Number.parseFloat), // 2.25 * ), * equals(2), * ), * ) * ``` * * #### Learn more * * View the [`Numeric` API documentation](https://serenity-js.org/api/core/class/Numeric) for more details * and examples. */ static floor(): MetaQuestion<number, QuestionAdapter<number>>; /** * Returns a [`Question`](https://serenity-js.org/api/core/class/Question/) that calculates * the maximum value in the lists of numbers provided and throws if any of the values is not a `number`. * * #### Calculating the maximum value * * ```ts * import { actorCalled, Numeric } from '@serenity-js/core' * import { Ensure, equals } from '@serenity-js/assertions' * import { Text } from '@serenity-js/web' * import { PriceList, PriceListItem } from './ui/price-list' * * await actorCalled('Zoé').attemptsTo( * Ensure.that( * Numeric.max( * PriceList.items() // get all the li.item elements * .eachMappedTo( * Text.of(PriceListItem.quantity()) // extract the text of the .quantity element * ) * .eachMappedTo( // interpret the quantity as an integer value * Numeric.intValue(), * ), * ), * equals(5), // 5 (apples) * ) * ) * ``` * * #### Learn more * * View the [`Numeric` API documentation](https://serenity-js.org/api/core/class/Numeric) for more details * and examples. * * @param values */ static max(...values: Array<Answerable<number | number[]>>): QuestionAdapter<number>; /** * Returns a [`Question`](https://serenity-js.org/api/core/class/Question/) that calculates * the minimum value in the lists of numbers provided and throws if any of the values is not a `number`. * * #### Calculating the minimum value * * ```ts * import { actorCalled, Numeric } from '@serenity-js/core' * import { Ensure, equals } from '@serenity-js/assertions' * import { Text } from '@serenity-js/web' * import { PriceList, PriceListItem } from './ui/price-list' * * await actorCalled('Zoé').attemptsTo( * Ensure.that( * Numeric.min( * PriceList.items() // get all the li.item elements * .eachMappedTo( * Text.of(PriceListItem.quantity()) // extract the text of the .quantity element * ) * .eachMappedTo( // interpret the quantity as an integer value * Numeric.intValue(), * ), * ), * equals(2), // 2 (bananas) * ) * ) * ``` * * #### Learn more * * View the [`Numeric` API documentation](https://serenity-js.org/api/core/class/Numeric) for more details * and examples. * * @param values */ static min(...values: Array<Answerable<number | number[]>>): QuestionAdapter<number>; /** * Returns a [`MetaQuestion`](https://serenity-js.org/api/core/interface/MetaQuestion/) that parses a string `value` * and returns an integer of the specified `base`. * Leading whitespace in the value to parse argument is ignored. * * #### Parsing a string as an integer * * ```ts * import { actorCalled, Numeric } from '@serenity-js/core' * import { Ensure, equals } from '@serenity-js/assertions' * import { Text } from '@serenity-js/web' * import { PriceList, PriceListItem } from './ui/price-list' * * await actorCalled('Zoé').attemptsTo( * Ensure.that( * Numeric.intValue().of( * Text.of( // '5' (apples) * PriceListItem.quantity().of( * PriceList.items().first() * ) * ) * ), * equals(5), * ), * ) * ``` * * #### Learn more * * View the [`Numeric` API documentation](https://serenity-js.org/api/core/class/Numeric) for more details * and examples. * * @param base * An integer between 2 and 36 that represents the base in mathematical numeral systems of the string. * 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. */ static intValue(base?: Answerable<number>): MetaQuestion<string, QuestionAdapter<number>>; /** * Returns a [`MetaQuestion`](https://serenity-js.org/api/core/interface/MetaQuestion/) that parses a string `value` * and returns a [`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt). * Leading whitespace in the value to parse argument is ignored. * * #### Parsing a string as a bigint * * ```ts * import { actorCalled, Numeric } from '@serenity-js/core' * import { Ensure, equals } from '@serenity-js/assertions' * import { Text } from '@serenity-js/web' * import { PriceList, PriceListItem } from './ui/price-list' * * await actorCalled('Zoé').attemptsTo( * Ensure.that( * Numeric.bigIntValue().of( * Text.of( // '5' (apples) * PriceListItem.quantity().of(PriceList.items().first()) * ) * ), * equals(BigInt('5')), * ), * ) * ``` * * #### Learn more * * View the [`Numeric` API documentation](https://serenity-js.org/api/core/class/Numeric) for more details * and examples. */ static bigIntValue(): MetaQuestion<string, QuestionAdapter<bigint>>; /** * Returns a [`MetaQuestion`](https://serenity-js.org/api/core/interface/MetaQuestion/) that parses a string `value` * and returns a floating-point number. * * #### Parsing a string as a floating point number * * ```ts * import { actorCalled, Numeric } from '@serenity-js/core' * import { Ensure, equals } from '@serenity-js/assertions' * import { Text } from '@serenity-js/web' * import { PriceList, PriceListItem } from './ui/price-list' * * await actorCalled('Zoé').attemptsTo( * Ensure.that( * Numeric.floatValue().of( * Text.of( // '£2.25' * PriceListItem.price().of(PriceList.items().first()) * ).replace('£', '') // '2.25' * ), * equals(2.25), * ), * ) * ``` * * #### Learn more * * View the [`Numeric` API documentation](https://serenity-js.org/api/core/class/Numeric) for more details * and examples. */ static floatValue(): MetaQuestion<string, QuestionAdapter<number>>; private static flatten; private static descriptionOf; } //# sourceMappingURL=Numeric.d.ts.map