@serenity-js/core
Version:
The core Serenity/JS framework, providing the Screenplay Pattern interfaces, as well as the test reporting and integration infrastructure
209 lines (194 loc) • 6.14 kB
text/typescript
import { TinyType } from 'tiny-types';
import { LogicError } from '../../errors';
import { d } from '../../io';
import { NotepadAdapter } from './NotepadAdapter';
/**
* Stores notes recorded by an [`Actor`](https://serenity-js.org/api/core/class/Actor/).
*
* See [`TakeNotes`](https://serenity-js.org/api/core/class/TakeNotes/) and [notes](https://serenity-js.org/api/core/function/notes) for more usage examples.
*
* ## Sharing a notepad between actors
*
* ```ts
* import { Actor, Cast, Notepad, TakeNotes } from '@serenity-js/core'
*
* interface MyNotes {
* auth: {
* username: string;
* password: string;
* }
* }
*
* export class Actors implements Cast {
*
* // initialise a shared notepad when the Actors class is initialised
* private readonly sharedNotepad = Notepad.with<MyNotes>({
* auth: {
* username: 'test-user',
* password: 'SuperSecretP@ssword!',
* }
* });
*
* prepare(actor: Actor): Actor {
* switch (actor.name) {
* case 'Alice':
* case 'Bob':
* // Alice and Bob will share their notepad
* return actor.whoCan(TakeNotes.using(this.sharedNotepad));
* default:
* // other actors will get their own notepads
* return actor.whoCan(TakeNotes.using(Notepad.empty<AuthCredentials>()));
* }
* }
* }
* ```
*
* ## Learn more
*
* - [`TakeNotes`](https://serenity-js.org/api/core/class/TakeNotes/)
* - [notes](https://serenity-js.org/api/core/function/notes)
* - [`Cast`](https://serenity-js.org/api/core/class/Cast/)
*
* @group Notes
*/
export class Notepad<Notes extends Record<any, any>> extends TinyType {
/**
* Instantiates a new empty Notepad.
*/
static empty<N extends Record<any, any>>(): Notepad<N> {
return new Notepad<N>({} as N);
}
/**
* Instantiates a new Notepad with an initial state.
*
* ```ts
* import { actorCalled, Notepad, notes, TakeNotes } from '@serenity-js/core'
* import { Ensure, equals } from '@serenity-js/assertions'
*
* interface MyNotes {
* personalDetails: {
* firstName: string;
* lastName: string;
* }
* }
*
* actorCalled('Leonard')
* .whoCan(
* TakeNotes.using(
* Notepad.with<MyNotes>({
* personalDetails: {
* firstName: 'Leonard',
* lastName: 'McLaud',
* }
* })
* )
* )
* .attemptsTo(
* Ensure.that(
* notes<MyNotes>().get('personalDetails').firstName,
* equals('Leonard')
* ),
* )
* ```
*
* @param notes
*/
static with<N extends Record<any, any>>(notes: N): Notepad<N> {
return new Notepad<N>(notes);
}
/**
* Creates a [`QuestionAdapter`](https://serenity-js.org/api/core/#QuestionAdapter) that simplifies access to the notes
* stored in this notepad. Allows the [`Actor`](https://serenity-js.org/api/core/class/Actor/) to record, read, and remove notes.
*
* #### Learn more
* - [notes](https://serenity-js.org/api/core/function/notes)
* - [`TakeNotes`](https://serenity-js.org/api/core/class/TakeNotes/)
* - [`Notepad`](https://serenity-js.org/api/core/class/Notepad/)
*/
static notes<N extends Record<any, any>>(): NotepadAdapter<N> {
return new NotepadAdapter<N>();
}
/**
* Instantiates a [`Notepad`](https://serenity-js.org/api/core/class/Notepad/) with an initial state.
*
* @param recordedNotes
* Initial state of the notepad
*/
protected constructor(private readonly recordedNotes: Notes) {
super();
}
/**
* Checks if a note identified by `subject` exists in the notepad.
*
* @param subject
* A subject (name) that uniquely identifies a given note
*
* @returns
* `true` if the note exists, `false` otherwise
*/
has<Subject extends keyof Notes>(subject: Subject): boolean {
return Object.prototype.hasOwnProperty.call(this.recordedNotes, subject);
}
/**
* Retrieves a note, identified by `subject`, from the notepad.
*
* @param subject
* A subject (name) that uniquely identifies a given note
*
* @returns
* The value of the previously recorded note.
*
* @throws [`LogicError`](https://serenity-js.org/api/core/class/LogicError/)
* Throws a [`LogicError`](https://serenity-js.org/api/core/class/LogicError/) if the note with a given `subject`
* has never been recorded.
*/
get<Subject extends keyof Notes>(subject: Subject): Notes[Subject] {
if (! this.has(subject)) {
throw new LogicError(d`Note of ${ subject } cannot be retrieved because it's never been recorded`);
}
return this.recordedNotes[subject];
}
/**
* Stores a given `value` uniquely identified by `subject` in the notepad.
*
* @param subject
* A subject (name) that uniquely identifies a given note
*
* @param value
* The value to record.
*/
set<Subject extends keyof Notes>(subject: Subject, value: Notes[Subject]): Notepad<Notes> {
this.recordedNotes[subject] = value;
return this;
}
/**
* Removes the note identified by `subject` from the notepad.
*
* @param subject
*
* @returns
* `true` if the item in the Notepad object existed and has been removed,
* `false` otherwise.
*/
delete<Subject extends keyof Notes>(subject: Subject): boolean {
if (this.has(subject)) {
return delete this.recordedNotes[subject];
}
return false;
}
/**
* Deletes all the notes stored in this notepad.
*/
clear(): void {
const keys = Object.keys(this.recordedNotes);
for (const key of keys) {
this.delete(key);
}
}
/**
* Returns the number of notes stored in the notepad.
*/
size(): number {
return Object.keys(this.recordedNotes).length;
}
}