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