NB: Normally, you'd use the [scaffold command.
A new Haibun module is created by extending the AStepper
abstract class from
@haibun/core (see example below), and adding the module to the testing target
directory (refer to the e2e-tests files package.json and local/config.json for
what this should look like).
For example, to create a new module that verifies files exist, using Haibun's abstract storage, you might do the following;
mkdir haibun-files-exist
npm init
npm i @haibun/core @haibun/domain-storage
Instrument your repository for Typescript and tests as appropriate (see haibun-sarif for an example, or use Haibun's scaffolding).
Create an appropriate source file, for example, src/files-exist.ts
Extend the AStepper
abstract class, and the appropriate properties and methods.
Your file might end up looking like this:
import { OK, TNamed, IHasOptions, IRequireDomains, AStepper, TWorld } from '@haibun/core/build/lib/defs.js';
import { actionNotOK, stringOrError, findStepperFromOption } from '@haibun/core/build/lib/util/index.js';
import { STORAGE_ITEM, STORAGE_LOCATION } from '@haibun/domain-storage/build/domain-storage.js';
import { AStorage } from '@haibun/domain-storage/build/AStorage.js';
const STORAGE = 'STORAGE';
const FilesExist = class FilesExist extends AStepper implements IHasOptions, IRequireDomains {
requireDomains = [STORAGE_LOCATION, STORAGE_ITEM];
options = {
[STORAGE]: {
required: true,
desc: 'Storage for file tests',
parse: (input: string) => stringOrError(input),
},
};
storage?: AStorage;
async setWorld(world: TWorld, steppers: AStepper[]) {
await super.setWorld(world, steppers);
this.storage = findStepperFromOption(steppers, this, this.getWorld().extraOptions, STORAGE);
}
steps = {
fileExists: {
gwta: `file {what} exists`,
action: async ({ what }: TNamed) => {
const exists = this.storage?.exists(what);
return exists ? OK : actionNotOK(`${what} does not exist`);
},
},
fileDoesNotExist: {
gwta: `missing file {what}`,
action: async ({ what }: TNamed) => {
const exists = this.storage?.exists(what);
return exists ? actionNotOK(`${what} is not missing`) : OK;
},
}
}
}
After compilation, you can now use statements like file "README.md" exists and missing file "missing.md" in your features. Using your module will require including a storage implementation as well, for example, storage-fs, or potentially multiple implementations via runtime variables, which would be specified via the testing repository's package.json, config.json, and a HAIBUN_O_FILESEXIST_STORAGE runtime variable.
AStepper
steps specify their statements using either exact
or gwta
.
gwta
is more useful,
since it supports BDD style Given, When, Then, And prefixes.
Additionally, it supports the haibun abstract data model for input,
where names in curly braces,
for example {name}, are resolved to a type,
which is string if unspecified.
...
For an example module external to the main haibun project, please refer to haibun sarif.
It may be helpful to refer to the haibun e2e-tests repository, which contains running examples of integration tests. For example, set up that repository, and run npm run test-xss
.
haibun-e2e-tests contains an example of adding a route to a runtime web server (start test route at {loc}) in its src directory.
Haibun supports stepper handlers for consistency in handling events. Steppers can implement IHasHandlers
.
Here's an exapmle:
export const HANDLE_RESULT_HISTORY = 'handleResultHistory';
export interface IHandleResultHistory extends ISourcedHandler {
handle(args: TTypes) : TReturnType
}
elsewhere:
const historyHandlers = findHandlers<IHandleResultHistory>(steppers, HANDLE_RESULT_HISTORY);
Handlers support a usage
property, which can be HANDLER_USAGE.FALLBACK
(only used if no others exist)
or `HANDLER_USAGE.EXCLUSIVE``.