@sap-ux/ui5-test-writer 1.0.13 → 1.1.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/README.md +16 -16
- package/dist/fiori-elements-opa-writer.d.ts +3 -11
- package/dist/fiori-elements-opa-writer.js +92 -47
- package/dist/index.d.ts +1 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.js +4 -0
- package/dist/utils/opaQUnitUtils.d.ts +33 -5
- package/dist/utils/opaQUnitUtils.js +189 -8
- package/package.json +5 -5
- package/templates/v4/integration/FirstJourney.ts +32 -0
- package/templates/v4/integration/ListReportJourney.ts +117 -0
- package/templates/v4/integration/ObjectPageJourney.ts +176 -0
- package/templates/v4/integration/pages/JourneyRunner.ts +28 -0
- package/templates/v4/integration/pages/ListReport.ts +8 -0
- package/templates/v4/integration/pages/ObjectPage.ts +18 -0
- package/templates/v4/integration/types/OpaJourneyTypes.d.ts +43 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# [`@sap-ux/ui5-test-writer`](https://github.com/SAP/open-ux-tools/tree/main/packages/ui5-test-writer)
|
|
4
4
|
|
|
5
|
-
OPA files writer for use within Yeoman generator and other prompting libraries.
|
|
5
|
+
OPA files writer for use within Yeoman generator and other prompting libraries.
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
## Installation
|
|
@@ -17,7 +17,7 @@ Pnpm
|
|
|
17
17
|
|
|
18
18
|
## Usage
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
The `generateOPAFiles` function creates an OPA5 test suite for a Fiori elements for OData V4 application.
|
|
21
21
|
|
|
22
22
|
### Generate all OPA test files for a Fiori elements for OData V4 application
|
|
23
23
|
|
|
@@ -56,25 +56,25 @@ await exampleWriter();
|
|
|
56
56
|
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
###
|
|
59
|
+
### TypeScript output
|
|
60
|
+
|
|
61
|
+
Pass `enableTypeScript: true` in the options to generate the OPA test suite as TypeScript instead of JavaScript. The generated files use ES module syntax (`import` / `export default`) instead of AMD `sap.ui.define`, and include typed `Given` / `When` / `Then` parameters in journey functions.
|
|
60
62
|
|
|
61
|
-
Calling the `generatePageObjectFile` function
|
|
62
63
|
```javascript
|
|
63
|
-
import {
|
|
64
|
+
import { generateOPAFiles } from '@sap-ux/ui5-test-writer'
|
|
64
65
|
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
const targetKey = 'MyNewPage'; // Key of the target in the app descriptor (in sap.ui5/routing/targets)
|
|
68
|
-
const fs = await generatePageObjectFile(myProjectPath, { targetKey });
|
|
69
|
-
return new Promise((resolve) => {
|
|
70
|
-
fs.commit(resolve); // When using with Yeoman it handle the fs commit.
|
|
71
|
-
});
|
|
72
|
-
}
|
|
66
|
+
const fs = await generateOPAFiles(myProjectPath, { enableTypeScript: true });
|
|
67
|
+
```
|
|
73
68
|
|
|
74
|
-
|
|
75
|
-
await exampleWriter();
|
|
69
|
+
What changes in the generated output:
|
|
76
70
|
|
|
77
|
-
|
|
71
|
+
- Page objects, journeys, and `JourneyRunner` are emitted as `.ts` files.
|
|
72
|
+
- A `webapp/test/integration/types/OpaJourneyTypes.d.ts` file is generated with `Given` / `When` / `Then` definitions tailored to the app's pages.
|
|
73
|
+
- The QUnit bootstrap (`opaTests.qunit.js`) and the HTML harness stay as `.js` / `.html` (they are loaded directly by the browser).
|
|
74
|
+
|
|
75
|
+
When run in standalone mode against an existing project, `enableTypeScript` is auto-detected from the presence of a `tsconfig.json`. An explicit value passed in the options always takes precedence.
|
|
76
|
+
|
|
77
|
+
Scope: TypeScript output is currently supported for List Report and Object Page templates.
|
|
78
78
|
|
|
79
79
|
## Keywords
|
|
80
80
|
SAP Fiori Elements
|
|
@@ -1,28 +1,20 @@
|
|
|
1
1
|
import type { Editor } from 'mem-fs-editor';
|
|
2
2
|
import type { Manifest } from '@sap-ux/project-access';
|
|
3
|
+
import type { OPAGenerationOptions } from './types.js';
|
|
3
4
|
import type { Logger } from '@sap-ux/logger';
|
|
4
5
|
/**
|
|
5
6
|
* Generate OPA test files for a Fiori elements for OData V4 application.
|
|
6
7
|
* Note: this can potentially overwrite existing files in the webapp/test folder.
|
|
7
8
|
*
|
|
8
9
|
* @param basePath - the absolute target path where the application will be generated
|
|
9
|
-
* @param
|
|
10
|
-
* @param opaConfig.scriptName - the name of the OPA journey file. If not specified, 'FirstJourney' will be used
|
|
11
|
-
* @param opaConfig.htmlTarget - the name of the html that will be used in OPA journey file. If not specified, 'index.html' will be used
|
|
12
|
-
* @param opaConfig.appID - the appID. If not specified, will be read from the manifest in sap.app/id
|
|
13
|
-
* @param opaConfig.useVirtualPreviewEndpoints - when true, OPA harness files are served virtually; skip writing them to disk
|
|
10
|
+
* @param options - OPA generation options
|
|
14
11
|
* @param metadata - optional metadata for the OPA test generation
|
|
15
12
|
* @param fs - an optional reference to a mem-fs editor
|
|
16
13
|
* @param log - optional logger instance
|
|
17
14
|
* @param standalone - opa test generation run standalone, not during app generation
|
|
18
15
|
* @returns Reference to a mem-fs-editor
|
|
19
16
|
*/
|
|
20
|
-
export declare function generateOPAFiles(basePath: string,
|
|
21
|
-
scriptName?: string;
|
|
22
|
-
appID?: string;
|
|
23
|
-
htmlTarget?: string;
|
|
24
|
-
useVirtualPreviewEndpoints?: boolean;
|
|
25
|
-
}, metadata?: string, fs?: Editor, log?: Logger, standalone?: boolean): Promise<Editor>;
|
|
17
|
+
export declare function generateOPAFiles(basePath: string, options: OPAGenerationOptions, metadata?: string, fs?: Editor, log?: Logger, standalone?: boolean): Promise<Editor>;
|
|
26
18
|
/**
|
|
27
19
|
* Reads the manifest for an app.
|
|
28
20
|
*
|
|
@@ -3,7 +3,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
3
3
|
import { existsSync } from 'node:fs';
|
|
4
4
|
import { create as createStorage } from 'mem-fs';
|
|
5
5
|
import { create } from 'mem-fs-editor';
|
|
6
|
-
import { SupportedPageTypes, ValidationError } from './types.js';
|
|
6
|
+
import { SupportedPageTypes, ValidationError, DotFileExtension } from './types.js';
|
|
7
7
|
import { t } from './i18n.js';
|
|
8
8
|
import { FileName, DirName, getWebappPath, updatePackageScript } from '@sap-ux/project-access';
|
|
9
9
|
import { getAppFeatures } from './utils/modelUtils.js';
|
|
@@ -16,22 +16,22 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
16
16
|
* Note: this can potentially overwrite existing files in the webapp/test folder.
|
|
17
17
|
*
|
|
18
18
|
* @param basePath - the absolute target path where the application will be generated
|
|
19
|
-
* @param
|
|
20
|
-
* @param opaConfig.scriptName - the name of the OPA journey file. If not specified, 'FirstJourney' will be used
|
|
21
|
-
* @param opaConfig.htmlTarget - the name of the html that will be used in OPA journey file. If not specified, 'index.html' will be used
|
|
22
|
-
* @param opaConfig.appID - the appID. If not specified, will be read from the manifest in sap.app/id
|
|
23
|
-
* @param opaConfig.useVirtualPreviewEndpoints - when true, OPA harness files are served virtually; skip writing them to disk
|
|
19
|
+
* @param options - OPA generation options
|
|
24
20
|
* @param metadata - optional metadata for the OPA test generation
|
|
25
21
|
* @param fs - an optional reference to a mem-fs editor
|
|
26
22
|
* @param log - optional logger instance
|
|
27
23
|
* @param standalone - opa test generation run standalone, not during app generation
|
|
28
24
|
* @returns Reference to a mem-fs-editor
|
|
29
25
|
*/
|
|
30
|
-
export async function generateOPAFiles(basePath,
|
|
26
|
+
export async function generateOPAFiles(basePath, options, metadata, fs, log, standalone = false) {
|
|
31
27
|
const editor = fs ?? create(createStorage());
|
|
32
28
|
const manifest = readManifest(editor, basePath);
|
|
33
29
|
const { applicationType, hideFilterBar } = getAppTypeAndHideFilterBarFromManifest(manifest);
|
|
34
|
-
const config = createConfig(manifest,
|
|
30
|
+
const config = createConfig(manifest, options, hideFilterBar);
|
|
31
|
+
// In standalone mode, auto-detect TS vs JS from the project (presence of `tsconfig.json`)
|
|
32
|
+
// when the caller has not made an explicit choice. This enforces "TS app → TS tests, JS app → JS tests".
|
|
33
|
+
const enableTypeScript = options.enableTypeScript ?? (standalone && existsSync(join(basePath, FileName.Tsconfig)));
|
|
34
|
+
const dotFileExtension = enableTypeScript ? DotFileExtension.TS : DotFileExtension.JS;
|
|
35
35
|
const rootCommonTemplateDirPath = join(__dirname, '../templates/common');
|
|
36
36
|
const rootV4TemplateDirPath = join(__dirname, `../templates/${applicationType}`); // Only v4 is supported for the time being
|
|
37
37
|
const testOutDirPath = join(await getWebappPath(basePath), 'test');
|
|
@@ -46,28 +46,43 @@ export async function generateOPAFiles(basePath, opaConfig, metadata, fs, log, s
|
|
|
46
46
|
navigatedOP: LROP.pageOP?.targetKey,
|
|
47
47
|
hideFilterBar: config.hideFilterBar
|
|
48
48
|
};
|
|
49
|
-
const writeContext = {
|
|
49
|
+
const writeContext = {
|
|
50
|
+
config,
|
|
51
|
+
rootV4TemplateDirPath,
|
|
52
|
+
testOutDirPath,
|
|
53
|
+
editor,
|
|
54
|
+
journeyParams,
|
|
55
|
+
dotFileExtension
|
|
56
|
+
};
|
|
57
|
+
// The active context is the one used to actually emit files. In standalone mode without an
|
|
58
|
+
// existing JourneyRunner it is replaced with the resolved standalone context (which may
|
|
59
|
+
// override fields like `htmlTarget`); otherwise it stays as the original `writeContext`.
|
|
60
|
+
let activeContext = writeContext;
|
|
50
61
|
if (standalone) {
|
|
51
|
-
const hasJourneyRunner = existsSync(join(testOutDirPath, 'integration', 'pages',
|
|
62
|
+
const hasJourneyRunner = existsSync(join(testOutDirPath, 'integration', 'pages', `JourneyRunner${dotFileExtension}`));
|
|
52
63
|
const virtualOPA5Configured = await hasVirtualOPA5(basePath);
|
|
53
64
|
if (hasJourneyRunner) {
|
|
54
65
|
writeJourneyFiles(appFeatures, writeContext, true, true, virtualOPA5Configured);
|
|
55
66
|
}
|
|
56
67
|
else {
|
|
57
|
-
|
|
68
|
+
activeContext = await resolveStandaloneWriteContext(basePath, testOutDirPath, writeContext, editor);
|
|
58
69
|
if (!virtualOPA5Configured) {
|
|
59
|
-
writeCommonAndPageFiles(
|
|
70
|
+
writeCommonAndPageFiles(activeContext, rootCommonTemplateDirPath);
|
|
60
71
|
}
|
|
61
|
-
writeJourneyFiles(appFeatures,
|
|
72
|
+
writeJourneyFiles(appFeatures, activeContext, true, hasJourneyRunner, virtualOPA5Configured);
|
|
62
73
|
}
|
|
63
74
|
}
|
|
64
75
|
else {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
76
|
+
const useVirtualPreviewEndpoints = options.useVirtualPreviewEndpoints ?? false;
|
|
77
|
+
writeCommonAndPageFiles(writeContext, rootCommonTemplateDirPath, useVirtualPreviewEndpoints);
|
|
78
|
+
writeJourneyFiles(appFeatures, writeContext, false, false, useVirtualPreviewEndpoints);
|
|
79
|
+
if (useVirtualPreviewEndpoints) {
|
|
68
80
|
await addVirtualTestConfig(basePath, [{ framework: 'OPA5', path: '/test/integration/opaTests.qunit.html' }, { framework: 'Testsuite' }], editor);
|
|
69
81
|
}
|
|
70
82
|
}
|
|
83
|
+
if (enableTypeScript) {
|
|
84
|
+
writeOpaJourneyTypes(activeContext);
|
|
85
|
+
}
|
|
71
86
|
return editor;
|
|
72
87
|
}
|
|
73
88
|
/**
|
|
@@ -217,22 +232,19 @@ function createPageConfig(manifest, targetKey, forcedAppID) {
|
|
|
217
232
|
* Create the configuration object from the app descriptor.
|
|
218
233
|
*
|
|
219
234
|
* @param manifest - the app descriptor of the target app
|
|
220
|
-
* @param
|
|
221
|
-
* @param opaConfig.scriptName - the name of the OPA journey file. If not specified, 'FirstJourney' will be used
|
|
222
|
-
* @param opaConfig.htmlTarget - the name of the html file that will be used in the OPA journey file. If not specified, 'index.html' will be used
|
|
223
|
-
* @param opaConfig.appID - the appID. If not specified, will be read from the manifest in sap.app/id
|
|
235
|
+
* @param options - OPA generation options
|
|
224
236
|
* @param hideFilterBar - whether the filter bar should be hidden in the generated tests
|
|
225
237
|
* @returns OPA test configuration object
|
|
226
238
|
*/
|
|
227
|
-
function createConfig(manifest,
|
|
239
|
+
function createConfig(manifest, options, hideFilterBar) {
|
|
228
240
|
// General application info
|
|
229
|
-
const { appID, appPath } = getAppFromManifest(manifest,
|
|
241
|
+
const { appID, appPath } = getAppFromManifest(manifest, options.appID);
|
|
230
242
|
const config = {
|
|
231
243
|
appID,
|
|
232
244
|
appPath,
|
|
233
245
|
pages: [],
|
|
234
|
-
opaJourneyFileName:
|
|
235
|
-
htmlTarget:
|
|
246
|
+
opaJourneyFileName: options.scriptName ?? 'FirstJourney',
|
|
247
|
+
htmlTarget: options.htmlTarget ?? 'index.html',
|
|
236
248
|
hideFilterBar
|
|
237
249
|
};
|
|
238
250
|
// Identify startup targets from the routes
|
|
@@ -248,7 +260,7 @@ function createConfig(manifest, opaConfig, hideFilterBar) {
|
|
|
248
260
|
// Create page configurations in supported cases
|
|
249
261
|
const appTargets = manifest['sap.ui5']?.routing?.targets;
|
|
250
262
|
for (const targetKey in appTargets) {
|
|
251
|
-
const pageConfig = createPageConfig(manifest, targetKey,
|
|
263
|
+
const pageConfig = createPageConfig(manifest, targetKey, options.appID);
|
|
252
264
|
if (pageConfig) {
|
|
253
265
|
pageConfig.isStartup = startupTargets.includes(targetKey);
|
|
254
266
|
config.pages.push(pageConfig);
|
|
@@ -311,7 +323,7 @@ function findLROP(pages, manifest) {
|
|
|
311
323
|
* @param useVirtualPreviewEndpoints - when true, testsuite harness files are served virtually; skip writing them to disk
|
|
312
324
|
*/
|
|
313
325
|
function writeCommonAndPageFiles(writeContext, rootCommonTemplateDirPath, useVirtualPreviewEndpoints = false) {
|
|
314
|
-
const { config, rootV4TemplateDirPath, testOutDirPath, editor, journeyParams } = writeContext;
|
|
326
|
+
const { config, rootV4TemplateDirPath, testOutDirPath, editor, journeyParams, dotFileExtension } = writeContext;
|
|
315
327
|
// Common test files (testsuite served virtually when useVirtualPreviewEndpoints is enabled)
|
|
316
328
|
if (!useVirtualPreviewEndpoints) {
|
|
317
329
|
editor.copyTpl(join(rootCommonTemplateDirPath), testOutDirPath,
|
|
@@ -321,36 +333,59 @@ function writeCommonAndPageFiles(writeContext, rootCommonTemplateDirPath, useVir
|
|
|
321
333
|
});
|
|
322
334
|
}
|
|
323
335
|
config.pages.forEach((page) => {
|
|
324
|
-
writePageObject(page, rootV4TemplateDirPath, testOutDirPath, editor);
|
|
336
|
+
writePageObject(page, rootV4TemplateDirPath, testOutDirPath, editor, dotFileExtension);
|
|
325
337
|
});
|
|
326
|
-
editor.copyTpl(join(rootV4TemplateDirPath, 'integration',
|
|
338
|
+
editor.copyTpl(join(rootV4TemplateDirPath, 'integration', `FirstJourney${dotFileExtension}`), join(testOutDirPath, 'integration', `${config.opaJourneyFileName}${dotFileExtension}`), { ...journeyParams, appPath: config.appPath }, undefined, {
|
|
327
339
|
globOptions: { dot: true }
|
|
328
340
|
});
|
|
329
341
|
// Journey Runner
|
|
330
|
-
editor.copyTpl(join(rootV4TemplateDirPath, 'integration', 'pages',
|
|
342
|
+
editor.copyTpl(join(rootV4TemplateDirPath, 'integration', 'pages', `JourneyRunner${dotFileExtension}`), join(testOutDirPath, 'integration', 'pages', `JourneyRunner${dotFileExtension}`), config, undefined, {
|
|
343
|
+
globOptions: { dot: true }
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Writes the OpaJourneyTypes.d.ts type definition file used by generated TypeScript OPA tests.
|
|
348
|
+
*
|
|
349
|
+
* @param writeContext - shared write context (config, paths, editor, journey params)
|
|
350
|
+
*/
|
|
351
|
+
function writeOpaJourneyTypes(writeContext) {
|
|
352
|
+
const { config, rootV4TemplateDirPath, testOutDirPath, editor } = writeContext;
|
|
353
|
+
editor.copyTpl(join(rootV4TemplateDirPath, 'integration', 'types', 'OpaJourneyTypes.d.ts'), join(testOutDirPath, 'integration', 'types', 'OpaJourneyTypes.d.ts'), config, undefined, {
|
|
331
354
|
globOptions: { dot: true }
|
|
332
355
|
});
|
|
333
356
|
}
|
|
334
357
|
/**
|
|
335
358
|
* Checks whether a page object file already exists for the given feature name.
|
|
336
|
-
*
|
|
359
|
+
* Both `.ts` and `.js` extensions are checked to avoid creating duplicate page objects
|
|
360
|
+
* when regenerating in a different language than the existing tests use.
|
|
361
|
+
* If neither exists, finds the matching page config and writes the file.
|
|
337
362
|
*
|
|
338
363
|
* @param featureName - the feature/page name (equals the manifest targetKey)
|
|
339
364
|
* @param config - the OPA config containing all page configurations
|
|
340
365
|
* @param rootV4TemplateDirPath - template root directory for v4 templates
|
|
341
366
|
* @param testOutDirPath - output test directory (.../webapp/test)
|
|
342
367
|
* @param editor - a reference to a mem-fs editor
|
|
368
|
+
* @param dotFileExtension - file extension ('.ts' or '.js')
|
|
343
369
|
* @returns JourneyRunnerPage if the page was newly created, undefined otherwise
|
|
344
370
|
*/
|
|
345
|
-
function ensurePageExists(featureName, config, rootV4TemplateDirPath, testOutDirPath, editor) {
|
|
346
|
-
const
|
|
347
|
-
if (editor.exists(
|
|
371
|
+
function ensurePageExists(featureName, config, rootV4TemplateDirPath, testOutDirPath, editor, dotFileExtension) {
|
|
372
|
+
const pagesDir = join(testOutDirPath, 'integration', 'pages');
|
|
373
|
+
if (editor.exists(join(pagesDir, `${featureName}${DotFileExtension.TS}`)) ||
|
|
374
|
+
editor.exists(join(pagesDir, `${featureName}${DotFileExtension.JS}`))) {
|
|
348
375
|
return undefined;
|
|
349
376
|
}
|
|
350
377
|
const pageConfig = config.pages.find((p) => p.targetKey === featureName);
|
|
351
378
|
if (pageConfig) {
|
|
352
|
-
writePageObject(pageConfig, rootV4TemplateDirPath, testOutDirPath, editor);
|
|
353
|
-
return {
|
|
379
|
+
writePageObject(pageConfig, rootV4TemplateDirPath, testOutDirPath, editor, dotFileExtension);
|
|
380
|
+
return {
|
|
381
|
+
targetKey: featureName,
|
|
382
|
+
appPath: config.appPath,
|
|
383
|
+
template: pageConfig.template,
|
|
384
|
+
appID: config.appID,
|
|
385
|
+
componentID: pageConfig.componentID,
|
|
386
|
+
entitySet: pageConfig.entitySet,
|
|
387
|
+
contextPath: pageConfig.contextPath
|
|
388
|
+
};
|
|
354
389
|
}
|
|
355
390
|
return undefined;
|
|
356
391
|
}
|
|
@@ -360,22 +395,23 @@ function ensurePageExists(featureName, config, rootV4TemplateDirPath, testOutDir
|
|
|
360
395
|
* @param appFeatures - object containing feature data for list report, object pages, and FPM
|
|
361
396
|
* @param writeContext - shared write context (config, paths, editor, journey params)
|
|
362
397
|
* @param isStandalone - whether the generation is run in standalone mode (not during app generation)
|
|
363
|
-
* @param hasJourneyRunner - whether a JourneyRunner
|
|
398
|
+
* @param hasJourneyRunner - whether a JourneyRunner already exists (standalone upgrade path)
|
|
364
399
|
* @param virtualOPA5Configured - whether virtual OPA5 is configured
|
|
365
400
|
*/
|
|
366
401
|
function writeJourneyFiles(appFeatures, writeContext, isStandalone, hasJourneyRunner = false, virtualOPA5Configured = false) {
|
|
367
|
-
const { config, rootV4TemplateDirPath, testOutDirPath, editor, journeyParams } = writeContext;
|
|
402
|
+
const { config, rootV4TemplateDirPath, testOutDirPath, editor, journeyParams, dotFileExtension } = writeContext;
|
|
368
403
|
const generatedJourneyPages = [];
|
|
369
404
|
const newPages = [];
|
|
370
405
|
if (appFeatures.listReport?.name) {
|
|
371
|
-
editor.copyTpl(join(rootV4TemplateDirPath, 'integration',
|
|
406
|
+
editor.copyTpl(join(rootV4TemplateDirPath, 'integration', `ListReportJourney${dotFileExtension}`), join(testOutDirPath, 'integration', `${appFeatures.listReport.name}Journey${dotFileExtension}`), {
|
|
372
407
|
...journeyParams,
|
|
373
|
-
...appFeatures.listReport
|
|
408
|
+
...appFeatures.listReport,
|
|
409
|
+
appPath: config.appPath
|
|
374
410
|
}, undefined, {
|
|
375
411
|
globOptions: { dot: true }
|
|
376
412
|
});
|
|
377
413
|
generatedJourneyPages.push(appFeatures.listReport.name);
|
|
378
|
-
const lrPage = ensurePageExists(appFeatures.listReport.name, config, rootV4TemplateDirPath, testOutDirPath, editor);
|
|
414
|
+
const lrPage = ensurePageExists(appFeatures.listReport.name, config, rootV4TemplateDirPath, testOutDirPath, editor, dotFileExtension);
|
|
379
415
|
if (lrPage) {
|
|
380
416
|
newPages.push(lrPage);
|
|
381
417
|
}
|
|
@@ -383,15 +419,16 @@ function writeJourneyFiles(appFeatures, writeContext, isStandalone, hasJourneyRu
|
|
|
383
419
|
if (appFeatures.objectPages && appFeatures.objectPages.length > 0) {
|
|
384
420
|
appFeatures.objectPages.forEach((objectPage) => {
|
|
385
421
|
if (objectPage.name) {
|
|
386
|
-
editor.copyTpl(join(rootV4TemplateDirPath, 'integration',
|
|
422
|
+
editor.copyTpl(join(rootV4TemplateDirPath, 'integration', `ObjectPageJourney${dotFileExtension}`), join(testOutDirPath, 'integration', `${objectPage.name}Journey${dotFileExtension}`), {
|
|
387
423
|
...journeyParams,
|
|
388
424
|
...objectPage,
|
|
389
|
-
isStandalone
|
|
425
|
+
isStandalone,
|
|
426
|
+
appPath: config.appPath
|
|
390
427
|
}, undefined, {
|
|
391
428
|
globOptions: { dot: true }
|
|
392
429
|
});
|
|
393
430
|
generatedJourneyPages.push(objectPage.name);
|
|
394
|
-
const opPage = ensurePageExists(objectPage.name, config, rootV4TemplateDirPath, testOutDirPath, editor);
|
|
431
|
+
const opPage = ensurePageExists(objectPage.name, config, rootV4TemplateDirPath, testOutDirPath, editor, dotFileExtension);
|
|
395
432
|
if (opPage) {
|
|
396
433
|
newPages.push(opPage);
|
|
397
434
|
}
|
|
@@ -399,6 +436,13 @@ function writeJourneyFiles(appFeatures, writeContext, isStandalone, hasJourneyRu
|
|
|
399
436
|
});
|
|
400
437
|
}
|
|
401
438
|
if (appFeatures.fpm?.name) {
|
|
439
|
+
// FPM TypeScript support is out of scope for the initial TS OPA5 work
|
|
440
|
+
// (LROP only). The FPM journey path below is hardcoded `.js` and there is
|
|
441
|
+
// no `FPM.ts` template, so we force `DotFileExtension.JS` for the FPM
|
|
442
|
+
// page-object regardless of the configured `dotFileExtension`. Otherwise
|
|
443
|
+
// an LR-OP-FPM mix with `enableTypeScript` would crash in `writePageObject`
|
|
444
|
+
// when trying to load the missing `FPM.ts` template.
|
|
445
|
+
// Future work: add FPM.ts/FPMJourney.ts templates and switch to `dotFileExtension`.
|
|
402
446
|
editor.copyTpl(join(rootV4TemplateDirPath, 'integration', 'FPMJourney.js'), join(testOutDirPath, 'integration', `${appFeatures.fpm.name}Journey.js`), {
|
|
403
447
|
...journeyParams,
|
|
404
448
|
...appFeatures.fpm
|
|
@@ -406,13 +450,13 @@ function writeJourneyFiles(appFeatures, writeContext, isStandalone, hasJourneyRu
|
|
|
406
450
|
globOptions: { dot: true }
|
|
407
451
|
});
|
|
408
452
|
generatedJourneyPages.push(appFeatures.fpm.name);
|
|
409
|
-
const fpmPage = ensurePageExists(appFeatures.fpm.name, config, rootV4TemplateDirPath, testOutDirPath, editor);
|
|
453
|
+
const fpmPage = ensurePageExists(appFeatures.fpm.name, config, rootV4TemplateDirPath, testOutDirPath, editor, DotFileExtension.JS);
|
|
410
454
|
if (fpmPage) {
|
|
411
455
|
newPages.push(fpmPage);
|
|
412
456
|
}
|
|
413
457
|
}
|
|
414
458
|
if (newPages.length > 0) {
|
|
415
|
-
addPagesToJourneyRunner(newPages, testOutDirPath, editor);
|
|
459
|
+
addPagesToJourneyRunner(newPages, testOutDirPath, editor, dotFileExtension);
|
|
416
460
|
}
|
|
417
461
|
if (!virtualOPA5Configured) {
|
|
418
462
|
if (hasJourneyRunner) {
|
|
@@ -434,9 +478,10 @@ function writeJourneyFiles(appFeatures, writeContext, isStandalone, hasJourneyRu
|
|
|
434
478
|
* @param rootTemplateDirPath - template root directory
|
|
435
479
|
* @param testOutDirPath - output test directory (.../webapp/test)
|
|
436
480
|
* @param fs - a reference to a mem-fs editor
|
|
481
|
+
* @param dotFileExtension - file extension ('.ts' or '.js')
|
|
437
482
|
*/
|
|
438
|
-
function writePageObject(pageConfig, rootTemplateDirPath, testOutDirPath, fs) {
|
|
439
|
-
fs.copyTpl(join(rootTemplateDirPath, 'integration', 'pages', `${pageConfig.template}
|
|
483
|
+
function writePageObject(pageConfig, rootTemplateDirPath, testOutDirPath, fs, dotFileExtension) {
|
|
484
|
+
fs.copyTpl(join(rootTemplateDirPath, 'integration', 'pages', `${pageConfig.template}${dotFileExtension}`), join(testOutDirPath, 'integration', 'pages', `${pageConfig.targetKey}${dotFileExtension}`), pageConfig, undefined, {
|
|
440
485
|
globOptions: { dot: true }
|
|
441
486
|
});
|
|
442
487
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { generateOPAFiles } from './fiori-elements-opa-writer.js';
|
|
2
2
|
export { generateFreestyleOPAFiles } from './fiori-freestyle-opa-writer.js';
|
|
3
3
|
export { addVirtualTestConfig } from './utils/opaQUnitUtils.js';
|
|
4
|
+
export type { OPAGenerationOptions } from './types.js';
|
|
4
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,24 @@
|
|
|
1
1
|
import type { Editor } from 'mem-fs-editor';
|
|
2
|
+
export declare const DotFileExtension: {
|
|
3
|
+
readonly JS: ".js";
|
|
4
|
+
readonly TS: ".ts";
|
|
5
|
+
};
|
|
6
|
+
export type DotFileExtension = (typeof DotFileExtension)[keyof typeof DotFileExtension];
|
|
7
|
+
/**
|
|
8
|
+
* Options accepted by the public OPA test generation entry point.
|
|
9
|
+
*/
|
|
10
|
+
export type OPAGenerationOptions = {
|
|
11
|
+
/** The name of the OPA journey file. If not specified, 'FirstJourney' will be used. */
|
|
12
|
+
scriptName?: string;
|
|
13
|
+
/** The appID. If not specified, will be read from the manifest in sap.app/id. */
|
|
14
|
+
appID?: string;
|
|
15
|
+
/** The name of the html that will be used in OPA journey file. If not specified, 'index.html' will be used. */
|
|
16
|
+
htmlTarget?: string;
|
|
17
|
+
/** When true, OPA harness files are served virtually; skip writing them to disk. */
|
|
18
|
+
useVirtualPreviewEndpoints?: boolean;
|
|
19
|
+
/** If true, generate TypeScript files instead of JavaScript. */
|
|
20
|
+
enableTypeScript?: boolean;
|
|
21
|
+
};
|
|
2
22
|
export declare const SupportedPageTypes: {
|
|
3
23
|
[id: string]: string;
|
|
4
24
|
};
|
|
@@ -193,6 +213,7 @@ export type WriteContext = {
|
|
|
193
213
|
testOutDirPath: string;
|
|
194
214
|
editor: Editor;
|
|
195
215
|
journeyParams: JourneyParams;
|
|
216
|
+
dotFileExtension: DotFileExtension;
|
|
196
217
|
};
|
|
197
218
|
export type FormField = {
|
|
198
219
|
fieldGroupQualifier?: string;
|
package/dist/types.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* all other content (formatting, comments, whitespace) is preserved exactly.
|
|
5
5
|
*/
|
|
6
6
|
import type { Editor } from 'mem-fs-editor';
|
|
7
|
+
import { DotFileExtension } from '../types.js';
|
|
7
8
|
import type { TestConfig as PreviewMiddlewareTestConfig } from '@sap-ux/preview-middleware';
|
|
8
9
|
/**
|
|
9
10
|
* Splices new module paths into the sap.ui.require array of the content string.
|
|
@@ -48,13 +49,23 @@ export declare function addIntegrationOldToGitignore(basePath: string, fs: Edito
|
|
|
48
49
|
*/
|
|
49
50
|
export declare function addPathsToQUnitJs(filePaths: string[], projectPath: string, fs: Editor): void;
|
|
50
51
|
/**
|
|
51
|
-
* Page entry to splice into an existing JourneyRunner.js.
|
|
52
|
+
* Page entry to splice into an existing JourneyRunner.js / JourneyRunner.ts.
|
|
52
53
|
*/
|
|
53
54
|
export interface JourneyRunnerPage {
|
|
54
55
|
/** The page's targetKey, used as both the variable name and `onThe<targetKey>` key */
|
|
55
56
|
targetKey: string;
|
|
56
57
|
/** The app module path prefix (e.g. "project1/test/integration/pages") */
|
|
57
58
|
appPath: string;
|
|
59
|
+
/** The framework page template (`'ListReport'` or `'ObjectPage'`); used by the TS splicer to construct `new <FW>(definition, Custom<Page>)`. */
|
|
60
|
+
template?: string;
|
|
61
|
+
/** The app id (sap.app.id from the manifest); only needed by the TS splicer. */
|
|
62
|
+
appID?: string;
|
|
63
|
+
/** The component id (defined in the target section); only needed by the TS splicer. */
|
|
64
|
+
componentID?: string;
|
|
65
|
+
/** The entity set name (if the page uses an entitySet rather than a contextPath); only needed by the TS splicer. */
|
|
66
|
+
entitySet?: string;
|
|
67
|
+
/** The context path (if the page uses a contextPath rather than an entitySet); only needed by the TS splicer. */
|
|
68
|
+
contextPath?: string;
|
|
58
69
|
}
|
|
59
70
|
/**
|
|
60
71
|
* Splices new page entries into the three locations of an existing JourneyRunner.js:
|
|
@@ -74,15 +85,32 @@ export interface JourneyRunnerPage {
|
|
|
74
85
|
*/
|
|
75
86
|
export declare function splicePageIntoJourneyRunner(fileContent: string, pages: JourneyRunnerPage[]): string;
|
|
76
87
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
88
|
+
* Splices new page entries into an existing TypeScript JourneyRunner.ts:
|
|
89
|
+
* - adds a default-import line after the last existing page import
|
|
90
|
+
* - adds an entry inside the `pages: { ... }` object literal
|
|
91
|
+
*
|
|
92
|
+
* Pages already present (detected by their import line) are skipped.
|
|
93
|
+
* All other content — formatting, comments, whitespace — is preserved exactly.
|
|
94
|
+
*
|
|
95
|
+
* Note: files exceeding MAX_FILE_CONTENT_LENGTH characters are returned unchanged to prevent
|
|
96
|
+
* ReDoS on crafted inputs. Valid generated files are well within this limit.
|
|
97
|
+
*
|
|
98
|
+
* @param fileContent - the full content of the JourneyRunner.ts file
|
|
99
|
+
* @param pages - pages to add
|
|
100
|
+
* @returns the updated file content, or the original content unchanged if nothing was added
|
|
101
|
+
*/
|
|
102
|
+
export declare function splicePageIntoJourneyRunnerTs(fileContent: string, pages: JourneyRunnerPage[]): string;
|
|
103
|
+
/**
|
|
104
|
+
* Reads JourneyRunner from the project, adds new page entries, and writes the updated
|
|
105
|
+
* content back. Pages already present are skipped. Both AMD (`.js`) and ES module
|
|
106
|
+
* (`.ts`) variants are supported and dispatched on `dotFileExtension`.
|
|
80
107
|
*
|
|
81
108
|
* @param pages - pages to add
|
|
82
109
|
* @param testOutDirPath - path to the test output directory (`.../webapp/test`)
|
|
83
110
|
* @param fs - mem-fs-editor instance used to read and write the file
|
|
111
|
+
* @param dotFileExtension - file extension of the JourneyRunner ('.ts' or '.js'); defaults to '.js'
|
|
84
112
|
*/
|
|
85
|
-
export declare function addPagesToJourneyRunner(pages: JourneyRunnerPage[], testOutDirPath: string, fs: Editor): void;
|
|
113
|
+
export declare function addPagesToJourneyRunner(pages: JourneyRunnerPage[], testOutDirPath: string, fs: Editor, dotFileExtension?: DotFileExtension): void;
|
|
86
114
|
/**
|
|
87
115
|
* Returns true if any UI5 yaml file in the project contains a `fiori-tools-preview`
|
|
88
116
|
* middleware whose `test` array includes an entry with `framework: OPA5`.
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { join } from 'node:path';
|
|
7
7
|
import { readHashFromFlpSandbox } from './flpSandboxUtils.js';
|
|
8
8
|
import { getAllUi5YamlFileNames, readUi5Yaml, FileName } from '@sap-ux/project-access';
|
|
9
|
+
import { DotFileExtension } from '../types.js';
|
|
9
10
|
/** Relative path from the test output directory to opaTests.qunit.js */
|
|
10
11
|
const OPA_QUNIT_FILE = join('integration', 'opaTests.qunit.js');
|
|
11
12
|
/**
|
|
@@ -25,6 +26,13 @@ const OPA_QUNIT_FILE = join('integration', 'opaTests.qunit.js');
|
|
|
25
26
|
const SAP_UI_REQUIRE_ARRAY_REGEX = /sap\.ui\.require\s*\(\s*\[([^\]]*)\]\s*,\s*function/d;
|
|
26
27
|
/** ReDoS mitigation: files larger than this are returned unchanged rather than matched with regex. */
|
|
27
28
|
const MAX_FILE_CONTENT_LENGTH = 10000;
|
|
29
|
+
/**
|
|
30
|
+
* Escapes regex metacharacters in a string so it can be safely embedded in a `RegExp` pattern.
|
|
31
|
+
*
|
|
32
|
+
* @param value - the string to escape
|
|
33
|
+
* @returns the escaped string
|
|
34
|
+
*/
|
|
35
|
+
const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, String.raw `\$&`);
|
|
28
36
|
/**
|
|
29
37
|
* Splices new module paths into the sap.ui.require array of the content string.
|
|
30
38
|
* Entries that are already present are skipped. All other content is preserved exactly.
|
|
@@ -161,8 +169,13 @@ export function addPathsToQUnitJs(filePaths, projectPath, fs) {
|
|
|
161
169
|
// If the file doesn't exist or can't be read, do nothing
|
|
162
170
|
}
|
|
163
171
|
}
|
|
164
|
-
/**
|
|
165
|
-
|
|
172
|
+
/**
|
|
173
|
+
* Builds the relative path from the test output directory to the JourneyRunner file.
|
|
174
|
+
*
|
|
175
|
+
* @param dotFileExtension - file extension ('.ts' or '.js')
|
|
176
|
+
* @returns the relative path
|
|
177
|
+
*/
|
|
178
|
+
const getJourneyRunnerFilePath = (dotFileExtension) => join('integration', 'pages', `JourneyRunner${dotFileExtension}`);
|
|
166
179
|
/**
|
|
167
180
|
* Splices new page entries into the three locations of an existing JourneyRunner.js:
|
|
168
181
|
* - the sap.ui.define dependency array
|
|
@@ -246,19 +259,187 @@ export function splicePageIntoJourneyRunner(fileContent, pages) {
|
|
|
246
259
|
return result;
|
|
247
260
|
}
|
|
248
261
|
/**
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
262
|
+
* Filters the input page list down to those whose `Custom<targetKey>` import line is not yet
|
|
263
|
+
* present in the existing JourneyRunner.ts content.
|
|
264
|
+
*
|
|
265
|
+
* @param fileContent - the existing JourneyRunner.ts content
|
|
266
|
+
* @param pages - the candidate pages to splice in
|
|
267
|
+
* @returns the subset of pages that need to be added
|
|
268
|
+
*/
|
|
269
|
+
function findPagesToAdd(fileContent, pages) {
|
|
270
|
+
return pages.filter((page) => {
|
|
271
|
+
const importPattern = new RegExp(String.raw `from\s+"\./${escapeRegex(page.targetKey)}"`);
|
|
272
|
+
return !importPattern.test(fileContent);
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Returns the offset of the character immediately after the last `import ... from "..."` line in
|
|
277
|
+
* the given content, or -1 if no import line is found.
|
|
278
|
+
*
|
|
279
|
+
* Uses `\b` word boundaries (zero-width) around `import` and `from` so the `[^\n]*?` middle
|
|
280
|
+
* quantifier doesn't sit next to another quantifier that could match the same characters. This
|
|
281
|
+
* avoids the consecutive-overlapping-quantifier shape that triggers catastrophic backtracking.
|
|
282
|
+
*
|
|
283
|
+
* @param content - the file content to scan
|
|
284
|
+
* @returns the index immediately after the last import line, or -1 if none found
|
|
285
|
+
*/
|
|
286
|
+
function findLastImportEnd(content) {
|
|
287
|
+
const importLineRegex = /^import\b[^\n]*?\bfrom[ \t]+["'][^"']+["'];?[ \t]*$/gm;
|
|
288
|
+
let lastImportEnd = -1;
|
|
289
|
+
let importMatch;
|
|
290
|
+
while ((importMatch = importLineRegex.exec(content)) !== null) {
|
|
291
|
+
lastImportEnd = importMatch.index + importMatch[0].length;
|
|
292
|
+
}
|
|
293
|
+
return lastImportEnd;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Walks forward from the opening `{` of a `pages: { ... }` object literal, counting braces, and
|
|
297
|
+
* returns the index of the matching closing `}`. The pages object now contains nested `{}` (the
|
|
298
|
+
* page-definition object) so a regex with `[^}]*` would stop at the first inner closing brace.
|
|
299
|
+
*
|
|
300
|
+
* @param content - the file content
|
|
301
|
+
* @param openBraceIdx - the index of the `{` that opens the pages object
|
|
302
|
+
* @returns the index of the matching closing `}` (or `content.length` if not found)
|
|
303
|
+
*/
|
|
304
|
+
function findMatchingClosingBrace(content, openBraceIdx) {
|
|
305
|
+
let depth = 1;
|
|
306
|
+
let i = openBraceIdx + 1;
|
|
307
|
+
while (i < content.length && depth > 0) {
|
|
308
|
+
const ch = content[i];
|
|
309
|
+
if (ch === '{') {
|
|
310
|
+
depth++;
|
|
311
|
+
}
|
|
312
|
+
else if (ch === '}') {
|
|
313
|
+
depth--;
|
|
314
|
+
}
|
|
315
|
+
if (depth === 0) {
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
i++;
|
|
319
|
+
}
|
|
320
|
+
return i;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Builds the source-code block for a single new entry in the `pages: { ... }` object literal.
|
|
324
|
+
*
|
|
325
|
+
* @param page - the page to render
|
|
326
|
+
* @param pageIndent - leading whitespace for the entry's outer line
|
|
327
|
+
* @param innerIndent - leading whitespace for the entry's nested lines
|
|
328
|
+
* @returns the multi-line source block
|
|
329
|
+
*/
|
|
330
|
+
function buildPageEntry(page, pageIndent, innerIndent) {
|
|
331
|
+
const fw = page.template ?? 'ListReport';
|
|
332
|
+
return [
|
|
333
|
+
`${pageIndent}onThe${page.targetKey}: new ${fw}(`,
|
|
334
|
+
`${innerIndent}{`,
|
|
335
|
+
`${innerIndent} appId: "${page.appID ?? ''}",`,
|
|
336
|
+
`${innerIndent} componentId: "${page.componentID ?? ''}",`,
|
|
337
|
+
`${innerIndent} entitySet: "${page.entitySet ?? ''}",`,
|
|
338
|
+
`${innerIndent} contextPath: "${page.contextPath ?? ''}"`,
|
|
339
|
+
`${innerIndent}},`,
|
|
340
|
+
`${innerIndent}Custom${page.targetKey}`,
|
|
341
|
+
`${pageIndent})`
|
|
342
|
+
].join('\n');
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Inserts the given import lines after the last existing `import` line in `content`. If `content`
|
|
346
|
+
* has no `import` lines, returns it unchanged.
|
|
347
|
+
*
|
|
348
|
+
* @param content - the file content
|
|
349
|
+
* @param newImportLines - the import lines to insert (each should NOT include a leading newline)
|
|
350
|
+
* @returns the updated content
|
|
351
|
+
*/
|
|
352
|
+
function insertAfterLastImport(content, newImportLines) {
|
|
353
|
+
if (newImportLines.length === 0) {
|
|
354
|
+
return content;
|
|
355
|
+
}
|
|
356
|
+
const lastImportEnd = findLastImportEnd(content);
|
|
357
|
+
if (lastImportEnd < 0) {
|
|
358
|
+
return content;
|
|
359
|
+
}
|
|
360
|
+
const newImports = newImportLines.map((line) => `\n${line}`).join('');
|
|
361
|
+
return `${content.slice(0, lastImportEnd)}${newImports}${content.slice(lastImportEnd)}`;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Inserts the given new page entries inside the `pages: { ... }` object literal of `content`. If
|
|
365
|
+
* `content` has no `pages: {` block, returns it unchanged.
|
|
366
|
+
*
|
|
367
|
+
* @param content - the file content
|
|
368
|
+
* @param toAdd - the pages to add
|
|
369
|
+
* @returns the updated content
|
|
370
|
+
*/
|
|
371
|
+
function insertIntoPagesObject(content, toAdd) {
|
|
372
|
+
const pagesStartMatch = /pages\s*:\s*\{/.exec(content);
|
|
373
|
+
if (!pagesStartMatch) {
|
|
374
|
+
return content;
|
|
375
|
+
}
|
|
376
|
+
const openBraceIdx = content.indexOf('{', pagesStartMatch.index);
|
|
377
|
+
const pagesBodyEnd = findMatchingClosingBrace(content, openBraceIdx);
|
|
378
|
+
const pagesBody = content.slice(openBraceIdx + 1, pagesBodyEnd);
|
|
379
|
+
// Detect indentation from the first existing page entry
|
|
380
|
+
const pageIndentMatch = /^([ \t]+)on/m.exec(pagesBody);
|
|
381
|
+
const pageIndent = pageIndentMatch ? pageIndentMatch[1] : ' ';
|
|
382
|
+
const innerIndent = pageIndent + ' ';
|
|
383
|
+
const newPageEntries = toAdd.map((page) => buildPageEntry(page, pageIndent, innerIndent)).join(',\n');
|
|
384
|
+
// Ensure the last existing entry ends with a comma before we insert after it.
|
|
385
|
+
const trimmedPagesEnd = content.slice(0, pagesBodyEnd).trimEnd();
|
|
386
|
+
const needsComma = !trimmedPagesEnd.endsWith(',') && !trimmedPagesEnd.endsWith('{');
|
|
387
|
+
const commaFix = needsComma ? ',' : '';
|
|
388
|
+
const trailingWhitespace = content.slice(trimmedPagesEnd.length, pagesBodyEnd);
|
|
389
|
+
return `${trimmedPagesEnd}${commaFix}\n${newPageEntries}${trailingWhitespace}${content.slice(pagesBodyEnd)}`;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Splices new page entries into an existing TypeScript JourneyRunner.ts:
|
|
393
|
+
* - adds a default-import line after the last existing page import
|
|
394
|
+
* - adds an entry inside the `pages: { ... }` object literal
|
|
395
|
+
*
|
|
396
|
+
* Pages already present (detected by their import line) are skipped.
|
|
397
|
+
* All other content — formatting, comments, whitespace — is preserved exactly.
|
|
398
|
+
*
|
|
399
|
+
* Note: files exceeding MAX_FILE_CONTENT_LENGTH characters are returned unchanged to prevent
|
|
400
|
+
* ReDoS on crafted inputs. Valid generated files are well within this limit.
|
|
401
|
+
*
|
|
402
|
+
* @param fileContent - the full content of the JourneyRunner.ts file
|
|
403
|
+
* @param pages - pages to add
|
|
404
|
+
* @returns the updated file content, or the original content unchanged if nothing was added
|
|
405
|
+
*/
|
|
406
|
+
export function splicePageIntoJourneyRunnerTs(fileContent, pages) {
|
|
407
|
+
if (fileContent.length > MAX_FILE_CONTENT_LENGTH) {
|
|
408
|
+
return fileContent;
|
|
409
|
+
}
|
|
410
|
+
const toAdd = findPagesToAdd(fileContent, pages);
|
|
411
|
+
if (toAdd.length === 0) {
|
|
412
|
+
return fileContent;
|
|
413
|
+
}
|
|
414
|
+
// Determine which framework imports (ListReport / ObjectPage) are missing and need to be added.
|
|
415
|
+
const frameworkTemplates = Array.from(new Set(toAdd.map((page) => page.template).filter((t) => Boolean(t))));
|
|
416
|
+
const missingFrameworkImports = frameworkTemplates.filter((tpl) => !fileContent.includes(`from "sap/fe/test/${tpl}"`));
|
|
417
|
+
const newImportLines = [
|
|
418
|
+
...missingFrameworkImports.map((tpl) => `import ${tpl} from "sap/fe/test/${tpl}";`),
|
|
419
|
+
...toAdd.map((page) => `import Custom${page.targetKey} from "./${page.targetKey}";`)
|
|
420
|
+
];
|
|
421
|
+
const withImports = insertAfterLastImport(fileContent, newImportLines);
|
|
422
|
+
return insertIntoPagesObject(withImports, toAdd);
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Reads JourneyRunner from the project, adds new page entries, and writes the updated
|
|
426
|
+
* content back. Pages already present are skipped. Both AMD (`.js`) and ES module
|
|
427
|
+
* (`.ts`) variants are supported and dispatched on `dotFileExtension`.
|
|
252
428
|
*
|
|
253
429
|
* @param pages - pages to add
|
|
254
430
|
* @param testOutDirPath - path to the test output directory (`.../webapp/test`)
|
|
255
431
|
* @param fs - mem-fs-editor instance used to read and write the file
|
|
432
|
+
* @param dotFileExtension - file extension of the JourneyRunner ('.ts' or '.js'); defaults to '.js'
|
|
256
433
|
*/
|
|
257
|
-
export function addPagesToJourneyRunner(pages, testOutDirPath, fs) {
|
|
434
|
+
export function addPagesToJourneyRunner(pages, testOutDirPath, fs, dotFileExtension = DotFileExtension.JS) {
|
|
435
|
+
if (pages.length === 0) {
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
258
438
|
try {
|
|
259
|
-
const filePath = join(testOutDirPath,
|
|
439
|
+
const filePath = join(testOutDirPath, getJourneyRunnerFilePath(dotFileExtension));
|
|
260
440
|
const content = fs.read(filePath);
|
|
261
|
-
const
|
|
441
|
+
const splice = dotFileExtension === DotFileExtension.TS ? splicePageIntoJourneyRunnerTs : splicePageIntoJourneyRunner;
|
|
442
|
+
const updated = splice(content, pages);
|
|
262
443
|
if (updated !== content) {
|
|
263
444
|
fs.write(filePath, updated);
|
|
264
445
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sap-ux/ui5-test-writer",
|
|
3
3
|
"description": "SAP UI5 tests writer",
|
|
4
|
-
"version": "1.0
|
|
4
|
+
"version": "1.1.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/SAP/open-ux-tools.git",
|
|
@@ -28,11 +28,11 @@
|
|
|
28
28
|
"@sap/ux-specification": "1.144.0",
|
|
29
29
|
"@sap-ux/edmx-parser": "0.10.0",
|
|
30
30
|
"@sap-ux/annotation-converter": "0.10.21",
|
|
31
|
-
"@sap-ux/ui5-application-writer": "2.0.
|
|
31
|
+
"@sap-ux/ui5-application-writer": "2.0.3",
|
|
32
32
|
"@sap-ux/logger": "1.0.1",
|
|
33
|
-
"@sap-ux/fiori-generator-shared": "1.0.
|
|
34
|
-
"@sap-ux/project-access": "2.1.
|
|
35
|
-
"@sap-ux/preview-middleware": "1.0.
|
|
33
|
+
"@sap-ux/fiori-generator-shared": "1.0.9",
|
|
34
|
+
"@sap-ux/project-access": "2.1.2",
|
|
35
|
+
"@sap-ux/preview-middleware": "1.0.14"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@jest/globals": "30.3.0",
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import opaTest from "sap/ui/test/opaQunit";
|
|
2
|
+
import type { Given, When, Then } from "./types/OpaJourneyTypes";
|
|
3
|
+
import runner from "./pages/JourneyRunner";
|
|
4
|
+
|
|
5
|
+
function journey() {
|
|
6
|
+
QUnit.module("First journey");
|
|
7
|
+
|
|
8
|
+
opaTest("Start application", function (Given: Given, _When: When<% if (startLR) { %>, Then: Then<% } else { %>, _Then: Then<% } %>) {
|
|
9
|
+
Given.iStartMyApp();
|
|
10
|
+
<% if (startLR) { %>Then.onThe<%- startLR %>.iSeeThisPage();<%} %>
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
<% if (startLR) { %>
|
|
14
|
+
opaTest("Navigate to ObjectPage", function (_Given: Given, When: When, Then: Then) {
|
|
15
|
+
// Note: this test will fail if the ListReport page doesn't show any data
|
|
16
|
+
<% if (!hideFilterBar) { %>
|
|
17
|
+
When.onThe<%- startLR%>.onFilterBar().iExecuteSearch();
|
|
18
|
+
<%} %>
|
|
19
|
+
Then.onThe<%- startLR%>.onTable("").iCheckRows();
|
|
20
|
+
<% if (navigatedOP) { %>
|
|
21
|
+
When.onThe<%- startLR%>.onTable("").iPressRow(0);
|
|
22
|
+
Then.onThe<%- navigatedOP%>.iSeeThisPage();
|
|
23
|
+
<%} %>
|
|
24
|
+
});
|
|
25
|
+
<%} %>
|
|
26
|
+
opaTest("Teardown", function (Given: Given) {
|
|
27
|
+
// Cleanup
|
|
28
|
+
Given.iTearDownMyApp();
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
runner.run([journey]);
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/******************************************************************************
|
|
2
|
+
* ╔═══════════════════════════════════════════════════════════════════════╗ *
|
|
3
|
+
* ║ ║ *
|
|
4
|
+
* ║ WARNING: AUTO-GENERATED FILE ║ *
|
|
5
|
+
* ║ ║ *
|
|
6
|
+
* ║ This file is automatically generated by SAP Fiori tools and is ║ *
|
|
7
|
+
* ║ overwritten when you run the OPA test generator again. ║ *
|
|
8
|
+
* ║ ║ *
|
|
9
|
+
* ║ To add your own custom tests: ║ *
|
|
10
|
+
* ║ - Create a new journey test file in this directory. ║ *
|
|
11
|
+
* ║ - Follow the same pattern as this file. ║ *
|
|
12
|
+
* ║ - Add the new file to the opaTests.qunit.js config file. ║ *
|
|
13
|
+
* ║ - Custom journey files are not overwritten. ║ *
|
|
14
|
+
* ║ ║ *
|
|
15
|
+
* ╚═══════════════════════════════════════════════════════════════════════╝ *
|
|
16
|
+
******************************************************************************/
|
|
17
|
+
|
|
18
|
+
import opaTest from "sap/ui/test/opaQunit";
|
|
19
|
+
import type { Given, When, Then } from "./types/OpaJourneyTypes";
|
|
20
|
+
<%_
|
|
21
|
+
const usesFilterFieldIdentifier =
|
|
22
|
+
(!hideFilterBar && filterBarItems && filterBarItems.length > 0) ||
|
|
23
|
+
(semanticKey && semanticKey.missingFromFilterBar && semanticKey.missingFromFilterBar.length > 0);
|
|
24
|
+
-%>
|
|
25
|
+
<% if (usesFilterFieldIdentifier) { -%>
|
|
26
|
+
import type { FilterFieldIdentifier } from "sap/fe/test/api/FilterBarAPI";
|
|
27
|
+
<% } -%>
|
|
28
|
+
import runner from "./pages/JourneyRunner";
|
|
29
|
+
|
|
30
|
+
function journey() {
|
|
31
|
+
QUnit.module("<%- name%>ListReport journey");
|
|
32
|
+
|
|
33
|
+
opaTest("Start application", function (Given: Given, _When: When, Then: Then) {
|
|
34
|
+
Given.iStartMyApp();
|
|
35
|
+
<%_ startPages.forEach(function(pageName) { %>
|
|
36
|
+
Then.onThe<%- pageName %>.iSeeThisPage();
|
|
37
|
+
<%_ }); -%>
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
<%_ if (!hideFilterBar && filterBarItems && filterBarItems.length > 0) { -%>
|
|
41
|
+
opaTest("Check filter bar", function (_Given: Given, _When: When, Then: Then) {
|
|
42
|
+
<%_ filterBarItems.forEach(function(item) { _%>
|
|
43
|
+
Then.onThe<%- startLR%>.onFilterBar().iCheckFilterField("<%- item %>" as unknown as FilterFieldIdentifier);
|
|
44
|
+
<%_ }); -%>
|
|
45
|
+
});
|
|
46
|
+
<%_ } -%>
|
|
47
|
+
<%_ if (semanticKey && semanticKey.missingFromFilterBar && semanticKey.missingFromFilterBar.length > 0) { %>
|
|
48
|
+
opaTest("Add semantic key properties to filter bar", function (_Given: Given, When: When, Then: Then) {
|
|
49
|
+
Then.onThe<%- startLR%>.onFilterBar().iOpenFilterAdaptation();
|
|
50
|
+
<%_ semanticKey.missingFromFilterBar.forEach(function(property) { _%>
|
|
51
|
+
When.onThe<%- startLR%>.onFilterBar().iAddAdaptationFilterField("<%- property %>");
|
|
52
|
+
<%_ }); -%>
|
|
53
|
+
Then.onThe<%- startLR%>.onFilterBar().iConfirmFilterAdaptation();
|
|
54
|
+
<%_ semanticKey.missingFromFilterBar.forEach(function(property) { _%>
|
|
55
|
+
Then.onThe<%- startLR%>.onFilterBar().iCheckFilterField("<%- property %>" as unknown as FilterFieldIdentifier);
|
|
56
|
+
<%_ }); -%>
|
|
57
|
+
<%_ semanticKey.missingFromFilterBar.forEach(function(property) { _%>
|
|
58
|
+
// Then.onThe<%- startLR%>.onFilterBar().iChangeFilterField({ property: "<%- property %>" });
|
|
59
|
+
<%_ }); -%>
|
|
60
|
+
// Then.onThe<%- startLR%>.onFilterBar().iExecuteSearch();
|
|
61
|
+
// Then.onThe<%- startLR%>.onTable("").iCheckRows();
|
|
62
|
+
// Then.onThe<%- startLR%>.onTable("").iSelectRows(0);
|
|
63
|
+
// Then.onThe<%- startLR%>.onTable("").iCheckAction("<Action Name>", { enabled: true });
|
|
64
|
+
});
|
|
65
|
+
<%_ } -%>
|
|
66
|
+
|
|
67
|
+
// Note: this test will only work if the ListReport page has a search field and shows data that matches the search term. Please ensure that the test data and search term are set up accordingly.
|
|
68
|
+
// opaTest("Perform a global search and check the result", function (Given: Given, When: When, Then: Then) {
|
|
69
|
+
// When.onThe<%- startLR%>.onFilterBar().iChangeSearchField("Search Term");
|
|
70
|
+
// When.onThe<%- startLR%>.onFilterBar().iExecuteSearch();
|
|
71
|
+
// Then.onThe<%- startLR%>.onTable("").iCheckRows();
|
|
72
|
+
// });
|
|
73
|
+
|
|
74
|
+
<%_ if ((toolBarActions && toolBarActions.length > 0 ) || (tableColumns && Object.keys(tableColumns).length > 0)) { -%>
|
|
75
|
+
opaTest("Check table columns and actions", function (_Given: Given, _When: When, Then: Then) {
|
|
76
|
+
<%_ if (toolBarActions && toolBarActions.length > 0) { -%>
|
|
77
|
+
<%_ if (createButton.visible && !isALP) { _%>
|
|
78
|
+
Then.onThe<%- startLR%>.onTable("").iCheckCreate({ visible: true });
|
|
79
|
+
// Then.onthe<%- startLR%>.onTable("").iPressCreate();
|
|
80
|
+
<%_ } _%>
|
|
81
|
+
<%_ if (deleteButton.visible) { _%>
|
|
82
|
+
// Then.onthe<%- startLR%>.onTable("").iPressDelete();
|
|
83
|
+
Then.onThe<%- startLR%>.onTable("").iCheckDelete({ visible: true });
|
|
84
|
+
<%_ } _%>
|
|
85
|
+
<%_ toolBarActions.forEach(function(item) { _%>
|
|
86
|
+
<%_ if (item.visible) { _%>
|
|
87
|
+
// Then.onThe<%- startLR%>.onTable("").iPressAction("<%- item.label %>");
|
|
88
|
+
Then.onThe<%- startLR%>.onTable("").iCheckAction("<%- item.label %>", { enabled: <%- item.enabled === true %> });
|
|
89
|
+
<%_ } _%>
|
|
90
|
+
<%_ }); -%>
|
|
91
|
+
<%_ } -%>
|
|
92
|
+
<%_ if (tableColumns && Object.keys(tableColumns).length > 0) { -%>
|
|
93
|
+
Then.onThe<%- startLR %>.onTable("").iCheckColumns(undefined, <%- JSON.stringify(tableColumns) %>);
|
|
94
|
+
<%_ } %>
|
|
95
|
+
});
|
|
96
|
+
<%_ } %>
|
|
97
|
+
|
|
98
|
+
<% if (startLR) { %>
|
|
99
|
+
opaTest("Navigate to ObjectPage", function (_Given: Given, When: When, Then: Then) {
|
|
100
|
+
// Note: this test will fail if the ListReport page doesn't show any data
|
|
101
|
+
<% if (!hideFilterBar) { %>
|
|
102
|
+
When.onThe<%- startLR%>.onFilterBar().iExecuteSearch();
|
|
103
|
+
<%} %>
|
|
104
|
+
Then.onThe<%- startLR%>.onTable("").iCheckRows();
|
|
105
|
+
<% if (navigatedOP) { %>
|
|
106
|
+
When.onThe<%- startLR%>.onTable("").iPressRow(0);
|
|
107
|
+
Then.onThe<%- navigatedOP%>.iSeeThisPage();
|
|
108
|
+
<%} %>
|
|
109
|
+
});
|
|
110
|
+
<%} %>
|
|
111
|
+
opaTest("Teardown", function (Given: Given) {
|
|
112
|
+
// Cleanup
|
|
113
|
+
Given.iTearDownMyApp();
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
runner.run([journey]);
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/******************************************************************************
|
|
2
|
+
* ╔═══════════════════════════════════════════════════════════════════════╗ *
|
|
3
|
+
* ║ ║ *
|
|
4
|
+
* ║ WARNING: AUTO-GENERATED FILE ║ *
|
|
5
|
+
* ║ ║ *
|
|
6
|
+
* ║ This file is automatically generated by SAP Fiori tools and is ║ *
|
|
7
|
+
* ║ overwritten when you run the OPA test generator again. ║ *
|
|
8
|
+
* ║ ║ *
|
|
9
|
+
* ║ To add your own custom tests: ║ *
|
|
10
|
+
* ║ - Create a new journey test file in this directory. ║ *
|
|
11
|
+
* ║ - Follow the same pattern as this file. ║ *
|
|
12
|
+
* ║ - Add the new file to the opaTests.qunit.js config file. ║ *
|
|
13
|
+
* ║ - Custom journey files are not overwritten. ║ *
|
|
14
|
+
* ║ ║ *
|
|
15
|
+
* ╚═══════════════════════════════════════════════════════════════════════╝ *
|
|
16
|
+
******************************************************************************/
|
|
17
|
+
|
|
18
|
+
import opaTest from "sap/ui/test/opaQunit";
|
|
19
|
+
import type { Given, When, Then } from "./types/OpaJourneyTypes";
|
|
20
|
+
<%_
|
|
21
|
+
const usesFieldIdentifier = (headerSections || []).some(function(section) {
|
|
22
|
+
return section.form && section.fields && section.fields.length > 0;
|
|
23
|
+
});
|
|
24
|
+
const usesFormIdentifier = !isStandalone && (bodySections || []).some(function(section) {
|
|
25
|
+
const subSectionsHaveForm = (section.subSections || []).some(function(sub) {
|
|
26
|
+
return sub.fields && sub.fields.length > 0;
|
|
27
|
+
});
|
|
28
|
+
const sectionHasFormFields = !(section.subSections && section.subSections.length > 0) && section.fields && section.fields.length > 0;
|
|
29
|
+
const hasFormAction = (section.actions || []).some(function(action) {
|
|
30
|
+
return action.visible && !(section.isTable && section.navigationProperty);
|
|
31
|
+
});
|
|
32
|
+
return subSectionsHaveForm || sectionHasFormFields || hasFormAction;
|
|
33
|
+
});
|
|
34
|
+
-%>
|
|
35
|
+
<% if (usesFieldIdentifier) { -%>
|
|
36
|
+
import type { FieldIdentifier } from "sap/fe/test/api/BaseAPI";
|
|
37
|
+
<% } -%>
|
|
38
|
+
<% if (usesFormIdentifier) { -%>
|
|
39
|
+
import type { FormIdentifier } from "sap/fe/test/api/FormAPI";
|
|
40
|
+
<% } -%>
|
|
41
|
+
import runner from "./pages/JourneyRunner";
|
|
42
|
+
|
|
43
|
+
function journey() {
|
|
44
|
+
QUnit.module("<%- name%>ObjectPage journey");
|
|
45
|
+
|
|
46
|
+
opaTest("Navigate to <%- name%>ObjectPage", function (Given: Given, When: When, Then: Then) {
|
|
47
|
+
Given.iStartMyApp();
|
|
48
|
+
<% if (!hideFilterBar) { %>
|
|
49
|
+
When.onThe<%- navigationParents.parentLRName%>.onFilterBar().iExecuteSearch();
|
|
50
|
+
<% } %>
|
|
51
|
+
Then.onThe<%- navigationParents.parentLRName%>.onTable("").iCheckRows();
|
|
52
|
+
When.onThe<%- navigationParents.parentLRName%>.onTable("").iPressRow(0);
|
|
53
|
+
<% if(navigationParents.parentOPName) { %>
|
|
54
|
+
Then.onThe<%- navigationParents.parentOPName%>.iSeeThisPage();
|
|
55
|
+
Then.onThe<%- navigationParents.parentOPName%>.onTable({ property: "<%- navigationParents.parentOPTableSection %>" }).iCheckRows();
|
|
56
|
+
When.onThe<%- navigationParents.parentOPName%>.onTable({ property: "<%- navigationParents.parentOPTableSection %>" }).iPressRow(0);
|
|
57
|
+
<% } %>
|
|
58
|
+
Then.onThe<%- name%>.iSeeThisPage();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
<% if (headerActions?.length > 0 && !isStandalone) { -%>
|
|
62
|
+
opaTest("Check header actions of the Object Page", function (_Given: Given, _When: When, Then: Then) {
|
|
63
|
+
<% if (editButton?.visible) { -%>
|
|
64
|
+
// Ensure the opened entity is not in Draft state before uncommenting
|
|
65
|
+
// Then.onThe<%- name%>.onHeader().iCheckEdit({ visible: true });
|
|
66
|
+
// When.onThe<%- name%>.onHeader().iPressEdit();
|
|
67
|
+
<% } -%>
|
|
68
|
+
<% headerActions.forEach(function(action) { -%>
|
|
69
|
+
<% if (action.visible) { -%>
|
|
70
|
+
<% if (action.enabled === 'dynamic') { -%>
|
|
71
|
+
Then.onThe<%- name%>.onHeader().iCheckAction("<%- action.label %>" /* , { enabled: true } */);
|
|
72
|
+
<% } else { -%>
|
|
73
|
+
Then.onThe<%- name%>.onHeader().iCheckAction("<%- action.label %>", { enabled: <%- action.enabled === true %> });
|
|
74
|
+
<% } -%>
|
|
75
|
+
// When.onThe<%- name%>.onHeader().iPressAction("<%- action.label %>");
|
|
76
|
+
<% } -%>
|
|
77
|
+
<% }); -%>
|
|
78
|
+
});
|
|
79
|
+
<% } -%>
|
|
80
|
+
|
|
81
|
+
<% if (headerSections?.length > 0) { -%>
|
|
82
|
+
opaTest("Check header facets of the Object Page", function (_Given: Given, _When: When, Then: Then) {
|
|
83
|
+
<% headerSections.forEach(function(section) { -%>
|
|
84
|
+
<% if (section.microChart) { -%>
|
|
85
|
+
Then.onThe<%- name%>.onHeader().iCheckMicroChart("<%- section.title %>", "");
|
|
86
|
+
<% } else { -%>
|
|
87
|
+
Then.onThe<%- name%>.onHeader().iCheckHeaderFacet({ facetId: "<%- section.facetId %>" });
|
|
88
|
+
<% if (section.form) { -%>
|
|
89
|
+
<% section.fields.forEach(function(field) { -%>
|
|
90
|
+
Then.onThe<%- name%>.onHeader().iCheckFieldInFieldGroup({
|
|
91
|
+
fieldGroup: "FieldGroup::<%- field.fieldGroupQualifier %>",
|
|
92
|
+
field: "<%- field.field %>",
|
|
93
|
+
targetAnnotation: "<%- field.targetAnnotation %>"
|
|
94
|
+
} as unknown as FieldIdentifier);
|
|
95
|
+
<% }) -%>
|
|
96
|
+
<% } -%>
|
|
97
|
+
<% } -%>
|
|
98
|
+
<% }) -%>
|
|
99
|
+
});
|
|
100
|
+
<% } -%>
|
|
101
|
+
|
|
102
|
+
<% if (bodySections?.length > 0 && !isStandalone) { -%>
|
|
103
|
+
opaTest("Check body sections of the Object Page", function (_Given: Given, <% if (bodySections?.length > 1) { %>When: When<% } else { %>_When: When<% } %>, Then: Then) {
|
|
104
|
+
<% if (bodySections?.length > 1) { -%>
|
|
105
|
+
Then.onThe<%- name%>.iCheckNumberOfSections(<%- bodySections.length %>);
|
|
106
|
+
<% } -%>
|
|
107
|
+
<% bodySections.forEach(function(section) { -%>
|
|
108
|
+
<% if (bodySections.length > 1) { -%>
|
|
109
|
+
When.onThe<%- name%>.iPressSectionIconTabFilterButton("<%- section.id %>");
|
|
110
|
+
<% } -%>
|
|
111
|
+
Then.onThe<%- name%>.iCheckSection({ section: "<%- section.id %>" }, {});
|
|
112
|
+
<% if (section.actions && section.actions.length > 0) { -%>
|
|
113
|
+
<% section.actions.forEach(function(action) { -%>
|
|
114
|
+
<% if (action.visible) { -%>
|
|
115
|
+
<% if (section.isTable && section.navigationProperty) { -%>
|
|
116
|
+
<% if (action.enabled === 'dynamic') { -%>
|
|
117
|
+
Then.onThe<%- name%>.onTable({ property: "<%- section.navigationProperty %>" }).iCheckAction("<%- action.label %>" /* , { enabled: true } */);
|
|
118
|
+
<% } else { -%>
|
|
119
|
+
Then.onThe<%- name%>.onTable({ property: "<%- section.navigationProperty %>" }).iCheckAction("<%- action.label %>", { enabled: <%- action.enabled === true %> });
|
|
120
|
+
<% } -%>
|
|
121
|
+
// When.onThe<%- name%>.onTable({ property: "<%- section.navigationProperty %>" }).iPressAction("<%- action.label %>");
|
|
122
|
+
<% } else { -%>
|
|
123
|
+
<% if (action.enabled === 'dynamic') { -%>
|
|
124
|
+
Then.onThe<%- name%>.onForm({ section: "<%- section.id %>" } as unknown as FormIdentifier).iCheckAction("<%- action.label %>" /* , { enabled: true } */);
|
|
125
|
+
<% } else { -%>
|
|
126
|
+
Then.onThe<%- name%>.onForm({ section: "<%- section.id %>" } as unknown as FormIdentifier).iCheckAction("<%- action.label %>", { enabled: <%- action.enabled === true %> });
|
|
127
|
+
<% } -%>
|
|
128
|
+
// When.onThe<%- name%>.onForm({ section: "<%- section.id %>" } as unknown as FormIdentifier).iPressAction("<%- action.label %>");
|
|
129
|
+
<% } -%>
|
|
130
|
+
<% } -%>
|
|
131
|
+
<% }); -%>
|
|
132
|
+
<% } -%>
|
|
133
|
+
<% if (section.isTable && section.navigationProperty) { -%>
|
|
134
|
+
<% if (section.createButton?.visible) { -%>
|
|
135
|
+
Then.onThe<%- name%>.onTable({ property: "<%- section.navigationProperty %>" }).iCheckCreate({ visible: true });
|
|
136
|
+
// When.onThe<%- name%>.onTable({ property: "<%- section.navigationProperty %>" }).iPressCreate();
|
|
137
|
+
<% } -%>
|
|
138
|
+
<% if (section.deleteButton?.visible) { -%>
|
|
139
|
+
Then.onThe<%- name%>.onTable({ property: "<%- section.navigationProperty %>" }).iCheckDelete({ visible: true });
|
|
140
|
+
// When.onThe<%- name%>.onTable({ property: "<%- section.navigationProperty %>" }).iPressDelete();
|
|
141
|
+
<% } -%>
|
|
142
|
+
<% } -%>
|
|
143
|
+
<% if (section?.subSections?.length > 0) { -%>
|
|
144
|
+
<% section.subSections.forEach(function(subSection) { -%>
|
|
145
|
+
//When.onThe<%- name%>.iGoToSection({ section: "<%- section.id %>", subSection: "<%- subSection.id %>" });
|
|
146
|
+
Then.onThe<%- name%>.iCheckSubSection({ section: "<%- subSection.id %>" });
|
|
147
|
+
<% if (subSection.fields && subSection.fields.length > 0) { -%>
|
|
148
|
+
<% subSection.fields.forEach(function(field) { -%>
|
|
149
|
+
Then.onThe<%- name%>.onForm({ section: "<%- subSection.id %>" } as unknown as FormIdentifier).iCheckField({ property: "<%- field.property %>" });
|
|
150
|
+
<% }) -%>
|
|
151
|
+
<% } -%>
|
|
152
|
+
<% if (subSection.tableColumns && Object.keys(subSection.tableColumns).length > 0 && subSection.navigationProperty) { -%>
|
|
153
|
+
Then.onThe<%- name%>.onTable({ property: "<%- subSection.navigationProperty %>" }).iCheckColumns(<%- JSON.stringify(subSection.tableColumns) %>);
|
|
154
|
+
<% } -%>
|
|
155
|
+
<% }) -%>
|
|
156
|
+
<% } else { -%>
|
|
157
|
+
<% if (section.fields && section.fields.length > 0) { -%>
|
|
158
|
+
<% section.fields.forEach(function(field) { -%>
|
|
159
|
+
Then.onThe<%- name%>.onForm({ section: "<%- section.id %>" } as unknown as FormIdentifier).iCheckField({ property: "<%- field.property %>" });
|
|
160
|
+
<% }) -%>
|
|
161
|
+
<% } -%>
|
|
162
|
+
<% if (section.tableColumns && Object.keys(section.tableColumns).length > 0 && section.navigationProperty) { -%>
|
|
163
|
+
Then.onThe<%- name%>.onTable({ property: "<%- section.navigationProperty %>" }).iCheckColumns(<%- JSON.stringify(section.tableColumns) %>);
|
|
164
|
+
<% } -%>
|
|
165
|
+
<% } -%>
|
|
166
|
+
<% }) -%>
|
|
167
|
+
});
|
|
168
|
+
<% } -%>
|
|
169
|
+
|
|
170
|
+
opaTest("Teardown", function (Given: Given) {
|
|
171
|
+
// Cleanup
|
|
172
|
+
Given.iTearDownMyApp();
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
runner.run([journey]);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import JourneyRunner from "sap/fe/test/JourneyRunner";
|
|
2
|
+
<% if (pages.some((p) => p.template === 'ListReport')) { -%>
|
|
3
|
+
import ListReport from "sap/fe/test/ListReport";
|
|
4
|
+
<% } -%>
|
|
5
|
+
<% if (pages.some((p) => p.template === 'ObjectPage')) { -%>
|
|
6
|
+
import ObjectPage from "sap/fe/test/ObjectPage";
|
|
7
|
+
<% } -%>
|
|
8
|
+
<%- pages.map((page) => 'import Custom' + page.targetKey + ' from "./' + page.targetKey + '";').join('\n') %>
|
|
9
|
+
|
|
10
|
+
const runner = new JourneyRunner({
|
|
11
|
+
launchUrl: sap.ui.require.toUrl("<%- appPath %>") + "/<%- htmlTarget %>",
|
|
12
|
+
pages: {
|
|
13
|
+
<%- pages.map((page) =>
|
|
14
|
+
' onThe' + page.targetKey + ': new ' + page.template + '(\n' +
|
|
15
|
+
' {\n' +
|
|
16
|
+
' appId: "' + page.appID + '",\n' +
|
|
17
|
+
' componentId: "' + page.componentID + '",\n' +
|
|
18
|
+
' entitySet: "' + (page.entitySet || '') + '",\n' +
|
|
19
|
+
' contextPath: "' + (page.contextPath || '') + '"\n' +
|
|
20
|
+
' },\n' +
|
|
21
|
+
' Custom' + page.targetKey + '\n' +
|
|
22
|
+
' )'
|
|
23
|
+
).join(',\n') %>
|
|
24
|
+
},
|
|
25
|
+
async: true
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export default runner;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type Opa5 from "sap/ui/test/Opa5";
|
|
2
|
+
import Press from "sap/ui/test/actions/Press";
|
|
3
|
+
|
|
4
|
+
export const actions = {
|
|
5
|
+
iPressSectionIconTabFilterButton(this: Opa5, section: string) {
|
|
6
|
+
return this.waitFor({
|
|
7
|
+
id: new RegExp(`.*--fe::FacetSection::${section}-anchor$`),
|
|
8
|
+
actions: new Press()
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const assertions = {};
|
|
14
|
+
|
|
15
|
+
export default class ObjectPage {
|
|
16
|
+
actions = actions;
|
|
17
|
+
assertions = assertions;
|
|
18
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type Opa5 from "sap/ui/test/Opa5";
|
|
2
|
+
<% if (pages.some(p => p.template === 'ListReport')) { -%>
|
|
3
|
+
import type { actions as ListReportActions, assertions as ListReportAssertions } from "sap/fe/test/ListReport";
|
|
4
|
+
<% } -%>
|
|
5
|
+
<% if (pages.some(p => p.template === 'ObjectPage')) { -%>
|
|
6
|
+
import type { actions as ObjectPageActions, assertions as ObjectPageAssertions } from "sap/fe/test/ObjectPage";
|
|
7
|
+
<% } -%>
|
|
8
|
+
<% if (pages.some(p => p.template === 'ListReport' || p.template === 'ObjectPage')) { -%>
|
|
9
|
+
import type { actions as TemplatePageActions, assertions as TemplatePageAssertions } from "sap/fe/test/TemplatePage";
|
|
10
|
+
<% } -%>
|
|
11
|
+
import type Shell from "sap/fe/test/Shell";
|
|
12
|
+
import type BaseArrangements from "sap/fe/test/BaseArrangements";
|
|
13
|
+
<% pages.filter((p) => p.template === 'ListReport' || p.template === 'ObjectPage').forEach(function(page) { -%>
|
|
14
|
+
import type { actions as <%- page.targetKey %>CustomActions, assertions as <%- page.targetKey %>CustomAssertions } from "../pages/<%- page.targetKey %>";
|
|
15
|
+
<% }); -%>
|
|
16
|
+
|
|
17
|
+
export type Given = Opa5 & BaseArrangements & {
|
|
18
|
+
iTearDownMyApp: () => Given;
|
|
19
|
+
iStartMyApp: (sAppHash?: string, mInUrlParameters?: object) => Given;
|
|
20
|
+
and: Given;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type When = Opa5 & BaseArrangements & {
|
|
24
|
+
<% pages.forEach(function(page) { -%>
|
|
25
|
+
<% if (page.template === 'ListReport') { -%>
|
|
26
|
+
onThe<%- page.targetKey %>: Opa5 & ListReportActions & TemplatePageActions & typeof <%- page.targetKey %>CustomActions;
|
|
27
|
+
<% } else if (page.template === 'ObjectPage') { -%>
|
|
28
|
+
onThe<%- page.targetKey %>: Opa5 & ObjectPageActions & TemplatePageActions & typeof <%- page.targetKey %>CustomActions;
|
|
29
|
+
<% } -%>
|
|
30
|
+
<% }); -%>
|
|
31
|
+
onTheShell: Shell;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type Then = Opa5 & BaseArrangements & {
|
|
35
|
+
<% pages.forEach(function(page) { -%>
|
|
36
|
+
<% if (page.template === 'ListReport') { -%>
|
|
37
|
+
onThe<%- page.targetKey %>: Opa5 & ListReportAssertions & TemplatePageAssertions & typeof <%- page.targetKey %>CustomAssertions;
|
|
38
|
+
<% } else if (page.template === 'ObjectPage') { -%>
|
|
39
|
+
onThe<%- page.targetKey %>: Opa5 & ObjectPageAssertions & TemplatePageAssertions & typeof <%- page.targetKey %>CustomAssertions;
|
|
40
|
+
<% } -%>
|
|
41
|
+
<% }); -%>
|
|
42
|
+
onTheShell: Shell;
|
|
43
|
+
};
|