UNPKG

@serenity-js/core

Version:

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

339 lines 11.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.NotepadAdapter = void 0; const io_1 = require("../../io"); const Interaction_1 = require("../Interaction"); const Question_1 = require("../Question"); const questions_1 = require("../questions"); const TakeNotes_1 = require("./TakeNotes"); /** * Serenity/JS Screenplay Pattern-style adapter for the [`Notepad`](https://serenity-js.org/api/core/class/Notepad/), * that makes it easier for the [actors](https://serenity-js.org/api/core/class/Actor/) to access its APIs. * * See [`TakeNotes`](https://serenity-js.org/api/core/class/TakeNotes/), [`Notepad`](https://serenity-js.org/api/core/class/Notepad/) and [`notes`](https://serenity-js.org/api/core/function/notes/) for more examples. * * @group Notes */ class NotepadAdapter { /** * Checks if a note identified by `subject` exists in the notepad. * * #### Learn more * - [`Notepad.has`](https://serenity-js.org/api/core/class/Notepad/#has)} * * @param subject * A subject (name) that uniquely identifies a given note * * @returns * Question that resolves to `true` if the note exists, `false` otherwise */ has(subject) { return Question_1.Question.about(`a note of ${String(subject)} exists`, actor => { return TakeNotes_1.TakeNotes.as(actor).notepad.has(subject); }); } /** * Retrieves a note, identified by `subject`, from the notepad. * * #### Learn more * - [`Notepad.get`](https://serenity-js.org/api/core/class/Notepad/#get)} * * @param subject * A subject (name) that uniquely identifies a given note * * @returns * The value of the previously recorded note. * * @throws * Throws a [`LogicError`](https://serenity-js.org/api/core/class/LogicError/) if the note with a given `subject` * has never been recorded. */ get(subject) { return Question_1.Question.about(`a note of ${String(subject)}`, actor => { return TakeNotes_1.TakeNotes.as(actor).notepad.get(subject); }).describedAs(Question_1.Question.formattedValue()); } /** * Resolves a given `Answerable<value>` and stores it in the notepad, * uniquely identified by its `subject`. * * **Pro tip:** calls to `set` can be chained and result in an accumulation * of values to be recorded in the [`Notepad`](https://serenity-js.org/api/core/class/Notepad/). * Those values are resolved and recorded when the [`Interaction`](https://serenity-js.org/api/core/class/Interaction/) * returned by this method is performed by an [`Actor`](https://serenity-js.org/api/core/class/Actor/). * * If a note identified by a given `subject` is set multiple times, * the last call wins. * * ```ts * import { actorCalled, notes, TakeNotes } from '@serenity-js/core' * import { Ensure, equals } from '@serenity-js/assertions' * * interface MyNotes { * stringNote: string; * numberNote: number; * } * * await actorCalled('Alice') * .whoCan(TakeNotes.usingAnEmptyNotepad<MyNotes>()); * .attemptsTo( * * notes<MyNotes>() * .set('stringNote', 'example') * .set('numberNote', Promise.resolve(42)) * .set('stringNote', 'another example'), * * Ensure.equal(notes().toJSON(), { * firstNote: 'another example', * secondNote: 42, * }) * ) * ``` * * #### Learn more * - [`Notepad.set`](https://serenity-js.org/api/core/class/Notepad/#set) * * @param subject * A subject (name) that uniquely identifies a given note * * @param value * The value to record */ set(subject, value) { return new ChainableNoteSetter({ [subject]: value }); } /** * Removes the note identified by `subject` from the notepad. * * #### Using as an `Interaction` * * ```ts * import { actorCalled, Check, Log, notes } from '@serenity-js/core' * import { isPresent } from '@serenity-js/assertions' * * interface MyNotes { * myNote: string; * } * * await actorCalled('Alice') * .whoCan(TakeNotes.using(Notepad.empty<MyNotes>())) * .attemptsTo( * notes<MyNotes>().set('myNote', 'example value'), * * notes<MyNotes>().delete('myNote'), * * Check.whether(notes<MyNotes>().get('myNote'), isPresent()) * .andIfSo( * Log.the('myNote is present'), * ) * .otherwise( * Log.the('myNote was deleted'), * ) * ) * // logs: myNote was deleted * ``` * * #### Using as a `Question` * * ```ts * import { actorCalled, Check, Log, notes } from '@serenity-js/core' * import { isTrue } from '@serenity-js/assertions' * * interface MyNotes { * myNote: string; * } * * await actorCalled('Alice') * .whoCan(TakeNotes.using(Notepad.empty<MyNotes>())) * .attemptsTo( * notes<MyNotes>().set('myNote', 'example value'), * * Check.whether(notes<MyNotes>().delete('myNote'), isTrue()) * .andIfSo( * Log.the('myNote was deleted'), * ) * .otherwise( * Log.the('myNote could not be deleted because it was not set'), * ) * ) * // logs: myNote was deleted * ``` * * #### Learn more * - [`Notepad.delete`](https://serenity-js.org/api/core/class/Notepad/#delete) * * @param subject * * @returns * When used as a `Question`, resolves to `true` if the item in the Notepad object existed and has been removed, * `false` otherwise. */ delete(subject) { return Question_1.Question.about(`#actor deletes a note of ${String(subject)}`, actor => { return TakeNotes_1.TakeNotes.as(actor).notepad.delete(subject); }); } /** * Deletes all the notes stored in this notepad. * * ```ts * import { actorCalled, notes } from '@serenity-js/core' * import { isTrue } from '@serenity-js/assertions' * * interface MyNotes { * myNote: string; * } * * await actorCalled('Alice') * .whoCan(TakeNotes.using(Notepad.empty<MyNotes>())) * .attemptsTo( * notes<MyNotes>().set('myNote', 'example value'), * Log.the(notes<MyNotes>().size()), // emits 1 * notes<MyNotes>().clear(), * Log.the(notes<MyNotes>().size()), // emits 0 * ) * ``` * * #### Learn more * - [`Notepad.clear`](https://serenity-js.org/api/core/class/Notepad/#clear) */ clear() { return Interaction_1.Interaction.where((0, questions_1.the) `#actor clears ${new NumberOfNotes()} from their notepad`, actor => { return TakeNotes_1.TakeNotes.as(actor).notepad.clear(); }); } /** * Returns the number of notes stored in the notepad. * * ```ts * import { actorCalled, notes } from '@serenity-js/core' * import { isTrue } from '@serenity-js/assertions' * * interface MyNotes { * myNote: string; * } * * await actorCalled('Alice') * .whoCan(TakeNotes.using(Notepad.empty<MyNotes>())) * .attemptsTo( * Log.the(notes<MyNotes>().size()), // emits 0 * notes<MyNotes>().set('myNote', 'example value'), * Log.the(notes<MyNotes>().size()), // emits 1 * ) * ``` * * #### Learn more * - [`Notepad.size`](https://serenity-js.org/api/core/class/Notepad/#size) */ size() { return Question_1.Question.about((0, questions_1.the) `${new NumberOfNotes()}`, async (actor) => { return TakeNotes_1.TakeNotes.as(actor).notepad.size(); }); } /** * Produces a [`QuestionAdapter`](https://serenity-js.org/api/core/#QuestionAdapter) that resolves to a `JSONObject` * representing the resolved notes stored in the notepad. * * Note that serialisation to JSON will simplify some data types that might not be serialisable by default, * but are commonly used in data structures representing actor's notes. * For example a [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) will be serialised as a regular JSON object, a [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) will be serialised as [`Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array). * * Additionally, notepad assumes that the data structure you use it with does not contain cyclic references. * * To learn more about the serialisation mechanism used by the notepad, please refer to [TinyTypes documentation](https://jan-molak.github.io/tiny-types/). * * ```ts * import { actorCalled, notes } from '@serenity-js/core' * * await actorCalled('Alice') * .whoCan(TakeNotes.using(Notepad.with({ * aSet: new Set(['apples', 'bananas', 'cucumbers']), * aPromisedValue: Promise.resolve(42), * aString: 'example' * }))) * .attemptsTo( * Log.the(notes().toJSON()), * ) * // emits: { * // aSet: ['apples', 'bananas', 'cucumbers'] * // aPromisedValue: 42, * // aString: 'example', * // } * ``` */ toJSON() { return Question_1.Question.about('notepad serialised to JSON', async (actor) => { return TakeNotes_1.TakeNotes.as(actor).notepad.toJSON(); }); } /** * @inheritDoc */ toString() { return 'notes'; } } exports.NotepadAdapter = NotepadAdapter; /** * @package */ class ChainableNoteSetter extends Interaction_1.Interaction { notes; constructor(notes) { super(new DescriptionOfNotes(notes)); this.notes = notes; } set(subject, value) { return new ChainableNoteSetter({ ...this.notes, [subject]: value, }); } async performAs(actor) { const notepad = TakeNotes_1.TakeNotes.as(actor).notepad; for (const [subject, value] of Object.entries(this.notes)) { const answer = await actor.answer(value); notepad.set(subject, answer); } } } class DescriptionOfNotes extends Question_1.Question { notes; options; constructor(notes, options) { super(`#actor takes notes: ${Object.keys(notes).join(', ')}`); this.notes = notes; this.options = options; } async answeredBy(actor) { const noteNames = Object.keys(this.notes); const maxWidth = noteNames.reduce((max, name) => Math.max(max, name.length), 0); const list = await (0, io_1.asyncMap)(noteNames, async (noteName) => { const label = `${noteName}:`.padEnd(maxWidth + 1); const noteDescription = await actor.answer(Question_1.Question.formattedValue(this.options).of(this.notes[noteName])); return `- ${label} ${noteDescription}`; }); return [ `#actor takes notes:`, ...list, ].join('\n'); } async describedBy(actor) { return this.answeredBy(actor); } } class NumberOfNotes extends Question_1.Question { constructor() { super('notes'); } async answeredBy(actor) { return TakeNotes_1.TakeNotes.as(actor).notepad.size(); } async describedBy(actor) { const count = await this.answeredBy(actor); return count === 1 ? '1 note' : `${count} notes`; } } //# sourceMappingURL=NotepadAdapter.js.map