Version not found. Please check the version and try again.

effect-bdd 0.1.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.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +465 -0
  3. package/dist/Bdd.d.ts +285 -0
  4. package/dist/Bdd.d.ts.map +1 -0
  5. package/dist/Bdd.js +304 -0
  6. package/dist/Bdd.js.map +1 -0
  7. package/dist/Errors.d.ts +65 -0
  8. package/dist/Errors.d.ts.map +1 -0
  9. package/dist/Errors.js +58 -0
  10. package/dist/Errors.js.map +1 -0
  11. package/dist/bin.d.ts +3 -0
  12. package/dist/bin.d.ts.map +1 -0
  13. package/dist/bin.js +7 -0
  14. package/dist/bin.js.map +1 -0
  15. package/dist/index.d.ts +147 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +31 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/internal/cli/errors.d.ts +30 -0
  20. package/dist/internal/cli/errors.d.ts.map +1 -0
  21. package/dist/internal/cli/errors.js +8 -0
  22. package/dist/internal/cli/errors.js.map +1 -0
  23. package/dist/internal/cli/glob.d.ts +13 -0
  24. package/dist/internal/cli/glob.d.ts.map +1 -0
  25. package/dist/internal/cli/glob.js +29 -0
  26. package/dist/internal/cli/glob.js.map +1 -0
  27. package/dist/internal/cli/loaders.d.ts +13 -0
  28. package/dist/internal/cli/loaders.d.ts.map +1 -0
  29. package/dist/internal/cli/loaders.js +44 -0
  30. package/dist/internal/cli/loaders.js.map +1 -0
  31. package/dist/internal/cli/models.d.ts +102 -0
  32. package/dist/internal/cli/models.d.ts.map +1 -0
  33. package/dist/internal/cli/models.js +2 -0
  34. package/dist/internal/cli/models.js.map +1 -0
  35. package/dist/internal/cli/moduleLoader.d.ts +14 -0
  36. package/dist/internal/cli/moduleLoader.d.ts.map +1 -0
  37. package/dist/internal/cli/moduleLoader.js +39 -0
  38. package/dist/internal/cli/moduleLoader.js.map +1 -0
  39. package/dist/internal/cli/reporter.d.ts +22 -0
  40. package/dist/internal/cli/reporter.d.ts.map +1 -0
  41. package/dist/internal/cli/reporter.js +227 -0
  42. package/dist/internal/cli/reporter.js.map +1 -0
  43. package/dist/internal/cli/runner.d.ts +14 -0
  44. package/dist/internal/cli/runner.d.ts.map +1 -0
  45. package/dist/internal/cli/runner.js +178 -0
  46. package/dist/internal/cli/runner.js.map +1 -0
  47. package/dist/internal/cli/tagExpression.d.ts +7 -0
  48. package/dist/internal/cli/tagExpression.d.ts.map +1 -0
  49. package/dist/internal/cli/tagExpression.js +127 -0
  50. package/dist/internal/cli/tagExpression.js.map +1 -0
  51. package/dist/internal/cucumberCompiler.d.ts +5 -0
  52. package/dist/internal/cucumberCompiler.d.ts.map +1 -0
  53. package/dist/internal/cucumberCompiler.js +49 -0
  54. package/dist/internal/cucumberCompiler.js.map +1 -0
  55. package/dist/internal/expression.d.ts +18 -0
  56. package/dist/internal/expression.d.ts.map +1 -0
  57. package/dist/internal/expression.js +59 -0
  58. package/dist/internal/expression.js.map +1 -0
  59. package/dist/internal/matching.d.ts +30 -0
  60. package/dist/internal/matching.d.ts.map +1 -0
  61. package/dist/internal/matching.js +37 -0
  62. package/dist/internal/matching.js.map +1 -0
  63. package/dist/internal/parser.d.ts +54 -0
  64. package/dist/internal/parser.d.ts.map +1 -0
  65. package/dist/internal/parser.js +93 -0
  66. package/dist/internal/parser.js.map +1 -0
  67. package/dist/internal/runner.d.ts +77 -0
  68. package/dist/internal/runner.d.ts.map +1 -0
  69. package/dist/internal/runner.js +117 -0
  70. package/dist/internal/runner.js.map +1 -0
  71. package/dist/main.d.ts +23 -0
  72. package/dist/main.d.ts.map +1 -0
  73. package/dist/main.js +104 -0
  74. package/dist/main.js.map +1 -0
  75. package/package.json +102 -0
  76. package/src/Bdd.ts +575 -0
  77. package/src/Errors.ts +60 -0
  78. package/src/bin.ts +10 -0
  79. package/src/index.ts +155 -0
  80. package/src/internal/cli/errors.ts +20 -0
  81. package/src/internal/cli/glob.ts +37 -0
  82. package/src/internal/cli/loaders.ts +100 -0
  83. package/src/internal/cli/models.ts +118 -0
  84. package/src/internal/cli/moduleLoader.ts +41 -0
  85. package/src/internal/cli/reporter.ts +367 -0
  86. package/src/internal/cli/runner.ts +336 -0
  87. package/src/internal/cli/tagExpression.ts +173 -0
  88. package/src/internal/cucumberCompiler.ts +58 -0
  89. package/src/internal/expression.ts +103 -0
  90. package/src/internal/matching.ts +81 -0
  91. package/src/internal/parser.ts +155 -0
  92. package/src/internal/runner.ts +373 -0
  93. package/src/main.ts +169 -0
