@thinkwise/testwise 0.2.7 → 0.2.20
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/artifact-builder/InterfaceGenerator.ts +2 -2
- package/artifact-builder/SchemaGenerator.ts +2 -2
- package/artifact-builder/SelectorBuilder.ts +6 -2
- package/artifact-builder/SubjectComponentGenerator.ts +68 -10
- package/artifact-builder/SubjectGenerator.ts +2 -2
- package/artifact-builder/SubjectRegistration.ts +14 -0
- package/artifact-builder/helpers/NamingHandler.ts +28 -56
- package/components/export/ExportComponent.ts +1 -0
- package/components/tab/BaseTabObjects.ts +1 -1
- package/controls/DateControl.ts +67 -0
- package/controls/FormComboBox.ts +74 -0
- package/controls/InputFieldControl.ts +79 -0
- package/controls/LookupDropdown.ts +4 -2
- package/controls/index.ts +3 -0
- package/dist/artifact-builder/InterfaceGenerator.js +3 -3
- package/dist/artifact-builder/InterfaceGenerator.js.map +1 -1
- package/dist/artifact-builder/SchemaGenerator.js +2 -2
- package/dist/artifact-builder/SchemaGenerator.js.map +1 -1
- package/dist/artifact-builder/SelectorBuilder.d.ts +1 -0
- package/dist/artifact-builder/SelectorBuilder.js +4 -1
- package/dist/artifact-builder/SelectorBuilder.js.map +1 -1
- package/dist/artifact-builder/SubjectComponentGenerator.d.ts +2 -2
- package/dist/artifact-builder/SubjectComponentGenerator.js +50 -11
- package/dist/artifact-builder/SubjectComponentGenerator.js.map +1 -1
- package/dist/artifact-builder/SubjectGenerator.js +2 -2
- package/dist/artifact-builder/SubjectGenerator.js.map +1 -1
- package/dist/artifact-builder/SubjectRegistration.d.ts +1 -0
- package/dist/artifact-builder/SubjectRegistration.js +11 -0
- package/dist/artifact-builder/SubjectRegistration.js.map +1 -1
- package/dist/artifact-builder/helpers/NamingHandler.d.ts +2 -4
- package/dist/artifact-builder/helpers/NamingHandler.js +26 -52
- package/dist/artifact-builder/helpers/NamingHandler.js.map +1 -1
- package/dist/components/export/ExportComponent.js +1 -0
- package/dist/components/export/ExportComponent.js.map +1 -1
- package/dist/components/tab/BaseTabObjects.js +1 -1
- package/dist/components/tab/BaseTabObjects.js.map +1 -1
- package/dist/controls/DateControl.d.ts +25 -0
- package/dist/controls/DateControl.js +44 -0
- package/dist/controls/DateControl.js.map +1 -0
- package/dist/controls/FormComboBox.d.ts +9 -0
- package/dist/controls/FormComboBox.js +45 -0
- package/dist/controls/FormComboBox.js.map +1 -0
- package/dist/controls/InputFieldControl.d.ts +26 -0
- package/dist/controls/InputFieldControl.js +48 -0
- package/dist/controls/InputFieldControl.js.map +1 -0
- package/dist/controls/LookupDropdown.d.ts +2 -1
- package/dist/controls/LookupDropdown.js.map +1 -1
- package/dist/controls/index.d.ts +3 -0
- package/dist/controls/index.js +3 -0
- package/dist/controls/index.js.map +1 -1
- package/dist/enums/ElementTypes.d.ts +4 -2
- package/dist/enums/ElementTypes.js +3 -1
- package/dist/enums/ElementTypes.js.map +1 -1
- package/dist/helpers/UserSimulationHelper.js +1 -1
- package/dist/helpers/UserSimulationHelper.js.map +1 -1
- package/enums/ElementTypes.ts +4 -2
- package/helpers/UserSimulationHelper.ts +1 -1
- package/package.json +7 -7
- package/scripts/postinstall.js +3 -1
- package/scripts/sync.js +748 -756
|
@@ -77,7 +77,7 @@ export class InterfaceGenerator {
|
|
|
77
77
|
const baseFileName = file.slice(0, file.lastIndexOf('.'));
|
|
78
78
|
const correctInterfaceName = isInterface
|
|
79
79
|
? this._namingHandler.generateScreenInterfaceName(baseFileName)
|
|
80
|
-
: this._namingHandler.
|
|
80
|
+
: this._namingHandler.toPascalCase(baseFileName);
|
|
81
81
|
|
|
82
82
|
const schemaPath = path.join(schemasDirectory, file);
|
|
83
83
|
const schemaContent = fs.readFileSync(schemaPath, 'utf-8');
|
|
@@ -139,7 +139,7 @@ export class InterfaceGenerator {
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
if (schemaType === SchemaType.Screen) {
|
|
142
|
-
const fileNameSnakeCase = this._namingHandler.
|
|
142
|
+
const fileNameSnakeCase = this._namingHandler.toSnakeCase(fileName);
|
|
143
143
|
|
|
144
144
|
if (this._screensToBuild.length > 0 && !this._screensToBuild.includes(fileNameSnakeCase)) {
|
|
145
145
|
return Promise.resolve(true);
|
|
@@ -75,7 +75,7 @@ export class SchemaGenerator {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
private getScreenContent(screenTypeId: string): string {
|
|
78
|
-
const screenFileName = this._namingHandler.
|
|
78
|
+
const screenFileName = this._namingHandler.toPascalCase(screenTypeId);
|
|
79
79
|
const _filename = fileURLToPath(import.meta.url);
|
|
80
80
|
const _dirname = path.dirname(_filename);
|
|
81
81
|
const screenLocation = path.resolve(_dirname, '..', '..', 'test-artifacts/screens/');
|
|
@@ -86,7 +86,7 @@ export class SchemaGenerator {
|
|
|
86
86
|
private createComponentSchema(subject: ISubject, componentType: SubjectComponents): void {
|
|
87
87
|
const interfaceName = `${subject.subject}_${componentType.toLowerCase()}`;
|
|
88
88
|
const schemasDir = path.join(currentDirname, '..', '..', 'dist/test-artifacts/schemas');
|
|
89
|
-
const fileName = `${this._namingHandler.
|
|
89
|
+
const fileName = `${this._namingHandler.toPascalCase(interfaceName)}.json`;
|
|
90
90
|
|
|
91
91
|
if (fs.existsSync(path.join(schemasDir, fileName))) {
|
|
92
92
|
return;
|
|
@@ -52,12 +52,16 @@ export class SelectorBuilder {
|
|
|
52
52
|
return suffix;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
public
|
|
56
|
-
|
|
55
|
+
public getElementId(property: string, componentName: SubjectComponents): string {
|
|
56
|
+
return this._namingHandler.getIdFromElementName(
|
|
57
57
|
property,
|
|
58
58
|
componentName,
|
|
59
59
|
this._namingHandler.getElementTypeFromElementName(property)
|
|
60
60
|
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public getPageLocatorString(property: string, componentName: SubjectComponents): string {
|
|
64
|
+
let elementId = this.getElementId(property, componentName);
|
|
61
65
|
|
|
62
66
|
if (componentName === 'Grid') elementId = elementId.replace(/-/g, '_');
|
|
63
67
|
|
|
@@ -105,9 +105,13 @@ export class SubjectComponentGenerator {
|
|
|
105
105
|
gridGetters = this.generateGridGetters(propertyLines, componentName);
|
|
106
106
|
} else {
|
|
107
107
|
propertyDeclarations = this.generatePropertyDeclarations(propertyLines, componentName);
|
|
108
|
-
|
|
108
|
+
|
|
109
|
+
if (componentName === 'Form') {
|
|
110
|
+
this.transformInterfaceTypes(componentFile);
|
|
111
|
+
}
|
|
112
|
+
|
|
109
113
|
assignments = this.getPropertyInitializations(propertyLines, componentName);
|
|
110
|
-
this.
|
|
114
|
+
this.addImportsDynamically(componentFile);
|
|
111
115
|
}
|
|
112
116
|
}
|
|
113
117
|
|
|
@@ -125,7 +129,7 @@ export class SubjectComponentGenerator {
|
|
|
125
129
|
const subjectName: string | null = subjectMatch ? subjectMatch[1] : null;
|
|
126
130
|
|
|
127
131
|
if (subjectName) {
|
|
128
|
-
const formattedSubjectName = this._namingHandler.
|
|
132
|
+
const formattedSubjectName = this._namingHandler.toPascalCase(subjectName);
|
|
129
133
|
const subjectFolder = this.getSubjectFolderPath(formattedSubjectName);
|
|
130
134
|
const fileName = path.basename(componentDirPath);
|
|
131
135
|
|
|
@@ -176,8 +180,17 @@ export class SubjectComponentGenerator {
|
|
|
176
180
|
const property = line.split(':')[0].replace(/\?$/, '').trim();
|
|
177
181
|
const pageLocatorString = this._selectorBuilder.getPageLocatorString(property, componentName);
|
|
178
182
|
|
|
179
|
-
if (componentName === 'Form'
|
|
180
|
-
return ` this.${property} = createLookupDropdown(${pageLocatorString});`;
|
|
183
|
+
if (componentName === 'Form') {
|
|
184
|
+
if (property.endsWith('Lookup')) return ` this.${property} = createLookupDropdown(${pageLocatorString});`;
|
|
185
|
+
|
|
186
|
+
if (property.endsWith('DatePicker'))
|
|
187
|
+
return ` this.${property} = createDateControl(page, '${this._selectorBuilder.getElementId(property, componentName)}');`;
|
|
188
|
+
|
|
189
|
+
if (property.endsWith('ComboBox'))
|
|
190
|
+
return ` this.${property} = createFormComboBox(page, '${this._selectorBuilder.getElementId(property, componentName)}');`;
|
|
191
|
+
|
|
192
|
+
if (property.endsWith('Field'))
|
|
193
|
+
return ` this.${property} = createInputFieldControl(page, '${this._selectorBuilder.getElementId(property, componentName)}');`;
|
|
181
194
|
}
|
|
182
195
|
|
|
183
196
|
return ` this.${property} = ${pageLocatorString};`;
|
|
@@ -185,14 +198,39 @@ export class SubjectComponentGenerator {
|
|
|
185
198
|
.join('\n');
|
|
186
199
|
}
|
|
187
200
|
|
|
188
|
-
private
|
|
201
|
+
private addImportsDynamically(componentFile: { content: string }): void {
|
|
189
202
|
const lookupType = 'ILookupDropdown';
|
|
203
|
+
const comboBoxType = 'IFormComboBox';
|
|
204
|
+
const inputFieldType = 'IInputFieldControl';
|
|
205
|
+
const datePickerType = 'IDateControl';
|
|
206
|
+
|
|
207
|
+
const inputFieldImport =
|
|
208
|
+
"import { createInputFieldControl, type IInputFieldControl } from '../../../../controls/InputFieldControl.js';";
|
|
209
|
+
|
|
190
210
|
const lookupImport =
|
|
191
211
|
"import { createLookupDropdown, type ILookupDropdown } from '../../../../controls/LookupDropdown.js';";
|
|
192
212
|
|
|
213
|
+
const comboBoxImport =
|
|
214
|
+
"import { createFormComboBox, type IFormComboBox } from '../../../../controls/FormComboBox.js';";
|
|
215
|
+
|
|
216
|
+
const datePickerImport =
|
|
217
|
+
"import { createDateControl, type IDateControl } from '../../../../controls/DateControl.js';";
|
|
218
|
+
|
|
193
219
|
if (componentFile.content.includes(lookupType) && !componentFile.content.includes(lookupImport)) {
|
|
194
220
|
componentFile.content = `${lookupImport}\n${componentFile.content}`;
|
|
195
221
|
}
|
|
222
|
+
|
|
223
|
+
if (componentFile.content.includes(comboBoxType) && !componentFile.content.includes(comboBoxImport)) {
|
|
224
|
+
componentFile.content = `${comboBoxImport}\n${componentFile.content}`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (componentFile.content.includes(inputFieldType) && !componentFile.content.includes(inputFieldImport)) {
|
|
228
|
+
componentFile.content = `${inputFieldImport}\n${componentFile.content}`;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (componentFile.content.includes(datePickerType) && !componentFile.content.includes(datePickerImport)) {
|
|
232
|
+
componentFile.content = `${datePickerImport}\n${componentFile.content}`;
|
|
233
|
+
}
|
|
196
234
|
}
|
|
197
235
|
|
|
198
236
|
private generatePropertyDeclarations(propertiesToConvert: string[], componentName: SubjectComponents): string {
|
|
@@ -204,19 +242,39 @@ export class SubjectComponentGenerator {
|
|
|
204
242
|
return ` ${property}: ILookupDropdown;`;
|
|
205
243
|
}
|
|
206
244
|
|
|
245
|
+
if (componentName === 'Form' && property.endsWith('ComboBox')) {
|
|
246
|
+
return ` ${property}: IFormComboBox;`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (componentName === 'Form' && property.endsWith('Field')) {
|
|
250
|
+
return ` ${property}: IInputFieldControl;`;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (componentName === 'Form' && property.endsWith('DatePicker')) {
|
|
254
|
+
return ` ${property}: IDateControl;`;
|
|
255
|
+
}
|
|
256
|
+
|
|
207
257
|
return ` ${property}: Locator;`;
|
|
208
258
|
})
|
|
209
259
|
.join('\n');
|
|
210
260
|
}
|
|
211
261
|
|
|
212
|
-
private
|
|
262
|
+
private transformInterfaceTypes(componentFile: { content: string }): void {
|
|
213
263
|
const interfaceRegex = /export interface \w+ extends Form \{([\s\S]*?)\}/g;
|
|
214
264
|
|
|
265
|
+
const typeMapping: Record<string, string> = {
|
|
266
|
+
Lookup: 'ILookupDropdown',
|
|
267
|
+
ComboBox: 'IFormComboBox',
|
|
268
|
+
Field: 'IInputFieldControl',
|
|
269
|
+
DatePicker: 'IDateControl'
|
|
270
|
+
};
|
|
271
|
+
|
|
215
272
|
componentFile.content = componentFile.content.replace(interfaceRegex, (match, interfaceBody) => {
|
|
216
|
-
const propertyRegex = /(\w+Lookup)\s*:\s*Locator;/g;
|
|
273
|
+
const propertyRegex = /(\w+(Lookup|ComboBox|Field|DatePicker))\s*:\s*Locator;/g;
|
|
217
274
|
|
|
218
|
-
const updatedBody = interfaceBody.replace(propertyRegex, (
|
|
219
|
-
|
|
275
|
+
const updatedBody = interfaceBody.replace(propertyRegex, (_match: string, propName: string, suffix: string) => {
|
|
276
|
+
const newType = typeMapping[suffix];
|
|
277
|
+
return `${propName}: ${newType};`;
|
|
220
278
|
});
|
|
221
279
|
|
|
222
280
|
return match.replace(interfaceBody, updatedBody);
|
|
@@ -78,7 +78,7 @@ export class SubjectGenerator {
|
|
|
78
78
|
const screenInterfaceName = this._namingHandler.generateScreenInterfaceName(subject.screentype_id);
|
|
79
79
|
const screenInterfacePath = this.getScreenInterfacePath(screenInterfaceName);
|
|
80
80
|
const screenFileContent = fs.readFileSync(screenInterfacePath, 'utf-8');
|
|
81
|
-
const subjectFolderName = this._namingHandler.
|
|
81
|
+
const subjectFolderName = this._namingHandler.toPascalCase(subject.subject);
|
|
82
82
|
const subjectFolderPath = this.getSubjectFolderRelativePath(subjectFolderName);
|
|
83
83
|
|
|
84
84
|
const subjectClass = {
|
|
@@ -294,7 +294,7 @@ export class SubjectGenerator {
|
|
|
294
294
|
}
|
|
295
295
|
|
|
296
296
|
private getComponentInitializer(component: { type: string; context: string }): string {
|
|
297
|
-
const componentNamePascal = this._namingHandler.
|
|
297
|
+
const componentNamePascal = this._namingHandler.toPascalCase(component.type);
|
|
298
298
|
|
|
299
299
|
switch (component.type) {
|
|
300
300
|
case 'Cardlist':
|
|
@@ -23,6 +23,20 @@ export class SubjectRegistration {
|
|
|
23
23
|
await this.registerGeneratedSubjectsAsSubjectTypes(subjectDetails);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
public resetSubjectRegistry(): void {
|
|
27
|
+
const root = path.resolve(this._currentDirname, '../../');
|
|
28
|
+
const registryPath = path.join(root, 'page-extensions', 'SubjectRegistry.ts');
|
|
29
|
+
const templatePath = path.join(root, 'templates', 'SubjectRegistry.template.ts');
|
|
30
|
+
|
|
31
|
+
if (fs.existsSync(registryPath)) {
|
|
32
|
+
fs.rmSync(registryPath, { force: true });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (fs.existsSync(templatePath)) {
|
|
36
|
+
fs.copyFileSync(templatePath, registryPath);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
26
40
|
private async registerGeneratedSubjectsAsSubjectTypes(subjectDetails: SubjectDetail[]): Promise<void> {
|
|
27
41
|
const subjectTypeImportsAndExports = this.buildSubjectTypeExport(subjectDetails);
|
|
28
42
|
const subject = { content: this.setSubjectTypeContent() };
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import * as path from 'node:path';
|
|
2
|
+
import { pascalCase, snakeCase } from 'change-case';
|
|
2
3
|
import { KnownElementTypes } from '../../enums/ElementTypes.js';
|
|
3
4
|
import type { ScreenComponents, SubjectComponents } from '../../types/Components.js';
|
|
4
5
|
|
|
5
6
|
export class NamingHandler {
|
|
6
7
|
public getDefaultElementSuffixFromComponentType(componentType: SubjectComponents): KnownElementTypes {
|
|
7
8
|
const controlTypeMapping: Record<SubjectComponents, KnownElementTypes> = {
|
|
8
|
-
Form: KnownElementTypes.
|
|
9
|
+
Form: KnownElementTypes.Field,
|
|
9
10
|
Grid: KnownElementTypes.Column
|
|
10
11
|
};
|
|
11
12
|
|
|
@@ -19,7 +20,6 @@ export class NamingHandler {
|
|
|
19
20
|
controlType = controlType.toLowerCase();
|
|
20
21
|
|
|
21
22
|
const formDefaultSuffix: KnownElementTypes = this.getDefaultElementSuffixFromComponentType('Form');
|
|
22
|
-
const gridDefaultSuffix: KnownElementTypes = this.getDefaultElementSuffixFromComponentType('Grid');
|
|
23
23
|
|
|
24
24
|
const controlTypeMappingMatrix: Record<SubjectComponents, Record<string, KnownElementTypes>> = {
|
|
25
25
|
Form: {
|
|
@@ -27,37 +27,41 @@ export class NamingHandler {
|
|
|
27
27
|
phone_number: formDefaultSuffix,
|
|
28
28
|
suggestion_starts_with: formDefaultSuffix,
|
|
29
29
|
lookup_dropdown: KnownElementTypes.Lookup,
|
|
30
|
+
dropdown: KnownElementTypes.Dropdown,
|
|
31
|
+
lookup: KnownElementTypes.Lookup,
|
|
30
32
|
checkbox: KnownElementTypes.Checkbox,
|
|
31
33
|
multiline: KnownElementTypes.Textarea,
|
|
32
|
-
combo: KnownElementTypes.
|
|
34
|
+
combo: KnownElementTypes.ComboBox,
|
|
35
|
+
combo_box: KnownElementTypes.ComboBox,
|
|
36
|
+
date: KnownElementTypes.DatePicker,
|
|
37
|
+
date_picker: KnownElementTypes.DatePicker
|
|
33
38
|
},
|
|
34
39
|
Grid: {
|
|
35
|
-
|
|
36
|
-
phone_number: gridDefaultSuffix,
|
|
37
|
-
suggestion_starts_with: gridDefaultSuffix,
|
|
38
|
-
lookup_dropdown: gridDefaultSuffix,
|
|
39
|
-
checkbox: gridDefaultSuffix,
|
|
40
|
-
multiline: gridDefaultSuffix,
|
|
41
|
-
combo: gridDefaultSuffix
|
|
40
|
+
// Insert Grid-specific control type mappings here
|
|
42
41
|
}
|
|
43
42
|
};
|
|
44
43
|
|
|
45
44
|
const componentMapping = controlTypeMappingMatrix[componentType];
|
|
46
45
|
return (
|
|
47
|
-
componentMapping[controlType] || this.getDefaultElementSuffixFromComponentType(componentType)
|
|
46
|
+
componentMapping[controlType.toLowerCase()] || this.getDefaultElementSuffixFromComponentType(componentType)
|
|
48
47
|
).toLowerCase();
|
|
49
48
|
}
|
|
50
49
|
|
|
51
50
|
public getIdFromElementName(elementName: string, componentType: SubjectComponents, controlType: string): string {
|
|
52
51
|
const elementSuffixToRemove = this.getElementSuffixFromControlTypeAndComponentType(controlType, componentType);
|
|
53
|
-
const
|
|
52
|
+
const elementSuffixToRemoveFormatted = elementSuffixToRemove.replace(/_/g, '-').toLowerCase();
|
|
54
53
|
|
|
55
|
-
|
|
56
|
-
.replace(new RegExp(`${elementSuffixToRemove}$`, 'i'), '')
|
|
54
|
+
let id = this.toSnakeCase(elementName)
|
|
57
55
|
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
58
56
|
.toLowerCase()
|
|
59
|
-
.replace(/_/g, '-')
|
|
60
|
-
|
|
57
|
+
.replace(/_/g, '-');
|
|
58
|
+
|
|
59
|
+
if (elementSuffixToRemove) {
|
|
60
|
+
const suffixRegex = new RegExp(`-?${elementSuffixToRemoveFormatted}$`, 'i');
|
|
61
|
+
id = id.replace(suffixRegex, '');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return id.replace(/-+/g, '-').replace(/-+$/g, '');
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
public getComponentTypeFromFileName(fileName: string): SubjectComponents {
|
|
@@ -72,7 +76,7 @@ export class NamingHandler {
|
|
|
72
76
|
}
|
|
73
77
|
|
|
74
78
|
public getElementTypeFromElementName(elementName: string): string {
|
|
75
|
-
const snakeCaseElementName = this.
|
|
79
|
+
const snakeCaseElementName = this.toSnakeCase(elementName);
|
|
76
80
|
const parts = snakeCaseElementName.split('_');
|
|
77
81
|
|
|
78
82
|
for (let i = parts.length - 1; i > 0; i--) {
|
|
@@ -91,24 +95,11 @@ export class NamingHandler {
|
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
public getPascalSubjectComponentName(file: string): string {
|
|
94
|
-
return this.
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
public formatPascalCaseName(name: string): string {
|
|
98
|
-
if (!name.includes('_')) {
|
|
99
|
-
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return name
|
|
103
|
-
.toLowerCase()
|
|
104
|
-
.split('_')
|
|
105
|
-
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
106
|
-
.join('');
|
|
98
|
+
return this.toPascalCase(path.basename(file, '.json'));
|
|
107
99
|
}
|
|
108
100
|
|
|
109
|
-
public
|
|
110
|
-
|
|
111
|
-
return subjectName.charAt(0).toUpperCase() + subjectName.slice(1);
|
|
101
|
+
public toPascalCase(name: string): string {
|
|
102
|
+
return pascalCase(name);
|
|
112
103
|
}
|
|
113
104
|
|
|
114
105
|
public formatPropertyName(name: string): string {
|
|
@@ -116,30 +107,11 @@ export class NamingHandler {
|
|
|
116
107
|
}
|
|
117
108
|
|
|
118
109
|
public generateScreenInterfaceName(screentypeId: string): string {
|
|
119
|
-
return `I${screentypeId
|
|
120
|
-
.toLowerCase()
|
|
121
|
-
.split('_')
|
|
122
|
-
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
123
|
-
.join('')}`;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
public snakeToPascalCase(value: string): string {
|
|
127
|
-
if (!value.includes('_')) {
|
|
128
|
-
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return value
|
|
132
|
-
.toLowerCase()
|
|
133
|
-
.split('_')
|
|
134
|
-
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
135
|
-
.join('');
|
|
110
|
+
return `I${this.toPascalCase(screentypeId)}`;
|
|
136
111
|
}
|
|
137
112
|
|
|
138
|
-
public
|
|
139
|
-
return value
|
|
140
|
-
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
|
|
141
|
-
.replace(/([a-z\d])([A-Z])/g, '$1_$2')
|
|
142
|
-
.toLowerCase();
|
|
113
|
+
public toSnakeCase(value: string): string {
|
|
114
|
+
return snakeCase(value);
|
|
143
115
|
}
|
|
144
116
|
|
|
145
117
|
public getComponentIdFromName(componentName: string, componentType: ScreenComponents): string {
|
|
@@ -181,6 +153,6 @@ export class NamingHandler {
|
|
|
181
153
|
public generateSubjectClassName(subject: { subject: string; screentype_context: string; variant: string }): string {
|
|
182
154
|
const uniqueSubjectIdentifier = subject.variant !== '' ? `${subject.subject}_${subject.variant}` : subject.subject;
|
|
183
155
|
const className = `${uniqueSubjectIdentifier}_${subject.screentype_context}`;
|
|
184
|
-
return this.
|
|
156
|
+
return this.toPascalCase(className);
|
|
185
157
|
}
|
|
186
158
|
}
|
|
@@ -26,6 +26,7 @@ export class ExportComponent {
|
|
|
26
26
|
public async selectColumns(columns: string[]): Promise<void> {
|
|
27
27
|
for (const column of columns) {
|
|
28
28
|
const checkbox = this._objects.selectColumnsCheckbox(column);
|
|
29
|
+
await this._page.waitForTimeout(100);
|
|
29
30
|
await checkbox.check();
|
|
30
31
|
}
|
|
31
32
|
}
|
|
@@ -12,7 +12,7 @@ export class BaseTabObjects extends BaseComponentObjects {
|
|
|
12
12
|
public tabList = (): Locator => this.context.getByRole('tablist');
|
|
13
13
|
|
|
14
14
|
public tabByTestId = (name: string): Locator =>
|
|
15
|
-
this.tabList().locator(`[role="tab"][data-testid^="tabstrip__tab__${name.toLowerCase()}"]`);
|
|
15
|
+
this.tabList().locator(`[role="tab"][data-testid^="tabstrip__tab__${name.toLowerCase()}__"]`);
|
|
16
16
|
|
|
17
17
|
// use role for generic index-based selection
|
|
18
18
|
public tabByIndex = (index: number): Locator => this.tabList().getByRole('tab').nth(index);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
/* biome-ignore-all lint/suspicious/noExplicitAny: reason42 */
|
|
3
|
+
import type { Locator, Page } from '@playwright/test';
|
|
4
|
+
|
|
5
|
+
export type IDateControl = Locator & IDateControlCore;
|
|
6
|
+
|
|
7
|
+
export type IDateControlCore = {
|
|
8
|
+
fill(value: string): Promise<void>;
|
|
9
|
+
clear(): Promise<void>;
|
|
10
|
+
inputValue(): Promise<string>;
|
|
11
|
+
inputElement(): Locator;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export class DateControl implements IDateControlCore {
|
|
15
|
+
private _dateFieldLocator: Locator;
|
|
16
|
+
private _inputLocator: Locator;
|
|
17
|
+
|
|
18
|
+
constructor(page: Page, id: string) {
|
|
19
|
+
this._dateFieldLocator = page.getByTestId(`form-field__${id}`);
|
|
20
|
+
this._inputLocator = this._dateFieldLocator.locator('input');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Enter a date value in any common format (e.g., "MM/DD/YYYY", "YYYY-MM-DD", "DD-MM-YYYY").
|
|
25
|
+
* The method will extract the numbers and input them sequentially, allowing the date picker to format it correctly.
|
|
26
|
+
* For example, entering "01012025" will be interpreted as "01/01/2025".
|
|
27
|
+
* This approach provides flexibility in how dates can be entered while ensuring compatibility with various date picker implementations.
|
|
28
|
+
* @param value
|
|
29
|
+
*/
|
|
30
|
+
public async fill(value: string): Promise<void> {
|
|
31
|
+
const numbersOnlyValue = value.replace(/\D/g, '');
|
|
32
|
+
|
|
33
|
+
await this._inputLocator.focus();
|
|
34
|
+
await this._inputLocator.pressSequentially(numbersOnlyValue);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public async clear(): Promise<void> {
|
|
38
|
+
await this._inputLocator.fill('');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public async inputValue(): Promise<string> {
|
|
42
|
+
return await this._inputLocator.inputValue();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public inputElement(): Locator {
|
|
46
|
+
return this._inputLocator;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function createDateControl(page: Page, id: string): IDateControl {
|
|
51
|
+
const inputFieldControl = new DateControl(page, id);
|
|
52
|
+
const locator = page.getByTestId(`form-field__${id}`);
|
|
53
|
+
|
|
54
|
+
return new Proxy(inputFieldControl, {
|
|
55
|
+
get(target, prop, receiver) {
|
|
56
|
+
if (prop in target) {
|
|
57
|
+
return Reflect.get(target, prop, receiver);
|
|
58
|
+
}
|
|
59
|
+
const value = (locator as any)[prop];
|
|
60
|
+
|
|
61
|
+
if (typeof value === 'function') {
|
|
62
|
+
return value.bind(locator);
|
|
63
|
+
}
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
}) as unknown as IDateControl;
|
|
67
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
/* biome-ignore-all lint/suspicious/noExplicitAny: Reason42 */
|
|
3
|
+
import type { Locator, Page } from '@playwright/test';
|
|
4
|
+
|
|
5
|
+
export type IFormComboBox = Locator & IFormComboBoxCore;
|
|
6
|
+
|
|
7
|
+
export type IFormComboBoxCore = {
|
|
8
|
+
lookupSelect(optionToSelect: string): Promise<void>;
|
|
9
|
+
selectOption(optionText: string): Promise<void>;
|
|
10
|
+
getValidationMessage(): Promise<string>;
|
|
11
|
+
clearByClick(): Promise<void>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
class FormComboBox implements IFormComboBoxCore {
|
|
15
|
+
private _formFieldLocator: Locator;
|
|
16
|
+
private _autoCompleteMenu: Locator;
|
|
17
|
+
private _id: string;
|
|
18
|
+
|
|
19
|
+
constructor(page: Page, id: string) {
|
|
20
|
+
this._id = id;
|
|
21
|
+
this._formFieldLocator = page.getByTestId(`form-field__${id}`);
|
|
22
|
+
this._autoCompleteMenu = page.getByTestId(`form-field__${id}__select__options-list`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async selectOption(optionText: string): Promise<void> {
|
|
26
|
+
await this._formFieldLocator.locator('input').click();
|
|
27
|
+
|
|
28
|
+
await this._autoCompleteMenu.waitFor({ state: 'visible' });
|
|
29
|
+
|
|
30
|
+
const optionTextLowercase = optionText.toLowerCase();
|
|
31
|
+
|
|
32
|
+
await this._autoCompleteMenu.getByTestId(`form-field__${this._id}__select__${optionTextLowercase}__text`).click();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async getValidationMessage(): Promise<string> {
|
|
36
|
+
await this._formFieldLocator.locator('.TooltipContainer p').waitFor({ state: 'visible' });
|
|
37
|
+
return await this._formFieldLocator.locator('.TooltipContainer p').innerText();
|
|
38
|
+
}
|
|
39
|
+
async clearByClick(): Promise<void> {
|
|
40
|
+
await this._formFieldLocator.locator('Button[aria-label="Clear"]').click();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async lookupSelect(optionToSelect: string): Promise<void> {
|
|
44
|
+
await this._formFieldLocator.locator('input').fill(optionToSelect);
|
|
45
|
+
|
|
46
|
+
await this._autoCompleteMenu.waitFor({ state: 'visible' });
|
|
47
|
+
|
|
48
|
+
const optionToSelectLowercase = optionToSelect.toLowerCase();
|
|
49
|
+
|
|
50
|
+
await this._autoCompleteMenu
|
|
51
|
+
.getByTestId(`form-field__${this._id}__select__${optionToSelectLowercase}__text`)
|
|
52
|
+
.click();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function createFormComboBox(page: Page, id: string): IFormComboBox {
|
|
57
|
+
const comboBox = new FormComboBox(page, id);
|
|
58
|
+
const locator = page.getByTestId(`form-field__${id}`);
|
|
59
|
+
|
|
60
|
+
return new Proxy(comboBox, {
|
|
61
|
+
get(target, prop, receiver) {
|
|
62
|
+
if (prop in target) {
|
|
63
|
+
return Reflect.get(target, prop, receiver);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const value = (locator as any)[prop];
|
|
67
|
+
|
|
68
|
+
if (typeof value === 'function') {
|
|
69
|
+
return value.bind(locator);
|
|
70
|
+
}
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
}) as unknown as IFormComboBox;
|
|
74
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
/** biome-ignore-all lint/suspicious/noExplicitAny: reason42 */
|
|
3
|
+
import type { Locator, Page } from '@playwright/test';
|
|
4
|
+
|
|
5
|
+
export type IInputFieldControl = Locator & ICoreInputFieldControl;
|
|
6
|
+
|
|
7
|
+
export type ICoreInputFieldControl = {
|
|
8
|
+
getValidationMessage(): Promise<string>;
|
|
9
|
+
getLabelText(): Promise<string>;
|
|
10
|
+
fill(value: string): Promise<void>;
|
|
11
|
+
click(): Promise<void>;
|
|
12
|
+
clear(): Promise<void>;
|
|
13
|
+
inputValue(): Promise<string>;
|
|
14
|
+
inputElement(): Locator;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export class InputFieldControl implements ICoreInputFieldControl {
|
|
18
|
+
private _formFieldLocator: Locator;
|
|
19
|
+
private _inputLocator: Locator;
|
|
20
|
+
private _id: string;
|
|
21
|
+
|
|
22
|
+
constructor(page: Page, id: string) {
|
|
23
|
+
this._id = id;
|
|
24
|
+
this._formFieldLocator = page.getByTestId(`form-field__${id}`);
|
|
25
|
+
this._inputLocator = this._formFieldLocator.locator('input');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public async getValidationMessage(): Promise<string> {
|
|
29
|
+
await this._formFieldLocator.locator('.TooltipContainer p').waitFor({ state: 'visible' });
|
|
30
|
+
return await this._formFieldLocator.locator('.TooltipContainer p').innerText();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public async fill(value: string): Promise<void> {
|
|
34
|
+
await this._inputLocator.fill(value);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public async click(): Promise<void> {
|
|
38
|
+
await this._inputLocator.click();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public async clear(): Promise<void> {
|
|
42
|
+
await this._inputLocator.fill('');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public async inputValue(): Promise<string> {
|
|
46
|
+
return await this._inputLocator.inputValue();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public inputElement(): Locator {
|
|
50
|
+
return this._inputLocator;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public async getLabelText(): Promise<string> {
|
|
54
|
+
let returnText = (await this._formFieldLocator.getByTestId(`form-field__${this._id}__label`).textContent()) || '';
|
|
55
|
+
|
|
56
|
+
returnText = returnText.replace(/\s+/g, ' ').trim();
|
|
57
|
+
|
|
58
|
+
return returnText;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function createInputFieldControl(page: Page, id: string): IInputFieldControl {
|
|
63
|
+
const inputFieldControl = new InputFieldControl(page, id);
|
|
64
|
+
const locator = page.getByTestId(`form-field__${id}`);
|
|
65
|
+
|
|
66
|
+
return new Proxy(inputFieldControl, {
|
|
67
|
+
get(target, prop, receiver) {
|
|
68
|
+
if (prop in target) {
|
|
69
|
+
return Reflect.get(target, prop, receiver);
|
|
70
|
+
}
|
|
71
|
+
const value = (locator as any)[prop];
|
|
72
|
+
|
|
73
|
+
if (typeof value === 'function') {
|
|
74
|
+
return value.bind(locator);
|
|
75
|
+
}
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
}) as unknown as IInputFieldControl;
|
|
79
|
+
}
|
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
import type { Locator } from '@playwright/test';
|
|
4
4
|
import { escapeRegex } from '../helpers/RegexHelper.js';
|
|
5
5
|
|
|
6
|
-
export type ILookupDropdown = Locator &
|
|
6
|
+
export type ILookupDropdown = Locator & ILookupDropdownCore;
|
|
7
|
+
|
|
8
|
+
export type ILookupDropdownCore = {
|
|
7
9
|
lookupSelect(optionToSelect: string): Promise<void>;
|
|
8
10
|
selectOption(optionText: string): Promise<void>;
|
|
9
11
|
};
|
|
10
12
|
|
|
11
|
-
class LookupDropdown {
|
|
13
|
+
class LookupDropdown implements ILookupDropdownCore {
|
|
12
14
|
private _locator: Locator;
|
|
13
15
|
|
|
14
16
|
constructor(locator: Locator) {
|
package/controls/index.ts
CHANGED