diff --git a/.changeset/breezy-candies-go.md b/.changeset/breezy-candies-go.md new file mode 100644 index 00000000000..9e21ca36e9b --- /dev/null +++ b/.changeset/breezy-candies-go.md @@ -0,0 +1,5 @@ +--- +'@sap-ux/ui5-test-writer': patch +--- + +Generate tests for Form and Table content in Object Page Sections diff --git a/packages/ui5-test-writer/src/types.ts b/packages/ui5-test-writer/src/types.ts index 850cee7a516..9cd8d4b55af 100644 --- a/packages/ui5-test-writer/src/types.ts +++ b/packages/ui5-test-writer/src/types.ts @@ -88,18 +88,34 @@ export type ObjectPageNavigationParents = { parentOPTableSection?: string; }; +export type SectionFormField = { + property: string; +}; + +export type TableColumn = { + header?: string; +}; + +export type TableColumnFeatureData = Record; + export type BodySubSectionFeatureData = { id: string; + navigationProperty?: string; isTable: boolean; custom: boolean; order: number; + fields: SectionFormField[]; + tableColumns: TableColumnFeatureData; }; export type BodySectionFeatureData = { id: string; + navigationProperty?: string; isTable: boolean; custom: boolean; order: number; + fields: SectionFormField[]; + tableColumns: TableColumnFeatureData; subSections: BodySubSectionFeatureData[]; }; diff --git a/packages/ui5-test-writer/src/utils/modelUtils.ts b/packages/ui5-test-writer/src/utils/modelUtils.ts index 046c3ee5728..289a7c514d1 100644 --- a/packages/ui5-test-writer/src/utils/modelUtils.ts +++ b/packages/ui5-test-writer/src/utils/modelUtils.ts @@ -13,6 +13,7 @@ import type { import type { AppFeatures, FPMFeatures } from '../types'; import { getObjectPageFeatures, getObjectPages } from './objectPageUtils'; import { getFilterFieldNames, getListReportFeatures } from './listReportUtils'; +import { extractTableColumnsFromNode } from './tableUtils'; export interface AggregationItem extends TreeAggregation { description: string; @@ -125,44 +126,6 @@ export async function getAppFeatures( return featureData; } -/** - * Gets identifier of a column for OPA5 tests. - * If the column is custom, the identifier is taken from the 'Key' entry in the schema keys. - * If the column is not custom, the identifier is taken from the 'Value' entry in the schema keys. - * If no such entry is found, undefined is returned. - * - * @param column - column module from ux specification - * @param column.custom boolean indicating whether the column is custom - * @param column.schema schema of the column - * @param column.schema.keys keys of the column; expected to have an entry with the name 'Key' or 'Value' - * @returns identifier of the column for OPA5 tests; can be the name or index - */ -function getColumnIdentifier(column: { - custom: boolean; - schema: { keys: { name: string; value: string }[] }; -}): string | undefined { - const key = column.custom ? 'Key' : 'Value'; - const keyEntry = column.schema.keys.find((entry: { name: string; value: string }) => entry.name === key); - return keyEntry?.value; -} - -/** - * Transforms column aggregations from the ux specification model into a map of columns for OPA5 tests. - * - * @param columnAggregations column aggregations from the ux specification model - * @returns a map of columns for OPA5 tests - */ -function transformTableColumns(columnAggregations: Record): Record { - const columns: Record = {}; - Object.values(columnAggregations).forEach((columnAggregation, index) => { - columns[getColumnIdentifier(columnAggregation) ?? index] = { - header: columnAggregation.description - // TODO possibly more reliable properties could be used? - }; - }); - return columns; -} - /** * Retrieves table column data from the page model using ux-specification. * @@ -177,8 +140,7 @@ export function getTableColumnData( let tableColumns: Record> = {}; try { - const columnAggregations = getTableColumns(pageModel); - tableColumns = transformTableColumns(columnAggregations); + tableColumns = extractTableColumnsFromNode(pageModel.root); } catch (error) { log?.debug(error); } @@ -289,17 +251,3 @@ export function getFilterFields(pageModel: TreeModel): TreeAggregations { const selectionFieldsAggregations = getAggregations(selectionFields); return selectionFieldsAggregations; } - -/** - * Retrieves the table columns aggregation from the given tree model. - * - * @param pageModel - The tree model containing table column definitions. - * @returns The table columns aggregation object. - */ -export function getTableColumns(pageModel: TreeModel): TreeAggregations { - const table = getAggregations(pageModel.root)['table']; - const tableAggregations = getAggregations(table); - const columns = tableAggregations['columns']; - const columnAggregations = getAggregations(columns); - return columnAggregations; -} diff --git a/packages/ui5-test-writer/src/utils/objectPageUtils.ts b/packages/ui5-test-writer/src/utils/objectPageUtils.ts index 3f9a2125b88..1598344db57 100644 --- a/packages/ui5-test-writer/src/utils/objectPageUtils.ts +++ b/packages/ui5-test-writer/src/utils/objectPageUtils.ts @@ -2,6 +2,7 @@ import type { Logger } from '@sap-ux/logger'; import type { ApplicationModel } from '@sap/ux-specification/dist/types/src/parser'; import type { FormField, + SectionFormField, BodySectionFeatureData, BodySubSectionFeatureData, HeaderSectionFeatureData, @@ -17,6 +18,7 @@ import { type SectionItem, getAggregations } from './modelUtils'; +import { extractTableColumnsFromNode } from './tableUtils'; import { PageTypeV4 } from '@sap/ux-specification/dist/types/src/common/page'; /** @@ -158,9 +160,12 @@ function extractObjectPageBodySectionsData(objectPage: PageWithModelV4): BodySec const subSections = extractBodySubSectionsData(section, sectionId); bodySections.push({ id: sectionId, + navigationProperty: getNavigationPropertyFromKey(sectionKey), isTable: !!section.isTable, custom: !!section.custom, order: section?.order ?? -1, // put a negative order number to signal that order was not in spec + fields: section.custom || section.isTable ? [] : extractFormFields(section), + tableColumns: section.custom || !section.isTable ? {} : extractTableColumnsFromNode(section), subSections }); }); @@ -184,14 +189,53 @@ function extractBodySubSectionsData(section: SectionItem, parentSectionId: strin const subSectionId = getSectionIdentifier(subSection) ?? `${parentSectionId}_${subSectionKey}`; subSections.push({ id: subSectionId, + navigationProperty: getNavigationPropertyFromKey(subSectionKey), isTable: !!subSection.isTable, custom: !!subSection.custom, - order: subSection?.order ?? -1 // put a negative order number to signal that order was not in spec + order: subSection?.order ?? -1, // put a negative order number to signal that order was not in spec + fields: subSection.custom || subSection.isTable ? [] : extractFormFields(subSection), + tableColumns: subSection.custom || !subSection.isTable ? {} : extractTableColumnsFromNode(subSection) }); }); return subSections; } +/** + * Extracts form field property paths from a body sub-section's form aggregation. + * + * @param subSection - body sub-section entry from the application model + * @returns array of form field property paths for use with iCheckField({ property }) + */ +function extractFormFields(subSection: BodySectionItem): SectionFormField[] { + const fields: SectionFormField[] = []; + const formAggregation = getAggregations(subSection)['form'] as AggregationItem; + if (!formAggregation) { + return fields; + } + const fieldsAggregation = getAggregations(formAggregation)['fields'] as AggregationItem; + const fieldItems = getAggregations(fieldsAggregation) as Record; + Object.values(fieldItems).forEach((field) => { + const property = field.schema?.keys?.find((key) => key.name === 'Value')?.value; + if (property) { + fields.push({ property }); + } + }); + return fields; +} + +/** + * Extracts the OData navigation property from a spec model section key. + * Section keys for table sections follow the pattern `_NavProperty::@annotation`, so the + * navigation property is the part before `::` when it starts with an underscore. + * + * @param sectionKey - the key of the section in the spec model aggregations + * @returns navigation property (e.g. '_Booking'), or undefined for non-navigation sections + */ +function getNavigationPropertyFromKey(sectionKey: string): string | undefined { + const prefix = sectionKey.split('::')[0]; + return prefix.startsWith('_') ? prefix : undefined; +} + /** * Gets the identifier of a section for OPA5 tests. * diff --git a/packages/ui5-test-writer/src/utils/tableUtils.ts b/packages/ui5-test-writer/src/utils/tableUtils.ts new file mode 100644 index 00000000000..86041d24b36 --- /dev/null +++ b/packages/ui5-test-writer/src/utils/tableUtils.ts @@ -0,0 +1,66 @@ +import type { TreeAggregation, TreeAggregations } from '@sap/ux-specification/dist/types/src/parser'; +import { getAggregations } from './modelUtils'; +import type { TableColumn, TableColumnFeatureData } from '../types'; + +type ColumnModelItem = { + custom?: boolean; + description?: string; + schema: { keys: { name: string; value: string }[] }; +}; + +export type ColumnAggregations = TreeAggregations & { + [key: string]: ColumnModelItem; +}; + +/** + * Gets the identifier of a column for OPA5 tests. + * Custom columns use the 'Key' entry; standard columns use the 'Value' entry from the schema keys. + * + * @param column - column item from ux specification + * @returns identifier of the column for OPA5 tests; undefined if no matching key entry is found + */ +export function getColumnIdentifier(column: ColumnModelItem): string | undefined { + const key = column.custom ? 'Key' : 'Value'; + return column.schema.keys.find((k) => k.name === key)?.value; +} + +/** + * Transforms column aggregations from the ux specification model into a map of columns for OPA5 tests. + * Each column entry includes the column header label for display verification. + * + * @param columnAggregations - column aggregations from the ux specification model + * @returns a map of column identifiers to column state objects for use with iCheckColumns() + */ +export function transformTableColumns(columnAggregations: ColumnAggregations): TableColumnFeatureData { + const columns: TableColumnFeatureData = {}; + Object.values(columnAggregations).forEach((column, index) => { + const id = getColumnIdentifier(column) ?? String(index); + const state: TableColumn = {}; + if (column.description) { + state['header'] = column.description; + } + columns[id] = state; + }); + return columns; +} + +/** + * Extracts table column data from a spec model node that contains a 'table' aggregation. + * Covers both page-level nodes (List Report, FPM) via their root and section-level nodes + * (Object Page body sections) — both are TreeAggregation nodes that expose a 'table' aggregation. + * + * @param node - tree aggregation node that exposes a 'table' aggregation + * @returns a map of column identifiers to column state objects for use with iCheckColumns() + */ +export function extractTableColumnsFromNode(node: TreeAggregation): TableColumnFeatureData { + const tableAggregation = getAggregations(node)['table']; + if (!tableAggregation) { + return {}; + } + const columnsAggregation = getAggregations(tableAggregation)['columns']; + if (!columnsAggregation) { + return {}; + } + const columnItems = getAggregations(columnsAggregation); + return transformTableColumns(columnItems as ColumnAggregations); +} diff --git a/packages/ui5-test-writer/templates/v4/integration/ObjectPageJourney.js b/packages/ui5-test-writer/templates/v4/integration/ObjectPageJourney.js index 4ba58d030c0..271b8df4dfe 100644 --- a/packages/ui5-test-writer/templates/v4/integration/ObjectPageJourney.js +++ b/packages/ui5-test-writer/templates/v4/integration/ObjectPageJourney.js @@ -76,8 +76,25 @@ sap.ui.define([ <% section.subSections.forEach(function(subSection) { -%> //When.onThe<%- name%>.iGoToSection({ section: "<%- section.id %>", subSection: "<%- subSection.id %>" }); Then.onThe<%- name%>.iCheckSubSection({ section: "<%- subSection.id %>" }); +<% if (subSection.fields && subSection.fields.length > 0) { -%> +<% subSection.fields.forEach(function(field) { -%> + Then.onThe<%- name%>.onForm({ section: "<%- subSection.id %>" }).iCheckField({ property: "<%- field.property %>" }); <% }) -%> <% } -%> +<% if (subSection.tableColumns && Object.keys(subSection.tableColumns).length > 0 && subSection.navigationProperty) { -%> + Then.onThe<%- name%>.onTable({ property: "<%- subSection.navigationProperty %>" }).iCheckColumns(<%- JSON.stringify(subSection.tableColumns) %>); +<% } -%> +<% }) -%> +<% } else { -%> +<% if (section.fields && section.fields.length > 0) { -%> +<% section.fields.forEach(function(field) { -%> + Then.onThe<%- name%>.onForm({ section: "<%- section.id %>" }).iCheckField({ property: "<%- field.property %>" }); +<% }) -%> +<% } -%> +<% if (section.tableColumns && Object.keys(section.tableColumns).length > 0 && section.navigationProperty) { -%> + Then.onThe<%- name%>.onTable({ property: "<%- section.navigationProperty %>" }).iCheckColumns(<%- JSON.stringify(section.tableColumns) %>); +<% } -%> +<% } -%> <% }) -%> }); <% } -%> diff --git a/packages/ui5-test-writer/test/test-input/constants.ts b/packages/ui5-test-writer/test/test-input/constants.ts index 1aa8eb6c6a8..a95e4728092 100644 --- a/packages/ui5-test-writer/test/test-input/constants.ts +++ b/packages/ui5-test-writer/test/test-input/constants.ts @@ -2,4 +2,4 @@ export const V4_MODEL = '{"applicationModel":{"$schema":"./.schemas/App.json","i export const V4_NO_FILTER_MODEL = '{"applicationModel":{"$schema":"./.schemas/App.json","id":"project1","pages":{"TravelList":{"pageType":"ListReport","entitySet":"Travel","contextPath":"/Travel","entityType":"com.sap.gateway.srvd.dmo.sd_travel_mdsk.v0001.TravelType","variantManagement":"Page","navigation":{"Travel":{"route":"TravelObjectPage"}},"routePattern":":?query:","template":"sap.fe.templates.ListReport","model":{"root":{"path":[],"aggregations":{"header":{"path":["header"],"aggregations":{"actions":{"path":["header","actions"],"aggregations":{},"properties":{},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":["NativeAction","NativeNavigation"],"schemaCreationForms":[{"name":"CustomAction","kind":"schema","title":"PAGE_EDITOR_OUTLINE_ADD_CUSTOM_ACTIONS_TITLE","disabled":false}],"state":0,"type":"Object","custom":false,"isTable":false,"schema":{"type":"object","additionalProperties":{"$ref":"#/definitions/CustomHeaderAction"},"isViewNode":true,"description":"Actions","properties":{}},"sortableList":true,"sortableCollection":"actions","i18nKey":"ACTIONS","name":"actions","order":0,"description":"Actions","isViewNode":true,"additionalProperties":{"path":[],"aggregations":{"actions":{"path":["header","actions","actions"],"aggregations":{"position":{"path":["header","actions","actions","position"],"aggregations":{},"properties":{"anchor":{"state":0,"schema":{"description":"The key of another action to be used as placement anchor.","artifactType":"Manifest","type":"string","oneOf":[]},"name":"Anchor","freeText":true,"description":"The key of another action to be used as placement anchor.","artifactType":"Manifest"},"placement":{"state":0,"schema":{"enum":["After","Before"],"type":"string","description":"Define the placement, either before or after the anchor action.","artifactType":"Manifest"},"name":"Placement","freeText":false,"description":"Define the placement, either before or after the anchor action.","artifactType":"Manifest","required":true}},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":[],"schemaCreationForms":[],"state":0,"type":"Object","custom":false,"isTable":false,"schema":{"type":"object","properties":{"anchor":{"description":"The key of another action to be used as placement anchor.","artifactType":"Manifest","type":"string","oneOf":[]},"placement":{"$ref":"#/definitions/ActionPlacement","description":"Define the placement, either before or after the anchor action.","artifactType":"Manifest"}},"additionalProperties":false,"required":["placement"],"description":"Defines the position of the action relative to other actions.","artifactType":"Manifest"},"name":"position","order":0,"description":"Defines the position of the action relative to other actions.","artifactType":"Manifest"}},"properties":{"text":{"state":0,"schema":{"description":"The text that is displayed on the button (typically a binding to an i18n entry).","i18nClassification":"COL: Custom action text","artifactType":"Manifest","type":"string"},"name":"Text","freeText":true,"description":"The text that is displayed on the button (typically a binding to an i18n entry).","i18nClassification":"COL: Custom action text","artifactType":"Manifest","required":true},"press":{"state":0,"schema":{"description":"Relevant for extension actions; allows the definition of a target action handler.","artifactType":"Manifest","type":"string"},"name":"Press","freeText":true,"description":"Relevant for extension actions; allows the definition of a target action handler.","artifactType":"Manifest","required":true},"visible":{"state":0,"schema":{"enum":[false,true]},"name":"Visible","freeText":true,"description":"Defines if the action button is visible.","artifactType":"Manifest"},"enabled":{"state":0,"schema":{"enum":[false,true]},"name":"Enabled","freeText":true,"description":"Defines if the action is enabled. The default value is true.","artifactType":"Manifest"},"group":{"state":0,"schema":{"artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/#/topic/cbf16c599f2d4b8796e3702f7d4aae6c","type":"string"},"name":"Group","freeText":true,"artifactType":"Manifest"}},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":[],"schemaCreationForms":[],"state":0,"type":"Object","custom":false,"isTable":false,"schema":{"type":"object","properties":{"position":{"$ref":"#/definitions/CustomHeaderActionPosition","description":"Defines the position of the action relative to other actions.","artifactType":"Manifest"},"text":{"description":"The text that is displayed on the button (typically a binding to an i18n entry).","i18nClassification":"COL: Custom action text","artifactType":"Manifest","type":"string"},"press":{"description":"Relevant for extension actions; allows the definition of a target action handler.","artifactType":"Manifest","type":"string"},"visible":{"anyOf":[{"enum":[false,true]},{"type":"string"}],"description":"Defines if the action button is visible.","artifactType":"Manifest"},"enabled":{"anyOf":[{"enum":[false,true]},{"type":"string"}],"description":"Defines if the action is enabled. The default value is true.","artifactType":"Manifest"},"group":{"artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/#/topic/cbf16c599f2d4b8796e3702f7d4aae6c","type":"string"}},"additionalProperties":false,"required":["press","text"],"isViewNode":true,"description":"Custom Action"},"name":"actions","order":0,"description":"Custom Action","isViewNode":true}},"properties":{},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":["AnalyticalChart"],"schemaCreationForms":[],"state":0,"type":"Object","custom":false,"isTable":false,"name":"root"},"value":{},"locations":[],"formSchema":{"path":["header","actions","actions"],"aggregations":{"position":{"path":["header","actions","actions","position"],"aggregations":{},"properties":{"anchor":{"state":0,"schema":{"description":"The key of another action to be used as placement anchor.","artifactType":"Manifest","type":"string","oneOf":[]},"name":"Anchor","freeText":true,"description":"The key of another action to be used as placement anchor.","artifactType":"Manifest"},"placement":{"state":0,"schema":{"enum":["After","Before"],"type":"string","description":"Define the placement, either before or after the anchor action.","artifactType":"Manifest"},"name":"Placement","freeText":false,"description":"Define the placement, either before or after the anchor action.","artifactType":"Manifest","required":true}},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":[],"schemaCreationForms":[],"state":0,"type":"Object","custom":false,"isTable":false,"schema":{"type":"object","properties":{"anchor":{"description":"The key of another action to be used as placement anchor.","artifactType":"Manifest","type":"string","oneOf":[]},"placement":{"$ref":"#/definitions/ActionPlacement","description":"Define the placement, either before or after the anchor action.","artifactType":"Manifest"}},"additionalProperties":false,"required":["placement"],"description":"Defines the position of the action relative to other actions.","artifactType":"Manifest"},"name":"position","order":0,"description":"Defines the position of the action relative to other actions.","artifactType":"Manifest"}},"properties":{"text":{"state":0,"schema":{"description":"The text that is displayed on the button (typically a binding to an i18n entry).","i18nClassification":"COL: Custom action text","artifactType":"Manifest","type":"string"},"name":"Text","freeText":true,"description":"The text that is displayed on the button (typically a binding to an i18n entry).","i18nClassification":"COL: Custom action text","artifactType":"Manifest","required":true},"press":{"state":0,"schema":{"description":"Relevant for extension actions; allows the definition of a target action handler.","artifactType":"Manifest","type":"string"},"name":"Press","freeText":true,"description":"Relevant for extension actions; allows the definition of a target action handler.","artifactType":"Manifest","required":true},"visible":{"state":0,"schema":{"enum":[false,true]},"name":"Visible","freeText":true,"description":"Defines if the action button is visible.","artifactType":"Manifest"},"enabled":{"state":0,"schema":{"enum":[false,true]},"name":"Enabled","freeText":true,"description":"Defines if the action is enabled. The default value is true.","artifactType":"Manifest"},"group":{"state":0,"schema":{"artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/#/topic/cbf16c599f2d4b8796e3702f7d4aae6c","type":"string"},"name":"Group","freeText":true,"artifactType":"Manifest"}},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":[],"schemaCreationForms":[],"state":0,"type":"Object","custom":false,"isTable":false,"schema":{"type":"object","properties":{"position":{"$ref":"#/definitions/CustomHeaderActionPosition","description":"Defines the position of the action relative to other actions.","artifactType":"Manifest"},"text":{"description":"The text that is displayed on the button (typically a binding to an i18n entry).","i18nClassification":"COL: Custom action text","artifactType":"Manifest","type":"string"},"press":{"description":"Relevant for extension actions; allows the definition of a target action handler.","artifactType":"Manifest","type":"string"},"visible":{"anyOf":[{"enum":[false,true]},{"type":"string"}],"description":"Defines if the action button is visible.","artifactType":"Manifest"},"enabled":{"anyOf":[{"enum":[false,true]},{"type":"string"}],"description":"Defines if the action is enabled. The default value is true.","artifactType":"Manifest"},"group":{"artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/#/topic/cbf16c599f2d4b8796e3702f7d4aae6c","type":"string"}},"additionalProperties":false,"required":["press","text"],"isViewNode":true,"description":"Custom Action"},"name":"actions","order":0,"description":"Custom Action","isViewNode":true}}},"properties":{},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":[],"schemaCreationForms":[],"state":0,"type":"Object","custom":false,"isTable":false,"schema":{"description":"Header","isViewNode":true,"type":"object","properties":{"actions":{"$ref":"#/definitions/HeaderActions"}},"additionalProperties":false,"propertyIndex":0},"name":"header","order":0,"description":"Header","isViewNode":true,"value":{"actions":{}},"locations":[]},"filterBar":{"path":["filterBar"],"aggregations":{"selectionFields":{"path":["filterBar","selectionFields"],"aggregations":{},"properties":{},"variants":[{"aggregations":{},"properties":{}}],"annotationCreationForms":[],"allowedAnnotationCreationForms":["NativeFilterFields"],"schemaCreationForms":[{"name":"CustomFilterField","kind":"schema","title":"PAGE_EDITOR_OUTLINE_ADD_CUSTOM_FILTER_FIELDS_TITLE","disabled":false}],"state":0,"type":"Object","custom":false,"isTable":false,"schema":{"description":"Filter Fields","isViewNode":true,"type":"object","additionalProperties":{"$ref":"#/definitions/CustomFilterField"},"properties":{},"annotationPath":"/com.sap.gateway.srvd.dmo.sd_travel_mdsk.v0001.TravelType/@com.sap.vocabularies.UI.v1.SelectionFields"},"sortableList":true,"i18nKey":"FILTER_FIELDS","name":"selectionFields","order":0,"description":"Filter Fields","isViewNode":true,"additionalProperties":{"path":[],"aggregations":{"selectionFields":{"path":["filterBar","selectionFields","selectionFields"],"aggregations":{"position":{"path":["filterBar","selectionFields","selectionFields","position"],"aggregations":{},"properties":{"anchor":{"state":0,"schema":{"description":"The key of another filter field is to be used as a placement anchor.","type":"string","artifactType":"Manifest","oneOf":[]},"name":"Anchor","freeText":true,"description":"The key of another filter field is to be used as a placement anchor.","artifactType":"Manifest"},"placement":{"state":0,"schema":{"enum":["After","Before"],"type":"string","description":"Define the placement, either before or after the anchor filter field.","artifactType":"Manifest"},"name":"Placement","freeText":false,"description":"Define the placement, either before or after the anchor filter field.","artifactType":"Manifest","required":true}},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":[],"schemaCreationForms":[],"state":0,"type":"Object","custom":false,"isTable":false,"schema":{"type":"object","properties":{"anchor":{"description":"The key of another filter field is to be used as a placement anchor.","type":"string","artifactType":"Manifest","oneOf":[]},"placement":{"$ref":"#/definitions/FilterFieldPlacement","description":"Define the placement, either before or after the anchor filter field.","artifactType":"Manifest"}},"additionalProperties":false,"required":["placement"],"description":"Defines the position of the filter field relative to another filter field.","artifactType":"Manifest"},"name":"position","order":0,"description":"Defines the position of the filter field relative to another filter field.","artifactType":"Manifest"}},"properties":{"label":{"state":0,"schema":{"description":"A static or i18n binding string.","i18nClassification":"COL: Custom filter field label","type":"string","artifactType":"Manifest"},"name":"Label","freeText":true,"description":"A static or i18n binding string.","i18nClassification":"COL: Custom filter field label","artifactType":"Manifest","required":true},"property":{"state":0,"schema":{"description":"The full path to the property to be filtered.","type":"string","artifactType":"Manifest"},"name":"Property","freeText":true,"description":"The full path to the property to be filtered.","artifactType":"Manifest","required":true},"template":{"state":0,"schema":{"description":"The path to the XML template containing the filter control.","type":"string","artifactType":"Manifest"},"name":"Template","freeText":true,"description":"The path to the XML template containing the filter control.","artifactType":"Manifest","required":true},"required":{"state":0,"schema":{"description":"Determines whether the filter field requires a value.","type":"boolean","artifactType":"Manifest"},"name":"Required","freeText":false,"description":"Determines whether the filter field requires a value.","artifactType":"Manifest"}},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":[],"schemaCreationForms":[],"state":0,"type":"Object","custom":false,"isTable":false,"schema":{"description":"Custom Filter Field","isViewNode":true,"type":"object","properties":{"label":{"description":"A static or i18n binding string.","i18nClassification":"COL: Custom filter field label","type":"string","artifactType":"Manifest"},"property":{"description":"The full path to the property to be filtered.","type":"string","artifactType":"Manifest"},"template":{"description":"The path to the XML template containing the filter control.","type":"string","artifactType":"Manifest"},"required":{"description":"Determines whether the filter field requires a value.","type":"boolean","artifactType":"Manifest"},"position":{"$ref":"#/definitions/CustomFilterFieldPosition","description":"Defines the position of the filter field relative to another filter field.","artifactType":"Manifest"}},"additionalProperties":false,"required":["label","property","template"]},"name":"selectionFields","order":0,"description":"Custom Filter Field","isViewNode":true}},"properties":{},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":["AnalyticalChart"],"schemaCreationForms":[],"state":0,"type":"Object","custom":false,"isTable":false,"name":"root"},"locations":[],"formSchema":{"path":["filterBar","selectionFields","selectionFields"],"aggregations":{"position":{"path":["filterBar","selectionFields","selectionFields","position"],"aggregations":{},"properties":{"anchor":{"state":0,"schema":{"description":"The key of another filter field is to be used as a placement anchor.","type":"string","artifactType":"Manifest","oneOf":[]},"name":"Anchor","freeText":true,"description":"The key of another filter field is to be used as a placement anchor.","artifactType":"Manifest"},"placement":{"state":0,"schema":{"enum":["After","Before"],"type":"string","description":"Define the placement, either before or after the anchor filter field.","artifactType":"Manifest"},"name":"Placement","freeText":false,"description":"Define the placement, either before or after the anchor filter field.","artifactType":"Manifest","required":true}},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":[],"schemaCreationForms":[],"state":0,"type":"Object","custom":false,"isTable":false,"schema":{"type":"object","properties":{"anchor":{"description":"The key of another filter field is to be used as a placement anchor.","type":"string","artifactType":"Manifest","oneOf":[]},"placement":{"$ref":"#/definitions/FilterFieldPlacement","description":"Define the placement, either before or after the anchor filter field.","artifactType":"Manifest"}},"additionalProperties":false,"required":["placement"],"description":"Defines the position of the filter field relative to another filter field.","artifactType":"Manifest"},"name":"position","order":0,"description":"Defines the position of the filter field relative to another filter field.","artifactType":"Manifest"}},"properties":{"label":{"state":0,"schema":{"description":"A static or i18n binding string.","i18nClassification":"COL: Custom filter field label","type":"string","artifactType":"Manifest"},"name":"Label","freeText":true,"description":"A static or i18n binding string.","i18nClassification":"COL: Custom filter field label","artifactType":"Manifest","required":true},"property":{"state":0,"schema":{"description":"The full path to the property to be filtered.","type":"string","artifactType":"Manifest"},"name":"Property","freeText":true,"description":"The full path to the property to be filtered.","artifactType":"Manifest","required":true},"template":{"state":0,"schema":{"description":"The path to the XML template containing the filter control.","type":"string","artifactType":"Manifest"},"name":"Template","freeText":true,"description":"The path to the XML template containing the filter control.","artifactType":"Manifest","required":true},"required":{"state":0,"schema":{"description":"Determines whether the filter field requires a value.","type":"boolean","artifactType":"Manifest"},"name":"Required","freeText":false,"description":"Determines whether the filter field requires a value.","artifactType":"Manifest"}},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":[],"schemaCreationForms":[],"state":0,"type":"Object","custom":false,"isTable":false,"schema":{"description":"Custom Filter Field","isViewNode":true,"type":"object","properties":{"label":{"description":"A static or i18n binding string.","i18nClassification":"COL: Custom filter field label","type":"string","artifactType":"Manifest"},"property":{"description":"The full path to the property to be filtered.","type":"string","artifactType":"Manifest"},"template":{"description":"The path to the XML template containing the filter control.","type":"string","artifactType":"Manifest"},"required":{"description":"Determines whether the filter field requires a value.","type":"boolean","artifactType":"Manifest"},"position":{"$ref":"#/definitions/CustomFilterFieldPosition","description":"Defines the position of the filter field relative to another filter field.","artifactType":"Manifest"}},"additionalProperties":false,"required":["label","property","template"]},"name":"selectionFields","order":0,"description":"Custom Filter Field","isViewNode":true}}},"properties":{"hideFilterBar":{"state":0,"schema":{"description":"Allows you to hide the filter bar.","artifactType":"Manifest","type":"boolean","descriptionSrcURL":"https://ui5.sap.com/sdk/#/topic/4bd7590569c74c61a0124c6e370030f6"},"name":"Hide Filter Bar","freeText":false,"description":"Allows you to hide the filter bar.","artifactType":"Manifest"}},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":[],"schemaCreationForms":[],"state":0,"type":"Object","custom":false,"isTable":false,"schema":{"description":"Filter Bar","isViewNode":true,"type":"object","properties":{"hideFilterBar":{"description":"Allows you to hide the filter bar.","artifactType":"Manifest","type":"boolean","descriptionSrcURL":"https://ui5.sap.com/sdk/#/topic/4bd7590569c74c61a0124c6e370030f6"},"selectionFields":{"isViewNode":true,"anyOf":[{"$ref":"#/definitions/SelectionFields"},{"$ref":"#/definitions/CompactFilters"}]},"visualFilters":{"$ref":"#/definitions/VisualFilters"},"initialLayout":{"$ref":"#/definitions/InitialLayoutType","description":"Allows you to specify the default filter mode on the initial load.","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/sdk/#/topic/33f3d807c10b47d9a8141692d2619dc2","hidden":true},"layout":{"$ref":"#/definitions/LayoutType","description":"Allows you to specify the layout of the filter bar.\\n- Compact: This setting shows filter fields in compact mode.\\n- CompactVisual: This setting shows filter fields in both compact and visual modes.","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/sdk/#/topic/33f3d807c10b47d9a8141692d2619dc2","hidden":true}},"additionalProperties":false,"propertyIndex":1},"name":"filterBar","order":1,"description":"Filter Bar","isViewNode":true,"value":{},"locations":[]},"table":{"path":["table"],"aggregations":{"columns":{"path":["table","columns"],"aggregations":{"DataField::TravelID":{"path":["table","columns","DataField::TravelID"],"aggregations":{},"properties":{"width":{"state":0,"schema":{"description":"A string type that represents CSS size values.\\nRefer to https://ui5.sap.com/api/sap.ui.core.CSSSize","type":"string","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/api/sap.ui.core.CSSSize"},"name":"Width","freeText":true,"description":"A string type that represents CSS size values.\\nRefer to https://ui5.sap.com/api/sap.ui.core.CSSSize","artifactType":"Manifest"},"availability":{"state":0,"schema":{"enum":["Adaptation","Default","Hidden"],"type":"string","description":"Defines where the column should be shown.\\nDefault: it will be shown by default in the table.\\nAdaptation: it will initially not be shown in the table but be available via end user adaptation.\\nHidden: the column is neither available in the table nor in adaptation.","artifactType":"Manifest"},"name":"Availability","freeText":false,"description":"Defines where the column should be shown.\\nDefault: it will be shown by default in the table.\\nAdaptation: it will initially not be shown in the table but be available via end user adaptation.\\nHidden: the column is neither available in the table nor in adaptation.","artifactType":"Manifest"},"widthIncludingColumnHeader":{"state":0,"schema":{"description":"By default, the column width is calculated based on the type of the content. You can include the column header in the width calculation using the widthIncludingColumnHeader setting in the manifest.json.","type":"boolean","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/#/topic/c0f6592a592e47f9bb6d09900de47412"},"name":"Width Including Column Header","freeText":false,"description":"By default, the column width is calculated based on the type of the content. You can include the column header in the width calculation using the widthIncludingColumnHeader setting in the manifest.json.","artifactType":"Manifest"}},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":[],"schemaCreationForms":[],"state":0,"type":"Object","custom":false,"isTable":false,"schema":{"description":"TravelID","isViewNode":true,"type":"object","properties":{"width":{"description":"A string type that represents CSS size values.\\nRefer to https://ui5.sap.com/api/sap.ui.core.CSSSize","type":"string","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/api/sap.ui.core.CSSSize"},"availability":{"$ref":"#/definitions/Availability","description":"Defines where the column should be shown.\\nDefault: it will be shown by default in the table.\\nAdaptation: it will initially not be shown in the table but be available via end user adaptation.\\nHidden: the column is neither available in the table nor in adaptation.","artifactType":"Manifest"},"widthIncludingColumnHeader":{"description":"By default, the column width is calculated based on the type of the content. You can include the column header in the width calculation using the widthIncludingColumnHeader setting in the manifest.json.","type":"boolean","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/#/topic/c0f6592a592e47f9bb6d09900de47412"}},"additionalProperties":false,"annotationType":"com.sap.vocabularies.UI.v1.DataField","annotationPath":"/com.sap.gateway.srvd.dmo.sd_travel_mdsk.v0001.TravelType/@com.sap.vocabularies.UI.v1.LineItem/0","propertyIndex":0,"dataType":"String","keys":[{"name":"Value","value":"TravelID"}]},"isViewNode":true,"actions":[],"sortableItem":"Readonly","name":"DataField::TravelID","order":0,"description":"TravelID","locations":[]},"DataField::AgencyID":{"path":["table","columns","DataField::AgencyID"],"aggregations":{},"properties":{"width":{"state":0,"schema":{"description":"A string type that represents CSS size values.\\nRefer to https://ui5.sap.com/api/sap.ui.core.CSSSize","type":"string","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/api/sap.ui.core.CSSSize"},"name":"Width","freeText":true,"description":"A string type that represents CSS size values.\\nRefer to https://ui5.sap.com/api/sap.ui.core.CSSSize","artifactType":"Manifest"},"availability":{"state":0,"schema":{"enum":["Adaptation","Default","Hidden"],"type":"string","description":"Defines where the column should be shown.\\nDefault: it will be shown by default in the table.\\nAdaptation: it will initially not be shown in the table but be available via end user adaptation.\\nHidden: the column is neither available in the table nor in adaptation.","artifactType":"Manifest"},"name":"Availability","freeText":false,"description":"Defines where the column should be shown.\\nDefault: it will be shown by default in the table.\\nAdaptation: it will initially not be shown in the table but be available via end user adaptation.\\nHidden: the column is neither available in the table nor in adaptation.","artifactType":"Manifest"},"widthIncludingColumnHeader":{"state":0,"schema":{"description":"By default, the column width is calculated based on the type of the content. You can include the column header in the width calculation using the widthIncludingColumnHeader setting in the manifest.json.","type":"boolean","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/#/topic/c0f6592a592e47f9bb6d09900de47412"},"name":"Width Including Column Header","freeText":false,"description":"By default, the column width is calculated based on the type of the content. You can include the column header in the width calculation using the widthIncludingColumnHeader setting in the manifest.json.","artifactType":"Manifest"}},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":[],"schemaCreationForms":[],"state":0,"type":"Object","custom":false,"isTable":false,"schema":{"description":"AgencyID","isViewNode":true,"type":"object","properties":{"width":{"description":"A string type that represents CSS size values.\\nRefer to https://ui5.sap.com/api/sap.ui.core.CSSSize","type":"string","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/api/sap.ui.core.CSSSize"},"availability":{"$ref":"#/definitions/Availability","description":"Defines where the column should be shown.\\nDefault: it will be shown by default in the table.\\nAdaptation: it will initially not be shown in the table but be available via end user adaptation.\\nHidden: the column is neither available in the table nor in adaptation.","artifactType":"Manifest"},"widthIncludingColumnHeader":{"description":"By default, the column width is calculated based on the type of the content. You can include the column header in the width calculation using the widthIncludingColumnHeader setting in the manifest.json.","type":"boolean","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/#/topic/c0f6592a592e47f9bb6d09900de47412"}},"additionalProperties":false,"annotationType":"com.sap.vocabularies.UI.v1.DataField","annotationPath":"/com.sap.gateway.srvd.dmo.sd_travel_mdsk.v0001.TravelType/@com.sap.vocabularies.UI.v1.LineItem/1","propertyIndex":1,"dataType":"String","keys":[{"name":"Value","value":"AgencyID"}]},"isViewNode":true,"actions":[],"sortableItem":"Readonly","name":"DataField::AgencyID","order":1,"description":"AgencyID","locations":[]},"DataField::CustomerID":{"path":["table","columns","DataField::CustomerID"],"aggregations":{},"properties":{"width":{"state":0,"schema":{"description":"A string type that represents CSS size values.\\nRefer to https://ui5.sap.com/api/sap.ui.core.CSSSize","type":"string","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/api/sap.ui.core.CSSSize"},"name":"Width","freeText":true,"description":"A string type that represents CSS size values.\\nRefer to https://ui5.sap.com/api/sap.ui.core.CSSSize","artifactType":"Manifest"},"availability":{"state":0,"schema":{"enum":["Adaptation","Default","Hidden"],"type":"string","description":"Defines where the column should be shown.\\nDefault: it will be shown by default in the table.\\nAdaptation: it will initially not be shown in the table but be available via end user adaptation.\\nHidden: the column is neither available in the table nor in adaptation.","artifactType":"Manifest"},"name":"Availability","freeText":false,"description":"Defines where the column should be shown.\\nDefault: it will be shown by default in the table.\\nAdaptation: it will initially not be shown in the table but be available via end user adaptation.\\nHidden: the column is neither available in the table nor in adaptation.","artifactType":"Manifest"},"widthIncludingColumnHeader":{"state":0,"schema":{"description":"By default, the column width is calculated based on the type of the content. You can include the column header in the width calculation using the widthIncludingColumnHeader setting in the manifest.json.","type":"boolean","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/#/topic/c0f6592a592e47f9bb6d09900de47412"},"name":"Width Including Column Header","freeText":false,"description":"By default, the column width is calculated based on the type of the content. You can include the column header in the width calculation using the widthIncludingColumnHeader setting in the manifest.json.","artifactType":"Manifest"}},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":[],"schemaCreationForms":[],"state":0,"type":"Object","custom":false,"isTable":false,"schema":{"description":"Kunden ID","isViewNode":true,"type":"object","properties":{"width":{"description":"A string type that represents CSS size values.\\nRefer to https://ui5.sap.com/api/sap.ui.core.CSSSize","type":"string","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/api/sap.ui.core.CSSSize"},"availability":{"$ref":"#/definitions/Availability","description":"Defines where the column should be shown.\\nDefault: it will be shown by default in the table.\\nAdaptation: it will initially not be shown in the table but be available via end user adaptation.\\nHidden: the column is neither available in the table nor in adaptation.","artifactType":"Manifest"},"widthIncludingColumnHeader":{"description":"By default, the column width is calculated based on the type of the content. You can include the column header in the width calculation using the widthIncludingColumnHeader setting in the manifest.json.","type":"boolean","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/#/topic/c0f6592a592e47f9bb6d09900de47412"}},"additionalProperties":false,"annotationType":"com.sap.vocabularies.UI.v1.DataField","annotationPath":"/com.sap.gateway.srvd.dmo.sd_travel_mdsk.v0001.TravelType/@com.sap.vocabularies.UI.v1.LineItem/2","propertyIndex":2,"dataType":"String","keys":[{"name":"Value","value":"CustomerID"}]},"isViewNode":true,"actions":[],"sortableItem":"Readonly","name":"DataField::CustomerID","order":2,"description":"Kunden ID","locations":[]},"DataField::BeginDate":{"path":["table","columns","DataField::BeginDate"],"aggregations":{},"properties":{"width":{"state":0,"schema":{"description":"A string type that represents CSS size values.\\nRefer to https://ui5.sap.com/api/sap.ui.core.CSSSize","type":"string","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/api/sap.ui.core.CSSSize"},"name":"Width","freeText":true,"description":"A string type that represents CSS size values.\\nRefer to https://ui5.sap.com/api/sap.ui.core.CSSSize","artifactType":"Manifest"},"availability":{"state":0,"schema":{"enum":["Adaptation","Default","Hidden"],"type":"string","description":"Defines where the column should be shown.\\nDefault: it will be shown by default in the table.\\nAdaptation: it will initially not be shown in the table but be available via end user adaptation.\\nHidden: the column is neither available in the table nor in adaptation.","artifactType":"Manifest"},"name":"Availability","freeText":false,"description":"Defines where the column should be shown.\\nDefault: it will be shown by default in the table.\\nAdaptation: it will initially not be shown in the table but be available via end user adaptation.\\nHidden: the column is neither available in the table nor in adaptation.","artifactType":"Manifest"},"widthIncludingColumnHeader":{"state":0,"schema":{"description":"By default, the column width is calculated based on the type of the content. You can include the column header in the width calculation using the widthIncludingColumnHeader setting in the manifest.json.","type":"boolean","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/#/topic/c0f6592a592e47f9bb6d09900de47412"},"name":"Width Including Column Header","freeText":false,"description":"By default, the column width is calculated based on the type of the content. You can include the column header in the width calculation using the widthIncludingColumnHeader setting in the manifest.json.","artifactType":"Manifest"}},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":[],"schemaCreationForms":[],"state":0,"type":"Object","custom":false,"isTable":false,"schema":{"description":"BeginDate","isViewNode":true,"type":"object","properties":{"width":{"description":"A string type that represents CSS size values.\\nRefer to https://ui5.sap.com/api/sap.ui.core.CSSSize","type":"string","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/api/sap.ui.core.CSSSize"},"availability":{"$ref":"#/definitions/Availability","description":"Defines where the column should be shown.\\nDefault: it will be shown by default in the table.\\nAdaptation: it will initially not be shown in the table but be available via end user adaptation.\\nHidden: the column is neither available in the table nor in adaptation.","artifactType":"Manifest"},"widthIncludingColumnHeader":{"description":"By default, the column width is calculated based on the type of the content. You can include the column header in the width calculation using the widthIncludingColumnHeader setting in the manifest.json.","type":"boolean","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/#/topic/c0f6592a592e47f9bb6d09900de47412"}},"additionalProperties":false,"annotationType":"com.sap.vocabularies.UI.v1.DataField","annotationPath":"/com.sap.gateway.srvd.dmo.sd_travel_mdsk.v0001.TravelType/@com.sap.vocabularies.UI.v1.LineItem/3","propertyIndex":3,"dataType":"Date","keys":[{"name":"Value","value":"BeginDate"}]},"isViewNode":true,"actions":[],"sortableItem":"Readonly","name":"DataField::BeginDate","order":3,"description":"BeginDate","locations":[]},"DataField::EndDate":{"path":["table","columns","DataField::EndDate"],"aggregations":{},"properties":{"width":{"state":0,"schema":{"description":"A string type that represents CSS size values.\\nRefer to https://ui5.sap.com/api/sap.ui.core.CSSSize","type":"string","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/api/sap.ui.core.CSSSize"},"name":"Width","freeText":true,"description":"A string type that represents CSS size values.\\nRefer to https://ui5.sap.com/api/sap.ui.core.CSSSize","artifactType":"Manifest"},"availability":{"state":0,"schema":{"enum":["Adaptation","Default","Hidden"],"type":"string","description":"Defines where the column should be shown.\\nDefault: it will be shown by default in the table.\\nAdaptation: it will initially not be shown in the table but be available via end user adaptation.\\nHidden: the column is neither available in the table nor in adaptation.","artifactType":"Manifest"},"name":"Availability","freeText":false,"description":"Defines where the column should be shown.\\nDefault: it will be shown by default in the table.\\nAdaptation: it will initially not be shown in the table but be available via end user adaptation.\\nHidden: the column is neither available in the table nor in adaptation.","artifactType":"Manifest"},"widthIncludingColumnHeader":{"state":0,"schema":{"description":"By default, the column width is calculated based on the type of the content. You can include the column header in the width calculation using the widthIncludingColumnHeader setting in the manifest.json.","type":"boolean","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/#/topic/c0f6592a592e47f9bb6d09900de47412"},"name":"Width Including Column Header","freeText":false,"description":"By default, the column width is calculated based on the type of the content. You can include the column header in the width calculation using the widthIncludingColumnHeader setting in the manifest.json.","artifactType":"Manifest"}},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":[],"schemaCreationForms":[],"state":0,"type":"Object","custom":false,"isTable":false,"schema":{"description":"EndDate","isViewNode":true,"type":"object","properties":{"width":{"description":"A string type that represents CSS size values.\\nRefer to https://ui5.sap.com/api/sap.ui.core.CSSSize","type":"string","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/api/sap.ui.core.CSSSize"},"availability":{"$ref":"#/definitions/Availability","description":"Defines where the column should be shown.\\nDefault: it will be shown by default in the table.\\nAdaptation: it will initially not be shown in the table but be available via end user adaptation.\\nHidden: the column is neither available in the table nor in adaptation.","artifactType":"Manifest"},"widthIncludingColumnHeader":{"description":"By default, the column width is calculated based on the type of the content. You can include the column header in the width calculation using the widthIncludingColumnHeader setting in the manifest.json.","type":"boolean","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/#/topic/c0f6592a592e47f9bb6d09900de47412"}},"additionalProperties":false,"annotationType":"com.sap.vocabularies.UI.v1.DataField","annotationPath":"/com.sap.gateway.srvd.dmo.sd_travel_mdsk.v0001.TravelType/@com.sap.vocabularies.UI.v1.LineItem/4","propertyIndex":4,"dataType":"Date","keys":[{"name":"Value","value":"EndDate"}]},"isViewNode":true,"actions":[],"sortableItem":"Readonly","name":"DataField::EndDate","order":4,"description":"EndDate","locations":[]},"DataField::TotalPrice":{"path":["table","columns","DataField::TotalPrice"],"aggregations":{},"properties":{"width":{"state":0,"schema":{"description":"A string type that represents CSS size values.\\nRefer to https://ui5.sap.com/api/sap.ui.core.CSSSize","type":"string","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/api/sap.ui.core.CSSSize"},"name":"Width","freeText":true,"description":"A string type that represents CSS size values.\\nRefer to https://ui5.sap.com/api/sap.ui.core.CSSSize","artifactType":"Manifest"},"availability":{"state":0,"schema":{"enum":["Adaptation","Default","Hidden"],"type":"string","description":"Defines where the column should be shown.\\nDefault: it will be shown by default in the table.\\nAdaptation: it will initially not be shown in the table but be available via end user adaptation.\\nHidden: the column is neither available in the table nor in adaptation.","artifactType":"Manifest"},"name":"Availability","freeText":false,"description":"Defines where the column should be shown.\\nDefault: it will be shown by default in the table.\\nAdaptation: it will initially not be shown in the table but be available via end user adaptation.\\nHidden: the column is neither available in the table nor in adaptation.","artifactType":"Manifest"},"widthIncludingColumnHeader":{"state":0,"schema":{"description":"By default, the column width is calculated based on the type of the content. You can include the column header in the width calculation using the widthIncludingColumnHeader setting in the manifest.json.","type":"boolean","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/#/topic/c0f6592a592e47f9bb6d09900de47412"},"name":"Width Including Column Header","freeText":false,"description":"By default, the column width is calculated based on the type of the content. You can include the column header in the width calculation using the widthIncludingColumnHeader setting in the manifest.json.","artifactType":"Manifest"}},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":[],"schemaCreationForms":[],"state":0,"type":"Object","custom":false,"isTable":false,"schema":{"description":"TotalPrice","isViewNode":true,"type":"object","properties":{"width":{"description":"A string type that represents CSS size values.\\nRefer to https://ui5.sap.com/api/sap.ui.core.CSSSize","type":"string","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/api/sap.ui.core.CSSSize"},"availability":{"$ref":"#/definitions/Availability","description":"Defines where the column should be shown.\\nDefault: it will be shown by default in the table.\\nAdaptation: it will initially not be shown in the table but be available via end user adaptation.\\nHidden: the column is neither available in the table nor in adaptation.","artifactType":"Manifest"},"widthIncludingColumnHeader":{"description":"By default, the column width is calculated based on the type of the content. You can include the column header in the width calculation using the widthIncludingColumnHeader setting in the manifest.json.","type":"boolean","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/#/topic/c0f6592a592e47f9bb6d09900de47412"}},"additionalProperties":false,"annotationType":"com.sap.vocabularies.UI.v1.DataField","annotationPath":"/com.sap.gateway.srvd.dmo.sd_travel_mdsk.v0001.TravelType/@com.sap.vocabularies.UI.v1.LineItem/5","propertyIndex":5,"dataType":"Decimal","keys":[{"name":"Value","value":"TotalPrice"}]},"isViewNode":true,"actions":[],"sortableItem":"Readonly","name":"DataField::TotalPrice","order":5,"description":"TotalPrice","locations":[]},"DataField::Memo":{"path":["table","columns","DataField::Memo"],"aggregations":{},"properties":{"width":{"state":0,"schema":{"description":"A string type that represents CSS size values.\\nRefer to https://ui5.sap.com/api/sap.ui.core.CSSSize","type":"string","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/api/sap.ui.core.CSSSize"},"name":"Width","freeText":true,"description":"A string type that represents CSS size values.\\nRefer to https://ui5.sap.com/api/sap.ui.core.CSSSize","artifactType":"Manifest"},"availability":{"state":0,"schema":{"enum":["Adaptation","Default","Hidden"],"type":"string","description":"Defines where the column should be shown.\\nDefault: it will be shown by default in the table.\\nAdaptation: it will initially not be shown in the table but be available via end user adaptation.\\nHidden: the column is neither available in the table nor in adaptation.","artifactType":"Manifest"},"name":"Availability","freeText":false,"description":"Defines where the column should be shown.\\nDefault: it will be shown by default in the table.\\nAdaptation: it will initially not be shown in the table but be available via end user adaptation.\\nHidden: the column is neither available in the table nor in adaptation.","artifactType":"Manifest"},"widthIncludingColumnHeader":{"state":0,"schema":{"description":"By default, the column width is calculated based on the type of the content. You can include the column header in the width calculation using the widthIncludingColumnHeader setting in the manifest.json.","type":"boolean","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/#/topic/c0f6592a592e47f9bb6d09900de47412"},"name":"Width Including Column Header","freeText":false,"description":"By default, the column width is calculated based on the type of the content. You can include the column header in the width calculation using the widthIncludingColumnHeader setting in the manifest.json.","artifactType":"Manifest"}},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":[],"schemaCreationForms":[],"state":0,"type":"Object","custom":false,"isTable":false,"schema":{"description":"Memo","isViewNode":true,"type":"object","properties":{"width":{"description":"A string type that represents CSS size values.\\nRefer to https://ui5.sap.com/api/sap.ui.core.CSSSize","type":"string","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/api/sap.ui.core.CSSSize"},"availability":{"$ref":"#/definitions/Availability","description":"Defines where the column should be shown.\\nDefault: it will be shown by default in the table.\\nAdaptation: it will initially not be shown in the table but be available via end user adaptation.\\nHidden: the column is neither available in the table nor in adaptation.","artifactType":"Manifest"},"widthIncludingColumnHeader":{"description":"By default, the column width is calculated based on the type of the content. You can include the column header in the width calculation using the widthIncludingColumnHeader setting in the manifest.json.","type":"boolean","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/#/topic/c0f6592a592e47f9bb6d09900de47412"}},"additionalProperties":false,"annotationType":"com.sap.vocabularies.UI.v1.DataField","annotationPath":"/com.sap.gateway.srvd.dmo.sd_travel_mdsk.v0001.TravelType/@com.sap.vocabularies.UI.v1.LineItem/6","propertyIndex":6,"dataType":"String","keys":[{"name":"Value","value":"Memo"}]},"isViewNode":true,"actions":[],"sortableItem":"Readonly","name":"DataField::Memo","order":6,"description":"Memo","locations":[]},"DataField::Status":{"path":["table","columns","DataField::Status"],"aggregations":{},"properties":{"width":{"state":0,"schema":{"description":"A string type that represents CSS size values.\\nRefer to https://ui5.sap.com/api/sap.ui.core.CSSSize","type":"string","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/api/sap.ui.core.CSSSize"},"name":"Width","freeText":true,"description":"A string type that represents CSS size values.\\nRefer to https://ui5.sap.com/api/sap.ui.core.CSSSize","artifactType":"Manifest"},"availability":{"state":0,"schema":{"enum":["Adaptation","Default","Hidden"],"type":"string","description":"Defines where the column should be shown.\\nDefault: it will be shown by default in the table.\\nAdaptation: it will initially not be shown in the table but be available via end user adaptation.\\nHidden: the column is neither available in the table nor in adaptation.","artifactType":"Manifest"},"name":"Availability","freeText":false,"description":"Defines where the column should be shown.\\nDefault: it will be shown by default in the table.\\nAdaptation: it will initially not be shown in the table but be available via end user adaptation.\\nHidden: the column is neither available in the table nor in adaptation.","artifactType":"Manifest"},"widthIncludingColumnHeader":{"state":0,"schema":{"description":"By default, the column width is calculated based on the type of the content. You can include the column header in the width calculation using the widthIncludingColumnHeader setting in the manifest.json.","type":"boolean","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/#/topic/c0f6592a592e47f9bb6d09900de47412"},"name":"Width Including Column Header","freeText":false,"description":"By default, the column width is calculated based on the type of the content. You can include the column header in the width calculation using the widthIncludingColumnHeader setting in the manifest.json.","artifactType":"Manifest"}},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":[],"schemaCreationForms":[],"state":0,"type":"Object","custom":false,"isTable":false,"schema":{"description":"Status","isViewNode":true,"type":"object","properties":{"width":{"description":"A string type that represents CSS size values.\\nRefer to https://ui5.sap.com/api/sap.ui.core.CSSSize","type":"string","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/api/sap.ui.core.CSSSize"},"availability":{"$ref":"#/definitions/Availability","description":"Defines where the column should be shown.\\nDefault: it will be shown by default in the table.\\nAdaptation: it will initially not be shown in the table but be available via end user adaptation.\\nHidden: the column is neither available in the table nor in adaptation.","artifactType":"Manifest"},"widthIncludingColumnHeader":{"description":"By default, the column width is calculated based on the type of the content. You can include the column header in the width calculation using the widthIncludingColumnHeader setting in the manifest.json.","type":"boolean","artifactType":"Manifest","descriptionSrcURL":"https://ui5.sap.com/#/topic/c0f6592a592e47f9bb6d09900de47412"}},"additionalProperties":false,"annotationType":"com.sap.vocabularies.UI.v1.DataField","annotationPath":"/com.sap.gateway.srvd.dmo.sd_travel_mdsk.v0001.TravelType/@com.sap.vocabularies.UI.v1.LineItem/7","propertyIndex":7,"dataType":"String","keys":[{"name":"Value","value":"Status"}]},"isViewNode":true,"actions":[],"sortableItem":"Readonly","name":"DataField::Status","order":7,"description":"Status","locations":[]}},"properties":{},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":["Basic","Rating","Chart","Progress","NativeAction","Contact","NativeNavigation"],"schemaCreationForms":[{"name":"CustomColumnV4","kind":"schema","title":"PAGE_EDITOR_OUTLINE_ADD_CUSTOM_COLUMNS_TITLE","disabled":false}],"state":0,"type":"Object","custom":false,"isTable":false,"schema":{"type":"object","properties":{"DataField::TravelID":{"description":"TravelID","$ref":"#/definitions/TableColumn","annotationType":"com.sap.vocabularies.UI.v1.DataField","annotationPath":"/com.sap.gateway.srvd.dmo.sd_travel_mdsk.v0001.TravelType/@com.sap.vocabularies.UI.v1.LineItem/0","propertyIndex":0,"dataType":"String","keys":[{"name":"Value","value":"TravelID"}]},"DataField::AgencyID":{"description":"AgencyID","$ref":"#/definitions/TableColumn","annotationType":"com.sap.vocabularies.UI.v1.DataField","annotationPath":"/com.sap.gateway.srvd.dmo.sd_travel_mdsk.v0001.TravelType/@com.sap.vocabularies.UI.v1.LineItem/1","propertyIndex":1,"dataType":"String","keys":[{"name":"Value","value":"AgencyID"}]},"DataField::CustomerID":{"description":"Kunden ID","$ref":"#/definitions/TableColumn","annotationType":"com.sap.vocabularies.UI.v1.DataField","annotationPath":"/com.sap.gateway.srvd.dmo.sd_travel_mdsk.v0001.TravelType/@com.sap.vocabularies.UI.v1.LineItem/2","propertyIndex":2,"dataType":"String","keys":[{"name":"Value","value":"CustomerID"}]},"DataField::BeginDate":{"description":"BeginDate","$ref":"#/definitions/TableColumn","annotationType":"com.sap.vocabularies.UI.v1.DataField","annotationPath":"/com.sap.gateway.srvd.dmo.sd_travel_mdsk.v0001.TravelType/@com.sap.vocabularies.UI.v1.LineItem/3","propertyIndex":3,"dataType":"Date","keys":[{"name":"Value","value":"BeginDate"}]},"DataField::EndDate":{"description":"EndDate","$ref":"#/definitions/TableColumn","annotationType":"com.sap.vocabularies.UI.v1.DataField","annotationPath":"/com.sap.gateway.srvd.dmo.sd_travel_mdsk.v0001.TravelType/@com.sap.vocabularies.UI.v1.LineItem/4","propertyIndex":4,"dataType":"Date","keys":[{"name":"Value","value":"EndDate"}]},"DataField::TotalPrice":{"description":"TotalPrice","$ref":"#/definitions/TableColumn","annotationType":"com.sap.vocabularies.UI.v1.DataField","annotationPath":"/com.sap.gateway.srvd.dmo.sd_travel_mdsk.v0001.TravelType/@com.sap.vocabularies.UI.v1.LineItem/5","propertyIndex":5,"dataType":"Decimal","keys":[{"name":"Value","value":"TotalPrice"}]},"DataField::Memo":{"description":"Memo","$ref":"#/definitions/TableColumn","annotationType":"com.sap.vocabularies.UI.v1.DataField","annotationPath":"/com.sap.gateway.srvd.dmo.sd_travel_mdsk.v0001.TravelType/@com.sap.vocabularies.UI.v1.LineItem/6","propertyIndex":6,"dataType":"String","keys":[{"name":"Value","value":"Memo"}]},"DataField::Status":{"description":"Status","$ref":"#/definitions/TableColumn","annotationType":"com.sap.vocabularies.UI.v1.DataField","annotationPath":"/com.sap.gateway.srvd.dmo.sd_travel_mdsk.v0001.TravelType/@com.sap.vocabularies.UI.v1.LineItem/7","propertyIndex":7,"dataType":"String","keys":[{"name":"Value","value":"Status"}]}},"description":"Columns","isViewNode":true,"additionalProperties":{"$ref":"#/definitions/TableCustomColumn"}},"customColumns":[],"columnKeys":[],"sortableCollection":"actions","isV4":true,"sortableList":true,"i18nKey":"COLUMNS","name":"columns","order":3,"description":"Columns","isViewNode":true,"additionalProperties":{"path":[],"aggregations":{"columns":{"path":["table","columns","columns"],"aggregations":{"position":{"path":["table","columns","columns","position"],"aggregations":{},"properties":{"anchor":{"state":0,"schema":{"description":"The key of another column to be used as placement anchor.","type":"string","artifactType":"Manifest","enum":["DataField::TravelID","DataField::AgencyID","DataField::CustomerID","DataField::BeginDate","DataField::EndDate","DataField::TotalPrice","DataField::Memo","DataField::Status"]},"name":"Anchor","freeText":false,"description":"The key of another column to be used as placement anchor.","artifactType":"Manifest"},"placement":{"state":0,"schema":{"enum":["After","Before"],"type":"string","description":"Define the placement, either before or after the anchor column.","artifactType":"Manifest"},"name":"Placement","freeText":false,"description":"Define the placement, either before or after the anchor column.","artifactType":"Manifest","required":true}},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":[],"schemaCreationForms":[],"state":0,"type":"Object","custom":false,"isTable":false,"schema":{"type":"object","properties":{"anchor":{"description":"The key of another column to be used as placement anchor.","type":"string","artifactType":"Manifest","enum":["DataField::TravelID","DataField::AgencyID","DataField::CustomerID","DataField::BeginDate","DataField::EndDate","DataField::TotalPrice","DataField::Memo","DataField::Status"]},"placement":{"$ref":"#/definitions/Placement","description":"Define the placement, either before or after the anchor column.","artifactType":"Manifest"}},"additionalProperties":false,"required":["placement"],"description":"Defines the position of the column relative to other columns.","artifactType":"Manifest"},"name":"position","order":0,"description":"Defines the position of the column relative to other columns.","artifactType":"Manifest"},"properties":{"path":["table","columns","columns","properties"],"aggregations":{},"properties":{},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":[],"schemaCreationForms":[{"name":"Generic","kind":"schema","title":"PAGE_EDITOR_OUTLINE_ADD_GENERIC_TITLE","disabled":false}],"state":0,"type":"Array","custom":false,"isTable":false,"schema":{"description":"If provided and sorting for the table is enabled, the custom column header can be clicked.\\nOnce clicked, a list of properties that can be sorted by are displayed.","artifactType":"Manifest","type":"array","items":{"type":"string","enum":["TravelID","AgencyID","CustomerID","BeginDate","EndDate","TotalPrice","Memo","Status"]}},"name":"properties","order":1,"isAtomic":true,"formSchema":{"path":[],"aggregations":{},"properties":{"":{"state":0,"schema":{"type":"string","enum":["TravelID","AgencyID","CustomerID","BeginDate","EndDate","TotalPrice","Memo","Status"]},"name":"","freeText":false}},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":["AnalyticalChart"],"schemaCreationForms":[],"state":0,"type":"Object","custom":false,"isTable":false,"name":"root"},"description":"If provided and sorting for the table is enabled, the custom column header can be clicked.\\nOnce clicked, a list of properties that can be sorted by are displayed.","artifactType":"Manifest"}},"properties":{"header":{"state":0,"schema":{"description":"The header is shown on the table as header, as well as in the add/remove dialog.","i18nClassification":"COL: Custom column header text","artifactType":"Manifest","type":"string"},"name":"Header","freeText":true,"description":"The header is shown on the table as header, as well as in the add/remove dialog.","i18nClassification":"COL: Custom column header text","artifactType":"Manifest","required":true},"width":{"state":0,"schema":{"description":"A string type that represents CSS size values.\\nRefer to https://ui5.sap.com/api/sap.ui.core.CSSSize","artifactType":"Manifest","type":"string","descriptionSrcURL":"https://ui5.sap.com/api/sap.ui.core.CSSSize"},"name":"Width","freeText":true,"description":"A string type that represents CSS size values.\\nRefer to https://ui5.sap.com/api/sap.ui.core.CSSSize","artifactType":"Manifest"},"template":{"state":0,"schema":{"description":"Defines a target fragment.","artifactType":"Manifest","type":"string"},"name":"Template","freeText":true,"description":"Defines a target fragment.","artifactType":"Manifest","required":true},"horizontalAlign":{"state":0,"schema":{"enum":["Begin","Center","End"],"type":"string","description":"Aligns the header as well as the content horizontally.","artifactType":"Manifest"},"name":"Horizontal Align","freeText":false,"description":"Aligns the header as well as the content horizontally.","artifactType":"Manifest"},"availability":{"state":0,"schema":{"enum":["Adaptation","Default","Hidden"],"type":"string","description":"Defines where the column should be shown.\\n- Default: it will be shown by default in the table.\\n- Adaptation: it will initially not be shown in the table but be available via end user adaptation\\n- Hidden: the column is neither available in the table nor in adaptation","artifactType":"Manifest"},"name":"Availability","freeText":false,"description":"Defines where the column should be shown.\\n- Default: it will be shown by default in the table.\\n- Adaptation: it will initially not be shown in the table but be available via end user adaptation\\n- Hidden: the column is neither available in the table nor in adaptation","artifactType":"Manifest"}},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":[],"schemaCreationForms":[],"state":0,"type":"Object","custom":false,"isTable":false,"schema":{"description":"Custom Column","isViewNode":true,"type":"object","properties":{"position":{"$ref":"#/definitions/Position","description":"Defines the position of the column relative to other columns.","artifactType":"Manifest"},"header":{"description":"The header is shown on the table as header, as well as in the add/remove dialog.","i18nClassification":"COL: Custom column header text","artifactType":"Manifest","type":"string"},"width":{"description":"A string type that represents CSS size values.\\nRefer to https://ui5.sap.com/api/sap.ui.core.CSSSize","artifactType":"Manifest","type":"string","descriptionSrcURL":"https://ui5.sap.com/api/sap.ui.core.CSSSize"},"template":{"description":"Defines a target fragment.","artifactType":"Manifest","type":"string"},"horizontalAlign":{"$ref":"#/definitions/HorizontalAlign","description":"Aligns the header as well as the content horizontally.","artifactType":"Manifest"},"availability":{"$ref":"#/definitions/Availability","description":"Defines where the column should be shown.\\n- Default: it will be shown by default in the table.\\n- Adaptation: it will initially not be shown in the table but be available via end user adaptation\\n- Hidden: the column is neither available in the table nor in adaptation","artifactType":"Manifest"},"properties":{"description":"If provided and sorting for the table is enabled, the custom column header can be clicked.\\nOnce clicked, a list of properties that can be sorted by are displayed.","artifactType":"Manifest","type":"array","items":{"type":"string","enum":["TravelID","AgencyID","CustomerID","BeginDate","EndDate","TotalPrice","Memo","Status"]}}},"additionalProperties":false,"required":["header","template"]},"name":"columns","order":0,"description":"Custom Column","isViewNode":true}},"properties":{},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":["AnalyticalChart"],"schemaCreationForms":[],"state":0,"type":"Object","custom":false,"isTable":false,"name":"root"},"locations":[],"tableColumnExtensionType":"ResponsiveTableColumnsExtension","formSchema":{"path":["table","columns","columns"],"aggregations":{"position":{"path":["table","columns","columns","position"],"aggregations":{},"properties":{"anchor":{"state":0,"schema":{"description":"The key of another column to be used as placement anchor.","type":"string","artifactType":"Manifest","enum":["DataField::TravelID","DataField::AgencyID","DataField::CustomerID","DataField::BeginDate","DataField::EndDate","DataField::TotalPrice","DataField::Memo","DataField::Status"]},"name":"Anchor","freeText":false,"description":"The key of another column to be used as placement anchor.","artifactType":"Manifest"},"placement":{"state":0,"schema":{"enum":["After","Before"],"type":"string","description":"Define the placement, either before or after the anchor column.","artifactType":"Manifest"},"name":"Placement","freeText":false,"description":"Define the placement, either before or after the anchor column.","artifactType":"Manifest","required":true}},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":[],"schemaCreationForms":[],"state":0,"type":"Object","custom":false,"isTable":false,"schema":{"type":"object","properties":{"anchor":{"description":"The key of another column to be used as placement anchor.","type":"string","artifactType":"Manifest","enum":["DataField::TravelID","DataField::AgencyID","DataField::CustomerID","DataField::BeginDate","DataField::EndDate","DataField::TotalPrice","DataField::Memo","DataField::Status"]},"placement":{"$ref":"#/definitions/Placement","description":"Define the placement, either before or after the anchor column.","artifactType":"Manifest"}},"additionalProperties":false,"required":["placement"],"description":"Defines the position of the column relative to other columns.","artifactType":"Manifest"},"name":"position","order":0,"description":"Defines the position of the column relative to other columns.","artifactType":"Manifest"},"properties":{"path":["table","columns","columns","properties"],"aggregations":{},"properties":{},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":[],"schemaCreationForms":[{"name":"Generic","kind":"schema","title":"PAGE_EDITOR_OUTLINE_ADD_GENERIC_TITLE","disabled":false}],"state":0,"type":"Array","custom":false,"isTable":false,"schema":{"description":"If provided and sorting for the table is enabled, the custom column header can be clicked.\\nOnce clicked, a list of properties that can be sorted by are displayed.","artifactType":"Manifest","type":"array","items":{"type":"string","enum":["TravelID","AgencyID","CustomerID","BeginDate","EndDate","TotalPrice","Memo","Status"]}},"name":"properties","order":1,"isAtomic":true,"formSchema":{"path":[],"aggregations":{},"properties":{"":{"state":0,"schema":{"type":"string","enum":["TravelID","AgencyID","CustomerID","BeginDate","EndDate","TotalPrice","Memo","Status"]},"name":"","freeText":false}},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":["AnalyticalChart"],"schemaCreationForms":[],"state":0,"type":"Object","custom":false,"isTable":false,"name":"root"},"description":"If provided and sorting for the table is enabled, the custom column header can be clicked.\\nOnce clicked, a list of properties that can be sorted by are displayed.","artifactType":"Manifest"}},"properties":{"header":{"state":0,"schema":{"description":"The header is shown on the table as header, as well as in the add/remove dialog.","i18nClassification":"COL: Custom column header text","artifactType":"Manifest","type":"string"},"name":"Header","freeText":true,"description":"The header is shown on the table as header, as well as in the add/remove dialog.","i18nClassification":"COL: Custom column header text","artifactType":"Manifest","required":true},"width":{"state":0,"schema":{"description":"A string type that represents CSS size values.\\nRefer to https://ui5.sap.com/api/sap.ui.core.CSSSize","artifactType":"Manifest","type":"string","descriptionSrcURL":"https://ui5.sap.com/api/sap.ui.core.CSSSize"},"name":"Width","freeText":true,"description":"A string type that represents CSS size values.\\nRefer to https://ui5.sap.com/api/sap.ui.core.CSSSize","artifactType":"Manifest"},"template":{"state":0,"schema":{"description":"Defines a target fragment.","artifactType":"Manifest","type":"string"},"name":"Template","freeText":true,"description":"Defines a target fragment.","artifactType":"Manifest","required":true},"horizontalAlign":{"state":0,"schema":{"enum":["Begin","Center","End"],"type":"string","description":"Aligns the header as well as the content horizontally.","artifactType":"Manifest"},"name":"Horizontal Align","freeText":false,"description":"Aligns the header as well as the content horizontally.","artifactType":"Manifest"},"availability":{"state":0,"schema":{"enum":["Adaptation","Default","Hidden"],"type":"string","description":"Defines where the column should be shown.\\n- Default: it will be shown by default in the table.\\n- Adaptation: it will initially not be shown in the table but be available via end user adaptation\\n- Hidden: the column is neither available in the table nor in adaptation","artifactType":"Manifest"},"name":"Availability","freeText":false,"description":"Defines where the column should be shown.\\n- Default: it will be shown by default in the table.\\n- Adaptation: it will initially not be shown in the table but be available via end user adaptation\\n- Hidden: the column is neither available in the table nor in adaptation","artifactType":"Manifest"}},"variants":[],"annotationCreationForms":[],"allowedAnnotationCreationForms":[],"schemaCreationForms":[],"state":0,"type":"Object","custom":false,"isTable":false,"schema":{"description":"Custom Column","isViewNode":true,"type":"object","properties":{"position":{"$ref":"#/definitions/Position","description":"Defines the position of the column relative to other columns.","artifactType":"Manifest"},"header":{"description":"The header is shown on the table as header, as well as in the add/remove dialog.","i18nClassification":"COL: Custom column header text","artifactType":"Manifest","type":"string"},"width":{"description":"A string type that represents CSS size values.\\nRefer to https://ui5.sap.com/api/sap.ui.core.CSSSize","artifactType":"Manifest","type":"string","descriptionSrcURL":"https://ui5.sap.com/api/sap.ui.core.CSSSize"},"template":{"description":"Defines a target fragment.","artifactType":"Manifest","type":"string"},"horizontalAlign":{"$ref":"#/definitions/HorizontalAlign","description":"Aligns the header as well as the content horizontally.","artifactType":"Manifest"},"availability":{"$ref":"#/definitions/Availability","description":"Defines where the column should be shown.\\n- Default: it will be shown by default in the table.\\n- Adaptation: it will initially not be shown in the table but be available via end user adaptation\\n- Hidden: the column is neither available in the table nor in adaptation","artifactType":"Manifest"},"properties":{"description":"If provided and sorting for the table is enabled, the custom column header can be clicked.\\nOnce clicked, a list of properties that can be sorted by are displayed.","artifactType":"Manifest","type":"array","items":{"type":"string","enum":["TravelID","AgencyID","CustomerID","BeginDate","EndDate","TotalPrice","Memo","Status"]}}},"additionalProperties":false,"required":["header","template"]},"name":"columns","order":0,"description":"Custom Column","isViewNode":true}}}}}}}}}}}' -export const V4_WITH_SUB_OBJECT_PAGE = '{"applicationModel":{"pages":{"TravelList":{"contextPath":"/Travel","template":"sap.fe.templates.ListReport","entitySet":"Travel","pageType":"ListReport","model":{"root":{"aggregations":{}}},"navigation":{"Travel":{"route":"TravelObjectPage"}}},"TravelObjectPage":{"template":"sap.fe.templates.ObjectPage","model":{"root":{"aggregations":{"header":{"aggregations":{"sections":{"aggregations":{}}}}}}},"entitySet":"Travel","contextPath":"/Travel","pageType":"ObjectPage","navigation":{"_Booking":{"route":"BookingObjectPage"}}},"BookingObjectPage":{"template":"sap.fe.templates.ObjectPage","model":{"root":{"aggregations":{"header":{"aggregations":{"sections":{"aggregations":{"FlightDateDP":{"name":"FlightDateDP","properties":{"stashed":{"freeText":false}},"schema":{"type":"object","keys":[{"name":"Target","value":"com.sap.vocabularies.UI.v1.DataPoint#FlightDate"},{"name":"ID","value":"DataPoint::FlightDate"},{"name":"Value","value":"Flight Date"}]},"aggregations":{}},"BookingDateDP":{"name":"BookingDateDP","properties":{"stashed":{"freeText":false}},"schema":{"type":"object","keys":[{"name":"Target","value":"com.sap.vocabularies.UI.v1.DataPoint#BookingDate"},{"name":"ID","value":"DataPoint::BookingDate"},{"name":"Value","value":"Booking Date"}]},"aggregations":{}},"FieldGroupNames":{"name":"FieldGroupNames","properties":{"stashed":{"freeText":false}},"schema":{"type":"object","keys":[{"name":"Target","value":"com.sap.vocabularies.UI.v1.FieldGroup#Names"},{"name":"ID","value":"FieldGroup::Names"},{"name":"Value","value":"Names"}]},"aggregations":{"form":{"schema":{"keys":[{"name":"Target","value":"com.sap.vocabularies.UI.v1.FieldGroup#Names"}]},"aggregations":{"fields":{"aggregations":{"AirlineNameField":{"name":"AirlineNameField","properties":{},"schema":{"keys":[{"name":"Target","value":"#/Names/AirlineName"},{"name":"Value","value":"AirlineName"}]}},"CustomerNameField":{"name":"CustomerNameField","properties":{},"schema":{"keys":[{"name":"Target","value":"#/Names/CustomerName"},{"name":"Value","value":"CustomerName"}]}},"DataFieldForAnnotation::carrier::Contact":{"name":"DataFieldForAnnotation::carrier::Contact","properties":{},"schema":{"keys":[{"name":"Target","value":"carrier/@Communication.Contact"}]}}}}},"properties":{}}},"fields":[]},"RevenueChart":{"name":"RevenueChart","title":"Supplement Price","properties":{"stashed":{"freeText":false}},"schema":{"dataType":"ChartDefinition","keys":[{"name":"Target","value":"com.sap.vocabularies.UI.v1.Chart#SupplementPrice"},{"name":"ID","value":"Chart::SupplementPrice"},{"name":"Value","value":"Revenue"}]},"aggregations":{}}}}}},"sections":{"aggregations":{"BookingDetailsSection":{"schema":{"keys":[{"name":"ID","value":"BookingDetails"}]},"aggregations":{"subSections":{"aggregations":{"BookingDataSubSection":{"schema":{"keys":[{"name":"ID","value":"BookingData"}]},"aggregations":{}},"AdministrativeDataSubSection":{"schema":{"keys":[{"name":"ID","value":"AdministrativeData"}]},"aggregations":{}}}}}},"FlightDataSection":{"schema":{"keys":[{"name":"ID","value":"FlightData"}]},"aggregations":{}},"PriceDataSection":{"schema":{"keys":[{"name":"ID","value":"PriceData"}]},"aggregations":{}}}}}}},"entitySet":"Booking","contextPath":"/Travel/_Booking","pageType":"ObjectPage"}}}}'; +export const V4_WITH_SUB_OBJECT_PAGE = '{"applicationModel":{"pages":{"TravelList":{"contextPath":"/Travel","template":"sap.fe.templates.ListReport","entitySet":"Travel","pageType":"ListReport","model":{"root":{"aggregations":{}}},"navigation":{"Travel":{"route":"TravelObjectPage"}}},"TravelObjectPage":{"template":"sap.fe.templates.ObjectPage","model":{"root":{"aggregations":{"header":{"aggregations":{"sections":{"aggregations":{}}}}}}},"entitySet":"Travel","contextPath":"/Travel","pageType":"ObjectPage","navigation":{"_Booking":{"route":"BookingObjectPage"}}},"BookingObjectPage":{"template":"sap.fe.templates.ObjectPage","model":{"root":{"aggregations":{"header":{"aggregations":{"sections":{"aggregations":{"FlightDateDP":{"name":"FlightDateDP","properties":{"stashed":{"freeText":false}},"schema":{"type":"object","keys":[{"name":"Target","value":"com.sap.vocabularies.UI.v1.DataPoint#FlightDate"},{"name":"ID","value":"DataPoint::FlightDate"},{"name":"Value","value":"Flight Date"}]},"aggregations":{}},"BookingDateDP":{"name":"BookingDateDP","properties":{"stashed":{"freeText":false}},"schema":{"type":"object","keys":[{"name":"Target","value":"com.sap.vocabularies.UI.v1.DataPoint#BookingDate"},{"name":"ID","value":"DataPoint::BookingDate"},{"name":"Value","value":"Booking Date"}]},"aggregations":{}},"FieldGroupNames":{"name":"FieldGroupNames","properties":{"stashed":{"freeText":false}},"schema":{"type":"object","keys":[{"name":"Target","value":"com.sap.vocabularies.UI.v1.FieldGroup#Names"},{"name":"ID","value":"FieldGroup::Names"},{"name":"Value","value":"Names"}]},"aggregations":{"form":{"schema":{"keys":[{"name":"Target","value":"com.sap.vocabularies.UI.v1.FieldGroup#Names"}]},"aggregations":{"fields":{"aggregations":{"AirlineNameField":{"name":"AirlineNameField","properties":{},"schema":{"keys":[{"name":"Target","value":"#/Names/AirlineName"},{"name":"Value","value":"AirlineName"}]}},"CustomerNameField":{"name":"CustomerNameField","properties":{},"schema":{"keys":[{"name":"Target","value":"#/Names/CustomerName"},{"name":"Value","value":"CustomerName"}]}},"DataFieldForAnnotation::carrier::Contact":{"name":"DataFieldForAnnotation::carrier::Contact","properties":{},"schema":{"keys":[{"name":"Target","value":"carrier/@Communication.Contact"}]}}}}},"properties":{}}},"fields":[]},"RevenueChart":{"name":"RevenueChart","title":"Supplement Price","properties":{"stashed":{"freeText":false}},"schema":{"dataType":"ChartDefinition","keys":[{"name":"Target","value":"com.sap.vocabularies.UI.v1.Chart#SupplementPrice"},{"name":"ID","value":"Chart::SupplementPrice"},{"name":"Value","value":"Revenue"}]},"aggregations":{}}}}}},"sections":{"aggregations":{"BookingDetailsSection":{"schema":{"keys":[{"name":"ID","value":"BookingDetails"}]},"aggregations":{"subSections":{"aggregations":{"BookingDataSubSection":{"schema":{"keys":[{"name":"ID","value":"BookingData"}]},"aggregations":{"form":{"aggregations":{"fields":{"aggregations":{"BookingIdField":{"schema":{"keys":[{"name":"Value","value":"BookingId"}]}},"FlightDateField":{"schema":{"keys":[{"name":"Value","value":"FlightDate"}]}}}}}}}},"_Supplements::@UI.LineItem":{"schema":{"keys":[{"name":"ID","value":"AdministrativeData"}]},"aggregations":{"table":{"aggregations":{"columns":{"aggregations":{"ConnectionIdCol":{"schema":{"keys":[{"name":"Value","value":"ConnectionId"}]},"description":"Connection"},"AirportCodeCol":{"schema":{"keys":[{"name":"Value","value":"AirportCode"}]},"description":"Airport"}}}}}},"isTable":true}}}}},"FlightDataSection":{"schema":{"keys":[{"name":"ID","value":"FlightData"}]},"aggregations":{}},"PriceDataSection":{"schema":{"keys":[{"name":"ID","value":"PriceData"}]},"aggregations":{}}}}}}},"entitySet":"Booking","contextPath":"/Travel/_Booking","pageType":"ObjectPage"}}}}'; diff --git a/packages/ui5-test-writer/test/unit/fiori-elements.test.ts b/packages/ui5-test-writer/test/unit/fiori-elements.test.ts index 3fe0080258a..4c520518995 100644 --- a/packages/ui5-test-writer/test/unit/fiori-elements.test.ts +++ b/packages/ui5-test-writer/test/unit/fiori-elements.test.ts @@ -429,6 +429,15 @@ describe('ui5-test-writer', () => { expect(bookingObjPageJourneyContent).toContain('iCheckSection({ section: "FlightData" })'); expect(bookingObjPageJourneyContent).toContain('iPressSectionIconTabFilterButton("PriceData")'); expect(bookingObjPageJourneyContent).toContain('iCheckSection({ section: "PriceData" })'); + expect(bookingObjPageJourneyContent).toContain( + 'onForm({ section: "BookingData" }).iCheckField({ property: "BookingId" })' + ); + expect(bookingObjPageJourneyContent).toContain( + 'onForm({ section: "BookingData" }).iCheckField({ property: "FlightDate" })' + ); + expect(bookingObjPageJourneyContent).toContain('onTable({ property: "_Supplements" }).iCheckColumns('); + expect(bookingObjPageJourneyContent).toContain('"ConnectionId":{"header":"Connection"}'); + expect(bookingObjPageJourneyContent).toContain('"AirportCode":{"header":"Airport"}'); }); }); }); diff --git a/packages/ui5-test-writer/test/unit/utils/modelUtils.test.ts b/packages/ui5-test-writer/test/unit/utils/modelUtils.test.ts index 7881aabebd8..c74fcccf4d8 100644 --- a/packages/ui5-test-writer/test/unit/utils/modelUtils.test.ts +++ b/packages/ui5-test-writer/test/unit/utils/modelUtils.test.ts @@ -9,7 +9,6 @@ import { getAggregations, getSelectionFieldItems, getFilterFields, - getTableColumns, getAppFeatures } from '../../../src/utils/modelUtils'; import type { Editor } from 'mem-fs-editor'; @@ -232,117 +231,6 @@ describe('Test getFilterFields()', () => { }); }); -describe('Test getTableColumns()', () => { - test('should return table columns aggregation from page model', () => { - const expectedColumns = { - column1: { name: 'Column 1' } as unknown as TreeAggregation, - column2: { name: 'Column 2' } as unknown as TreeAggregation - }; - const mockPageModel = { - root: { - aggregations: { - table: { - aggregations: { - columns: { - aggregations: expectedColumns - } as unknown as TreeAggregation - } - } as unknown as TreeAggregation - } - } as unknown as TreeAggregation, - name: 'test', - schema: {} - } as unknown as TreeModel; - const result = getTableColumns(mockPageModel); - expect(result).toEqual(expectedColumns); - }); - - test('should return empty object when table is missing', () => { - const mockPageModel = { - root: { - aggregations: {} - } as unknown as TreeAggregation, - name: 'test', - schema: {} - } as unknown as TreeModel; - const result = getTableColumns(mockPageModel); - expect(result).toEqual({}); - }); - - test('should return empty object when columns is missing', () => { - const mockPageModel = { - root: { - aggregations: { - table: { - aggregations: {} - } as unknown as TreeAggregation - } - } as unknown as TreeAggregation, - name: 'test', - schema: {} - } as unknown as TreeModel; - const result = getTableColumns(mockPageModel); - expect(result).toEqual({}); - }); - - test('should return empty object when root has no aggregations', () => { - const mockPageModel = { - root: {} as unknown as TreeAggregation, - name: 'test', - schema: {} - } as unknown as TreeModel; - const result = getTableColumns(mockPageModel); - expect(result).toEqual({}); - }); - - test('should return empty object when column aggregations is empty', () => { - const mockPageModel = { - root: { - aggregations: { - table: { - aggregations: { - columns: { - aggregations: {} - } as unknown as TreeAggregation - } - } as unknown as TreeAggregation - } - } as unknown as TreeAggregation, - name: 'test', - schema: {} - } as unknown as TreeModel; - const result = getTableColumns(mockPageModel); - expect(result).toEqual({}); - }); - - test('should handle multiple columns correctly', () => { - const expectedColumns = { - id: { name: 'ID' } as unknown as TreeAggregation, - name: { name: 'Name' } as unknown as TreeAggregation, - email: { name: 'Email' } as unknown as TreeAggregation, - status: { name: 'Status' } as unknown as TreeAggregation - }; - const mockPageModel = { - root: { - aggregations: { - table: { - aggregations: { - columns: { - aggregations: expectedColumns - } as unknown as TreeAggregation - } - } as unknown as TreeAggregation - } - } as unknown as TreeAggregation, - name: 'test', - schema: {} - } as unknown as TreeModel; - const result = getTableColumns(mockPageModel); - expect(result).toEqual(expectedColumns); - expect(Object.keys(result)).toHaveLength(4); - }); -}); - describe('Test getFeatureData()', () => { test('should return empty feature data when project access fails', async () => { const mockLogger: Logger = { @@ -413,24 +301,6 @@ describe('Test edge cases for better branch coverage', () => { expect(result).toEqual({}); }); - test('getTableColumns should handle missing table aggregations', () => { - const mockPageModel = { - root: { - aggregations: { - table: { - aggregations: { - columns: {} as unknown as TreeAggregation - } - } as unknown as TreeAggregation - } - } as unknown as TreeAggregation, - name: 'test', - schema: {} - } as unknown as TreeModel; - const result = getTableColumns(mockPageModel); - expect(result).toEqual({}); - }); - test('getListReportPage should iterate through all pages', () => { const applicationModel = { pages: { @@ -458,20 +328,6 @@ describe('Test edge cases for better branch coverage', () => { expect(result).toEqual({}); }); - test('getTableColumns should handle table without columns aggregation', () => { - const mockPageModel = { - root: { - aggregations: { - table: {} as unknown as TreeAggregation - } - } as unknown as TreeAggregation, - name: 'test', - schema: {} - } as unknown as TreeModel; - const result = getTableColumns(mockPageModel); - expect(result).toEqual({}); - }); - test('getAggregations should return empty for primitive types', () => { expect(getAggregations(null as unknown as TreeAggregation)).toEqual({}); expect(getAggregations(undefined as unknown as TreeAggregation)).toEqual({}); @@ -512,14 +368,4 @@ describe('Test edge cases for better branch coverage', () => { const result = getFilterFields(mockPageModel); expect(result).toEqual({}); }); - - test('getTableColumns should handle null root aggregations', () => { - const mockPageModel = { - root: null as unknown as TreeAggregation, - name: 'test', - schema: {} - } as unknown as TreeModel; - const result = getTableColumns(mockPageModel); - expect(result).toEqual({}); - }); }); diff --git a/packages/ui5-test-writer/test/unit/utils/objectPageUtils.test.ts b/packages/ui5-test-writer/test/unit/utils/objectPageUtils.test.ts index 7b121c7f202..e735aaae8d6 100644 --- a/packages/ui5-test-writer/test/unit/utils/objectPageUtils.test.ts +++ b/packages/ui5-test-writer/test/unit/utils/objectPageUtils.test.ts @@ -1296,4 +1296,681 @@ describe('Test getObjectPageFeatures()', () => { const result = await getObjectPageFeatures([objectPage] as PageWithModelV4[], undefined, mockLogger); expect(result[0].bodySections?.[0].subSections?.[0].id).toBe('GeneralInformation_subSection1'); }); + + test('should extract form field properties from a body sub-section', async () => { + const objectPage = { + name: 'objectPage1', + pageType: 'ObjectPage', + model: { + root: { + aggregations: { + header: { + aggregations: { + sections: { aggregations: {} } as unknown as TreeAggregation + } as unknown as TreeAggregation + } as unknown as TreeAggregation, + sections: { + aggregations: { + section1: { + isTable: false, + custom: false, + order: 1, + schema: { keys: [{ name: 'ID', value: 'GeneralInformation' }] }, + aggregations: { + subSections: { + aggregations: { + subSection1: { + isTable: false, + custom: false, + order: 1, + schema: { keys: [{ name: 'ID', value: 'SubSection1' }] }, + aggregations: { + form: { + schema: { keys: [] }, + aggregations: { + fields: { + aggregations: { + field1: { + name: 'DataField::CompanyCode', + schema: { + keys: [ + { + name: 'Value', + value: 'CompanyCode' + } + ] + } + } as unknown as TreeAggregation, + field2: { + name: 'DataField::SalesOrder', + schema: { + keys: [ + { + name: 'Value', + value: 'SalesOrder' + } + ] + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } as unknown as TreeAggregation + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation, + name: 'test', + schema: {} + } + }; + const result = await getObjectPageFeatures([objectPage] as PageWithModelV4[], undefined, mockLogger); + const subSection = result[0].bodySections?.[0].subSections?.[0]; + expect(subSection?.fields).toHaveLength(2); + expect(subSection?.fields?.[0]).toEqual({ property: 'CompanyCode' }); + expect(subSection?.fields?.[1]).toEqual({ property: 'SalesOrder' }); + }); + + test('should return empty fields array for sub-section without form aggregation', async () => { + const objectPage = { + name: 'objectPage1', + pageType: 'ObjectPage', + model: { + root: { + aggregations: { + header: { + aggregations: { + sections: { aggregations: {} } as unknown as TreeAggregation + } as unknown as TreeAggregation + } as unknown as TreeAggregation, + sections: { + aggregations: { + section1: { + isTable: false, + custom: false, + order: 1, + schema: { keys: [{ name: 'ID', value: 'GeneralInformation' }] }, + aggregations: { + subSections: { + aggregations: { + subSection1: { + isTable: false, + custom: false, + order: 1, + schema: { keys: [{ name: 'ID', value: 'SubSection1' }] }, + aggregations: {} + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation, + name: 'test', + schema: {} + } + }; + const result = await getObjectPageFeatures([objectPage] as PageWithModelV4[], undefined, mockLogger); + const subSection = result[0].bodySections?.[0].subSections?.[0]; + expect(subSection?.fields).toEqual([]); + }); + + test('should skip fields without Value key in schema', async () => { + const objectPage = { + name: 'objectPage1', + pageType: 'ObjectPage', + model: { + root: { + aggregations: { + header: { + aggregations: { + sections: { aggregations: {} } as unknown as TreeAggregation + } as unknown as TreeAggregation + } as unknown as TreeAggregation, + sections: { + aggregations: { + section1: { + isTable: false, + custom: false, + order: 1, + schema: { keys: [{ name: 'ID', value: 'GeneralInformation' }] }, + aggregations: { + subSections: { + aggregations: { + subSection1: { + isTable: false, + custom: false, + order: 1, + schema: { keys: [{ name: 'ID', value: 'SubSection1' }] }, + aggregations: { + form: { + schema: { keys: [] }, + aggregations: { + fields: { + aggregations: { + field1: { + name: 'DataField::CompanyCode', + schema: { keys: [] } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } as unknown as TreeAggregation + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation, + name: 'test', + schema: {} + } + }; + const result = await getObjectPageFeatures([objectPage] as PageWithModelV4[], undefined, mockLogger); + expect(result[0].bodySections?.[0].subSections?.[0].fields).toEqual([]); + }); + + test('should extract table columns from a table sub-section', async () => { + const objectPage = { + name: 'objectPage1', + pageType: 'ObjectPage', + model: { + root: { + aggregations: { + header: { + aggregations: { + sections: { aggregations: {} } as unknown as TreeAggregation + } as unknown as TreeAggregation + } as unknown as TreeAggregation, + sections: { + aggregations: { + section1: { + isTable: false, + custom: false, + order: 1, + schema: { keys: [{ name: 'ID', value: 'Items' }] }, + aggregations: { + subSections: { + aggregations: { + subSection1: { + isTable: true, + custom: false, + order: 1, + schema: { keys: [{ name: 'ID', value: 'ItemsTable' }] }, + aggregations: { + table: { + schema: { keys: [] }, + aggregations: { + columns: { + aggregations: { + col1: { + custom: false, + description: 'Product', + schema: { + keys: [ + { name: 'Value', value: 'Product' } + ] + } + } as unknown as TreeAggregation, + col2: { + custom: false, + description: 'Quantity', + schema: { + keys: [ + { name: 'Value', value: 'Quantity' } + ] + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } as unknown as TreeAggregation + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation, + name: 'test', + schema: {} + } + }; + const result = await getObjectPageFeatures([objectPage] as PageWithModelV4[], undefined, mockLogger); + const subSection = result[0].bodySections?.[0].subSections?.[0]; + expect(subSection?.tableColumns).toEqual({ Product: { header: 'Product' }, Quantity: { header: 'Quantity' } }); + }); + + test('should use Key for custom table columns', async () => { + const objectPage = { + name: 'objectPage1', + pageType: 'ObjectPage', + model: { + root: { + aggregations: { + header: { + aggregations: { + sections: { aggregations: {} } as unknown as TreeAggregation + } as unknown as TreeAggregation + } as unknown as TreeAggregation, + sections: { + aggregations: { + section1: { + isTable: false, + custom: false, + order: 1, + schema: { keys: [{ name: 'ID', value: 'Items' }] }, + aggregations: { + subSections: { + aggregations: { + subSection1: { + isTable: true, + custom: false, + order: 1, + schema: { keys: [{ name: 'ID', value: 'ItemsTable' }] }, + aggregations: { + table: { + schema: { keys: [] }, + aggregations: { + columns: { + aggregations: { + col1: { + custom: true, + description: 'Custom Col', + schema: { + keys: [ + { + name: 'Key', + value: 'customColumn1' + } + ] + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } as unknown as TreeAggregation + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation, + name: 'test', + schema: {} + } + }; + const result = await getObjectPageFeatures([objectPage] as PageWithModelV4[], undefined, mockLogger); + expect(result[0].bodySections?.[0].subSections?.[0].tableColumns).toEqual({ + customColumn1: { header: 'Custom Col' } + }); + }); + + test('should return empty tableColumns for sub-section without table aggregation', async () => { + const objectPage = { + name: 'objectPage1', + pageType: 'ObjectPage', + model: { + root: { + aggregations: { + header: { + aggregations: { + sections: { aggregations: {} } as unknown as TreeAggregation + } as unknown as TreeAggregation + } as unknown as TreeAggregation, + sections: { + aggregations: { + section1: { + isTable: false, + custom: false, + order: 1, + schema: { keys: [{ name: 'ID', value: 'GeneralInformation' }] }, + aggregations: { + subSections: { + aggregations: { + subSection1: { + isTable: true, + custom: false, + order: 1, + schema: { keys: [{ name: 'ID', value: 'SubSection1' }] }, + aggregations: {} + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation, + name: 'test', + schema: {} + } + }; + const result = await getObjectPageFeatures([objectPage] as PageWithModelV4[], undefined, mockLogger); + expect(result[0].bodySections?.[0].subSections?.[0].tableColumns).toEqual({}); + }); + + test('should return empty fields and tableColumns for custom sub-sections', async () => { + const objectPage = { + name: 'objectPage1', + pageType: 'ObjectPage', + model: { + root: { + aggregations: { + header: { + aggregations: { + sections: { aggregations: {} } as unknown as TreeAggregation + } as unknown as TreeAggregation + } as unknown as TreeAggregation, + sections: { + aggregations: { + section1: { + isTable: false, + custom: false, + order: 1, + schema: { keys: [{ name: 'ID', value: 'GeneralInformation' }] }, + aggregations: { + subSections: { + aggregations: { + subSection1: { + isTable: false, + custom: true, + order: 1, + schema: { keys: [{ name: 'ID', value: 'CustomSubSection' }] }, + aggregations: { + form: { + schema: { keys: [] }, + aggregations: { + fields: { + aggregations: { + field1: { + name: 'DataField::CompanyCode', + schema: { + keys: [ + { + name: 'Value', + value: 'CompanyCode' + } + ] + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } as unknown as TreeAggregation + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation, + name: 'test', + schema: {} + } + }; + const result = await getObjectPageFeatures([objectPage] as PageWithModelV4[], undefined, mockLogger); + const subSection = result[0].bodySections?.[0].subSections?.[0]; + expect(subSection?.fields).toEqual([]); + expect(subSection?.tableColumns).toEqual({}); + }); + + test('should extract form field properties directly from a body section without subsections', async () => { + const objectPage = { + name: 'objectPage1', + pageType: 'ObjectPage', + model: { + root: { + aggregations: { + header: { + aggregations: { + sections: { aggregations: {} } as unknown as TreeAggregation + } as unknown as TreeAggregation + } as unknown as TreeAggregation, + sections: { + aggregations: { + section1: { + isTable: false, + custom: false, + order: 1, + schema: { keys: [{ name: 'ID', value: 'GeneralInformation' }] }, + aggregations: { + subSections: { aggregations: {} } as unknown as TreeAggregation, + form: { + schema: { keys: [] }, + aggregations: { + fields: { + aggregations: { + field1: { + name: 'DataField::CompanyCode', + schema: { keys: [{ name: 'Value', value: 'CompanyCode' }] } + } as unknown as TreeAggregation, + field2: { + name: 'DataField::SalesOrder', + schema: { keys: [{ name: 'Value', value: 'SalesOrder' }] } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } as unknown as TreeAggregation + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation, + name: 'test', + schema: {} + } + }; + const result = await getObjectPageFeatures([objectPage] as PageWithModelV4[], undefined, mockLogger); + const section = result[0].bodySections?.[0]; + expect(section?.fields).toHaveLength(2); + expect(section?.fields?.[0]).toEqual({ property: 'CompanyCode' }); + expect(section?.fields?.[1]).toEqual({ property: 'SalesOrder' }); + expect(section?.subSections).toHaveLength(0); + }); + + test('should extract table columns directly from a body section without subsections', async () => { + const objectPage = { + name: 'objectPage1', + pageType: 'ObjectPage', + model: { + root: { + aggregations: { + header: { + aggregations: { + sections: { aggregations: {} } as unknown as TreeAggregation + } as unknown as TreeAggregation + } as unknown as TreeAggregation, + sections: { + aggregations: { + section1: { + isTable: true, + custom: false, + order: 1, + schema: { keys: [{ name: 'Key', value: '_Items' }] }, + aggregations: { + subSections: { aggregations: {} } as unknown as TreeAggregation, + table: { + schema: { keys: [] }, + aggregations: { + columns: { + aggregations: { + col1: { + custom: false, + description: 'Product', + schema: { keys: [{ name: 'Value', value: 'Product' }] } + } as unknown as TreeAggregation, + col2: { + custom: false, + description: 'Quantity', + schema: { keys: [{ name: 'Value', value: 'Quantity' }] } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } as unknown as TreeAggregation + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation, + name: 'test', + schema: {} + } + }; + const result = await getObjectPageFeatures([objectPage] as PageWithModelV4[], undefined, mockLogger); + const section = result[0].bodySections?.[0]; + expect(section?.tableColumns).toEqual({ + Product: { header: 'Product' }, + Quantity: { header: 'Quantity' } + }); + expect(section?.subSections).toHaveLength(0); + }); + + test('should return empty fields and tableColumns for custom body sections', async () => { + const objectPage = { + name: 'objectPage1', + pageType: 'ObjectPage', + model: { + root: { + aggregations: { + header: { + aggregations: { + sections: { aggregations: {} } as unknown as TreeAggregation + } as unknown as TreeAggregation + } as unknown as TreeAggregation, + sections: { + aggregations: { + section1: { + isTable: false, + custom: true, + order: 1, + schema: { keys: [{ name: 'ID', value: 'CustomSection' }] }, + aggregations: { + subSections: { aggregations: {} } as unknown as TreeAggregation, + form: { + schema: { keys: [] }, + aggregations: { + fields: { + aggregations: { + field1: { + name: 'DataField::CompanyCode', + schema: { keys: [{ name: 'Value', value: 'CompanyCode' }] } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } as unknown as TreeAggregation + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation, + name: 'test', + schema: {} + } + }; + const result = await getObjectPageFeatures([objectPage] as PageWithModelV4[], undefined, mockLogger); + const section = result[0].bodySections?.[0]; + expect(section?.fields).toEqual([]); + expect(section?.tableColumns).toEqual({}); + }); + + test('should extract navigationProperty from table section key with underscore prefix', async () => { + const objectPage = { + name: 'objectPage1', + pageType: 'ObjectPage', + model: { + root: { + aggregations: { + header: { + aggregations: { + sections: { aggregations: {} } as unknown as TreeAggregation + } as unknown as TreeAggregation + } as unknown as TreeAggregation, + sections: { + aggregations: { + '_Booking::@com.sap.vocabularies.UI.v1.LineItem': { + isTable: false, + custom: false, + order: 1, + schema: { keys: [{ name: 'ID', value: 'Booking' }] }, + aggregations: { + subSections: { aggregations: {} } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation, + name: 'test', + schema: {} + } + }; + const result = await getObjectPageFeatures([objectPage] as PageWithModelV4[], undefined, mockLogger); + expect(result[0].bodySections?.[0].id).toBe('Booking'); + expect(result[0].bodySections?.[0].navigationProperty).toBe('_Booking'); + }); + + test('should not set navigationProperty for non-navigation section keys', async () => { + const objectPage = { + name: 'objectPage1', + pageType: 'ObjectPage', + model: { + root: { + aggregations: { + header: { + aggregations: { + sections: { aggregations: {} } as unknown as TreeAggregation + } as unknown as TreeAggregation + } as unknown as TreeAggregation, + sections: { + aggregations: { + '@com.sap.vocabularies.UI.v1.Identification': { + isTable: false, + custom: false, + order: 1, + schema: { keys: [{ name: 'ID', value: 'Travel' }] }, + aggregations: { + subSections: { aggregations: {} } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation + } + } as unknown as TreeAggregation, + name: 'test', + schema: {} + } + }; + const result = await getObjectPageFeatures([objectPage] as PageWithModelV4[], undefined, mockLogger); + expect(result[0].bodySections?.[0].navigationProperty).toBeUndefined(); + }); }); diff --git a/packages/ui5-test-writer/test/unit/utils/tableUtils.test.ts b/packages/ui5-test-writer/test/unit/utils/tableUtils.test.ts new file mode 100644 index 00000000000..394c2e8d310 --- /dev/null +++ b/packages/ui5-test-writer/test/unit/utils/tableUtils.test.ts @@ -0,0 +1,162 @@ +import { getColumnIdentifier, transformTableColumns, extractTableColumnsFromNode } from '../../../src/utils/tableUtils'; +import type { ColumnAggregations } from '../../../src/utils/tableUtils'; +import type { TreeAggregation } from '@sap/ux-specification/dist/types/src/parser'; + +describe('getColumnIdentifier()', () => { + test('returns Value key for a standard column', () => { + const column = { + schema: { keys: [{ name: 'Value', value: 'ProductID' }] } + }; + expect(getColumnIdentifier(column)).toBe('ProductID'); + }); + + test('returns Key entry for a custom column', () => { + const column = { + custom: true, + schema: { keys: [{ name: 'Key', value: 'myCustomCol' }] } + }; + expect(getColumnIdentifier(column)).toBe('myCustomCol'); + }); + + test('returns undefined when standard column has no Value key', () => { + const column = { + schema: { keys: [{ name: 'Label', value: 'Something' }] } + }; + expect(getColumnIdentifier(column)).toBeUndefined(); + }); + + test('returns undefined when custom column has no Key entry', () => { + const column = { + custom: true, + schema: { keys: [{ name: 'Value', value: 'ProductID' }] } + }; + expect(getColumnIdentifier(column)).toBeUndefined(); + }); +}); + +describe('transformTableColumns()', () => { + test('maps standard columns using Value key with header from description', () => { + const columnAggregations: ColumnAggregations = { + 'ProductID::col': { + path: [], + aggregations: {}, + description: 'Product ID', + schema: { keys: [{ name: 'Value', value: 'ProductID' }] } + }, + 'Name::col': { + path: [], + aggregations: {}, + description: 'Name', + schema: { keys: [{ name: 'Value', value: 'Name' }] } + } + }; + expect(transformTableColumns(columnAggregations)).toEqual({ + ProductID: { header: 'Product ID' }, + Name: { header: 'Name' } + }); + }); + + test('maps custom column using Key entry', () => { + const columnAggregations: ColumnAggregations = { + myCustomCol: { + path: [], + aggregations: {}, + custom: true, + description: 'Custom Col', + schema: { keys: [{ name: 'Key', value: 'customColumn1' }] } + } + }; + expect(transformTableColumns(columnAggregations)).toEqual({ + customColumn1: { header: 'Custom Col' } + }); + }); + + test('omits header when description is absent', () => { + const columnAggregations: ColumnAggregations = { + 'ProductID::col': { + path: [], + aggregations: {}, + schema: { keys: [{ name: 'Value', value: 'ProductID' }] } + } + }; + expect(transformTableColumns(columnAggregations)).toEqual({ + ProductID: {} + }); + }); + + test('falls back to index as key when identifier cannot be determined', () => { + const columnAggregations: ColumnAggregations = { + unknownCol: { + path: [], + aggregations: {}, + description: 'Unknown', + schema: { keys: [{ name: 'Label', value: 'something' }] } + } + }; + expect(transformTableColumns(columnAggregations)).toEqual({ + '0': { header: 'Unknown' } + }); + }); + + test('returns empty object for empty input', () => { + expect(transformTableColumns({})).toEqual({}); + }); +}); + +function makeNode(columnItems: Record): TreeAggregation { + return { + aggregations: { + table: { + aggregations: { + columns: { + aggregations: columnItems + } + } + } + } + } as unknown as TreeAggregation; +} + +describe('extractTableColumnsFromNode()', () => { + test('extracts columns from a node with a table aggregation', () => { + const node = makeNode({ + 'ProductID::col': { + description: 'Product ID', + schema: { keys: [{ name: 'Value', value: 'ProductID' }] } + }, + 'Name::col': { + description: 'Name', + schema: { keys: [{ name: 'Value', value: 'Name' }] } + } + }); + expect(extractTableColumnsFromNode(node)).toEqual({ + ProductID: { header: 'Product ID' }, + Name: { header: 'Name' } + }); + }); + + test('extracts a custom column from a node', () => { + const node = makeNode({ + myCol: { + custom: true, + description: 'Custom Col', + schema: { keys: [{ name: 'Key', value: 'customColumn1' }] } + } + }); + expect(extractTableColumnsFromNode(node)).toEqual({ + customColumn1: { header: 'Custom Col' } + }); + }); + + test('returns empty object when node has no table aggregation', () => { + const node = { aggregations: {} } as unknown as TreeAggregation; + expect(extractTableColumnsFromNode(node)).toEqual({}); + }); + + test('returns empty object when table has no columns aggregation', () => { + const node = { + aggregations: { table: { aggregations: {} } } + } as unknown as TreeAggregation; + expect(extractTableColumnsFromNode(node)).toEqual({}); + }); +});