UNPKG

@serenity-js/core

Version:

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

272 lines 10 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MetaList = exports.List = void 0; const errors_1 = require("../../errors"); const io_1 = require("../../io"); const Question_1 = require("../Question"); const Task_1 = require("../Task"); const expectations_1 = require("./expectations"); /** * Serenity/JS Screenplay Pattern-style wrapper around [`Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) * and array-like structures - see [`PageElement`](https://serenity-js.org/api/web/class/PageElements/). * * @group Questions */ class List extends Question_1.Question { collection; subject; static of(collection) { if (Question_1.Question.isAMetaQuestion(collection)) { return new MetaList(collection); } return new ArrayList(collection); } constructor(collection) { super((0, io_1.d) `${collection}`); this.collection = collection; } forEach(callback) { return new ForEachLoop(this.collection, this.toString(), callback); } async answeredBy(actor) { const collection = await actor.answer(this.collection); if (!Array.isArray(collection)) { throw new errors_1.LogicError((0, io_1.d) `A List has to wrap an Array-compatible object. ${collection} given.`); } return collection; } /** * @param {number} index */ ordinal(index) { const lastDigit = Math.abs(index) % 10, lastTwoDigits = Math.abs(index) % 100; switch (true) { case (lastDigit === 1 && lastTwoDigits !== 11): return index + 'st'; case (lastDigit === 2 && lastTwoDigits !== 12): return index + 'nd'; case (lastDigit === 3 && lastTwoDigits !== 13): return index + 'rd'; default: return index + 'th'; } } } exports.List = List; /** * @package */ class ArrayList extends List { eachMappedTo(question) { return new ArrayList(new EachMappedTo(this.collection, question, this.toString())); } where(question, expectation) { return new ArrayList(new Where(this.collection, question, expectation, this.toString())); } count() { return Question_1.Question.about(`the number of ${this.toString()}`, async (actor) => { const items = await this.answeredBy(actor); return items.length; }); } first() { return Question_1.Question.about(`the first of ${this.toString()}`, async (actor) => { const items = await this.answeredBy(actor); if (items.length === 0) { throw new errors_1.ListItemNotFoundError((0, io_1.d) `Can't retrieve the first item from a list with 0 items: ${items}`); } return items[0]; }); } last() { return Question_1.Question.about(`the last of ${this.toString()}`, async (actor) => { const items = await this.answeredBy(actor); if (items.length === 0) { throw new errors_1.ListItemNotFoundError((0, io_1.d) `Can't retrieve the last item from a list with 0 items: ${items}`); } return items.at(-1); }); } nth(index) { return Question_1.Question.about(`the ${this.ordinal(index + 1)} of ${this.toString()}`, async (actor) => { const items = await this.answeredBy(actor); if (index < 0 || items.length <= index) { throw new errors_1.ListItemNotFoundError(`Can't retrieve the ${this.ordinal(index)} item from a list with ${items.length} items: ` + (0, io_1.d) `${items}`); } return items[index]; }); } } /** * Serenity/JS Screenplay Pattern-style wrapper around * a [`ChainableMetaQuestion`](https://serenity-js.org/api/core/interface/ChainableMetaQuestion/) representing a collection * that can be resolved in `Supported_Context_Type` of another [`Question`](https://serenity-js.org/api/core/class/Question/). * * For example, [`PageElements.located`](https://serenity-js.org/api/web/class/PageElements/#located) returns `MetaList<PageElement>`, * which allows for the collection of page elements to be resolved in the context * of dynamically-provided root element. * * ```typescript * import { By, PageElements, PageElement } from '@serenity-js/web' * * const firstLabel = () => * PageElements.located(By.css('label')) * .first() * .describedAs('first label') * * const exampleForm = () => * PageElement.located(By.css('form#example1')) * .describedAs('example form') * * const anotherExampleForm = () => * PageElement.located(By.css('form#example2')) * .describedAs('another example form') * * // Next, you can compose the above questions dynamically with various "contexts": * // firstLabel().of(exampleForm()) * // firstLabel().of(anotherExampleForm()) * ``` * * @group Questions */ class MetaList extends List { collection; constructor(collection) { super(collection); this.collection = collection; } of(context) { return new MetaList(this.collection.of(context)).describedAs(this.toString() + (0, io_1.d) ` of ${context}`); } eachMappedTo(question) { return new MetaList(new MetaEachMappedTo(this.collection, question, this.toString())); } where(question, expectation) { return new MetaList(new MetaWhere(this.collection, question, expectation, this.toString())); } count() { return Question_1.Question.about(`the number of ${this.toString()}`, async (actor) => { const items = await this.answeredBy(actor); return items.length; }, (parent) => this.of(parent).count()); } first() { return Question_1.Question.about(`the first of ${this.toString()}`, async (actor) => { const items = await this.answeredBy(actor); if (items.length === 0) { throw new errors_1.ListItemNotFoundError((0, io_1.d) `Can't retrieve the first item from a list with 0 items: ${items}`); } return items[0]; }, (parent) => this.of(parent).first()); } last() { return Question_1.Question.about(`the last of ${this.toString()}`, async (actor) => { const items = await this.answeredBy(actor); if (items.length === 0) { throw new errors_1.ListItemNotFoundError((0, io_1.d) `Can't retrieve the last item from a list with 0 items: ${items}`); } return items.at(-1); }, (parent) => this.of(parent).last()); } nth(index) { return Question_1.Question.about(`the ${this.ordinal(index + 1)} of ${this.toString()}`, async (actor) => { const items = await this.answeredBy(actor); if (index < 0 || items.length <= index) { throw new errors_1.ListItemNotFoundError(`Can't retrieve the ${this.ordinal(index)} item from a list with ${items.length} items: ` + (0, io_1.d) `${items}`); } return items[index]; }, (parent) => this.of(parent).nth(index)); } } exports.MetaList = MetaList; /** * @package */ class Where extends Question_1.Question { collection; question; expectation; constructor(collection, question, expectation, originalSubject) { const prefix = collection instanceof Where ? ' and' : ' where'; super(originalSubject + prefix + (0, io_1.d) ` ${question} does ${expectation}`); this.collection = collection; this.question = question; this.expectation = expectation; } async answeredBy(actor) { try { const collection = await actor.answer(this.collection); const results = []; for (const item of collection) { const actual = this.question.of(item); const expectationOutcome = await actor.answer(this.expectation.isMetFor(actual)); if (expectationOutcome instanceof expectations_1.ExpectationMet) { results.push(item); } } return results; } catch (error) { throw new errors_1.LogicError((0, io_1.d) `Couldn't check if ${this.question} of an item of ${this.collection} does ${this.expectation}: ` + error.message, error); } } } /** * @package */ class MetaWhere extends Where { of(context) { return new MetaWhere(this.collection.of(context), this.question, this.expectation, this.toString()); } } /** * @package */ class EachMappedTo extends Question_1.Question { collection; mapping; constructor(collection, mapping, originalSubject) { super(originalSubject + (0, io_1.d) ` mapped to ${mapping}`); this.collection = collection; this.mapping = mapping; } async answeredBy(actor) { const collection = await actor.answer(this.collection); const mapped = []; for (const item of collection) { mapped.push(await actor.answer(this.mapping.of(item))); } return mapped; } } /** * @package */ class MetaEachMappedTo extends EachMappedTo { of(context) { return new MetaEachMappedTo(this.collection.of(context), this.mapping, this.toString()); } } /** * @package */ class ForEachLoop extends Task_1.Task { collection; subject; fn; constructor(collection, subject, fn) { super(`#actor iterates over ${subject}`); this.collection = collection; this.subject = subject; this.fn = fn; } async performAs(actor) { const collection = await actor.answer(this.collection); for (const [index, item] of collection.entries()) { await this.fn({ actor, item }, index, collection); } } } //# sourceMappingURL=List.js.map