@sap-ux/ui5-test-writer 1.1.13 → 1.2.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.
Files changed (29) hide show
  1. package/dist/fiori-elements-opa-writer.js +360 -143
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/translations/ui5-test-writer.i18n.json +10 -0
  5. package/dist/types.d.ts +10 -0
  6. package/dist/utils/fileWritingUtils.d.ts +55 -0
  7. package/dist/utils/fileWritingUtils.js +103 -0
  8. package/dist/utils/journeyRunnerUtils.d.ts +64 -0
  9. package/dist/utils/journeyRunnerUtils.js +251 -0
  10. package/dist/utils/opaJourneyTypesUtils.d.ts +27 -0
  11. package/dist/utils/opaJourneyTypesUtils.js +167 -0
  12. package/dist/utils/opaQUnitUtils.d.ts +6 -92
  13. package/dist/utils/opaQUnitUtils.js +20 -374
  14. package/dist/utils/virtualOpaUtils.d.ts +21 -0
  15. package/dist/utils/virtualOpaUtils.js +51 -0
  16. package/package.json +2 -1
  17. package/templates/v4/integration/FPMJourney.js +3 -3
  18. package/templates/v4/integration/FirstJourney.ts +5 -5
  19. package/templates/v4/integration/ListReportJourney.js +25 -25
  20. package/templates/v4/integration/ListReportJourney.ts +25 -25
  21. package/templates/v4/integration/ObjectPageJourney.js +37 -37
  22. package/templates/v4/integration/ObjectPageJourney.ts +38 -38
  23. package/templates/v4/integration/opaTests.qunit.js +5 -2
  24. package/templates/v4/integration/pages/FPM.js +17 -0
  25. package/templates/v4/integration/pages/JourneyRunner.js +6 -6
  26. package/templates/v4/integration/pages/JourneyRunner.ts +21 -12
  27. package/templates/v4/integration/pages/ListReport.js +17 -0
  28. package/templates/v4/integration/pages/ObjectPage.js +17 -0
  29. package/templates/v4/integration/types/OpaJourneyTypes.d.ts +5 -5
@@ -7,7 +7,10 @@ import { SupportedPageTypes, ValidationError, DotFileExtension } from './types.j
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';
10
- import { addIntegrationOldToGitignore, addPathsToQUnitJs, addPagesToJourneyRunner, hasVirtualOPA5, readHtmlTargetFromQUnitJs, addVirtualTestConfig } from './utils/opaQUnitUtils.js';
10
+ import { addPathsToQUnitJs, readHtmlTargetFromQUnitJs } from './utils/opaQUnitUtils.js';
11
+ import { addPagesToJourneyRunner } from './utils/journeyRunnerUtils.js';
12
+ import { hasVirtualOPA5, addVirtualTestConfig } from './utils/virtualOpaUtils.js';
13
+ import { addJourneysToOpaJourneyTypes } from './utils/opaJourneyTypesUtils.js';
11
14
  import { getPackageScripts } from '@sap-ux/fiori-generator-shared';
12
15
  import { readHashFromFlpSandbox } from './utils/flpSandboxUtils.js';
13
16
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -51,77 +54,203 @@ export async function generateOPAFiles(basePath, options, metadata, fs, log, sta
51
54
  };
52
55
  const writeContext = {
53
56
  config,
57
+ basePath,
58
+ rootCommonTemplateDirPath,
54
59
  rootV4TemplateDirPath,
55
60
  testOutDirPath,
56
61
  editor,
62
+ log,
57
63
  journeyParams,
58
- dotFileExtension
64
+ dotFileExtension,
65
+ modifiedFiles: []
59
66
  };
60
- // The active context is the one used to actually emit files. In standalone mode without an
61
- // existing JourneyRunner it is replaced with the resolved standalone context (which may
62
- // override fields like `htmlTarget`); otherwise it stays as the original `writeContext`.
63
- let activeContext = writeContext;
64
67
  if (standalone) {
65
- const hasJourneyRunner = existsSync(join(testOutDirPath, 'integration', 'pages', `JourneyRunner${dotFileExtension}`));
66
- const virtualOPA5Configured = await hasVirtualOPA5(basePath);
67
- if (hasJourneyRunner) {
68
- writeJourneyFiles(appFeatures, writeContext, true, true, virtualOPA5Configured);
69
- }
70
- else {
71
- activeContext = await resolveStandaloneWriteContext(basePath, testOutDirPath, writeContext, editor);
72
- if (!virtualOPA5Configured) {
73
- writeCommonAndPageFiles(activeContext, rootCommonTemplateDirPath);
74
- }
75
- writeJourneyFiles(appFeatures, activeContext, true, hasJourneyRunner, virtualOPA5Configured);
76
- }
68
+ await generateOPAFilesForExistingApp(writeContext, appFeatures);
77
69
  }
