@cepharum/contextual-gherkin 1.2.1 → 1.2.3

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/cli.js CHANGED
@@ -6,6 +6,7 @@ const Path = require( "node:path" );
6
6
  const os = require( "node:os" );
7
7
 
8
8
  const semver = require( "semver" );
9
+ const { getShellConfigurationSync, quote } = require( "@cepharum/quoting-db" );
9
10
 
10
11
  const cgFolder = "./node_modules/@cepharum/contextual-gherkin";
11
12
  const isWindows = os.platform() === "win32";
@@ -59,17 +60,48 @@ const isWindows = os.platform() === "win32";
59
60
  const patterns = [];
60
61
  const options = {};
61
62
  const passedOptions = [];
63
+ let expectOptions = true;
62
64
 
63
65
  for ( let i = 2; i < process.argv.length; i++ ) {
64
66
  const arg = process.argv[i];
65
67
 
68
+ if ( arg === "--" && expectOptions ) {
69
+ expectOptions = false;
70
+ continue;
71
+ }
72
+
66
73
  if ( arg.startsWith( "-" ) ) {
67
- const [ option, ...parts ] = arg.split( "=" );
74
+ let [ option, ...parts ] = arg.split( "=" ); // eslint-disable-line prefer-const
75
+
76
+ switch ( expectOptions ? option : null ) {
77
+ case "-E" :
78
+ case "--env" :
79
+ if ( parts.length > 0 ) {
80
+ console.error( `use separate argument to define environment variable with ${option}` );
81
+ process.exit( 1 );
82
+ }
68
83
 
69
- switch ( option ) {
84
+ if ( ++i < process.argv.length ) {
85
+ const value = process.argv[i].trim();
86
+ const split = value.indexOf( "=" );
87
+
88
+ if ( split < 0 ) {
89
+ console.error( `missing value for defined environment variable ${value}` );
90
+ process.exit( 1 );
91
+ }
92
+
93
+ ( options.env ??= {} )[value.substring( 0, split )] = value.substring( split + 1 );
94
+ } else {
95
+ console.error( `found --${name} option without value` );
96
+ process.exit( 1 );
97
+ }
98
+
99
+ break;
100
+
101
+ case "--env-file" :
102
+ case "--features" :
70
103
  case "--folder" :
71
- case "--steps" :
72
- case "--features" : {
104
+ case "--steps" : {
73
105
  const name = option.slice( 2 );
74
106
 
75
107
  if ( parts.length > 0 ) {
@@ -114,10 +146,79 @@ const isWindows = os.platform() === "win32";
114
146
  }
115
147
 
116
148
 
149
+ // pre-compile environment
150
+ let env = {
151
+ ...process.env,
152
+ ...options.env
153
+ };
154
+
155
+
156
+ // read optionally selected environment file
157
+ let content;
158
+
159
+ try {
160
+ content = ( await File.promises.readFile( options["env-file"], { encoding: "utf-8" } ) ).trim();
161
+ } catch ( cause ) {
162
+ if ( cause.code !== "ENOENT" && options["env-file"] != null ) {
163
+ throw cause;
164
+ }
165
+ }
166
+
167
+ if ( content ) {
168
+ options["env-file"] = {};
169
+
170
+ for ( const line of content.split( /\r?\n/ ) ) {
171
+ const trimmed = line.trim();
172
+
173
+ if ( trimmed === "" || trimmed[0] === "#" || trimmed[0] === ";" ) {
174
+ continue;
175
+ }
176
+
177
+ let [ name, ...value ] = trimmed.split( "=" );
178
+
179
+ name = name.trim();
180
+ value = value.join( "=" ).trim();
181
+
182
+ if ( value.startsWith( "'" ) && value.endsWith( "'" ) ) {
183
+ value = value.substring( 1, value.length - 1 );
184
+ } else {
185
+ value = value.replace( /^"(.*)"$/, "$1" )
186
+ .replace( /\$(?:\{\s*([a-z0-9_]+)\s*}|([a-z0-9_]+))/g, ( _, a, b ) => env[a ?? b] ?? "" );
187
+ }
188
+
189
+ env[name.trim()] = options["env-file"][name.trim()] = value;
190
+ }
191
+ }
192
+
193
+
194
+ // recompile environment
195
+ env = {
196
+ ...process.env,
197
+ ...options["env-file"],
198
+ ...options.env
199
+ };
200
+
201
+ const proxy = new Proxy( env, {
202
+ get( target, p ) {
203
+ if ( target.hasOwnProperty( p ) ) {
204
+ return target[p];
205
+ }
206
+
207
+ for ( const [ key, value ] of Object.entries( target ) ) {
208
+ if ( key.toLowerCase() === p.toLowerCase() ) {
209
+ return value;
210
+ }
211
+ }
212
+
213
+ return undefined;
214
+ }
215
+ } );
216
+
217
+
117
218
  // inspect local installation of npm to pick sanitizer for passed parameters
118
219
  let npm;
119
220
 
120
- for ( const path of process.env.PATH.split( Path.delimiter ) ) {
221
+ for ( const path of proxy.PATH.split( Path.delimiter ) ) {
121
222
  const candidate = Path.resolve( path, isWindows ? "npm.cmd" : "npm" );
122
223
  const npmStat = await stat( candidate ); // eslint-disable-line no-await-in-loop
123
224
 
@@ -148,24 +249,24 @@ const isWindows = os.platform() === "win32";
148
249
  }
149
250
 
150
251
  if ( spawnOptions.shell ) {
151
- const { getShellConfiguration, quote } = await import( "@cepharum/quoting-db" );
152
- const configuration = await getShellConfiguration();
252
+ const configuration = getShellConfigurationSync( spawnOptions.shell );
153
253
 
154
254
  sanitizer = argument => quote( argument, configuration );
155
255
  }
156
256
 
257
+
157
258
  // invoke gherkin-testcafe and provide all required information
158
259
  const child = spawn( "npm", [
159
260
  "exec",
160
261
  "gherkin-testcafe",
161
262
  "--",
162
- `${( process.env.E2E_BROWSER && sanitizer( process.env.E2E_BROWSER ) ) || ( isWindows ? "chrome" : "chromium:headless" )}`,
263
+ `${( env.E2E_BROWSER && sanitizer( env.E2E_BROWSER ) ) || ( isWindows ? "chrome" : "chromium:headless" )}`,
163
264
  `${cgFolder}/steps/**/*.js`,
164
265
  ...patterns.map( sanitizer ),
165
266
  "--param-type-registry-file",
166
267
  `${cgFolder}/steps/types.js`,
167
268
  ...passedOptions.map( sanitizer )
168
- ], { stdio: "inherit", ...spawnOptions } );
269
+ ], { stdio: "inherit", ...spawnOptions, env } );
169
270
 
170
271
  child.once( "error", console.error );
171
272
  child.once( "exit", process.exit );
package/lib/context.js CHANGED
@@ -256,27 +256,33 @@ class Context {
256
256
  * node's matches, only.
257
257
  *
258
258
  * @param {string|RegExp} name name of attribute to look up per matching element
259
- * @param {string|RegExp} value value expected in named attribute of either element
259
+ * @param {string|RegExp|function(any):boolean} value value expected in named attribute of either element, callback for custom test of attribute value
260
260
  * @returns {Promise<Context>} promises another node listing those matches of current node matching named attribute
261
261
  */
262
- withAttribute( name, value ) {
263
- if ( !this.matches ) {
262
+ async withAttribute( name, value ) {
263
+ if ( ! await this.matches.count ) {
264
264
  throw new TypeError( "there are no matches to filter" );
265
265
  }
266
266
 
267
- const { query } = this.getSelector( "@" + name, false ) ?? {};
268
- const matches = typeof value === "function" ? this.matches.filter( node => {
269
- const actual = node.attributes.getNamedItem( name );
267
+ const { query: nameQuery } = this.getSelector( "@" + name, false ) ?? {};
268
+ const ___name = nameQuery ?? name;
269
+ const ___value = value;
270
270
 
271
- return value( actual == null ? undefined : actual.value );
272
- }, { name: query ?? name, value } ) : this.matches.withAttribute( query ?? name, value );
271
+ return this.filterBySubsWithFn( "$" + name, false, node => {
272
+ const expected = ___value;
273
+ const attribute = node.attributes.getNamedItem( ___name );
274
+ const actual = attribute == null ? undefined : attribute.value;
273
275
 
274
- return Promise.resolve( new this.constructor( this.testController, this, this.api, {
275
- matches,
276
- type: this.type,
277
- selectors: this.selectors,
278
- cardinality: this.cardinality,
279
- } ) );
276
+ if ( typeof expected === "function" && expected( actual ) ) {
277
+ return true;
278
+ }
279
+
280
+ if ( expected instanceof RegExp && expected.test( actual ) ) {
281
+ return true;
282
+ }
283
+
284
+ return expected === actual;
285
+ }, { ___name, ___value } );
280
286
  }
281
287
 
282
288
  /**
@@ -287,37 +293,32 @@ class Context {
287
293
  * node's matches, only.
288
294
  *
289
295
  * @param {string} name name of DOM property to look up per matching element
290
- * @param {string|RegExp|function(any):boolean} value value expected in named DOM property of either element, callback for testing individual property value
296
+ * @param {string|RegExp|function(any):boolean} value value expected in named DOM property of either element, callback for custom test of property value
291
297
  * @returns {Promise<Context>} promises another node listing those matches of current node matching named property
292
298
  */
293
- withProperty( name, value ) {
294
- if ( !this.matches ) {
299
+ async withProperty( name, value ) {
300
+ if ( ! await this.matches.count ) {
295
301
  throw new TypeError( "there are no matches to filter" );
296
302
  }
297
303
 
298
- const { query: _name } = this.getSelector( "#" + name, false ) ?? {};
304
+ const { query: nameQuery } = this.getSelector( "#" + name, false ) ?? {};
305
+ const ___name = nameQuery ?? name;
306
+ const ___value = value;
299
307
 
300
- const matches = this.matches.filter( node => {
301
- const expected = value;
302
- const actual = node[name];
308
+ return this.filterBySubsWithFn( "$" + name, false, node => {
309
+ const expected = ___value;
310
+ const actual = node[___name];
303
311
 
304
- if ( typeof expected === "function" ) {
305
- return expected( actual );
312
+ if ( typeof expected === "function" && expected( actual ) ) {
313
+ return true;
306
314
  }
307
315
 
308
- if ( expected instanceof RegExp ) {
309
- return expected.test( actual );
316
+ if ( expected instanceof RegExp && expected.test( actual ) ) {
317
+ return true;
310
318
  }
311
319
 
312
320
  return expected === actual;
313
- }, { name: _name ?? name, value } );
314
-
315
- return Promise.resolve( new this.constructor( this.testController, this, this.api, {
316
- matches,
317
- type: this.type,
318
- selectors: this.selectors,
319
- cardinality: this.cardinality,
320
- } ) );
321
+ }, { ___name, ___value } );
321
322
  }
322
323
 
323
324
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cepharum/contextual-gherkin",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
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/",
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "keywords": [],
14
14
  "dependencies": {
15
- "@cepharum/quoting-db": "^1.0.0",
15
+ "@cepharum/quoting-db": "^1.2.0",
16
16
  "pluralize": "^8.0.0",
17
17
  "semver": "^7.5.0"
18
18
  },
@@ -5,8 +5,8 @@ const actAndTrack = async( testController, cardinal, refined, action, input ) =>
5
5
  const target = await refined.find( "$" + action, false );
6
6
  const targetCount = await target.matches.count;
7
7
 
8
- await testController.expect( targetCount ).gt( 0, `there is no $${action} target for selected ${cardinal.word} to ${input == null ? action : "enter text 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 text into"}` ); // eslint-disable-line max-len
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
10
10
 
11
11
  await refined.detach();
12
12
 
@@ -44,7 +44,7 @@ When( "I enter {string-or-word} into {cardinal-word} named {text-or-regexp}", (
44
44
  * @param {NumberAndWord} cardinal describes what to search
45
45
  * @param {string|RegExp} name name of attribute to match
46
46
  * @param {string|RegExp} value value of named attribute to match
47
- * @param {'click'|'hover'} action action to perform on found element
47
+ * @param {'click'|'hover'|'enter'} action action to perform on found element
48
48
  * @param {string} input text to enter into selected field (instead of clicking or hovering)
49
49
  * @return {Promise<void>} promises step passed
50
50
  */
@@ -54,11 +54,16 @@ async function globalActionByAttribute( testController, cardinal, name, value, a
54
54
  }
55
55
 
56
56
  const matches = await Api.access( testController ).find( cardinal.word );
57
+
58
+ if ( cardinal.cardinality !== "singular" ) {
59
+ 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
+ }
61
+
57
62
  const refined = await matches.withAttribute( name, value );
58
63
  const matchCount = await refined.matches.count;
59
64
 
60
- await testController.expect( matchCount ).gt( 0, `there is no matching ${cardinal.word} with attribute ${name} == "${value}" to ${input == null ? action : "enter text into"}` );
61
- await testController.expect( matchCount ).eql( 1, `there is no single matching ${cardinal.word} with attribute ${name} == "${value}" to ${input == null ? action : "enter text into"}` ); // eslint-disable-line max-len
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
62
67
 
63
68
  await actAndTrack( testController, cardinal, refined, action, input );
64
69
  }
@@ -79,7 +84,7 @@ When( "I enter {string-or-word} into {cardinal-word} not marked/tagged as {word}
79
84
  * @param {TestController} testController exposes test controller
80
85
  * @param {NumberAndWord} cardinal describes what to search
81
86
  * @param {string|RegExp} className class required for match
82
- * @param {'click'|'hover'} action action to perform on found element
87
+ * @param {'click'|'hover'|'enter'} action action to perform on found element
83
88
  * @param {boolean} negated inverts the assertion by means of requiring one or all elements of context not to have selected class
84
89
  * @param {string} input text to enter into selected field (instead of clicking or hovering)
85
90
  * @return {Promise<void>} promises step passed
@@ -90,12 +95,17 @@ async function globalActionByClass( testController, cardinal, className, action,
90
95
  }
91
96
 
92
97
  const matches = await Api.access( testController ).find( cardinal.word );
98
+
99
+ if ( cardinal.cardinality !== "singular" ) {
100
+ 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`}` ); // eslint-disable-line max-len
101
+ }
102
+
93
103
  const { query: property } = matches.getSelector( "#classList", "classList" );
94
- const refined = await matches.filterFn( node => node?.[property]?.contains( className ) ^ negated, { property, className, negated } );
104
+ const refined = await matches.filterBySubsWithFn( "$classList", false, node => node?.[property]?.contains( className ) ^ negated, { property, className, negated } );
95
105
  const matchCount = await refined.matches.count;
96
106
 
97
- await testController.expect( matchCount ).gt( 0, `there is no matching ${cardinal.word} ${negated ? "not " : ""}marked as ${className} to ${input == null ? action : "enter text into"}` );
98
- await testController.expect( matchCount ).eql( 1, `there is no single matching ${cardinal.word} ${negated ? "not " : ""}marked as ${className} to ${input == null ? action : "enter text into"}` );
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`}` );
99
109
 
100
110
  await actAndTrack( testController, cardinal, refined, action, input );
101
111
  }
@@ -114,7 +124,7 @@ When( "I enter {string-or-word} into {cardinal-word} with value {text-or-regexp}
114
124
  * @param {NumberAndWord} cardinal describes what to search
115
125
  * @param {string|RegExp} name name of attribute to match
116
126
  * @param {string|RegExp} value value of named attribute to match
117
- * @param {'click'|'hover'} action action to perform on found element
127
+ * @param {'click'|'hover'|'enter'} action action to perform on found element
118
128
  * @param {string} input text to enter into selected field (instead of clicking or hovering)
119
129
  * @return {Promise<void>} promises step passed
120
130
  */
@@ -124,11 +134,16 @@ async function globalActionByProperty( testController, cardinal, name, value, ac
124
134
  }
125
135
 
126
136
  const matches = await Api.access( testController ).find( cardinal.word );
137
+
138
+ if ( cardinal.cardinality !== "singular" ) {
139
+ 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
+ }
141
+
127
142
  const refined = await matches.withProperty( name, value );
128
143
  const matchCount = await refined.matches.count;
129
144
 
130
- await testController.expect( matchCount ).gt( 0, `there is no matching ${cardinal.word} with property ${name} == "${value}" to ${input == null ? action : "enter text into"}` );
131
- await testController.expect( matchCount ).eql( 1, `there is no single matching ${cardinal.word} with property ${name} == "${value}" to ${input == null ? action : "enter text into"}` ); // eslint-disable-line max-len
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
132
147
 
133
148
  await actAndTrack( testController, cardinal, refined, action, input );
134
149
  }
@@ -156,7 +171,7 @@ When( "I enter {string-or-word} into {cardinal-word} partially labelled with {te
156
171
  * @param {NumberAndWord} cardinal provides cardinal number and word
157
172
  * @param {string} label provides required label of matching element(s)
158
173
  * @param {boolean} partially if true, provided text does not have to match whole content of matching subs
159
- * @param {'click'|'hover'} action action to perform on found element
174
+ * @param {'click'|'hover'|'enter'} action action to perform on found element
160
175
  * @param {string} input text to enter into selected field (instead of clicking or hovering)
161
176
  * @returns {Promise<void>} promises step passed
162
177
  */
@@ -166,11 +181,16 @@ async function globalActionByLabel( testController, cardinal, label, partially,
166
181
  }
167
182
 
168
183
  const matches = await Api.access( testController ).find( cardinal.word );
184
+
185
+ if ( cardinal.cardinality !== "singular" ) {
186
+ 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`}` ); // eslint-disable-line max-len
187
+ }
188
+
169
189
  const refined = await matches.filterBySubsWithText( "$label", "label", label, partially );
170
190
  const matchCount = await refined.matches.count;
171
191
 
172
- await testController.expect( matchCount ).gt( 0, `there is no matching ${cardinal.word} with ${partially ? "partial " : ""}label "${label}" to ${input == null ? action : "enter text into"}` );
173
- await testController.expect( matchCount ).eql( 1, `there is no single matching ${cardinal.word} with ${partially ? "partial " : ""}label "${label}" to ${input == null ? action : "enter text into"}` );
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`}` );
174
194
 
175
195
  await actAndTrack( testController, cardinal, refined, action, input );
176
196
  }
@@ -198,7 +218,7 @@ When( "I enter {string-or-word} into {cardinal-word} with partial text {text-or-
198
218
  * @param {NumberAndWord} cardinal provides cardinal number and word
199
219
  * @param {string} text provides text to be found in eventually matching element(s)
200
220
  * @param {boolean} partially if true, provided text does not have to match whole content of matching subs
201
- * @param {'click'|'hover'} action action to perform on found element
221
+ * @param {'click'|'hover'|'enter'} action action to perform on found element
202
222
  * @param {string} input text to enter into selected field (instead of clicking or hovering)
203
223
  * @returns {Promise<void>} promises step passed
204
224
  */
@@ -208,11 +228,16 @@ async function globalActionByTextContent( testController, cardinal, text, partia
208
228
  }
209
229
 
210
230
  const matches = await Api.access( testController ).find( cardinal.word );
231
+
232
+ if ( cardinal.cardinality !== "singular" ) {
233
+ 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`}` ); // eslint-disable-line max-len
234
+ }
235
+
211
236
  const refined = await matches.filterBySubsWithText( "$text", false, text, partially );
212
237
  const matchCount = await refined.matches.count;
213
238
 
214
- await testController.expect( matchCount ).gt( 0, `there is no matching ${cardinal.word} ${partially ? "partially " : ""}reading "${text}" to ${input == null ? action : "enter text into"}` );
215
- await testController.expect( matchCount ).eql( 1, `there is no single matching ${cardinal.word} ${partially ? "partially " : ""}reading "${text}" to ${input == null ? action : "enter text into"}` );
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`}` );
216
241
 
217
242
  await actAndTrack( testController, cardinal, refined, action, input );
218
243
  }
@@ -94,7 +94,7 @@ async function globalFindByClass( testController, cardinal, className, negated )
94
94
 
95
95
  const matches = await Api.access( testController ).find( cardinal.word );
96
96
  const { query: property } = matches.getSelector( "#classList", "classList" );
97
- const refined = await matches.filterFn( node => node?.[property]?.contains( className ) ^ negated, { property, className, negated } );
97
+ const refined = await 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
  }
@@ -25,14 +25,14 @@ async function localAction( testController, phrase, action, input ) {
25
25
  const context = await Api.access( testController ).getContextFor( phrase );
26
26
  const matchCount = await context.matches.count;
27
27
 
28
- await testController.expect( matchCount ).gt( 0, `there is no ${phrase.word} to ${input == null ? action : "enter text into"}` );
29
- await testController.expect( matchCount ).eql( 1, `there is no single ${phrase.word} to ${input == null ? action : "enter text into"}` );
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`}` );
30
30
 
31
31
  const target = await context.find( "$" + action, false );
32
32
  const targetCount = await target.matches.count;
33
33
 
34
- await testController.expect( targetCount ).gt( 0, `there is no $${action} target for selected ${phrase.word} to ${input == null ? action : "enter text 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 text into"}` ); // eslint-disable-line max-len
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
36
36
 
37
37
  await context.detach();
38
38
 
@@ -101,7 +101,7 @@ async function localFindByClass( testController, phrase, cardinal, className, ne
101
101
  const context = await Api.access( testController ).getContextFor( phrase );
102
102
  const matches = await context.find( cardinal.word );
103
103
  const { query: property } = matches.getSelector( "#classList", "classList" );
104
- const refined = await matches.filterFn( node => node?.[property]?.contains( className ) ^ negated, { property, className, negated } );
104
+ const refined = await matches.filterBySubsWithFn( "$classList", false, node => node?.[property]?.contains( className ) ^ negated, { property, className, negated } );
105
105
 
106
106
  await refined.checkCardinalWord( cardinal, true, `${negated ? "not " : ""}marked as "${className}"` );
107
107
  }
@@ -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.filterFn( node => node?.[property]?.contains( className ) ^ negated, { property, className, negated } );
82
+ const filtered = await 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
  }