@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 +110 -9
- package/lib/context.js +34 -33
- package/package.json +2 -2
- package/steps/global-action.js +43 -18
- package/steps/global-find.js +1 -1
- package/steps/local-action.js +4 -4
- package/steps/local-find.js +1 -1
- package/steps/local-state.js +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
`${(
|
|
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
|
|
269
|
-
|
|
267
|
+
const { query: nameQuery } = this.getSelector( "@" + name, false ) ?? {};
|
|
268
|
+
const ___name = nameQuery ?? name;
|
|
269
|
+
const ___value = value;
|
|
270
270
|
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
|
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:
|
|
304
|
+
const { query: nameQuery } = this.getSelector( "#" + name, false ) ?? {};
|
|
305
|
+
const ___name = nameQuery ?? name;
|
|
306
|
+
const ___value = value;
|
|
299
307
|
|
|
300
|
-
|
|
301
|
-
const expected =
|
|
302
|
-
const actual = node[
|
|
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
|
|
312
|
+
if ( typeof expected === "function" && expected( actual ) ) {
|
|
313
|
+
return true;
|
|
306
314
|
}
|
|
307
315
|
|
|
308
|
-
if ( expected instanceof RegExp ) {
|
|
309
|
-
return
|
|
316
|
+
if ( expected instanceof RegExp && expected.test( actual ) ) {
|
|
317
|
+
return true;
|
|
310
318
|
}
|
|
311
319
|
|
|
312
320
|
return expected === actual;
|
|
313
|
-
}, {
|
|
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.
|
|
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.
|
|
15
|
+
"@cepharum/quoting-db": "^1.2.0",
|
|
16
16
|
"pluralize": "^8.0.0",
|
|
17
17
|
"semver": "^7.5.0"
|
|
18
18
|
},
|
package/steps/global-action.js
CHANGED
|
@@ -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 :
|
|
9
|
-
await testController.expect( targetCount ).eql( 1, `there is no single $${action} target for selected ${cardinal.word} to ${input == null ? action :
|
|
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 :
|
|
61
|
-
await testController.expect( matchCount ).eql( 1, `there is no single matching ${cardinal.word} with attribute ${name} == "${value}" to ${input == null ? action :
|
|
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.
|
|
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 :
|
|
98
|
-
await testController.expect( matchCount ).eql( 1, `there is no single matching ${cardinal.word} ${negated ? "not " : ""}marked as ${className} to ${input == null ? action :
|
|
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 :
|
|
131
|
-
await testController.expect( matchCount ).eql( 1, `there is no single matching ${cardinal.word} with property ${name} == "${value}" to ${input == null ? action :
|
|
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 :
|
|
173
|
-
await testController.expect( matchCount ).eql( 1, `there is no single matching ${cardinal.word} with ${partially ? "partial " : ""}label "${label}" to ${input == null ? action :
|
|
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 :
|
|
215
|
-
await testController.expect( matchCount ).eql( 1, `there is no single matching ${cardinal.word} ${partially ? "partially " : ""}reading "${text}" to ${input == null ? action :
|
|
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
|
}
|
package/steps/global-find.js
CHANGED
|
@@ -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.
|
|
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
|
}
|
package/steps/local-action.js
CHANGED
|
@@ -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 :
|
|
29
|
-
await testController.expect( matchCount ).eql( 1, `there is no single ${phrase.word} to ${input == null ? action :
|
|
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 :
|
|
35
|
-
await testController.expect( targetCount ).eql( 1, `there is no single $${action} target for selected ${phrase.word} to ${input == null ? action :
|
|
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
|
|
package/steps/local-find.js
CHANGED
|
@@ -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.
|
|
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
|
}
|
package/steps/local-state.js
CHANGED
|
@@ -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.
|
|
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
|
}
|