@harnessed-ts/gherkin 0.0.0-stage → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +53 -0
- package/LICENSE +21 -0
- package/README.md +27 -2
- package/dist/chunk-6AIO5EWN.js +40 -0
- package/dist/chunk-6AIO5EWN.js.map +1 -0
- package/dist/chunk-MMY7SRBU.js +28 -0
- package/dist/chunk-MMY7SRBU.js.map +1 -0
- package/dist/cucumber.cjs +37 -0
- package/dist/cucumber.cjs.map +1 -0
- package/dist/cucumber.d.ts +44 -0
- package/dist/cucumber.d.ts.map +1 -0
- package/dist/cucumber.js +4 -0
- package/dist/cucumber.js.map +1 -0
- package/dist/cypress.cjs +41 -0
- package/dist/cypress.cjs.map +1 -0
- package/dist/cypress.d.ts +49 -0
- package/dist/cypress.d.ts.map +1 -0
- package/dist/cypress.js +36 -0
- package/dist/cypress.js.map +1 -0
- package/dist/index.cjs +44 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/nightwatch.cjs +53 -0
- package/dist/nightwatch.cjs.map +1 -0
- package/dist/nightwatch.d.ts +38 -0
- package/dist/nightwatch.d.ts.map +1 -0
- package/dist/nightwatch.js +20 -0
- package/dist/nightwatch.js.map +1 -0
- package/dist/playwright-bdd.cjs +23 -0
- package/dist/playwright-bdd.cjs.map +1 -0
- package/dist/playwright-bdd.d.ts +29 -0
- package/dist/playwright-bdd.d.ts.map +1 -0
- package/dist/playwright-bdd.js +18 -0
- package/dist/playwright-bdd.js.map +1 -0
- package/dist/yadda.cjs +118 -0
- package/dist/yadda.cjs.map +1 -0
- package/dist/yadda.d.ts +90 -0
- package/dist/yadda.d.ts.map +1 -0
- package/dist/yadda.js +90 -0
- package/dist/yadda.js.map +1 -0
- package/package.json +124 -3
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var nightwatch = require('@harnessed-ts/nightwatch');
|
|
4
|
+
var cucumber = require('@cucumber/cucumber');
|
|
5
|
+
|
|
6
|
+
// src/nightwatch.ts
|
|
7
|
+
|
|
8
|
+
// src/index.ts
|
|
9
|
+
function createWorld() {
|
|
10
|
+
return {};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// src/cucumber.ts
|
|
14
|
+
var HarnessedWorld = class extends cucumber.World {
|
|
15
|
+
/** The scenario's bag. Starts empty; cucumber builds a new world per scenario. */
|
|
16
|
+
bag = createWorld();
|
|
17
|
+
/** The env every harness in the scenario is built with. Set it in a `Before`. */
|
|
18
|
+
env;
|
|
19
|
+
/** The pages features can name. Assign the registry in a subclass. */
|
|
20
|
+
pages;
|
|
21
|
+
constructor(options) {
|
|
22
|
+
super(options);
|
|
23
|
+
}
|
|
24
|
+
/** Constructs a registered page with the scenario's env. */
|
|
25
|
+
open(name) {
|
|
26
|
+
if (this.pages === void 0) {
|
|
27
|
+
throw new Error("harnessed: this world has no page registry. Assign `pages` in a subclass.");
|
|
28
|
+
}
|
|
29
|
+
if (this.env === void 0) {
|
|
30
|
+
throw new Error("harnessed: this world has no env yet. Set `this.env` in a Before hook.");
|
|
31
|
+
}
|
|
32
|
+
return this.pages.open(name, this.env);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// src/nightwatch.ts
|
|
37
|
+
function browserOf(world) {
|
|
38
|
+
const browser = world.browser ?? Reflect.get(globalThis, "browser");
|
|
39
|
+
if (browser === void 0) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
"harnessed: Nightwatch has not started a browser for this scenario. Leave test_runner.options.auto_start_session on, or launch one and set this.env = nightwatch(browser) yourself."
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
return browser;
|
|
45
|
+
}
|
|
46
|
+
function nightwatchEnv() {
|
|
47
|
+
this.env = nightwatch.nightwatch(browserOf(this));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
exports.HarnessedWorld = HarnessedWorld;
|
|
51
|
+
exports.nightwatchEnv = nightwatchEnv;
|
|
52
|
+
//# sourceMappingURL=nightwatch.cjs.map
|
|
53
|
+
//# sourceMappingURL=nightwatch.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/cucumber.ts","../src/nightwatch.ts"],"names":["World","nightwatch"],"mappings":";;;;;;;;AAwGO,SAAS,WAAA,GAA6B;AAC3C,EAAA,OAAO,EAAC;AACV;;;ACxEO,IAAM,cAAA,GAAN,cAGGA,cAAA,CAAM;AAAA;AAAA,EAEd,MAAkB,WAAA,EAAe;AAAA;AAAA,EAEjC,GAAA;AAAA;AAAA,EAEA,KAAA;AAAA,EAEA,YAAY,OAAA,EAAwB;AAClC,IAAA,KAAA,CAAM,OAAO,CAAA;AAAA,EACf;AAAA;AAAA,EAGA,KAAiC,IAAA,EAA6B;AAC5D,IAAA,IAAI,IAAA,CAAK,UAAU,MAAA,EAAW;AAC5B,MAAA,MAAM,IAAI,MAAM,2EAA2E,CAAA;AAAA,IAC7F;AACA,IAAA,IAAI,IAAA,CAAK,QAAQ,MAAA,EAAW;AAC1B,MAAA,MAAM,IAAI,MAAM,wEAAwE,CAAA;AAAA,IAC1F;AACA,IAAA,OAAO,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,IAAA,EAAM,KAAK,GAAG,CAAA;AAAA,EACvC;AACF;;;AC3CA,SAAS,UAAU,KAAA,EAAuC;AACxD,EAAA,MAAM,UAAU,KAAA,CAAM,OAAA,IAAY,OAAA,CAAQ,GAAA,CAAI,YAAY,SAAS,CAAA;AACnE,EAAA,IAAI,YAAY,MAAA,EAAW;AACzB,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KAEF;AAAA,EACF;AACA,EAAA,OAAO,OAAA;AACT;AA0BO,SAAS,aAAA,GAA2C;AACzD,EAAA,IAAA,CAAK,GAAA,GAAMC,qBAAA,CAAW,SAAA,CAAU,IAAI,CAAC,CAAA;AACvC","file":"nightwatch.cjs","sourcesContent":["import type { EnvConfig } from '@harnessed-ts/core'\n\n/**\n * Gherkin support for harnessed: what every runner needs, with no runner in it.\n *\n * - A **world** — the scenario-scoped bag where steps keep the pages a scenario\n * builds up. Steps are separate functions, so the obvious place for a harness\n * is a module-level `let`, which leaks into the next scenario the moment a\n * runner reuses a worker. Each runner adapter gives the bag the right lifetime.\n * - A **page registry** — the pages a feature can name, so a step reads\n * `Given I open the checkout page` and gets a `CheckoutPage`, typed.\n * - A **`{page}` parameter type** built from the registry, so a feature naming\n * an unknown page fails at step matching, where the typo is, rather than deep\n * inside a step.\n *\n * Step bodies stay hand-written: the feature's wording is the team's, not a\n * mirror of method names.\n */\n\n/** Anything a page registry can construct: a page or component harness class. */\nexport type PageConstructor<T = object> = new (env: EnvConfig) => T\n\nexport type PageMap = Record<string, PageConstructor>\n\n/** The pages a feature can name. */\nexport interface PageRegistry<P extends PageMap> {\n /** Every registered name, longest first — the order a matcher must try them. */\n readonly names: ReadonlyArray<keyof P & string>\n /** Whether `name` is registered: narrows a string from a step to a page name. */\n has(name: string): name is keyof P & string\n /** Constructs the named page with the scenario's env. */\n open<N extends keyof P & string>(name: N, env: EnvConfig): InstanceType<P[N]>\n}\n\nexport function definePages<P extends PageMap>(pages: P): PageRegistry<P> {\n const names = (Object.keys(pages) as Array<keyof P & string>).sort((a, b) => b.length - a.length)\n if (names.length === 0) {\n // `{page}` is an alternation of the names; with none it is the empty\n // pattern, which matches anywhere and would let any step through.\n throw new Error('harnessed: definePages() needs at least one page.')\n }\n const has = (name: string): name is keyof P & string =>\n Object.prototype.hasOwnProperty.call(pages, name)\n return {\n names,\n has,\n open(name, env) {\n // Own names only: `constructor` and the rest of Object.prototype are not pages.\n if (!has(name)) {\n throw new Error(`harnessed: no page named \"${name}\". Registered: ${names.join(', ')}.`)\n }\n const Page = pages[name]\n return new Page(env) as InstanceType<P[typeof name]>\n },\n }\n}\n\n/**\n * A cucumber-expression parameter type, in the shape every runner's\n * `defineParameterType` takes: @cucumber/cucumber, playwright-bdd and\n * @badeball/cypress-cucumber-preprocessor alike.\n */\nexport interface ParameterTypeDefinition<T> {\n name: string\n regexp: RegExp\n transformer: (match: string) => T\n useForSnippets?: boolean\n}\n\nfunction escape(text: string): string {\n return text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n/**\n * `{page}` for step patterns: matches exactly the registered names (longest\n * first, so `checkout summary` wins over `checkout`) and hands the step the\n * name, typed. Name it something else with `{ name }`.\n *\n * ```ts\n * defineParameterType(pageParameter(pages))\n * Given('I open the {page} page', async function (name) { … pages.open(name, env) })\n * ```\n */\nexport function pageParameter<P extends PageMap>(\n registry: PageRegistry<P>,\n options: { name?: string } = {},\n): ParameterTypeDefinition<keyof P & string> {\n return {\n name: options.name ?? 'page',\n regexp: new RegExp(registry.names.map(escape).join('|')),\n transformer: match => {\n // The regexp only admits registered names, so this cannot fail; it is\n // here so a hand-built pattern that bypasses it still gets a clear error.\n if (!registry.has(match)) throw new Error(`harnessed: no page named \"${match}\".`)\n return match\n },\n useForSnippets: false,\n }\n}\n\n/**\n * A fresh, empty world. Adapters call this once per scenario; it exists so the\n * bag's type is written once, where the steps declare it.\n */\nexport function createWorld<W>(): Partial<W> {\n return {}\n}\n","import { World } from '@cucumber/cucumber'\nimport type { IWorldOptions } from '@cucumber/cucumber'\nimport type { EnvConfig } from '@harnessed-ts/core'\nimport { createWorld } from './index'\nimport type { PageMap, PageRegistry } from './index'\n\n/**\n * A cucumber-js World with the harnessed pieces on it. Cucumber constructs one\n * per scenario, so everything here is scenario-scoped by construction.\n *\n * ```ts\n * const pageMap = { checkout: CheckoutPage }\n * const pages = definePages(pageMap)\n *\n * class AppWorld extends HarnessedWorld<{ checkout: CheckoutPage }, typeof pageMap> {\n * override pages = pages\n * }\n * setWorldConstructor(AppWorld)\n * defineParameterType(pageParameter(pages))\n *\n * // One browser for the run; a page per scenario.\n * let browser: Browser\n * BeforeAll(async () => {\n * browser = await chromium.launch()\n * })\n * Before(async function (this: AppWorld) {\n * this.env = pw(await browser.newPage()) // or wdio(browser), …\n * })\n * Given('I open the {page} page', async function (this: AppWorld, name: 'checkout') {\n * this.bag.checkout = this.open(name)\n * await this.bag.checkout.goto()\n * })\n * ```\n */\nexport class HarnessedWorld<\n W = Record<string, unknown>,\n P extends PageMap = PageMap,\n> extends World {\n /** The scenario's bag. Starts empty; cucumber builds a new world per scenario. */\n bag: Partial<W> = createWorld<W>()\n /** The env every harness in the scenario is built with. Set it in a `Before`. */\n env: EnvConfig | undefined\n /** The pages features can name. Assign the registry in a subclass. */\n pages: PageRegistry<P> | undefined\n\n constructor(options: IWorldOptions) {\n super(options)\n }\n\n /** Constructs a registered page with the scenario's env. */\n open<N extends keyof P & string>(name: N): InstanceType<P[N]> {\n if (this.pages === undefined) {\n throw new Error('harnessed: this world has no page registry. Assign `pages` in a subclass.')\n }\n if (this.env === undefined) {\n throw new Error('harnessed: this world has no env yet. Set `this.env` in a Before hook.')\n }\n return this.pages.open(name, this.env)\n }\n}\n","import type { EnvConfig } from '@harnessed-ts/core'\nimport { nightwatch } from '@harnessed-ts/nightwatch'\nimport type { NightwatchAPI } from 'nightwatch'\n\nexport { HarnessedWorld } from './cucumber'\n\n/**\n * What the hook needs of a world — any `HarnessedWorld`, whatever its pages —\n * as Nightwatch's Cucumber runner hands it over: the runner's own `Before`\n * launches a browser for each scenario and sets it on the world as `browser`.\n */\nexport interface NightwatchWorld {\n env: EnvConfig | undefined\n browser?: NightwatchAPI\n}\n\nfunction browserOf(world: NightwatchWorld): NightwatchAPI {\n const browser = world.browser ?? (Reflect.get(globalThis, 'browser') as NightwatchAPI | undefined)\n if (browser === undefined) {\n throw new Error(\n 'harnessed: Nightwatch has not started a browser for this scenario. Leave ' +\n 'test_runner.options.auto_start_session on, or launch one and set this.env = nightwatch(browser) yourself.',\n )\n }\n return browser\n}\n\n/**\n * A `Before` hook for Nightwatch's Cucumber runner: builds the scenario's env\n * from the browser Nightwatch launched for it, so `this.open(name)` works in\n * every step. Nightwatch registers its own `Before` ahead of the step files,\n * so the browser is there by the time this runs.\n *\n * ```ts\n * import { Before, setWorldConstructor, When } from '@cucumber/cucumber'\n * import { definePages } from '@harnessed-ts/gherkin'\n * import { HarnessedWorld, nightwatchEnv } from '@harnessed-ts/gherkin/nightwatch'\n *\n * const pageMap = { checkout: CheckoutPage }\n * class AppWorld extends HarnessedWorld<{ checkout: CheckoutPage }, typeof pageMap> {\n * override pages = definePages(pageMap)\n * }\n * setWorldConstructor(AppWorld)\n * Before(nightwatchEnv)\n *\n * When('I open the {page} page', async function (this: AppWorld, name: 'checkout') {\n * this.bag.checkout = this.open(name)\n * await this.bag.checkout.goto() // browser.navigateTo(), against launch_url\n * })\n * ```\n */\nexport function nightwatchEnv(this: NightwatchWorld): void {\n this.env = nightwatch(browserOf(this))\n}\n"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { EnvConfig } from '@harnessed-ts/core';
|
|
2
|
+
import type { NightwatchAPI } from 'nightwatch';
|
|
3
|
+
export { HarnessedWorld } from './cucumber';
|
|
4
|
+
/**
|
|
5
|
+
* What the hook needs of a world — any `HarnessedWorld`, whatever its pages —
|
|
6
|
+
* as Nightwatch's Cucumber runner hands it over: the runner's own `Before`
|
|
7
|
+
* launches a browser for each scenario and sets it on the world as `browser`.
|
|
8
|
+
*/
|
|
9
|
+
export interface NightwatchWorld {
|
|
10
|
+
env: EnvConfig | undefined;
|
|
11
|
+
browser?: NightwatchAPI;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A `Before` hook for Nightwatch's Cucumber runner: builds the scenario's env
|
|
15
|
+
* from the browser Nightwatch launched for it, so `this.open(name)` works in
|
|
16
|
+
* every step. Nightwatch registers its own `Before` ahead of the step files,
|
|
17
|
+
* so the browser is there by the time this runs.
|
|
18
|
+
*
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { Before, setWorldConstructor, When } from '@cucumber/cucumber'
|
|
21
|
+
* import { definePages } from '@harnessed-ts/gherkin'
|
|
22
|
+
* import { HarnessedWorld, nightwatchEnv } from '@harnessed-ts/gherkin/nightwatch'
|
|
23
|
+
*
|
|
24
|
+
* const pageMap = { checkout: CheckoutPage }
|
|
25
|
+
* class AppWorld extends HarnessedWorld<{ checkout: CheckoutPage }, typeof pageMap> {
|
|
26
|
+
* override pages = definePages(pageMap)
|
|
27
|
+
* }
|
|
28
|
+
* setWorldConstructor(AppWorld)
|
|
29
|
+
* Before(nightwatchEnv)
|
|
30
|
+
*
|
|
31
|
+
* When('I open the {page} page', async function (this: AppWorld, name: 'checkout') {
|
|
32
|
+
* this.bag.checkout = this.open(name)
|
|
33
|
+
* await this.bag.checkout.goto() // browser.navigateTo(), against launch_url
|
|
34
|
+
* })
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare function nightwatchEnv(this: NightwatchWorld): void;
|
|
38
|
+
//# sourceMappingURL=nightwatch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nightwatch.d.ts","sourceRoot":"","sources":["../src/nightwatch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAEnD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAE3C;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,SAAS,GAAG,SAAS,CAAA;IAC1B,OAAO,CAAC,EAAE,aAAa,CAAA;CACxB;AAaD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAEzD"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export { HarnessedWorld } from './chunk-MMY7SRBU.js';
|
|
2
|
+
import './chunk-6AIO5EWN.js';
|
|
3
|
+
import { nightwatch } from '@harnessed-ts/nightwatch';
|
|
4
|
+
|
|
5
|
+
function browserOf(world) {
|
|
6
|
+
const browser = world.browser ?? Reflect.get(globalThis, "browser");
|
|
7
|
+
if (browser === void 0) {
|
|
8
|
+
throw new Error(
|
|
9
|
+
"harnessed: Nightwatch has not started a browser for this scenario. Leave test_runner.options.auto_start_session on, or launch one and set this.env = nightwatch(browser) yourself."
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
return browser;
|
|
13
|
+
}
|
|
14
|
+
function nightwatchEnv() {
|
|
15
|
+
this.env = nightwatch(browserOf(this));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { nightwatchEnv };
|
|
19
|
+
//# sourceMappingURL=nightwatch.js.map
|
|
20
|
+
//# sourceMappingURL=nightwatch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/nightwatch.ts"],"names":[],"mappings":";;;;AAgBA,SAAS,UAAU,KAAA,EAAuC;AACxD,EAAA,MAAM,UAAU,KAAA,CAAM,OAAA,IAAY,OAAA,CAAQ,GAAA,CAAI,YAAY,SAAS,CAAA;AACnE,EAAA,IAAI,YAAY,MAAA,EAAW;AACzB,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KAEF;AAAA,EACF;AACA,EAAA,OAAO,OAAA;AACT;AA0BO,SAAS,aAAA,GAA2C;AACzD,EAAA,IAAA,CAAK,GAAA,GAAM,UAAA,CAAW,SAAA,CAAU,IAAI,CAAC,CAAA;AACvC","file":"nightwatch.js","sourcesContent":["import type { EnvConfig } from '@harnessed-ts/core'\nimport { nightwatch } from '@harnessed-ts/nightwatch'\nimport type { NightwatchAPI } from 'nightwatch'\n\nexport { HarnessedWorld } from './cucumber'\n\n/**\n * What the hook needs of a world — any `HarnessedWorld`, whatever its pages —\n * as Nightwatch's Cucumber runner hands it over: the runner's own `Before`\n * launches a browser for each scenario and sets it on the world as `browser`.\n */\nexport interface NightwatchWorld {\n env: EnvConfig | undefined\n browser?: NightwatchAPI\n}\n\nfunction browserOf(world: NightwatchWorld): NightwatchAPI {\n const browser = world.browser ?? (Reflect.get(globalThis, 'browser') as NightwatchAPI | undefined)\n if (browser === undefined) {\n throw new Error(\n 'harnessed: Nightwatch has not started a browser for this scenario. Leave ' +\n 'test_runner.options.auto_start_session on, or launch one and set this.env = nightwatch(browser) yourself.',\n )\n }\n return browser\n}\n\n/**\n * A `Before` hook for Nightwatch's Cucumber runner: builds the scenario's env\n * from the browser Nightwatch launched for it, so `this.open(name)` works in\n * every step. Nightwatch registers its own `Before` ahead of the step files,\n * so the browser is there by the time this runs.\n *\n * ```ts\n * import { Before, setWorldConstructor, When } from '@cucumber/cucumber'\n * import { definePages } from '@harnessed-ts/gherkin'\n * import { HarnessedWorld, nightwatchEnv } from '@harnessed-ts/gherkin/nightwatch'\n *\n * const pageMap = { checkout: CheckoutPage }\n * class AppWorld extends HarnessedWorld<{ checkout: CheckoutPage }, typeof pageMap> {\n * override pages = definePages(pageMap)\n * }\n * setWorldConstructor(AppWorld)\n * Before(nightwatchEnv)\n *\n * When('I open the {page} page', async function (this: AppWorld, name: 'checkout') {\n * this.bag.checkout = this.open(name)\n * await this.bag.checkout.goto() // browser.navigateTo(), against launch_url\n * })\n * ```\n */\nexport function nightwatchEnv(this: NightwatchWorld): void {\n this.env = nightwatch(browserOf(this))\n}\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
function createWorld() {
|
|
5
|
+
return {};
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// src/playwright-bdd.ts
|
|
9
|
+
function withWorld(base) {
|
|
10
|
+
const extended = base.extend({
|
|
11
|
+
// Playwright requires the object-destructuring pattern here: it reads the
|
|
12
|
+
// destructured names to work out what this fixture depends on, and rejects a
|
|
13
|
+
// plain parameter outright. Empty means it depends on nothing.
|
|
14
|
+
world: async ({}, use) => {
|
|
15
|
+
await use(createWorld());
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
return extended;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
exports.withWorld = withWorld;
|
|
22
|
+
//# sourceMappingURL=playwright-bdd.cjs.map
|
|
23
|
+
//# sourceMappingURL=playwright-bdd.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/playwright-bdd.ts"],"names":[],"mappings":";;;AAwGO,SAAS,WAAA,GAA6B;AAC3C,EAAA,OAAO,EAAC;AACV;;;AC7DO,SAAS,UAAgB,IAAA,EAA0B;AACxD,EAAA,MAAM,QAAA,GAAY,KAAqB,MAAA,CAAwB;AAAA;AAAA;AAAA;AAAA,IAI7D,KAAA,EAAO,OAAO,EAAC,EAAG,GAAA,KAA8C;AAC9D,MAAA,MAAM,GAAA,CAAI,aAAgB,CAAA;AAAA,IAC5B;AAAA,GACD,CAAA;AACD,EAAA,OAAO,QAAA;AACT","file":"playwright-bdd.cjs","sourcesContent":["import type { EnvConfig } from '@harnessed-ts/core'\n\n/**\n * Gherkin support for harnessed: what every runner needs, with no runner in it.\n *\n * - A **world** — the scenario-scoped bag where steps keep the pages a scenario\n * builds up. Steps are separate functions, so the obvious place for a harness\n * is a module-level `let`, which leaks into the next scenario the moment a\n * runner reuses a worker. Each runner adapter gives the bag the right lifetime.\n * - A **page registry** — the pages a feature can name, so a step reads\n * `Given I open the checkout page` and gets a `CheckoutPage`, typed.\n * - A **`{page}` parameter type** built from the registry, so a feature naming\n * an unknown page fails at step matching, where the typo is, rather than deep\n * inside a step.\n *\n * Step bodies stay hand-written: the feature's wording is the team's, not a\n * mirror of method names.\n */\n\n/** Anything a page registry can construct: a page or component harness class. */\nexport type PageConstructor<T = object> = new (env: EnvConfig) => T\n\nexport type PageMap = Record<string, PageConstructor>\n\n/** The pages a feature can name. */\nexport interface PageRegistry<P extends PageMap> {\n /** Every registered name, longest first — the order a matcher must try them. */\n readonly names: ReadonlyArray<keyof P & string>\n /** Whether `name` is registered: narrows a string from a step to a page name. */\n has(name: string): name is keyof P & string\n /** Constructs the named page with the scenario's env. */\n open<N extends keyof P & string>(name: N, env: EnvConfig): InstanceType<P[N]>\n}\n\nexport function definePages<P extends PageMap>(pages: P): PageRegistry<P> {\n const names = (Object.keys(pages) as Array<keyof P & string>).sort((a, b) => b.length - a.length)\n if (names.length === 0) {\n // `{page}` is an alternation of the names; with none it is the empty\n // pattern, which matches anywhere and would let any step through.\n throw new Error('harnessed: definePages() needs at least one page.')\n }\n const has = (name: string): name is keyof P & string =>\n Object.prototype.hasOwnProperty.call(pages, name)\n return {\n names,\n has,\n open(name, env) {\n // Own names only: `constructor` and the rest of Object.prototype are not pages.\n if (!has(name)) {\n throw new Error(`harnessed: no page named \"${name}\". Registered: ${names.join(', ')}.`)\n }\n const Page = pages[name]\n return new Page(env) as InstanceType<P[typeof name]>\n },\n }\n}\n\n/**\n * A cucumber-expression parameter type, in the shape every runner's\n * `defineParameterType` takes: @cucumber/cucumber, playwright-bdd and\n * @badeball/cypress-cucumber-preprocessor alike.\n */\nexport interface ParameterTypeDefinition<T> {\n name: string\n regexp: RegExp\n transformer: (match: string) => T\n useForSnippets?: boolean\n}\n\nfunction escape(text: string): string {\n return text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n/**\n * `{page}` for step patterns: matches exactly the registered names (longest\n * first, so `checkout summary` wins over `checkout`) and hands the step the\n * name, typed. Name it something else with `{ name }`.\n *\n * ```ts\n * defineParameterType(pageParameter(pages))\n * Given('I open the {page} page', async function (name) { … pages.open(name, env) })\n * ```\n */\nexport function pageParameter<P extends PageMap>(\n registry: PageRegistry<P>,\n options: { name?: string } = {},\n): ParameterTypeDefinition<keyof P & string> {\n return {\n name: options.name ?? 'page',\n regexp: new RegExp(registry.names.map(escape).join('|')),\n transformer: match => {\n // The regexp only admits registered names, so this cannot fail; it is\n // here so a hand-built pattern that bypasses it still gets a clear error.\n if (!registry.has(match)) throw new Error(`harnessed: no page named \"${match}\".`)\n return match\n },\n useForSnippets: false,\n }\n}\n\n/**\n * A fresh, empty world. Adapters call this once per scenario; it exists so the\n * bag's type is written once, where the steps declare it.\n */\nexport function createWorld<W>(): Partial<W> {\n return {}\n}\n","import type { TestType } from '@playwright/test'\nimport { createWorld } from './index'\n\n/**\n * A scenario-scoped bag for the pages a Gherkin scenario builds up.\n *\n * Steps are separate functions, so the obvious place to put a harness is a\n * module-level `let` — which breaks the moment Playwright reuses a worker,\n * because the next scenario inherits the last one's state. A fixture is\n * per-scenario, so it cannot leak.\n */\nexport interface WorldFixture<W> {\n world: Partial<W>\n}\n\n/**\n * A structural stand-in for \"some Playwright test object\", used to call `extend`.\n *\n * Deliberately not used as a bound on `withWorld`'s `T`. Playwright's real\n * `TestType` is not assignable to this one — its concrete fixture args do not\n * satisfy an index-signature record — so constraining `T` rejects the very thing\n * callers pass. The conditional return type is what gives misuse a readable\n * error instead.\n */\ntype AnyTestType = TestType<Record<string, unknown>, Record<string, unknown>>\n\n/**\n * The base test type with the world fixture added. Written as a conditional so\n * the base's own fixtures survive — returning the base type unchanged would make\n * `world` invisible to every step that destructures it.\n */\nexport type WithWorld<T, W> =\n T extends TestType<infer TestArgs, infer WorkerArgs>\n ? TestType<TestArgs & WorldFixture<W>, WorkerArgs>\n : never\n\n/**\n * Adds a `world` fixture to any Playwright-shaped `test` — plain\n * `@playwright/test` or playwright-bdd's.\n *\n * ```ts\n * export const test = withWorld<World, typeof apiTest>(apiTest)\n * const { Given, When, Then } = createBdd(test)\n * ```\n */\nexport function withWorld<W, T>(base: T): WithWorld<T, W> {\n const extended = (base as AnyTestType).extend<WorldFixture<W>>({\n // Playwright requires the object-destructuring pattern here: it reads the\n // destructured names to work out what this fixture depends on, and rejects a\n // plain parameter outright. Empty means it depends on nothing.\n world: async ({}, use: (value: Partial<W>) => Promise<void>) => {\n await use(createWorld<W>())\n },\n })\n return extended as unknown as WithWorld<T, W>\n}\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { TestType } from '@playwright/test';
|
|
2
|
+
/**
|
|
3
|
+
* A scenario-scoped bag for the pages a Gherkin scenario builds up.
|
|
4
|
+
*
|
|
5
|
+
* Steps are separate functions, so the obvious place to put a harness is a
|
|
6
|
+
* module-level `let` — which breaks the moment Playwright reuses a worker,
|
|
7
|
+
* because the next scenario inherits the last one's state. A fixture is
|
|
8
|
+
* per-scenario, so it cannot leak.
|
|
9
|
+
*/
|
|
10
|
+
export interface WorldFixture<W> {
|
|
11
|
+
world: Partial<W>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The base test type with the world fixture added. Written as a conditional so
|
|
15
|
+
* the base's own fixtures survive — returning the base type unchanged would make
|
|
16
|
+
* `world` invisible to every step that destructures it.
|
|
17
|
+
*/
|
|
18
|
+
export type WithWorld<T, W> = T extends TestType<infer TestArgs, infer WorkerArgs> ? TestType<TestArgs & WorldFixture<W>, WorkerArgs> : never;
|
|
19
|
+
/**
|
|
20
|
+
* Adds a `world` fixture to any Playwright-shaped `test` — plain
|
|
21
|
+
* `@playwright/test` or playwright-bdd's.
|
|
22
|
+
*
|
|
23
|
+
* ```ts
|
|
24
|
+
* export const test = withWorld<World, typeof apiTest>(apiTest)
|
|
25
|
+
* const { Given, When, Then } = createBdd(test)
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare function withWorld<W, T>(base: T): WithWorld<T, W>;
|
|
29
|
+
//# sourceMappingURL=playwright-bdd.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playwright-bdd.d.ts","sourceRoot":"","sources":["../src/playwright-bdd.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAGhD;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;CAClB;AAaD;;;;GAIG;AACH,MAAM,MAAM,SAAS,CAAC,CAAC,EAAE,CAAC,IACxB,CAAC,SAAS,QAAQ,CAAC,MAAM,QAAQ,EAAE,MAAM,UAAU,CAAC,GAChD,QAAQ,CAAC,QAAQ,GAAG,YAAY,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,GAChD,KAAK,CAAA;AAEX;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAUxD"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createWorld } from './chunk-6AIO5EWN.js';
|
|
2
|
+
|
|
3
|
+
// src/playwright-bdd.ts
|
|
4
|
+
function withWorld(base) {
|
|
5
|
+
const extended = base.extend({
|
|
6
|
+
// Playwright requires the object-destructuring pattern here: it reads the
|
|
7
|
+
// destructured names to work out what this fixture depends on, and rejects a
|
|
8
|
+
// plain parameter outright. Empty means it depends on nothing.
|
|
9
|
+
world: async ({}, use) => {
|
|
10
|
+
await use(createWorld());
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
return extended;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { withWorld };
|
|
17
|
+
//# sourceMappingURL=playwright-bdd.js.map
|
|
18
|
+
//# sourceMappingURL=playwright-bdd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/playwright-bdd.ts"],"names":[],"mappings":";;;AA6CO,SAAS,UAAgB,IAAA,EAA0B;AACxD,EAAA,MAAM,QAAA,GAAY,KAAqB,MAAA,CAAwB;AAAA;AAAA;AAAA;AAAA,IAI7D,KAAA,EAAO,OAAO,EAAC,EAAG,GAAA,KAA8C;AAC9D,MAAA,MAAM,GAAA,CAAI,aAAgB,CAAA;AAAA,IAC5B;AAAA,GACD,CAAA;AACD,EAAA,OAAO,QAAA;AACT","file":"playwright-bdd.js","sourcesContent":["import type { TestType } from '@playwright/test'\nimport { createWorld } from './index'\n\n/**\n * A scenario-scoped bag for the pages a Gherkin scenario builds up.\n *\n * Steps are separate functions, so the obvious place to put a harness is a\n * module-level `let` — which breaks the moment Playwright reuses a worker,\n * because the next scenario inherits the last one's state. A fixture is\n * per-scenario, so it cannot leak.\n */\nexport interface WorldFixture<W> {\n world: Partial<W>\n}\n\n/**\n * A structural stand-in for \"some Playwright test object\", used to call `extend`.\n *\n * Deliberately not used as a bound on `withWorld`'s `T`. Playwright's real\n * `TestType` is not assignable to this one — its concrete fixture args do not\n * satisfy an index-signature record — so constraining `T` rejects the very thing\n * callers pass. The conditional return type is what gives misuse a readable\n * error instead.\n */\ntype AnyTestType = TestType<Record<string, unknown>, Record<string, unknown>>\n\n/**\n * The base test type with the world fixture added. Written as a conditional so\n * the base's own fixtures survive — returning the base type unchanged would make\n * `world` invisible to every step that destructures it.\n */\nexport type WithWorld<T, W> =\n T extends TestType<infer TestArgs, infer WorkerArgs>\n ? TestType<TestArgs & WorldFixture<W>, WorkerArgs>\n : never\n\n/**\n * Adds a `world` fixture to any Playwright-shaped `test` — plain\n * `@playwright/test` or playwright-bdd's.\n *\n * ```ts\n * export const test = withWorld<World, typeof apiTest>(apiTest)\n * const { Given, When, Then } = createBdd(test)\n * ```\n */\nexport function withWorld<W, T>(base: T): WithWorld<T, W> {\n const extended = (base as AnyTestType).extend<WorldFixture<W>>({\n // Playwright requires the object-destructuring pattern here: it reads the\n // destructured names to work out what this fixture depends on, and rejects a\n // plain parameter outright. Empty means it depends on nothing.\n world: async ({}, use: (value: Partial<W>) => Promise<void>) => {\n await use(createWorld<W>())\n },\n })\n return extended as unknown as WithWorld<T, W>\n}\n"]}
|
package/dist/yadda.cjs
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var Yadda = require('yadda');
|
|
4
|
+
|
|
5
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
6
|
+
|
|
7
|
+
var Yadda__default = /*#__PURE__*/_interopDefault(Yadda);
|
|
8
|
+
|
|
9
|
+
// src/yadda.ts
|
|
10
|
+
|
|
11
|
+
// src/index.ts
|
|
12
|
+
function escape(text) {
|
|
13
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
14
|
+
}
|
|
15
|
+
function pageParameter(registry, options = {}) {
|
|
16
|
+
return {
|
|
17
|
+
name: options.name ?? "page",
|
|
18
|
+
regexp: new RegExp(registry.names.map(escape).join("|")),
|
|
19
|
+
transformer: (match) => {
|
|
20
|
+
if (!registry.has(match)) throw new Error(`harnessed: no page named "${match}".`);
|
|
21
|
+
return match;
|
|
22
|
+
},
|
|
23
|
+
useForSnippets: false
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function createWorld() {
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/yadda.ts
|
|
31
|
+
var extended = /* @__PURE__ */ new WeakMap();
|
|
32
|
+
function pageDictionary(registry, options = {}) {
|
|
33
|
+
const { regexp, transformer } = pageParameter(registry);
|
|
34
|
+
const term = options.term ?? "page";
|
|
35
|
+
const dictionary = options.dictionary ?? new Yadda__default.default.Dictionary();
|
|
36
|
+
const pattern = `(${regexp.source})`;
|
|
37
|
+
const terms = extended.get(dictionary) ?? /* @__PURE__ */ new Map();
|
|
38
|
+
extended.set(dictionary, terms);
|
|
39
|
+
const existing = terms.get(term);
|
|
40
|
+
if (existing === pattern) return dictionary;
|
|
41
|
+
if (existing !== void 0) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`harnessed: this dictionary already defines $${term} with other pages. Give the second registry its own term.`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
const convert = (match, next, ..._unused) => {
|
|
47
|
+
if (typeof match !== "string" || typeof next !== "function") {
|
|
48
|
+
throw new Error(`harnessed: $${term} expects one capture and Yadda's callback.`);
|
|
49
|
+
}
|
|
50
|
+
let page;
|
|
51
|
+
try {
|
|
52
|
+
page = transformer(match);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
next(error);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
next(null, page);
|
|
58
|
+
};
|
|
59
|
+
dictionary.define(term, pattern, convert);
|
|
60
|
+
terms.set(term, pattern);
|
|
61
|
+
return dictionary;
|
|
62
|
+
}
|
|
63
|
+
function yaddaWorld() {
|
|
64
|
+
return {
|
|
65
|
+
steps(options) {
|
|
66
|
+
return createSteps(options);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function createSteps(options) {
|
|
71
|
+
const { pages, env } = options;
|
|
72
|
+
const language = options.language ?? Yadda__default.default.localisation.English;
|
|
73
|
+
const library = language.localise(
|
|
74
|
+
new Yadda__default.default.ContextParamLibrary(options.dictionary ?? pageDictionary(pages))
|
|
75
|
+
);
|
|
76
|
+
const world = createWorld();
|
|
77
|
+
const open = (name) => pages.open(name, env());
|
|
78
|
+
const wrap = (step) => async (context, ...rest) => {
|
|
79
|
+
await step({ world, open, context }, ...rest.slice(0, -1));
|
|
80
|
+
};
|
|
81
|
+
const promise = { mode: "promise" };
|
|
82
|
+
const anyKeyword = `(?:${["given", "when", "then"].map((keyword) => language.translate(keyword)).join("|")})`;
|
|
83
|
+
return {
|
|
84
|
+
world,
|
|
85
|
+
library,
|
|
86
|
+
given(signature, step) {
|
|
87
|
+
library.given(signature, wrap(step), void 0, promise);
|
|
88
|
+
return this;
|
|
89
|
+
},
|
|
90
|
+
when(signature, step) {
|
|
91
|
+
library.when(signature, wrap(step), void 0, promise);
|
|
92
|
+
return this;
|
|
93
|
+
},
|
|
94
|
+
then(signature, step) {
|
|
95
|
+
library.then(signature, wrap(step), void 0, promise);
|
|
96
|
+
return this;
|
|
97
|
+
},
|
|
98
|
+
step(signature, step) {
|
|
99
|
+
const signatures = Array.isArray(signature) ? signature : [signature];
|
|
100
|
+
library.define(
|
|
101
|
+
signatures.map((one) => withKeyword(anyKeyword, one)),
|
|
102
|
+
wrap(step),
|
|
103
|
+
void 0,
|
|
104
|
+
promise
|
|
105
|
+
);
|
|
106
|
+
return this;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function withKeyword(keyword, signature) {
|
|
111
|
+
const source = signature instanceof RegExp ? signature.source : signature;
|
|
112
|
+
return `^(?:\\s)*${keyword}\\s+${source.replace(/^\^/, "")}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
exports.pageDictionary = pageDictionary;
|
|
116
|
+
exports.yaddaWorld = yaddaWorld;
|
|
117
|
+
//# sourceMappingURL=yadda.cjs.map
|
|
118
|
+
//# sourceMappingURL=yadda.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/yadda.ts"],"names":["Yadda"],"mappings":"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","file":"yadda.cjs","sourcesContent":["import type { EnvConfig } from '@harnessed-ts/core'\n\n/**\n * Gherkin support for harnessed: what every runner needs, with no runner in it.\n *\n * - A **world** — the scenario-scoped bag where steps keep the pages a scenario\n * builds up. Steps are separate functions, so the obvious place for a harness\n * is a module-level `let`, which leaks into the next scenario the moment a\n * runner reuses a worker. Each runner adapter gives the bag the right lifetime.\n * - A **page registry** — the pages a feature can name, so a step reads\n * `Given I open the checkout page` and gets a `CheckoutPage`, typed.\n * - A **`{page}` parameter type** built from the registry, so a feature naming\n * an unknown page fails at step matching, where the typo is, rather than deep\n * inside a step.\n *\n * Step bodies stay hand-written: the feature's wording is the team's, not a\n * mirror of method names.\n */\n\n/** Anything a page registry can construct: a page or component harness class. */\nexport type PageConstructor<T = object> = new (env: EnvConfig) => T\n\nexport type PageMap = Record<string, PageConstructor>\n\n/** The pages a feature can name. */\nexport interface PageRegistry<P extends PageMap> {\n /** Every registered name, longest first — the order a matcher must try them. */\n readonly names: ReadonlyArray<keyof P & string>\n /** Whether `name` is registered: narrows a string from a step to a page name. */\n has(name: string): name is keyof P & string\n /** Constructs the named page with the scenario's env. */\n open<N extends keyof P & string>(name: N, env: EnvConfig): InstanceType<P[N]>\n}\n\nexport function definePages<P extends PageMap>(pages: P): PageRegistry<P> {\n const names = (Object.keys(pages) as Array<keyof P & string>).sort((a, b) => b.length - a.length)\n if (names.length === 0) {\n // `{page}` is an alternation of the names; with none it is the empty\n // pattern, which matches anywhere and would let any step through.\n throw new Error('harnessed: definePages() needs at least one page.')\n }\n const has = (name: string): name is keyof P & string =>\n Object.prototype.hasOwnProperty.call(pages, name)\n return {\n names,\n has,\n open(name, env) {\n // Own names only: `constructor` and the rest of Object.prototype are not pages.\n if (!has(name)) {\n throw new Error(`harnessed: no page named \"${name}\". Registered: ${names.join(', ')}.`)\n }\n const Page = pages[name]\n return new Page(env) as InstanceType<P[typeof name]>\n },\n }\n}\n\n/**\n * A cucumber-expression parameter type, in the shape every runner's\n * `defineParameterType` takes: @cucumber/cucumber, playwright-bdd and\n * @badeball/cypress-cucumber-preprocessor alike.\n */\nexport interface ParameterTypeDefinition<T> {\n name: string\n regexp: RegExp\n transformer: (match: string) => T\n useForSnippets?: boolean\n}\n\nfunction escape(text: string): string {\n return text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n/**\n * `{page}` for step patterns: matches exactly the registered names (longest\n * first, so `checkout summary` wins over `checkout`) and hands the step the\n * name, typed. Name it something else with `{ name }`.\n *\n * ```ts\n * defineParameterType(pageParameter(pages))\n * Given('I open the {page} page', async function (name) { … pages.open(name, env) })\n * ```\n */\nexport function pageParameter<P extends PageMap>(\n registry: PageRegistry<P>,\n options: { name?: string } = {},\n): ParameterTypeDefinition<keyof P & string> {\n return {\n name: options.name ?? 'page',\n regexp: new RegExp(registry.names.map(escape).join('|')),\n transformer: match => {\n // The regexp only admits registered names, so this cannot fail; it is\n // here so a hand-built pattern that bypasses it still gets a clear error.\n if (!registry.has(match)) throw new Error(`harnessed: no page named \"${match}\".`)\n return match\n },\n useForSnippets: false,\n }\n}\n\n/**\n * A fresh, empty world. Adapters call this once per scenario; it exists so the\n * bag's type is written once, where the steps declare it.\n */\nexport function createWorld<W>(): Partial<W> {\n return {}\n}\n","import type { EnvConfig } from '@harnessed-ts/core'\nimport Yadda from 'yadda'\nimport { createWorld, pageParameter } from './index'\nimport type { PageMap, PageRegistry } from './index'\n\n/**\n * The page terms each dictionary was given, and the pattern each one matches.\n * Yadda refuses to define a term twice, and ember-cli-yadda calls a steps\n * module's default export once per scenario, so extending a module-level\n * dictionary there would throw from the second scenario on. Copying it instead\n * is no answer: Yadda's `merge` drops converters.\n */\nconst extended = new WeakMap<Yadda.Dictionary, Map<string, string>>()\n\n/**\n * A Yadda dictionary with a `$page` term built from a page registry — the same\n * names and pattern as `{page}`, so the two cannot drift. A step reading\n * `I open the $page page` matches only a registered name (longest first), and a\n * feature naming an unknown page fails as an undefined step, where the typo is.\n *\n * Pass `dictionary` to add the term to one that already defines others, and\n * `term` to call it something else. Adding the same term to the same dictionary\n * again returns it unchanged, so it is safe inside a per-scenario steps function.\n */\nexport function pageDictionary<P extends PageMap>(\n registry: PageRegistry<P>,\n options: { term?: string; dictionary?: Yadda.Dictionary } = {},\n): Yadda.Dictionary {\n const { regexp, transformer } = pageParameter(registry)\n const term = options.term ?? 'page'\n const dictionary = options.dictionary ?? new Yadda.Dictionary()\n const pattern = `(${regexp.source})`\n const terms = extended.get(dictionary) ?? new Map<string, string>()\n extended.set(dictionary, terms)\n const existing = terms.get(term)\n if (existing === pattern) return dictionary\n if (existing !== undefined) {\n throw new Error(\n `harnessed: this dictionary already defines $${term} with other pages. ` +\n 'Give the second registry its own term.',\n )\n }\n // A callback converter, not an async one: Yadda tells an async converter by\n // its constructor's name, which a transpiler that rewrites async functions (an\n // Ember build's Babel) changes. A callback converter's captures are its arity\n // less the callback, so this names exactly two parameters and checks them;\n // they are optional, and the rest parameter there, only because Yadda types a\n // callback converter as any number of captures followed by the callback.\n const convert = (\n match?: string | Yadda.ConverterCallback,\n next?: string | Yadda.ConverterCallback,\n ..._unused: Array<string | Yadda.ConverterCallback>\n ): void => {\n if (typeof match !== 'string' || typeof next !== 'function') {\n throw new Error(`harnessed: $${term} expects one capture and Yadda's callback.`)\n }\n let page: keyof P & string\n try {\n page = transformer(match)\n } catch (error) {\n next(error)\n return\n }\n next(null, page)\n }\n dictionary.define(term, pattern, convert)\n terms.set(term, pattern)\n return dictionary\n}\n\n/** What a harnessed Yadda step receives first. */\nexport interface YaddaScenario<W, P extends PageMap> {\n /** The scenario's bag: empty when the scenario starts, shared by its steps. */\n readonly world: Partial<W>\n /** Constructs a registered page with the scenario's env. */\n open<N extends keyof P & string>(name: N): InstanceType<P[N]>\n /**\n * Yadda's own context for the step: `step` (the line being run) and whatever\n * the runner passed in — ember-cli-yadda's `ctx`, and the QUnit test context.\n */\n readonly context: Readonly<Record<string, unknown>>\n}\n\n/** A step: the scenario first, then the values its signature captured. */\nexport type YaddaStep<W, P extends PageMap, A extends unknown[]> = (\n scenario: YaddaScenario<W, P>,\n ...args: A\n) => unknown\n\nexport interface YaddaStepsOptions<P extends PageMap> {\n /** The pages features can name. Defines `$page` unless `dictionary` is given. */\n pages: PageRegistry<P>\n /**\n * The env pages are built with. Called each time a step opens a page, so it\n * can read per-test state — `() => ember()` in an application test.\n */\n env: () => EnvConfig\n /** The dictionary signatures expand against. Defaults to `pageDictionary(pages)`. */\n dictionary?: Yadda.Dictionary\n /** The step keywords' language. Defaults to English. */\n language?: Yadda.Language\n}\n\n/** A step library whose steps share one world. */\nexport interface YaddaSteps<W, P extends PageMap> {\n /** The world this library's steps share. */\n readonly world: Partial<W>\n /** The Yadda library to run: what an ember-cli-yadda steps module returns. */\n readonly library: Yadda.BaseLibrary\n given<A extends unknown[]>(signature: Yadda.Signature, step: YaddaStep<W, P, A>): this\n when<A extends unknown[]>(signature: Yadda.Signature, step: YaddaStep<W, P, A>): this\n then<A extends unknown[]>(signature: Yadda.Signature, step: YaddaStep<W, P, A>): this\n /** A step that matches after any keyword: `Given`, `When`, `Then`, `And`, `But`. */\n step<A extends unknown[]>(signature: Yadda.Signature, step: YaddaStep<W, P, A>): this\n}\n\n/** Fixes the world's type; `steps` then infers the pages from the registry. */\nexport interface YaddaWorld<W> {\n /** A fresh step library with a fresh, empty world. Build one per scenario. */\n steps<P extends PageMap>(options: YaddaStepsOptions<P>): YaddaSteps<W, P>\n}\n\n/**\n * Yadda steps with a scenario-scoped world and the page registry. Steps are\n * plain (arrow) functions taking the scenario first, then what their signature\n * captured; every step is awaited.\n *\n * The world belongs to the library `steps()` builds, so build one per scenario —\n * which is what ember-cli-yadda already does: it calls a steps module's default\n * export inside each scenario's test.\n *\n * ```ts\n * // tests/acceptance/steps/checkout-steps.ts\n * const pages = definePages({ checkout: CheckoutPage })\n * const checkout = yaddaWorld<{ checkout: CheckoutPage }>()\n *\n * export default function (assert: Assert) {\n * const steps = checkout.steps({ pages, env: () => ember() })\n * steps.when('I open the $page page', async ({ world, open }, name: 'checkout') => {\n * world.checkout = open(name)\n * await world.checkout.goto()\n * })\n * return steps.library\n * }\n * ```\n *\n * The world's type is a call of its own because TypeScript cannot take one type\n * argument explicitly and infer another; `steps` infers the pages.\n */\nexport function yaddaWorld<W = Record<string, unknown>>(): YaddaWorld<W> {\n return {\n steps<P extends PageMap>(options: YaddaStepsOptions<P>) {\n return createSteps<W, P>(options)\n },\n }\n}\n\nfunction createSteps<W, P extends PageMap>(options: YaddaStepsOptions<P>): YaddaSteps<W, P> {\n const { pages, env } = options\n const language = options.language ?? Yadda.localisation.English\n const library = language.localise(\n new Yadda.ContextParamLibrary(options.dictionary ?? pageDictionary(pages)),\n )\n const world = createWorld<W>()\n const open = <N extends keyof P & string>(name: N): InstanceType<P[N]> => pages.open(name, env())\n\n // Yadda passes the step's context first and, in promise mode, its completion\n // callback last; a harnessed step sees neither, only the scenario and the\n // captured values. Promise mode is explicit so Yadda never guesses from the\n // wrapper's arity.\n const wrap =\n <A extends unknown[]>(step: YaddaStep<W, P, A>) =>\n async (context: Record<string, unknown>, ...rest: unknown[]): Promise<void> => {\n await step({ world, open, context }, ...(rest.slice(0, -1) as A))\n }\n const promise: Yadda.DefineOptions = { mode: 'promise' }\n\n // Every step keyword the language has, as one alternation: `the world is\n // empty` is the same step after `Given` as after `Then`.\n const anyKeyword = `(?:${['given', 'when', 'then'].map(keyword => language.translate(keyword)).join('|')})`\n\n return {\n world,\n library,\n given(signature, step) {\n library.given(signature, wrap(step), undefined, promise)\n return this\n },\n when(signature, step) {\n library.when(signature, wrap(step), undefined, promise)\n return this\n },\n then(signature, step) {\n library.then(signature, wrap(step), undefined, promise)\n return this\n },\n step(signature, step) {\n const signatures = Array.isArray(signature) ? signature : [signature]\n library.define(\n signatures.map(one => withKeyword(anyKeyword, one)),\n wrap(step),\n undefined,\n promise,\n )\n return this\n },\n }\n}\n\n/** Prefixes a signature with a keyword pattern, the way a Yadda language does. */\nfunction withKeyword(keyword: string, signature: string | RegExp): string {\n const source = signature instanceof RegExp ? signature.source : signature\n return `^(?:\\\\s)*${keyword}\\\\s+${source.replace(/^\\^/, '')}`\n}\n"]}
|
package/dist/yadda.d.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { EnvConfig } from '@harnessed-ts/core';
|
|
2
|
+
import Yadda from 'yadda';
|
|
3
|
+
import type { PageMap, PageRegistry } from './index';
|
|
4
|
+
/**
|
|
5
|
+
* A Yadda dictionary with a `$page` term built from a page registry — the same
|
|
6
|
+
* names and pattern as `{page}`, so the two cannot drift. A step reading
|
|
7
|
+
* `I open the $page page` matches only a registered name (longest first), and a
|
|
8
|
+
* feature naming an unknown page fails as an undefined step, where the typo is.
|
|
9
|
+
*
|
|
10
|
+
* Pass `dictionary` to add the term to one that already defines others, and
|
|
11
|
+
* `term` to call it something else. Adding the same term to the same dictionary
|
|
12
|
+
* again returns it unchanged, so it is safe inside a per-scenario steps function.
|
|
13
|
+
*/
|
|
14
|
+
export declare function pageDictionary<P extends PageMap>(registry: PageRegistry<P>, options?: {
|
|
15
|
+
term?: string;
|
|
16
|
+
dictionary?: Yadda.Dictionary;
|
|
17
|
+
}): Yadda.Dictionary;
|
|
18
|
+
/** What a harnessed Yadda step receives first. */
|
|
19
|
+
export interface YaddaScenario<W, P extends PageMap> {
|
|
20
|
+
/** The scenario's bag: empty when the scenario starts, shared by its steps. */
|
|
21
|
+
readonly world: Partial<W>;
|
|
22
|
+
/** Constructs a registered page with the scenario's env. */
|
|
23
|
+
open<N extends keyof P & string>(name: N): InstanceType<P[N]>;
|
|
24
|
+
/**
|
|
25
|
+
* Yadda's own context for the step: `step` (the line being run) and whatever
|
|
26
|
+
* the runner passed in — ember-cli-yadda's `ctx`, and the QUnit test context.
|
|
27
|
+
*/
|
|
28
|
+
readonly context: Readonly<Record<string, unknown>>;
|
|
29
|
+
}
|
|
30
|
+
/** A step: the scenario first, then the values its signature captured. */
|
|
31
|
+
export type YaddaStep<W, P extends PageMap, A extends unknown[]> = (scenario: YaddaScenario<W, P>, ...args: A) => unknown;
|
|
32
|
+
export interface YaddaStepsOptions<P extends PageMap> {
|
|
33
|
+
/** The pages features can name. Defines `$page` unless `dictionary` is given. */
|
|
34
|
+
pages: PageRegistry<P>;
|
|
35
|
+
/**
|
|
36
|
+
* The env pages are built with. Called each time a step opens a page, so it
|
|
37
|
+
* can read per-test state — `() => ember()` in an application test.
|
|
38
|
+
*/
|
|
39
|
+
env: () => EnvConfig;
|
|
40
|
+
/** The dictionary signatures expand against. Defaults to `pageDictionary(pages)`. */
|
|
41
|
+
dictionary?: Yadda.Dictionary;
|
|
42
|
+
/** The step keywords' language. Defaults to English. */
|
|
43
|
+
language?: Yadda.Language;
|
|
44
|
+
}
|
|
45
|
+
/** A step library whose steps share one world. */
|
|
46
|
+
export interface YaddaSteps<W, P extends PageMap> {
|
|
47
|
+
/** The world this library's steps share. */
|
|
48
|
+
readonly world: Partial<W>;
|
|
49
|
+
/** The Yadda library to run: what an ember-cli-yadda steps module returns. */
|
|
50
|
+
readonly library: Yadda.BaseLibrary;
|
|
51
|
+
given<A extends unknown[]>(signature: Yadda.Signature, step: YaddaStep<W, P, A>): this;
|
|
52
|
+
when<A extends unknown[]>(signature: Yadda.Signature, step: YaddaStep<W, P, A>): this;
|
|
53
|
+
then<A extends unknown[]>(signature: Yadda.Signature, step: YaddaStep<W, P, A>): this;
|
|
54
|
+
/** A step that matches after any keyword: `Given`, `When`, `Then`, `And`, `But`. */
|
|
55
|
+
step<A extends unknown[]>(signature: Yadda.Signature, step: YaddaStep<W, P, A>): this;
|
|
56
|
+
}
|
|
57
|
+
/** Fixes the world's type; `steps` then infers the pages from the registry. */
|
|
58
|
+
export interface YaddaWorld<W> {
|
|
59
|
+
/** A fresh step library with a fresh, empty world. Build one per scenario. */
|
|
60
|
+
steps<P extends PageMap>(options: YaddaStepsOptions<P>): YaddaSteps<W, P>;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Yadda steps with a scenario-scoped world and the page registry. Steps are
|
|
64
|
+
* plain (arrow) functions taking the scenario first, then what their signature
|
|
65
|
+
* captured; every step is awaited.
|
|
66
|
+
*
|
|
67
|
+
* The world belongs to the library `steps()` builds, so build one per scenario —
|
|
68
|
+
* which is what ember-cli-yadda already does: it calls a steps module's default
|
|
69
|
+
* export inside each scenario's test.
|
|
70
|
+
*
|
|
71
|
+
* ```ts
|
|
72
|
+
* // tests/acceptance/steps/checkout-steps.ts
|
|
73
|
+
* const pages = definePages({ checkout: CheckoutPage })
|
|
74
|
+
* const checkout = yaddaWorld<{ checkout: CheckoutPage }>()
|
|
75
|
+
*
|
|
76
|
+
* export default function (assert: Assert) {
|
|
77
|
+
* const steps = checkout.steps({ pages, env: () => ember() })
|
|
78
|
+
* steps.when('I open the $page page', async ({ world, open }, name: 'checkout') => {
|
|
79
|
+
* world.checkout = open(name)
|
|
80
|
+
* await world.checkout.goto()
|
|
81
|
+
* })
|
|
82
|
+
* return steps.library
|
|
83
|
+
* }
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* The world's type is a call of its own because TypeScript cannot take one type
|
|
87
|
+
* argument explicitly and infer another; `steps` infers the pages.
|
|
88
|
+
*/
|
|
89
|
+
export declare function yaddaWorld<W = Record<string, unknown>>(): YaddaWorld<W>;
|
|
90
|
+
//# sourceMappingURL=yadda.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"yadda.d.ts","sourceRoot":"","sources":["../src/yadda.ts"],"names":[],"mappings":"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"}
|
package/dist/yadda.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { pageParameter, createWorld } from './chunk-6AIO5EWN.js';
|
|
2
|
+
import Yadda from 'yadda';
|
|
3
|
+
|
|
4
|
+
var extended = /* @__PURE__ */ new WeakMap();
|
|
5
|
+
function pageDictionary(registry, options = {}) {
|
|
6
|
+
const { regexp, transformer } = pageParameter(registry);
|
|
7
|
+
const term = options.term ?? "page";
|
|
8
|
+
const dictionary = options.dictionary ?? new Yadda.Dictionary();
|
|
9
|
+
const pattern = `(${regexp.source})`;
|
|
10
|
+
const terms = extended.get(dictionary) ?? /* @__PURE__ */ new Map();
|
|
11
|
+
extended.set(dictionary, terms);
|
|
12
|
+
const existing = terms.get(term);
|
|
13
|
+
if (existing === pattern) return dictionary;
|
|
14
|
+
if (existing !== void 0) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
`harnessed: this dictionary already defines $${term} with other pages. Give the second registry its own term.`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
const convert = (match, next, ..._unused) => {
|
|
20
|
+
if (typeof match !== "string" || typeof next !== "function") {
|
|
21
|
+
throw new Error(`harnessed: $${term} expects one capture and Yadda's callback.`);
|
|
22
|
+
}
|
|
23
|
+
let page;
|
|
24
|
+
try {
|
|
25
|
+
page = transformer(match);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
next(error);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
next(null, page);
|
|
31
|
+
};
|
|
32
|
+
dictionary.define(term, pattern, convert);
|
|
33
|
+
terms.set(term, pattern);
|
|
34
|
+
return dictionary;
|
|
35
|
+
}
|
|
36
|
+
function yaddaWorld() {
|
|
37
|
+
return {
|
|
38
|
+
steps(options) {
|
|
39
|
+
return createSteps(options);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function createSteps(options) {
|
|
44
|
+
const { pages, env } = options;
|
|
45
|
+
const language = options.language ?? Yadda.localisation.English;
|
|
46
|
+
const library = language.localise(
|
|
47
|
+
new Yadda.ContextParamLibrary(options.dictionary ?? pageDictionary(pages))
|
|
48
|
+
);
|
|
49
|
+
const world = createWorld();
|
|
50
|
+
const open = (name) => pages.open(name, env());
|
|
51
|
+
const wrap = (step) => async (context, ...rest) => {
|
|
52
|
+
await step({ world, open, context }, ...rest.slice(0, -1));
|
|
53
|
+
};
|
|
54
|
+
const promise = { mode: "promise" };
|
|
55
|
+
const anyKeyword = `(?:${["given", "when", "then"].map((keyword) => language.translate(keyword)).join("|")})`;
|
|
56
|
+
return {
|
|
57
|
+
world,
|
|
58
|
+
library,
|
|
59
|
+
given(signature, step) {
|
|
60
|
+
library.given(signature, wrap(step), void 0, promise);
|
|
61
|
+
return this;
|
|
62
|
+
},
|
|
63
|
+
when(signature, step) {
|
|
64
|
+
library.when(signature, wrap(step), void 0, promise);
|
|
65
|
+
return this;
|
|
66
|
+
},
|
|
67
|
+
then(signature, step) {
|
|
68
|
+
library.then(signature, wrap(step), void 0, promise);
|
|
69
|
+
return this;
|
|
70
|
+
},
|
|
71
|
+
step(signature, step) {
|
|
72
|
+
const signatures = Array.isArray(signature) ? signature : [signature];
|
|
73
|
+
library.define(
|
|
74
|
+
signatures.map((one) => withKeyword(anyKeyword, one)),
|
|
75
|
+
wrap(step),
|
|
76
|
+
void 0,
|
|
77
|
+
promise
|
|
78
|
+
);
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function withKeyword(keyword, signature) {
|
|
84
|
+
const source = signature instanceof RegExp ? signature.source : signature;
|
|
85
|
+
return `^(?:\\s)*${keyword}\\s+${source.replace(/^\^/, "")}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export { pageDictionary, yaddaWorld };
|
|
89
|
+
//# sourceMappingURL=yadda.js.map
|
|
90
|
+
//# sourceMappingURL=yadda.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/yadda.ts"],"names":[],"mappings":"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","file":"yadda.js","sourcesContent":["import type { EnvConfig } from '@harnessed-ts/core'\nimport Yadda from 'yadda'\nimport { createWorld, pageParameter } from './index'\nimport type { PageMap, PageRegistry } from './index'\n\n/**\n * The page terms each dictionary was given, and the pattern each one matches.\n * Yadda refuses to define a term twice, and ember-cli-yadda calls a steps\n * module's default export once per scenario, so extending a module-level\n * dictionary there would throw from the second scenario on. Copying it instead\n * is no answer: Yadda's `merge` drops converters.\n */\nconst extended = new WeakMap<Yadda.Dictionary, Map<string, string>>()\n\n/**\n * A Yadda dictionary with a `$page` term built from a page registry — the same\n * names and pattern as `{page}`, so the two cannot drift. A step reading\n * `I open the $page page` matches only a registered name (longest first), and a\n * feature naming an unknown page fails as an undefined step, where the typo is.\n *\n * Pass `dictionary` to add the term to one that already defines others, and\n * `term` to call it something else. Adding the same term to the same dictionary\n * again returns it unchanged, so it is safe inside a per-scenario steps function.\n */\nexport function pageDictionary<P extends PageMap>(\n registry: PageRegistry<P>,\n options: { term?: string; dictionary?: Yadda.Dictionary } = {},\n): Yadda.Dictionary {\n const { regexp, transformer } = pageParameter(registry)\n const term = options.term ?? 'page'\n const dictionary = options.dictionary ?? new Yadda.Dictionary()\n const pattern = `(${regexp.source})`\n const terms = extended.get(dictionary) ?? new Map<string, string>()\n extended.set(dictionary, terms)\n const existing = terms.get(term)\n if (existing === pattern) return dictionary\n if (existing !== undefined) {\n throw new Error(\n `harnessed: this dictionary already defines $${term} with other pages. ` +\n 'Give the second registry its own term.',\n )\n }\n // A callback converter, not an async one: Yadda tells an async converter by\n // its constructor's name, which a transpiler that rewrites async functions (an\n // Ember build's Babel) changes. A callback converter's captures are its arity\n // less the callback, so this names exactly two parameters and checks them;\n // they are optional, and the rest parameter there, only because Yadda types a\n // callback converter as any number of captures followed by the callback.\n const convert = (\n match?: string | Yadda.ConverterCallback,\n next?: string | Yadda.ConverterCallback,\n ..._unused: Array<string | Yadda.ConverterCallback>\n ): void => {\n if (typeof match !== 'string' || typeof next !== 'function') {\n throw new Error(`harnessed: $${term} expects one capture and Yadda's callback.`)\n }\n let page: keyof P & string\n try {\n page = transformer(match)\n } catch (error) {\n next(error)\n return\n }\n next(null, page)\n }\n dictionary.define(term, pattern, convert)\n terms.set(term, pattern)\n return dictionary\n}\n\n/** What a harnessed Yadda step receives first. */\nexport interface YaddaScenario<W, P extends PageMap> {\n /** The scenario's bag: empty when the scenario starts, shared by its steps. */\n readonly world: Partial<W>\n /** Constructs a registered page with the scenario's env. */\n open<N extends keyof P & string>(name: N): InstanceType<P[N]>\n /**\n * Yadda's own context for the step: `step` (the line being run) and whatever\n * the runner passed in — ember-cli-yadda's `ctx`, and the QUnit test context.\n */\n readonly context: Readonly<Record<string, unknown>>\n}\n\n/** A step: the scenario first, then the values its signature captured. */\nexport type YaddaStep<W, P extends PageMap, A extends unknown[]> = (\n scenario: YaddaScenario<W, P>,\n ...args: A\n) => unknown\n\nexport interface YaddaStepsOptions<P extends PageMap> {\n /** The pages features can name. Defines `$page` unless `dictionary` is given. */\n pages: PageRegistry<P>\n /**\n * The env pages are built with. Called each time a step opens a page, so it\n * can read per-test state — `() => ember()` in an application test.\n */\n env: () => EnvConfig\n /** The dictionary signatures expand against. Defaults to `pageDictionary(pages)`. */\n dictionary?: Yadda.Dictionary\n /** The step keywords' language. Defaults to English. */\n language?: Yadda.Language\n}\n\n/** A step library whose steps share one world. */\nexport interface YaddaSteps<W, P extends PageMap> {\n /** The world this library's steps share. */\n readonly world: Partial<W>\n /** The Yadda library to run: what an ember-cli-yadda steps module returns. */\n readonly library: Yadda.BaseLibrary\n given<A extends unknown[]>(signature: Yadda.Signature, step: YaddaStep<W, P, A>): this\n when<A extends unknown[]>(signature: Yadda.Signature, step: YaddaStep<W, P, A>): this\n then<A extends unknown[]>(signature: Yadda.Signature, step: YaddaStep<W, P, A>): this\n /** A step that matches after any keyword: `Given`, `When`, `Then`, `And`, `But`. */\n step<A extends unknown[]>(signature: Yadda.Signature, step: YaddaStep<W, P, A>): this\n}\n\n/** Fixes the world's type; `steps` then infers the pages from the registry. */\nexport interface YaddaWorld<W> {\n /** A fresh step library with a fresh, empty world. Build one per scenario. */\n steps<P extends PageMap>(options: YaddaStepsOptions<P>): YaddaSteps<W, P>\n}\n\n/**\n * Yadda steps with a scenario-scoped world and the page registry. Steps are\n * plain (arrow) functions taking the scenario first, then what their signature\n * captured; every step is awaited.\n *\n * The world belongs to the library `steps()` builds, so build one per scenario —\n * which is what ember-cli-yadda already does: it calls a steps module's default\n * export inside each scenario's test.\n *\n * ```ts\n * // tests/acceptance/steps/checkout-steps.ts\n * const pages = definePages({ checkout: CheckoutPage })\n * const checkout = yaddaWorld<{ checkout: CheckoutPage }>()\n *\n * export default function (assert: Assert) {\n * const steps = checkout.steps({ pages, env: () => ember() })\n * steps.when('I open the $page page', async ({ world, open }, name: 'checkout') => {\n * world.checkout = open(name)\n * await world.checkout.goto()\n * })\n * return steps.library\n * }\n * ```\n *\n * The world's type is a call of its own because TypeScript cannot take one type\n * argument explicitly and infer another; `steps` infers the pages.\n */\nexport function yaddaWorld<W = Record<string, unknown>>(): YaddaWorld<W> {\n return {\n steps<P extends PageMap>(options: YaddaStepsOptions<P>) {\n return createSteps<W, P>(options)\n },\n }\n}\n\nfunction createSteps<W, P extends PageMap>(options: YaddaStepsOptions<P>): YaddaSteps<W, P> {\n const { pages, env } = options\n const language = options.language ?? Yadda.localisation.English\n const library = language.localise(\n new Yadda.ContextParamLibrary(options.dictionary ?? pageDictionary(pages)),\n )\n const world = createWorld<W>()\n const open = <N extends keyof P & string>(name: N): InstanceType<P[N]> => pages.open(name, env())\n\n // Yadda passes the step's context first and, in promise mode, its completion\n // callback last; a harnessed step sees neither, only the scenario and the\n // captured values. Promise mode is explicit so Yadda never guesses from the\n // wrapper's arity.\n const wrap =\n <A extends unknown[]>(step: YaddaStep<W, P, A>) =>\n async (context: Record<string, unknown>, ...rest: unknown[]): Promise<void> => {\n await step({ world, open, context }, ...(rest.slice(0, -1) as A))\n }\n const promise: Yadda.DefineOptions = { mode: 'promise' }\n\n // Every step keyword the language has, as one alternation: `the world is\n // empty` is the same step after `Given` as after `Then`.\n const anyKeyword = `(?:${['given', 'when', 'then'].map(keyword => language.translate(keyword)).join('|')})`\n\n return {\n world,\n library,\n given(signature, step) {\n library.given(signature, wrap(step), undefined, promise)\n return this\n },\n when(signature, step) {\n library.when(signature, wrap(step), undefined, promise)\n return this\n },\n then(signature, step) {\n library.then(signature, wrap(step), undefined, promise)\n return this\n },\n step(signature, step) {\n const signatures = Array.isArray(signature) ? signature : [signature]\n library.define(\n signatures.map(one => withKeyword(anyKeyword, one)),\n wrap(step),\n undefined,\n promise,\n )\n return this\n },\n }\n}\n\n/** Prefixes a signature with a keyword pattern, the way a Yadda language does. */\nfunction withKeyword(keyword: string, signature: string | RegExp): string {\n const source = signature instanceof RegExp ? signature.source : signature\n return `^(?:\\\\s)*${keyword}\\\\s+${source.replace(/^\\^/, '')}`\n}\n"]}
|