@serenity-js/assertions 3.0.0-rc.43 → 3.0.0-rc.45
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 +26 -0
- package/lib/Ensure.d.ts +24 -0
- package/lib/Ensure.d.ts.map +1 -1
- package/lib/Ensure.js +26 -0
- package/lib/Ensure.js.map +1 -1
- package/lib/EnsureEventually.d.ts +97 -0
- package/lib/EnsureEventually.d.ts.map +1 -0
- package/lib/EnsureEventually.js +143 -0
- package/lib/EnsureEventually.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/package.json +8 -7
- package/src/Ensure.ts +28 -0
- package/src/EnsureEventually.ts +173 -0
- package/src/index.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,32 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [3.0.0-rc.45](https://github.com/serenity-js/serenity-js/compare/v3.0.0-rc.44...v3.0.0-rc.45) (2023-03-22)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @serenity-js/assertions
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# [3.0.0-rc.44](https://github.com/serenity-js/serenity-js/compare/v3.0.0-rc.43...v3.0.0-rc.44) (2023-03-19)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
* **core:** support for NPM 9 ([0493474](https://github.com/serenity-js/serenity-js/commit/0493474a1e28b86b1b60f69ec0d591c1a3265425))
|
|
20
|
+
* **deps:** update dependency tiny-types to ^1.19.1 ([ce335eb](https://github.com/serenity-js/serenity-js/commit/ce335ebca434d1fd0e6e809a65a0882fd10a311a))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Features
|
|
24
|
+
|
|
25
|
+
* **assertions:** fault-tolerant interaction to Ensure.eventually ([d6297f7](https://github.com/serenity-js/serenity-js/commit/d6297f7f15c096a51461c484c6a8d1eeb2182b24)), closes [#1522](https://github.com/serenity-js/serenity-js/issues/1522)
|
|
26
|
+
* **core:** introduced a new ability ScheduleWork to enable [#1083](https://github.com/serenity-js/serenity-js/issues/1083) and [#1522](https://github.com/serenity-js/serenity-js/issues/1522) ([b275d18](https://github.com/serenity-js/serenity-js/commit/b275d18434cdedf069c5f1da3b9b359fc7da60fe))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
6
32
|
# [3.0.0-rc.43](https://github.com/serenity-js/serenity-js/compare/v3.0.0-rc.42...v3.0.0-rc.43) (2023-03-10)
|
|
7
33
|
|
|
8
34
|
**Note:** Version bump only for package @serenity-js/assertions
|
package/lib/Ensure.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Answerable, AnswersQuestions, CollectsArtifacts, Expectation, Interaction, RuntimeError, UsesAbilities } from '@serenity-js/core';
|
|
2
|
+
import { EnsureEventually } from './EnsureEventually';
|
|
2
3
|
/**
|
|
3
4
|
* The {@apilink Interaction|interaction} to `Ensure`
|
|
4
5
|
* verifies if the resolved value of the provided {@apilink Answerable}
|
|
@@ -50,6 +51,11 @@ export declare class Ensure<Actual> extends Interaction {
|
|
|
50
51
|
protected readonly actual: Answerable<Actual>;
|
|
51
52
|
protected readonly expectation: Expectation<Actual>;
|
|
52
53
|
/**
|
|
54
|
+
* Creates an {@apilink Interaction|interaction} to `Ensure`, which
|
|
55
|
+
* verifies if the resolved value of the provided {@apilink Answerable}
|
|
56
|
+
* meets the specified {@apilink Expectation}.
|
|
57
|
+
* If not, it immediately throws an {@apilink AssertionError}.
|
|
58
|
+
*
|
|
53
59
|
* @param {Answerable<Actual_Type>} actual
|
|
54
60
|
* An {@apilink Answerable} describing the actual state of the system.
|
|
55
61
|
*
|
|
@@ -59,6 +65,24 @@ export declare class Ensure<Actual> extends Interaction {
|
|
|
59
65
|
* @returns {Ensure<Actual_Type>}
|
|
60
66
|
*/
|
|
61
67
|
static that<Actual_Type>(actual: Answerable<Actual_Type>, expectation: Expectation<Actual_Type>): Ensure<Actual_Type>;
|
|
68
|
+
/**
|
|
69
|
+
* Creates an {@apilink Interaction|interaction} to {@apilink EnsureEventually}, which
|
|
70
|
+
* verifies if the resolved value of the provided {@apilink Answerable}
|
|
71
|
+
* meets the specified {@apilink Expectation} within the expected timeframe.
|
|
72
|
+
*
|
|
73
|
+
* If the expectation is not met by the time the timeout expires, the interaction throws an {@apilink AssertionError}.
|
|
74
|
+
* `EnsureEventually` ignores retries the evaluation if resolving the `actual` results in an {@apilink OptionalNotPresentError},
|
|
75
|
+
* but rethrows any other errors.
|
|
76
|
+
*
|
|
77
|
+
* @param {Answerable<Actual_Type>} actual
|
|
78
|
+
* An {@apilink Answerable} describing the actual state of the system.
|
|
79
|
+
*
|
|
80
|
+
* @param {Expectation<Actual_Type>} expectation
|
|
81
|
+
* An {@apilink Expectation} you expect the `actual` value to meet
|
|
82
|
+
*
|
|
83
|
+
* @returns {Ensure<Actual_Type>}
|
|
84
|
+
*/
|
|
85
|
+
static eventually<Actual_Type>(actual: Answerable<Actual_Type>, expectation: Expectation<Actual_Type>): EnsureEventually<Actual_Type>;
|
|
62
86
|
/**
|
|
63
87
|
* @param actual
|
|
64
88
|
* @param expectation
|
package/lib/Ensure.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Ensure.d.ts","sourceRoot":"","sources":["../src/Ensure.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,UAAU,EACV,gBAAgB,EAEhB,iBAAiB,EAEjB,WAAW,EAIX,WAAW,EAGX,YAAY,EACZ,aAAa,EAChB,MAAM,mBAAmB,CAAC;AAG3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,qBAAa,MAAM,CAAC,MAAM,CAAE,SAAQ,WAAW;
|
|
1
|
+
{"version":3,"file":"Ensure.d.ts","sourceRoot":"","sources":["../src/Ensure.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,UAAU,EACV,gBAAgB,EAEhB,iBAAiB,EAEjB,WAAW,EAIX,WAAW,EAGX,YAAY,EACZ,aAAa,EAChB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,qBAAa,MAAM,CAAC,MAAM,CAAE,SAAQ,WAAW;IA+CvC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC;IAC7C,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC;IA9CvD;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC,WAAW,CAAC,EAAE,WAAW,EAAE,WAAW,CAAC,WAAW,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC;IAIrH;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC,WAAW,CAAC,EAAE,WAAW,EAAE,WAAW,CAAC,WAAW,CAAC,GAAG,gBAAgB,CAAC,WAAW,CAAC;IAIrI;;;;OAIG;IACH,OAAO;IAQP;;OAEG;IACG,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,gBAAgB,GAAG,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAoB3F;;;;;;;;;OASG;IACH,iBAAiB,CAAC,kBAAkB,EAAE,KAAK,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,KAAK,YAAY,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,WAAW;CAgB7H"}
|
package/lib/Ensure.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Ensure = void 0;
|
|
4
4
|
const core_1 = require("@serenity-js/core");
|
|
5
|
+
const EnsureEventually_1 = require("./EnsureEventually");
|
|
5
6
|
/**
|
|
6
7
|
* The {@apilink Interaction|interaction} to `Ensure`
|
|
7
8
|
* verifies if the resolved value of the provided {@apilink Answerable}
|
|
@@ -51,6 +52,11 @@ const core_1 = require("@serenity-js/core");
|
|
|
51
52
|
*/
|
|
52
53
|
class Ensure extends core_1.Interaction {
|
|
53
54
|
/**
|
|
55
|
+
* Creates an {@apilink Interaction|interaction} to `Ensure`, which
|
|
56
|
+
* verifies if the resolved value of the provided {@apilink Answerable}
|
|
57
|
+
* meets the specified {@apilink Expectation}.
|
|
58
|
+
* If not, it immediately throws an {@apilink AssertionError}.
|
|
59
|
+
*
|
|
54
60
|
* @param {Answerable<Actual_Type>} actual
|
|
55
61
|
* An {@apilink Answerable} describing the actual state of the system.
|
|
56
62
|
*
|
|
@@ -62,6 +68,26 @@ class Ensure extends core_1.Interaction {
|
|
|
62
68
|
static that(actual, expectation) {
|
|
63
69
|
return new Ensure(actual, expectation, core_1.Activity.callerLocation(5));
|
|
64
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Creates an {@apilink Interaction|interaction} to {@apilink EnsureEventually}, which
|
|
73
|
+
* verifies if the resolved value of the provided {@apilink Answerable}
|
|
74
|
+
* meets the specified {@apilink Expectation} within the expected timeframe.
|
|
75
|
+
*
|
|
76
|
+
* If the expectation is not met by the time the timeout expires, the interaction throws an {@apilink AssertionError}.
|
|
77
|
+
* `EnsureEventually` ignores retries the evaluation if resolving the `actual` results in an {@apilink OptionalNotPresentError},
|
|
78
|
+
* but rethrows any other errors.
|
|
79
|
+
*
|
|
80
|
+
* @param {Answerable<Actual_Type>} actual
|
|
81
|
+
* An {@apilink Answerable} describing the actual state of the system.
|
|
82
|
+
*
|
|
83
|
+
* @param {Expectation<Actual_Type>} expectation
|
|
84
|
+
* An {@apilink Expectation} you expect the `actual` value to meet
|
|
85
|
+
*
|
|
86
|
+
* @returns {Ensure<Actual_Type>}
|
|
87
|
+
*/
|
|
88
|
+
static eventually(actual, expectation) {
|
|
89
|
+
return new EnsureEventually_1.EnsureEventually(actual, expectation, core_1.Activity.callerLocation(5));
|
|
90
|
+
}
|
|
65
91
|
/**
|
|
66
92
|
* @param actual
|
|
67
93
|
* @param expectation
|
package/lib/Ensure.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Ensure.js","sourceRoot":"","sources":["../src/Ensure.ts"],"names":[],"mappings":";;;AAAA,4CAgB2B;AAG3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,MAAa,MAAe,SAAQ,kBAAW;IAE3C
|
|
1
|
+
{"version":3,"file":"Ensure.js","sourceRoot":"","sources":["../src/Ensure.ts"],"names":[],"mappings":";;;AAAA,4CAgB2B;AAG3B,yDAAsD;AAEtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,MAAa,MAAe,SAAQ,kBAAW;IAE3C;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,IAAI,CAAc,MAA+B,EAAE,WAAqC;QAC3F,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,eAAQ,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;IACvE,CAAC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,UAAU,CAAc,MAA+B,EAAE,WAAqC;QACjG,OAAO,IAAI,mCAAgB,CAAC,MAAM,EAAE,WAAW,EAAE,eAAQ,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;IACjF,CAAC;IAED;;;;OAIG;IACH,YACuB,MAA0B,EAC1B,WAAgC,EACnD,QAA4B;QAE5B,KAAK,CAAC,IAAA,QAAC,EAAA,uBAAwB,MAAO,SAAU,WAAY,EAAE,EAAE,QAAQ,CAAC,CAAC;QAJvD,WAAM,GAAN,MAAM,CAAoB;QAC1B,gBAAW,GAAX,WAAW,CAAqB;IAIvD,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,SAAS,CAAC,KAA2D;QACvE,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QAE3E,IAAI,OAAO,YAAY,wBAAiB,EAAE;YACtC,MAAM,iBAAiB,GAAG,IAAA,QAAC,EAAA,GAAI,IAAI,CAAC,MAAO,EAAE,CAAC;YAC9C,MAAM,OAAO,GAAG,YAAa,iBAAkB,OAAQ,OAAO,CAAC,OAAQ,EAAE,CAAC;YAE1E,MAAM,kBAAW,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,qBAAc,EAAE;gBAC/C,OAAO;gBACP,WAAW,EAAE,OAAO,CAAC,WAAW;gBAChC,IAAI,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE;gBAC5D,QAAQ,EAAE,IAAI,CAAC,qBAAqB,EAAE;aACzC,CAAC,CAAC;SACN;QAED,IAAI,CAAE,CAAC,OAAO,YAAY,qBAAc,CAAC,EAAE;YACvC,MAAM,IAAI,iBAAU,CAAC,IAAA,QAAC,EAAA,wFAAyF,OAAQ,EAAE,CAAC,CAAC;SAC9H;IACL,CAAC;IAED;;;;;;;;;OASG;IACH,iBAAiB,CAAC,kBAAwE,EAAE,OAAgB;QACxG,MAAM,QAAQ,GAAG,IAAI,CAAC,qBAAqB,EAAE,CAAC;QAE9C,OAAO,kBAAW,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAC,KAAK,EAAC,EAAE;YACpD,IAAI;gBACA,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;aAC/B;YACD,OAAO,KAAK,EAAE;gBACV,MAAM,kBAAW,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,kBAAkB,EAAE;oBACnD,OAAO,EAAE,OAAO,aAAP,OAAO,cAAP,OAAO,GAAI,KAAK,CAAC,OAAO;oBACjC,QAAQ;oBACR,KAAK,EAAE,KAAK;iBACf,CAAC,CAAC;aACN;QACL,CAAC,CAAC,CAAC;IACP,CAAC;CACJ;AAvGD,wBAuGC"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Answerable, AnswersQuestions, CollectsArtifacts, Duration, Expectation, Interaction, RuntimeError, UsesAbilities } from '@serenity-js/core';
|
|
2
|
+
import { FileSystemLocation } from '@serenity-js/core/lib/io';
|
|
3
|
+
/**
|
|
4
|
+
* The {@apilink Interaction|interaction} to `EnsureEventually`
|
|
5
|
+
* verifies if the resolved value of the provided {@apilink Answerable}
|
|
6
|
+
* meets the specified {@apilink Expectation} within the expected timeframe.
|
|
7
|
+
*
|
|
8
|
+
* If the expectation is not met by the time the timeout expires, the interaction throws an {@apilink AssertionError}.
|
|
9
|
+
* `EnsureEventually` retries the evaluation if resolving the `actual` results in an {@apilink ListItemNotFoundError},
|
|
10
|
+
* but rethrows any other errors.
|
|
11
|
+
*
|
|
12
|
+
* :::tip Use the factory method
|
|
13
|
+
* Use the factory method {@apilink Ensure.eventually} to instantiate this interaction.
|
|
14
|
+
* :::
|
|
15
|
+
*
|
|
16
|
+
* ## Basic usage with dynamic values
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { actorCalled } from '@serenity-js/core'
|
|
19
|
+
* import { Ensure, equals } from '@serenity-js/assertions'
|
|
20
|
+
* import { Text, PageElement, By } from '@serenity-js/web'
|
|
21
|
+
*
|
|
22
|
+
* await actorCalled('Erica').attemptsTo(
|
|
23
|
+
* Ensure.eventually(
|
|
24
|
+
* Text.of(PageElement.located(By.css('h1'))),
|
|
25
|
+
* equals('Learn Serenity/JS!')
|
|
26
|
+
* )
|
|
27
|
+
* )
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* ## Composing expectations with `and`
|
|
31
|
+
*
|
|
32
|
+
* ```ts
|
|
33
|
+
* import { actorCalled } from '@serenity-js/core'
|
|
34
|
+
* import { and, Ensure, startsWith, endsWith } from '@serenity-js/assertions'
|
|
35
|
+
* import { Text, PageElement, By } from '@serenity-js/web'
|
|
36
|
+
*
|
|
37
|
+
* await actorCalled('Erica').attemptsTo(
|
|
38
|
+
* Ensure.eventually(
|
|
39
|
+
* Text.of(PageElement.located(By.css('h1'))),
|
|
40
|
+
* and(startsWith('Serenity'), endsWith('!'))
|
|
41
|
+
* )
|
|
42
|
+
* )
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* ## Overriding the type of Error thrown upon assertion failure
|
|
46
|
+
*
|
|
47
|
+
* ```ts
|
|
48
|
+
* import { actorCalled } from '@serenity-js/core'
|
|
49
|
+
* import { and, Ensure, startsWith, endsWith } from '@serenity-js/assertions'
|
|
50
|
+
* import { Text, PageElement, By } from '@serenity-js/web'
|
|
51
|
+
*
|
|
52
|
+
* await actorCalled('Erica').attemptsTo(
|
|
53
|
+
* Ensure.eventually(
|
|
54
|
+
* Text.of(PageElement.located(By.css('h1'))),
|
|
55
|
+
* and(startsWith('Serenity'), endsWith('!'))
|
|
56
|
+
* ).otherwiseFailWith(LogicError, `Looks like we're not on the right page`)
|
|
57
|
+
* )
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* @experimental
|
|
61
|
+
*
|
|
62
|
+
* @group Activities
|
|
63
|
+
*/
|
|
64
|
+
export declare class EnsureEventually<Actual> extends Interaction {
|
|
65
|
+
protected readonly actual: Answerable<Actual>;
|
|
66
|
+
protected readonly expectation: Expectation<Actual>;
|
|
67
|
+
protected readonly timeout?: Duration;
|
|
68
|
+
/**
|
|
69
|
+
* @param actual
|
|
70
|
+
* @param expectation
|
|
71
|
+
* @param location
|
|
72
|
+
* @param timeout
|
|
73
|
+
*/
|
|
74
|
+
constructor(actual: Answerable<Actual>, expectation: Expectation<Actual>, location: FileSystemLocation, timeout?: Duration);
|
|
75
|
+
/**
|
|
76
|
+
* Override the default timeout set via {@apilink SerenityConfig.interactionTimeout}.
|
|
77
|
+
*
|
|
78
|
+
* @param timeout
|
|
79
|
+
*/
|
|
80
|
+
timeoutAfter(timeout: Duration): EnsureEventually<Actual>;
|
|
81
|
+
/**
|
|
82
|
+
* @inheritDoc
|
|
83
|
+
*/
|
|
84
|
+
performAs(actor: UsesAbilities & AnswersQuestions & CollectsArtifacts): Promise<void>;
|
|
85
|
+
/**
|
|
86
|
+
* Overrides the default {@apilink AssertionError} thrown when
|
|
87
|
+
* the actual value does not meet the expectation.
|
|
88
|
+
*
|
|
89
|
+
* @param typeOfRuntimeError
|
|
90
|
+
* A constructor function producing a subtype of {@apilink RuntimeError} to throw, e.g. {@apilink TestCompromisedError}
|
|
91
|
+
*
|
|
92
|
+
* @param message
|
|
93
|
+
* The message explaining the failure
|
|
94
|
+
*/
|
|
95
|
+
otherwiseFailWith(typeOfRuntimeError: new (message: string, cause?: Error) => RuntimeError, message?: string): Interaction;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=EnsureEventually.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EnsureEventually.d.ts","sourceRoot":"","sources":["../src/EnsureEventually.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,UAAU,EACV,gBAAgB,EAEhB,iBAAiB,EAEjB,QAAQ,EACR,WAAW,EAGX,WAAW,EAGX,YAAY,EAGZ,aAAa,EAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAE9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AACH,qBAAa,gBAAgB,CAAC,MAAM,CAAE,SAAQ,WAAW;IAQjD,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC;IAC7C,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC;IAEnD,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ;IAVzC;;;;;OAKG;gBAEoB,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC,EAC1B,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,EACnD,QAAQ,EAAE,kBAAkB,EACT,OAAO,CAAC,EAAE,QAAQ;IAKzC;;;;OAIG;IACH,YAAY,CAAC,OAAO,EAAE,QAAQ,GAAG,gBAAgB,CAAC,MAAM,CAAC;IAIzD;;OAEG;IACG,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,gBAAgB,GAAG,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAsC3F;;;;;;;;;OASG;IACH,iBAAiB,CAAC,kBAAkB,EAAE,KAAK,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,KAAK,YAAY,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,WAAW;CAe7H"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EnsureEventually = void 0;
|
|
4
|
+
const core_1 = require("@serenity-js/core");
|
|
5
|
+
/**
|
|
6
|
+
* The {@apilink Interaction|interaction} to `EnsureEventually`
|
|
7
|
+
* verifies if the resolved value of the provided {@apilink Answerable}
|
|
8
|
+
* meets the specified {@apilink Expectation} within the expected timeframe.
|
|
9
|
+
*
|
|
10
|
+
* If the expectation is not met by the time the timeout expires, the interaction throws an {@apilink AssertionError}.
|
|
11
|
+
* `EnsureEventually` retries the evaluation if resolving the `actual` results in an {@apilink ListItemNotFoundError},
|
|
12
|
+
* but rethrows any other errors.
|
|
13
|
+
*
|
|
14
|
+
* :::tip Use the factory method
|
|
15
|
+
* Use the factory method {@apilink Ensure.eventually} to instantiate this interaction.
|
|
16
|
+
* :::
|
|
17
|
+
*
|
|
18
|
+
* ## Basic usage with dynamic values
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { actorCalled } from '@serenity-js/core'
|
|
21
|
+
* import { Ensure, equals } from '@serenity-js/assertions'
|
|
22
|
+
* import { Text, PageElement, By } from '@serenity-js/web'
|
|
23
|
+
*
|
|
24
|
+
* await actorCalled('Erica').attemptsTo(
|
|
25
|
+
* Ensure.eventually(
|
|
26
|
+
* Text.of(PageElement.located(By.css('h1'))),
|
|
27
|
+
* equals('Learn Serenity/JS!')
|
|
28
|
+
* )
|
|
29
|
+
* )
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* ## Composing expectations with `and`
|
|
33
|
+
*
|
|
34
|
+
* ```ts
|
|
35
|
+
* import { actorCalled } from '@serenity-js/core'
|
|
36
|
+
* import { and, Ensure, startsWith, endsWith } from '@serenity-js/assertions'
|
|
37
|
+
* import { Text, PageElement, By } from '@serenity-js/web'
|
|
38
|
+
*
|
|
39
|
+
* await actorCalled('Erica').attemptsTo(
|
|
40
|
+
* Ensure.eventually(
|
|
41
|
+
* Text.of(PageElement.located(By.css('h1'))),
|
|
42
|
+
* and(startsWith('Serenity'), endsWith('!'))
|
|
43
|
+
* )
|
|
44
|
+
* )
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* ## Overriding the type of Error thrown upon assertion failure
|
|
48
|
+
*
|
|
49
|
+
* ```ts
|
|
50
|
+
* import { actorCalled } from '@serenity-js/core'
|
|
51
|
+
* import { and, Ensure, startsWith, endsWith } from '@serenity-js/assertions'
|
|
52
|
+
* import { Text, PageElement, By } from '@serenity-js/web'
|
|
53
|
+
*
|
|
54
|
+
* await actorCalled('Erica').attemptsTo(
|
|
55
|
+
* Ensure.eventually(
|
|
56
|
+
* Text.of(PageElement.located(By.css('h1'))),
|
|
57
|
+
* and(startsWith('Serenity'), endsWith('!'))
|
|
58
|
+
* ).otherwiseFailWith(LogicError, `Looks like we're not on the right page`)
|
|
59
|
+
* )
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* @experimental
|
|
63
|
+
*
|
|
64
|
+
* @group Activities
|
|
65
|
+
*/
|
|
66
|
+
class EnsureEventually extends core_1.Interaction {
|
|
67
|
+
/**
|
|
68
|
+
* @param actual
|
|
69
|
+
* @param expectation
|
|
70
|
+
* @param location
|
|
71
|
+
* @param timeout
|
|
72
|
+
*/
|
|
73
|
+
constructor(actual, expectation, location, timeout) {
|
|
74
|
+
super((0, core_1.d) `#actor ensures that ${actual} does eventually ${expectation}`, location);
|
|
75
|
+
this.actual = actual;
|
|
76
|
+
this.expectation = expectation;
|
|
77
|
+
this.timeout = timeout;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Override the default timeout set via {@apilink SerenityConfig.interactionTimeout}.
|
|
81
|
+
*
|
|
82
|
+
* @param timeout
|
|
83
|
+
*/
|
|
84
|
+
timeoutAfter(timeout) {
|
|
85
|
+
return new EnsureEventually(this.actual, this.expectation, this.instantiationLocation(), timeout);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* @inheritDoc
|
|
89
|
+
*/
|
|
90
|
+
async performAs(actor) {
|
|
91
|
+
await core_1.ScheduleWork.as(actor).repeatUntil(() => actor.answer(this.expectation.isMetFor(this.actual)), {
|
|
92
|
+
exitCondition: outcome => outcome instanceof core_1.ExpectationMet,
|
|
93
|
+
delayBetweenInvocations: (invocation) => invocation === 0
|
|
94
|
+
? core_1.Duration.ofMilliseconds(0) // perform the first evaluation straight away
|
|
95
|
+
: core_1.Duration.ofMilliseconds(2 ** invocation * 100),
|
|
96
|
+
timeout: this.timeout,
|
|
97
|
+
errorHandler: (error, outcome) => {
|
|
98
|
+
if (error instanceof core_1.ListItemNotFoundError) {
|
|
99
|
+
return; // ignore, lists might get populated later
|
|
100
|
+
}
|
|
101
|
+
if (error instanceof core_1.TimeoutExpiredError) {
|
|
102
|
+
const actualDescription = (0, core_1.d) `${this.actual}`;
|
|
103
|
+
const message = outcome ? `Expected ${actualDescription} to eventually ${outcome === null || outcome === void 0 ? void 0 : outcome.message}` : error.message;
|
|
104
|
+
throw core_1.RaiseErrors.as(actor).create(core_1.AssertionError, {
|
|
105
|
+
message,
|
|
106
|
+
expectation: outcome === null || outcome === void 0 ? void 0 : outcome.expectation,
|
|
107
|
+
diff: outcome && { expected: outcome === null || outcome === void 0 ? void 0 : outcome.expected, actual: outcome === null || outcome === void 0 ? void 0 : outcome.actual },
|
|
108
|
+
location: this.instantiationLocation(),
|
|
109
|
+
cause: error,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
throw error;
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Overrides the default {@apilink AssertionError} thrown when
|
|
118
|
+
* the actual value does not meet the expectation.
|
|
119
|
+
*
|
|
120
|
+
* @param typeOfRuntimeError
|
|
121
|
+
* A constructor function producing a subtype of {@apilink RuntimeError} to throw, e.g. {@apilink TestCompromisedError}
|
|
122
|
+
*
|
|
123
|
+
* @param message
|
|
124
|
+
* The message explaining the failure
|
|
125
|
+
*/
|
|
126
|
+
otherwiseFailWith(typeOfRuntimeError, message) {
|
|
127
|
+
const location = this.instantiationLocation();
|
|
128
|
+
return core_1.Interaction.where(this.toString(), async (actor) => {
|
|
129
|
+
try {
|
|
130
|
+
await this.performAs(actor);
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
throw core_1.RaiseErrors.as(actor).create(typeOfRuntimeError, {
|
|
134
|
+
message: message !== null && message !== void 0 ? message : error.message,
|
|
135
|
+
location,
|
|
136
|
+
cause: error,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
exports.EnsureEventually = EnsureEventually;
|
|
143
|
+
//# sourceMappingURL=EnsureEventually.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EnsureEventually.js","sourceRoot":"","sources":["../src/EnsureEventually.ts"],"names":[],"mappings":"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"}
|
package/lib/index.d.ts
CHANGED
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC"}
|
package/lib/index.js
CHANGED
|
@@ -15,5 +15,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./Ensure"), exports);
|
|
18
|
+
__exportStar(require("./EnsureEventually"), exports);
|
|
18
19
|
__exportStar(require("./expectations"), exports);
|
|
19
20
|
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,iDAA+B"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,qDAAmC;AACnC,iDAA+B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@serenity-js/assertions",
|
|
3
|
-
"version": "3.0.0-rc.
|
|
3
|
+
"version": "3.0.0-rc.45",
|
|
4
4
|
"description": "Screenplay-style assertion library",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Jan Molak",
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"testing"
|
|
27
27
|
],
|
|
28
28
|
"scripts": {
|
|
29
|
-
"clean": "rimraf
|
|
30
|
-
"test": "nyc mocha --config ../../.mocharc.yml 'spec/**/*.spec.*'",
|
|
29
|
+
"clean": "rimraf target",
|
|
30
|
+
"test": "nyc mocha --config ../../.mocharc.yml --reporter-options 'mocha-multi=.mocha-reporters.json' 'spec/**/*.spec.*'",
|
|
31
31
|
"compile": "rimraf lib && tsc --project tsconfig.build.json"
|
|
32
32
|
},
|
|
33
33
|
"repository": {
|
|
@@ -40,20 +40,21 @@
|
|
|
40
40
|
},
|
|
41
41
|
"engines": {
|
|
42
42
|
"node": "^14 || ^16 || ^18",
|
|
43
|
-
"npm": "^6 || ^7 || ^8"
|
|
43
|
+
"npm": "^6 || ^7 || ^8 || ^9"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@serenity-js/core": "3.0.0-rc.
|
|
47
|
-
"tiny-types": "^1.19.
|
|
46
|
+
"@serenity-js/core": "3.0.0-rc.45",
|
|
47
|
+
"tiny-types": "^1.19.1"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@integration/testing-tools": "3.0.0",
|
|
51
51
|
"@types/chai": "^4.3.4",
|
|
52
52
|
"@types/mocha": "^10.0.1",
|
|
53
53
|
"mocha": "^10.2.0",
|
|
54
|
+
"mocha-multi": "^1.1.7",
|
|
54
55
|
"nyc": "15.1.0",
|
|
55
56
|
"ts-node": "^10.9.1",
|
|
56
57
|
"typescript": "^4.9.5"
|
|
57
58
|
},
|
|
58
|
-
"gitHead": "
|
|
59
|
+
"gitHead": "4c5c6cdb30f726b51d1567862074bb6fa9b5aa66"
|
|
59
60
|
}
|
package/src/Ensure.ts
CHANGED
|
@@ -17,6 +17,8 @@ import {
|
|
|
17
17
|
} from '@serenity-js/core';
|
|
18
18
|
import { FileSystemLocation } from '@serenity-js/core/lib/io';
|
|
19
19
|
|
|
20
|
+
import { EnsureEventually } from './EnsureEventually';
|
|
21
|
+
|
|
20
22
|
/**
|
|
21
23
|
* The {@apilink Interaction|interaction} to `Ensure`
|
|
22
24
|
* verifies if the resolved value of the provided {@apilink Answerable}
|
|
@@ -67,6 +69,11 @@ import { FileSystemLocation } from '@serenity-js/core/lib/io';
|
|
|
67
69
|
export class Ensure<Actual> extends Interaction {
|
|
68
70
|
|
|
69
71
|
/**
|
|
72
|
+
* Creates an {@apilink Interaction|interaction} to `Ensure`, which
|
|
73
|
+
* verifies if the resolved value of the provided {@apilink Answerable}
|
|
74
|
+
* meets the specified {@apilink Expectation}.
|
|
75
|
+
* If not, it immediately throws an {@apilink AssertionError}.
|
|
76
|
+
*
|
|
70
77
|
* @param {Answerable<Actual_Type>} actual
|
|
71
78
|
* An {@apilink Answerable} describing the actual state of the system.
|
|
72
79
|
*
|
|
@@ -79,6 +86,27 @@ export class Ensure<Actual> extends Interaction {
|
|
|
79
86
|
return new Ensure(actual, expectation, Activity.callerLocation(5));
|
|
80
87
|
}
|
|
81
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Creates an {@apilink Interaction|interaction} to {@apilink EnsureEventually}, which
|
|
91
|
+
* verifies if the resolved value of the provided {@apilink Answerable}
|
|
92
|
+
* meets the specified {@apilink Expectation} within the expected timeframe.
|
|
93
|
+
*
|
|
94
|
+
* If the expectation is not met by the time the timeout expires, the interaction throws an {@apilink AssertionError}.
|
|
95
|
+
* `EnsureEventually` ignores retries the evaluation if resolving the `actual` results in an {@apilink OptionalNotPresentError},
|
|
96
|
+
* but rethrows any other errors.
|
|
97
|
+
*
|
|
98
|
+
* @param {Answerable<Actual_Type>} actual
|
|
99
|
+
* An {@apilink Answerable} describing the actual state of the system.
|
|
100
|
+
*
|
|
101
|
+
* @param {Expectation<Actual_Type>} expectation
|
|
102
|
+
* An {@apilink Expectation} you expect the `actual` value to meet
|
|
103
|
+
*
|
|
104
|
+
* @returns {Ensure<Actual_Type>}
|
|
105
|
+
*/
|
|
106
|
+
static eventually<Actual_Type>(actual: Answerable<Actual_Type>, expectation: Expectation<Actual_Type>): EnsureEventually<Actual_Type> {
|
|
107
|
+
return new EnsureEventually(actual, expectation, Activity.callerLocation(5));
|
|
108
|
+
}
|
|
109
|
+
|
|
82
110
|
/**
|
|
83
111
|
* @param actual
|
|
84
112
|
* @param expectation
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Answerable,
|
|
3
|
+
AnswersQuestions,
|
|
4
|
+
AssertionError,
|
|
5
|
+
CollectsArtifacts,
|
|
6
|
+
d,
|
|
7
|
+
Duration,
|
|
8
|
+
Expectation,
|
|
9
|
+
ExpectationMet,
|
|
10
|
+
ExpectationOutcome,
|
|
11
|
+
Interaction,
|
|
12
|
+
ListItemNotFoundError,
|
|
13
|
+
RaiseErrors,
|
|
14
|
+
RuntimeError,
|
|
15
|
+
ScheduleWork,
|
|
16
|
+
TimeoutExpiredError,
|
|
17
|
+
UsesAbilities,
|
|
18
|
+
} from '@serenity-js/core';
|
|
19
|
+
import { FileSystemLocation } from '@serenity-js/core/lib/io';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The {@apilink Interaction|interaction} to `EnsureEventually`
|
|
23
|
+
* verifies if the resolved value of the provided {@apilink Answerable}
|
|
24
|
+
* meets the specified {@apilink Expectation} within the expected timeframe.
|
|
25
|
+
*
|
|
26
|
+
* If the expectation is not met by the time the timeout expires, the interaction throws an {@apilink AssertionError}.
|
|
27
|
+
* `EnsureEventually` retries the evaluation if resolving the `actual` results in an {@apilink ListItemNotFoundError},
|
|
28
|
+
* but rethrows any other errors.
|
|
29
|
+
*
|
|
30
|
+
* :::tip Use the factory method
|
|
31
|
+
* Use the factory method {@apilink Ensure.eventually} to instantiate this interaction.
|
|
32
|
+
* :::
|
|
33
|
+
*
|
|
34
|
+
* ## Basic usage with dynamic values
|
|
35
|
+
* ```ts
|
|
36
|
+
* import { actorCalled } from '@serenity-js/core'
|
|
37
|
+
* import { Ensure, equals } from '@serenity-js/assertions'
|
|
38
|
+
* import { Text, PageElement, By } from '@serenity-js/web'
|
|
39
|
+
*
|
|
40
|
+
* await actorCalled('Erica').attemptsTo(
|
|
41
|
+
* Ensure.eventually(
|
|
42
|
+
* Text.of(PageElement.located(By.css('h1'))),
|
|
43
|
+
* equals('Learn Serenity/JS!')
|
|
44
|
+
* )
|
|
45
|
+
* )
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* ## Composing expectations with `and`
|
|
49
|
+
*
|
|
50
|
+
* ```ts
|
|
51
|
+
* import { actorCalled } from '@serenity-js/core'
|
|
52
|
+
* import { and, Ensure, startsWith, endsWith } from '@serenity-js/assertions'
|
|
53
|
+
* import { Text, PageElement, By } from '@serenity-js/web'
|
|
54
|
+
*
|
|
55
|
+
* await actorCalled('Erica').attemptsTo(
|
|
56
|
+
* Ensure.eventually(
|
|
57
|
+
* Text.of(PageElement.located(By.css('h1'))),
|
|
58
|
+
* and(startsWith('Serenity'), endsWith('!'))
|
|
59
|
+
* )
|
|
60
|
+
* )
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* ## Overriding the type of Error thrown upon assertion failure
|
|
64
|
+
*
|
|
65
|
+
* ```ts
|
|
66
|
+
* import { actorCalled } from '@serenity-js/core'
|
|
67
|
+
* import { and, Ensure, startsWith, endsWith } from '@serenity-js/assertions'
|
|
68
|
+
* import { Text, PageElement, By } from '@serenity-js/web'
|
|
69
|
+
*
|
|
70
|
+
* await actorCalled('Erica').attemptsTo(
|
|
71
|
+
* Ensure.eventually(
|
|
72
|
+
* Text.of(PageElement.located(By.css('h1'))),
|
|
73
|
+
* and(startsWith('Serenity'), endsWith('!'))
|
|
74
|
+
* ).otherwiseFailWith(LogicError, `Looks like we're not on the right page`)
|
|
75
|
+
* )
|
|
76
|
+
* ```
|
|
77
|
+
*
|
|
78
|
+
* @experimental
|
|
79
|
+
*
|
|
80
|
+
* @group Activities
|
|
81
|
+
*/
|
|
82
|
+
export class EnsureEventually<Actual> extends Interaction {
|
|
83
|
+
/**
|
|
84
|
+
* @param actual
|
|
85
|
+
* @param expectation
|
|
86
|
+
* @param location
|
|
87
|
+
* @param timeout
|
|
88
|
+
*/
|
|
89
|
+
constructor(
|
|
90
|
+
protected readonly actual: Answerable<Actual>,
|
|
91
|
+
protected readonly expectation: Expectation<Actual>,
|
|
92
|
+
location: FileSystemLocation,
|
|
93
|
+
protected readonly timeout?: Duration,
|
|
94
|
+
) {
|
|
95
|
+
super(d`#actor ensures that ${ actual } does eventually ${ expectation }`, location);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Override the default timeout set via {@apilink SerenityConfig.interactionTimeout}.
|
|
100
|
+
*
|
|
101
|
+
* @param timeout
|
|
102
|
+
*/
|
|
103
|
+
timeoutAfter(timeout: Duration): EnsureEventually<Actual> {
|
|
104
|
+
return new EnsureEventually<Actual>(this.actual, this.expectation, this.instantiationLocation(), timeout);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @inheritDoc
|
|
109
|
+
*/
|
|
110
|
+
async performAs(actor: UsesAbilities & AnswersQuestions & CollectsArtifacts): Promise<void> {
|
|
111
|
+
await ScheduleWork.as(actor).repeatUntil<ExpectationOutcome>(
|
|
112
|
+
() => actor.answer(this.expectation.isMetFor(this.actual)),
|
|
113
|
+
{
|
|
114
|
+
exitCondition: outcome =>
|
|
115
|
+
outcome instanceof ExpectationMet,
|
|
116
|
+
|
|
117
|
+
delayBetweenInvocations: (invocation) => invocation === 0
|
|
118
|
+
? Duration.ofMilliseconds(0) // perform the first evaluation straight away
|
|
119
|
+
: Duration.ofMilliseconds(2 ** invocation * 100), // use simple exponential backoff strategy for subsequent calls
|
|
120
|
+
|
|
121
|
+
timeout: this.timeout,
|
|
122
|
+
|
|
123
|
+
errorHandler: (error, outcome) => {
|
|
124
|
+
if (error instanceof ListItemNotFoundError) {
|
|
125
|
+
return; // ignore, lists might get populated later
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (error instanceof TimeoutExpiredError) {
|
|
129
|
+
|
|
130
|
+
const actualDescription = d`${ this.actual }`;
|
|
131
|
+
const message = outcome ? `Expected ${ actualDescription } to eventually ${ outcome?.message }` : error.message;
|
|
132
|
+
|
|
133
|
+
throw RaiseErrors.as(actor).create(AssertionError, {
|
|
134
|
+
message,
|
|
135
|
+
expectation: outcome?.expectation,
|
|
136
|
+
diff: outcome && { expected: outcome?.expected, actual: outcome?.actual },
|
|
137
|
+
location: this.instantiationLocation(),
|
|
138
|
+
cause: error,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
throw error;
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Overrides the default {@apilink AssertionError} thrown when
|
|
150
|
+
* the actual value does not meet the expectation.
|
|
151
|
+
*
|
|
152
|
+
* @param typeOfRuntimeError
|
|
153
|
+
* A constructor function producing a subtype of {@apilink RuntimeError} to throw, e.g. {@apilink TestCompromisedError}
|
|
154
|
+
*
|
|
155
|
+
* @param message
|
|
156
|
+
* The message explaining the failure
|
|
157
|
+
*/
|
|
158
|
+
otherwiseFailWith(typeOfRuntimeError: new (message: string, cause?: Error) => RuntimeError, message?: string): Interaction {
|
|
159
|
+
const location = this.instantiationLocation();
|
|
160
|
+
|
|
161
|
+
return Interaction.where(this.toString(), async actor => {
|
|
162
|
+
try {
|
|
163
|
+
await this.performAs(actor);
|
|
164
|
+
} catch (error) {
|
|
165
|
+
throw RaiseErrors.as(actor).create(typeOfRuntimeError, {
|
|
166
|
+
message: message ?? error.message,
|
|
167
|
+
location,
|
|
168
|
+
cause: error,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
package/src/index.ts
CHANGED