@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
JavaScript
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
;