@serenity-js/jasmine 3.37.2 → 3.39.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 (49) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +1 -0
  3. package/esm/SerenityReporterForJasmine.d.ts +11 -1
  4. package/esm/SerenityReporterForJasmine.d.ts.map +1 -1
  5. package/esm/SerenityReporterForJasmine.js +44 -8
  6. package/esm/SerenityReporterForJasmine.js.map +1 -1
  7. package/esm/bootstrap.d.ts +7 -2
  8. package/esm/bootstrap.d.ts.map +1 -1
  9. package/esm/bootstrap.js +99 -13
  10. package/esm/bootstrap.js.map +1 -1
  11. package/esm/jasmine/SpecResult.d.ts +10 -1
  12. package/esm/jasmine/SpecResult.d.ts.map +1 -1
  13. package/esm/jasmine/SuiteResult.d.ts +10 -1
  14. package/esm/jasmine/SuiteResult.d.ts.map +1 -1
  15. package/esm/jasmine/index.d.ts +1 -0
  16. package/esm/jasmine/index.d.ts.map +1 -1
  17. package/esm/jasmine/index.js +1 -0
  18. package/esm/jasmine/index.js.map +1 -1
  19. package/esm/monkeyPatched.d.ts +4 -0
  20. package/esm/monkeyPatched.d.ts.map +1 -1
  21. package/esm/monkeyPatched.js +36 -2
  22. package/esm/monkeyPatched.js.map +1 -1
  23. package/lib/SerenityReporterForJasmine.d.ts +11 -1
  24. package/lib/SerenityReporterForJasmine.d.ts.map +1 -1
  25. package/lib/SerenityReporterForJasmine.js +44 -8
  26. package/lib/SerenityReporterForJasmine.js.map +1 -1
  27. package/lib/bootstrap.d.ts +7 -2
  28. package/lib/bootstrap.d.ts.map +1 -1
  29. package/lib/bootstrap.js +99 -13
  30. package/lib/bootstrap.js.map +1 -1
  31. package/lib/jasmine/SpecResult.d.ts +10 -1
  32. package/lib/jasmine/SpecResult.d.ts.map +1 -1
  33. package/lib/jasmine/SuiteResult.d.ts +10 -1
  34. package/lib/jasmine/SuiteResult.d.ts.map +1 -1
  35. package/lib/jasmine/index.d.ts +1 -0
  36. package/lib/jasmine/index.d.ts.map +1 -1
  37. package/lib/jasmine/index.js +1 -0
  38. package/lib/jasmine/index.js.map +1 -1
  39. package/lib/monkeyPatched.d.ts +4 -0
  40. package/lib/monkeyPatched.d.ts.map +1 -1
  41. package/lib/monkeyPatched.js +36 -2
  42. package/lib/monkeyPatched.js.map +1 -1
  43. package/package.json +5 -5
  44. package/src/SerenityReporterForJasmine.ts +61 -10
  45. package/src/bootstrap.ts +112 -13
  46. package/src/jasmine/SpecResult.ts +11 -1
  47. package/src/jasmine/SuiteResult.ts +11 -1
  48. package/src/jasmine/index.ts +1 -0
  49. package/src/monkeyPatched.ts +39 -2
@@ -46,6 +46,7 @@ import type {
46
46
  JasmineDoneInfo,
47
47
  JasmineReporter,
48
48
  JasmineStartedInfo,
49
+ Location,
49
50
  SpecResult,
50
51
  SuiteResult
51
52
  } from './jasmine/index.js';