78
70
  else {
79
- const useVirtualPreviewEndpoints = options.useVirtualPreviewEndpoints ?? false;
80
- writeCommonAndPageFiles(writeContext, rootCommonTemplateDirPath, useVirtualPreviewEndpoints);
81
- writeJourneyFiles(appFeatures, writeContext, false, false, useVirtualPreviewEndpoints);
82
- if (useVirtualPreviewEndpoints) {
83
- await addVirtualTestConfig(basePath, [{ framework: 'OPA5', path: '/test/integration/opaTests.qunit.html' }, { framework: 'Testsuite' }], editor);
84
- }
85
- }
86
- if (enableTypeScript) {
87
- writeOpaJourneyTypes(activeContext);
71
+ await generateOPAFilesForNewApp(writeContext, appFeatures);
88
72
  }
89
73
  return editor;
90
74
  }
91
75
  /**
92
- * Resolves the write context for standalone mode when no JourneyRunner.js exists yet.
93
- * Moves any existing integration folder to integration_old, or adds the int-test script
94
- * and resolves the htmlTarget from flpSandbox.html if present.
76
+ * Generate OPA test files for an existing Fiori elements for OData V4 application.
95
77
  *
96
- * @param basePath - the absolute target path of the application
97
- * @param testOutDirPath - output test directory (.../webapp/test)
98
- * @param writeContext - shared write context to base the resolved context on
99
- * @param editor - a reference to a mem-fs editor
100
- * @returns a new WriteContext with the resolved htmlTarget
78
+ * @param writeContext - shared write context (config, paths, editor, journey params)
79
+ * @param appFeatures - object containing feature data for list report, object pages, and FPM, used for generating the journey files
101
80
  */
