diff --git a/.changeset/eight-pots-protect.md b/.changeset/eight-pots-protect.md new file mode 100644 index 000000000..7aa557d03 --- /dev/null +++ b/.changeset/eight-pots-protect.md @@ -0,0 +1,7 @@ +--- +"@ui5-language-assistant/vscode-ui5-language-assistant-bas-ext": patch +"vscode-ui5-language-assistant": patch +"@ui5-language-assistant/fe": patch +--- + +feat: add absolute path support for meta path diff --git a/packages/fe/src/i18n/i18n.json b/packages/fe/src/i18n/i18n.json index 535df1450..af82d08e2 100644 --- a/packages/fe/src/i18n/i18n.json +++ b/packages/fe/src/i18n/i18n.json @@ -24,7 +24,7 @@ "UNKNOWN_PATH": "Unknown path: \"{{value}}\"", "INVALID_PROPERTY_PATH_MULTIPLE_1_TO_MANY": "Invalid property path value. Multiple 1:many association segments not allowed", - "ENTITY_SET_OR_CONTEXT_PATH_IS_MISSING_IN_MANIFEST": "EntitySet or contextPath for the current view are not defined in application manifest. Attribute value completion and diagnostics is not possible if EntitySet or contextPath are not defined or defined dynamically in controllers", + "ENTITY_SET_OR_CONTEXT_PATH_IS_MISSING_IN_MANIFEST": "Path cannot be identified: use absolute path or define contextPath", "EMPTY_CONTEXT_PATH_IN_MANIFEST": "ContextPath in manifest is empty. Attribute value completion and diagnostics are disabled", "RELATIVE_CONTEXT_PATH_IN_MANIFEST": "ContextPath in manifest \"{{value}}\" must be absolute. Attribute value completion and diagnostics are disabled", "UNKNOWN_CONTEXT_PATH_IN_MANIFEST": "Unknown contextPath in manifest \"{{value}}\". Attribute value completion and diagnostics are disabled", diff --git a/packages/fe/src/services/completion/providers/context-path.ts b/packages/fe/src/services/completion/providers/context-path.ts index 52da27e17..b71109617 100644 --- a/packages/fe/src/services/completion/providers/context-path.ts +++ b/packages/fe/src/services/completion/providers/context-path.ts @@ -1,37 +1,17 @@ import { getUI5PropertyByXMLAttributeKey } from "@ui5-language-assistant/logic-utils"; -import { - getPathConstraintsForControl, - getNextPossibleContextPathTargets, - getRootElements, - resolvePathTarget, -} from "../../../utils"; +import { getPathConstraintsForControl } from "../../../utils"; import { AnnotationTargetInXMLAttributeValueCompletion, SAP_FE_MACROS, } from "../../../types"; import { UI5AttributeValueCompletionOptions } from "./index"; +import { CompletionSuggestion } from "../../../types/completion"; import { - EntityContainer, - EntitySet, - EntityType, - NavigationProperty, - Singleton, -} from "@sap-ux/vocabularies-types"; -import { getAffectedRange } from "../utils"; -import { AnnotationTargetInXMLAttributeValueTypeName } from "../../../types/completion"; - -type ApplicableMetadataElement = - | EntityContainer - | EntitySet - | EntityType - | Singleton - | NavigationProperty; - -interface CompletionSuggestion { - element: ApplicableMetadataElement; - isLastSegment: boolean; -} + getNavigationSuggestion, + getRootElementSuggestions, + suggestionToTargetCompletion, +} from "./utils"; /** * Suggests values for macros contextPath @@ -46,150 +26,84 @@ export function contextPathSuggestions({ attribute, context.ui5Model ); + if (!ui5Property) { + return []; + } + const metaPathStartsWithAbsolutePath = element.attributes.find( + (i) => i.key === "metaPath" && i.value?.startsWith("/") + ); + // no CC for contextPath if metaPath starts with absolute path + if (metaPathStartsWithAbsolutePath) { + return []; + } if ( - ui5Property?.library === SAP_FE_MACROS && - ui5Property.parent?.name === "Chart" && - ui5Property.name === "contextPath" + !["contextPath"].includes(ui5Property.name) || + ui5Property?.library !== SAP_FE_MACROS || + ui5Property.parent?.name !== "Chart" ) { - const mainServicePath = context.manifestDetails.mainServicePath; - const service = mainServicePath - ? context.services[mainServicePath] - : undefined; - if (!service) { - return []; - } - const metadata = service.convertedMetadata; - const { expectedAnnotations, expectedTypes } = getPathConstraintsForControl( - element.name, - ui5Property - ); - const isPropertyPath = expectedTypes.includes("Property"); - const suggestions: CompletionSuggestion[] = []; - const segments = (attribute.value || "").split("/"); - const precedingSegments = (prefix || "").split("/"); - const completionSegmentIndex = precedingSegments.length - 1; - precedingSegments.pop(); - const completionSegmentOffset = - precedingSegments.join("/").length + (precedingSegments.length ? 1 : 0); - const isAbsolutePath = segments.length > 1 && !segments[0]; - if (!isAbsolutePath && completionSegmentIndex > 0) { - // relative paths are not supported - return []; - } - if (expectedAnnotations.length + expectedTypes.length === 0) { - return []; - } + return []; + } - const isNextSegmentPossible = ( - currentTarget: EntitySet | EntityType | Singleton | EntityContainer, - milestones: string[] = [] - ): boolean => { - return ( - getNextPossibleContextPathTargets( - service.convertedMetadata, - currentTarget, - { - allowedTerms: expectedAnnotations, - allowedTargets: expectedTypes, - isPropertyPath, - }, - [...milestones, currentTarget.fullyQualifiedName] - ).length > 0 - ); - }; + const mainServicePath = context.manifestDetails.mainServicePath; + const service = mainServicePath + ? context.services[mainServicePath] + : undefined; + if (!service) { + return []; + } + const metadata = service.convertedMetadata; + const { expectedAnnotations, expectedTypes } = getPathConstraintsForControl( + element.name, + ui5Property + ); + if (expectedAnnotations.length + expectedTypes.length === 0) { + return []; + } + const isPropertyPath = expectedTypes.includes("Property"); + const suggestions: CompletionSuggestion[] = []; + const segments = (attribute.value || "").split("/"); + const precedingSegments = (prefix || "").split("/"); + const completionSegmentIndex = precedingSegments.length - 1; + precedingSegments.pop(); + const completionSegmentOffset = + precedingSegments.join("/").length + (precedingSegments.length ? 1 : 0); + const isAbsolutePath = segments.length > 1 && !segments[0]; + if (!isAbsolutePath && completionSegmentIndex > 0) { + // relative paths are not supported + return []; + } - if (completionSegmentIndex < 2) { - // completion for root element - const roots = getRootElements( + if (completionSegmentIndex < 2) { + // completion for root element + suggestions.push( + ...getRootElementSuggestions( metadata, expectedAnnotations, expectedTypes, isPropertyPath - ); - suggestions.push( - ...roots.map((root) => ({ - element: root, - isLastSegment: !isNextSegmentPossible(root), - })) - ); - } else { - // completion for navigation property segment - const precedingPath = segments.slice(0, completionSegmentIndex).join("/"); - const { target, isCollection, milestones } = resolvePathTarget( - service.convertedMetadata, - precedingPath - ); - if (!target) { - // target not resolved or path leads to collection - no further segments possible - return []; - } else if (target._type === "Property") { - // no further segments possible after entity property, container is not supported - return []; - } else { - const possibleTargets = getNextPossibleContextPathTargets( - service.convertedMetadata, - target, - { - allowedTerms: expectedAnnotations, - allowedTargets: expectedTypes, - isPropertyPath, - isCollection: isCollection ? false : undefined, - }, - milestones - ); - suggestions.push( - ...possibleTargets.map((t) => { - const entityType = - t._type === "NavigationProperty" ? t.targetType : t.entityType; - return { - element: t, - isLastSegment: !isNextSegmentPossible(entityType, milestones), - }; - }) - ); - } - } - - const sortMap: Record = { - EntityContainer: "Z", - EntityType: "A", - EntitySet: "B", - Singleton: "C", - NavigationProperty: "N", - }; - - const getSuggestionText = (suggestion: CompletionSuggestion): string => { - const isFullyQualifiedName = [ - "EntityContainer", - "EntitySet", - "Singleton", - ].includes(suggestion.element._type); - return `${completionSegmentIndex === 0 ? "/" : ""}${ - isFullyQualifiedName && completionSegmentIndex < 2 - ? suggestion.element.fullyQualifiedName - : suggestion.element.name - }`; + ) + ); + } else { + // completion for navigation property segment + const precedingPath = segments.slice(0, completionSegmentIndex).join("/"); + const options = { + allowedTerms: expectedAnnotations, + allowedTargets: expectedTypes, + isPropertyPath, }; - - return suggestions.map((suggestion) => { - const text = getSuggestionText(suggestion); - return { - type: AnnotationTargetInXMLAttributeValueTypeName, - node: { - kind: suggestion.element._type, - name: text, - text, - affectedRange: getAffectedRange( - attribute.syntax.value, - completionSegmentOffset - ), - commitCharacters: suggestion.isLastSegment ? [] : ["/"], - commitCharactersRequired: true, - sortText: sortMap[suggestion.element._type] + text, - }, - }; - }); + suggestions.push( + ...getNavigationSuggestion( + service.convertedMetadata, + precedingPath, + options + ) + ); } - return []; + return suggestionToTargetCompletion( + attribute, + suggestions, + completionSegmentIndex, + completionSegmentOffset + ); } diff --git a/packages/fe/src/services/completion/providers/meta-path.ts b/packages/fe/src/services/completion/providers/meta-path.ts index 5dd719946..3e6c482fe 100644 --- a/packages/fe/src/services/completion/providers/meta-path.ts +++ b/packages/fe/src/services/completion/providers/meta-path.ts @@ -6,12 +6,12 @@ import { getNextPossiblePathTargets, resolvePathTarget, normalizePath, - getContextPath, + ResolvedPathTargetType, + resolveContextPath, } from "../../../utils"; import type { UI5AttributeValueCompletionOptions } from "./index"; import type { - EntityContainer, EntitySet, Singleton, EntityType, @@ -20,15 +20,22 @@ import type { } from "@sap-ux/vocabularies-types"; import { AnnotationPathInXMLAttributeValueCompletion, - PropertyPathInXMLAttributeValueCompletion, + ContextPathOrigin, SAP_FE_MACROS, } from "../../../types"; import { Range } from "vscode-languageserver-types"; import { getAffectedRange } from "../utils"; import { AnnotationPathInXMLAttributeValueTypeName, + AnnotationTargetInXMLAttributeValueTypeName, + MetaPathSuggestion, PropertyPathInXMLAttributeValueTypeName, } from "../../../types/completion"; +import { + getRootElementSuggestions, + sortMap, + suggestionToTargetCompletion, +} from "./utils"; export interface CompletionItem { name: string; @@ -45,14 +52,8 @@ export function metaPathSuggestions({ attribute, context, prefix, -}: UI5AttributeValueCompletionOptions): ( - | AnnotationPathInXMLAttributeValueCompletion - | PropertyPathInXMLAttributeValueCompletion -)[] { - const result: ( - | AnnotationPathInXMLAttributeValueCompletion - | PropertyPathInXMLAttributeValueCompletion - )[] = []; +}: UI5AttributeValueCompletionOptions): MetaPathSuggestion[] { + const result: MetaPathSuggestion[] = []; const ui5Property = getUI5PropertyByXMLAttributeKey( attribute, context.ui5Model @@ -64,7 +65,6 @@ export function metaPathSuggestions({ return []; } const contextPathAttr = getElementAttributeValue(element, "contextPath"); - let contextPath = getContextPath(contextPathAttr, context); const mainServicePath = context.manifestDetails.mainServicePath; const service = mainServicePath @@ -74,206 +74,220 @@ export function metaPathSuggestions({ return []; } - const entitySet = - context.manifestDetails.customViews[context.customViewId || ""] - ?.entitySet ?? ""; - const metadata = service.convertedMetadata; let baseType: EntityType | undefined; - let base: - | EntityContainer - | EntitySet - | EntityType - | Singleton - | Property - | undefined; - let isNavSegmentsAllowed = true; + let base: ResolvedPathTargetType | undefined; + // navigation is only allowed when contextPath in xml attribute is undefined + const isNavSegmentsAllowed = typeof contextPathAttr === "undefined"; + const segments = (attribute.value || "").split("/"); + const precedingSegments = (prefix || "").split("/"); + const completionSegmentIndex = precedingSegments.length - 1; + precedingSegments.pop(); + const completionSegmentOffset = + precedingSegments.join("/").length + (precedingSegments.length ? 1 : 0); + const isAbsolutePath = segments.length > 1 && !segments[0]; + const { expectedAnnotations, expectedTypes } = getPathConstraintsForControl( + element.name, + ui5Property + ); + if (expectedAnnotations.length + expectedTypes.length === 0) { + return []; + } + const precedingPath = segments.slice(0, completionSegmentIndex).join("/"); - if (typeof contextPath === "string") { - if (!contextPath.startsWith("/")) { - return []; - } - ({ target: base, targetStructuredType: baseType } = resolvePathTarget( + const isPropertiesAllowed = expectedTypes.includes("Property"); + + // no CC if contextPath is defined in xml and CC is request after absolute path e.g `/` + if (contextPathAttr && isAbsolutePath && completionSegmentIndex === 1) { + return []; + } + const resolvedContext = resolveContextPath( + context, + element, + isAbsolutePath, + precedingPath + ); + + if ( + (!contextPathAttr && isAbsolutePath && completionSegmentIndex === 1) || + !resolvedContext + ) { + // for first absolute segment e.g / + const suggestions = getRootElementSuggestions( metadata, - normalizePath(contextPath) - )); - isNavSegmentsAllowed = typeof contextPathAttr === "undefined"; - } else { - contextPath = `/${entitySet}`; + expectedAnnotations, + expectedTypes, + isPropertiesAllowed + ); + const targetSuggestion = suggestionToTargetCompletion( + attribute, + suggestions, + completionSegmentIndex, + completionSegmentOffset + ); + return targetSuggestion; + } + const { contextPath, origin } = resolvedContext; + if (origin === ContextPathOrigin.entitySetInManifest) { base = service.convertedMetadata.entitySets.find( - (e) => e.name === entitySet + (e) => `/${e.name}` === contextPath ); baseType = base?.entityType; } + let isCollection: boolean | undefined; - if (baseType) { - const { expectedAnnotations, expectedTypes } = getPathConstraintsForControl( - element.name, - ui5Property - ); - const isPropertiesAllowed = expectedTypes.includes("Property"); - const segments = (attribute.value || "").split("/"); - const precedingSegments = (prefix || "").split("/"); - const completionSegmentIndex = precedingSegments.length - 1; - precedingSegments.pop(); - const completionSegmentOffset = - precedingSegments.join("/").length + (precedingSegments.length ? 1 : 0); - const isAbsolutePath = segments.length > 1 && !segments[0]; + // for (navigation) property segment or annotation term + if (!isAbsolutePath && completionSegmentIndex > 0) { + const contextToConsider = [contextPath, precedingPath].join("/"); - if (isAbsolutePath && completionSegmentIndex > 0) { - // absolute paths are not supported in metaPath + if (!isNavSegmentsAllowed) { return []; } - if (!isNavSegmentsAllowed && completionSegmentIndex > 0) { - return []; - } - if (expectedAnnotations.length + expectedTypes.length === 0) { + + ({ + target: base, + targetStructuredType: baseType, + isCollection, + } = resolvePathTarget(metadata, contextToConsider, baseType)); + if (!base) { + // target not resolved e.g. for wrong nav segment - no further segments possible return []; } + } - // completion for (navigation) property segment or annotation term - const precedingPath = segments.slice(0, completionSegmentIndex).join("/"); - const { target, isCollection, targetStructuredType } = - completionSegmentIndex === 0 - ? { - target: base, - targetStructuredType: baseType, - isCollection: undefined, - } - : resolvePathTarget(service.convertedMetadata, precedingPath, baseType); - if (!target) { - // target not resolved - no further segments possible - return []; - } else if (isPropertiesAllowed && target._type === "Property") { - // no further segments possible after entity property - return []; - } else { - // Calculate completion range considering that value region includes quotes - const affectedRange: Range | undefined = getAffectedRange( - attribute.syntax.value, - completionSegmentOffset - ); + if (!base) { + ({ + target: base, + targetStructuredType: baseType, + isCollection, + } = resolvePathTarget(metadata, normalizePath(contextPath))); + } - let possibleTargets: ( - | EntitySet - | Singleton - | NavigationProperty - | Property - )[] = []; - if (target._type === "Property" || target._type === "EntityContainer") { - return []; - } - // collect existing terms - const annotationList = collectAnnotationsForElement( - expectedAnnotations, - target - ); - if (["EntitySet", "Singleton"].includes(target._type)) { - // for first path segment completion, where current base can be entity set or singleton, - // we collect also terms applied on their structural entity type + if (!base) { + // target not resolved - no further segments possible + return []; + } - // targetStructuredType is never undefined in this context - annotationList.push( - ...collectAnnotationsForElement( - expectedAnnotations, - targetStructuredType - ) - ); - } - result.push( - ...annotationList.map((annotation) => { - const fullPath = `@${ - annotation.qualifier - ? `${annotation.term}#${annotation.qualifier}` - : annotation.term - }`; - return { - type: AnnotationPathInXMLAttributeValueTypeName, - node: { - kind: "Term", - name: fullPath, - text: fullPath, - affectedRange, - }, - } as AnnotationPathInXMLAttributeValueCompletion; - }) - ); + if (base._type === "Property") { + // no further segments possible after entity property + return []; + } + + // Calculate completion range considering that value region includes quotes + const affectedRange: Range | undefined = getAffectedRange( + attribute.syntax.value, + completionSegmentOffset + ); - // collect possible properties or navigation segments - possibleTargets = getNextPossiblePathTargets( - service.convertedMetadata, - target, - false, - { - allowedTerms: expectedAnnotations, - allowedTargets: expectedTypes, - isPropertyPath: isPropertiesAllowed, - isCollection: isCollection ? false : undefined, + let possibleTargets: ( + | EntitySet + | Singleton + | NavigationProperty + | Property + )[] = []; + + // collect existing terms + const annotationList = collectAnnotationsForElement( + expectedAnnotations, + base + ); + if (["EntitySet", "Singleton"].includes(base._type)) { + // for first path segment completion, where current base can be entity set or singleton, + // we collect also terms applied on their structural entity type + + // baseType is never undefined in this context + annotationList.push( + ...collectAnnotationsForElement(expectedAnnotations, baseType) + ); + } + result.push( + ...annotationList.map((annotation) => { + const fullPath = `@${ + annotation.qualifier + ? `${annotation.term}#${annotation.qualifier}` + : annotation.term + }`; + return { + type: AnnotationPathInXMLAttributeValueTypeName, + node: { + kind: "Term", + name: fullPath, + text: fullPath, + affectedRange, }, - [target.fullyQualifiedName] - ); + } as AnnotationPathInXMLAttributeValueCompletion; + }) + ); - result.push( - ...convertTargetsToCompletionItems( - possibleTargets, - isNavSegmentsAllowed, - isPropertiesAllowed, - affectedRange - ) - ); - } + // collect possible properties or navigation segments + possibleTargets = getNextPossiblePathTargets( + service.convertedMetadata, + base, + false, + { + allowedTerms: expectedAnnotations, + allowedTargets: expectedTypes, + isPropertyPath: isPropertiesAllowed, + isCollection: isCollection ? false : undefined, + }, + [base.fullyQualifiedName] + ); + if (!isNavSegmentsAllowed) { + // filter out Property + possibleTargets = possibleTargets.filter((i) => i._type === "Property"); + } + result.push( + ...convertTargetsToCompletionItems( + possibleTargets, + isPropertiesAllowed, + base._type === "EntityContainer", + affectedRange + ) + ); + + // only if contextPath is not defined in xml and CC is request for initial segment + if (!contextPathAttr && completionSegmentIndex === 0) { + const suggestions = getRootElementSuggestions( + metadata, + expectedAnnotations, + expectedTypes, + isPropertiesAllowed + ); + const targetSuggestion = suggestionToTargetCompletion( + attribute, + suggestions, + completionSegmentIndex, + completionSegmentOffset + ); + result.push(...targetSuggestion); } return result; } function convertTargetsToCompletionItems( targets: (EntitySet | Singleton | Property | NavigationProperty)[], - isNavSegmentsAllowed: boolean, isPropertyPath: boolean, + isEntityContainer: boolean, affectedRange: Range | undefined -): ( - | AnnotationPathInXMLAttributeValueCompletion - | PropertyPathInXMLAttributeValueCompletion -)[] { - const applicableTargets: (NavigationProperty | Property)[] = targets.reduce( - (acc, t) => { - if ( - t._type === "Property" || - (isNavSegmentsAllowed && t._type === "NavigationProperty") - ) { - acc.push(t); - } - return acc; - }, - [] as (NavigationProperty | Property)[] - ); - - return applicableTargets.map((t) => { - if (t._type === "Property") { - return { - type: PropertyPathInXMLAttributeValueTypeName, - node: { - kind: t._type, - name: t.name, - text: t.name, - affectedRange, - sortText: "A" + t.name, - }, - } as PropertyPathInXMLAttributeValueCompletion; - } else { - return { - type: isPropertyPath - ? PropertyPathInXMLAttributeValueTypeName - : AnnotationPathInXMLAttributeValueTypeName, - node: { - kind: t._type, - name: t.name, - text: t.name, - affectedRange, - commitCharacters: ["/"], - sortText: "B" + t.name, - }, - } as AnnotationPathInXMLAttributeValueCompletion; +): MetaPathSuggestion[] { + return targets.map((t) => { + let type = PropertyPathInXMLAttributeValueTypeName; + if (!isPropertyPath) { + type = AnnotationPathInXMLAttributeValueTypeName; + } + if (isEntityContainer) { + type = AnnotationTargetInXMLAttributeValueTypeName; } + return { + type, + node: { + kind: t._type, + name: t.name, + text: t.name, + affectedRange, + commitCharacters: ["/"], + sortText: sortMap[t._type] + t.name, + }, + } as MetaPathSuggestion; }); } diff --git a/packages/fe/src/services/completion/providers/utils.ts b/packages/fe/src/services/completion/providers/utils.ts new file mode 100644 index 000000000..0dcaa2423 --- /dev/null +++ b/packages/fe/src/services/completion/providers/utils.ts @@ -0,0 +1,139 @@ +import { XMLAttribute } from "@xml-tools/ast"; +import { + AnnotationTargetInXMLAttributeValueCompletion, + AnnotationTargetInXMLAttributeValueTypeName, + CompletionSuggestion, +} from "../../../types/completion"; +import { getAffectedRange } from "../utils"; +import { + getNextPossibleContextPathTargets, + getRootElements, + resolvePathTarget, + AllowedTargetType, + isNextSegmentPossible, +} from "../../../utils"; +import { ConvertedMetadata } from "@sap-ux/vocabularies-types"; +import { AnnotationTerm } from "src/types"; + +const getSuggestionText = ( + suggestion: CompletionSuggestion, + completionSegmentIndex: number +): string => { + const isFullyQualifiedName = [ + "EntityContainer", + "EntitySet", + "Singleton", + ].includes(suggestion.element._type); + return `${completionSegmentIndex === 0 ? "/" : ""}${ + isFullyQualifiedName && completionSegmentIndex < 2 + ? suggestion.element.fullyQualifiedName + : suggestion.element.name + }`; +}; + +export const sortMap: Record = { + Property: "A", + NavigationProperty: "B", + Term: "C", + EntityType: "D", + EntitySet: "E", + Singleton: "F", + EntityContainer: "Z", +}; + +export function suggestionToTargetCompletion( + attribute: XMLAttribute, + suggestions: CompletionSuggestion[], + completionIndex: number, + completionOffset: number +): AnnotationTargetInXMLAttributeValueCompletion[] { + return suggestions.map((suggestion) => { + const text = getSuggestionText(suggestion, completionIndex); + return { + type: AnnotationTargetInXMLAttributeValueTypeName, + node: { + kind: suggestion.element._type, + name: text, + text, + affectedRange: getAffectedRange( + attribute.syntax.value, + completionOffset + ), + commitCharacters: suggestion.isLastSegment ? [] : ["/"], + commitCharactersRequired: true, + sortText: sortMap[suggestion.element._type] + text, + }, + }; + }); +} + +export function getRootElementSuggestions( + metadata: ConvertedMetadata, + expectedAnnotations: AnnotationTerm[], + expectedTypes: AllowedTargetType[], + isPropertyPath: boolean +): CompletionSuggestion[] { + const roots = getRootElements( + metadata, + expectedAnnotations, + expectedTypes, + isPropertyPath + ); + return roots.map((root) => ({ + element: root, + isLastSegment: !isNextSegmentPossible(metadata, root, {}), + })); +} + +/** + * Any suggestion after root segment + */ +export function getNavigationSuggestion( + metadata: ConvertedMetadata, + precedingPath: string, + options: { + isPropertyPath?: boolean; + allowedTerms?: AnnotationTerm[]; + allowedTargets?: AllowedTargetType[]; + isCollection?: boolean; + } +): CompletionSuggestion[] { + const { target, isCollection, milestones } = resolvePathTarget( + metadata, + precedingPath + ); + if (!target) { + // target not resolved or path leads to collection - no further segments possible + return []; + } + if (target._type === "Property") { + // no further segments possible after entity property, container is not supported + return []; + } + + options = { ...options, isCollection: isCollection ? false : undefined }; + + const possibleTargets = getNextPossibleContextPathTargets( + metadata, + target, + options, + milestones + ); + const suggestions: CompletionSuggestion[] = []; + suggestions.push( + ...possibleTargets.map((t) => { + const entityType = + t._type === "NavigationProperty" ? t.targetType : t.entityType; + return { + element: t, + isLastSegment: !isNextSegmentPossible( + metadata, + entityType, + options, + milestones + ), + }; + }) + ); + return suggestions; +} diff --git a/packages/fe/src/services/diagnostics/validators/missing-entity-set.ts b/packages/fe/src/services/diagnostics/validators/missing-entity-set.ts index 58d6ac889..5c53bc6fb 100644 --- a/packages/fe/src/services/diagnostics/validators/missing-entity-set.ts +++ b/packages/fe/src/services/diagnostics/validators/missing-entity-set.ts @@ -30,6 +30,10 @@ export function validateMissingViewEntitySet( ) { return []; } + const isAbsolutePath = actualAttributeValue.startsWith("/"); + if (isAbsolutePath) { + return []; + } const element = attribute.parent; const contextPathAttr = getElementAttributeValue(element, "contextPath"); const contextPath = getContextPath(contextPathAttr, context); @@ -51,7 +55,7 @@ export function validateMissingViewEntitySet( start: actualAttributeValueToken.startOffset, end: actualAttributeValueToken.endOffset, }, - severity: "info", + severity: "warn", }, ]; } diff --git a/packages/fe/src/services/diagnostics/validators/unknown-annotation-path.ts b/packages/fe/src/services/diagnostics/validators/unknown-annotation-path.ts index 93f56bf79..0f78fdd5b 100644 --- a/packages/fe/src/services/diagnostics/validators/unknown-annotation-path.ts +++ b/packages/fe/src/services/diagnostics/validators/unknown-annotation-path.ts @@ -19,10 +19,28 @@ import { normalizePath, t, getContextPath, + TypeNameMap, } from "../../../utils"; import { getAnnotationAppliedOnElement } from "../../../utils"; -import { EntityType } from "@sap-ux/vocabularies-types"; +import { EntityType, Property } from "@sap-ux/vocabularies-types"; + +const getMessageValue = ( + isAbsolutePath: boolean, + value: string | null, + normalizedContext: string | undefined +): string => { + if (!value) { + return ""; + } + if (isAbsolutePath) { + return value; + } + if (normalizedContext) { + return `${normalizedContext}/${value}`; + } + return value; +}; export function validateUnknownAnnotationPath( attribute: XMLAttribute, @@ -65,10 +83,12 @@ export function validateUnknownAnnotationPath( context.manifestDetails.customViews[context.customViewId || ""] ?.entitySet ?? ""; + const isAbsolutePath = actualAttributeValue.startsWith("/"); + let isNavSegmentsAllowed = true; let base: ResolvedPathTargetType | undefined; let baseType: EntityType | undefined; - let normalizedContextPath: string; + let normalizedContextPath: string | undefined; // resolve context and get annotations for it if (typeof contextPath === "string") { @@ -81,7 +101,7 @@ export function validateUnknownAnnotationPath( normalizedContextPath )); isNavSegmentsAllowed = typeof contextPathAttr === "undefined"; - } else { + } else if (!isAbsolutePath) { if (!entitySet) { return []; } @@ -106,12 +126,12 @@ export function validateUnknownAnnotationPath( ]; } } - const { expectedAnnotations } = getPathConstraintsForControl( + const { expectedAnnotations, expectedTypes } = getPathConstraintsForControl( control, ui5Property ); - if (!base || base._type === "Property") { + if (!isAbsolutePath && (!base || base._type === "Property")) { return []; } @@ -158,21 +178,7 @@ export function validateUnknownAnnotationPath( segment.includes("@") ); segments.splice(termSegmentIndex); - if (segments.length > 1 && !segments[0]) { - // absolute path not allowed - return [ - { - kind: "InvalidAnnotationTerm", - issueType: ANNOTATION_ISSUE_TYPE, - message: t("ABSOLUTE_ANNOTATION_PATH_NOT_ALLOWED"), - offsetRange: { - start: actualAttributeValueToken.startOffset, - end: actualAttributeValueToken.endOffset, - }, - severity: "warn", - } as AnnotationIssue, - ]; - } else if (segments.length > 0 && !isNavSegmentsAllowed) { + if (segments.length > 0 && !isNavSegmentsAllowed) { return [ { kind: "InvalidAnnotationTerm", @@ -188,22 +194,31 @@ export function validateUnknownAnnotationPath( } as AnnotationIssue, ]; } - - let targetEntity: EntityType | undefined = baseType; + let targetEntity: ResolvedPathTargetType | undefined = baseType; let lastValidSegmentIndex = -1; - for (const segment of segments) { - if (!targetEntity) { - break; - } - const navProperty = targetEntity.navigationProperties.find( - (p) => p.name === segment + if (isAbsolutePath) { + const resolvedPathTarget = resolvePathTarget( + service.convertedMetadata, + segments.join("/"), + baseType ); - targetEntity = navProperty?.targetType; - if (targetEntity) { - lastValidSegmentIndex++; + targetEntity = resolvedPathTarget.target; + lastValidSegmentIndex = resolvedPathTarget.lastValidSegmentIndex; + } else { + for (const segment of segments) { + if (!targetEntity) { + break; + } + const navProperty = ( + targetEntity as EntityType + ).navigationProperties.find((p) => p.name === segment); + + targetEntity = navProperty?.targetType; + if (targetEntity) { + lastValidSegmentIndex++; + } } } - if (!targetEntity) { originalSegments.splice(lastValidSegmentIndex + 1); const correctPart = originalSegments.join("/"); @@ -212,7 +227,11 @@ export function validateUnknownAnnotationPath( kind: "PathDoesNotExist", issueType: ANNOTATION_ISSUE_TYPE, message: t("UNKNOWN_ANNOTATION_PATH", { - value: `${normalizedContextPath}/${attribute.value}`, + value: getMessageValue( + isAbsolutePath, + attribute.value, + normalizedContextPath + ), }), offsetRange: { start: @@ -222,65 +241,18 @@ export function validateUnknownAnnotationPath( severity: "warn", }, ]; - } else { - const termSegment = originalSegments[termSegmentIndex]; - const parts = termSegment.split("@"); - let annotations: AnnotationBase[] | undefined; - annotations = getAnnotationAppliedOnElement( - expectedAnnotations, - segments.length === 0 ? base : targetEntity, - parts[0] - ); - - const match = annotations.find( - (anno) => composeAnnotationPath(anno) === "@" + parts[1] - ); - if (match) { - return []; - } else { - // check whether the provided term exists on target - const term: AnnotationTerm = fullyQualifiedNameToTerm(parts[1]); - annotations = getAnnotationAppliedOnElement( - [term], - segments.length === 0 ? base : targetEntity, - parts[0] - ); - const match = annotations.find( - (anno) => composeAnnotationPath(anno) === "@" + parts[1] - ); - if (match) { - // determine whether any allowed term exists in the project suitable for the current context - annotations = getAnnotationAppliedOnElement( - expectedAnnotations, - base - ); - - return [ - { - kind: "InvalidAnnotationTerm", - issueType: ANNOTATION_ISSUE_TYPE, - message: t( - annotations.length - ? "INVALID_ANNOTATION_TERM_TRIGGER_CODE_COMPLETION" - : "INVALID_ANNOTATION_TERM_THERE_ARE_NO_SUITABLE_ANNOTATIONS", - { value: attribute.value } - ), - offsetRange: { - start: actualAttributeValueToken.startOffset, - end: actualAttributeValueToken.endOffset, - }, - severity: "warn", - }, - ]; - } - } - + } + if (targetEntity._type === "Property") { + const expectedTypesList = expectedTypes + .map((item) => TypeNameMap[item]) + .join(", "); return [ { - kind: "PathDoesNotExist", + kind: "UnknownEnumValue", issueType: ANNOTATION_ISSUE_TYPE, - message: t("UNKNOWN_ANNOTATION_PATH", { - value: `${normalizedContextPath}/${attribute.value}`, + message: t("CONTEXT_PATH_LEADS_TO_WRONG_TARGET", { + actualType: "Edm.Property", + expectedTypes: expectedTypesList, }), offsetRange: { start: actualAttributeValueToken.startOffset, @@ -290,6 +262,79 @@ export function validateUnknownAnnotationPath( }, ]; } + + const baseEntity = + segments.length === 0 + ? (base as Exclude) + : targetEntity; + + const termSegment = originalSegments[termSegmentIndex]; + const parts = termSegment.split("@"); + let annotations: AnnotationBase[] | undefined; + + annotations = getAnnotationAppliedOnElement( + expectedAnnotations, + baseEntity, + parts[0] + ); + + const match = annotations.find( + (anno) => composeAnnotationPath(anno) === "@" + parts[1] + ); + if (match) { + return []; + } else { + // check whether the provided term exists on target + const term: AnnotationTerm = fullyQualifiedNameToTerm(parts[1]); + annotations = getAnnotationAppliedOnElement([term], baseEntity, parts[0]); + const match = annotations.find( + (anno) => composeAnnotationPath(anno) === "@" + parts[1] + ); + if (match) { + // determine whether any allowed term exists in the project suitable for the current context + annotations = getAnnotationAppliedOnElement( + expectedAnnotations, + baseEntity + ); + + return [ + { + kind: "InvalidAnnotationTerm", + issueType: ANNOTATION_ISSUE_TYPE, + message: t( + annotations.length + ? "INVALID_ANNOTATION_TERM_TRIGGER_CODE_COMPLETION" + : "INVALID_ANNOTATION_TERM_THERE_ARE_NO_SUITABLE_ANNOTATIONS", + { value: attribute.value } + ), + offsetRange: { + start: actualAttributeValueToken.startOffset, + end: actualAttributeValueToken.endOffset, + }, + severity: "warn", + }, + ]; + } + } + + return [ + { + kind: "PathDoesNotExist", + issueType: ANNOTATION_ISSUE_TYPE, + message: t("UNKNOWN_ANNOTATION_PATH", { + value: getMessageValue( + isAbsolutePath, + attribute.value, + normalizedContextPath + ), + }), + offsetRange: { + start: actualAttributeValueToken.startOffset, + end: actualAttributeValueToken.endOffset, + }, + severity: "warn", + }, + ]; } return []; diff --git a/packages/fe/src/services/diagnostics/validators/unknown-property-path.ts b/packages/fe/src/services/diagnostics/validators/unknown-property-path.ts index ee12a8471..cc808ee70 100644 --- a/packages/fe/src/services/diagnostics/validators/unknown-property-path.ts +++ b/packages/fe/src/services/diagnostics/validators/unknown-property-path.ts @@ -49,7 +49,6 @@ export function validateUnknownPropertyPath( ) { const element = attribute.parent; const control = element.name; - const mainServicePath = context.manifestDetails.mainServicePath; const service = mainServicePath ? context.services[mainServicePath] diff --git a/packages/fe/src/types/completion.ts b/packages/fe/src/types/completion.ts index 950d158a2..f762d8002 100644 --- a/packages/fe/src/types/completion.ts +++ b/packages/fe/src/types/completion.ts @@ -1,4 +1,11 @@ import { MarkupContent, Range } from "vscode-languageserver-types"; +import { + EntityContainer, + EntitySet, + EntityType, + NavigationProperty, + Singleton, +} from "@sap-ux/vocabularies-types"; export type CompletionItemKind = | "Term" @@ -123,3 +130,20 @@ export interface FilterBarIdInXMLAttributeValueCompletion extends BaseXMLViewAnnotationCompletion { type: typeof FilterBarIdInXMLAttributeValueTypeName; } + +export type ApplicableMetadataElement = + | EntityContainer + | EntitySet + | EntityType + | Singleton + | NavigationProperty; + +export interface CompletionSuggestion { + element: ApplicableMetadataElement; + isLastSegment: boolean; +} + +export type MetaPathSuggestion = + | AnnotationPathInXMLAttributeValueCompletion + | PropertyPathInXMLAttributeValueCompletion + | AnnotationTargetInXMLAttributeValueCompletion; diff --git a/packages/fe/src/types/index.ts b/packages/fe/src/types/index.ts index 446a95acb..580354908 100644 --- a/packages/fe/src/types/index.ts +++ b/packages/fe/src/types/index.ts @@ -15,3 +15,15 @@ export type AnnotationBase = { term: string; qualifier: string; }; + +export enum ContextPathOrigin { + xmlAttributeInContextPath = "xml-attribute-in-context-path", + xmlAttributeInMetaPath = "xml-attribute-in-meta-path", + contextPathInManifest = "context-path-in-manifest", + entitySetInManifest = "entitySet-in-manifest", +} + +export interface ResolveContextPath { + contextPath: string; + origin: ContextPathOrigin; +} diff --git a/packages/fe/src/utils/metadata.ts b/packages/fe/src/utils/metadata.ts index c51844e6e..ada008b3a 100644 --- a/packages/fe/src/utils/metadata.ts +++ b/packages/fe/src/utils/metadata.ts @@ -101,7 +101,7 @@ export function getRootElements( export function collectAnnotationsForElement( allowedTerms: AnnotationTerm[], - element: EntityType | EntitySet | Singleton | undefined, + element: EntityContainer | EntityType | EntitySet | Singleton | undefined, property?: string, navigationProperty?: string ): AnnotationBase[] { @@ -110,20 +110,25 @@ export function collectAnnotationsForElement( if (!element) { return []; } - const type: EntityType = getEntityTypeForElement(element); let target: + | EntityContainer | EntityType | EntitySet | Singleton | Property | NavigationProperty | undefined; - if (property) { - target = type.entityProperties.find((p) => p.name === property); - } else if (navigationProperty) { - target = type.navigationProperties.find( - (p) => p.name === navigationProperty - ); + if (element._type !== "EntityContainer") { + const type: EntityType = getEntityTypeForElement(element); + if (property) { + target = type.entityProperties.find((p) => p.name === property); + } else if (navigationProperty) { + target = type.navigationProperties.find( + (p) => p.name === navigationProperty + ); + } else { + target = element; + } } else { target = element; } diff --git a/packages/fe/src/utils/misc.ts b/packages/fe/src/utils/misc.ts index 63d0c801b..5bb490949 100644 --- a/packages/fe/src/utils/misc.ts +++ b/packages/fe/src/utils/misc.ts @@ -2,7 +2,11 @@ import { UI5Prop } from "@ui5-language-assistant/semantic-model-types"; import { Context } from "@ui5-language-assistant/context"; import { XMLElement } from "@xml-tools/ast"; import i18next, { TFunction } from "i18next"; -import type { AnnotationTerm } from "../types"; +import { + ContextPathOrigin, + type AnnotationTerm, + type ResolveContextPath, +} from "../types"; import { AllowedTargetType } from "./metadata"; import { BuildingBlockPathConstraints, @@ -72,7 +76,9 @@ export const t: TFunction = (key: string, ...args) => { }; /** - * Returns context path for completion and diagnostics services + * Returns context path for completion and diagnostics services. Context path defined in xml attribute wins over + * context path defined in manifest.json file. + * * @param attributeValue - current element contextPath attribute value * @param context - global context object * @returns - context path @@ -82,9 +88,76 @@ export function getContextPath( context: Context ): AttributeValueType { const contextPathInManifest: string | undefined = - context.manifestDetails?.customViews?.[context.customViewId || ""] - ?.contextPath; + getManifestContextPath(context); return typeof attributeValue !== "undefined" ? attributeValue : contextPathInManifest || undefined; } + +/** + * Get context path defined in manifest.json file. + * + * @param context + * @returns + */ +export function getManifestContextPath(context: Context): string | undefined { + return context.manifestDetails?.customViews?.[context.customViewId || ""] + ?.contextPath; +} + +/** + * Resolve context path. Context path can be defined + * - as xml attribute + * - as contextPath in manifest.json file + * - in meta path if path is starting as absolute + * - as entity set of view defined in manifest.json file + * + * @param context context + * @param element xml element + * @param isAbsolutePath path started as absolute + * @param precedingPath proceeding path segment + * @returns context path with its origin + */ +export function resolveContextPath( + context: Context, + element: XMLElement, + isAbsolutePath: boolean, + precedingPath: string +): ResolveContextPath | undefined { + const contextPathAttr = getElementAttributeValue(element, "contextPath"); + if (contextPathAttr) { + return { + contextPath: contextPathAttr, + origin: ContextPathOrigin.xmlAttributeInContextPath, + }; + } + + // if not absolute path and context path is not defined in xml, take it from manifest.json + const contextPath = getManifestContextPath(context); + if (!isAbsolutePath && contextPath) { + return { + contextPath, + origin: ContextPathOrigin.contextPathInManifest, + }; + } + + // context path is preceding path if it started as absolute path + if (isAbsolutePath && precedingPath.length > 0) { + return { + contextPath: precedingPath, + origin: ContextPathOrigin.xmlAttributeInMetaPath, + }; + } + + const entitySet = + context.manifestDetails.customViews[context.customViewId || ""] + ?.entitySet ?? ""; + + if (entitySet) { + // context path is entity set + return { + contextPath: `/${entitySet}`, + origin: ContextPathOrigin.entitySetInManifest, + }; + } +} diff --git a/packages/fe/src/utils/path.ts b/packages/fe/src/utils/path.ts index 38afb4807..70ef8bfbd 100644 --- a/packages/fe/src/utils/path.ts +++ b/packages/fe/src/utils/path.ts @@ -13,6 +13,7 @@ import { getEntityTypeForElement, } from "./metadata"; import { AnnotationTerm } from "./spec"; +import { ApplicableMetadataElement } from "../types/completion"; export type ResolvedPathTargetType = | EntityContainer @@ -272,3 +273,24 @@ export function getNextPossiblePathTargets( return result; } + +export const isNextSegmentPossible = ( + convertedMetadata: ConvertedMetadata, + currentTarget: Exclude, + options: { + isPropertyPath?: boolean; + allowedTerms?: AnnotationTerm[]; + allowedTargets?: AllowedTargetType[]; + isCollection?: boolean; + }, + milestones: string[] = [] +): boolean => { + return ( + getNextPossibleContextPathTargets( + convertedMetadata, + currentTarget, + options, + [...milestones, currentTarget.fullyQualifiedName] + ).length > 0 + ); +}; diff --git a/packages/fe/test/unit/services/completion/providers/context-path.test.ts b/packages/fe/test/unit/services/completion/providers/context-path.test.ts index 599495ab4..690941c6c 100644 --- a/packages/fe/test/unit/services/completion/providers/context-path.test.ts +++ b/packages/fe/test/unit/services/completion/providers/context-path.test.ts @@ -113,54 +113,54 @@ describe("contextPath attribute value completion", () => { expect( result.map((item) => completionItemToSnapshot(item)) ).toStrictEqual([ - "label: /TravelService.EntityContainer; text: /TravelService.EntityContainer; kind:19; commit:/; sort:Z", - "label: /TravelService.EntityContainer/Travel; text: /TravelService.EntityContainer/Travel; kind:2; commit:/; sort:B", - "label: /TravelService.EntityContainer/HighestTotal; text: /TravelService.EntityContainer/HighestTotal; kind:2; commit:; sort:B", - "label: /TravelService.EntityContainer/Currencies; text: /TravelService.EntityContainer/Currencies; kind:2; commit:/; sort:B", - "label: /TravelService.EntityContainer/TravelStatus; text: /TravelService.EntityContainer/TravelStatus; kind:2; commit:/; sort:B", - "label: /TravelService.EntityContainer/TravelAgency; text: /TravelService.EntityContainer/TravelAgency; kind:2; commit:/; sort:B", - "label: /TravelService.EntityContainer/Passenger; text: /TravelService.EntityContainer/Passenger; kind:2; commit:/; sort:B", - "label: /TravelService.EntityContainer/Booking; text: /TravelService.EntityContainer/Booking; kind:2; commit:/; sort:B", - "label: /TravelService.EntityContainer/BookedFlights; text: /TravelService.EntityContainer/BookedFlights; kind:2; commit:/; sort:B", - "label: /TravelService.EntityContainer/Countries; text: /TravelService.EntityContainer/Countries; kind:2; commit:/; sort:B", - "label: /TravelService.EntityContainer/BookingStatus; text: /TravelService.EntityContainer/BookingStatus; kind:2; commit:/; sort:B", - "label: /TravelService.EntityContainer/BookingSupplement; text: /TravelService.EntityContainer/BookingSupplement; kind:2; commit:/; sort:B", - "label: /TravelService.EntityContainer/Airline; text: /TravelService.EntityContainer/Airline; kind:2; commit:/; sort:B", - "label: /TravelService.EntityContainer/Flight; text: /TravelService.EntityContainer/Flight; kind:2; commit:/; sort:B", - "label: /TravelService.EntityContainer/Supplement; text: /TravelService.EntityContainer/Supplement; kind:2; commit:/; sort:B", - "label: /TravelService.EntityContainer/FlightConnection; text: /TravelService.EntityContainer/FlightConnection; kind:2; commit:/; sort:B", - "label: /TravelService.EntityContainer/SupplementType; text: /TravelService.EntityContainer/SupplementType; kind:2; commit:/; sort:B", - "label: /TravelService.EntityContainer/Airport; text: /TravelService.EntityContainer/Airport; kind:2; commit:/; sort:B", - "label: /TravelService.EntityContainer/Currencies_texts; text: /TravelService.EntityContainer/Currencies_texts; kind:2; commit:; sort:B", - "label: /TravelService.EntityContainer/TravelStatus_texts; text: /TravelService.EntityContainer/TravelStatus_texts; kind:2; commit:; sort:B", - "label: /TravelService.EntityContainer/Countries_texts; text: /TravelService.EntityContainer/Countries_texts; kind:2; commit:; sort:B", - "label: /TravelService.EntityContainer/BookingStatus_texts; text: /TravelService.EntityContainer/BookingStatus_texts; kind:2; commit:; sort:B", - "label: /TravelService.EntityContainer/Supplement_texts; text: /TravelService.EntityContainer/Supplement_texts; kind:2; commit:; sort:B", - "label: /TravelService.EntityContainer/SupplementType_texts; text: /TravelService.EntityContainer/SupplementType_texts; kind:2; commit:; sort:B", - "label: /Travel; text: /Travel; kind:7; commit:/; sort:A", - "label: /HighestTotal; text: /HighestTotal; kind:7; commit:; sort:A", - "label: /Currencies; text: /Currencies; kind:7; commit:/; sort:A", - "label: /TravelStatus; text: /TravelStatus; kind:7; commit:/; sort:A", - "label: /TravelAgency; text: /TravelAgency; kind:7; commit:/; sort:A", - "label: /Passenger; text: /Passenger; kind:7; commit:/; sort:A", - "label: /Booking; text: /Booking; kind:7; commit:/; sort:A", - "label: /BookedFlights; text: /BookedFlights; kind:7; commit:/; sort:A", - "label: /Countries; text: /Countries; kind:7; commit:/; sort:A", - "label: /BookingStatus; text: /BookingStatus; kind:7; commit:/; sort:A", - "label: /BookingSupplement; text: /BookingSupplement; kind:7; commit:/; sort:A", - "label: /Airline; text: /Airline; kind:7; commit:/; sort:A", - "label: /Flight; text: /Flight; kind:7; commit:/; sort:A", - "label: /Supplement; text: /Supplement; kind:7; commit:/; sort:A", - "label: /FlightConnection; text: /FlightConnection; kind:7; commit:/; sort:A", - "label: /SupplementType; text: /SupplementType; kind:7; commit:/; sort:A", - "label: /Airport; text: /Airport; kind:7; commit:/; sort:A", - "label: /DraftAdministrativeData; text: /DraftAdministrativeData; kind:7; commit:; sort:A", - "label: /Currencies_texts; text: /Currencies_texts; kind:7; commit:; sort:A", - "label: /TravelStatus_texts; text: /TravelStatus_texts; kind:7; commit:; sort:A", - "label: /Countries_texts; text: /Countries_texts; kind:7; commit:; sort:A", - "label: /BookingStatus_texts; text: /BookingStatus_texts; kind:7; commit:; sort:A", - "label: /Supplement_texts; text: /Supplement_texts; kind:7; commit:; sort:A", - "label: /SupplementType_texts; text: /SupplementType_texts; kind:7; commit:; sort:A", + "label: /TravelService.EntityContainer; text: /TravelService.EntityContainer; kind:19; commit:; sort:Z", + "label: /TravelService.EntityContainer/Travel; text: /TravelService.EntityContainer/Travel; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/HighestTotal; text: /TravelService.EntityContainer/HighestTotal; kind:2; commit:; sort:E", + "label: /TravelService.EntityContainer/Currencies; text: /TravelService.EntityContainer/Currencies; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/TravelStatus; text: /TravelService.EntityContainer/TravelStatus; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/TravelAgency; text: /TravelService.EntityContainer/TravelAgency; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/Passenger; text: /TravelService.EntityContainer/Passenger; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/Booking; text: /TravelService.EntityContainer/Booking; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/BookedFlights; text: /TravelService.EntityContainer/BookedFlights; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/Countries; text: /TravelService.EntityContainer/Countries; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/BookingStatus; text: /TravelService.EntityContainer/BookingStatus; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/BookingSupplement; text: /TravelService.EntityContainer/BookingSupplement; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/Airline; text: /TravelService.EntityContainer/Airline; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/Flight; text: /TravelService.EntityContainer/Flight; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/Supplement; text: /TravelService.EntityContainer/Supplement; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/FlightConnection; text: /TravelService.EntityContainer/FlightConnection; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/SupplementType; text: /TravelService.EntityContainer/SupplementType; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/Airport; text: /TravelService.EntityContainer/Airport; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/Currencies_texts; text: /TravelService.EntityContainer/Currencies_texts; kind:2; commit:; sort:E", + "label: /TravelService.EntityContainer/TravelStatus_texts; text: /TravelService.EntityContainer/TravelStatus_texts; kind:2; commit:; sort:E", + "label: /TravelService.EntityContainer/Countries_texts; text: /TravelService.EntityContainer/Countries_texts; kind:2; commit:; sort:E", + "label: /TravelService.EntityContainer/BookingStatus_texts; text: /TravelService.EntityContainer/BookingStatus_texts; kind:2; commit:; sort:E", + "label: /TravelService.EntityContainer/Supplement_texts; text: /TravelService.EntityContainer/Supplement_texts; kind:2; commit:; sort:E", + "label: /TravelService.EntityContainer/SupplementType_texts; text: /TravelService.EntityContainer/SupplementType_texts; kind:2; commit:; sort:E", + "label: /Travel; text: /Travel; kind:7; commit:/; sort:D", + "label: /HighestTotal; text: /HighestTotal; kind:7; commit:; sort:D", + "label: /Currencies; text: /Currencies; kind:7; commit:/; sort:D", + "label: /TravelStatus; text: /TravelStatus; kind:7; commit:/; sort:D", + "label: /TravelAgency; text: /TravelAgency; kind:7; commit:/; sort:D", + "label: /Passenger; text: /Passenger; kind:7; commit:/; sort:D", + "label: /Booking; text: /Booking; kind:7; commit:/; sort:D", + "label: /BookedFlights; text: /BookedFlights; kind:7; commit:/; sort:D", + "label: /Countries; text: /Countries; kind:7; commit:/; sort:D", + "label: /BookingStatus; text: /BookingStatus; kind:7; commit:/; sort:D", + "label: /BookingSupplement; text: /BookingSupplement; kind:7; commit:/; sort:D", + "label: /Airline; text: /Airline; kind:7; commit:/; sort:D", + "label: /Flight; text: /Flight; kind:7; commit:/; sort:D", + "label: /Supplement; text: /Supplement; kind:7; commit:/; sort:D", + "label: /FlightConnection; text: /FlightConnection; kind:7; commit:/; sort:D", + "label: /SupplementType; text: /SupplementType; kind:7; commit:/; sort:D", + "label: /Airport; text: /Airport; kind:7; commit:/; sort:D", + "label: /DraftAdministrativeData; text: /DraftAdministrativeData; kind:7; commit:; sort:D", + "label: /Currencies_texts; text: /Currencies_texts; kind:7; commit:; sort:D", + "label: /TravelStatus_texts; text: /TravelStatus_texts; kind:7; commit:; sort:D", + "label: /Countries_texts; text: /Countries_texts; kind:7; commit:; sort:D", + "label: /BookingStatus_texts; text: /BookingStatus_texts; kind:7; commit:; sort:D", + "label: /Supplement_texts; text: /Supplement_texts; kind:7; commit:; sort:D", + "label: /SupplementType_texts; text: /SupplementType_texts; kind:7; commit:; sort:D", ]); }); @@ -227,22 +227,22 @@ describe("contextPath attribute value completion", () => { expect( result.map((item) => completionItemToSnapshot(item)) ).toStrictEqual([ - "label: Travel; text: Travel; kind:2; commit:/; sort:B", - "label: Currencies; text: Currencies; kind:2; commit:/; sort:B", - "label: TravelStatus; text: TravelStatus; kind:2; commit:/; sort:B", - "label: TravelAgency; text: TravelAgency; kind:2; commit:/; sort:B", - "label: Passenger; text: Passenger; kind:2; commit:/; sort:B", - "label: Booking; text: Booking; kind:2; commit:/; sort:B", - "label: BookedFlights; text: BookedFlights; kind:2; commit:/; sort:B", - "label: Countries; text: Countries; kind:2; commit:/; sort:B", - "label: BookingStatus; text: BookingStatus; kind:2; commit:/; sort:B", - "label: BookingSupplement; text: BookingSupplement; kind:2; commit:/; sort:B", - "label: Airline; text: Airline; kind:2; commit:/; sort:B", - "label: Flight; text: Flight; kind:2; commit:/; sort:B", - "label: Supplement; text: Supplement; kind:2; commit:/; sort:B", - "label: FlightConnection; text: FlightConnection; kind:2; commit:/; sort:B", - "label: SupplementType; text: SupplementType; kind:2; commit:/; sort:B", - "label: Airport; text: Airport; kind:2; commit:/; sort:B", + "label: Travel; text: Travel; kind:2; commit:/; sort:E", + "label: Currencies; text: Currencies; kind:2; commit:/; sort:E", + "label: TravelStatus; text: TravelStatus; kind:2; commit:/; sort:E", + "label: TravelAgency; text: TravelAgency; kind:2; commit:/; sort:E", + "label: Passenger; text: Passenger; kind:2; commit:/; sort:E", + "label: Booking; text: Booking; kind:2; commit:/; sort:E", + "label: BookedFlights; text: BookedFlights; kind:2; commit:/; sort:E", + "label: Countries; text: Countries; kind:2; commit:/; sort:E", + "label: BookingStatus; text: BookingStatus; kind:2; commit:/; sort:E", + "label: BookingSupplement; text: BookingSupplement; kind:2; commit:/; sort:E", + "label: Airline; text: Airline; kind:2; commit:/; sort:E", + "label: Flight; text: Flight; kind:2; commit:/; sort:E", + "label: Supplement; text: Supplement; kind:2; commit:/; sort:E", + "label: FlightConnection; text: FlightConnection; kind:2; commit:/; sort:E", + "label: SupplementType; text: SupplementType; kind:2; commit:/; sort:E", + "label: Airport; text: Airport; kind:2; commit:/; sort:E", ]); }); @@ -253,14 +253,14 @@ describe("contextPath attribute value completion", () => { expect( result.map((item) => completionItemToSnapshot(item)) ).toStrictEqual([ - "label: CurrencyCode; text: CurrencyCode; kind:18; commit:/; sort:N", - "label: BookingStatus; text: BookingStatus; kind:18; commit:/; sort:N", - "label: to_BookSupplement; text: to_BookSupplement; kind:18; commit:/; sort:N", - "label: to_Carrier; text: to_Carrier; kind:18; commit:/; sort:N", - "label: to_Customer; text: to_Customer; kind:18; commit:/; sort:N", - "label: to_Travel; text: to_Travel; kind:18; commit:/; sort:N", - "label: to_Flight; text: to_Flight; kind:18; commit:/; sort:N", - "label: DraftAdministrativeData; text: DraftAdministrativeData; kind:18; commit:; sort:N", + "label: CurrencyCode; text: CurrencyCode; kind:18; commit:/; sort:B", + "label: BookingStatus; text: BookingStatus; kind:18; commit:/; sort:B", + "label: to_BookSupplement; text: to_BookSupplement; kind:18; commit:/; sort:B", + "label: to_Carrier; text: to_Carrier; kind:18; commit:/; sort:B", + "label: to_Customer; text: to_Customer; kind:18; commit:/; sort:B", + "label: to_Travel; text: to_Travel; kind:18; commit:/; sort:B", + "label: to_Flight; text: to_Flight; kind:18; commit:/; sort:B", + "label: DraftAdministrativeData; text: DraftAdministrativeData; kind:18; commit:; sort:B", ]); }); it("navigation segment completion (case 2)", async function () { @@ -270,14 +270,14 @@ describe("contextPath attribute value completion", () => { expect( result.map((item) => completionItemToSnapshot(item)) ).toStrictEqual([ - "label: CurrencyCode; text: CurrencyCode; kind:18; commit:/; sort:N", - "label: BookingStatus; text: BookingStatus; kind:18; commit:/; sort:N", - "label: to_BookSupplement; text: to_BookSupplement; kind:18; commit:/; sort:N", - "label: to_Carrier; text: to_Carrier; kind:18; commit:/; sort:N", - "label: to_Customer; text: to_Customer; kind:18; commit:/; sort:N", - "label: to_Travel; text: to_Travel; kind:18; commit:/; sort:N", - "label: to_Flight; text: to_Flight; kind:18; commit:/; sort:N", - "label: DraftAdministrativeData; text: DraftAdministrativeData; kind:18; commit:; sort:N", + "label: CurrencyCode; text: CurrencyCode; kind:18; commit:/; sort:B", + "label: BookingStatus; text: BookingStatus; kind:18; commit:/; sort:B", + "label: to_BookSupplement; text: to_BookSupplement; kind:18; commit:/; sort:B", + "label: to_Carrier; text: to_Carrier; kind:18; commit:/; sort:B", + "label: to_Customer; text: to_Customer; kind:18; commit:/; sort:B", + "label: to_Travel; text: to_Travel; kind:18; commit:/; sort:B", + "label: to_Flight; text: to_Flight; kind:18; commit:/; sort:B", + "label: DraftAdministrativeData; text: DraftAdministrativeData; kind:18; commit:; sort:B", ]); }); @@ -288,13 +288,13 @@ describe("contextPath attribute value completion", () => { expect( result.map((item) => completionItemToSnapshot(item)) ).toStrictEqual([ - "label: CurrencyCode; text: CurrencyCode; kind:18; commit:/; sort:N", - "label: BookingStatus; text: BookingStatus; kind:18; commit:/; sort:N", - "label: to_Carrier; text: to_Carrier; kind:18; commit:/; sort:N", - "label: to_Customer; text: to_Customer; kind:18; commit:/; sort:N", - "label: to_Travel; text: to_Travel; kind:18; commit:/; sort:N", - "label: to_Flight; text: to_Flight; kind:18; commit:/; sort:N", - "label: DraftAdministrativeData; text: DraftAdministrativeData; kind:18; commit:; sort:N", + "label: CurrencyCode; text: CurrencyCode; kind:18; commit:/; sort:B", + "label: BookingStatus; text: BookingStatus; kind:18; commit:/; sort:B", + "label: to_Carrier; text: to_Carrier; kind:18; commit:/; sort:B", + "label: to_Customer; text: to_Customer; kind:18; commit:/; sort:B", + "label: to_Travel; text: to_Travel; kind:18; commit:/; sort:B", + "label: to_Flight; text: to_Flight; kind:18; commit:/; sort:B", + "label: DraftAdministrativeData; text: DraftAdministrativeData; kind:18; commit:; sort:B", ]); }); @@ -305,12 +305,12 @@ describe("contextPath attribute value completion", () => { expect( result.map((item) => completionItemToSnapshot(item)) ).toStrictEqual([ - "label: CurrencyCode; text: CurrencyCode; kind:18; commit:/; sort:N", - "label: BookingStatus; text: BookingStatus; kind:18; commit:/; sort:N", - "label: to_Carrier; text: to_Carrier; kind:18; commit:/; sort:N", - "label: to_Customer; text: to_Customer; kind:18; commit:/; sort:N", - "label: to_Flight; text: to_Flight; kind:18; commit:/; sort:N", - "label: DraftAdministrativeData; text: DraftAdministrativeData; kind:18; commit:; sort:N", + "label: CurrencyCode; text: CurrencyCode; kind:18; commit:/; sort:B", + "label: BookingStatus; text: BookingStatus; kind:18; commit:/; sort:B", + "label: to_Carrier; text: to_Carrier; kind:18; commit:/; sort:B", + "label: to_Customer; text: to_Customer; kind:18; commit:/; sort:B", + "label: to_Flight; text: to_Flight; kind:18; commit:/; sort:B", + "label: DraftAdministrativeData; text: DraftAdministrativeData; kind:18; commit:; sort:B", ]); }); @@ -341,15 +341,15 @@ describe("contextPath attribute value completion", () => { expect( result.map((item) => completionItemToSnapshot(item)) ).toStrictEqual([ - "label: /TravelService.EntityContainer; text: /TravelService.EntityContainer; kind:19; commit:/; sort:Z", - "label: /TravelService.EntityContainer/Travel; text: /TravelService.EntityContainer/Travel; kind:2; commit:/; sort:B", - "label: /TravelService.EntityContainer/Booking; text: /TravelService.EntityContainer/Booking; kind:2; commit:/; sort:B", - "label: /TravelService.EntityContainer/BookedFlights; text: /TravelService.EntityContainer/BookedFlights; kind:2; commit:/; sort:B", - "label: /TravelService.EntityContainer/BookingSupplement; text: /TravelService.EntityContainer/BookingSupplement; kind:2; commit:/; sort:B", - "label: /Travel; text: /Travel; kind:7; commit:; sort:A", - "label: /Booking; text: /Booking; kind:7; commit:/; sort:A", - "label: /BookedFlights; text: /BookedFlights; kind:7; commit:/; sort:A", - "label: /BookingSupplement; text: /BookingSupplement; kind:7; commit:/; sort:A", + "label: /TravelService.EntityContainer; text: /TravelService.EntityContainer; kind:19; commit:; sort:Z", + "label: /TravelService.EntityContainer/Travel; text: /TravelService.EntityContainer/Travel; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/Booking; text: /TravelService.EntityContainer/Booking; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/BookedFlights; text: /TravelService.EntityContainer/BookedFlights; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/BookingSupplement; text: /TravelService.EntityContainer/BookingSupplement; kind:2; commit:/; sort:E", + "label: /Travel; text: /Travel; kind:7; commit:/; sort:D", + "label: /Booking; text: /Booking; kind:7; commit:/; sort:D", + "label: /BookedFlights; text: /BookedFlights; kind:7; commit:/; sort:D", + "label: /BookingSupplement; text: /BookingSupplement; kind:7; commit:/; sort:D", ]); }); @@ -377,10 +377,10 @@ describe("contextPath attribute value completion", () => { expect( result.map((item) => completionItemToSnapshot(item)) ).toStrictEqual([ - "label: Travel; text: Travel; kind:2; commit:; sort:B", - "label: Booking; text: Booking; kind:2; commit:/; sort:B", - "label: BookedFlights; text: BookedFlights; kind:2; commit:/; sort:B", - "label: BookingSupplement; text: BookingSupplement; kind:2; commit:/; sort:B", + "label: Travel; text: Travel; kind:2; commit:; sort:E", + "label: Booking; text: Booking; kind:2; commit:/; sort:E", + "label: BookedFlights; text: BookedFlights; kind:2; commit:/; sort:E", + "label: BookingSupplement; text: BookingSupplement; kind:2; commit:/; sort:E", ]); }); @@ -391,8 +391,8 @@ describe("contextPath attribute value completion", () => { expect( result.map((item) => completionItemToSnapshot(item)) ).toStrictEqual([ - "label: to_BookSupplement; text: to_BookSupplement; kind:18; commit:/; sort:N", - "label: to_Travel; text: to_Travel; kind:18; commit:; sort:N", + "label: to_BookSupplement; text: to_BookSupplement; kind:18; commit:/; sort:B", + "label: to_Travel; text: to_Travel; kind:18; commit:; sort:B", ]); }); @@ -403,8 +403,8 @@ describe("contextPath attribute value completion", () => { expect( result.map((item) => completionItemToSnapshot(item)) ).toStrictEqual([ - "label: to_BookSupplement; text: to_BookSupplement; kind:18; commit:/; sort:N", - "label: to_Travel; text: to_Travel; kind:18; commit:; sort:N", + "label: to_BookSupplement; text: to_BookSupplement; kind:18; commit:/; sort:B", + "label: to_Travel; text: to_Travel; kind:18; commit:; sort:B", ]); }); @@ -415,7 +415,7 @@ describe("contextPath attribute value completion", () => { expect( result.map((item) => completionItemToSnapshot(item)) ).toStrictEqual([ - "label: to_Travel; text: to_Travel; kind:18; commit:; sort:N", + "label: to_Travel; text: to_Travel; kind:18; commit:; sort:B", ]); }); diff --git a/packages/fe/test/unit/services/completion/providers/meta-path.test.ts b/packages/fe/test/unit/services/completion/providers/meta-path.test.ts index 3d291a912..848b41519 100644 --- a/packages/fe/test/unit/services/completion/providers/meta-path.test.ts +++ b/packages/fe/test/unit/services/completion/providers/meta-path.test.ts @@ -134,13 +134,6 @@ describe("metaPath attribute value completion", () => { expect(result.length).toEqual(0); }); - it("path is absolute - not supported", async function () { - const result = await getCompletionResult( - `` - ); - expect(result.length).toEqual(0); - }); - it("existing navigation segments not allowed", async function () { const result = await getCompletionResult( `` @@ -198,6 +191,15 @@ describe("metaPath attribute value completion", () => { expect(result.length).toEqual(0); }); + it("contextPath is proved and CC is request after absolute path", async function () { + const result = await getCompletionResult( + `` + ); + expect( + result.map((item) => completionItemToSnapshot(item)) + ).toStrictEqual([]); + }); + it("service path is not provided in manifest", async function () { const result = await getCompletionResult( ``, @@ -211,34 +213,6 @@ describe("metaPath attribute value completion", () => { ); expect(result.length).toEqual(0); }); - - it("custom views are empty manifest", async function () { - const result = await getCompletionResult( - ``, - (c) => { - const newContext = { - ...c, - manifestDetails: { ...c.manifestDetails, customViews: {} }, - }; - return newContext; - } - ); - expect(result.length).toEqual(0); - }); - - it("custom view id not determined", async function () { - const result = await getCompletionResult( - ``, - (c) => { - const newContext: Context = { - ...c, - customViewId: "", - }; - return newContext; - } - ); - expect(result.length).toEqual(0); - }); }); describe("Annotation path", () => { @@ -254,6 +228,33 @@ describe("metaPath attribute value completion", () => { "label: @com.sap.vocabularies.UI.v1.Chart#sample1; text: @com.sap.vocabularies.UI.v1.Chart#sample1; kind:12; commit:undefined; sort:", "label: to_Booking; text: to_Booking; kind:18; commit:/; sort:B", "label: to_BookedFlights; text: to_BookedFlights; kind:18; commit:/; sort:B", + "label: /TravelService.EntityContainer; text: /TravelService.EntityContainer; kind:19; commit:; sort:Z", + "label: /TravelService.EntityContainer/Travel; text: /TravelService.EntityContainer/Travel; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/Booking; text: /TravelService.EntityContainer/Booking; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/BookedFlights; text: /TravelService.EntityContainer/BookedFlights; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/BookingSupplement; text: /TravelService.EntityContainer/BookingSupplement; kind:2; commit:/; sort:E", + "label: /Travel; text: /Travel; kind:7; commit:/; sort:D", + "label: /Booking; text: /Booking; kind:7; commit:/; sort:D", + "label: /BookedFlights; text: /BookedFlights; kind:7; commit:/; sort:D", + "label: /BookingSupplement; text: /BookingSupplement; kind:7; commit:/; sort:D", + ]); + }); + it("first segment completion - starting with absolute path", async function () { + const result = await getCompletionResult( + `` + ); + expect( + result.map((item) => completionItemToSnapshot(item)) + ).toStrictEqual([ + "label: TravelService.EntityContainer; text: TravelService.EntityContainer; kind:19; commit:; sort:Z", + "label: TravelService.EntityContainer/Travel; text: TravelService.EntityContainer/Travel; kind:2; commit:/; sort:E", + "label: TravelService.EntityContainer/Booking; text: TravelService.EntityContainer/Booking; kind:2; commit:/; sort:E", + "label: TravelService.EntityContainer/BookedFlights; text: TravelService.EntityContainer/BookedFlights; kind:2; commit:/; sort:E", + "label: TravelService.EntityContainer/BookingSupplement; text: TravelService.EntityContainer/BookingSupplement; kind:2; commit:/; sort:E", + "label: Travel; text: Travel; kind:7; commit:/; sort:D", + "label: Booking; text: Booking; kind:7; commit:/; sort:D", + "label: BookedFlights; text: BookedFlights; kind:7; commit:/; sort:D", + "label: BookingSupplement; text: BookingSupplement; kind:7; commit:/; sort:D", ]); }); it("second segment completion", async function () { @@ -266,6 +267,17 @@ describe("metaPath attribute value completion", () => { "label: to_Travel; text: to_Travel; kind:18; commit:/; sort:B", ]); }); + it("second segment completion - with absolute path", async function () { + const result = await getCompletionResult( + `` + ); + expect( + result.map((item) => completionItemToSnapshot(item)) + ).toStrictEqual([ + "label: @com.sap.vocabularies.UI.v1.Chart; text: @com.sap.vocabularies.UI.v1.Chart; kind:12; commit:undefined; sort:", + "label: @com.sap.vocabularies.UI.v1.Chart#sample1; text: @com.sap.vocabularies.UI.v1.Chart#sample1; kind:12; commit:undefined; sort:", + ]); + }); it("third segment completion", async function () { const result = await getCompletionResult( `` @@ -277,6 +289,30 @@ describe("metaPath attribute value completion", () => { "label: @com.sap.vocabularies.UI.v1.Chart#sample1; text: @com.sap.vocabularies.UI.v1.Chart#sample1; kind:12; commit:undefined; sort:", ]); }); + it("third segment completion - with absolute path", async function () { + const result = await getCompletionResult( + `` + ); + expect( + result.map((item) => completionItemToSnapshot(item)) + ).toStrictEqual([ + "label: @com.sap.vocabularies.UI.v1.Chart; text: @com.sap.vocabularies.UI.v1.Chart; kind:12; commit:undefined; sort:", + "label: @com.sap.vocabularies.UI.v1.Chart#sample1; text: @com.sap.vocabularies.UI.v1.Chart#sample1; kind:12; commit:undefined; sort:", + ]); + }); + it("after entity container ", async function () { + const result = await getCompletionResult( + `` + ); + expect( + result.map((item) => completionItemToSnapshot(item)) + ).toStrictEqual([ + "label: Travel; text: Travel; kind:2; commit:/; sort:E", + "label: Booking; text: Booking; kind:2; commit:/; sort:E", + "label: BookedFlights; text: BookedFlights; kind:2; commit:/; sort:E", + "label: BookingSupplement; text: BookingSupplement; kind:2; commit:/; sort:E", + ]); + }); }); describe("metaPath completion with contextPath provided in manifest", () => { @@ -290,6 +326,34 @@ describe("metaPath attribute value completion", () => { ).toStrictEqual([ "label: @com.sap.vocabularies.UI.v1.Chart; text: @com.sap.vocabularies.UI.v1.Chart; kind:12; commit:undefined; sort:", "label: @com.sap.vocabularies.UI.v1.Chart#sample1; text: @com.sap.vocabularies.UI.v1.Chart#sample1; kind:12; commit:undefined; sort:", + "label: /TravelService.EntityContainer; text: /TravelService.EntityContainer; kind:19; commit:; sort:Z", + "label: /TravelService.EntityContainer/Travel; text: /TravelService.EntityContainer/Travel; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/Booking; text: /TravelService.EntityContainer/Booking; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/BookedFlights; text: /TravelService.EntityContainer/BookedFlights; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/BookingSupplement; text: /TravelService.EntityContainer/BookingSupplement; kind:2; commit:/; sort:E", + "label: /Travel; text: /Travel; kind:7; commit:/; sort:D", + "label: /Booking; text: /Booking; kind:7; commit:/; sort:D", + "label: /BookedFlights; text: /BookedFlights; kind:7; commit:/; sort:D", + "label: /BookingSupplement; text: /BookingSupplement; kind:7; commit:/; sort:D", + ]); + }); + it("term segment completion from entity type - starting with absolute path", async function () { + const result = await getCompletionResult( + ``, + prepareContextAdapter("/Travel") + ); + expect( + result.map((item) => completionItemToSnapshot(item)) + ).toStrictEqual([ + "label: TravelService.EntityContainer; text: TravelService.EntityContainer; kind:19; commit:; sort:Z", + "label: TravelService.EntityContainer/Travel; text: TravelService.EntityContainer/Travel; kind:2; commit:/; sort:E", + "label: TravelService.EntityContainer/Booking; text: TravelService.EntityContainer/Booking; kind:2; commit:/; sort:E", + "label: TravelService.EntityContainer/BookedFlights; text: TravelService.EntityContainer/BookedFlights; kind:2; commit:/; sort:E", + "label: TravelService.EntityContainer/BookingSupplement; text: TravelService.EntityContainer/BookingSupplement; kind:2; commit:/; sort:E", + "label: Travel; text: Travel; kind:7; commit:/; sort:D", + "label: Booking; text: Booking; kind:7; commit:/; sort:D", + "label: BookedFlights; text: BookedFlights; kind:7; commit:/; sort:D", + "label: BookingSupplement; text: BookingSupplement; kind:7; commit:/; sort:D", ]); }); it("segment completion from entity set (nav segments allowed)", async function () { @@ -302,6 +366,27 @@ describe("metaPath attribute value completion", () => { ).toStrictEqual([ "label: to_BookSupplement; text: to_BookSupplement; kind:18; commit:/; sort:B", "label: to_Travel; text: to_Travel; kind:18; commit:/; sort:B", + "label: /TravelService.EntityContainer; text: /TravelService.EntityContainer; kind:19; commit:; sort:Z", + "label: /TravelService.EntityContainer/Travel; text: /TravelService.EntityContainer/Travel; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/Booking; text: /TravelService.EntityContainer/Booking; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/BookedFlights; text: /TravelService.EntityContainer/BookedFlights; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/BookingSupplement; text: /TravelService.EntityContainer/BookingSupplement; kind:2; commit:/; sort:E", + "label: /Travel; text: /Travel; kind:7; commit:/; sort:D", + "label: /Booking; text: /Booking; kind:7; commit:/; sort:D", + "label: /BookedFlights; text: /BookedFlights; kind:7; commit:/; sort:D", + "label: /BookingSupplement; text: /BookingSupplement; kind:7; commit:/; sort:D", + ]); + }); + it("two segment completion with absolute path", async function () { + const result = await getCompletionResult( + ``, + prepareContextAdapter("/TravelService.EntityContainer/Booking") + ); + expect( + result.map((item) => completionItemToSnapshot(item)) + ).toStrictEqual([ + "label: @com.sap.vocabularies.UI.v1.Chart; text: @com.sap.vocabularies.UI.v1.Chart; kind:12; commit:undefined; sort:", + "label: @com.sap.vocabularies.UI.v1.Chart#sample1; text: @com.sap.vocabularies.UI.v1.Chart#sample1; kind:12; commit:undefined; sort:", ]); }); }); @@ -348,30 +433,135 @@ describe("metaPath attribute value completion", () => { "label: to_Booking; text: to_Booking; kind:18; commit:/; sort:B", "label: to_BookedFlights; text: to_BookedFlights; kind:18; commit:/; sort:B", "label: DraftAdministrativeData; text: DraftAdministrativeData; kind:18; commit:/; sort:B", - "label: createdAt; text: createdAt; kind:10; commit:undefined; sort:A", - "label: createdBy; text: createdBy; kind:10; commit:undefined; sort:A", - "label: LastChangedAt; text: LastChangedAt; kind:10; commit:undefined; sort:A", - "label: LastChangedBy; text: LastChangedBy; kind:10; commit:undefined; sort:A", - "label: TravelUUID; text: TravelUUID; kind:10; commit:undefined; sort:A", - "label: TravelID; text: TravelID; kind:10; commit:undefined; sort:A", - "label: BeginDate; text: BeginDate; kind:10; commit:undefined; sort:A", - "label: EndDate; text: EndDate; kind:10; commit:undefined; sort:A", - "label: BookingFee; text: BookingFee; kind:10; commit:undefined; sort:A", - "label: TotalPrice; text: TotalPrice; kind:10; commit:undefined; sort:A", - "label: CurrencyCode_code; text: CurrencyCode_code; kind:10; commit:undefined; sort:A", - "label: Description; text: Description; kind:10; commit:undefined; sort:A", - "label: TravelStatus_code; text: TravelStatus_code; kind:10; commit:undefined; sort:A", - "label: GoGreen; text: GoGreen; kind:10; commit:undefined; sort:A", - "label: GreenFee; text: GreenFee; kind:10; commit:undefined; sort:A", - "label: TreesPlanted; text: TreesPlanted; kind:10; commit:undefined; sort:A", - "label: to_Agency_AgencyID; text: to_Agency_AgencyID; kind:10; commit:undefined; sort:A", - "label: to_Customer_CustomerID; text: to_Customer_CustomerID; kind:10; commit:undefined; sort:A", - "label: acceptEnabled; text: acceptEnabled; kind:10; commit:undefined; sort:A", - "label: rejectEnabled; text: rejectEnabled; kind:10; commit:undefined; sort:A", - "label: deductDiscountEnabled; text: deductDiscountEnabled; kind:10; commit:undefined; sort:A", - "label: IsActiveEntity; text: IsActiveEntity; kind:10; commit:undefined; sort:A", - "label: HasActiveEntity; text: HasActiveEntity; kind:10; commit:undefined; sort:A", - "label: HasDraftEntity; text: HasDraftEntity; kind:10; commit:undefined; sort:A", + "label: createdAt; text: createdAt; kind:10; commit:/; sort:A", + "label: createdBy; text: createdBy; kind:10; commit:/; sort:A", + "label: LastChangedAt; text: LastChangedAt; kind:10; commit:/; sort:A", + "label: LastChangedBy; text: LastChangedBy; kind:10; commit:/; sort:A", + "label: TravelUUID; text: TravelUUID; kind:10; commit:/; sort:A", + "label: TravelID; text: TravelID; kind:10; commit:/; sort:A", + "label: BeginDate; text: BeginDate; kind:10; commit:/; sort:A", + "label: EndDate; text: EndDate; kind:10; commit:/; sort:A", + "label: BookingFee; text: BookingFee; kind:10; commit:/; sort:A", + "label: TotalPrice; text: TotalPrice; kind:10; commit:/; sort:A", + "label: CurrencyCode_code; text: CurrencyCode_code; kind:10; commit:/; sort:A", + "label: Description; text: Description; kind:10; commit:/; sort:A", + "label: TravelStatus_code; text: TravelStatus_code; kind:10; commit:/; sort:A", + "label: GoGreen; text: GoGreen; kind:10; commit:/; sort:A", + "label: GreenFee; text: GreenFee; kind:10; commit:/; sort:A", + "label: TreesPlanted; text: TreesPlanted; kind:10; commit:/; sort:A", + "label: to_Agency_AgencyID; text: to_Agency_AgencyID; kind:10; commit:/; sort:A", + "label: to_Customer_CustomerID; text: to_Customer_CustomerID; kind:10; commit:/; sort:A", + "label: acceptEnabled; text: acceptEnabled; kind:10; commit:/; sort:A", + "label: rejectEnabled; text: rejectEnabled; kind:10; commit:/; sort:A", + "label: deductDiscountEnabled; text: deductDiscountEnabled; kind:10; commit:/; sort:A", + "label: IsActiveEntity; text: IsActiveEntity; kind:10; commit:/; sort:A", + "label: HasActiveEntity; text: HasActiveEntity; kind:10; commit:/; sort:A", + "label: HasDraftEntity; text: HasDraftEntity; kind:10; commit:/; sort:A", + "label: /TravelService.EntityContainer; text: /TravelService.EntityContainer; kind:19; commit:; sort:Z", + "label: /TravelService.EntityContainer/Travel; text: /TravelService.EntityContainer/Travel; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/HighestTotal; text: /TravelService.EntityContainer/HighestTotal; kind:2; commit:; sort:E", + "label: /TravelService.EntityContainer/Currencies; text: /TravelService.EntityContainer/Currencies; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/TravelStatus; text: /TravelService.EntityContainer/TravelStatus; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/TravelAgency; text: /TravelService.EntityContainer/TravelAgency; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/Passenger; text: /TravelService.EntityContainer/Passenger; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/Booking; text: /TravelService.EntityContainer/Booking; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/BookedFlights; text: /TravelService.EntityContainer/BookedFlights; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/Countries; text: /TravelService.EntityContainer/Countries; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/BookingStatus; text: /TravelService.EntityContainer/BookingStatus; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/BookingSupplement; text: /TravelService.EntityContainer/BookingSupplement; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/Airline; text: /TravelService.EntityContainer/Airline; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/Flight; text: /TravelService.EntityContainer/Flight; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/Supplement; text: /TravelService.EntityContainer/Supplement; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/FlightConnection; text: /TravelService.EntityContainer/FlightConnection; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/SupplementType; text: /TravelService.EntityContainer/SupplementType; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/Airport; text: /TravelService.EntityContainer/Airport; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/Currencies_texts; text: /TravelService.EntityContainer/Currencies_texts; kind:2; commit:; sort:E", + "label: /TravelService.EntityContainer/TravelStatus_texts; text: /TravelService.EntityContainer/TravelStatus_texts; kind:2; commit:; sort:E", + "label: /TravelService.EntityContainer/Countries_texts; text: /TravelService.EntityContainer/Countries_texts; kind:2; commit:; sort:E", + "label: /TravelService.EntityContainer/BookingStatus_texts; text: /TravelService.EntityContainer/BookingStatus_texts; kind:2; commit:; sort:E", + "label: /TravelService.EntityContainer/Supplement_texts; text: /TravelService.EntityContainer/Supplement_texts; kind:2; commit:; sort:E", + "label: /TravelService.EntityContainer/SupplementType_texts; text: /TravelService.EntityContainer/SupplementType_texts; kind:2; commit:; sort:E", + "label: /Travel; text: /Travel; kind:7; commit:/; sort:D", + "label: /HighestTotal; text: /HighestTotal; kind:7; commit:; sort:D", + "label: /Currencies; text: /Currencies; kind:7; commit:/; sort:D", + "label: /TravelStatus; text: /TravelStatus; kind:7; commit:/; sort:D", + "label: /TravelAgency; text: /TravelAgency; kind:7; commit:/; sort:D", + "label: /Passenger; text: /Passenger; kind:7; commit:/; sort:D", + "label: /Booking; text: /Booking; kind:7; commit:/; sort:D", + "label: /BookedFlights; text: /BookedFlights; kind:7; commit:/; sort:D", + "label: /Countries; text: /Countries; kind:7; commit:/; sort:D", + "label: /BookingStatus; text: /BookingStatus; kind:7; commit:/; sort:D", + "label: /BookingSupplement; text: /BookingSupplement; kind:7; commit:/; sort:D", + "label: /Airline; text: /Airline; kind:7; commit:/; sort:D", + "label: /Flight; text: /Flight; kind:7; commit:/; sort:D", + "label: /Supplement; text: /Supplement; kind:7; commit:/; sort:D", + "label: /FlightConnection; text: /FlightConnection; kind:7; commit:/; sort:D", + "label: /SupplementType; text: /SupplementType; kind:7; commit:/; sort:D", + "label: /Airport; text: /Airport; kind:7; commit:/; sort:D", + "label: /DraftAdministrativeData; text: /DraftAdministrativeData; kind:7; commit:; sort:D", + "label: /Currencies_texts; text: /Currencies_texts; kind:7; commit:; sort:D", + "label: /TravelStatus_texts; text: /TravelStatus_texts; kind:7; commit:; sort:D", + "label: /Countries_texts; text: /Countries_texts; kind:7; commit:; sort:D", + "label: /BookingStatus_texts; text: /BookingStatus_texts; kind:7; commit:; sort:D", + "label: /Supplement_texts; text: /Supplement_texts; kind:7; commit:; sort:D", + "label: /SupplementType_texts; text: /SupplementType_texts; kind:7; commit:; sort:D", + ]); + }); + it("first segment completion - starting with absolute path", async function () { + const result = await getCompletionResult( + `` + ); + expect( + result.map((item) => completionItemToSnapshot(item)) + ).toStrictEqual([ + "label: TravelService.EntityContainer; text: TravelService.EntityContainer; kind:19; commit:; sort:Z", + "label: TravelService.EntityContainer/Travel; text: TravelService.EntityContainer/Travel; kind:2; commit:/; sort:E", + "label: TravelService.EntityContainer/HighestTotal; text: TravelService.EntityContainer/HighestTotal; kind:2; commit:; sort:E", + "label: TravelService.EntityContainer/Currencies; text: TravelService.EntityContainer/Currencies; kind:2; commit:/; sort:E", + "label: TravelService.EntityContainer/TravelStatus; text: TravelService.EntityContainer/TravelStatus; kind:2; commit:/; sort:E", + "label: TravelService.EntityContainer/TravelAgency; text: TravelService.EntityContainer/TravelAgency; kind:2; commit:/; sort:E", + "label: TravelService.EntityContainer/Passenger; text: TravelService.EntityContainer/Passenger; kind:2; commit:/; sort:E", + "label: TravelService.EntityContainer/Booking; text: TravelService.EntityContainer/Booking; kind:2; commit:/; sort:E", + "label: TravelService.EntityContainer/BookedFlights; text: TravelService.EntityContainer/BookedFlights; kind:2; commit:/; sort:E", + "label: TravelService.EntityContainer/Countries; text: TravelService.EntityContainer/Countries; kind:2; commit:/; sort:E", + "label: TravelService.EntityContainer/BookingStatus; text: TravelService.EntityContainer/BookingStatus; kind:2; commit:/; sort:E", + "label: TravelService.EntityContainer/BookingSupplement; text: TravelService.EntityContainer/BookingSupplement; kind:2; commit:/; sort:E", + "label: TravelService.EntityContainer/Airline; text: TravelService.EntityContainer/Airline; kind:2; commit:/; sort:E", + "label: TravelService.EntityContainer/Flight; text: TravelService.EntityContainer/Flight; kind:2; commit:/; sort:E", + "label: TravelService.EntityContainer/Supplement; text: TravelService.EntityContainer/Supplement; kind:2; commit:/; sort:E", + "label: TravelService.EntityContainer/FlightConnection; text: TravelService.EntityContainer/FlightConnection; kind:2; commit:/; sort:E", + "label: TravelService.EntityContainer/SupplementType; text: TravelService.EntityContainer/SupplementType; kind:2; commit:/; sort:E", + "label: TravelService.EntityContainer/Airport; text: TravelService.EntityContainer/Airport; kind:2; commit:/; sort:E", + "label: TravelService.EntityContainer/Currencies_texts; text: TravelService.EntityContainer/Currencies_texts; kind:2; commit:; sort:E", + "label: TravelService.EntityContainer/TravelStatus_texts; text: TravelService.EntityContainer/TravelStatus_texts; kind:2; commit:; sort:E", + "label: TravelService.EntityContainer/Countries_texts; text: TravelService.EntityContainer/Countries_texts; kind:2; commit:; sort:E", + "label: TravelService.EntityContainer/BookingStatus_texts; text: TravelService.EntityContainer/BookingStatus_texts; kind:2; commit:; sort:E", + "label: TravelService.EntityContainer/Supplement_texts; text: TravelService.EntityContainer/Supplement_texts; kind:2; commit:; sort:E", + "label: TravelService.EntityContainer/SupplementType_texts; text: TravelService.EntityContainer/SupplementType_texts; kind:2; commit:; sort:E", + "label: Travel; text: Travel; kind:7; commit:/; sort:D", + "label: HighestTotal; text: HighestTotal; kind:7; commit:; sort:D", + "label: Currencies; text: Currencies; kind:7; commit:/; sort:D", + "label: TravelStatus; text: TravelStatus; kind:7; commit:/; sort:D", + "label: TravelAgency; text: TravelAgency; kind:7; commit:/; sort:D", + "label: Passenger; text: Passenger; kind:7; commit:/; sort:D", + "label: Booking; text: Booking; kind:7; commit:/; sort:D", + "label: BookedFlights; text: BookedFlights; kind:7; commit:/; sort:D", + "label: Countries; text: Countries; kind:7; commit:/; sort:D", + "label: BookingStatus; text: BookingStatus; kind:7; commit:/; sort:D", + "label: BookingSupplement; text: BookingSupplement; kind:7; commit:/; sort:D", + "label: Airline; text: Airline; kind:7; commit:/; sort:D", + "label: Flight; text: Flight; kind:7; commit:/; sort:D", + "label: Supplement; text: Supplement; kind:7; commit:/; sort:D", + "label: FlightConnection; text: FlightConnection; kind:7; commit:/; sort:D", + "label: SupplementType; text: SupplementType; kind:7; commit:/; sort:D", + "label: Airport; text: Airport; kind:7; commit:/; sort:D", + "label: DraftAdministrativeData; text: DraftAdministrativeData; kind:7; commit:; sort:D", + "label: Currencies_texts; text: Currencies_texts; kind:7; commit:; sort:D", + "label: TravelStatus_texts; text: TravelStatus_texts; kind:7; commit:; sort:D", + "label: Countries_texts; text: Countries_texts; kind:7; commit:; sort:D", + "label: BookingStatus_texts; text: BookingStatus_texts; kind:7; commit:; sort:D", + "label: Supplement_texts; text: Supplement_texts; kind:7; commit:; sort:D", + "label: SupplementType_texts; text: SupplementType_texts; kind:7; commit:; sort:D", ]); }); it("second segment completion", async function () { @@ -388,27 +578,67 @@ describe("metaPath attribute value completion", () => { "label: to_Travel; text: to_Travel; kind:18; commit:/; sort:B", "label: to_Flight; text: to_Flight; kind:18; commit:/; sort:B", "label: DraftAdministrativeData; text: DraftAdministrativeData; kind:18; commit:/; sort:B", - "label: createdAt; text: createdAt; kind:10; commit:undefined; sort:A", - "label: createdBy; text: createdBy; kind:10; commit:undefined; sort:A", - "label: LastChangedAt; text: LastChangedAt; kind:10; commit:undefined; sort:A", - "label: LastChangedBy; text: LastChangedBy; kind:10; commit:undefined; sort:A", - "label: BookingUUID; text: BookingUUID; kind:10; commit:undefined; sort:A", - "label: BookingID; text: BookingID; kind:10; commit:undefined; sort:A", - "label: BookingDate; text: BookingDate; kind:10; commit:undefined; sort:A", - "label: ConnectionID; text: ConnectionID; kind:10; commit:undefined; sort:A", - "label: FlightDate; text: FlightDate; kind:10; commit:undefined; sort:A", - "label: FlightPrice; text: FlightPrice; kind:10; commit:undefined; sort:A", - "label: CurrencyCode_code; text: CurrencyCode_code; kind:10; commit:undefined; sort:A", - "label: BookingStatus_code; text: BookingStatus_code; kind:10; commit:undefined; sort:A", - "label: to_Carrier_AirlineID; text: to_Carrier_AirlineID; kind:10; commit:undefined; sort:A", - "label: to_Customer_CustomerID; text: to_Customer_CustomerID; kind:10; commit:undefined; sort:A", - "label: to_Travel_TravelUUID; text: to_Travel_TravelUUID; kind:10; commit:undefined; sort:A", - "label: criticality; text: criticality; kind:10; commit:undefined; sort:A", - "label: BookedFlights; text: BookedFlights; kind:10; commit:undefined; sort:A", - "label: EligibleForPrime; text: EligibleForPrime; kind:10; commit:undefined; sort:A", - "label: IsActiveEntity; text: IsActiveEntity; kind:10; commit:undefined; sort:A", - "label: HasActiveEntity; text: HasActiveEntity; kind:10; commit:undefined; sort:A", - "label: HasDraftEntity; text: HasDraftEntity; kind:10; commit:undefined; sort:A", + "label: createdAt; text: createdAt; kind:10; commit:/; sort:A", + "label: createdBy; text: createdBy; kind:10; commit:/; sort:A", + "label: LastChangedAt; text: LastChangedAt; kind:10; commit:/; sort:A", + "label: LastChangedBy; text: LastChangedBy; kind:10; commit:/; sort:A", + "label: BookingUUID; text: BookingUUID; kind:10; commit:/; sort:A", + "label: BookingID; text: BookingID; kind:10; commit:/; sort:A", + "label: BookingDate; text: BookingDate; kind:10; commit:/; sort:A", + "label: ConnectionID; text: ConnectionID; kind:10; commit:/; sort:A", + "label: FlightDate; text: FlightDate; kind:10; commit:/; sort:A", + "label: FlightPrice; text: FlightPrice; kind:10; commit:/; sort:A", + "label: CurrencyCode_code; text: CurrencyCode_code; kind:10; commit:/; sort:A", + "label: BookingStatus_code; text: BookingStatus_code; kind:10; commit:/; sort:A", + "label: to_Carrier_AirlineID; text: to_Carrier_AirlineID; kind:10; commit:/; sort:A", + "label: to_Customer_CustomerID; text: to_Customer_CustomerID; kind:10; commit:/; sort:A", + "label: to_Travel_TravelUUID; text: to_Travel_TravelUUID; kind:10; commit:/; sort:A", + "label: criticality; text: criticality; kind:10; commit:/; sort:A", + "label: BookedFlights; text: BookedFlights; kind:10; commit:/; sort:A", + "label: EligibleForPrime; text: EligibleForPrime; kind:10; commit:/; sort:A", + "label: IsActiveEntity; text: IsActiveEntity; kind:10; commit:/; sort:A", + "label: HasActiveEntity; text: HasActiveEntity; kind:10; commit:/; sort:A", + "label: HasDraftEntity; text: HasDraftEntity; kind:10; commit:/; sort:A", + ]); + }); + it("second segment completion - with absolute path", async function () { + const result = await getCompletionResult( + `` + ); + expect( + result.map((item) => completionItemToSnapshot(item)) + ).toStrictEqual([ + "label: CurrencyCode; text: CurrencyCode; kind:18; commit:/; sort:B", + "label: TravelStatus; text: TravelStatus; kind:18; commit:/; sort:B", + "label: to_Agency; text: to_Agency; kind:18; commit:/; sort:B", + "label: to_Customer; text: to_Customer; kind:18; commit:/; sort:B", + "label: to_Booking; text: to_Booking; kind:18; commit:/; sort:B", + "label: to_BookedFlights; text: to_BookedFlights; kind:18; commit:/; sort:B", + "label: DraftAdministrativeData; text: DraftAdministrativeData; kind:18; commit:/; sort:B", + "label: createdAt; text: createdAt; kind:10; commit:/; sort:A", + "label: createdBy; text: createdBy; kind:10; commit:/; sort:A", + "label: LastChangedAt; text: LastChangedAt; kind:10; commit:/; sort:A", + "label: LastChangedBy; text: LastChangedBy; kind:10; commit:/; sort:A", + "label: TravelUUID; text: TravelUUID; kind:10; commit:/; sort:A", + "label: TravelID; text: TravelID; kind:10; commit:/; sort:A", + "label: BeginDate; text: BeginDate; kind:10; commit:/; sort:A", + "label: EndDate; text: EndDate; kind:10; commit:/; sort:A", + "label: BookingFee; text: BookingFee; kind:10; commit:/; sort:A", + "label: TotalPrice; text: TotalPrice; kind:10; commit:/; sort:A", + "label: CurrencyCode_code; text: CurrencyCode_code; kind:10; commit:/; sort:A", + "label: Description; text: Description; kind:10; commit:/; sort:A", + "label: TravelStatus_code; text: TravelStatus_code; kind:10; commit:/; sort:A", + "label: GoGreen; text: GoGreen; kind:10; commit:/; sort:A", + "label: GreenFee; text: GreenFee; kind:10; commit:/; sort:A", + "label: TreesPlanted; text: TreesPlanted; kind:10; commit:/; sort:A", + "label: to_Agency_AgencyID; text: to_Agency_AgencyID; kind:10; commit:/; sort:A", + "label: to_Customer_CustomerID; text: to_Customer_CustomerID; kind:10; commit:/; sort:A", + "label: acceptEnabled; text: acceptEnabled; kind:10; commit:/; sort:A", + "label: rejectEnabled; text: rejectEnabled; kind:10; commit:/; sort:A", + "label: deductDiscountEnabled; text: deductDiscountEnabled; kind:10; commit:/; sort:A", + "label: IsActiveEntity; text: IsActiveEntity; kind:10; commit:/; sort:A", + "label: HasActiveEntity; text: HasActiveEntity; kind:10; commit:/; sort:A", + "label: HasDraftEntity; text: HasDraftEntity; kind:10; commit:/; sort:A", ]); }); it("third segment completion", async function () { @@ -423,30 +653,49 @@ describe("metaPath attribute value completion", () => { "label: to_Agency; text: to_Agency; kind:18; commit:/; sort:B", "label: to_Customer; text: to_Customer; kind:18; commit:/; sort:B", "label: DraftAdministrativeData; text: DraftAdministrativeData; kind:18; commit:/; sort:B", - "label: createdAt; text: createdAt; kind:10; commit:undefined; sort:A", - "label: createdBy; text: createdBy; kind:10; commit:undefined; sort:A", - "label: LastChangedAt; text: LastChangedAt; kind:10; commit:undefined; sort:A", - "label: LastChangedBy; text: LastChangedBy; kind:10; commit:undefined; sort:A", - "label: TravelUUID; text: TravelUUID; kind:10; commit:undefined; sort:A", - "label: TravelID; text: TravelID; kind:10; commit:undefined; sort:A", - "label: BeginDate; text: BeginDate; kind:10; commit:undefined; sort:A", - "label: EndDate; text: EndDate; kind:10; commit:undefined; sort:A", - "label: BookingFee; text: BookingFee; kind:10; commit:undefined; sort:A", - "label: TotalPrice; text: TotalPrice; kind:10; commit:undefined; sort:A", - "label: CurrencyCode_code; text: CurrencyCode_code; kind:10; commit:undefined; sort:A", - "label: Description; text: Description; kind:10; commit:undefined; sort:A", - "label: TravelStatus_code; text: TravelStatus_code; kind:10; commit:undefined; sort:A", - "label: GoGreen; text: GoGreen; kind:10; commit:undefined; sort:A", - "label: GreenFee; text: GreenFee; kind:10; commit:undefined; sort:A", - "label: TreesPlanted; text: TreesPlanted; kind:10; commit:undefined; sort:A", - "label: to_Agency_AgencyID; text: to_Agency_AgencyID; kind:10; commit:undefined; sort:A", - "label: to_Customer_CustomerID; text: to_Customer_CustomerID; kind:10; commit:undefined; sort:A", - "label: acceptEnabled; text: acceptEnabled; kind:10; commit:undefined; sort:A", - "label: rejectEnabled; text: rejectEnabled; kind:10; commit:undefined; sort:A", - "label: deductDiscountEnabled; text: deductDiscountEnabled; kind:10; commit:undefined; sort:A", - "label: IsActiveEntity; text: IsActiveEntity; kind:10; commit:undefined; sort:A", - "label: HasActiveEntity; text: HasActiveEntity; kind:10; commit:undefined; sort:A", - "label: HasDraftEntity; text: HasDraftEntity; kind:10; commit:undefined; sort:A", + "label: createdAt; text: createdAt; kind:10; commit:/; sort:A", + "label: createdBy; text: createdBy; kind:10; commit:/; sort:A", + "label: LastChangedAt; text: LastChangedAt; kind:10; commit:/; sort:A", + "label: LastChangedBy; text: LastChangedBy; kind:10; commit:/; sort:A", + "label: TravelUUID; text: TravelUUID; kind:10; commit:/; sort:A", + "label: TravelID; text: TravelID; kind:10; commit:/; sort:A", + "label: BeginDate; text: BeginDate; kind:10; commit:/; sort:A", + "label: EndDate; text: EndDate; kind:10; commit:/; sort:A", + "label: BookingFee; text: BookingFee; kind:10; commit:/; sort:A", + "label: TotalPrice; text: TotalPrice; kind:10; commit:/; sort:A", + "label: CurrencyCode_code; text: CurrencyCode_code; kind:10; commit:/; sort:A", + "label: Description; text: Description; kind:10; commit:/; sort:A", + "label: TravelStatus_code; text: TravelStatus_code; kind:10; commit:/; sort:A", + "label: GoGreen; text: GoGreen; kind:10; commit:/; sort:A", + "label: GreenFee; text: GreenFee; kind:10; commit:/; sort:A", + "label: TreesPlanted; text: TreesPlanted; kind:10; commit:/; sort:A", + "label: to_Agency_AgencyID; text: to_Agency_AgencyID; kind:10; commit:/; sort:A", + "label: to_Customer_CustomerID; text: to_Customer_CustomerID; kind:10; commit:/; sort:A", + "label: acceptEnabled; text: acceptEnabled; kind:10; commit:/; sort:A", + "label: rejectEnabled; text: rejectEnabled; kind:10; commit:/; sort:A", + "label: deductDiscountEnabled; text: deductDiscountEnabled; kind:10; commit:/; sort:A", + "label: IsActiveEntity; text: IsActiveEntity; kind:10; commit:/; sort:A", + "label: HasActiveEntity; text: HasActiveEntity; kind:10; commit:/; sort:A", + "label: HasDraftEntity; text: HasDraftEntity; kind:10; commit:/; sort:A", + ]); + }); + it("third segment completion - with absolute path", async function () { + const result = await getCompletionResult( + `` + ); + expect( + result.map((item) => completionItemToSnapshot(item)) + ).toStrictEqual([ + "label: CountryCode; text: CountryCode; kind:18; commit:/; sort:B", + "label: AgencyID; text: AgencyID; kind:10; commit:/; sort:A", + "label: Name; text: Name; kind:10; commit:/; sort:A", + "label: Street; text: Street; kind:10; commit:/; sort:A", + "label: PostalCode; text: PostalCode; kind:10; commit:/; sort:A", + "label: City; text: City; kind:10; commit:/; sort:A", + "label: CountryCode_code; text: CountryCode_code; kind:10; commit:/; sort:A", + "label: PhoneNumber; text: PhoneNumber; kind:10; commit:/; sort:A", + "label: EMailAddress; text: EMailAddress; kind:10; commit:/; sort:A", + "label: WebAddress; text: WebAddress; kind:10; commit:/; sort:A", ]); }); }); @@ -459,30 +708,30 @@ describe("metaPath attribute value completion", () => { expect( result.map((item) => completionItemToSnapshot(item)) ).toStrictEqual([ - "label: createdAt; text: createdAt; kind:10; commit:undefined; sort:A", - "label: createdBy; text: createdBy; kind:10; commit:undefined; sort:A", - "label: LastChangedAt; text: LastChangedAt; kind:10; commit:undefined; sort:A", - "label: LastChangedBy; text: LastChangedBy; kind:10; commit:undefined; sort:A", - "label: TravelUUID; text: TravelUUID; kind:10; commit:undefined; sort:A", - "label: TravelID; text: TravelID; kind:10; commit:undefined; sort:A", - "label: BeginDate; text: BeginDate; kind:10; commit:undefined; sort:A", - "label: EndDate; text: EndDate; kind:10; commit:undefined; sort:A", - "label: BookingFee; text: BookingFee; kind:10; commit:undefined; sort:A", - "label: TotalPrice; text: TotalPrice; kind:10; commit:undefined; sort:A", - "label: CurrencyCode_code; text: CurrencyCode_code; kind:10; commit:undefined; sort:A", - "label: Description; text: Description; kind:10; commit:undefined; sort:A", - "label: TravelStatus_code; text: TravelStatus_code; kind:10; commit:undefined; sort:A", - "label: GoGreen; text: GoGreen; kind:10; commit:undefined; sort:A", - "label: GreenFee; text: GreenFee; kind:10; commit:undefined; sort:A", - "label: TreesPlanted; text: TreesPlanted; kind:10; commit:undefined; sort:A", - "label: to_Agency_AgencyID; text: to_Agency_AgencyID; kind:10; commit:undefined; sort:A", - "label: to_Customer_CustomerID; text: to_Customer_CustomerID; kind:10; commit:undefined; sort:A", - "label: acceptEnabled; text: acceptEnabled; kind:10; commit:undefined; sort:A", - "label: rejectEnabled; text: rejectEnabled; kind:10; commit:undefined; sort:A", - "label: deductDiscountEnabled; text: deductDiscountEnabled; kind:10; commit:undefined; sort:A", - "label: IsActiveEntity; text: IsActiveEntity; kind:10; commit:undefined; sort:A", - "label: HasActiveEntity; text: HasActiveEntity; kind:10; commit:undefined; sort:A", - "label: HasDraftEntity; text: HasDraftEntity; kind:10; commit:undefined; sort:A", + "label: createdAt; text: createdAt; kind:10; commit:/; sort:A", + "label: createdBy; text: createdBy; kind:10; commit:/; sort:A", + "label: LastChangedAt; text: LastChangedAt; kind:10; commit:/; sort:A", + "label: LastChangedBy; text: LastChangedBy; kind:10; commit:/; sort:A", + "label: TravelUUID; text: TravelUUID; kind:10; commit:/; sort:A", + "label: TravelID; text: TravelID; kind:10; commit:/; sort:A", + "label: BeginDate; text: BeginDate; kind:10; commit:/; sort:A", + "label: EndDate; text: EndDate; kind:10; commit:/; sort:A", + "label: BookingFee; text: BookingFee; kind:10; commit:/; sort:A", + "label: TotalPrice; text: TotalPrice; kind:10; commit:/; sort:A", + "label: CurrencyCode_code; text: CurrencyCode_code; kind:10; commit:/; sort:A", + "label: Description; text: Description; kind:10; commit:/; sort:A", + "label: TravelStatus_code; text: TravelStatus_code; kind:10; commit:/; sort:A", + "label: GoGreen; text: GoGreen; kind:10; commit:/; sort:A", + "label: GreenFee; text: GreenFee; kind:10; commit:/; sort:A", + "label: TreesPlanted; text: TreesPlanted; kind:10; commit:/; sort:A", + "label: to_Agency_AgencyID; text: to_Agency_AgencyID; kind:10; commit:/; sort:A", + "label: to_Customer_CustomerID; text: to_Customer_CustomerID; kind:10; commit:/; sort:A", + "label: acceptEnabled; text: acceptEnabled; kind:10; commit:/; sort:A", + "label: rejectEnabled; text: rejectEnabled; kind:10; commit:/; sort:A", + "label: deductDiscountEnabled; text: deductDiscountEnabled; kind:10; commit:/; sort:A", + "label: IsActiveEntity; text: IsActiveEntity; kind:10; commit:/; sort:A", + "label: HasActiveEntity; text: HasActiveEntity; kind:10; commit:/; sort:A", + "label: HasDraftEntity; text: HasDraftEntity; kind:10; commit:/; sort:A", ]); }); it("property segment completion from entity set", async function () { @@ -492,32 +741,86 @@ describe("metaPath attribute value completion", () => { expect( result.map((item) => completionItemToSnapshot(item)) ).toStrictEqual([ - "label: createdAt; text: createdAt; kind:10; commit:undefined; sort:A", - "label: createdBy; text: createdBy; kind:10; commit:undefined; sort:A", - "label: LastChangedAt; text: LastChangedAt; kind:10; commit:undefined; sort:A", - "label: LastChangedBy; text: LastChangedBy; kind:10; commit:undefined; sort:A", - "label: TravelUUID; text: TravelUUID; kind:10; commit:undefined; sort:A", - "label: TravelID; text: TravelID; kind:10; commit:undefined; sort:A", - "label: BeginDate; text: BeginDate; kind:10; commit:undefined; sort:A", - "label: EndDate; text: EndDate; kind:10; commit:undefined; sort:A", - "label: BookingFee; text: BookingFee; kind:10; commit:undefined; sort:A", - "label: TotalPrice; text: TotalPrice; kind:10; commit:undefined; sort:A", - "label: CurrencyCode_code; text: CurrencyCode_code; kind:10; commit:undefined; sort:A", - "label: Description; text: Description; kind:10; commit:undefined; sort:A", - "label: TravelStatus_code; text: TravelStatus_code; kind:10; commit:undefined; sort:A", - "label: GoGreen; text: GoGreen; kind:10; commit:undefined; sort:A", - "label: GreenFee; text: GreenFee; kind:10; commit:undefined; sort:A", - "label: TreesPlanted; text: TreesPlanted; kind:10; commit:undefined; sort:A", - "label: to_Agency_AgencyID; text: to_Agency_AgencyID; kind:10; commit:undefined; sort:A", - "label: to_Customer_CustomerID; text: to_Customer_CustomerID; kind:10; commit:undefined; sort:A", - "label: acceptEnabled; text: acceptEnabled; kind:10; commit:undefined; sort:A", - "label: rejectEnabled; text: rejectEnabled; kind:10; commit:undefined; sort:A", - "label: deductDiscountEnabled; text: deductDiscountEnabled; kind:10; commit:undefined; sort:A", - "label: IsActiveEntity; text: IsActiveEntity; kind:10; commit:undefined; sort:A", - "label: HasActiveEntity; text: HasActiveEntity; kind:10; commit:undefined; sort:A", - "label: HasDraftEntity; text: HasDraftEntity; kind:10; commit:undefined; sort:A", + "label: createdAt; text: createdAt; kind:10; commit:/; sort:A", + "label: createdBy; text: createdBy; kind:10; commit:/; sort:A", + "label: LastChangedAt; text: LastChangedAt; kind:10; commit:/; sort:A", + "label: LastChangedBy; text: LastChangedBy; kind:10; commit:/; sort:A", + "label: TravelUUID; text: TravelUUID; kind:10; commit:/; sort:A", + "label: TravelID; text: TravelID; kind:10; commit:/; sort:A", + "label: BeginDate; text: BeginDate; kind:10; commit:/; sort:A", + "label: EndDate; text: EndDate; kind:10; commit:/; sort:A", + "label: BookingFee; text: BookingFee; kind:10; commit:/; sort:A", + "label: TotalPrice; text: TotalPrice; kind:10; commit:/; sort:A", + "label: CurrencyCode_code; text: CurrencyCode_code; kind:10; commit:/; sort:A", + "label: Description; text: Description; kind:10; commit:/; sort:A", + "label: TravelStatus_code; text: TravelStatus_code; kind:10; commit:/; sort:A", + "label: GoGreen; text: GoGreen; kind:10; commit:/; sort:A", + "label: GreenFee; text: GreenFee; kind:10; commit:/; sort:A", + "label: TreesPlanted; text: TreesPlanted; kind:10; commit:/; sort:A", + "label: to_Agency_AgencyID; text: to_Agency_AgencyID; kind:10; commit:/; sort:A", + "label: to_Customer_CustomerID; text: to_Customer_CustomerID; kind:10; commit:/; sort:A", + "label: acceptEnabled; text: acceptEnabled; kind:10; commit:/; sort:A", + "label: rejectEnabled; text: rejectEnabled; kind:10; commit:/; sort:A", + "label: deductDiscountEnabled; text: deductDiscountEnabled; kind:10; commit:/; sort:A", + "label: IsActiveEntity; text: IsActiveEntity; kind:10; commit:/; sort:A", + "label: HasActiveEntity; text: HasActiveEntity; kind:10; commit:/; sort:A", + "label: HasDraftEntity; text: HasDraftEntity; kind:10; commit:/; sort:A", ]); }); }); }); + + describe("custom views", () => { + it("custom views are empty manifest", async function () { + const result = await getCompletionResult( + ``, + (c) => { + const newContext = { + ...c, + manifestDetails: { ...c.manifestDetails, customViews: {} }, + }; + return newContext; + } + ); + expect( + result.map((item) => completionItemToSnapshot(item)) + ).toStrictEqual([ + "label: /TravelService.EntityContainer; text: /TravelService.EntityContainer; kind:19; commit:; sort:Z", + "label: /TravelService.EntityContainer/Travel; text: /TravelService.EntityContainer/Travel; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/Booking; text: /TravelService.EntityContainer/Booking; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/BookedFlights; text: /TravelService.EntityContainer/BookedFlights; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/BookingSupplement; text: /TravelService.EntityContainer/BookingSupplement; kind:2; commit:/; sort:E", + "label: /Travel; text: /Travel; kind:7; commit:/; sort:D", + "label: /Booking; text: /Booking; kind:7; commit:/; sort:D", + "label: /BookedFlights; text: /BookedFlights; kind:7; commit:/; sort:D", + "label: /BookingSupplement; text: /BookingSupplement; kind:7; commit:/; sort:D", + ]); + }); + + it("custom view id not determined", async function () { + const result = await getCompletionResult( + ``, + (c) => { + const newContext: Context = { + ...c, + customViewId: "", + }; + return newContext; + } + ); + expect( + result.map((item) => completionItemToSnapshot(item)) + ).toStrictEqual([ + "label: /TravelService.EntityContainer; text: /TravelService.EntityContainer; kind:19; commit:; sort:Z", + "label: /TravelService.EntityContainer/Travel; text: /TravelService.EntityContainer/Travel; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/Booking; text: /TravelService.EntityContainer/Booking; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/BookedFlights; text: /TravelService.EntityContainer/BookedFlights; kind:2; commit:/; sort:E", + "label: /TravelService.EntityContainer/BookingSupplement; text: /TravelService.EntityContainer/BookingSupplement; kind:2; commit:/; sort:E", + "label: /Travel; text: /Travel; kind:7; commit:/; sort:D", + "label: /Booking; text: /Booking; kind:7; commit:/; sort:D", + "label: /BookedFlights; text: /BookedFlights; kind:7; commit:/; sort:D", + "label: /BookingSupplement; text: /BookingSupplement; kind:7; commit:/; sort:D", + ]); + }); + }); }); diff --git a/packages/fe/test/unit/services/diagnostics/validators/missing-entity-set.test.ts b/packages/fe/test/unit/services/diagnostics/validators/missing-entity-set.test.ts index bcfde4a31..b1a804c9c 100644 --- a/packages/fe/test/unit/services/diagnostics/validators/missing-entity-set.test.ts +++ b/packages/fe/test/unit/services/diagnostics/validators/missing-entity-set.test.ts @@ -76,7 +76,7 @@ describe("missing entitySet validation", () => { } ); expect(result.map((item) => issueToSnapshot(item))).toStrictEqual([ - "kind: MissingEntitySet; text: EntitySet or contextPath for the current view are not defined in application manifest. Attribute value completion and diagnostics is not possible if EntitySet or contextPath are not defined or defined dynamically in controllers; severity:info; offset:344-354", + "kind: MissingEntitySet; text: Path cannot be identified: use absolute path or define contextPath; severity:warn; offset:344-354", ]); }); @@ -87,7 +87,7 @@ describe("missing entitySet validation", () => { (c) => ({ ...c, manifestDetails: undefined } as any) ); expect(result.map((item) => issueToSnapshot(item))).toStrictEqual([ - "kind: MissingEntitySet; text: EntitySet or contextPath for the current view are not defined in application manifest. Attribute value completion and diagnostics is not possible if EntitySet or contextPath are not defined or defined dynamically in controllers; severity:info; offset:344-349", + "kind: MissingEntitySet; text: Path cannot be identified: use absolute path or define contextPath; severity:warn; offset:344-349", ]); }); @@ -103,7 +103,7 @@ describe("missing entitySet validation", () => { } ); expect(result.map((item) => issueToSnapshot(item))).toStrictEqual([ - "kind: MissingEntitySet; text: EntitySet or contextPath for the current view are not defined in application manifest. Attribute value completion and diagnostics is not possible if EntitySet or contextPath are not defined or defined dynamically in controllers; severity:info; offset:344-349", + "kind: MissingEntitySet; text: Path cannot be identified: use absolute path or define contextPath; severity:warn; offset:344-349", ]); }); }); @@ -123,6 +123,13 @@ describe("missing entitySet validation", () => { expect(result.length).toEqual(0); }); + it("attribute value is absolute", async function () { + const result = await validateView( + `` + ); + expect(result.length).toEqual(0); + }); + it("contextPath exists", async function () { const result = await validateView( `` diff --git a/packages/fe/test/unit/services/diagnostics/validators/unknown-annotation-path.test.ts b/packages/fe/test/unit/services/diagnostics/validators/unknown-annotation-path.test.ts index d866790c9..18628de21 100644 --- a/packages/fe/test/unit/services/diagnostics/validators/unknown-annotation-path.test.ts +++ b/packages/fe/test/unit/services/diagnostics/validators/unknown-annotation-path.test.ts @@ -136,6 +136,20 @@ describe("metaPath attribute value validation (annotation path)", () => { expect(result.length).toEqual(0); }); + it("custom views are empty manifest - absolute path", async function () { + const result = await validateView( + ``, + (c) => { + const newContext = { + ...c, + manifestDetails: { ...c.manifestDetails, customViews: {} }, + }; + return newContext; + } + ); + expect(result.length).toEqual(0); + }); + it("custom view id not determined", async function () { const result = await validateView( ``, @@ -149,6 +163,19 @@ describe("metaPath attribute value validation (annotation path)", () => { ); expect(result.length).toEqual(0); }); + it("custom view id not determined - absolute path", async function () { + const result = await validateView( + `>`, + (c) => { + const newContext: Context = { + ...c, + customViewId: "", + }; + return newContext; + } + ); + expect(result.length).toEqual(0); + }); it("contextPath is not absolute", async function () { const result = await validateView( @@ -185,6 +212,13 @@ describe("metaPath attribute value validation (annotation path)", () => { ); expect(result.length).toEqual(0); }); + + it("is absolute path", async function () { + const result = await validateView( + `` + ); + expect(result.length).toEqual(0); + }); }); describe("shows info message when...", () => { @@ -235,12 +269,12 @@ describe("metaPath attribute value validation (annotation path)", () => { ]); }); - it("is absolute path", async function () { + it("contains wrong segments - absolute path", async function () { const result = await validateView( - `` + `` ); expect(result.map((item) => issueToSnapshot(item))).toStrictEqual([ - "kind: InvalidAnnotationTerm; text: Absolute annotation paths not allowed in metaPath. Use contextPath attribute to change path context; severity:warn; offset:344-405", + 'kind: PathDoesNotExist; text: Unknown annotation path: "/Booking_01/to_Travel/@com.sap.vocabularies.UI.v1.Chart#sample1"; severity:warn; offset:345-407', ]); }); @@ -253,6 +287,15 @@ describe("metaPath attribute value validation (annotation path)", () => { ]); }); + it("is incomplete - absolute path", async function () { + const result = await validateView( + `` + ); + expect(result.map((item) => issueToSnapshot(item))).toStrictEqual([ + "kind: PropertyPathNotAllowed; text: Path value must end with annotation term. Use code completion to select annotation path; severity:warn; offset:344-363", + ]); + }); + it("is property path", async function () { const result = await validateView( `` @@ -262,6 +305,15 @@ describe("metaPath attribute value validation (annotation path)", () => { ]); }); + it("is property path - absolute path", async function () { + const result = await validateView( + `` + ); + expect(result.map((item) => issueToSnapshot(item))).toStrictEqual([ + "kind: PropertyPathNotAllowed; text: Path value must end with annotation term. Use code completion to select annotation path; severity:warn; offset:344-373", + ]); + }); + it("contains navigation segments when contextPath is specified", async function () { const result = await validateView( `` @@ -280,6 +332,15 @@ describe("metaPath attribute value validation (annotation path)", () => { ]); }); + it("is pointing to not existing term - absolute path", async function () { + const result = await validateView( + `` + ); + expect(result.map((item) => issueToSnapshot(item))).toStrictEqual([ + 'kind: PathDoesNotExist; text: Unknown annotation path: "/Booking/to_Travel/@com.sap.vocabularies.UI.v1.Chart#NotExisting"; severity:warn; offset:344-409', + ]); + }); + it("is pointing to not existing term (with contextPath)", async function () { const result = await validateView( `` diff --git a/packages/fe/test/unit/services/diagnostics/validators/wrong-filterbar-id.test.ts b/packages/fe/test/unit/services/diagnostics/validators/wrong-filterbar-id.test.ts index 2753a9783..e27c55db5 100644 --- a/packages/fe/test/unit/services/diagnostics/validators/wrong-filterbar-id.test.ts +++ b/packages/fe/test/unit/services/diagnostics/validators/wrong-filterbar-id.test.ts @@ -101,7 +101,7 @@ describe("filterBar attribute value validation", () => { }); }); - it("shows warning when is is not empty and no filterBar macros elements in the document", async function () { + it("shows warning when it is not empty and no filterBar macros elements in the document", async function () { const result = await validateView( `` ); diff --git a/packages/vscode-ui5-language-assistant/README.md b/packages/vscode-ui5-language-assistant/README.md index b469747a9..5bf31d5dd 100644 --- a/packages/vscode-ui5-language-assistant/README.md +++ b/packages/vscode-ui5-language-assistant/README.md @@ -202,6 +202,8 @@ For SAPUI5 XML views, this means:`*.view.xml` or `*.fragment.xml` files. Note that the extension **lazily** downloads the SAPUI5 metadata needed for its features. This means that there may be a delay between starting VS Code and having the relevant features available. +When working with CAP projects, make sure you have @sap/cds module installed. For this, run npm install on your project. This command will download and install all necessary modules from the npm package repository. + ### Enabling offline work You can set up a local web server to host one or more supported versions of SAP UI5 SDK and register it in the user/workspace setting `"UI5LanguageAssistant.SAPUI5WebServer"`. This overrides the public CDN of SAP UI5 SDK in the extension and enables offline work with the apps having the matching hosted `"minUI5Version"` in `manifest.json`. diff --git a/packages/vscode-ui5-language-assistant/test/manual-tests/annotation_metaPath_attribute_handling.md b/packages/vscode-ui5-language-assistant/test/manual-tests/annotation_metaPath_attribute_handling.md index 542ee597a..e52b873e4 100644 --- a/packages/vscode-ui5-language-assistant/test/manual-tests/annotation_metaPath_attribute_handling.md +++ b/packages/vscode-ui5-language-assistant/test/manual-tests/annotation_metaPath_attribute_handling.md @@ -55,18 +55,17 @@ annotate service.Travel with @( 2. Observe diagnostics warning: `Annotation path value cannot be empty`. 3. Set the metaPath attribute value as `@com.sap.vocabularies.UI.v1.Chart#sample`. Observe warning message `Unknown annotation path: "/Travel/@com.sap.vocabularies.UI.v1.Chart#sample"`. 4. Set the metaPath attribute value as `to_Booking`. Observe warning message `Path value must end with annotation term. Use code completion to select annotation path`. -5. Set the metaPath attribute value as `/Travel/@com.sap.vocabularies.UI.v1.Chart#sample1`. Observe warning message `Absolute annotation paths not allowed in metaPath. Use contextPath attribute to change path context` -6. Set the metaPath attribute value as `@com.sap.vocabularies.UI.v1.LineItem`. Observe warning message `Invalid annotation term: "@com.sap.vocabularies.UI.v1.LineItem". Trigger code completion to choose one of allowed annotations`. -7. Go to app manifest file, find `routing\targets\TravelMain` settings entry and rename `entitySet` property in the nested object structure to `entitySet_`. Save the file. -8. Set the metaPath attribute value as `@com.sap.vocabularies.UI.v1.Chart#sample1`. Observe info message `EntitySet or contextPath for the current view are not defined in application manifest. Attribute value completion and diagnostics is not possible if EntitySet or contextPath are not defined or defined dynamically in controllers`. -9. Revert manifest change that is done at previous step 7. Change property `entitySet` value to `Travel_`. Save the file. -10. Set the metaPath attribute value as `@com.sap.vocabularies.UI.v1.Chart#sample`. Observe info message `Entity Set "Travel_" specified in manifest for the current view is not found. Attribute value completion and diagnostics are disabled`. -11. Reset property `entitySet` value to `Travel` in app manifest. Save the file. -12. Replace current macros element with the snippet: +5. Set the metaPath attribute value as `@com.sap.vocabularies.UI.v1.LineItem`. Observe warning message `Invalid annotation term: "@com.sap.vocabularies.UI.v1.LineItem". Trigger code completion to choose one of allowed annotations`. +6. Go to app manifest file, find `routing\targets\TravelMain` settings entry and rename `entitySet` property in the nested object structure to `entitySet_`. Save the file. +7. Set the metaPath attribute value as `@com.sap.vocabularies.UI.v1.Chart#sample1`. Observe warn message `Path cannot be identified: use absolute path or define contextPath`. +8. Revert manifest change that is done at previous step 7. Change property `entitySet` value to `Travel_`. Save the file. +9. Set the metaPath attribute value as `@com.sap.vocabularies.UI.v1.Chart#sample`. Observe info message `Entity Set "Travel_" specified in manifest for the current view is not found. Attribute value completion and diagnostics are disabled`. +10. Reset property `entitySet` value to `Travel` in app manifest. Save the file. +11. Replace current macros element with the snippet: ```XML ``` -13. Set the metaPath attribute value as `@com.sap.vocabularies.UI.v1.Chart#sample`. Observe warning message `Unknown annotation path: "/TravelService.EntityContainer/Travel/@com.sap.vocabularies.UI.v1.Chart#sample`. -14. Set the metaPath attribute value as `to_Booking/@com.sap.vocabularies.UI.v1.Chart#sample`. Observe warning message `Navigation segments not allowed when contextPath is provided`. +13. Set the metaPath attribute value as `@com.sap.vocabularies.UI.v1.Chart#notDefined`. Observe warning message `Unknown annotation path: "/TravelService.EntityContainer/Travel/@com.sap.vocabularies.UI.v1.Chart#notDefined`. +14. Set the metaPath attribute value as `to_Booking/@com.sap.vocabularies.UI.v1.Chart#sample1`. Observe warning message `Navigation segments not allowed when contextPath is provided`. diff --git a/packages/vscode-ui5-language-assistant/test/manual-tests/property_metaPath_attribute_handling.md b/packages/vscode-ui5-language-assistant/test/manual-tests/property_metaPath_attribute_handling.md index 3ba3d1ba2..8f9e98e79 100644 --- a/packages/vscode-ui5-language-assistant/test/manual-tests/property_metaPath_attribute_handling.md +++ b/packages/vscode-ui5-language-assistant/test/manual-tests/property_metaPath_attribute_handling.md @@ -28,7 +28,16 @@ Associated user stories: ``` 4. Place the cursor at the position of the `metaPath` attribute value and trigger code completion. -5. Observe the list of suggestions for the first path segment. Make sure that properties of the current default entity set `Travel` specified in manifest go first in the list and followed by possible navigation segments. Choose first property and press `Enter`. Observe no error messages are shown for the attribute value. +5. Observe the list of suggestions for the first path segment. Make sure that list is sort as: + + - properties of the current default entity set `Travel` specified in manifest + - navigation segments + - entity types with absolute path + - entity sets + - entity container + + Choose first property and press `Enter`. Observe no error messages are shown for the attribute value. + 6. Place cursor at value's first position and trigger code completion. Choose option `to_Booking` and press `/` to confirm. Observe the segment is added, and completion for next segment is triggered. Choose navigation property `to_Travel` and press `/` to confirm. Observe `Travel` properties are listed and further navigation segment `to_Booking` is not available to avoid cyclic routes. Choose first property and press `Enter`. Observe no error messages are shown for the attribute value. 7. Remove entire current element and place following snippet instead: