@sap-ux/ui5-test-writer 1.2.18 → 1.2.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/types.d.ts +7 -2
- package/dist/utils/modelUtils.d.ts +10 -0
- package/dist/utils/modelUtils.js +21 -0
- package/dist/utils/objectPageUtils.js +100 -23
- package/package.json +1 -1
- package/templates/v4/integration/ObjectPageJourney.js +7 -7
- package/templates/v4/integration/ObjectPageJourney.ts +7 -7
package/dist/types.d.ts
CHANGED
|
@@ -101,13 +101,18 @@ export interface FFOPAConfig {
|
|
|
101
101
|
ui5Theme?: string;
|
|
102
102
|
useVirtualPreviewEndpoints?: boolean;
|
|
103
103
|
}
|
|
104
|
+
export type ObjectPageNavigationParent = {
|
|
105
|
+
name: string;
|
|
106
|
+
navigationProperty: string;
|
|
107
|
+
};
|
|
104
108
|
export type ObjectPageNavigationParents = {
|
|
105
109
|
parentLRName?: string;
|
|
106
|
-
|
|
107
|
-
parentOPTableSection?: string;
|
|
110
|
+
parentOPs: ObjectPageNavigationParent[];
|
|
108
111
|
};
|
|
109
112
|
export type SectionFormField = {
|
|
110
113
|
property: string;
|
|
114
|
+
connectedFields?: string;
|
|
115
|
+
fieldGroup?: string;
|
|
111
116
|
};
|
|
112
117
|
export type TableColumn = {
|
|
113
118
|
header?: string;
|
|
@@ -107,4 +107,14 @@ export declare function getSelectionFieldItems(selectionFieldsAgg: TreeAggregati
|
|
|
107
107
|
* @returns An array of filter field descriptions.
|
|
108
108
|
*/
|
|
109
109
|
export declare function getFilterFields(pageModel: TreeModel): TreeAggregations;
|
|
110
|
+
/**
|
|
111
|
+
* Parses a `DataFieldForAnnotation::<property>::<targetAnnotation>` style identifier.
|
|
112
|
+
*
|
|
113
|
+
* @param name - aggregation key or `field.name` from the spec model
|
|
114
|
+
* @returns the parsed property and target annotation, or undefined for non-annotation entries
|
|
115
|
+
*/
|
|
116
|
+
export declare function parseDataFieldForAnnotationName(name: string | undefined): {
|
|
117
|
+
property: string;
|
|
118
|
+
targetAnnotation: string;
|
|
119
|
+
} | undefined;
|
|
110
120
|
//# sourceMappingURL=modelUtils.d.ts.map
|
package/dist/utils/modelUtils.js
CHANGED
|
@@ -174,4 +174,25 @@ export function getFilterFields(pageModel) {
|
|
|
174
174
|
const selectionFieldsAggregations = getAggregations(selectionFields);
|
|
175
175
|
return selectionFieldsAggregations;
|
|
176
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Parses a `DataFieldForAnnotation::<property>::<targetAnnotation>` style identifier.
|
|
179
|
+
*
|
|
180
|
+
* @param name - aggregation key or `field.name` from the spec model
|
|
181
|
+
* @returns the parsed property and target annotation, or undefined for non-annotation entries
|
|
182
|
+
*/
|
|
183
|
+
export function parseDataFieldForAnnotationName(name) {
|
|
184
|
+
if (!name) {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
const segments = name.split('::');
|
|
188
|
+
if (segments.length < 3 || segments[0] !== 'DataFieldForAnnotation') {
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
const property = segments[1];
|
|
192
|
+
const targetAnnotation = segments[2];
|
|
193
|
+
if (!property || !targetAnnotation) {
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
return { property, targetAnnotation };
|
|
197
|
+
}
|
|
177
198
|
//# sourceMappingURL=modelUtils.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getAggregations } from './modelUtils.js';
|
|
1
|
+
import { getAggregations, parseDataFieldForAnnotationName } from './modelUtils.js';
|
|
2
2
|
import { extractTableColumnsFromNode } from './tableUtils.js';
|
|
3
3
|
import { PageTypeV4 } from '@sap/ux-specification/dist/types/src/common/page.js';
|
|
4
4
|
import { parse } from '@sap-ux/edmx-parser';
|
|
@@ -60,26 +60,40 @@ export function getObjectPages(applicationModel) {
|
|
|
60
60
|
return objectPages;
|
|
61
61
|
}
|
|
62
62
|
/**
|
|
63
|
-
* Finds parent
|
|
63
|
+
* Finds the chain of parent Object Pages leading from the List Report down to the target page.
|
|
64
64
|
*
|
|
65
65
|
* @param targetObjectPageKey - key of the target object page
|
|
66
66
|
* @param objectPages - the array of object pages extracted from the application model
|
|
67
67
|
* @param listReportPageKey - the key of the List Report page in the application model, used to find navigation routes to object pages
|
|
68
|
-
* @returns navigation data including
|
|
68
|
+
* @returns navigation data including the ordered ancestor Object Page chain
|
|
69
69
|
*/
|
|
70
70
|
function getObjectPageNavigationParents(targetObjectPageKey, objectPages, listReportPageKey) {
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
71
|
+
const parentOPs = [];
|
|
72
|
+
const visited = new Set([targetObjectPageKey]); // guard against infinite loop in case of invalid manifest entries
|
|
73
|
+
let cursor = targetObjectPageKey;
|
|
74
|
+
while (true) {
|
|
75
|
+
const childKey = cursor;
|
|
76
|
+
let parent;
|
|
77
|
+
let parentNavigationProperty;
|
|
78
|
+
for (const objectPage of objectPages) {
|
|
79
|
+
const route = getNavigationRoutes(objectPage).find((navigation) => navigation.route === childKey);
|
|
80
|
+
if (route) {
|
|
81
|
+
parent = objectPage;
|
|
82
|
+
parentNavigationProperty = route.identifier;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
80
85
|
}
|
|
81
|
-
|
|
82
|
-
|
|
86
|
+
if (!parent?.name || !parentNavigationProperty || visited.has(parent.name)) {
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
visited.add(parent.name);
|
|
90
|
+
parentOPs.unshift({ name: parent.name, navigationProperty: parentNavigationProperty });
|
|
91
|
+
cursor = parent.name;
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
parentLRName: listReportPageKey ?? '', // app is possibly malformed if no LR found
|
|
95
|
+
parentOPs
|
|
96
|
+
};
|
|
83
97
|
}
|
|
84
98
|
/**
|
|
85
99
|
* Extracts header sections data from an object page model.
|
|
@@ -133,7 +147,7 @@ function extractObjectPageBodySectionsData(objectPage, convertedMetadata, schema
|
|
|
133
147
|
const sections = getAggregations(sectionsAggregation);
|
|
134
148
|
Object.entries(sections).forEach(([sectionKey, section]) => {
|
|
135
149
|
const sectionId = getSectionIdentifier(section) ?? sectionKey;
|
|
136
|
-
const subSections = extractBodySubSectionsData(section, sectionId);
|
|
150
|
+
const subSections = extractBodySubSectionsData(section, sectionId, convertedMetadata, objectPage.entitySet);
|
|
137
151
|
const navigationProperty = getNavigationPropertyFromKey(sectionKey);
|
|
138
152
|
const isTable = isTableSection(section);
|
|
139
153
|
const sectionData = {
|
|
@@ -142,7 +156,9 @@ function extractObjectPageBodySectionsData(objectPage, convertedMetadata, schema
|
|
|
142
156
|
isTable,
|
|
143
157
|
custom: !!section.custom,
|
|
144
158
|
order: section?.order ?? -1,
|
|
145
|
-
fields: section.custom || isTable
|
|
159
|
+
fields: section.custom || isTable
|
|
160
|
+
? []
|
|
161
|
+
: extractFormFields(section, convertedMetadata, objectPage.entitySet),
|
|
146
162
|
tableColumns: section.custom || !isTable ? {} : extractTableColumnsFromNode(section),
|
|
147
163
|
subSections,
|
|
148
164
|
actions: !section.custom && convertedMetadata && schemaNamespace
|
|
@@ -215,9 +231,11 @@ function extractSectionActions(section, convertedMetadata, schemaNamespace) {
|
|
|
215
231
|
*
|
|
216
232
|
* @param section - body section entry from the application model
|
|
217
233
|
* @param parentSectionId - identifier of the parent section (used as fallback key prefix)
|
|
234
|
+
* @param convertedMetadata - optional converted OData metadata for drilling into ConnectedFields / FieldGroup wrappers
|
|
235
|
+
* @param entitySetName - the entity set the section is bound to (used to locate the entity type)
|
|
218
236
|
* @returns array of sub-section feature data
|
|
219
237
|
*/
|
|
220
|
-
function extractBodySubSectionsData(section, parentSectionId) {
|
|
238
|
+
function extractBodySubSectionsData(section, parentSectionId, convertedMetadata, entitySetName) {
|
|
221
239
|
const subSections = [];
|
|
222
240
|
const subSectionsAggregation = getAggregations(section)['subSections'];
|
|
223
241
|
const subSectionItems = getAggregations(subSectionsAggregation);
|
|
@@ -230,7 +248,7 @@ function extractBodySubSectionsData(section, parentSectionId) {
|
|
|
230
248
|
isTable,
|
|
231
249
|
custom: !!subSection.custom,
|
|
232
250
|
order: subSection?.order ?? -1, // put a negative order number to signal that order was not in spec
|
|
233
|
-
fields: subSection.custom || isTable ? [] : extractFormFields(subSection),
|
|
251
|
+
fields: subSection.custom || isTable ? [] : extractFormFields(subSection, convertedMetadata, entitySetName),
|
|
234
252
|
tableColumns: subSection.custom || !isTable ? {} : extractTableColumnsFromNode(subSection)
|
|
235
253
|
});
|
|
236
254
|
});
|
|
@@ -240,9 +258,11 @@ function extractBodySubSectionsData(section, parentSectionId) {
|
|
|
240
258
|
* Extracts form field property paths from a body sub-section's form aggregation.
|
|
241
259
|
*
|
|
242
260
|
* @param subSection - body sub-section entry from the application model
|
|
261
|
+
* @param convertedMetadata - optional converted OData metadata for drilling into ConnectedFields / FieldGroup wrappers
|
|
262
|
+
* @param entitySetName - the entity set the sub-section is bound to (used to locate the entity type)
|
|
243
263
|
* @returns array of form field property paths for use with iCheckField({ property })
|
|
244
264
|
*/
|
|
245
|
-
function extractFormFields(subSection) {
|
|
265
|
+
function extractFormFields(subSection, convertedMetadata, entitySetName) {
|
|
246
266
|
const fields = [];
|
|
247
267
|
const formAggregation = getAggregations(subSection)['form'];
|
|
248
268
|
if (!formAggregation) {
|
|
@@ -250,14 +270,71 @@ function extractFormFields(subSection) {
|
|
|
250
270
|
}
|
|
251
271
|
const fieldsAggregation = getAggregations(formAggregation)['fields'];
|
|
252
272
|
const fieldItems = getAggregations(fieldsAggregation);
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
273
|
+
const entityType = convertedMetadata && entitySetName ? resolveEntityType(convertedMetadata, entitySetName) : undefined;
|
|
274
|
+
Object.values(fieldItems).forEach((fieldItem) => {
|
|
275
|
+
const annotationParts = parseDataFieldForAnnotationName(fieldItem.name);
|
|
276
|
+
const valueProperty = fieldItem.schema?.keys?.find((key) => key.name === 'Value')?.value;
|
|
277
|
+
const baseProperty = valueProperty ?? annotationParts?.property;
|
|
278
|
+
if (!baseProperty) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (annotationParts) {
|
|
282
|
+
const qualifier = annotationParts.targetAnnotation;
|
|
283
|
+
if (annotationParts.property === 'ConnectedFields' && entityType) {
|
|
284
|
+
resolveConnectedFieldsInnerProperties(entityType, qualifier).forEach((property) => {
|
|
285
|
+
fields.push({ property, connectedFields: qualifier });
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
else if (annotationParts.property === 'FieldGroup' && entityType) {
|
|
289
|
+
resolveFieldGroupInnerProperties(entityType, qualifier).forEach((property) => {
|
|
290
|
+
fields.push({ property, fieldGroup: qualifier });
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
// ConnectedFields/FieldGroup without metadata: skip
|
|
294
|
+
// Unknown annotation wrapper type: skip
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
fields.push({ property: baseProperty });
|
|
257
298
|
}
|
|
258
299
|
});
|
|
259
300
|
return fields;
|
|
260
301
|
}
|
|
302
|
+
/**
|
|
303
|
+
* Resolves the inner `Value` paths of a `@UI.ConnectedFields#<qualifier>` annotation.
|
|
304
|
+
*
|
|
305
|
+
* @param entityType - the entity type carrying the annotation
|
|
306
|
+
* @param qualifier - the annotation qualifier
|
|
307
|
+
* @returns the inner DataField property paths
|
|
308
|
+
*/
|
|
309
|
+
function resolveConnectedFieldsInnerProperties(entityType, qualifier) {
|
|
310
|
+
const annotation = entityType.annotations?.UI?.[`ConnectedFields#${qualifier}`];
|
|
311
|
+
const dictionary = annotation?.Data ?? {};
|
|
312
|
+
return Object.values(dictionary)
|
|
313
|
+
.map((dataField) => dataField?.Value?.path)
|
|
314
|
+
.filter((path) => Boolean(path));
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Resolves the inner `Value` paths of a `@UI.FieldGroup#<qualifier>` annotation.
|
|
318
|
+
*
|
|
319
|
+
* @param entityType - the entity type carrying the annotation
|
|
320
|
+
* @param qualifier - the annotation qualifier
|
|
321
|
+
* @returns the inner DataField property paths
|
|
322
|
+
*/
|
|
323
|
+
function resolveFieldGroupInnerProperties(entityType, qualifier) {
|
|
324
|
+
const annotation = entityType.annotations?.UI?.[`FieldGroup#${qualifier}`];
|
|
325
|
+
const dataFields = annotation?.Data ?? [];
|
|
326
|
+
return dataFields.map((dataField) => dataField?.Value?.path).filter((path) => Boolean(path));
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Looks up the entity type for the given entity set name in the converted metadata.
|
|
330
|
+
*
|
|
331
|
+
* @param convertedMetadata - the converted OData metadata
|
|
332
|
+
* @param entitySetName - the entity set name
|
|
333
|
+
* @returns the entity type, or undefined if not found
|
|
334
|
+
*/
|
|
335
|
+
function resolveEntityType(convertedMetadata, entitySetName) {
|
|
336
|
+
return convertedMetadata.entitySets.find((es) => es.name === entitySetName)?.entityType;
|
|
337
|
+
}
|
|
261
338
|
/**
|
|
262
339
|
* Extracts the OData navigation property from a spec model section key.
|
|
263
340
|
* Section keys for table sections follow the pattern `_NavProperty::@annotation`, so the
|
package/package.json
CHANGED
|
@@ -33,11 +33,11 @@ sap.ui.define([
|
|
|
33
33
|
Then.onThe<%- navigationParents.parentLRName%>Generated.onTable().iCheckRows();
|
|
34
34
|
When.onThe<%- navigationParents.parentLRName%>Generated.onTable().iPressRow(0);
|
|
35
35
|
<% } -%>
|
|
36
|
-
<%
|
|
37
|
-
Then.onThe<%-
|
|
38
|
-
Then.onThe<%-
|
|
39
|
-
When.onThe<%-
|
|
40
|
-
<% } %>
|
|
36
|
+
<% navigationParents.parentOPs.forEach(function(parent) { %>
|
|
37
|
+
Then.onThe<%- parent.name %>Generated.iSeeThisPage();
|
|
38
|
+
Then.onThe<%- parent.name %>Generated.onTable({ property: "<%- parent.navigationProperty %>" }).iCheckRows();
|
|
39
|
+
When.onThe<%- parent.name %>Generated.onTable({ property: "<%- parent.navigationProperty %>" }).iPressRow(0);
|
|
40
|
+
<% }); %>
|
|
41
41
|
Then.onThe<%- name%>Generated.iSeeThisPage();
|
|
42
42
|
});
|
|
43
43
|
|
|
@@ -129,7 +129,7 @@ sap.ui.define([
|
|
|
129
129
|
Then.onThe<%- name%>Generated.iCheckSubSection({ section: "<%- subSection.id %>" });
|
|
130
130
|
<% if (subSection.fields && subSection.fields.length > 0) { -%>
|
|
131
131
|
<% subSection.fields.forEach(function(field) { -%>
|
|
132
|
-
Then.onThe<%- name%>Generated.onForm({ section: "<%- subSection.id %>" }).iCheckField({ property: "<%- field.property %>" });
|
|
132
|
+
Then.onThe<%- name%>Generated.onForm({ section: "<%- subSection.id %>" }).iCheckField({ property: "<%- field.property %>"<% if (field.connectedFields) { %>, connectedFields: "<%- field.connectedFields %>"<% } %><% if (field.fieldGroup) { %>, fieldGroup: "<%- field.fieldGroup %>"<% } %> });
|
|
133
133
|
<% }) -%>
|
|
134
134
|
<% } -%>
|
|
135
135
|
<% if (subSection.tableColumns && Object.keys(subSection.tableColumns).length > 0 && subSection.navigationProperty) { -%>
|
|
@@ -139,7 +139,7 @@ sap.ui.define([
|
|
|
139
139
|
<% } else { -%>
|
|
140
140
|
<% if (section.fields && section.fields.length > 0) { -%>
|
|
141
141
|
<% section.fields.forEach(function(field) { -%>
|
|
142
|
-
Then.onThe<%- name%>Generated.onForm({ section: "<%- section.id %>" }).iCheckField({ property: "<%- field.property %>" });
|
|
142
|
+
Then.onThe<%- name%>Generated.onForm({ section: "<%- section.id %>" }).iCheckField({ property: "<%- field.property %>"<% if (field.connectedFields) { %>, connectedFields: "<%- field.connectedFields %>"<% } %><% if (field.fieldGroup) { %>, fieldGroup: "<%- field.fieldGroup %>"<% } %> });
|
|
143
143
|
<% }) -%>
|
|
144
144
|
<% } -%>
|
|
145
145
|
<% if (section.tableColumns && Object.keys(section.tableColumns).length > 0 && section.navigationProperty) { -%>
|
|
@@ -52,11 +52,11 @@ function journey() {
|
|
|
52
52
|
Then.onThe<%- navigationParents.parentLRName%>Generated.onTable("").iCheckRows();
|
|
53
53
|
When.onThe<%- navigationParents.parentLRName%>Generated.onTable("").iPressRow(0);
|
|
54
54
|
<% } -%>
|
|
55
|
-
<%
|
|
56
|
-
Then.onThe<%-
|
|
57
|
-
Then.onThe<%-
|
|
58
|
-
When.onThe<%-
|
|
59
|
-
<% } %>
|
|
55
|
+
<% navigationParents.parentOPs.forEach(function(parent) { %>
|
|
56
|
+
Then.onThe<%- parent.name %>Generated.iSeeThisPage();
|
|
57
|
+
Then.onThe<%- parent.name %>Generated.onTable({ property: "<%- parent.navigationProperty %>" }).iCheckRows();
|
|
58
|
+
When.onThe<%- parent.name %>Generated.onTable({ property: "<%- parent.navigationProperty %>" }).iPressRow(0);
|
|
59
|
+
<% }); %>
|
|
60
60
|
Then.onThe<%- name%>Generated.iSeeThisPage();
|
|
61
61
|
});
|
|
62
62
|
|
|
@@ -148,7 +148,7 @@ function journey() {
|
|
|
148
148
|
Then.onThe<%- name%>Generated.iCheckSubSection({ section: "<%- subSection.id %>" });
|
|
149
149
|
<% if (subSection.fields && subSection.fields.length > 0) { -%>
|
|
150
150
|
<% subSection.fields.forEach(function(field) { -%>
|
|
151
|
-
Then.onThe<%- name%>Generated.onForm({ section: "<%- subSection.id %>" } as unknown as FormIdentifier).iCheckField({ property: "<%- field.property %>" });
|
|
151
|
+
Then.onThe<%- name%>Generated.onForm({ section: "<%- subSection.id %>" } as unknown as FormIdentifier).iCheckField({ property: "<%- field.property %>"<% if (field.connectedFields) { %>, connectedFields: "<%- field.connectedFields %>"<% } %><% if (field.fieldGroup) { %>, fieldGroup: "<%- field.fieldGroup %>"<% } %> });
|
|
152
152
|
<% }) -%>
|
|
153
153
|
<% } -%>
|
|
154
154
|
<% if (subSection.tableColumns && Object.keys(subSection.tableColumns).length > 0 && subSection.navigationProperty) { -%>
|
|
@@ -158,7 +158,7 @@ function journey() {
|
|
|
158
158
|
<% } else { -%>
|
|
159
159
|
<% if (section.fields && section.fields.length > 0) { -%>
|
|
160
160
|
<% section.fields.forEach(function(field) { -%>
|
|
161
|
-
Then.onThe<%- name%>Generated.onForm({ section: "<%- section.id %>" } as unknown as FormIdentifier).iCheckField({ property: "<%- field.property %>" });
|
|
161
|
+
Then.onThe<%- name%>Generated.onForm({ section: "<%- section.id %>" } as unknown as FormIdentifier).iCheckField({ property: "<%- field.property %>"<% if (field.connectedFields) { %>, connectedFields: "<%- field.connectedFields %>"<% } %><% if (field.fieldGroup) { %>, fieldGroup: "<%- field.fieldGroup %>"<% } %> });
|
|
162
162
|
<% }) -%>
|
|
163
163
|
<% } -%>
|
|
164
164
|
<% if (section.tableColumns && Object.keys(section.tableColumns).length > 0 && section.navigationProperty) { -%>
|