@cepharum/contextual-gherkin 1.2.6 → 2.0.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/lib/api.js CHANGED
@@ -45,7 +45,7 @@ class Api {
45
45
  *
46
46
  * @param {string} typeOfElement names type of element to be looked up
47
47
  * @param {SimpleSelector} defaultSelector selector to use as fallback
48
- * @returns {Promise<Context>} promises node describing performed search, its context and any yielded matches
48
+ * @returns {Context} node describing performed search, its context and any yielded matches
49
49
  * @throws Error if no or multiple selectors have been found for named type and no default has been provided
50
50
  */
51
51
  find( typeOfElement, defaultSelector = undefined ) {
@@ -58,7 +58,7 @@ class Api {
58
58
  * @param {string} typeOfElement names of type of element to look up
59
59
  * @param {string|number} index provided index of match to fetch from type's queue of previous matches
60
60
  * @param {Cardinality} cardinality selects expected number of elements in match to find
61
- * @returns {Context} node describing found previous match of elements of named type
61
+ * @returns {Promise<Context>} promise for node describing found previous match of elements of named type
62
62
  */
63
63
  async get( typeOfElement, index, cardinality = null ) {
64
64
  const alias = this.getNormalizedAliases( typeOfElement )[0];
@@ -105,7 +105,7 @@ class Api {
105
105
  * Conveniently recovers context based on provided description.
106
106
  *
107
107
  * @param {ContextualWord} context describes context to recover
108
- * @returns {Promise<Context>} promises tracked node describing result of a previous search
108
+ * @returns {Promise<Context>} promise for tracked node describing result of a previous search
109
109
  */
110
110
  async getContextFor( context ) {
111
111
  try {
package/lib/context.js CHANGED
@@ -76,7 +76,7 @@ class Context {
76
76
  *
77
77
  * @param {string} typeOfElement names type of elements to search
78
78
  * @param {SimpleSelector} defaultSelector selector to use as fallback
79
- * @returns {Promise<Context>} node describing found elements and their processing context
79
+ * @returns {Context} node describing found elements and their processing context
80
80
  */
81
81
  find( typeOfElement, defaultSelector = undefined ) {
82
82
  const type = this.api.toNormalizedSingularName( typeOfElement );
@@ -91,7 +91,7 @@ class Context {
91
91
  matches = Selector( query, dependencies ? { dependencies } : {} );
92
92
  }
93
93
 
94
- return Promise.resolve( new Context( this.testController, this, this.api, { matches, type, selectors } ) );
94
+ return new this.constructor( this.testController, this, this.api, { matches, type, selectors } );
95
95
  }
96
96
 
97
97
  /**
@@ -140,7 +140,7 @@ class Context {
140
140
  *
141
141
  * @param {string} typeOfElement names type of elements to focus on
142
142
  * @param {SimpleSelector} defaultSelector selector to use as fallback
143
- * @returns {Promise<Context>} node describing matches of filtering and their processing context
143
+ * @returns {Context} node describing matches of filtering and their processing context
144
144
  */
145
145
  filter( typeOfElement, defaultSelector = undefined ) {
146
146
  if ( !this.matches ) {
@@ -151,12 +151,12 @@ class Context {
151
151
  const { query, dependencies } = this.getSelector( type, defaultSelector );
152
152
  const matches = query === "" ? this.matches : Selector( this.matches ).filter( ...dependencies ? [ query, dependencies ] : [query] );
153
153
 
154
- return Promise.resolve( new this.constructor( this.testController, this, this.api, {
154
+ return new this.constructor( this.testController, this, this.api, {
155
155
  matches,
156
156
  type: this.type,
157
157
  selectors: this.selectors,
158
158
  cardinality: this.cardinality
159
- } ) );
159
+ } );
160
160
  }
161
161
 
162
162
  /**
@@ -165,7 +165,7 @@ class Context {
165
165
  *
166
166
  * @param {(node:Element, index:number) => boolean} fn callback invoked in context of browser
167
167
  * @param {Object<string,any>} dependencies serializable data exposed in browser for use in callback
168
- * @returns {Promise<Context>} node describing matches of filtering and their processing context
168
+ * @returns {Context} node describing matches of filtering and their processing context
169
169
  */
170
170
  filterFn( fn, dependencies = {} ) {
171
171
  if ( !this.matches ) {
@@ -175,12 +175,12 @@ class Context {
175
175
  const matches = Selector( this.matches, { boundTestRun: this.testController } )
176
176
  .filter( fn, dependencies );
177
177
 
178
- return Promise.resolve( new this.constructor( this.testController, this, this.api, {
178
+ return new this.constructor( this.testController, this, this.api, {
179
179
  matches,
180
180
  type: this.type,
181
181
  selectors: this.selectors,
182
182
  cardinality: this.cardinality,
183
- } ) );
183
+ } );
184
184
  }
185
185
 
186
186
  /**
@@ -192,7 +192,7 @@ class Context {
192
192
  * @param {SimpleSelector} defaultSelector selector to use as fallback on searching sub-elements as candidates
193
193
  * @param {(node: Element, index: number) => boolean} fn callback invoked in context of browser
194
194
  * @param {Object<string,any>} context serializable data provided as context (via `this`) of invoked callback
195
- * @returns {Promise<Context>} node describing matches of filtering and their processing context
195
+ * @returns {Context} node describing matches of filtering and their processing context
196
196
  */
197
197
  filterBySubsWithFn( typeOfSub, defaultSelector, fn, context = {} ) {
198
198
  const { query, dependencies } = this.getSelector( typeOfSub, defaultSelector ) || { query: false };
@@ -232,7 +232,7 @@ class Context {
232
232
  * @param {SimpleSelector} defaultSelector selector to use as fallback on searching sub-elements as candidates
233
233
  * @param {string|RegExp} text text to be contained in either selected sub-element for matching
234
234
  * @param {boolean} partially if true, provided text or regular expression does not have to match whole textual content of matching subs
235
- * @returns {Promise<Context>} promises context describing matches reduced to those having subs with given text
235
+ * @returns {Context} node describing matches reduced to those having subs with given text
236
236
  */
237
237
  filterBySubsWithText( typeOfSub, defaultSelector, text, partially = false ) {
238
238
  if ( typeof text === "string" ) {
@@ -263,13 +263,9 @@ class Context {
263
263
  *
264
264
  * @param {string|RegExp} name name of attribute to look up per matching element
265
265
  * @param {string|RegExp|function(any):boolean} value value expected in named attribute of either element, callback for custom test of attribute value
266
- * @returns {Promise<Context>} promises another node listing those matches of current node matching named attribute
266
+ * @returns {Context} node listing those matches of current node matching named attribute
267
267
  */
268
- async withAttribute( name, value ) {
269
- if ( ! await this.matches.count ) {
270
- throw new TypeError( "there are no matches to filter" );
271
- }
272
-
268
+ withAttribute( name, value ) {
273
269
  if ( typeof value !== "function" && !( value instanceof RegExp ) ) {
274
270
  value = normalizedString( value );
275
271
  }
@@ -305,13 +301,9 @@ class Context {
305
301
  *
306
302
  * @param {string} name name of DOM property to look up per matching element
307
303
  * @param {string|RegExp|function(any):boolean} value value expected in named DOM property of either element, callback for custom test of property value
308
- * @returns {Promise<Context>} promises another node listing those matches of current node matching named property
304
+ * @returns {Context} node listing those matches of current node matching named property
309
305
  */
310
- async withProperty( name, value ) {
311
- if ( ! await this.matches.count ) {
312
- throw new TypeError( "there are no matches to filter" );
313
- }
314
-
306
+ withProperty( name, value ) {
315
307
  if ( value === "string" ) {
316
308
  value = normalizedString( value );
317
309
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cepharum/contextual-gherkin",
3
- "version": "1.2.6",
3
+ "version": "2.0.0",
4
4
  "description": "flexible step definitions for Gherkin",
5
5
  "author": "cepharum GmbH <thomas.urban@cepharum.de>",
6
6
  "homepage": "https://cepharum-foss.gitlab.io/contextual-gherkin/",
@@ -2,11 +2,10 @@ const { When } = require( "@cucumber/cucumber" );
2
2
  const { Api } = require( "../lib/api" );
3
3
 
4
4
  const actAndTrack = async( testController, cardinal, refined, action, input ) => {
5
- const target = await refined.find( "$" + action, false );
6
- const targetCount = await target.matches.count;
5
+ const target = refined.find( "$" + action, false );
7
6
 
8
- await testController.expect( targetCount ).gt( 0, `there is no $${action} target for selected ${cardinal.word} to ${input == null ? action : `enter '${input}' into`}` ); // eslint-disable-line max-len
9
- await testController.expect( targetCount ).eql( 1, `there is no single $${action} target for selected ${cardinal.word} to ${input == null ? action : `enter '${input}' into`}` ); // eslint-disable-line max-len
7
+ await testController.expect( target.matches.count ).gt( 0, `there is no $${action} target for selected ${cardinal.word} to ${input == null ? action : `enter '${input}' into`}` ); // eslint-disable-line max-len
8
+ await testController.expect( target.matches.count ).eql( 1, `there is no single $${action} target for selected ${cardinal.word} to ${input == null ? action : `enter '${input}' into`}` ); // eslint-disable-line max-len
10
9
 
11
10
  await refined.detach();
12
11
 
@@ -53,17 +52,14 @@ async function globalActionByAttribute( testController, cardinal, name, value, a
53
52
  await testController.debug();
54
53
  }
55
54
 
56
- const matches = await Api.access( testController ).find( cardinal.word );
57
-
58
55
  if ( cardinal.cardinality !== "singular" ) {
59
56
  await testController.expect( false ).eql( true, `selector is not addressing a single ${cardinal.word} to ${input == null ? action : `enter '${input}' into`}` ); // eslint-disable-line max-len
60
57
  }
61
58
 
62
- const refined = await matches.withAttribute( name, value );
63
- const matchCount = await refined.matches.count;
59
+ const refined = Api.access( testController ).find( cardinal.word ).withAttribute( name, value );
64
60
 
65
- await testController.expect( matchCount ).gt( 0, `there is no matching ${cardinal.word} with attribute ${name} == "${value}" to ${input == null ? action : `enter '${input}' into`}` ); // eslint-disable-line max-len
66
- await testController.expect( matchCount ).eql( 1, `there is no single matching ${cardinal.word} with attribute ${name} == "${value}" to ${input == null ? action : `enter '${input}' into`}` ); // eslint-disable-line max-len
61
+ await testController.expect( refined.matches.count ).gt( 0, `there is no matching ${cardinal.word} with attribute ${name} == "${value}" to ${input == null ? action : `enter '${input}' into`}` ); // eslint-disable-line max-len
62
+ await testController.expect( refined.matches.count ).eql( 1, `there is no single matching ${cardinal.word} with attribute ${name} == "${value}" to ${input == null ? action : `enter '${input}' into`}` ); // eslint-disable-line max-len
67
63
 
68
64
  await actAndTrack( testController, cardinal, refined, action, input );
69
65
  }
@@ -94,18 +90,17 @@ async function globalActionByClass( testController, cardinal, className, action,
94
90
  await testController.debug();
95
91
  }
96
92
 
97
- const matches = await Api.access( testController ).find( cardinal.word );
98
-
99
93
  if ( cardinal.cardinality !== "singular" ) {
100
94
  await testController.expect( false ).eql( true, `selector is not addressing a single ${cardinal.word} ${negated ? "not " : ""}marked as ${className} to ${input == null ? action : `enter '${input}' into`}` );
101
95
  }
102
96
 
97
+ const matches = Api.access( testController ).find( cardinal.word );
98
+
103
99
  const { query: property } = matches.getSelector( "#classList", "classList" );
104
- const refined = await matches.filterBySubsWithFn( "$classList", false, node => node?.[property]?.contains( className ) ^ negated, { property, className, negated } );
105
- const matchCount = await refined.matches.count;
100
+ const refined = matches.filterBySubsWithFn( "$classList", false, node => node?.[property]?.contains( className ) ^ negated, { property, className, negated } );
106
101
 
107
- await testController.expect( matchCount ).gt( 0, `there is no matching ${cardinal.word} ${negated ? "not " : ""}marked as ${className} to ${input == null ? action : `enter '${input}' into`}` );
108
- await testController.expect( matchCount ).eql( 1, `there is no single matching ${cardinal.word} ${negated ? "not " : ""}marked as ${className} to ${input == null ? action : `enter '${input}' into`}` );
102
+ await testController.expect( refined.matches.count ).gt( 0, `there is no matching ${cardinal.word} ${negated ? "not " : ""}marked as ${className} to ${input == null ? action : `enter '${input}' into`}` );
103
+ await testController.expect( refined.matches.count ).eql( 1, `there is no single matching ${cardinal.word} ${negated ? "not " : ""}marked as ${className} to ${input == null ? action : `enter '${input}' into`}` );
109
104
 
110
105
  await actAndTrack( testController, cardinal, refined, action, input );
111
106
  }
@@ -133,17 +128,14 @@ async function globalActionByProperty( testController, cardinal, name, value, ac
133
128
  await testController.debug();
134
129
  }
135
130
 
136
- const matches = await Api.access( testController ).find( cardinal.word );
137
-
138
131
  if ( cardinal.cardinality !== "singular" ) {
139
132
  await testController.expect( false ).eql( true, `selector is not addressing a single ${cardinal.word} with property ${name} == "${value}" to ${input == null ? action : `enter '${input}' into`}` ); // eslint-disable-line max-len
140
133
  }
141
134
 
142
- const refined = await matches.withProperty( name, value );
143
- const matchCount = await refined.matches.count;
135
+ const refined = Api.access( testController ).find( cardinal.word ).withProperty( name, value );
144
136
 
145
- await testController.expect( matchCount ).gt( 0, `there is no matching ${cardinal.word} with property ${name} == "${value}" to ${input == null ? action : `enter '${input}' into`}` ); // eslint-disable-line max-len
146
- await testController.expect( matchCount ).eql( 1, `there is no single matching ${cardinal.word} with property ${name} == "${value}" to ${input == null ? action : `enter '${input}' into`}` ); // eslint-disable-line max-len
137
+ await testController.expect( refined.matches.count ).gt( 0, `there is no matching ${cardinal.word} with property ${name} == "${value}" to ${input == null ? action : `enter '${input}' into`}` ); // eslint-disable-line max-len
138
+ await testController.expect( refined.matches.count ).eql( 1, `there is no single matching ${cardinal.word} with property ${name} == "${value}" to ${input == null ? action : `enter '${input}' into`}` ); // eslint-disable-line max-len
147
139
 
148
140
  await actAndTrack( testController, cardinal, refined, action, input );
149
141
  }
@@ -180,17 +172,14 @@ async function globalActionByLabel( testController, cardinal, label, partially,
180
172
  await testController.debug();
181
173
  }
182
174
 
183
- const matches = await Api.access( testController ).find( cardinal.word );
184
-
185
175
  if ( cardinal.cardinality !== "singular" ) {
186
176
  await testController.expect( false ).eql( true, `selector is not addressing a single ${cardinal.word} with ${partially ? "partial " : ""}label "${label}" to ${input == null ? action : `enter '${input}' into`}` );
187
177
  }
188
178
 
189
- const refined = await matches.filterBySubsWithText( "$label", "label", label, partially );
190
- const matchCount = await refined.matches.count;
179
+ const refined = Api.access( testController ).find( cardinal.word ).filterBySubsWithText( "$label", "label", label, partially );
191
180
 
192
- await testController.expect( matchCount ).gt( 0, `there is no matching ${cardinal.word} with ${partially ? "partial " : ""}label "${label}" to ${input == null ? action : `enter '${input}' into`}` );
193
- await testController.expect( matchCount ).eql( 1, `there is no single matching ${cardinal.word} with ${partially ? "partial " : ""}label "${label}" to ${input == null ? action : `enter '${input}' into`}` );
181
+ await testController.expect( refined.matches.count ).gt( 0, `there is no matching ${cardinal.word} with ${partially ? "partial " : ""}label "${label}" to ${input == null ? action : `enter '${input}' into`}` );
182
+ await testController.expect( refined.matches.count ).eql( 1, `there is no single matching ${cardinal.word} with ${partially ? "partial " : ""}label "${label}" to ${input == null ? action : `enter '${input}' into`}` );
194
183
 
195
184
  await actAndTrack( testController, cardinal, refined, action, input );
196
185
  }
@@ -227,17 +216,14 @@ async function globalActionByTextContent( testController, cardinal, text, partia
227
216
  await testController.debug();
228
217
  }
229
218
 
230
- const matches = await Api.access( testController ).find( cardinal.word );
231
-
232
219
  if ( cardinal.cardinality !== "singular" ) {
233
220
  await testController.expect( false ).eql( true, `selector is not addressing a single ${cardinal.word} ${partially ? "partially " : ""}reading "${text}" to ${input == null ? action : `enter '${input}' into`}` );
234
221
  }
235
222
 
236
- const refined = await matches.filterBySubsWithText( "$text", false, text, partially );
237
- const matchCount = await refined.matches.count;
223
+ const refined = Api.access( testController ).find( cardinal.word ).filterBySubsWithText( "$text", false, text, partially );
238
224
 
239
- await testController.expect( matchCount ).gt( 0, `there is no matching ${cardinal.word} ${partially ? "partially " : ""}reading "${text}" to ${input == null ? action : `enter '${input}' into`}` );
240
- await testController.expect( matchCount ).eql( 1, `there is no single matching ${cardinal.word} ${partially ? "partially " : ""}reading "${text}" to ${input == null ? action : `enter '${input}' into`}` );
225
+ await testController.expect( refined.matches.count ).gt( 0, `there is no matching ${cardinal.word} ${partially ? "partially " : ""}reading "${text}" to ${input == null ? action : `enter '${input}' into`}` );
226
+ await testController.expect( refined.matches.count ).eql( 1, `there is no single matching ${cardinal.word} ${partially ? "partially " : ""}reading "${text}" to ${input == null ? action : `enter '${input}' into`}` );
241
227
 
242
228
  await actAndTrack( testController, cardinal, refined, action, input );
243
229
  }
@@ -16,9 +16,9 @@ async function globalFind( testController, cardinal ) {
16
16
  await testController.debug();
17
17
  }
18
18
 
19
- const found = await Api.access( testController ).find( cardinal.word );
20
-
21
- await found.checkCardinalWord( cardinal );
19
+ await Api.access( testController )
20
+ .find( cardinal.word )
21
+ .checkCardinalWord( cardinal );
22
22
  }
23
23
 
24
24
  Given( "there is/are {cardinal-word} with ID/id {text-or-regexp}", ( t, [ cardinal, id ] ) => globalFindByAttribute( t, cardinal, "id", id ) );
@@ -42,10 +42,10 @@ async function globalFindByAttribute( testController, cardinal, name, value ) {
42
42
  await testController.debug();
43
43
  }
44
44
 
45
- const matches = await Api.access( testController ).find( cardinal.word );
46
- const refined = await matches.withAttribute( name, value );
47
-
48
- await refined.checkCardinalWord( cardinal, true, `with ${name} ${value instanceof RegExp ? "matching" : "equals"} ${value}` );
45
+ await Api.access( testController )
46
+ .find( cardinal.word )
47
+ .withAttribute( name, value )
48
+ .checkCardinalWord( cardinal, true, `with ${name} ${value instanceof RegExp ? "matching" : "equals"} ${value}` );
49
49
  }
50
50
 
51
51
  Given( "there is/are {cardinal-word} with value {text-or-regexp}", ( t, [ cardinal, value ] ) => globalFindByProperty( t, cardinal, "value", value ) );
@@ -69,10 +69,10 @@ async function globalFindByProperty( testController, cardinal, name, value ) {
69
69
  await testController.debug();
70
70
  }
71
71
 
72
- const matches = await Api.access( testController ).find( cardinal.word );
73
- const refined = await matches.withProperty( name, value );
74
-
75
- await refined.checkCardinalWord( cardinal, true, typeof value === "function" ? `${value() ? "not " : ""}${name}` : `with ${name} ${value instanceof RegExp ? "matching" : "equals"} ${value}` );
72
+ await Api.access( testController )
73
+ .find( cardinal.word )
74
+ .withProperty( name, value )
75
+ .checkCardinalWord( cardinal, true, typeof value === "function" ? `${value() ? "not " : ""}${name}` : `with ${name} ${value instanceof RegExp ? "matching" : "equals"} ${value}` );
76
76
  }
77
77
 
78
78
  Given( "there is/are {cardinal-word} marked/tagged as {word}", ( t, [ cardinal, mark ] ) => globalFindByClass( t, cardinal, mark, false ) );
@@ -92,9 +92,9 @@ async function globalFindByClass( testController, cardinal, className, negated )
92
92
  await testController.debug();
93
93
  }
94
94
 
95
- const matches = await Api.access( testController ).find( cardinal.word );
95
+ const matches = Api.access( testController ).find( cardinal.word );
96
96
  const { query: property } = matches.getSelector( "#classList", "classList" );
97
- const refined = await matches.filterBySubsWithFn( "$classList", false, node => node?.[property]?.contains( className ) ^ negated, { property, className, negated } );
97
+ const refined = matches.filterBySubsWithFn( "$classList", false, node => node?.[property]?.contains( className ) ^ negated, { property, className, negated } );
98
98
 
99
99
  await refined.checkCardinalWord( cardinal, true, `${negated ? "not " : ""}marked as "${className}"` );
100
100
  }
@@ -118,10 +118,10 @@ async function globalFindByLabel( testController, cardinal, label, partially ) {
118
118
  await testController.debug();
119
119
  }
120
120
 
121
- const matches = await Api.access( testController ).find( cardinal.word );
122
- const refined = await matches.filterBySubsWithText( "$label", "label", label, partially );
123
-
124
- await refined.checkCardinalWord( cardinal, true, `${partially ? "partially " : ""}labelled with "${label}"` );
121
+ await Api.access( testController )
122
+ .find( cardinal.word )
123
+ .filterBySubsWithText( "$label", "label", label, partially )
124
+ .checkCardinalWord( cardinal, true, `${partially ? "partially " : ""}labelled with "${label}"` );
125
125
  }
126
126
 
127
127
  Given( "there is/are {cardinal-word} reading {text-or-regexp}", ( t, [ cardinal, text ] ) => globalFindByTextContent( t, cardinal, text, false ) );
@@ -143,10 +143,10 @@ async function globalFindByTextContent( testController, cardinal, text, partiall
143
143
  await testController.debug();
144
144
  }
145
145
 
146
- const matches = await Api.access( testController ).find( cardinal.word );
147
- const refined = await matches.filterBySubsWithText( "$text", false, text, partially );
148
-
149
- await refined.checkCardinalWord( cardinal, true, `${partially ? "partially " : ""}reading "${text}"` );
146
+ await Api.access( testController )
147
+ .find( cardinal.word )
148
+ .filterBySubsWithText( "$text", false, text, partially )
149
+ .checkCardinalWord( cardinal, true, `${partially ? "partially " : ""}reading "${text}"` );
150
150
  }
151
151
 
152
152
  module.exports = {
@@ -23,16 +23,16 @@ async function localAction( testController, phrase, action, input ) {
23
23
  }
24
24
 
25
25
  const context = await Api.access( testController ).getContextFor( phrase );
26
- const matchCount = await context.matches.count;
27
26
 
28
- await testController.expect( matchCount ).gt( 0, `there is no ${phrase.word} to ${input == null ? action : `enter '${input}' into`}` );
29
- await testController.expect( matchCount ).eql( 1, `there is no single ${phrase.word} to ${input == null ? action : `enter '${input}' into`}` );
27
+ await testController.expect( context.matches.count ).gt( 0, `there is no ${phrase.word} to ${input == null ? action : `enter '${input}' into`}` );
28
+ await testController.expect( context.matches.count ).eql( 1, `there is no single ${phrase.word} to ${input == null ? action : `enter '${input}' into`}` );
29
+
30
+ const matchCount = await context.matches.count;
30
31
 
31
- const target = await context.find( "$" + action, false );
32
- const targetCount = await target.matches.count;
32
+ const target = context.find( "$" + action, false );
33
33
 
34
- await testController.expect( targetCount ).gt( 0, `there is no $${action} target for selected ${phrase.word} to ${input == null ? action : `enter '${input}' into`}` ); // eslint-disable-line max-len
35
- await testController.expect( targetCount ).eql( 1, `there is no single $${action} target for selected ${phrase.word} to ${input == null ? action : `enter '${input}' into`}` ); // eslint-disable-line max-len
34
+ await testController.expect( target.matches.count ).gt( 0, `there is no $${action} target for selected ${phrase.word} to ${input == null ? action : `enter '${input}' into`}` ); // eslint-disable-line max-len
35
+ await testController.expect( target.matches.count ).eql( 1, `there is no single $${action} target for selected ${phrase.word} to ${input == null ? action : `enter '${input}' into`}` ); // eslint-disable-line max-len
36
36
 
37
37
  await context.detach();
38
38
 
@@ -18,9 +18,10 @@ async function localFind( testController, phrase, cardinal ) {
18
18
  }
19
19
 
20
20
  const context = await Api.access( testController ).getContextFor( phrase );
21
- const matches = await context.find( cardinal.word );
22
21
 
23
- await matches.checkCardinalWord( cardinal );
22
+ await context
23
+ .find( cardinal.word )
24
+ .checkCardinalWord( cardinal );
24
25
  }
25
26
 
26
27
  Given( "{contextual-word} has/have {cardinal-word} with ID/id {text-or-regexp}", ( t, [ context, cardinal, id ] ) => localFindByAttribute( t, context, cardinal, "id", id ) );
@@ -44,10 +45,11 @@ async function localFindByAttribute( testController, phrase, cardinal, name, val
44
45
  }
45
46
 
46
47
  const context = await Api.access( testController ).getContextFor( phrase );
47
- const matches = await context.find( cardinal.word );
48
- const refined = await matches.withAttribute( name, value );
49
48
 
50
- await refined.checkCardinalWord( cardinal, true, `with ${name} ${value instanceof RegExp ? "matching" : "equals"} ${value}` );
49
+ await context
50
+ .find( cardinal.word )
51
+ .withAttribute( name, value )
52
+ .checkCardinalWord( cardinal, true, `with ${name} ${value instanceof RegExp ? "matching" : "equals"} ${value}` );
51
53
  }
52
54
 
53
55
  Given( "{contextual-word} has/have {cardinal-word} with value {text-or-regexp}", ( t, [ context, cardinal, value ] ) => localFindByProperty( t, context, cardinal, "value", value ) );
@@ -74,10 +76,11 @@ async function localFindByProperty( testController, phrase, cardinal, name, valu
74
76
  }
75
77
 
76
78
  const context = await Api.access( testController ).getContextFor( phrase );
77
- const matches = await context.find( cardinal.word );
78
- const refined = await matches.withProperty( name, value );
79
79
 
80
- await refined.checkCardinalWord( cardinal, true, typeof value === "function" ? `${value() ? "not " : ""}${name}` : `with ${name} ${value instanceof RegExp ? "matching" : "equals"} ${value}` );
80
+ await context
81
+ .find( cardinal.word )
82
+ .withProperty( name, value )
83
+ .checkCardinalWord( cardinal, true, typeof value === "function" ? `${value() ? "not " : ""}${name}` : `with ${name} ${value instanceof RegExp ? "matching" : "equals"} ${value}` );
81
84
  }
82
85
 
83
86
  Given( "{contextual-word} has/have {cardinal-word} marked/tagged as {word}", ( t, [ context, cardinal, className ] ) => localFindByClass( t, context, cardinal, className, false ) );
@@ -99,11 +102,12 @@ async function localFindByClass( testController, phrase, cardinal, className, ne
99
102
  }
100
103
 
101
104
  const context = await Api.access( testController ).getContextFor( phrase );
102
- const matches = await context.find( cardinal.word );
105
+ const matches = context.find( cardinal.word );
103
106
  const { query: property } = matches.getSelector( "#classList", "classList" );
104
- const refined = await matches.filterBySubsWithFn( "$classList", false, node => node?.[property]?.contains( className ) ^ negated, { property, className, negated } );
105
107
 
106
- await refined.checkCardinalWord( cardinal, true, `${negated ? "not " : ""}marked as "${className}"` );
108
+ await matches
109
+ .filterBySubsWithFn( "$classList", false, node => node?.[property]?.contains( className ) ^ negated, { property, className, negated } )
110
+ .checkCardinalWord( cardinal, true, `${negated ? "not " : ""}marked as "${className}"` );
107
111
  }
108
112
 
109
113
  Given( "{contextual-word} has/have {cardinal-word} with label {text-or-regexp}", ( t, [ context, cardinal, label ] ) => localFindByLabel( t, context, cardinal, label, false ) );
@@ -127,10 +131,11 @@ async function localFindByLabel( testController, phrase, cardinal, label, partia
127
131
  }
128
132
 
129
133
  const context = await Api.access( testController ).getContextFor( phrase );
130
- const matches = await context.find( cardinal.word );
131
- const refined = await matches.filterBySubsWithText( "$label", "label", label, partially );
132
134
 
133
- await refined.checkCardinalWord( cardinal, true, `${partially ? "partially " : ""}labelled with "${label}"` );
135
+ await context
136
+ .find( cardinal.word )
137
+ .filterBySubsWithText( "$label", "label", label, partially )
138
+ .checkCardinalWord( cardinal, true, `${partially ? "partially " : ""}labelled with "${label}"` );
134
139
  }
135
140
 
136
141
  Given( "{contextual-word} has/have {cardinal-word} reading {text-or-regexp}", ( t, [ context, cardinal, text ] ) => localFindByTextContent( t, context, cardinal, text, false ) );
@@ -154,10 +159,11 @@ async function localFindByTextContent( testController, phrase, cardinal, text, p
154
159
  }
155
160
 
156
161
  const context = await Api.access( testController ).getContextFor( phrase );
157
- const matches = await context.find( cardinal.word );
158
- const refined = await matches.filterBySubsWithText( "$text", false, text, partially );
159
162
 
160
- await refined.checkCardinalWord( cardinal, true, `${partially ? "partially " : ""}reading "${text}"` );
163
+ await context
164
+ .find( cardinal.word )
165
+ .filterBySubsWithText( "$text", false, text, partially )
166
+ .checkCardinalWord( cardinal, true, `${partially ? "partially " : ""}reading "${text}"` );
161
167
  }
162
168
 
163
169
  module.exports = {
@@ -25,7 +25,7 @@ async function localStateByAttribute( testController, phrase, all, name, value )
25
25
  }
26
26
 
27
27
  const context = await Api.access( testController ).getContextFor( phrase );
28
- const filtered = await context.withAttribute( name, value );
28
+ const filtered = context.withAttribute( name, value );
29
29
 
30
30
  await context.checkFilteredInContext( filtered, phrase, all );
31
31
  }
@@ -54,7 +54,7 @@ async function localStateByProperty( testController, phrase, all, name, value )
54
54
  }
55
55
 
56
56
  const context = await Api.access( testController ).getContextFor( phrase );
57
- const filtered = await context.withProperty( name, value );
57
+ const filtered = context.withProperty( name, value );
58
58
 
59
59
  await context.checkFilteredInContext( filtered, phrase, all );
60
60
  }
@@ -79,7 +79,7 @@ async function localStateByClass( testController, phrase, all, className, negate
79
79
 
80
80
  const context = await Api.access( testController ).getContextFor( phrase );
81
81
  const { query: property } = context.getSelector( "#classList", "classList" );
82
- const filtered = await context.filterBySubsWithFn( "$classList", false, node => node?.[property]?.contains( className ) ^ negated, { property, className, negated } );
82
+ const filtered = context.filterBySubsWithFn( "$classList", false, node => node?.[property]?.contains( className ) ^ negated, { property, className, negated } );
83
83
 
84
84
  await context.checkFilteredInContext( filtered, phrase, all );
85
85
  }
@@ -105,9 +105,11 @@ async function localStateByLabel( testController, phrase, text, partially ) {
105
105
  }
106
106
 
107
107
  const context = await Api.access( testController ).getContextFor( phrase );
108
- const refined = await context.filterBySubsWithText( "$label", "label", text, partially );
108
+ const cardinal = phrase.asCardinalWord( await context.matches.count );
109
109
 
110
- await refined.checkCardinalWord( phrase.asCardinalWord( await context.matches.count ), phrase.isRefining, `labelled with "${text}"` );
110
+ await context
111
+ .filterBySubsWithText( "$label", "label", text, partially )
112
+ .checkCardinalWord( cardinal, phrase.isRefining, `labelled with "${text}"` );
111
113
  }
112
114
 
113
115
  Then( "{contextual-word} read/reads {text-or-regexp}", ( t, [ context, text ] ) => localStateByTextContent( t, context, text, false ) );
@@ -133,9 +135,11 @@ async function localStateByTextContent( testController, phrase, text, partially
133
135
  }
134
136
 
135
137
  const context = await Api.access( testController ).getContextFor( phrase );
136
- const refined = await context.filterBySubsWithText( "$text", false, text, partially );
138
+ const cardinal = phrase.asCardinalWord( await context.matches.count );
137
139
 
138
- await refined.checkCardinalWord( phrase.asCardinalWord( await context.matches.count ), phrase.isRefining, `${partially ? "partially " : ""}reading "${text}"` );
140
+ await context
141
+ .filterBySubsWithText( "$text", false, text, partially )
142
+ .checkCardinalWord( cardinal, phrase.isRefining, `${partially ? "partially " : ""}reading "${text}"` );
139
143
  }
140
144
 
141
145
  Then( "{contextual-word} (still )exists/exist", ( t, [context] ) => localStateExists( t, context, true ) );
@@ -157,15 +161,16 @@ async function localStateExists( testController, phrase, exists ) {
157
161
  }
158
162
 
159
163
  const context = await Api.access( testController ).getContextFor( phrase );
160
- const matchCount = await context.matches.count;
161
164
 
162
165
  if ( exists ) {
163
166
  // FIXME might pass unintentionally if just one of several elements to be tested exists
164
- await testController.expect( matchCount ).gt( 0 );
167
+ await testController.expect( context.matches.count ).gt( 0 );
165
168
  } else {
166
- await testController.expect( matchCount ).eql( 0 );
169
+ await testController.expect( context.matches.count ).eql( 0 );
167
170
  }
168
171
 
172
+ const matchCount = await context.matches.count;
173
+
169
174
  if ( phrase.isRefining && matchCount > 0 ) {
170
175
  await context.track( phrase.word, phrase.targetCardinality );
171
176
  }