@@ -62,11 +63,12 @@ export class SerenityReporterForJasmine implements JasmineReporter {
62
63
  private currentSceneId: CorrelationId = undefined;
63
64
 
64
65
  /**
65
- * @param {Serenity} serenity
66
+ * @param serenity - The Serenity instance
67
+ * @param requirementsHierarchy - The requirements hierarchy for tagging
66
68
  */
67
69
  constructor(
68
70
  private readonly serenity: Serenity,
69
- private readonly requirementsHierachy: RequirementsHierarchy
71
+ private readonly requirementsHierachy: RequirementsHierarchy,
70
72
  ) {
71
73
  }
72
74
 
@@ -112,12 +114,13 @@ export class SerenityReporterForJasmine implements JasmineReporter {
112
114
  if (result.failedExpectations.length > 1) {
113
115
  result.failedExpectations.forEach(failedExpectation => {
114
116
  const sceneId = this.serenity.currentSceneId();
117
+ const location = this.locationOf(result);
115
118
  const activityDetails = new ActivityDetails(
116
119
  new Name('Expectation'),
117
120
  new FileSystemLocation(
118
- Path.from(result.location.path),
119
- result.location.line,
120
- result.location.column,
121
+ Path.from(location.path),
122
+ location.line,
123
+ location.column,
121
124
  ),
122
125
  );
123
126
 
@@ -136,6 +139,7 @@ export class SerenityReporterForJasmine implements JasmineReporter {
136
139
 
137
140
  this.emit(new SceneFinishes(
138
141
  this.currentSceneId,
142
+ outcome,
139
143
  this.serenity.currentTime(),
140
144
  ));
141
145
 
@@ -148,10 +152,14 @@ export class SerenityReporterForJasmine implements JasmineReporter {
148
152
  this.serenity.currentTime(),
149
153
  ));
150
154
  }, error => {
155
+ const errorOutcome = new ExecutionFailedWithError(error);
156
+
151
157
  this.emit(new SceneFinished(
152
158
  this.currentSceneId,
153
159
  scenarioDetails,
154
- new ExecutionFailedWithError(error),
160
+ errorOutcome.isWorseThan(outcome)
161
+ ? errorOutcome
162
+ : outcome,
155
163
  this.serenity.currentTime(),
156
164
  ));
157
165
 
@@ -180,6 +188,37 @@ export class SerenityReporterForJasmine implements JasmineReporter {
180
188
  events.forEach(event => this.serenity.announce(event));
181
189
  }
182
190
 
191
+ /**
192
+ * Extracts location information from a spec or suite result.
193
+ * Supports both Jasmine 5.x and 6.x (location object from monkey-patching).
194
+ *
195
+ * @private
196
+ * @param result - The spec or suite result
197
+ * @returns Location object with path, line, and column
198
+ */
199
+ private locationOf(result: SpecResult | SuiteResult): Location {
200
+ // Jasmine 5.x and 6.x with monkey-patching provides location object
201
+ if (result.location) {
202
+ return result.location;
203
+ }
204
+
205
+ // Fallback: use filename property if available (Jasmine 6.x without monkey-patching)
206
+ if (result.filename) {
207
+ return {
208
+ path: result.filename,
209
+ line: 0,
210
+ column: 0,
211
+ };
212
+ }
213
+
214
+ // Fallback for edge cases
215
+ return {
216
+ path: 'unknown',
217
+ line: 0,
218
+ column: 0,
219
+ };
220
+ }
221
+
183
222
  /**
184
223
  * @private
185
224
  * @param {SpecResult} spec
@@ -188,12 +227,17 @@ export class SerenityReporterForJasmine implements JasmineReporter {
188
227
  private scenarioDetailsOf(spec: SpecResult): { scenarioDetails: ScenarioDetails, scenarioTags: Tag[] } {
189
228
  const name = this.currentScenarioNameFor(spec.description);
190
229
  const featureName = this.currentFeatureNameFor(spec);
230
+ const location = this.locationOf(spec);
191
231
 
192
232
  return {
193
233
  scenarioDetails: new ScenarioDetails(
194
234
  new Name(Tags.stripFrom(name)),
195
235
  new Category(Tags.stripFrom(featureName)),
196
- FileSystemLocation.fromJSON(spec.location as any),
236
+ new FileSystemLocation(
237
+ Path.from(location.path),
238
+ location.line,
239
+ location.column,
240
+ ),
197
241
  ),
198
242
  scenarioTags: Tags.from(`${ featureName } ${ name }`)
199
243
  };
@@ -205,9 +249,15 @@ export class SerenityReporterForJasmine implements JasmineReporter {
205
249
  * @returns {TestSuiteDetails}
206
250
  */
207
251
  private testSuiteDetailsOf(result: SuiteResult): TestSuiteDetails {
252
+ const location = this.locationOf(result);
253
+
208
254
  return new TestSuiteDetails(
209
255
  new Name(result.description),
210
- FileSystemLocation.fromJSON(result.location as any),
256
+ new FileSystemLocation(
257
+ Path.from(location.path),
258
+ location.line,
259
+ location.column,
260
+ ),
211
261
  new CorrelationId(result.id),
212
262
  );
213
263
  }
@@ -217,7 +267,8 @@ export class SerenityReporterForJasmine implements JasmineReporter {
217
267
  * @returns {string}
218
268
  */
219
269
  private currentFeatureNameFor(spec: SpecResult): string {
220
- const path = new Path(spec.location.path);
270
+ const location = this.locationOf(spec);
271
+ const path = new Path(location.path);
221
272
 
222
273
  return this.describes[0]
223
274
  ? this.describes[0].description
@@ -249,7 +300,7 @@ export class SerenityReporterForJasmine implements JasmineReporter {
249
300
  return new ImplementationPending(new ImplementationPendingError((result as any).pendingReason || ''));
250
301
  case 'excluded':
251
302
  return new ExecutionSkipped();
252
- case 'passed': // eslint-disable-line unicorn/no-useless-switch-case
303
+ case 'passed':
253
304
  default:
254
305
  return new ExecutionSuccessful();
255
306
  }
package/src/bootstrap.ts CHANGED
@@ -5,8 +5,13 @@ import { monkeyPatched } from './monkeyPatched.js';
5
5
  import { SerenityReporterForJasmine } from './SerenityReporterForJasmine.js';
6
6
 
7
7
  /**
8
- * Monkey-patches Jasmine's Suite and Spec so that they provide more accurate information,
9
- * and returns a bootstrapped instance of the [`SerenityReporterForJasmine`](https://serenity-js.org/api/jasmine/function/default/) to be registered with Jasmine.
8
+ * Bootstraps the Serenity/JS reporter for Jasmine.
9
+ *
10
+ * This function monkey-patches Jasmine's Suite and Spec constructors
11
+ * so that they provide more accurate location information.
12
+ *
13
+ * For Jasmine 5.x, Suite and Spec are on the jasmine object directly.
14
+ * For Jasmine 6.x, Suite and Spec are in jasmine.private.
10
15
  *
11
16
  * ## Registering the reporter from the command line
12
17
  *
@@ -27,20 +32,42 @@ import { SerenityReporterForJasmine } from './SerenityReporterForJasmine.js';
27
32
  * the global.jasmine instance
28
33
  */
29
34
  export function bootstrap(config: SerenityReporterForJasmineConfig = {}, jasmine = (global as any).jasmine): SerenityReporterForJasmine {
30
- const wrappers = {
31
- expectationResultFactory: originalExpectationResultFactory => ((attributes: { passed: boolean, error: Error }) => {
32
- const result = originalExpectationResultFactory(attributes);
33
35
 
34
- if (! attributes.passed && attributes.error instanceof RuntimeError) {
35
- result.stack = attributes.error.stack;
36
- }
36
+ // Jasmine 6+ moved Suite and Spec to jasmine.private,
37
+ // so we check both locations for backwards compatibility
38
+ const Suite = jasmine.Suite || jasmine.private?.Suite;
39
+ const Spec = jasmine.Spec || jasmine.private?.Spec;
37
40
 
38
- return result;
39
- }),
40
- };
41
+ if (Suite && Spec) {
42
+ // Monkey-patch Suite and Spec to capture location info
43
+ const wrappers = {
44
+ expectationResultFactory: originalExpectationResultFactory => ((attributes: { passed: boolean, error: Error }) => {
45
+ const result = originalExpectationResultFactory(attributes);
46
+
47
+ if (! attributes.passed && attributes.error instanceof RuntimeError) {
48
+ result.stack = attributes.error.stack;
49
+ }
41
50
 
42
- jasmine.Suite = monkeyPatched(jasmine.Suite, wrappers);
43
- jasmine.Spec = monkeyPatched(jasmine.Spec, wrappers);
51
+ return result;
52
+ }),
53
+ };
54
+
55
+ // Jasmine 5.x: Suite and Spec are on jasmine directly
56
+ if (jasmine.Suite && jasmine.Spec) {
57
+ jasmine.Suite = monkeyPatched(jasmine.Suite, wrappers);
58
+ jasmine.Spec = monkeyPatched(jasmine.Spec, wrappers);
59
+ }
60
+ // Jasmine 6+: Suite and Spec are in jasmine.private
61
+ else if (jasmine.private?.Suite && jasmine.private?.Spec) {
62
+ jasmine.private.Suite = monkeyPatched(jasmine.private.Suite, wrappers);
63
+ jasmine.private.Spec = monkeyPatched(jasmine.private.Spec, wrappers);
64
+ }
65
+ }
66
+
67
+ // For Jasmine 6+, patch the Expector and buildExpectationResult to restore expected/actual values
68
+ // This is needed because Jasmine 6 removed these from expectation results
69
+ patchBuildExpectationResultForExpectedActual(jasmine);
70
+ patchExpectorForExpectedActual(jasmine);
44
71
 
45
72
  const cwd = Path.from(process.cwd());
46
73
  const requirementsHierarchy = new RequirementsHierarchy(
@@ -51,6 +78,78 @@ export function bootstrap(config: SerenityReporterForJasmineConfig = {}, jasmine
51
78
  return new SerenityReporterForJasmine(serenity, requirementsHierarchy);
52
79
  }
53
80
 
81
+ /**
82
+ * Patches Jasmine's buildExpectationResult to include expected/actual values
83
+ * in the expectation result. This restores functionality that was removed in Jasmine 6.
84
+ *
85
+ * In Jasmine 6, the buildExpectationResult function no longer includes expected/actual
86
+ * values in the result object. This patch wraps the function to preserve these values
87
+ * when they are provided.
88
+ *
89
+ * @param jasmine - The global jasmine instance
90
+ */
91
+ function patchBuildExpectationResultForExpectedActual(jasmine: any): void {
92
+ // Access the private buildExpectationResult function
93
+ const originalBuildExpectationResult = jasmine?.private?.buildExpectationResult;
94
+
95
+ if (!originalBuildExpectationResult) {
96
+ // buildExpectationResult not available, skip
97
+ return;
98
+ }
99
+
100
+ jasmine.private.buildExpectationResult = function(options: any): any {
101
+ const result = originalBuildExpectationResult(options);
102
+
103
+ // Restore expected/actual values that Jasmine 6 removed
104
+ if (options.expected !== undefined) {
105
+ result.expected = options.expected;
106
+ }
107
+ if (options.actual !== undefined) {
108
+ result.actual = options.actual;
109
+ }
110
+
111
+ return result;
112
+ };
113
+ }
114
+
115
+ /**
116
+ * Patches Jasmine's Expector.prototype.processResult to include expected/actual values
117
+ * when calling addExpectationResult. This ensures the values are passed to
118
+ * buildExpectationResult.
119
+ *
120
+ * @param jasmine - The global jasmine instance
121
+ */
122
+ function patchExpectorForExpectedActual(jasmine: any): void {
123
+ // Access the private Expector class through jasmine's internal structure
124
+ // In Jasmine 6+, Expector is in jasmine.private.Expector
125
+ const Expector = jasmine?.private?.Expector;
126
+
127
+ if (!Expector || !Expector.prototype || !Expector.prototype.processResult) {
128
+ // Expector not available, skip
129
+ return;
130
+ }
131
+
132
+ Expector.prototype.processResult = function(result: any, errorForStack?: Error): void {
133
+ const message = this.buildMessage(result);
134
+
135
+ let expected = this.expected;
136
+ if (Array.isArray(expected) && expected.length === 1) {
137
+ expected = expected[0];
138
+ }
139
+
140
+ this.addExpectationResult(result.pass, {
141
+ matcherName: this.matcherName,
142
+ passed: result.pass,
143
+ message: message,
144
+ error: errorForStack ? undefined : result.error,
145
+ errorForStack: errorForStack || undefined,
146
+ // Include expected/actual values that Jasmine 6 removed
147
+ expected: expected,
148
+ actual: this.actual,
149
+ });
150
+ };
151
+ }
152
+
54
153
  export interface SerenityReporterForJasmineConfig {
55
154
  specDirectory?: string;
56
155
  }
@@ -29,5 +29,15 @@ export interface SpecResult {
29
29
  /** The time in ms used by the spec execution, including any before/afterEach. */
30
30
  duration: number | null;
31
31
 
32
- location: Location;
32
+ /**
33
+ * The file system location of the spec.
34
+ * Available in Jasmine 5.x and earlier when monkey-patched by Serenity/JS.
35
+ */
36
+ location?: Location;
37
+
38
+ /**
39
+ * The filename where the spec is defined.
40
+ * Available in Jasmine 6.x and later.
41
+ */
42
+ filename?: string;
33
43
  }
@@ -28,5 +28,15 @@ export interface SuiteResult extends Result {
28
28
  /** The time in ms for Suite execution, including any before/afterAll, before/afterEach. */
29
29
  duration: number | null;
30
30
 
31
- location: Location;
31
+ /**
32
+ * The file system location of the suite.
33
+ * Available in Jasmine 5.x and earlier when monkey-patched by Serenity/JS.
34
+ */
35
+ location?: Location;
36
+
37
+ /**
38
+ * The filename where the suite is defined.
39
+ * Available in Jasmine 6.x and later.
40
+ */
41
+ filename?: string;
32
42
  }
@@ -2,6 +2,7 @@ export * from './Expectation.js';
2
2
  export * from './JasmineDoneInfo.js';
3
3
  export * from './JasmineReporter.js';
4
4
  export * from './JasmineStartedInfo.js';
5
+ export * from './Location.js';
5
6
  export * from './Order.js';
6
7
  export * from './SpecResult.js';
7
8
  export * from './SuiteResult.js';
@@ -2,7 +2,7 @@ import { ErrorStackParser } from '@serenity-js/core/lib/errors/index.js';
2
2
 
3
3
  const parser = new ErrorStackParser();
4
4
 
5
- /* eslint-disable @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/ban-types */
5
+ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
6
6
 
7
7
  /**
8
8
  * Monkey-patches Jasmine domain model constructors so that they
@@ -10,6 +10,10 @@ const parser = new ErrorStackParser();
10
10
  *
11
11
  * This helps to make reporting more accurate.
12
12
  *
13
+ * For Jasmine 5.x, the location is stored on instance.result.location.
14
+ * For Jasmine 6.x, the location is stored on instance._serenityLocation and
15
+ * injected into the result events via patched startedEvent/doneEvent methods.
16
+ *
13
17
  * @param jasmineConstructor - A Jasmine constructor function to be patched
14
18
  * @param {object} wrappers - Attributes to wrap when the monkey-patched Jasmine constructor is invoked
15
19
  */
@@ -23,7 +27,40 @@ export function monkeyPatched(
23
27
  });
24
28
 
25
29
  const instance = new jasmineConstructor(attrs);
26
- instance.result.location = callerLocation();
30
+ const location = callerLocation();
31
+
32
+ // Jasmine 5.x: instance.result exists, store location there
33
+ if (instance.result) {
34
+ instance.result.location = location;
35
+ }
36
+ // Jasmine 6.x: store location on instance and patch event methods
37
+ else {
38
+ instance._serenityLocation = location;
39
+
40
+ // Patch startedEvent to include location
41
+ if (typeof instance.startedEvent === 'function') {
42
+ const originalStartedEvent = instance.startedEvent.bind(instance);
43
+ instance.startedEvent = function() {
44
+ const event = originalStartedEvent();
45
+ if (event && instance._serenityLocation) {
46
+ event.location = instance._serenityLocation;
47
+ }
48
+ return event;
49
+ };
50
+ }
51
+
52
+ // Patch doneEvent to include location
53
+ if (typeof instance.doneEvent === 'function') {
54
+ const originalDoneEvent = instance.doneEvent.bind(instance);
55
+ instance.doneEvent = function() {
56
+ const event = originalDoneEvent();
57
+ if (event && instance._serenityLocation) {
58
+ event.location = instance._serenityLocation;
59
+ }
60
+ return event;
61
+ };
62
+ }
63
+ }
27
64
 
28
65
  return instance;
29
66
  };