From 7749869a280a98cebaecffdec20d25f549be4746 Mon Sep 17 00:00:00 2001 From: Martynas <43886789+MartynasStrazdas@users.noreply.github.com> Date: Wed, 13 Nov 2024 10:04:41 +0200 Subject: [PATCH] Presentation: Fix enum values formatting (#7345) Co-authored-by: Grigas <35135765+grigasp@users.noreply.github.com> --- ...t-enum-formating-fix_2024-11-12-10-54.json | 10 + .../content/PropertyValueFormatter.ts | 59 +-- .../test/content/PropertyFormatter.test.ts | 340 +++++++++++++++--- 3 files changed, 339 insertions(+), 70 deletions(-) create mode 100644 common/changes/@itwin/presentation-common/mast-enum-formating-fix_2024-11-12-10-54.json diff --git a/common/changes/@itwin/presentation-common/mast-enum-formating-fix_2024-11-12-10-54.json b/common/changes/@itwin/presentation-common/mast-enum-formating-fix_2024-11-12-10-54.json new file mode 100644 index 000000000000..fe85c8dac277 --- /dev/null +++ b/common/changes/@itwin/presentation-common/mast-enum-formating-fix_2024-11-12-10-54.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@itwin/presentation-common", + "comment": "Fixed enum property values formatting issue, where raw value was used instead of enum's display value.", + "type": "none" + } + ], + "packageName": "@itwin/presentation-common" +} \ No newline at end of file diff --git a/presentation/common/src/presentation-common/content/PropertyValueFormatter.ts b/presentation/common/src/presentation-common/content/PropertyValueFormatter.ts index c763a7883ebb..af2fd8a028ed 100644 --- a/presentation/common/src/presentation-common/content/PropertyValueFormatter.ts +++ b/presentation/common/src/presentation-common/content/PropertyValueFormatter.ts @@ -15,7 +15,6 @@ import { Content } from "./Content"; import { Descriptor } from "./Descriptor"; import { ArrayPropertiesField, Field, PropertiesField, StructPropertiesField } from "./Fields"; import { Item } from "./Item"; -import { ArrayTypeDescription, PrimitiveTypeDescription, PropertyValueFormat, StructTypeDescription, TypeDescription } from "./TypeDescription"; import { DisplayValue, DisplayValuesMap, NestedContentValue, Value, ValuesArray, ValuesMap } from "./Value"; /** @internal */ @@ -120,76 +119,90 @@ export class ContentPropertyValueFormatter { } : async (rawValue: number) => formatDouble(rawValue); - return this.formatValue(field.type, value, { doubleFormatter }); + return this.formatValue(field, value, { doubleFormatter }); } - private async formatValue(type: TypeDescription, value: Value, ctx?: { doubleFormatter: (raw: number) => Promise }): Promise { - switch (type.valueFormat) { - case PropertyValueFormat.Primitive: - return this.formatPrimitiveValue(type, value, ctx); - case PropertyValueFormat.Array: - return this.formatArrayValue(type, value); - case PropertyValueFormat.Struct: - return this.formatStructValue(type, value); + private async formatValue(field: Field, value: Value, ctx?: { doubleFormatter: (raw: number) => Promise }): Promise { + if (field.isPropertiesField()) { + if (field.isArrayPropertiesField()) { + return this.formatArrayValue(field, value); + } + + if (field.isStructPropertiesField()) { + return this.formatStructValue(field, value); + } } + + return this.formatPrimitiveValue(field, value, ctx); } - private async formatPrimitiveValue(type: PrimitiveTypeDescription, value: Value, ctx?: { doubleFormatter: (raw: number) => Promise }) { + private async formatPrimitiveValue(field: Field, value: Value, ctx?: { doubleFormatter: (raw: number) => Promise }) { if (value === undefined) { return ""; } const formatDoubleValue = async (raw: number) => (ctx ? ctx.doubleFormatter(raw) : formatDouble(raw)); - if (type.typeName === "point2d" && isPoint2d(value)) { + if (field.type.typeName === "point2d" && isPoint2d(value)) { return `X: ${await formatDoubleValue(value.x)}; Y: ${await formatDoubleValue(value.y)}`; } - if (type.typeName === "point3d" && isPoint3d(value)) { + if (field.type.typeName === "point3d" && isPoint3d(value)) { return `X: ${await formatDoubleValue(value.x)}; Y: ${await formatDoubleValue(value.y)}; Z: ${await formatDoubleValue(value.z)}`; } - if (type.typeName === "dateTime") { + if (field.type.typeName === "dateTime") { assert(typeof value === "string"); return value; } - if (type.typeName === "bool" || type.typeName === "boolean") { + if (field.type.typeName === "bool" || field.type.typeName === "boolean") { assert(typeof value === "boolean"); return value ? "@Presentation:value.true@" : "@Presentation:value.false@"; } - if (type.typeName === "int" || type.typeName === "long") { + if (field.type.typeName === "int" || field.type.typeName === "long") { assert(isNumber(value)); return value.toFixed(0); } - if (type.typeName === "double") { + if (field.type.typeName === "double") { assert(isNumber(value)); return formatDoubleValue(value); } - if (type.typeName === "navigation") { + if (field.type.typeName === "navigation") { assert(Value.isNavigationValue(value)); return value.label.displayValue; } + if (field.type.typeName === "enum" && field.isPropertiesField()) { + const defaultValue = !field.properties[0].property.enumerationInfo?.isStrict + ? value.toString() // eslint-disable-line @typescript-eslint/no-base-to-string + : undefined; + + return field.properties[0].property.enumerationInfo?.choices.find(({ value: enumValue }) => enumValue === value)?.label ?? defaultValue; + } // eslint-disable-next-line @typescript-eslint/no-base-to-string return value.toString(); } - private async formatStructValue(type: StructTypeDescription, value: Value) { + private async formatStructValue(field: StructPropertiesField, value: Value) { if (!Value.isMap(value)) { return {}; } const formattedMember: DisplayValuesMap = {}; - for (const member of type.members) { - formattedMember[member.name] = await this.formatValue(member.type, value[member.name]); + for (const member of field.memberFields) { + formattedMember[member.name] = await this.formatValue(member, value[member.name]); } return formattedMember; } - private async formatArrayValue(type: ArrayTypeDescription, value: Value) { + private async formatArrayValue(field: ArrayPropertiesField, value: Value) { if (!Value.isArray(value)) { return []; } - return Promise.all(value.map(async (arrayVal) => this.formatValue(type.memberType, arrayVal))); + return Promise.all( + value.map(async (arrayVal) => { + return this.formatValue(field.itemsField, arrayVal); + }), + ); } } diff --git a/presentation/common/src/test/content/PropertyFormatter.test.ts b/presentation/common/src/test/content/PropertyFormatter.test.ts index 8a21c7b49a58..f838bcd7aef6 100644 --- a/presentation/common/src/test/content/PropertyFormatter.test.ts +++ b/presentation/common/src/test/content/PropertyFormatter.test.ts @@ -229,6 +229,11 @@ describe("ContentPropertyValueFormatter", () => { }); } + it("Returns empty string when provided a field without properties", async () => { + const field = createTestSimpleContentField(); + expect(await formatter.formatPropertyValue(field, undefined)).to.be.eq(""); + }); + describe("formats primitive", () => { it("'undefined' value", async () => { const field = createField({ valueFormat: PropertyValueFormat.Primitive, typeName: "string" }); @@ -278,6 +283,24 @@ describe("ContentPropertyValueFormatter", () => { expect(await formatter.formatPropertyValue(field, value)).to.be.eq("Test Target Instance"); }); + it("'enum' property value", async () => { + const field = createField({ valueFormat: PropertyValueFormat.Primitive, typeName: "enum" }); + field.properties = [{ property: createTestPropertyInfo({ enumerationInfo: { choices: [{ value: 0, label: "formatted value" }], isStrict: false } }) }]; + expect(await formatter.formatPropertyValue(field, 0)).to.be.eq("formatted value"); + }); + + it("'enum' property value when provided value is not included in choices and isStrict is false", async () => { + const field = createField({ valueFormat: PropertyValueFormat.Primitive, typeName: "enum" }); + field.properties = [{ property: createTestPropertyInfo({ enumerationInfo: { choices: [{ value: 0, label: "formatted value" }], isStrict: false } }) }]; + expect(await formatter.formatPropertyValue(field, 1)).to.be.eq("1"); + }); + + it("'enum' property value when provided value is not included in choices and isStrict is true", async () => { + const field = createField({ valueFormat: PropertyValueFormat.Primitive, typeName: "enum" }); + field.properties = [{ property: createTestPropertyInfo({ enumerationInfo: { choices: [{ value: 0, label: "formatted value" }], isStrict: true } }) }]; + expect(await formatter.formatPropertyValue(field, 1)).to.be.eq(undefined); + }); + it("KOQ property value", async () => { const field = createField({ valueFormat: PropertyValueFormat.Primitive, typeName: "double" }); field.properties = [{ property: createTestPropertyInfo({ kindOfQuantity: { label: "KOQ Label", name: "KOQProp", persistenceUnit: "Unit" } }) }]; @@ -320,30 +343,111 @@ describe("ContentPropertyValueFormatter", () => { describe("formats struct", () => { it("'undefined' value", async () => { - const field = createField({ - valueFormat: PropertyValueFormat.Struct, - typeName: "struct", - members: [{ name: "doubleProp", label: "Double Property", type: { valueFormat: PropertyValueFormat.Primitive, typeName: "double" } }], + const structPropField = createTestStructPropertiesContentField({ + name: "structPropFieldName", + properties: [ + { + property: createTestPropertyInfo({ name: "structProperty" }), + }, + ], + memberFields: [ + createTestPropertiesContentField({ + name: "doubleProp", + label: "Double Property", + properties: [{ property: createTestPropertyInfo() }], + type: { valueFormat: PropertyValueFormat.Primitive, typeName: "double" }, + }), + ], + type: { + valueFormat: PropertyValueFormat.Struct, + typeName: "struct", + members: [{ name: "doubleProp", label: "Double Property", type: { valueFormat: PropertyValueFormat.Primitive, typeName: "double" } }], + }, }); - const formattedValue = (await formatter.formatPropertyValue(field, undefined)) as DisplayValuesMap; + + const formattedValue = (await formatter.formatPropertyValue(structPropField, undefined)) as DisplayValuesMap; expect(Object.keys(formattedValue)).to.be.empty; }); it("value without members", async () => { - const field = createField({ valueFormat: PropertyValueFormat.Struct, typeName: "struct", members: [] }); - const formattedValue = (await formatter.formatPropertyValue(field, {})) as DisplayValuesMap; + const structPropField = createTestStructPropertiesContentField({ + name: "structPropFieldName", + properties: [ + { + property: createTestPropertyInfo({ name: "structProperty" }), + }, + ], + memberFields: [], + }); + + const formattedValue = (await formatter.formatPropertyValue(structPropField, {})) as DisplayValuesMap; expect(Object.keys(formattedValue)).to.be.empty; }); + it("'enum' value", async () => { + const structPropField = createTestStructPropertiesContentField({ + name: "structPropFieldName", + properties: [ + { + property: createTestPropertyInfo({ name: "structProperty" }), + }, + ], + memberFields: [ + createTestPropertiesContentField({ + name: "enumProp", + label: "Enum Property", + properties: [{ property: createTestPropertyInfo({ enumerationInfo: { choices: [{ value: 0, label: "formatedLabel" }], isStrict: false } }) }], + type: { valueFormat: PropertyValueFormat.Primitive, typeName: "enum" }, + }), + ], + type: { + valueFormat: PropertyValueFormat.Struct, + typeName: "struct", + members: [{ name: "enumProp", label: "Enum Property", type: { valueFormat: PropertyValueFormat.Primitive, typeName: "enum" } }], + }, + }); + + const formattedValue = (await formatter.formatPropertyValue(structPropField, { enumProp: 0 })) as DisplayValuesMap; + expect(formattedValue.enumProp).to.be.eq("formatedLabel"); + }); + it("value with different type members", async () => { - const field = createField({ - valueFormat: PropertyValueFormat.Struct, - typeName: "struct", - members: [ - { name: "doubleProp", label: "Double Property", type: { valueFormat: PropertyValueFormat.Primitive, typeName: "double" } }, - { name: "intProp", label: "Int Property", type: { valueFormat: PropertyValueFormat.Primitive, typeName: "int" } }, - { name: "pointProp", label: "Point Property", type: { valueFormat: PropertyValueFormat.Primitive, typeName: "point2d" } }, + const structPropField = createTestStructPropertiesContentField({ + name: "structPropFieldName", + properties: [ + { + property: createTestPropertyInfo({ name: "structProperty" }), + }, + ], + memberFields: [ + createTestPropertiesContentField({ + name: "doubleProp", + label: "Double Property", + properties: [{ property: createTestPropertyInfo() }], + type: { valueFormat: PropertyValueFormat.Primitive, typeName: "double" }, + }), + createTestPropertiesContentField({ + name: "intProp", + label: "Int Property", + properties: [{ property: createTestPropertyInfo() }], + type: { valueFormat: PropertyValueFormat.Primitive, typeName: "int" }, + }), + createTestPropertiesContentField({ + name: "pointProp", + label: "Point Property", + properties: [{ property: createTestPropertyInfo() }], + type: { valueFormat: PropertyValueFormat.Primitive, typeName: "point2d" }, + }), ], + type: { + valueFormat: PropertyValueFormat.Struct, + typeName: "struct", + members: [ + { name: "doubleProp", label: "Double Property", type: { valueFormat: PropertyValueFormat.Primitive, typeName: "double" } }, + { name: "intProp", label: "Int Property", type: { valueFormat: PropertyValueFormat.Primitive, typeName: "int" } }, + { name: "pointProp", label: "Point Property", type: { valueFormat: PropertyValueFormat.Primitive, typeName: "point2d" } }, + ], + }, }); const structValue = { @@ -353,7 +457,7 @@ describe("ContentPropertyValueFormatter", () => { pointProp: { x: 1.234, y: 4.567 }, }; - const formattedValue = (await formatter.formatPropertyValue(field, structValue)) as DisplayValuesMap; + const formattedValue = (await formatter.formatPropertyValue(structPropField, structValue)) as DisplayValuesMap; expect(Object.keys(formattedValue)).to.have.lengthOf(3); expect(formattedValue.doubleProp).to.be.eq("1.50"); expect(formattedValue.intProp).to.be.eq("1"); @@ -361,14 +465,42 @@ describe("ContentPropertyValueFormatter", () => { }); it("value with struct members", async () => { - const field = createField({ - valueFormat: PropertyValueFormat.Struct, - typeName: "struct", - members: [ - { name: "doubleProp", label: "Double Property", type: { valueFormat: PropertyValueFormat.Primitive, typeName: "double" } }, + const structPropField = createTestStructPropertiesContentField({ + name: "structPropFieldName", + properties: [ { + property: createTestPropertyInfo({ name: "structProperty" }), + }, + ], + memberFields: [ + createTestPropertiesContentField({ + name: "doubleProp", + label: "Double Property", + properties: [{ property: createTestPropertyInfo() }], + type: { valueFormat: PropertyValueFormat.Primitive, typeName: "double" }, + }), + createTestStructPropertiesContentField({ name: "structProp", label: "Struct Property", + properties: [ + { + property: createTestPropertyInfo({ name: "structProperty" }), + }, + ], + memberFields: [ + createTestPropertiesContentField({ + name: "nestedDoubleProp", + label: "Nested Double Property", + properties: [{ property: createTestPropertyInfo() }], + type: { valueFormat: PropertyValueFormat.Primitive, typeName: "double" }, + }), + createTestPropertiesContentField({ + name: "nestedIntProp", + label: "Nested Int Property", + properties: [{ property: createTestPropertyInfo() }], + type: { valueFormat: PropertyValueFormat.Primitive, typeName: "int" }, + }), + ], type: { valueFormat: PropertyValueFormat.Struct, typeName: "struct", @@ -377,8 +509,27 @@ describe("ContentPropertyValueFormatter", () => { { name: "nestedIntProp", label: "Nested Int Property", type: { valueFormat: PropertyValueFormat.Primitive, typeName: "int" } }, ], }, - }, + }), ], + type: { + valueFormat: PropertyValueFormat.Struct, + typeName: "struct", + members: [ + { name: "doubleProp", label: "Double Property", type: { valueFormat: PropertyValueFormat.Primitive, typeName: "double" } }, + { + name: "structProp", + label: "Struct Property", + type: { + valueFormat: PropertyValueFormat.Struct, + typeName: "struct", + members: [ + { name: "nestedDoubleProp", label: "Nested Double Property", type: { valueFormat: PropertyValueFormat.Primitive, typeName: "double" } }, + { name: "nestedIntProp", label: "Nested Int Property", type: { valueFormat: PropertyValueFormat.Primitive, typeName: "int" } }, + ], + }, + }, + ], + }, }); const structValue = { @@ -389,7 +540,7 @@ describe("ContentPropertyValueFormatter", () => { }, }; - const formattedValue = (await formatter.formatPropertyValue(field, structValue)) as DisplayValuesMap; + const formattedValue = (await formatter.formatPropertyValue(structPropField, structValue)) as DisplayValuesMap; expect(Object.keys(formattedValue)).to.have.lengthOf(2); expect(formattedValue.doubleProp).to.be.eq("1.50"); const structProp = formattedValue.structProp as DisplayValuesMap; @@ -401,48 +552,143 @@ describe("ContentPropertyValueFormatter", () => { describe("formats array", () => { it("'undefined' value", async () => { - const field = createField({ - valueFormat: PropertyValueFormat.Array, - typeName: "array", - memberType: { valueFormat: PropertyValueFormat.Primitive, typeName: "double" }, + const arrayPropField = createTestArrayPropertiesContentField({ + name: "arrayPropFieldName", + properties: [ + { + property: createTestPropertyInfo({ name: "arrayProperty" }), + }, + ], + itemsField: createTestPropertiesContentField({ + properties: [{ property: createTestPropertyInfo() }], + type: { valueFormat: PropertyValueFormat.Primitive, typeName: "double" }, + }), + type: { + valueFormat: PropertyValueFormat.Array, + typeName: "array", + memberType: { valueFormat: PropertyValueFormat.Primitive, typeName: "double" }, + }, }); - const formattedValue = (await formatter.formatPropertyValue(field, undefined)) as DisplayValuesArray; + + const formattedValue = (await formatter.formatPropertyValue(arrayPropField, undefined)) as DisplayValuesArray; expect(formattedValue).to.be.empty; }); it("empty value", async () => { - const field = createField({ - valueFormat: PropertyValueFormat.Array, - typeName: "array", - memberType: { valueFormat: PropertyValueFormat.Primitive, typeName: "double" }, + const arrayPropField = createTestArrayPropertiesContentField({ + name: "arrayPropFieldName", + properties: [ + { + property: createTestPropertyInfo({ name: "arrayProperty" }), + }, + ], + itemsField: createTestPropertiesContentField({ + properties: [{ property: createTestPropertyInfo() }], + type: { valueFormat: PropertyValueFormat.Primitive, typeName: "double" }, + }), + type: { + valueFormat: PropertyValueFormat.Array, + typeName: "array", + memberType: { valueFormat: PropertyValueFormat.Primitive, typeName: "double" }, + }, }); - const formattedValue = (await formatter.formatPropertyValue(field, [])) as DisplayValuesArray; + + const formattedValue = (await formatter.formatPropertyValue(arrayPropField, [])) as DisplayValuesArray; expect(formattedValue).to.be.empty; }); + it("'enum' value", async () => { + const arrayPropField = createTestArrayPropertiesContentField({ + name: "arrayPropFieldName", + properties: [ + { + property: createTestPropertyInfo({ name: "arrayProperty" }), + }, + ], + itemsField: createTestPropertiesContentField({ + properties: [{ property: createTestPropertyInfo({ enumerationInfo: { choices: [{ value: 0, label: "formatedLabel" }], isStrict: false } }) }], + type: { valueFormat: PropertyValueFormat.Primitive, typeName: "enum" }, + }), + type: { + valueFormat: PropertyValueFormat.Array, + typeName: "array", + memberType: { valueFormat: PropertyValueFormat.Primitive, typeName: "enum" }, + }, + }); + + const formattedValue = (await formatter.formatPropertyValue(arrayPropField, [0])) as DisplayValuesArray; + expect(formattedValue[0]).to.be.eq("formatedLabel"); + }); + it("value with primitive items", async () => { - const field = createField({ - valueFormat: PropertyValueFormat.Array, - typeName: "array", - memberType: { valueFormat: PropertyValueFormat.Primitive, typeName: "double" }, + const arrayPropField = createTestArrayPropertiesContentField({ + name: "arrayPropFieldName", + properties: [ + { + property: createTestPropertyInfo({ name: "arrayProperty" }), + }, + ], + itemsField: createTestPropertiesContentField({ + properties: [{ property: createTestPropertyInfo() }], + type: { valueFormat: PropertyValueFormat.Primitive, typeName: "double" }, + }), + type: { + valueFormat: PropertyValueFormat.Array, + typeName: "array", + memberType: { valueFormat: PropertyValueFormat.Primitive, typeName: "double" }, + }, }); - const formattedValue = (await formatter.formatPropertyValue(field, [1.234, 5.678])) as DisplayValuesArray; + + const formattedValue = (await formatter.formatPropertyValue(arrayPropField, [1.234, 5.678])) as DisplayValuesArray; expect(formattedValue).to.have.lengthOf(2); expect(formattedValue[0]).to.be.eq("1.23"); expect(formattedValue[1]).to.be.eq("5.68"); }); it("value with struct items", async () => { - const field = createField({ - valueFormat: PropertyValueFormat.Array, - typeName: "array", - memberType: { - valueFormat: PropertyValueFormat.Struct, - typeName: "struct", - members: [ - { name: "doubleProp", label: "Double Property", type: { valueFormat: PropertyValueFormat.Primitive, typeName: "double" } }, - { name: "pointProp", label: "Point Property", type: { valueFormat: PropertyValueFormat.Primitive, typeName: "point2d" } }, + const arrayPropField = createTestArrayPropertiesContentField({ + name: "arrayPropFieldName", + properties: [ + { + property: createTestPropertyInfo({ name: "arrayProperty" }), + }, + ], + itemsField: createTestStructPropertiesContentField({ + properties: [{ property: createTestPropertyInfo() }], + memberFields: [ + createTestPropertiesContentField({ + name: "doubleProp", + label: "Double Property", + properties: [{ property: createTestPropertyInfo() }], + type: { valueFormat: PropertyValueFormat.Primitive, typeName: "double" }, + }), + createTestPropertiesContentField({ + name: "pointProp", + label: "Point Property", + properties: [{ property: createTestPropertyInfo() }], + type: { valueFormat: PropertyValueFormat.Primitive, typeName: "point2d" }, + }), ], + type: { + valueFormat: PropertyValueFormat.Struct, + typeName: "struct", + members: [ + { name: "doubleProp", label: "Double Property", type: { valueFormat: PropertyValueFormat.Primitive, typeName: "double" } }, + { name: "pointProp", label: "Point Property", type: { valueFormat: PropertyValueFormat.Primitive, typeName: "point2d" } }, + ], + }, + }), + type: { + valueFormat: PropertyValueFormat.Array, + typeName: "array", + memberType: { + valueFormat: PropertyValueFormat.Struct, + typeName: "struct", + members: [ + { name: "doubleProp", label: "Double Property", type: { valueFormat: PropertyValueFormat.Primitive, typeName: "double" } }, + { name: "pointProp", label: "Point Property", type: { valueFormat: PropertyValueFormat.Primitive, typeName: "point2d" } }, + ], + }, }, }); @@ -457,7 +703,7 @@ describe("ContentPropertyValueFormatter", () => { }, ]; - const formattedValue = (await formatter.formatPropertyValue(field, value)) as DisplayValuesArray; + const formattedValue = (await formatter.formatPropertyValue(arrayPropField, value)) as DisplayValuesArray; expect(formattedValue).to.have.lengthOf(2); const item1 = formattedValue[0] as DisplayValuesMap; expect(item1.doubleProp).to.be.eq("1.23");