diff --git a/.changeset/eager-snakes-add.md b/.changeset/eager-snakes-add.md new file mode 100644 index 000000000..ad31a57c1 --- /dev/null +++ b/.changeset/eager-snakes-add.md @@ -0,0 +1,5 @@ +--- +"@itwin/presentation-components": patch +--- + +Fix calculated properties formatting when unit system changes. diff --git a/packages/components/src/presentation-components/common/ContentDataProvider.ts b/packages/components/src/presentation-components/common/ContentDataProvider.ts index 8f3e995d6..fbcfebf6e 100644 --- a/packages/components/src/presentation-components/common/ContentDataProvider.ts +++ b/packages/components/src/presentation-components/common/ContentDataProvider.ts @@ -622,6 +622,7 @@ class ContentFormatter { displayValues[field.name] = await this._propertyValueFormatter.formatPropertyValue( field, value, + displayValues[field.name], this._unitSystem, ); } diff --git a/packages/components/src/test/common/ContentDataProvider.test.ts b/packages/components/src/test/common/ContentDataProvider.test.ts index 304556c81..d617010fb 100644 --- a/packages/components/src/test/common/ContentDataProvider.test.ts +++ b/packages/components/src/test/common/ContentDataProvider.test.ts @@ -93,7 +93,7 @@ describe("ContentDataProvider", () => { const rulesetManager = { onRulesetModified }; const imodelKey = "test-imodel-Key"; - const imodel = { key: imodelKey } as IModelConnection; + const imodel = { key: imodelKey, schemaContext: {} } as unknown as IModelConnection; beforeEach(() => { presentationManager = createMocked(PresentationManager); @@ -686,6 +686,45 @@ describe("ContentDataProvider", () => { }); }); + describe("content formatting", () => { + beforeEach(() => { + provider.keys = new KeySet([createTestECInstanceKey()]); + // set an active unit system to make sure it's not accidentally used as a fallback display value + vi.spyOn(IModelApp, "quantityFormatter", "get").mockReturnValue({ + onActiveFormattingUnitSystemChanged, + activeUnitSystem: "metric", + } as QuantityFormatter); + }); + + it("keeps original display value of a non-numeric calculated property when re-formatting", async () => { + // calculated properties are represented as plain `Field`s (not `PropertiesField`s) + const field = createTestSimpleContentField({ + name: "calculated", + type: { valueFormat: PropertyValueFormat.Primitive, typeName: "string" }, + }); + const descriptor = createTestContentDescriptor({ fields: [field] }); + presentationManager.getContentIterator.mockResolvedValue({ + descriptor, + items: createAsyncIterator([ + createTestContentItem({ + values: { [field.name]: "raw value" }, + displayValues: { [field.name]: "Calculated Display Value" }, + }), + ]), + total: 1, + }); + + // first request gets already-formatted content from the manager + await provider.getContent(); + // invalidating formatting forces the provider to re-format the cached content locally + provider.invalidateCache({ formatting: true }); + const content = await provider.getContent(); + + // the display value should be preserved and not replaced with the active unit system ("metric") + expect(content!.contentSet[0].displayValues[field.name]).toEqual("Calculated Display Value"); + }); + }); + describe("[deprecated] getFieldByPropertyRecord", () => { it("passes record's description to `getFieldByPropertyDescription`", async () => { const value: PrimitiveValue = { displayValue: "displayValue", value: "rawValue", valueFormat: 0 };