102
- async function resolveStandaloneWriteContext(basePath, testOutDirPath, writeContext, editor) {
103
- const { config } = writeContext;
104
- let htmlTarget = readHtmlTargetFromQUnitJs(testOutDirPath, editor) ?? config.htmlTarget;
105
- if (existsSync(join(testOutDirPath, 'integration'))) {
106
- editor.move(join(testOutDirPath, 'integration', '**'), join(testOutDirPath, 'integration_old'));
107
- await addIntegrationOldToGitignore(basePath, editor);
81
+ async function generateOPAFilesForExistingApp(writeContext, appFeatures) {
82
+ const virtualOPA5Configured = await hasVirtualOPA5(writeContext.basePath);
83
+ const standaloneWriteContext = await resolveStandaloneWriteContext(writeContext);
84
+ const generatedPages = writePageFiles(standaloneWriteContext);
85
+ const generatedJourneys = writeJourneyFiles(appFeatures, standaloneWriteContext);
86
+ handleJourneyRunner(standaloneWriteContext, generatedPages);
87
+ if (virtualOPA5Configured) {
88
+ await addVirtualOpa5Config(standaloneWriteContext);
108
89
  }
109
90
  else {
110
- const hasIntTestScript = checkScriptInPackageJson(editor, basePath, 'int-test');
111
- if (!hasIntTestScript) {
112
- const script = getPackageScripts({ localOnly: false, addTest: true })['int-test'];
113
- if (script) {
114
- await updatePackageScript(basePath, 'int-test', script, editor);
91
+ handleOpaTestsStartupFiles(standaloneWriteContext, generatedJourneys);
92
+ }
93
+ if (standaloneWriteContext.dotFileExtension === DotFileExtension.TS) {
94
+ handleOPAJourneyTypes(standaloneWriteContext, generatedPages);
95
+ }
96
+ logGeneratedAndModifiedFiles(standaloneWriteContext, generatedJourneys, generatedPages);
97
+ }
98
+ /**
99
+ * Generate OPA test files for a newly generated Fiori elements for OData V4 application.
100
+ *
101
+ * @param writeContext - shared write context (config, paths, editor, journey params)
102
+ * @param appFeatures - object containing feature data for list report, object pages, and FPM, used for generating the journey files
103
+ */
104
+ async function generateOPAFilesForNewApp(writeContext, appFeatures) {
105
+ const generatedPages = writePageFiles(writeContext);
106
+ writeJourneyRunner(writeContext);
107
+ const generatedJourneys = writeJourneyFiles(appFeatures, writeContext);
108
+ if (writeContext.config.useVirtualPreviewEndpoints) {
109
+ await addVirtualOpa5Config(writeContext);
110
+ }
111
+ else {
112
+ handleOpaTestsStartupFiles(writeContext, generatedJourneys);
113
+ }
114
+ if (writeContext.dotFileExtension === DotFileExtension.TS) {
115
+ writeOpaJourneyTypes(writeContext);
116
+ }
117
+ logGeneratedAndModifiedFiles(writeContext, generatedJourneys, generatedPages);
118
+ }
119
+ /**
120
+ * Log a summary of all files the generator produced or updated, grouped into journeys, pages,
121
+ * and other modified files (JourneyRunner, qunit harness, type definitions, etc.).
122
+ *
123
+ * @param writeContext - shared write context (provides logger, file extension, and modifiedFiles accumulator)
124
+ * @param generatedJourneys - feature names for which a `<name>Journey.gen.<ext>` file was written
125
+ * @param generatedPages - page objects written under `pages/`
126
+ */
127
+ function logGeneratedAndModifiedFiles(writeContext, generatedJourneys, generatedPages) {
128
+ const { log, dotFileExtension, modifiedFiles, incompatibleTestSetup } = writeContext;
129
+ if (!log?.info) {
130
+ return;
131
+ }
132
+ logGeneratedJourneyFiles(log, generatedJourneys, dotFileExtension);
133
+ logGeneratedPageFiles(log, generatedPages, dotFileExtension);
134
+ logModifiedFiles(log, modifiedFiles);
135
+ // In an incompatible existing setup, the qunit/testsuite files are not touched, so the
136
+ // generated `.gen` Journeys/Pages must be wired in by the app owner.
137
+ if (incompatibleTestSetup && (generatedJourneys.length > 0 || generatedPages.length > 0)) {
138
+ log.info(t('info.manualIntegrationRequired'));
139
+ }
140
+ }
141
+ /**
142
+ * Log the generated journey files (one per line) under a "Generated Journeys:" heading.
143
+ *
144
+ * @param log - logger instance to use for logging
145
+ * @param generatedJourneys - feature names for which a `<name>Journey.gen.<ext>` file was written
146
+ * @param dotFileExtension - the journey file extension (`.js` or `.ts`)
147
+ */
148
+ function logGeneratedJourneyFiles(log, generatedJourneys, dotFileExtension) {
149
+ if (generatedJourneys.length === 0) {
150
+ return;
151
+ }
152
+ const lines = generatedJourneys.map((journey) => ` ${journey}Journey.gen${dotFileExtension}`);
153
+ log.info?.(['Generated Journeys:', ...lines].join('\n'));
154
+ }
155
+ /**
156
+ * Log the generated page files (one per line) under a "Generated Pages:" heading.
157
+ *
158
+ * @param log - logger instance to use for logging
159
+ * @param generatedPages - page objects written under `pages/`
160
+ * @param dotFileExtension - the page file extension (`.js` or `.ts`)
161
+ */
162
+ function logGeneratedPageFiles(log, generatedPages, dotFileExtension) {
163
+ if (generatedPages.length === 0) {
164
+ return;
165
+ }
166
+ const lines = generatedPages.map((page) => ` pages/${page.fileName}${dotFileExtension}`);
167
+ log.info?.(['Generated Pages:', ...lines].join('\n'));
168
+ }
169
+ /**
170
+ * Log the additional files updated during generation (one per line) under a "Modified files:" heading.
171
+ *
172
+ * @param log - logger instance to use for logging
173
+ * @param modifiedFiles - paths (relative to the test output dir) of files that were written or spliced
174
+ */
175
+ function logModifiedFiles(log, modifiedFiles) {
176
+ if (modifiedFiles.length === 0) {
177
+ return;
178
+ }
179
+ const lines = modifiedFiles.map((file) => ` ${file}`);
180
+ log.info?.(['Modified files:', ...lines].join('\n'));
181
+ }
182
+ /**
183
+ * Resolve the standalone-mode write context: detect preexisting integration tests and an
184
+ * incompatible legacy setup, and resolve the htmlTarget from flpSandbox.html if present.
185
+ *
186
+ * @param writeContext - shared write context to base the resolved context on
187
+ * @returns a new WriteContext with the resolved values for the standalone generation
188
+ */
189
+ async function resolveStandaloneWriteContext(writeContext) {
190
+ const hasPreexistingTests = existsSync(join(writeContext.testOutDirPath, 'integration'));
191
+ if (hasPreexistingTests) {
192
+ return {
193
+ ...writeContext,
194
+ hasPreexistingTests: true,
195
+ incompatibleTestSetup: hasIncompatibleTestSetup(writeContext.testOutDirPath),
196
+ config: {
197
+ ...writeContext.config,
198
+ htmlTarget: readHtmlTargetFromQUnitJs(writeContext.testOutDirPath, writeContext.editor) ??
199
+ writeContext.config.htmlTarget
115
200
  }
201
+ };
202
+ }
203
+ // app has no integration tests yet
204
+ return await resolveWriteContextForMissingIntegrationFolder(writeContext);
205
+ }
206
+ /**
207
+ * Resolve the standalone-mode write context for the case where the integration folder is
208
+ * missing entirely: there are no OPA tests yet, the int-test package script may need to be
209
+ * added, and the htmlTarget is resolved from flpSandbox.html if present.
210
+ *
211
+ * @param writeContext - shared write context to base the resolved context on
212
+ * @returns a new WriteContext with the resolved values for the standalone generation
213
+ */
214
+ async function resolveWriteContextForMissingIntegrationFolder(writeContext) {
215
+ const hasIntTestScript = checkScriptInPackageJson(writeContext.editor, writeContext.basePath, 'int-test');
216
+ if (!hasIntTestScript) {
217
+ const script = getPackageScripts({ localOnly: false, addTest: true })['int-test'];
218
+ if (script) {
219
+ await updatePackageScript(writeContext.basePath, 'int-test', script, writeContext.editor);
220
+ writeContext.modifiedFiles.push('package.json');
116
221
  }
117
- if (existsSync(join(testOutDirPath, 'flpSandbox.html'))) {
118
- const hashFromFlpSandbox = readHashFromFlpSandbox(join('test', 'flpSandbox.html'), await getWebappPath(basePath), editor);
119
- if (hashFromFlpSandbox) {
120
- htmlTarget = `test/flpSandbox.html#${hashFromFlpSandbox}`;
121
- }
222
+ }
223
+ let htmlTarget = writeContext.config.htmlTarget;
224
+ if (existsSync(join(writeContext.testOutDirPath, 'flpSandbox.html'))) {
225
+ const hashFromFlpSandbox = readHashFromFlpSandbox(join('test', 'flpSandbox.html'), await getWebappPath(writeContext.basePath), writeContext.editor);
226
+ if (hashFromFlpSandbox) {
227
+ htmlTarget = `test/flpSandbox.html#${hashFromFlpSandbox}`;
122
228
  }
123
229
  }
124
- return { ...writeContext, config: { ...config, htmlTarget } };
230
+ return {
231
+ ...writeContext,
232
+ hasPreexistingTests: false,
233
+ config: { ...writeContext.config, htmlTarget }
234
+ };
235
+ }
236
+ /**
237
+ * Checks for incompatible test setup — the integration folder exists but no own JourneyRunner.js / .ts file is present.
238
+ *
239
+ * @param testOutDirPath - path to the test output directory (`.../webapp/test`)
240
+ * @returns true if the test setup is incompatible, false otherwise
241
+ */
242
+ function hasIncompatibleTestSetup(testOutDirPath) {
243
+ return !hasJourneyRunnerFile(testOutDirPath);
244
+ }
245
+ /**
246
+ * Checks whether the existing test setup contains its own JourneyRunner.js or JourneyRunner.ts file.
247
+ *
248
+ * @param testOutDirPath - path to the test output directory (`.../webapp/test`)
249
+ * @returns true if the JourneyRunner.js or JourneyRunner.ts file exists, false otherwise
250
+ */
251
+ function hasJourneyRunnerFile(testOutDirPath) {
252
+ const pagesDir = join(testOutDirPath, 'integration', 'pages');
253
+ return existsSync(join(pagesDir, 'JourneyRunner.js')) || existsSync(join(pagesDir, 'JourneyRunner.ts'));
125
254
  }
126
255
  /**
127
256
  * Checks whether a script with the given name exists in the package.json.
@@ -217,7 +346,8 @@ function createPageConfig(manifest, targetKey, forcedAppID) {
217
346
  targetKey,
218
347
  componentID: target.id,
219
348
  template: SupportedPageTypes[target.name],
220
- isStartup: false
349
+ isStartup: false,
350
+ fileName: targetKey + '.gen'
221
351
  };
222
352
  if (target.options.settings.contextPath) {
223
353
  pageConfig.contextPath = target.options.settings.contextPath;
@@ -248,7 +378,8 @@ function createConfig(manifest, options, hideFilterBar) {
248
378
  pages: [],
249
379
  opaJourneyFileName: options.scriptName ?? 'FirstJourney',
250
380
  htmlTarget: options.htmlTarget ?? 'index.html',
251
- hideFilterBar
381
+ hideFilterBar,
382
+ useVirtualPreviewEndpoints: options.useVirtualPreviewEndpoints ?? false
252
383
  };
253
384
  // Identify startup targets from the routes
254
385
  const appRoutes = (manifest['sap.ui5']?.routing?.routes ?? []);
@@ -319,32 +450,43 @@ function findLROP(pages, manifest) {
319
450
  return { pageLR, pageOP };
320
451
  }
321
452
  /**
322
- * Writes common test files, page objects, and the first journey file.
453
+ * Writes the common test files for the Fiori elements app.
323
454
  *
324
455
  * @param writeContext - shared write context (config, paths, editor, journey params)
325
- * @param rootCommonTemplateDirPath - template root directory for common files
326
- * @param useVirtualPreviewEndpoints - when true, testsuite harness files are served virtually; skip writing them to disk
327
456
  */
328
- function writeCommonAndPageFiles(writeContext, rootCommonTemplateDirPath, useVirtualPreviewEndpoints = false) {
329
- const { config, rootV4TemplateDirPath, testOutDirPath, editor, journeyParams, dotFileExtension } = writeContext;
457
+ function writeTestsuiteFiles(writeContext) {
330
458
  // Common test files (testsuite served virtually when useVirtualPreviewEndpoints is enabled)
331
- if (!useVirtualPreviewEndpoints) {
332
- editor.copyTpl(join(rootCommonTemplateDirPath), testOutDirPath,
333
- // unit tests are not added for Fiori elements app
334
- { appId: config.appID }, undefined, {
335
- globOptions: { dot: true }
336
- });
337
- }
338
- config.pages.forEach((page) => {
339
- writePageObject(page, rootV4TemplateDirPath, testOutDirPath, editor, dotFileExtension);
340
- });
341
- editor.copyTpl(join(rootV4TemplateDirPath, 'integration', `FirstJourney${dotFileExtension}`), join(testOutDirPath, 'integration', `${config.opaJourneyFileName}${dotFileExtension}`), { ...journeyParams, appPath: config.appPath }, undefined, {
459
+ writeContext.editor.copyTpl(join(writeContext.rootCommonTemplateDirPath), writeContext.testOutDirPath,
460
+ // unit tests are not added for Fiori elements app
461
+ { appId: writeContext.config.appID }, undefined, {
342
462
  globOptions: { dot: true }
343
463
  });
344
- // Journey Runner
464
+ }
465
+ /**
466
+ * Writes common test files, page objects, and the first journey file.
467
+ *
468
+ * @param writeContext - shared write context (config, paths, editor, journey params)
469
+ * @returns an array of the written page objects with their targetKey and appPath
470
+ */
471
+ function writePageFiles(writeContext) {
472
+ const { config, rootV4TemplateDirPath, testOutDirPath, editor } = writeContext;
473
+ const writtenPages = [];
474
+ config.pages.forEach((page) => {
475
+ writtenPages.push(writePageObject(page, rootV4TemplateDirPath, testOutDirPath, editor, writeContext.dotFileExtension));
476
+ });
477
+ return writtenPages;
478
+ }
479
+ /**
480
+ * Writes the JourneyRunner file.
481
+ *
482
+ * @param writeContext - shared write context (config, paths, editor, journey params)
483
+ */
484
+ function writeJourneyRunner(writeContext) {
485
+ const { config, rootV4TemplateDirPath, testOutDirPath, editor, dotFileExtension, modifiedFiles } = writeContext;
345
486
  editor.copyTpl(join(rootV4TemplateDirPath, 'integration', 'pages', `JourneyRunner${dotFileExtension}`), join(testOutDirPath, 'integration', 'pages', `JourneyRunner${dotFileExtension}`), config, undefined, {
346
487
  globOptions: { dot: true }
347
488
  });
489
+ modifiedFiles.push(`integration/pages/JourneyRunner${dotFileExtension}`);
348
490
  }
349
491
  /**
350
492
  * Writes the OpaJourneyTypes.d.ts type definition file used by generated TypeScript OPA tests.
@@ -352,89 +494,43 @@ function writeCommonAndPageFiles(writeContext, rootCommonTemplateDirPath, useVir
352
494
  * @param writeContext - shared write context (config, paths, editor, journey params)
353
495
  */
354
496
  function writeOpaJourneyTypes(writeContext) {
355
- const { config, rootV4TemplateDirPath, testOutDirPath, editor } = writeContext;
497
+ const { config, rootV4TemplateDirPath, testOutDirPath, editor, modifiedFiles } = writeContext;
356
498
  editor.copyTpl(join(rootV4TemplateDirPath, 'integration', 'types', 'OpaJourneyTypes.d.ts'), join(testOutDirPath, 'integration', 'types', 'OpaJourneyTypes.d.ts'), config, undefined, {
357
499
  globOptions: { dot: true }
358
500
  });
359
- }
360
- /**
361
- * Checks whether a page object file already exists for the given feature name.
362
- * Both `.ts` and `.js` extensions are checked to avoid creating duplicate page objects
363
- * when regenerating in a different language than the existing tests use.
364
- * If neither exists, finds the matching page config and writes the file.
365
- *
366
- * @param featureName - the feature/page name (equals the manifest targetKey)
367
- * @param config - the OPA config containing all page configurations
368
- * @param rootV4TemplateDirPath - template root directory for v4 templates
369
- * @param testOutDirPath - output test directory (.../webapp/test)
370
- * @param editor - a reference to a mem-fs editor
371
- * @param dotFileExtension - file extension ('.ts' or '.js')
372
- * @returns JourneyRunnerPage if the page was newly created, undefined otherwise
373
- */
374
- function ensurePageExists(featureName, config, rootV4TemplateDirPath, testOutDirPath, editor, dotFileExtension) {
375
- const pagesDir = join(testOutDirPath, 'integration', 'pages');
376
- if (editor.exists(join(pagesDir, `${featureName}${DotFileExtension.TS}`)) ||
377
- editor.exists(join(pagesDir, `${featureName}${DotFileExtension.JS}`))) {
378
- return undefined;
379
- }
380
- const pageConfig = config.pages.find((p) => p.targetKey === featureName);
381
- if (pageConfig) {
382
- writePageObject(pageConfig, rootV4TemplateDirPath, testOutDirPath, editor, dotFileExtension);
383
- return {
384
- targetKey: featureName,
385
- appPath: config.appPath,
386
- template: pageConfig.template,
387
- appID: config.appID,
388
- componentID: pageConfig.componentID,
389
- entitySet: pageConfig.entitySet,
390
- contextPath: pageConfig.contextPath
391
- };
392
- }
393
- return undefined;
501
+ modifiedFiles.push('integration/types/OpaJourneyTypes.d.ts');
394
502
  }
395
503
  /**
396
504
  * Writes journey files for list report, object pages and FPM pages.
397
505
  *
398
506
  * @param appFeatures - object containing feature data for list report, object pages, and FPM
399
507
  * @param writeContext - shared write context (config, paths, editor, journey params)
400
- * @param isStandalone - whether the generation is run in standalone mode (not during app generation)
401
- * @param hasJourneyRunner - whether a JourneyRunner already exists (standalone upgrade path)
402
- * @param virtualOPA5Configured - whether virtual OPA5 is configured
508
+ * @returns an array of feature names for which journey files were generated
403
509
  */
404
- function writeJourneyFiles(appFeatures, writeContext, isStandalone, hasJourneyRunner = false, virtualOPA5Configured = false) {
510
+ function writeJourneyFiles(appFeatures, writeContext) {
405
511
  const { config, rootV4TemplateDirPath, testOutDirPath, editor, journeyParams, dotFileExtension } = writeContext;
406
- const generatedJourneyPages = [];
407
- const newPages = [];
512
+ const generatedJourneys = [];
408
513
  if (appFeatures.listReport?.name) {
409
- editor.copyTpl(join(rootV4TemplateDirPath, 'integration', `ListReportJourney${dotFileExtension}`), join(testOutDirPath, 'integration', `${appFeatures.listReport.name}Journey${dotFileExtension}`), {
514
+ editor.copyTpl(join(rootV4TemplateDirPath, 'integration', `ListReportJourney${dotFileExtension}`), join(testOutDirPath, 'integration', `${appFeatures.listReport.name}Journey.gen${dotFileExtension}`), {
410
515
  ...journeyParams,
411
516
  ...appFeatures.listReport,
412
517
  appPath: config.appPath
413
518
  }, undefined, {
414
519
  globOptions: { dot: true }
415
520
  });
416
- generatedJourneyPages.push(appFeatures.listReport.name);
417
- const lrPage = ensurePageExists(appFeatures.listReport.name, config, rootV4TemplateDirPath, testOutDirPath, editor, dotFileExtension);
418
- if (lrPage) {
419
- newPages.push(lrPage);
420
- }
521
+ generatedJourneys.push(appFeatures.listReport.name);
421
522
  }
422
523
  if (appFeatures.objectPages && appFeatures.objectPages.length > 0) {
423
524
  appFeatures.objectPages.forEach((objectPage) => {
424
525
  if (objectPage.name) {
425
- editor.copyTpl(join(rootV4TemplateDirPath, 'integration', `ObjectPageJourney${dotFileExtension}`), join(testOutDirPath, 'integration', `${objectPage.name}Journey${dotFileExtension}`), {
526
+ editor.copyTpl(join(rootV4TemplateDirPath, 'integration', `ObjectPageJourney${dotFileExtension}`), join(testOutDirPath, 'integration', `${objectPage.name}Journey.gen${dotFileExtension}`), {
426
527
  ...journeyParams,
427
528
  ...objectPage,
428
- isStandalone,
429
529
  appPath: config.appPath
430
530
  }, undefined, {
431
531
  globOptions: { dot: true }
432
532
  });
433
- generatedJourneyPages.push(objectPage.name);
434
- const opPage = ensurePageExists(objectPage.name, config, rootV4TemplateDirPath, testOutDirPath, editor, dotFileExtension);
435
- if (opPage) {
436
- newPages.push(opPage);
437
- }
533
+ generatedJourneys.push(objectPage.name);
438
534
  }
439
535
  });
440
536
  }
@@ -446,33 +542,142 @@ function writeJourneyFiles(appFeatures, writeContext, isStandalone, hasJourneyRu
446
542
  // an LR-OP-FPM mix with `enableTypeScript` would crash in `writePageObject`
447
543
  // when trying to load the missing `FPM.ts` template.
448
544
  // Future work: add FPM.ts/FPMJourney.ts templates and switch to `dotFileExtension`.
449
- editor.copyTpl(join(rootV4TemplateDirPath, 'integration', 'FPMJourney.js'), join(testOutDirPath, 'integration', `${appFeatures.fpm.name}Journey.js`), {
545
+ editor.copyTpl(join(rootV4TemplateDirPath, 'integration', 'FPMJourney.js'), join(testOutDirPath, 'integration', `${appFeatures.fpm.name}Journey.gen.js`), {
450
546
  ...journeyParams,
451
547
  ...appFeatures.fpm
452
548
  }, undefined, {
453
549
  globOptions: { dot: true }
454
550
  });
455
- generatedJourneyPages.push(appFeatures.fpm.name);
456
- const fpmPage = ensurePageExists(appFeatures.fpm.name, config, rootV4TemplateDirPath, testOutDirPath, editor, DotFileExtension.JS);
457
- if (fpmPage) {
458
- newPages.push(fpmPage);
551
+ generatedJourneys.push(appFeatures.fpm.name);
552
+ }
553
+ if (generatedJourneys.length === 0 && !writeContext.hasPreexistingTests) {
554
+ writeFallbackJourney(writeContext);
555
+ }
556
+ return generatedJourneys;
557
+ }
558
+ /**
559
+ * Writes the fallback `FirstJourney.js` starter file when no dynamically generated journeys
560
+ * were produced.
561
+ *
562
+ * @param writeContext - shared write context (config, paths, editor, journey params)
563
+ */
564
+ function writeFallbackJourney(writeContext) {
565
+ const { config, rootV4TemplateDirPath, testOutDirPath, editor, journeyParams, dotFileExtension } = writeContext;
566
+ editor.copyTpl(join(rootV4TemplateDirPath, 'integration', `FirstJourney${dotFileExtension}`), join(testOutDirPath, 'integration', `${config.opaJourneyFileName}${dotFileExtension}`), journeyParams, undefined, {
567
+ globOptions: { dot: true }
568
+ });
569
+ }
570
+ /**
571
+ * Update the `OpaJourneyTypes.d.ts` type-definition file used by generated TypeScript OPA tests.
572
+ *
573
+ * @param writeContext - shared write context (config, paths, editor, journey params)
574
+ * @param generatedPages - pages whose journeys should be reflected in the type definitions
575
+ */
576
+ function handleOPAJourneyTypes(writeContext, generatedPages) {
577
+ if (writeContext.incompatibleTestSetup) {
578
+ writeContext.log?.info(t('info.opaJourneyTypesNotUpdated'));
579
+ return;
580
+ }
581
+ if (writeContext.hasPreexistingTests) {
582
+ const written = addJourneysToOpaJourneyTypes(generatedPages, writeContext.testOutDirPath, writeContext.editor, writeContext.log);
583
+ if (written) {
584
+ writeContext.modifiedFiles.push('integration/types/OpaJourneyTypes.d.ts');
459
585
  }
460
586
  }
461
- if (newPages.length > 0) {
462
- addPagesToJourneyRunner(newPages, testOutDirPath, editor, dotFileExtension);
587
+ else {
588
+ writeOpaJourneyTypes(writeContext);
589
+ }
590
+ }
591
+ /**
592
+ * Handles JourneyRunner File.
593
+ * If there is already a JourneyRunner file, it is updated with the generated pages, otherwise a new JourneyRunner file is written.
594
+ *
595
+ * @param writeContext - shared write context (config, paths, editor, journey params)
596
+ * @param generatedPages - an array of page objects that were generated, each containing a targetKey and appPath
597
+ */
598
+ function handleJourneyRunner(writeContext, generatedPages) {
599
+ if (writeContext.incompatibleTestSetup) {
600
+ return;
601
+ }
602
+ else if (writeContext.hasPreexistingTests) {
603
+ updatePagesInJourneyRunner(writeContext, generatedPages);
463
604
  }
464
- if (!virtualOPA5Configured) {
465
- if (hasJourneyRunner) {
466
- addPathsToQUnitJs(generatedJourneyPages.map((page) => {
467
- return `${config.appPath}/test/integration/${page}Journey`;
468
- }), testOutDirPath, editor);
605
+ else {
606
+ writeJourneyRunner(writeContext);
607
+ }
608
+ }
609
+ /**
610
+ * Ensures that there is a page object file for each generated journey and adds the pages to the JourneyRunner.js file if they were newly created.
611
+ *
612
+ * @param writeContext - shared write context (config, paths, editor, journey params)
613
+ * @param generatedPages - an array of page objects that were generated, each containing a targetKey and appPath
614
+ */
615
+ function updatePagesInJourneyRunner(writeContext, generatedPages) {
616
+ const { testOutDirPath, editor, log, dotFileExtension, modifiedFiles } = writeContext;
617
+ if (generatedPages.length > 0) {
618
+ const written = addPagesToJourneyRunner(generatedPages, testOutDirPath, editor, dotFileExtension, log);
619
+ if (written) {
620
+ modifiedFiles.push(`integration/pages/JourneyRunner${dotFileExtension}`);
469
621
  }
470
- else {
471
- editor.copyTpl(join(rootV4TemplateDirPath, 'integration', 'opaTests.*.*'), join(testOutDirPath, 'integration'), { ...config, generatedJourneyPages }, undefined, {
472
- globOptions: { dot: true }
473
- });
622
+ }
623
+ }
624
+ /**
625
+ * Configures `ui5-mock.yaml` to serve the OPA harness virtually so the qunit/testsuite files
626
+ * do not need to be written to disk. Used when virtual OPA5 is configured for the app.
627
+ *
628
+ * @param writeContext - shared write context (config, paths, editor, journey params)
629
+ */
630
+ async function addVirtualOpa5Config(writeContext) {
631
+ await addVirtualTestConfig(writeContext.basePath, [{ framework: 'OPA5', path: '/test/integration/opaTests.qunit.html' }, { framework: 'Testsuite' }], writeContext.editor);
632
+ writeContext.modifiedFiles.push('ui5-mock.yaml');
633
+ }
634
+ /**
635
+ * Handles the opaTests.qunit.html, opaTests.qunit.js, and testsuite.qunit.* files for non-virtual setups.
636
+ * For virtual OPA5 setups, call `addVirtualOpa5Config` instead.
637
+ *
638
+ * @param writeContext - shared write context (config, paths, editor, journey params)
639
+ * @param generatedJourneys - an array of feature names for which journey files were generated
640
+ */
641
+ function handleOpaTestsStartupFiles(writeContext, generatedJourneys = []) {
642
+ if (writeContext.incompatibleTestSetup) {
643
+ writeContext.log?.info(t('info.incompatibleTestSetupSkipped'));
644
+ }
645
+ else if (writeContext.hasPreexistingTests) {
646
+ // update existing opaTests.qunit.js only in a compatible setup, as long as virtual OPA5 is not used
647
+ const written = updateReferencesInOpaTestsStartupFiles(writeContext, generatedJourneys);
648
+ if (written) {
649
+ writeContext.modifiedFiles.push('integration/opaTests.qunit.js');
474
650
  }
475
651
  }
652
+ else {
653
+ // new app or missing integration folder: write all files
654
+ writeOpaTestsStartupFiles(writeContext, generatedJourneys);
655
+ writeTestsuiteFiles(writeContext);
656
+ writeContext.modifiedFiles.push('integration/opaTests.qunit.html', 'integration/opaTests.qunit.js', 'testsuite.qunit.html', 'testsuite.qunit.js');
657
+ }
658
+ }
659
+ /**
660
+ * Writes the opaTests.qunit.html and opaTests.qunit.js files to the test folder. The files are copied from the common templates, which are used for both new app generation and standalone generation in case of an incompatible existing test setup or when virtual OPA5 is used.
661
+ *
662
+ * @param writeContext - shared write context (config, paths, editor, journey params)
663
+ * @param generatedJourneys - an array of feature names for which journey files were generated, used for conditionally adding the test configuration for the virtual OPA5 setup
664
+ */
665
+ function writeOpaTestsStartupFiles(writeContext, generatedJourneys = []) {
666
+ writeContext.editor.copyTpl(join(writeContext.rootV4TemplateDirPath, 'integration', 'opaTests.*.*'), join(writeContext.testOutDirPath, 'integration'), { ...writeContext.config, generatedJourneys }, undefined, {
667
+ globOptions: { dot: true }
668
+ });
669
+ }
670
+ /**
671
+ * Updates the references in the opaTests.qunit.js file to the generated journey files and adds the test configuration for virtual OPA5 in case it's used.
672
+ *
673
+ * @param writeContext - shared write context (config, paths, editor, journey params)
674
+ * @param generatedJourneys - an array of feature names for which journey files were generated, used for conditionally adding the test configuration for the virtual OPA5 setup
675
+ * @returns true if the file was written, false otherwise
676
+ */
677
+ function updateReferencesInOpaTestsStartupFiles(writeContext, generatedJourneys = []) {
678
+ return addPathsToQUnitJs(generatedJourneys.map((page) => {
679
+ return `${writeContext.config.appPath}/test/integration/${page}Journey.gen`;
680
+ }), writeContext.testOutDirPath, writeContext.editor, writeContext.log);
476
681
  }
477
682
  /**
478
683
  * Writes a page object in a mem-fs-editor.
@@ -482,12 +687,24 @@ function writeJourneyFiles(appFeatures, writeContext, isStandalone, hasJourneyRu
482
687
  * @param testOutDirPath - output test directory (.../webapp/test)
483
688
  * @param fs - a reference to a mem-fs editor
484
689
  * @param dotFileExtension - file extension ('.ts' or '.js')
690
+ * @returns path to the written page object
485
691
  */
486
692
  function writePageObject(pageConfig, rootTemplateDirPath, testOutDirPath, fs, dotFileExtension) {
487
693
  // FPM has no .ts template; force .js regardless of the configured extension
488
694
  const ext = pageConfig.template === 'FPM' ? DotFileExtension.JS : dotFileExtension;
489
- fs.copyTpl(join(rootTemplateDirPath, 'integration', 'pages', `${pageConfig.template}${ext}`), join(testOutDirPath, 'integration', 'pages', `${pageConfig.targetKey}${ext}`), pageConfig, undefined, {
695
+ fs.copyTpl(join(rootTemplateDirPath, 'integration', 'pages', `${pageConfig.template}${ext}`), join(testOutDirPath, 'integration', 'pages', `${pageConfig.targetKey}.gen${ext}`), pageConfig, undefined, {
490
696
  globOptions: { dot: true }
491
697
  });
698
+ return {
699
+ targetKey: pageConfig.targetKey,
700
+ appPath: pageConfig.appPath,
701
+ fileName: `${pageConfig.targetKey}.gen`,
702
+ dotFileExtension: ext,
703
+ template: pageConfig.template,
704
+ appID: pageConfig.appID,
705
+ componentID: pageConfig.componentID,
706
+ entitySet: pageConfig.entitySet,
707
+ contextPath: pageConfig.contextPath
708
+ };
492
709
  }
493
710
  //# sourceMappingURL=fiori-elements-opa-writer.js.map
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { generateOPAFiles } from './fiori-elements-opa-writer.js';
2
2
  export { generateFreestyleOPAFiles } from './fiori-freestyle-opa-writer.js';
3
- export { addVirtualTestConfig } from './utils/opaQUnitUtils.js';
3
+ export { addVirtualTestConfig } from './utils/virtualOpaUtils.js';
4
4
  export type { OPAGenerationOptions } from './types.js';
5
5
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export { generateOPAFiles } from './fiori-elements-opa-writer.js';
2
2
  export { generateFreestyleOPAFiles } from './fiori-freestyle-opa-writer.js';
3
- export { addVirtualTestConfig } from './utils/opaQUnitUtils.js';
3
+ export { addVirtualTestConfig } from './utils/virtualOpaUtils.js';
4
4
  //# sourceMappingURL=index.js.map