package/src/index.ts ADDED
@@ -0,0 +1,155 @@
1
+ import { Bdd as bdd } from "./Bdd.ts"
2
+ import type {
3
+ Capture as Capture_,
4
+ DocStringArg as DocStringArg_,
5
+ Feature as Feature_,
6
+ GherkinCompiler as GherkinCompiler_,
7
+ Report as Report_,
8
+ RunError as RunError_,
9
+ TableArg as TableArg_
10
+ } from "./Bdd.ts"
11
+ import { MatchError as matchError, ParseError as parseError, StepError as stepError } from "./Errors.ts"
12
+
13
+ /**
14
+ * Namespace-style API for building and running BDD feature definitions.
15
+ *
16
+ * @category re-exports
17
+ * @since 4.0.0
18
+ */
19
+ export const Bdd = bdd
20
+
21
+ /**
22
+ * Type helpers for the {@link Bdd} value namespace.
23
+ *
24
+ * @since 4.0.0
25
+ */
26
+ export declare namespace Bdd {
27
+ /**
28
+ * A local immutable feature definition used to interpret scenarios from Gherkin source.
29
+ *
30
+ * @since 4.0.0
31
+ */
32
+ export type Feature<State, E = never, R = never> = Feature_<State, E, R>
33
+
34
+ /**
35
+ * Result returned after all scenarios pass.
36
+ *
37
+ * @since 4.0.0
38
+ */
39
+ export type Report = Report_
40
+
41
+ /**
42
+ * Error type returned by `Bdd.run`.
43
+ *
44
+ * @since 4.0.0
45
+ */
46
+ export type RunError = RunError_
47
+
48
+ /**
49
+ * Service used to compile Gherkin source into executable scenarios.
50
+ *
51
+ * @since 4.0.0
52
+ */
53
+ export type GherkinCompiler = GherkinCompiler_
54
+
55
+ /**
56
+ * A named capture decoded from step text with a Schema.
57
+ *
58
+ * @since 4.0.0
59
+ */
60
+ export type Capture<Name extends string, A> = Capture_<Name, A>
61
+
62
+ /**
63
+ * A decoded DataTable argument.
64
+ *
65
+ * @since 4.0.0
66
+ */
67
+ export type TableArg<A> = TableArg_<A>
68
+
69
+ /**
70
+ * A decoded DocString argument.
71
+ *
72
+ * @since 4.0.0
73
+ */
74
+ export type DocStringArg<A> = DocStringArg_<A>
75
+ }
76
+
77
+ /**
78
+ * Error raised when a Gherkin step cannot be matched or decoded.
79
+ *
80
+ * @category re-exports
81
+ * @since 4.0.0
82
+ */
83
+ export const MatchError = matchError
84
+
85
+ /**
86
+ * Error raised when Gherkin source cannot be parsed.
87
+ *
88
+ * @category re-exports
89
+ * @since 4.0.0
90
+ */
91
+ export const ParseError = parseError
92
+
93
+ /**
94
+ * Error raised when a matched step implementation fails.
95
+ *
96
+ * @category re-exports
97
+ * @since 4.0.0
98
+ */
99
+ export const StepError = stepError
100
+
101
+ /**
102
+ * A named capture decoded from step text with a Schema.
103
+ *
104
+ * @category re-exports
105
+ * @since 4.0.0
106
+ */
107
+ export type Capture<Name extends string, A> = Capture_<Name, A>
108
+
109
+ /**
110
+ * A decoded DocString argument.
111
+ *
112
+ * @category re-exports
113
+ * @since 4.0.0
114
+ */
115
+ export type DocStringArg<A> = DocStringArg_<A>
116
+
117
+ /**
118
+ * A local immutable feature definition used to interpret scenarios from Gherkin source.
119
+ *
120
+ * @category re-exports
121
+ * @since 4.0.0
122
+ */
123
+ export type Feature<State, E = never, R = never> = Feature_<State, E, R>
124
+
125
+ /**
126
+ * Result returned after all scenarios pass.
127
+ *
128
+ * @category re-exports
129
+ * @since 4.0.0
130
+ */
131
+ export type Report = Report_
132
+
133
+ /**
134
+ * Error type returned by `Bdd.run`.
135
+ *
136
+ * @category re-exports
137
+ * @since 4.0.0
138
+ */
139
+ export type RunError = RunError_
140
+
141
+ /**
142
+ * Service used to compile Gherkin source into executable scenarios.
143
+ *
144
+ * @category re-exports
145
+ * @since 4.0.0
146
+ */
147
+ export type GherkinCompiler = GherkinCompiler_
148
+
149
+ /**
150
+ * A decoded DataTable argument.
151
+ *
152
+ * @category re-exports
153
+ * @since 4.0.0
154
+ */
155
+ export type TableArg<A> = TableArg_<A>
@@ -0,0 +1,20 @@
1
+ import * as Data from "effect/Data"
2
+
3
+ /** @internal */
4
+ export class DiscoveryError extends Data.TaggedError("DiscoveryError")<{
5
+ readonly message: string
6
+ readonly cause?: unknown
7
+ }> {}
8
+
9
+ /** @internal */
10
+ export class ModuleLoadError extends Data.TaggedError("ModuleLoadError")<{
11
+ readonly path: string
12
+ readonly message: string
13
+ readonly cause?: unknown
14
+ }> {}
15
+
16
+ /** @internal */
17
+ export class ReporterError extends Data.TaggedError("ReporterError")<{
18
+ readonly message: string
19
+ readonly cause?: unknown
20
+ }> {}
@@ -0,0 +1,37 @@
1
+ import * as Arr from "effect/Array"
2
+ import * as Context from "effect/Context"
3
+ import * as Effect from "effect/Effect"
4
+ import { pipe } from "effect/Function"
5
+ import * as Layer from "effect/Layer"
6
+ import * as Order from "effect/Order"
7
+ import { glob } from "glob"
8
+ import { DiscoveryError } from "./errors.ts"
9
+
10
+ const resolve = Effect.fnUntraced(function*(patterns: ReadonlyArray<string>) {
11
+ const paths = yield* Effect.forEach(patterns, resolvePattern)
12
+ return pipe(
13
+ paths,
14
+ Arr.flatten,
15
+ Arr.dedupe,
16
+ Arr.sort(Order.String)
17
+ )
18
+ })
19
+
20
+ /** @internal */
21
+ export class GlobResolver extends Context.Service<GlobResolver, {
22
+ readonly resolve: (patterns: ReadonlyArray<string>) => Effect.Effect<ReadonlyArray<string>, DiscoveryError>
23
+ }>()("effect-bdd/cli/GlobResolver") {
24
+ static readonly Live = Layer.succeed(GlobResolver, {
25
+ resolve
26
+ })
27
+ }
28
+
29
+ const resolvePattern = (pattern: string): Effect.Effect<ReadonlyArray<string>, DiscoveryError> =>
30
+ Effect.tryPromise({
31
+ try: () => glob(pattern, { absolute: true, nodir: true }),
32
+ catch: (cause) =>
33
+ new DiscoveryError({
34
+ message: `Could not resolve glob pattern "${pattern}"`,
35
+ cause
36
+ })
37
+ })
@@ -0,0 +1,100 @@
1
+ import * as Arr from "effect/Array"
2
+ import * as Effect from "effect/Effect"
3
+ import * as FileSystem from "effect/FileSystem"
4
+ import { pipe } from "effect/Function"
5
+ import type * as Path from "effect/Path"
6
+ import * as Record_ from "effect/Record"
7
+ import type * as Bdd from "../../Bdd.ts"
8
+ import { DiscoveryError, type ModuleLoadError } from "./errors.ts"
9
+ import { GlobResolver } from "./glob.ts"
10
+ import type { FeatureSource } from "./models.ts"
11
+ import { ModuleLoader } from "./moduleLoader.ts"
12
+
13
+ /** @internal */
14
+ export const loadFeatureSources: (
15
+ patterns: ReadonlyArray<string>
16
+ ) => Effect.Effect<ReadonlyArray<FeatureSource>, DiscoveryError, FileSystem.FileSystem | GlobResolver> = Effect
17
+ .fnUntraced(function*(
18
+ patterns: ReadonlyArray<string>
19
+ ) {
20
+ const fs = yield* FileSystem.FileSystem
21
+ const glob = yield* GlobResolver
22
+ const paths = yield* nonEmptyPaths(
23
+ yield* glob.resolve(patterns),
24
+ "No feature files matched --features"
25
+ )
26
+ return yield* Effect.forEach(paths, (path) =>
27
+ pipe(
28
+ fs.readFileString(path),
29
+ Effect.map((source): FeatureSource => ({ path, source })),
30
+ Effect.mapError((cause) =>
31
+ new DiscoveryError({
32
+ message: `Could not read feature file "${path}"`,
33
+ cause
34
+ })
35
+ )
36
+ ))
37
+ })
38
+
39
+ /** @internal */
40
+ export const loadFeatureDefinitions: (
41
+ patterns: ReadonlyArray<string>
42
+ ) => Effect.Effect<
43
+ ReadonlyArray<Bdd.Feature<unknown, unknown, never>>,
44
+ DiscoveryError | ModuleLoadError,
45
+ GlobResolver | ModuleLoader | Path.Path
46
+ > = Effect.fnUntraced(function*(
47
+ patterns: ReadonlyArray<string>
48
+ ) {
49
+ const glob = yield* GlobResolver
50
+ const loader = yield* ModuleLoader
51
+ const paths = yield* nonEmptyPaths(
52
+ yield* glob.resolve(patterns),
53
+ "No step definition modules matched --steps"
54
+ )
55
+ const definitions = yield* Effect.forEach(paths, (path) =>
56
+ pipe(
57
+ loader.load(path),
58
+ Effect.map(extractFeatureDefinitions)
59
+ ))
60
+ return yield* nonEmptyDefinitions(Arr.flatten(definitions))
61
+ })
62
+
63
+ const nonEmptyPaths = (
64
+ paths: ReadonlyArray<string>,
65
+ message: string
66
+ ): Effect.Effect<ReadonlyArray<string>, DiscoveryError> =>
67
+ paths.length === 0
68
+ ? Effect.fail(new DiscoveryError({ message }))
69
+ : Effect.succeed(paths)
70
+
71
+ const nonEmptyDefinitions = (
72
+ definitions: ReadonlyArray<Bdd.Feature<unknown, unknown, never>>
73
+ ): Effect.Effect<ReadonlyArray<Bdd.Feature<unknown, unknown, never>>, DiscoveryError> =>
74
+ definitions.length === 0
75
+ ? Effect.fail(new DiscoveryError({ message: "No Bdd.Feature exports found in matched step definition modules" }))
76
+ : Effect.succeed(definitions)
77
+
78
+ const extractFeatureDefinitions = (
79
+ module: Record<string, unknown>
80
+ ): ReadonlyArray<Bdd.Feature<unknown, unknown, never>> =>
81
+ pipe(
82
+ Record_.values(module),
83
+ Arr.filter(isFeatureDefinition)
84
+ )
85
+
86
+ const isFeatureDefinition = (value: unknown): value is Bdd.Feature<unknown, unknown, never> => {
87
+ if (typeof value !== "object" || value === null) {
88
+ return false
89
+ }
90
+ const candidate = value as {
91
+ readonly _tag?: unknown
92
+ readonly initial?: unknown
93
+ readonly name?: unknown
94
+ readonly transitions?: unknown
95
+ }
96
+ return candidate._tag === "Feature" &&
97
+ "initial" in candidate &&
98
+ typeof candidate.name === "string" &&
99
+ Array.isArray(candidate.transitions)
100
+ }
@@ -0,0 +1,118 @@
1
+ import type { PickleStep } from "@cucumber/messages"
2
+ import type * as Bdd from "../../Bdd.ts"
3
+ import type * as Parser from "../parser.ts"
4
+ import type * as CoreRunner from "../runner.ts"
5
+
6
+ /** @internal */
7
+ export interface FeatureSource {
8
+ readonly path: string
9
+ readonly source: string
10
+ }
11
+
12
+ /** @internal */
13
+ export interface ScenarioTask {
14
+ readonly featurePath: string
15
+ readonly core: CoreRunner.ScenarioTask<unknown, unknown, never>
16
+ }
17
+
18
+ /** @internal */
19
+ export type ScenarioOutcome =
20
+ | {
21
+ readonly _tag: "Passed"
22
+ readonly steps: number
23
+ }
24
+ | {
25
+ readonly _tag: "Failed"
26
+ readonly error: Bdd.RunError
27
+ }
28
+
29
+ /** @internal */
30
+ export interface ScenarioResult {
31
+ readonly task: ScenarioTask
32
+ readonly outcome: ScenarioOutcome
33
+ readonly durationMillis: number
34
+ }
35
+
36
+ /** @internal */
37
+ export interface RunSummary {
38
+ readonly features: number
39
+ readonly total: number
40
+ readonly passed: number
41
+ readonly failed: number
42
+ readonly durationMillis: number
43
+ }
44
+
45
+ /** @internal */
46
+ export type CliDiagnostic =
47
+ | {
48
+ readonly _tag: "UnmatchedFeature"
49
+ readonly featurePath: string
50
+ readonly featureName: string
51
+ readonly line: number
52
+ readonly message: string
53
+ }
54
+ | {
55
+ readonly _tag: "UnmatchedStep"
56
+ readonly featurePath: string
57
+ readonly featureName: string
58
+ readonly scenarioName: string
59
+ readonly scenarioLine: number
60
+ readonly step: PickleStep
61
+ readonly source: Parser.SourceIndex
62
+ readonly reason: "NoMatch" | "WrongKeyword" | "MultipleMatches"
63
+ readonly candidates: ReadonlyArray<string>
64
+ readonly message: string
65
+ }
66
+ | {
67
+ readonly _tag: "UnmatchedScenario"
68
+ readonly featurePath: string
69
+ readonly featureName: string
70
+ readonly scenarioName: string
71
+ readonly scenarioLine: number
72
+ readonly message: string
73
+ }
74
+ | {
75
+ readonly _tag: "UnusedFeatureDefinition"
76
+ readonly featureName: string
77
+ readonly message: string
78
+ }
79
+ | {
80
+ readonly _tag: "UnusedStepDefinition"
81
+ readonly featureName: string
82
+ readonly expression: string
83
+ readonly kind: string
84
+ readonly message: string
85
+ }
86
+
87
+ /** @internal */
88
+ export interface CliRunResult {
89
+ readonly results: ReadonlyArray<ScenarioResult>
90
+ readonly diagnostics: ReadonlyArray<CliDiagnostic>
91
+ readonly summary: RunSummary
92
+ }
93
+
94
+ /** @internal */
95
+ export interface CliFilters {
96
+ readonly tags: ReadonlyArray<string>
97
+ readonly names: ReadonlyArray<string>
98
+ readonly failFast: boolean
99
+ }
100
+
101
+ /** @internal */
102
+ export interface CliOptions {
103
+ readonly features: ReadonlyArray<string>
104
+ readonly steps: ReadonlyArray<string>
105
+ readonly reporters: ReadonlyArray<ReporterName>
106
+ readonly outputFiles: {
107
+ readonly text?: string
108
+ readonly html?: string
109
+ readonly json?: string
110
+ readonly junit?: string
111
+ }
112
+ readonly verbose: boolean
113
+ readonly filters: CliFilters
114
+ readonly parallel: number
115
+ }
116
+
117
+ /** @internal */
118
+ export type ReporterName = "text" | "html" | "json" | "junit"
@@ -0,0 +1,41 @@
1
+ import * as Context from "effect/Context"
2
+ import * as Effect from "effect/Effect"
3
+ import * as Layer from "effect/Layer"
4
+ import * as Path from "effect/Path"
5
+ import { pathToFileURL } from "node:url"
6
+ import { ModuleLoadError } from "./errors.ts"
7
+
8
+ const load = Effect.fnUntraced(function*(path: string) {
9
+ const pathService = yield* Path.Path
10
+ const resolved = pathService.resolve(path)
11
+ return yield* Effect.tryPromise({
12
+ try: () => import(pathToFileURL(resolved).href) as Promise<Record<string, unknown>>,
13
+ catch: (cause) => moduleLoadError(resolved, cause)
14
+ })
15
+ })
16
+
17
+ /** @internal */
18
+ export class ModuleLoader extends Context.Service<ModuleLoader, {
19
+ readonly load: (path: string) => Effect.Effect<Record<string, unknown>, ModuleLoadError, Path.Path>
20
+ }>()("effect-bdd/cli/ModuleLoader") {
21
+ static readonly Live = Layer.succeed(ModuleLoader, {
22
+ load
23
+ })
24
+ }
25
+
26
+ const moduleLoadError = (path: string, cause: unknown): ModuleLoadError => {
27
+ const reason = causeMessage(cause)
28
+ const isTsLoaderFailure = reason.includes("Unknown file extension") && reason.includes(".ts")
29
+ return new ModuleLoadError({
30
+ path,
31
+ message: isTsLoaderFailure
32
+ ? `Could not load TypeScript step module "${path}". Register a TypeScript loader when running on Node, for example: node --import tsx ./node_modules/.bin/effect-bdd`
33
+ : `Could not load step module "${path}": ${reason}`,
34
+ cause
35
+ })
36
+ }
37
+
38
+ const causeMessage = (cause: unknown): string =>
39
+ typeof cause === "object" && cause !== null && "message" in cause && typeof cause.message === "string"
40
+ ? cause.message
41
+ : String(cause)