@itwin/core-backend 5.14.0-dev.21 → 5.14.0-dev.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/lib/cjs/ECDb.d.ts +11 -6
  2. package/lib/cjs/ECDb.d.ts.map +1 -1
  3. package/lib/cjs/ECDb.js +11 -6
  4. package/lib/cjs/ECDb.js.map +1 -1
  5. package/lib/cjs/ECSqlStatement.d.ts +2 -2
  6. package/lib/cjs/ECSqlStatement.js +2 -2
  7. package/lib/cjs/ECSqlStatement.js.map +1 -1
  8. package/lib/cjs/IModelDb.d.ts +12 -10
  9. package/lib/cjs/IModelDb.d.ts.map +1 -1
  10. package/lib/cjs/IModelDb.js +19 -12
  11. package/lib/cjs/IModelDb.js.map +1 -1
  12. package/lib/cjs/annotations/ElementDrivesTextAnnotation.d.ts +90 -5
  13. package/lib/cjs/annotations/ElementDrivesTextAnnotation.d.ts.map +1 -1
  14. package/lib/cjs/annotations/ElementDrivesTextAnnotation.js +59 -1
  15. package/lib/cjs/annotations/ElementDrivesTextAnnotation.js.map +1 -1
  16. package/lib/cjs/internal/annotations/fieldSpecs.d.ts +82 -0
  17. package/lib/cjs/internal/annotations/fieldSpecs.d.ts.map +1 -0
  18. package/lib/cjs/internal/annotations/fieldSpecs.js +187 -0
  19. package/lib/cjs/internal/annotations/fieldSpecs.js.map +1 -0
  20. package/lib/cjs/internal/annotations/fields.d.ts +39 -2
  21. package/lib/cjs/internal/annotations/fields.d.ts.map +1 -1
  22. package/lib/cjs/internal/annotations/fields.js +140 -13
  23. package/lib/cjs/internal/annotations/fields.js.map +1 -1
  24. package/lib/esm/ECDb.d.ts +11 -6
  25. package/lib/esm/ECDb.d.ts.map +1 -1
  26. package/lib/esm/ECDb.js +11 -6
  27. package/lib/esm/ECDb.js.map +1 -1
  28. package/lib/esm/ECSqlStatement.d.ts +2 -2
  29. package/lib/esm/ECSqlStatement.js +2 -2
  30. package/lib/esm/ECSqlStatement.js.map +1 -1
  31. package/lib/esm/IModelDb.d.ts +12 -10
  32. package/lib/esm/IModelDb.d.ts.map +1 -1
  33. package/lib/esm/IModelDb.js +19 -12
  34. package/lib/esm/IModelDb.js.map +1 -1
  35. package/lib/esm/annotations/ElementDrivesTextAnnotation.d.ts +90 -5
  36. package/lib/esm/annotations/ElementDrivesTextAnnotation.d.ts.map +1 -1
  37. package/lib/esm/annotations/ElementDrivesTextAnnotation.js +60 -2
  38. package/lib/esm/annotations/ElementDrivesTextAnnotation.js.map +1 -1
  39. package/lib/esm/internal/annotations/fieldSpecs.d.ts +82 -0
  40. package/lib/esm/internal/annotations/fieldSpecs.d.ts.map +1 -0
  41. package/lib/esm/internal/annotations/fieldSpecs.js +179 -0
  42. package/lib/esm/internal/annotations/fieldSpecs.js.map +1 -0
  43. package/lib/esm/internal/annotations/fields.d.ts +39 -2
  44. package/lib/esm/internal/annotations/fields.d.ts.map +1 -1
  45. package/lib/esm/internal/annotations/fields.js +140 -13
  46. package/lib/esm/internal/annotations/fields.js.map +1 -1
  47. package/lib/esm/test/AnnotationTestUtils.d.ts +9 -0
  48. package/lib/esm/test/AnnotationTestUtils.d.ts.map +1 -1
  49. package/lib/esm/test/AnnotationTestUtils.js +17 -0
  50. package/lib/esm/test/AnnotationTestUtils.js.map +1 -1
  51. package/lib/esm/test/annotations/FieldFormat.test.d.ts +2 -0
  52. package/lib/esm/test/annotations/FieldFormat.test.d.ts.map +1 -0
  53. package/lib/esm/test/annotations/FieldFormat.test.js +738 -0
  54. package/lib/esm/test/annotations/FieldFormat.test.js.map +1 -0
  55. package/lib/esm/test/annotations/FieldFormattingLifecycle.test.d.ts +2 -0
  56. package/lib/esm/test/annotations/FieldFormattingLifecycle.test.d.ts.map +1 -0
  57. package/lib/esm/test/annotations/FieldFormattingLifecycle.test.js +323 -0
  58. package/lib/esm/test/annotations/FieldFormattingLifecycle.test.js.map +1 -0
  59. package/lib/esm/test/annotations/FieldSpecs.test.d.ts +2 -0
  60. package/lib/esm/test/annotations/FieldSpecs.test.d.ts.map +1 -0
  61. package/lib/esm/test/annotations/FieldSpecs.test.js +54 -0
  62. package/lib/esm/test/annotations/FieldSpecs.test.js.map +1 -0
  63. package/lib/esm/test/annotations/Fields.test.js +221 -27
  64. package/lib/esm/test/annotations/Fields.test.js.map +1 -1
  65. package/lib/esm/test/ecdb/QueryReaders.test.js +105 -30
  66. package/lib/esm/test/ecdb/QueryReaders.test.js.map +1 -1
  67. package/package.json +15 -13
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FieldFormattingLifecycle.test.js","sourceRoot":"","sources":["../../../../src/test/annotations/FieldFormattingLifecycle.test.ts"],"names":[],"mappings":"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","sourcesContent":["/*---------------------------------------------------------------------------------------------\r\n* Copyright (c) Bentley Systems, Incorporated. All rights reserved.\r\n* See LICENSE.md in the project root for license terms and full copyright notice.\r\n*--------------------------------------------------------------------------------------------*/\r\nimport { expect } from \"chai\";\r\nimport { Code, FieldRun, PhysicalElementProps, SubCategoryAppearance, TextAnnotation, TextBlock, traverseTextBlockComponent } from \"@itwin/core-common\";\r\nimport { FormatDefinition } from \"@itwin/core-quantity\";\r\nimport { DbResult, Id64String, UnexpectedErrors } from \"@itwin/core-bentley\";\r\nimport { Point3d, YawPitchRollAngles } from \"@itwin/core-geometry\";\r\nimport { StandaloneDb } from \"../../IModelDb\";\r\nimport { IModelTestUtils } from \"../IModelTestUtils\";\r\nimport { SpatialCategory } from \"../../Category\";\r\nimport { Schema, Schemas } from \"../../Schema\";\r\nimport { ClassRegistry } from \"../../ClassRegistry\";\r\nimport { PhysicalElement } from \"../../Element\";\r\nimport { FontFile, TextAnnotation3d } from \"../../core-backend\";\r\nimport { ElementDrivesTextAnnotation, TextAnnotationUsesTextStyleByDefault } from \"../../annotations/ElementDrivesTextAnnotation\";\r\nimport { decimalFormat, toFormatSet } from \"../AnnotationTestUtils\";\r\nimport { withEditTxn } from \"../../EditTxn\";\r\n\r\n/**\r\n * The lifecycle of [[ElementDrivesTextAnnotation.registerFieldFormatting]] and the persistence\r\n * contract of [[ElementDrivesTextAnnotation.evaluateFields]]: when the change event fires, what a\r\n * registration does (and does not do) to already-persisted `cachedContent`, and which code paths\r\n * consult the registered FormatSets.\r\n *\r\n * Which format a given field resolves to is covered exhaustively by FieldFormat.test.ts; nothing\r\n * here varies `kindOfQuantity` or `persistenceUnit`. The registration-level `unitSystem` argument\r\n * is covered here because it is part of the registration contract rather than the per-field one.\r\n */\r\n\r\nconst lifecycleSchemaXml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<ECSchema schemaName=\"FieldLifecycle\" alias=\"flc\" version=\"01.00.00\" xmlns=\"http://www.bentley.com/schemas/Bentley.ECXML.3.2\">\r\n <ECSchemaReference name=\"BisCore\" version=\"01.00.04\" alias=\"bis\"/>\r\n <ECSchemaReference name=\"Formats\" version=\"01.00.00\" alias=\"f\"/>\r\n <ECSchemaReference name=\"Units\" version=\"01.00.09\" alias=\"u\"/>\r\n\r\n <KindOfQuantity typeName=\"LENGTH\" displayLabel=\"Length\" persistenceUnit=\"u:M\" relativeError=\"0.0001\" presentationUnits=\"f:DefaultRealU(4)[u:M]\"/>\r\n <KindOfQuantity typeName=\"DUAL_LENGTH\" displayLabel=\"Dual Length\" persistenceUnit=\"u:M\" relativeError=\"0.0001\" presentationUnits=\"f:DefaultRealU(4)[u:M];f:DefaultRealU(4)[u:FT]\"/>\r\n\r\n <ECEntityClass typeName=\"LifecycleElement\" modifier=\"None\">\r\n <BaseClass>bis:PhysicalElement</BaseClass>\r\n <ECProperty propertyName=\"lengthProp\" typeName=\"double\" kindOfQuantity=\"LENGTH\"/>\r\n <ECProperty propertyName=\"dualLengthProp\" typeName=\"double\" kindOfQuantity=\"DUAL_LENGTH\"/>\r\n <ECProperty propertyName=\"maybeNull\" typeName=\"double\"/>\r\n <ECArrayProperty propertyName=\"strings\" typeName=\"string\"/>\r\n </ECEntityClass>\r\n</ECSchema>\r\n`;\r\n\r\ninterface LifecycleElementProps extends PhysicalElementProps {\r\n lengthProp: number;\r\n dualLengthProp: number;\r\n maybeNull?: number;\r\n strings: string[];\r\n}\r\n\r\nclass LifecycleElement extends PhysicalElement {\r\n public static override get className() { return \"LifecycleElement\"; }\r\n declare public lengthProp: number;\r\n declare public dualLengthProp: number;\r\n declare public maybeNull?: number;\r\n declare public strings: string[];\r\n}\r\n\r\nclass FieldLifecycleSchema extends Schema {\r\n public static override get schemaName() { return \"FieldLifecycle\"; }\r\n}\r\n\r\ndescribe(\"Field formatting lifecycle\", () => {\r\n let imodel: StandaloneDb;\r\n let model: Id64String;\r\n let category: Id64String;\r\n let sourceElementId: Id64String;\r\n\r\n const propertyHost = (elementId: Id64String) => ({ elementId, schemaName: \"FieldLifecycle\", className: \"LifecycleElement\" });\r\n\r\n // Id referenced by fields via formatOptions.quantity.formatSet and supplied through the\r\n // `formatSets` array at registration time.\r\n const PRIMARY_FORMAT_SET = \"0x111\";\r\n\r\n before(async () => {\r\n const iModelPath = IModelTestUtils.prepareOutputFile(\"FieldFormattingLifecycle\", \"test.bim\");\r\n imodel = StandaloneDb.createEmpty(iModelPath, { rootSubject: { name: \"FieldFormattingLifecycle\" }, enableTransactions: true });\r\n\r\n if (!Schemas.getRegisteredSchema(\"FieldLifecycle\")) {\r\n Schemas.registerSchema(FieldLifecycleSchema);\r\n ClassRegistry.register(LifecycleElement, FieldLifecycleSchema);\r\n }\r\n await imodel.importSchemaStrings([lifecycleSchemaXml]);\r\n\r\n await withEditTxn(imodel, async (txn) => {\r\n model = IModelTestUtils.createAndInsertPhysicalPartitionAndModel(txn, Code.createEmpty(), true)[1];\r\n category = SpatialCategory.insert(txn, StandaloneDb.dictionaryId, \"FieldFormattingLifecycleCategory\", new SubCategoryAppearance());\r\n await imodel.fonts.embedFontFile({\r\n file: FontFile.createFromTrueTypeFileName(IModelTestUtils.resolveFontFile(\"Karla-Regular.ttf\")),\r\n });\r\n sourceElementId = txn.insertElement(sourceElementProps());\r\n });\r\n });\r\n\r\n after(() => {\r\n imodel.close();\r\n });\r\n\r\n afterEach(() => {\r\n ElementDrivesTextAnnotation.registerFieldFormatting({ iModel: imodel });\r\n // Drop the annotations inserted below so their ElementDrivesTextAnnotation relationships do\r\n // not leak into the next test.\r\n const ids: Id64String[] = [];\r\n // eslint-disable-next-line @typescript-eslint/no-deprecated\r\n imodel.withPreparedStatement(\"SELECT ECInstanceId FROM BisCore.TextAnnotation3d\", (stmt) => {\r\n while (stmt.step() === DbResult.BE_SQLITE_ROW)\r\n ids.push(stmt.getValue(0).getId());\r\n });\r\n if (ids.length > 0)\r\n withEditTxn(imodel, (txn) => { for (const id of ids) txn.deleteElement(id); });\r\n });\r\n\r\n function sourceElementProps(): LifecycleElementProps {\r\n return {\r\n classFullName: \"FieldLifecycle:LifecycleElement\",\r\n model,\r\n category,\r\n code: Code.createEmpty(),\r\n lengthProp: 2.5,\r\n dualLengthProp: 2.5,\r\n strings: [\"a\", \"b\"],\r\n placement: { origin: new Point3d(0, 0, 0), angles: new YawPitchRollAngles() },\r\n };\r\n }\r\n\r\n // Configures `imodel` with the given per-field FormatSets.\r\n function registerSets(formatSets: ReadonlyArray<{ id: string, formats: Record<string, FormatDefinition> }>): void {\r\n ElementDrivesTextAnnotation.registerFieldFormatting({\r\n iModel: imodel,\r\n formatSets: formatSets.map(({ id, formats }) => ({ id, formatSet: toFormatSet(\"TestSet\", formats) })),\r\n });\r\n }\r\n\r\n // A mm FormatSet: 2.5 m -> \"2500 mm\".\r\n function mmSet(): Record<string, FormatDefinition> {\r\n return { \"FieldLifecycle.LENGTH\": decimalFormat(\"Units.MM\", \"mm\", 2) };\r\n }\r\n\r\n function readFieldCachedContent(block: TextBlock): string | undefined {\r\n for (const { child } of traverseTextBlockComponent(block)) {\r\n if (child.type === \"field\")\r\n return child.cachedContent;\r\n }\r\n return undefined;\r\n }\r\n\r\n function readFieldCachedContentById(annotationElementId: Id64String): string | undefined {\r\n const reloaded = imodel.elements.getElement<TextAnnotation3d>(annotationElementId);\r\n const reloadedBlock = reloaded.getAnnotation()?.textBlock;\r\n return reloadedBlock ? readFieldCachedContent(reloadedBlock) : undefined;\r\n }\r\n\r\n function insertAnnotationElement(textBlock: TextBlock): Id64String {\r\n const elem = TextAnnotation3d.fromJSON({\r\n model,\r\n category,\r\n code: Code.createEmpty(),\r\n placement: {\r\n origin: { x: 0, y: 0, z: 0 },\r\n angles: YawPitchRollAngles.createDegrees(0, 0, 0).toJSON(),\r\n },\r\n classFullName: TextAnnotation3d.classFullName,\r\n defaultTextStyle: new TextAnnotationUsesTextStyleByDefault(\"0x123\").toJSON(),\r\n }, imodel);\r\n elem.setAnnotation(TextAnnotation.fromJSON({ textBlock: textBlock.toJSON() }));\r\n return withEditTxn(imodel, (txn) => txn.insertElement(elem.toJSON()));\r\n }\r\n\r\n /** Inserts an annotation whose single field reads `lengthProp` of `sourceId` through\r\n * `PRIMARY_FORMAT_SET`, and wires up its dependency so txn callbacks evaluate it.\r\n */\r\n function insertAnnotationWithLengthField(sourceId: Id64String, formatSet: string | undefined = PRIMARY_FORMAT_SET): Id64String {\r\n const textBlock = TextBlock.create();\r\n const field = FieldRun.create({\r\n styleOverrides: { font: { name: \"Karla\" } },\r\n propertyHost: propertyHost(sourceId),\r\n propertyPath: { propertyName: \"lengthProp\" },\r\n formatOptions: formatSet ? { quantity: { formatSet } } : undefined,\r\n cachedContent: \"old\",\r\n });\r\n textBlock.appendRun(field);\r\n const annotationElementId = insertAnnotationElement(textBlock);\r\n withEditTxn(imodel, (txn) => {\r\n ElementDrivesTextAnnotation.updateFieldDependencies(txn, annotationElementId);\r\n });\r\n return annotationElementId;\r\n }\r\n\r\n describe(\"evaluateFields\", () => {\r\n it(\"preserves non-quantity field formatting\", () => {\r\n const textBlock = TextBlock.create();\r\n const stringField = FieldRun.create({\r\n propertyHost: propertyHost(sourceElementId),\r\n propertyPath: { propertyName: \"strings\", accessors: [0] },\r\n formatOptions: { prefix: \"[\", suffix: \"]\" },\r\n cachedContent: \"old\",\r\n });\r\n textBlock.appendRun(stringField);\r\n\r\n registerSets([{ id: PRIMARY_FORMAT_SET, formats: mmSet() }]);\r\n const updatedCount = ElementDrivesTextAnnotation.evaluateFields({ iModel: imodel, block: textBlock });\r\n\r\n expect(updatedCount).to.equal(1);\r\n expect(stringField.cachedContent).to.equal(\"[a]\");\r\n });\r\n\r\n it(\"marks the field invalid when a quantity property value is missing\", () => {\r\n const textBlock = TextBlock.create();\r\n const field = FieldRun.create({\r\n propertyHost: propertyHost(sourceElementId),\r\n // maybeNull has no value on the source element.\r\n propertyPath: { propertyName: \"maybeNull\" },\r\n formatOptions: { quantity: { persistenceUnit: \"Units.M\" } },\r\n cachedContent: \"old\",\r\n });\r\n textBlock.appendRun(field);\r\n\r\n const updatedCount = ElementDrivesTextAnnotation.evaluateFields({ iModel: imodel, block: textBlock });\r\n\r\n expect(updatedCount).to.equal(1);\r\n expect(field.cachedContent).to.equal(FieldRun.invalidContentIndicator);\r\n });\r\n\r\n it(\"mutates the in-memory TextBlock but does not persist to the element on its own\", () => {\r\n // Contract: evaluateFields formats in place and returns a count. Persistence is the\r\n // caller's responsibility (setAnnotation + element.update inside an EditTxn).\r\n const annotationElementId = insertAnnotationWithLengthField(sourceElementId);\r\n const persistedBefore = readFieldCachedContentById(annotationElementId);\r\n expect(persistedBefore).to.equal(\"2.5 m\");\r\n\r\n registerSets([{ id: PRIMARY_FORMAT_SET, formats: mmSet() }]);\r\n\r\n const reloaded = imodel.elements.getElement<TextAnnotation3d>(annotationElementId);\r\n const inMemoryBlock = reloaded.getAnnotation()!.textBlock;\r\n\r\n const updated = ElementDrivesTextAnnotation.evaluateFields({ iModel: imodel, block: inMemoryBlock });\r\n\r\n // The in-memory half of the contract: the field resolved through the registered FormatSet\r\n // and the call reported the one field it changed.\r\n expect(updated).to.equal(1);\r\n expect(readFieldCachedContent(inMemoryBlock)).to.equal(\"2500 mm\");\r\n\r\n // The persisted half: the disk copy is untouched, because nothing called `update`.\r\n expect(readFieldCachedContentById(annotationElementId)).to.equal(persistedBefore);\r\n });\r\n });\r\n\r\n describe(\"registerFieldFormatting\", () => {\r\n it(\"raises onFieldFormattingChanged on every register call\", () => {\r\n let events = 0;\r\n const drop = ElementDrivesTextAnnotation.onFieldFormattingChanged.addListener((args) => {\r\n expect(args.iModel).to.equal(imodel);\r\n ++events;\r\n });\r\n\r\n try {\r\n registerSets([{ id: PRIMARY_FORMAT_SET, formats: mmSet() }]);\r\n // Re-registering the same configuration still reports.\r\n registerSets([{ id: PRIMARY_FORMAT_SET, formats: mmSet() }]);\r\n // Reverting to the schema default is a register call too.\r\n ElementDrivesTextAnnotation.registerFieldFormatting({ iModel: imodel });\r\n\r\n expect(events).to.equal(3);\r\n } finally {\r\n drop();\r\n }\r\n });\r\n\r\n it(\"keeps the registration when a listener throws\", () => {\r\n // BeEvent routes listener exceptions to UnexpectedErrors rather than to the caller.\r\n const unexpected: unknown[] = [];\r\n const previousHandler = UnexpectedErrors.setHandler((e) => unexpected.push(e));\r\n const drop = ElementDrivesTextAnnotation.onFieldFormattingChanged.addListener(() => {\r\n throw new Error(\"listener failure\");\r\n });\r\n\r\n try {\r\n expect(() => registerSets([{ id: PRIMARY_FORMAT_SET, formats: mmSet() }])).not.to.throw();\r\n } finally {\r\n drop();\r\n UnexpectedErrors.setHandler(previousHandler);\r\n }\r\n\r\n expect(unexpected.length).to.equal(1);\r\n const annotationElementId = insertAnnotationWithLengthField(sourceElementId);\r\n expect(readFieldCachedContentById(annotationElementId)).to.equal(\"2500 mm\");\r\n });\r\n\r\n it(\"selects the KindOfQuantity's presentation format for the registered unitSystem\", () => {\r\n // DUAL_LENGTH lists a metric and an imperial presentation format. The unit system chosen\r\n // at registration decides which one the schema provider hands back, and an explicit\r\n // `unitSystem` wins over the adopted FormatSet's own.\r\n // Persisted on the element: dualLengthProp 2.5 m\r\n const evaluate = () => {\r\n const block = TextBlock.create();\r\n const field = FieldRun.create({\r\n propertyHost: propertyHost(sourceElementId),\r\n propertyPath: { propertyName: \"dualLengthProp\" },\r\n cachedContent: \"old\",\r\n });\r\n block.appendRun(field);\r\n ElementDrivesTextAnnotation.evaluateFields({ iModel: imodel, block });\r\n return field.cachedContent;\r\n };\r\n\r\n // Nothing registered: metric.\r\n expect(evaluate()).to.equal(\"2.5 m\");\r\n\r\n // An explicit unit system alone.\r\n ElementDrivesTextAnnotation.registerFieldFormatting({ iModel: imodel, unitSystem: \"imperial\" });\r\n expect(evaluate()).to.equal(\"8.2021 ft\");\r\n\r\n // An adopted metric FormatSet that does not define the key defers to the schema, using the\r\n // set's own system when none is given and the explicit one when it is.\r\n const metricSet = toFormatSet(\"Adopted\", mmSet());\r\n ElementDrivesTextAnnotation.registerFieldFormatting({ iModel: imodel, formatSet: metricSet });\r\n expect(evaluate()).to.equal(\"2.5 m\");\r\n ElementDrivesTextAnnotation.registerFieldFormatting({ iModel: imodel, formatSet: metricSet, unitSystem: \"imperial\" });\r\n expect(evaluate()).to.equal(\"8.2021 ft\");\r\n });\r\n\r\n it(\"formats through the schema default on the txn callback path when nothing is registered\", () => {\r\n const annotationElementId = insertAnnotationWithLengthField(sourceElementId, undefined);\r\n expect(readFieldCachedContentById(annotationElementId)).to.equal(\"2.5 m\");\r\n });\r\n\r\n it(\"routes txn-driven field updates through the registered FormatSet\", () => {\r\n registerSets([{ id: PRIMARY_FORMAT_SET, formats: mmSet() }]);\r\n\r\n const annotationElementId = insertAnnotationWithLengthField(sourceElementId);\r\n\r\n expect(readFieldCachedContentById(annotationElementId)).to.equal(\"2500 mm\");\r\n });\r\n\r\n it(\"documents the registration-lifecycle contract for persisted cachedContent\", () => {\r\n // Deliberately one narrative test rather than one per step: this is documentation of an\r\n // accepted contract, not a bug under guard. The contract is that registering never walks\r\n // existing annotations, so persisted cachedContent changes only on the next source-element\r\n // edit -- which formats according to whatever happens to be registered at that moment.\r\n const sourceId = withEditTxn(imodel, (txn) => txn.insertElement(sourceElementProps()));\r\n\r\n // 1. Nothing registered: insert persists the schema's presentation format.\r\n const annotationElementId = insertAnnotationWithLengthField(sourceId);\r\n expect(readFieldCachedContentById(annotationElementId)).to.equal(\"2.5 m\");\r\n\r\n // 2. Registering is not retroactive; persisted content is untouched.\r\n registerSets([{ id: PRIMARY_FORMAT_SET, formats: mmSet() }]);\r\n expect(readFieldCachedContentById(annotationElementId)).to.equal(\"2.5 m\");\r\n\r\n // 3. The next source edit fires the txn callback, which routes through the FormatSet.\r\n const source = imodel.elements.getElement<LifecycleElement>(sourceId);\r\n source.lengthProp = 4.25;\r\n withEditTxn(imodel, \"source update\", (txn) => {\r\n source.update(txn);\r\n txn.saveChanges(\"source update\");\r\n });\r\n expect(readFieldCachedContentById(annotationElementId)).to.equal(\"4250 mm\");\r\n\r\n // 4. Reverting to the schema default is likewise not retroactive...\r\n ElementDrivesTextAnnotation.registerFieldFormatting({ iModel: imodel });\r\n expect(readFieldCachedContentById(annotationElementId)).to.equal(\"4250 mm\");\r\n\r\n // ...but the following edit falls back to the schema default and overwrites the FormatSet's\r\n // millimeters with the schema's meters. This is why registerFieldFormatting's docs tell\r\n // hosts to swap FormatSets with a single re-register rather than reverting first.\r\n const reloadedSource = imodel.elements.getElement<LifecycleElement>(sourceId);\r\n reloadedSource.lengthProp = 3.5;\r\n withEditTxn(imodel, \"source update after revert\", (txn) => {\r\n reloadedSource.update(txn);\r\n txn.saveChanges(\"source update after revert\");\r\n });\r\n expect(readFieldCachedContentById(annotationElementId)).to.equal(\"3.5 m\");\r\n });\r\n });\r\n});\r\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=FieldSpecs.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FieldSpecs.test.d.ts","sourceRoot":"","sources":["../../../../src/test/annotations/FieldSpecs.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,54 @@
1
+ /*---------------------------------------------------------------------------------------------
2
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
3
+ * See LICENSE.md in the project root for license terms and full copyright notice.
4
+ *--------------------------------------------------------------------------------------------*/
5
+ import { expect } from "chai";
6
+ import { collectFieldQuantityPairs } from "../../internal/annotations/fieldSpecs";
7
+ describe("collectFieldQuantityPairs candidate priority", () => {
8
+ const PROPERTY_PAIR = { name: "P.KOQ", persistenceUnitName: "Units.M" };
9
+ it("keeps the property-side fallback for a kindOfQuantity-only override", () => {
10
+ expect(collectFieldQuantityPairs({
11
+ overrideName: "A.KOQ", propertyName: "P.KOQ", propertyPersistence: "Units.M",
12
+ })).to.deep.equal([{ name: "A.KOQ", persistenceUnitName: "Units.M" }, PROPERTY_PAIR]);
13
+ });
14
+ it("keeps the property-side fallback when the persistence override restates the property's unit", () => {
15
+ expect(collectFieldQuantityPairs({
16
+ overrideName: "A.KOQ", overridePersistence: "Units.M", propertyName: "P.KOQ", propertyPersistence: "Units.M",
17
+ })).to.deep.equal([{ name: "A.KOQ", persistenceUnitName: "Units.M" }, PROPERTY_PAIR]);
18
+ });
19
+ it("keeps the property's own persistence unit over a persistence override that disagrees", () => {
20
+ // The override only supplies a unit the property lacks; it never reinterprets a stored value.
21
+ expect(collectFieldQuantityPairs({
22
+ overridePersistence: "Units.FT", propertyName: "P.KOQ", propertyPersistence: "Units.M",
23
+ })).to.deep.equal([PROPERTY_PAIR]);
24
+ expect(collectFieldQuantityPairs({
25
+ overrideName: "A.KOQ", overridePersistence: "Units.FT", propertyName: "P.KOQ", propertyPersistence: "Units.M",
26
+ })).to.deep.equal([{ name: "A.KOQ", persistenceUnitName: "Units.M" }, PROPERTY_PAIR]);
27
+ });
28
+ it("uses the persistence override when the property has no unit of its own", () => {
29
+ expect(collectFieldQuantityPairs({
30
+ overrideName: "A.KOQ", overridePersistence: "Units.FT", propertyName: undefined, propertyPersistence: undefined,
31
+ })).to.deep.equal([{ name: "A.KOQ", persistenceUnitName: "Units.FT" }]);
32
+ });
33
+ it("emits a single pair when the overrides only restate the property's own pair", () => {
34
+ expect(collectFieldQuantityPairs({
35
+ overrideName: "P.KOQ", overridePersistence: "Units.M", propertyName: "P.KOQ", propertyPersistence: "Units.M",
36
+ })).to.deep.equal([PROPERTY_PAIR]);
37
+ });
38
+ it("compares names exactly; callers normalize spellings to the schema's before building pairs", () => {
39
+ // `lookupFieldSpec` resolves `p:koq` to `P.KOQ` first. Anything that reaches here unnormalized
40
+ // is a name no schema defines, and those really are different keys.
41
+ expect(collectFieldQuantityPairs({
42
+ overrideName: "p:koq", propertyName: "P.KOQ", propertyPersistence: "Units.M",
43
+ })).to.deep.equal([{ name: "p:koq", persistenceUnitName: "Units.M" }, PROPERTY_PAIR]);
44
+ });
45
+ it("emits no property-side pair at all when the property has no persistence unit", () => {
46
+ expect(collectFieldQuantityPairs({
47
+ overrideName: "A.KOQ", overridePersistence: "Units.ARC_DEG", propertyName: undefined, propertyPersistence: undefined,
48
+ })).to.deep.equal([{ name: "A.KOQ", persistenceUnitName: "Units.ARC_DEG" }]);
49
+ for (const overridePersistence of [undefined, "", "Units.ARC_DEG"]) {
50
+ expect(collectFieldQuantityPairs({ overridePersistence, propertyName: undefined, propertyPersistence: undefined })).to.deep.equal([]);
51
+ }
52
+ });
53
+ });
54
+ //# sourceMappingURL=FieldSpecs.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FieldSpecs.test.js","sourceRoot":"","sources":["../../../../src/test/annotations/FieldSpecs.test.ts"],"names":[],"mappings":"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","sourcesContent":["/*---------------------------------------------------------------------------------------------\r\n* Copyright (c) Bentley Systems, Incorporated. All rights reserved.\r\n* See LICENSE.md in the project root for license terms and full copyright notice.\r\n*--------------------------------------------------------------------------------------------*/\r\n\r\nimport { expect } from \"chai\";\r\nimport { collectFieldQuantityPairs } from \"../../internal/annotations/fieldSpecs\";\r\n\r\ndescribe(\"collectFieldQuantityPairs candidate priority\", () => {\r\n const PROPERTY_PAIR = { name: \"P.KOQ\", persistenceUnitName: \"Units.M\" };\r\n\r\n it(\"keeps the property-side fallback for a kindOfQuantity-only override\", () => {\r\n expect(collectFieldQuantityPairs({\r\n overrideName: \"A.KOQ\", propertyName: \"P.KOQ\", propertyPersistence: \"Units.M\",\r\n })).to.deep.equal([{ name: \"A.KOQ\", persistenceUnitName: \"Units.M\" }, PROPERTY_PAIR]);\r\n });\r\n\r\n it(\"keeps the property-side fallback when the persistence override restates the property's unit\", () => {\r\n expect(collectFieldQuantityPairs({\r\n overrideName: \"A.KOQ\", overridePersistence: \"Units.M\", propertyName: \"P.KOQ\", propertyPersistence: \"Units.M\",\r\n })).to.deep.equal([{ name: \"A.KOQ\", persistenceUnitName: \"Units.M\" }, PROPERTY_PAIR]);\r\n });\r\n\r\n it(\"keeps the property's own persistence unit over a persistence override that disagrees\", () => {\r\n // The override only supplies a unit the property lacks; it never reinterprets a stored value.\r\n expect(collectFieldQuantityPairs({\r\n overridePersistence: \"Units.FT\", propertyName: \"P.KOQ\", propertyPersistence: \"Units.M\",\r\n })).to.deep.equal([PROPERTY_PAIR]);\r\n expect(collectFieldQuantityPairs({\r\n overrideName: \"A.KOQ\", overridePersistence: \"Units.FT\", propertyName: \"P.KOQ\", propertyPersistence: \"Units.M\",\r\n })).to.deep.equal([{ name: \"A.KOQ\", persistenceUnitName: \"Units.M\" }, PROPERTY_PAIR]);\r\n });\r\n\r\n it(\"uses the persistence override when the property has no unit of its own\", () => {\r\n expect(collectFieldQuantityPairs({\r\n overrideName: \"A.KOQ\", overridePersistence: \"Units.FT\", propertyName: undefined, propertyPersistence: undefined,\r\n })).to.deep.equal([{ name: \"A.KOQ\", persistenceUnitName: \"Units.FT\" }]);\r\n });\r\n\r\n it(\"emits a single pair when the overrides only restate the property's own pair\", () => {\r\n expect(collectFieldQuantityPairs({\r\n overrideName: \"P.KOQ\", overridePersistence: \"Units.M\", propertyName: \"P.KOQ\", propertyPersistence: \"Units.M\",\r\n })).to.deep.equal([PROPERTY_PAIR]);\r\n });\r\n\r\n it(\"compares names exactly; callers normalize spellings to the schema's before building pairs\", () => {\r\n // `lookupFieldSpec` resolves `p:koq` to `P.KOQ` first. Anything that reaches here unnormalized\r\n // is a name no schema defines, and those really are different keys.\r\n expect(collectFieldQuantityPairs({\r\n overrideName: \"p:koq\", propertyName: \"P.KOQ\", propertyPersistence: \"Units.M\",\r\n })).to.deep.equal([{ name: \"p:koq\", persistenceUnitName: \"Units.M\" }, PROPERTY_PAIR]);\r\n });\r\n\r\n it(\"emits no property-side pair at all when the property has no persistence unit\", () => {\r\n expect(collectFieldQuantityPairs({\r\n overrideName: \"A.KOQ\", overridePersistence: \"Units.ARC_DEG\", propertyName: undefined, propertyPersistence: undefined,\r\n })).to.deep.equal([{ name: \"A.KOQ\", persistenceUnitName: \"Units.ARC_DEG\" }]);\r\n\r\n for (const overridePersistence of [undefined, \"\", \"Units.ARC_DEG\"]) {\r\n expect(collectFieldQuantityPairs({ overridePersistence, propertyName: undefined, propertyPersistence: undefined })).to.deep.equal([]);\r\n }\r\n });\r\n});\r\n"]}
@@ -3,11 +3,13 @@
3
3
  * See LICENSE.md in the project root for license terms and full copyright notice.
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
  import { expect } from "chai";
6
+ import * as sinon from "sinon";
6
7
  import { Code, FieldRun, SubCategoryAppearance, TextAnnotation, TextBlock, TextRun } from "@itwin/core-common";
7
8
  import { StandaloneDb } from "../../IModelDb";
8
9
  import { IModelTestUtils } from "../IModelTestUtils";
9
10
  import { createUpdateContext, updateField, updateFields } from "../../internal/annotations/fields";
10
- import { DbResult, Id64, ProcessDetector } from "@itwin/core-bentley";
11
+ import { createFieldFormatting } from "../../internal/annotations/fieldSpecs";
12
+ import { Id64, Logger, ProcessDetector } from "@itwin/core-bentley";
11
13
  import { SpatialCategory } from "../../Category";
12
14
  import { Point3d, YawPitchRollAngles } from "@itwin/core-geometry";
13
15
  import { Schema, Schemas } from "../../Schema";
@@ -22,6 +24,7 @@ function isIntlSupported() {
22
24
  // solution.
23
25
  return !ProcessDetector.isMobileAppBackend;
24
26
  }
27
+ //cspell: ignore classid ecdbmap oldval reqs uppercased
25
28
  function insertTestElement(txn, model, category, overrides, aspectProp = 999) {
26
29
  const props = {
27
30
  classFullName: "Fields:TestElement",
@@ -31,10 +34,17 @@ function insertTestElement(txn, model, category, overrides, aspectProp = 999) {
31
34
  intProp: 100,
32
35
  point: { x: 1, y: 2, z: 3 },
33
36
  strings: ["a", "b", `"name": "c"`],
37
+ dateStrings: ["2025-08-28T13:45:30.123Z"],
34
38
  datetime: new Date("2025-08-28T13:45:30.123Z"),
39
+ lengthProp: 2.5,
35
40
  intEnum: 1,
36
41
  outerStruct: {
37
- innerStruct: { bool: false, doubles: [1, 2, 3] },
42
+ innerStruct: {
43
+ bool: false,
44
+ doubles: [1, 2, 3],
45
+ json: JSON.stringify({ contactInfo: { email: "duck@pond.example", phoneNumbers: [{ areaCode: 555 }] } }),
46
+ notJson: JSON.stringify({ contactInfo: { email: "not-indexable" } }),
47
+ },
38
48
  innerStructs: [{ bool: true, doubles: [] }, { bool: false, doubles: [5, 4, 3, 2, 1] }],
39
49
  },
40
50
  outerStructs: [{
@@ -49,6 +59,11 @@ function insertTestElement(txn, model, category, overrides, aspectProp = 999) {
49
59
  stringProp: "abc",
50
60
  ints: [10, 11, 12, 13],
51
61
  bool: true,
62
+ // Deliberately present-but-null, to check that a JSON null resolves to no value rather than
63
+ // to a FieldValue the formatters would have to stringify.
64
+ nullProp: null,
65
+ lengthMeters: 2.5,
66
+ readings: [1.5, null, "text"],
52
67
  zoo: {
53
68
  address: {
54
69
  zipcode: 12345,
@@ -157,6 +172,10 @@ const fieldsSchemaXml = `
157
172
  <ECSchema schemaName="Fields" alias="ts" version="01.00.00" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
158
173
  <ECSchemaReference name="BisCore" version="01.00.04" alias="bis"/>
159
174
  <ECSchemaReference name='ECDbMap' version='02.00.04' alias='ecdbmap' />
175
+ <ECSchemaReference name="Formats" version="01.00.00" alias="f"/>
176
+ <ECSchemaReference name="Units" version="01.00.09" alias="u"/>
177
+
178
+ <KindOfQuantity typeName="LENGTH" displayLabel="Length" persistenceUnit="u:M" relativeError="0.0001" presentationUnits="f:DefaultRealU(4)[u:M]"/>
160
179
 
161
180
  <ECEnumeration typeName="IntEnum" backingTypeName="int">
162
181
  <ECEnumerator name="one" displayLabel="One" value="1" />
@@ -165,6 +184,8 @@ const fieldsSchemaXml = `
165
184
 
166
185
  <ECStructClass typeName="InnerStruct" modifier="None">
167
186
  <ECProperty propertyName="bool" typeName="boolean"/>
187
+ <ECProperty propertyName="json" typeName="string" extendedTypeName="Json"/>
188
+ <ECProperty propertyName="notJson" typeName="string"/>
168
189
  <ECArrayProperty propertyName="doubles" typeName="double" minOccurs="0" maxOccurs="unbounded"/>
169
190
  </ECStructClass>
170
191
 
@@ -179,7 +200,9 @@ const fieldsSchemaXml = `
179
200
  <ECProperty propertyName="point" typeName="point3d"/>
180
201
  <ECProperty propertyName="maybeNull" typeName="int"/>
181
202
  <ECProperty propertyName="datetime" typeName="dateTime"/>
203
+ <ECProperty propertyName="lengthProp" typeName="double" kindOfQuantity="LENGTH"/>
182
204
  <ECArrayProperty propertyName="strings" typeName="string" minOccurs="0" maxOccurs="unbounded"/>
205
+ <ECArrayProperty propertyName="dateStrings" typeName="string" extendedTypeName="DateTime" minOccurs="0" maxOccurs="unbounded"/>
183
206
  <ECStructProperty propertyName="outerStruct" typeName="OuterStruct"/>
184
207
  <ECStructArrayProperty propertyName="outerStructs" typeName="OuterStruct" minOccurs="0" maxOccurs="unbounded"/>
185
208
  <ECProperty propertyName="intEnum" typeName="IntEnum"/>
@@ -244,13 +267,14 @@ describe("Field evaluation", () => {
244
267
  after(() => {
245
268
  imodel.close();
246
269
  });
247
- function evaluateField(propertyPath, propertyHost, deletedDependency = false) {
270
+ function evaluateField(propertyPath, propertyHost, deletedDependency = false, formatOptions) {
248
271
  if (typeof propertyHost === "string") {
249
272
  propertyHost = { schemaName: "Fields", className: "TestElement", elementId: propertyHost };
250
273
  }
251
274
  const field = FieldRun.create({
252
275
  propertyPath,
253
276
  propertyHost,
277
+ formatOptions,
254
278
  });
255
279
  const context = createUpdateContext(propertyHost.elementId, imodel, deletedDependency);
256
280
  return context.getProperty(field);
@@ -354,7 +378,10 @@ describe("Field evaluation", () => {
354
378
  }
355
379
  it("deduces type for primitive properties", () => {
356
380
  const propertyHost = { elementId: sourceElementId, schemaName: "Fields", className: "TestElement" };
357
- expect(getPropertyType(propertyHost, "intProp")).to.equal("string");
381
+ // "quantity" here is a routing decision, not a claim that this count has units: intProp
382
+ // names no KindOfQuantity, so it resolves no format and still renders as a bare "100".
383
+ // Classifying it this way is what lets a field supply its own KindOfQuantity to format it.
384
+ expect(getPropertyType(propertyHost, "intProp")).to.equal("quantity");
358
385
  expect(getPropertyType(propertyHost, "point")).to.equal("coordinate");
359
386
  expect(getPropertyType(propertyHost, { propertyName: "strings", accessors: [0] })).to.equal("string");
360
387
  expect(getPropertyType(propertyHost, "intEnum")).to.equal("int-enum");
@@ -365,6 +392,15 @@ describe("Field evaluation", () => {
365
392
  expect(getPropertyType(propertyHost, "LastMod")).to.equal("datetime");
366
393
  expect(getPropertyType(propertyHost, "FederationGuid")).to.equal("string");
367
394
  });
395
+ it("deduces the type of an array leaf from the schema rather than from the value", () => {
396
+ // Regression: the type used to be inferred by sniffing `typeof value` whenever the
397
+ // terminal EC property was a String primitive reached through accessors. Since a String
398
+ // *array* property also reports isPrimitive(), legitimate array leaves took the
399
+ // JSON-in-string branch and lost their schema-declared extended type.
400
+ const propertyHost = { elementId: sourceElementId, schemaName: "Fields", className: "TestElement" };
401
+ expect(getPropertyType(propertyHost, { propertyName: "strings", accessors: [0] })).to.equal("string");
402
+ expect(getPropertyType(propertyHost, { propertyName: "dateStrings", accessors: [0] })).to.equal("datetime");
403
+ });
368
404
  it("returns undefined for non-primitive properties", () => {
369
405
  const propertyHost = { elementId: sourceElementId, schemaName: "Fields", className: "TestElement" };
370
406
  expect(getPropertyType(propertyHost, "outerStruct")).to.equal(undefined);
@@ -427,6 +463,162 @@ describe("Field evaluation", () => {
427
463
  expect(fieldRun2.cachedContent).to.equal("a");
428
464
  });
429
465
  });
466
+ /** Drives the production format path for a hand-built [[ResolvedFieldValue]]: `updateField` builds a
467
+ * [FormatterSpec]($core-quantity) from the iModel's schema formats and hands `formatFieldValue`
468
+ * a magnitude callback bound to it. Returns the resulting cached content.
469
+ *
470
+ * Going through `updateField` rather than reimplementing the composition here is the point --
471
+ * these tests pin how the backend wires spec lookup to formatting, so a change to that wiring
472
+ * fails them. `onMiss` fires when the field produced candidates but none resolved.
473
+ */
474
+ function formatThroughSchema(value, options, onMiss) {
475
+ const field = FieldRun.create({
476
+ propertyHost: { elementId: "0x1", schemaName: "Fields", className: "TestElement" },
477
+ propertyPath: { propertyName: "unused" },
478
+ formatOptions: options,
479
+ });
480
+ const context = {
481
+ hostElementId: undefined,
482
+ getProperty: () => value,
483
+ formatting: createFieldFormatting({ iModel: imodel }),
484
+ };
485
+ const logWarning = sinon.stub(Logger, "logWarning").callsFake((_category, message) => {
486
+ if (message.startsWith("No format resolved for text annotation field"))
487
+ onMiss?.();
488
+ });
489
+ try {
490
+ updateField(field, context);
491
+ }
492
+ finally {
493
+ logWarning.restore();
494
+ }
495
+ return field.cachedContent;
496
+ }
497
+ describe("JSON-in-string properties", () => {
498
+ // `JsonProperties` is a String column of extended type `Json`; `jsonAccessors` index into the
499
+ // parsed JSON, so none of these paths have an EC property behind the leaf.
500
+ function jsonPath(...jsonAccessors) {
501
+ return { propertyName: "JsonProperties", jsonAccessors };
502
+ }
503
+ function evaluateJson(accessors, formatOptions) {
504
+ return evaluateField(jsonPath(...accessors), sourceElementId, false, formatOptions);
505
+ }
506
+ it("indexes into a deserialized JSON object", () => {
507
+ expect(evaluateJson(["stringProp"])?.value).to.equal("abc");
508
+ expect(evaluateJson(["bool"])?.value).to.equal(true);
509
+ expect(evaluateJson(["zoo", "address", "zipcode"])?.value).to.equal(12345);
510
+ });
511
+ it("indexes into a deserialized JSON array, including negative indices", () => {
512
+ expect(evaluateJson(["ints", 0])?.value).to.equal(10);
513
+ expect(evaluateJson(["ints", 3])?.value).to.equal(13);
514
+ expect(evaluateJson(["ints", -1])?.value).to.equal(13);
515
+ expect(evaluateJson(["zoo", "birds", 1, "sound"])?.value).to.equal("scree!");
516
+ });
517
+ it("returns undefined for a JSON null leaf rather than an unformattable value", () => {
518
+ // A JSON null is not a FieldPrimitiveValue. Producing one here would yield a
519
+ // `{ value: null, type: "string" }` FieldValue whose formatter called `null.toString()`.
520
+ expect(evaluateJson(["nullProp"])).to.be.undefined;
521
+ expect(evaluateJson(["readings", 1])).to.be.undefined;
522
+ });
523
+ it("does not throw when a field resolves to a JSON null", () => {
524
+ const fieldRun = FieldRun.create({
525
+ propertyHost: { elementId: sourceElementId, schemaName: "Fields", className: "TestElement" },
526
+ propertyPath: jsonPath("nullProp"),
527
+ cachedContent: "stale",
528
+ });
529
+ const context = createUpdateContext(sourceElementId, imodel, false);
530
+ expect(() => updateField(fieldRun, context)).to.not.throw();
531
+ expect(fieldRun.cachedContent).to.equal(FieldRun.invalidContentIndicator);
532
+ });
533
+ it("returns undefined for a missing key or an out-of-range index", () => {
534
+ expect(evaluateJson(["nope"])).to.be.undefined;
535
+ expect(evaluateJson(["toString"])).to.be.undefined;
536
+ expect(evaluateJson(["zoo", "address", "street"])).to.be.undefined;
537
+ expect(evaluateJson(["ints", 4])).to.be.undefined;
538
+ expect(evaluateJson(["ints", -5])).to.be.undefined;
539
+ });
540
+ it("returns undefined when the path stops on a JSON object or array", () => {
541
+ expect(evaluateJson(["zoo"])).to.be.undefined;
542
+ expect(evaluateJson(["ints"])).to.be.undefined;
543
+ });
544
+ it("returns undefined when indexing past a JSON scalar", () => {
545
+ expect(evaluateJson(["stringProp", "more"])).to.be.undefined;
546
+ expect(evaluateJson(["bool", 0])).to.be.undefined;
547
+ });
548
+ it("keeps the raw string when the property is not indexed", () => {
549
+ const value = evaluateField({ propertyName: "JsonProperties" }, sourceElementId);
550
+ expect(value?.type).to.equal("string");
551
+ expect(value?.value).to.be.a("string").and.to.contain("stringProp");
552
+ });
553
+ it("does not treat plain accessors as JSON accessors", () => {
554
+ // A String property cannot be walked with `accessors`; only `jsonAccessors` continue into the JSON.
555
+ expect(evaluateField({ propertyName: "JsonProperties", accessors: ["stringProp"] }, sourceElementId)).to.be.undefined;
556
+ });
557
+ it("indexes into a JSON property reached through accessors", () => {
558
+ const path = (...jsonAccessors) => ({
559
+ propertyName: "outerStruct",
560
+ accessors: ["innerStruct", "json"],
561
+ jsonAccessors,
562
+ });
563
+ expect(evaluateField(path("contactInfo", "email"), sourceElementId)?.value).to.equal("duck@pond.example");
564
+ expect(evaluateField(path("contactInfo", "phoneNumbers", 0, "areaCode"), sourceElementId)?.value).to.equal(555);
565
+ expect(evaluateField(path("contactInfo", "missing"), sourceElementId)).to.be.undefined;
566
+ });
567
+ it("requires the property to have the Json extended type", () => {
568
+ const path = {
569
+ propertyName: "outerStruct",
570
+ accessors: ["innerStruct", "notJson"],
571
+ jsonAccessors: ["contactInfo", "email"],
572
+ };
573
+ expect(evaluateField(path, sourceElementId)).to.be.undefined;
574
+ // Without jsonAccessors the plain string still resolves to itself.
575
+ expect(evaluateField({ ...path, jsonAccessors: undefined }, sourceElementId)?.value).to.be.a("string").and.to.contain("not-indexable");
576
+ });
577
+ it("ignores an empty jsonAccessors array", () => {
578
+ const value = evaluateField({ propertyName: "JsonProperties", jsonAccessors: [] }, sourceElementId);
579
+ expect(value?.type).to.equal("string");
580
+ });
581
+ it("types string and boolean leaves from the parsed JSON", () => {
582
+ expect(evaluateJson(["stringProp"])?.type).to.equal("string");
583
+ expect(evaluateJson(["bool"])?.type).to.equal("boolean");
584
+ expect(evaluateJson(["readings", 2])?.type).to.equal("string");
585
+ });
586
+ it("types a numeric leaf as a quantity", () => {
587
+ const value = evaluateJson(["lengthMeters"]);
588
+ expect(value?.type).to.equal("quantity");
589
+ expect(value?.value).to.equal(2.5);
590
+ });
591
+ it("renders a numeric leaf as its raw value", () => {
592
+ const fieldRun = FieldRun.create({
593
+ propertyHost: { elementId: sourceElementId, schemaName: "Fields", className: "TestElement" },
594
+ propertyPath: jsonPath("lengthMeters"),
595
+ });
596
+ const context = createUpdateContext(sourceElementId, imodel, false);
597
+ expect(updateField(fieldRun, context)).to.be.true;
598
+ expect(fieldRun.cachedContent).to.equal("2.5");
599
+ });
600
+ it("carries no property-side KoQ for a numeric leaf", () => {
601
+ // There is no EC property behind a JSON leaf, so the only pair the formatter can build is
602
+ // the one the field supplies.
603
+ const value = evaluateJson(["lengthMeters"], { quantity: { kindOfQuantity: "AecUnits.LENGTH", persistenceUnit: "Units.M" } });
604
+ expect(value?.type).to.equal("quantity");
605
+ expect(value?.value).to.equal(2.5);
606
+ expect(value?.kindOfQuantityFullName).to.be.undefined;
607
+ expect(value?.persistenceUnitFullName).to.be.undefined;
608
+ });
609
+ it("renders a numeric leaf as its raw value when the field supplies an incomplete key", () => {
610
+ // "quantity" with no resolvable (KoQ, unit) pair is indistinguishable from the plain string
611
+ // rendering, and logs no warning.
612
+ let missed = false;
613
+ for (const quantity of [undefined, { kindOfQuantity: "Fields.LENGTH" }, { persistenceUnit: "Units.M" }]) {
614
+ const options = quantity ? { quantity } : undefined;
615
+ const value = evaluateJson(["lengthMeters"], options);
616
+ expect(value?.type).to.equal("quantity");
617
+ expect(formatThroughSchema(value, options, () => { missed = true; })).to.equal("2.5");
618
+ }
619
+ expect(missed, "an unformattable JSON leaf is not an unresolved requirement").to.be.false;
620
+ });
621
+ });
430
622
  function createAnnotationElement(textBlock) {
431
623
  const elem = TextAnnotation3d.fromJSON({
432
624
  model,
@@ -450,19 +642,21 @@ describe("Field evaluation", () => {
450
642
  return withEditTxn(imodel, (txn) => txn.insertElement(elem.toJSON()));
451
643
  }
452
644
  describe("ElementDrivesTextAnnotation", () => {
453
- function expectNumRelationships(expected, targetId) {
454
- const where = targetId ? ` WHERE TargetECInstanceId=${targetId}` : "";
455
- const ecsql = `SELECT COUNT(*) FROM BisCore.ElementDrivesTextAnnotation ${where}`;
456
- // eslint-disable-next-line @typescript-eslint/no-deprecated
457
- imodel.withPreparedStatement(ecsql, (stmt) => {
458
- expect(stmt.step()).to.equal(DbResult.BE_SQLITE_ROW);
459
- expect(stmt.getValue(0).getInteger()).to.equal(expected);
460
- });
645
+ /** Counts relationships targeting `targetId`. Deliberately requires a target: a global count
646
+ * would silently depend on cleanup performed by other describe blocks.
647
+ */
648
+ async function expectNumRelationships(expected, targetId) {
649
+ const ecsql = `SELECT COUNT(*) FROM BisCore.ElementDrivesTextAnnotation WHERE TargetECInstanceId=${targetId}`;
650
+ const reader = imodel.createQueryReader(ecsql);
651
+ expect(await reader.step()).to.be.true;
652
+ expect(reader.current[0]).to.equal(expected);
461
653
  }
462
- it("can be inserted", () => {
463
- expectNumRelationships(0);
654
+ it("can be inserted", async () => {
464
655
  const targetId = insertAnnotationElement(undefined);
465
656
  expect(targetId).not.to.equal(Id64.invalid);
657
+ // Scoped to this target rather than the whole iModel: other describe blocks insert
658
+ // annotations of their own, so a global count would couple this test to their cleanup.
659
+ await expectNumRelationships(0, targetId);
466
660
  const target = imodel.elements.getElement(targetId);
467
661
  expect(target.classFullName).to.equal("BisCore:TextAnnotation3d");
468
662
  expect(target).instanceof(TextAnnotation3d);
@@ -471,7 +665,7 @@ describe("Field evaluation", () => {
471
665
  const rel = ElementDrivesTextAnnotation.create(imodel, sourceElementId, targetId);
472
666
  const relId = withEditTxn(imodel, (txn) => txn.insertRelationship(rel.toJSON()));
473
667
  expect(relId).not.to.equal(Id64.invalid);
474
- expectNumRelationships(1);
668
+ await expectNumRelationships(1, targetId);
475
669
  const relationship = imodel.relationships.getInstance("BisCore:ElementDrivesTextAnnotation", relId);
476
670
  expect(relationship.sourceId).to.equal(sourceElementId);
477
671
  expect(relationship.targetId).to.equal(targetId);
@@ -488,37 +682,37 @@ describe("Field evaluation", () => {
488
682
  });
489
683
  }
490
684
  describe("updateFieldDependencies", () => {
491
- it("creates exactly one relationship for each unique source element on insert and update", () => {
685
+ it("creates exactly one relationship for each unique source element on insert and update", async () => {
492
686
  const source1 = withEditTxn(imodel, (editTxn) => insertTestElement(editTxn, model, category));
493
687
  const block = TextBlock.create();
494
688
  block.appendRun(createField(source1, "1"));
495
689
  const targetId = insertAnnotationElement(block);
496
- expectNumRelationships(1, targetId);
690
+ await expectNumRelationships(1, targetId);
497
691
  const source2 = withEditTxn(imodel, (editTxn) => insertTestElement(editTxn, model, category));
498
692
  const target = imodel.elements.getElement(targetId);
499
693
  const anno = target.getAnnotation();
500
694
  anno.textBlock.appendRun(createField(source2, "2a"));
501
695
  target.setAnnotation(anno);
502
696
  withEditTxn(imodel, (txn) => target.update(txn));
503
- expectNumRelationships(2, targetId);
697
+ await expectNumRelationships(2, targetId);
504
698
  anno.textBlock.appendRun(createField(source2, "2b"));
505
699
  target.setAnnotation(anno);
506
700
  withEditTxn(imodel, (txn) => target.update(txn));
507
- expectNumRelationships(2, targetId);
701
+ await expectNumRelationships(2, targetId);
508
702
  const source3 = withEditTxn(imodel, (editTxn) => insertTestElement(editTxn, model, category));
509
703
  anno.textBlock.appendRun(createField(source3, "3"));
510
704
  target.setAnnotation(anno);
511
705
  withEditTxn(imodel, (txn) => target.update(txn));
512
- expectNumRelationships(3, targetId);
706
+ await expectNumRelationships(3, targetId);
513
707
  });
514
- it("deletes stale relationships", () => {
708
+ it("deletes stale relationships", async () => {
515
709
  const sourceA = withEditTxn(imodel, (editTxn) => insertTestElement(editTxn, model, category));
516
710
  const sourceB = withEditTxn(imodel, (editTxn) => insertTestElement(editTxn, model, category));
517
711
  const block = TextBlock.create();
518
712
  block.appendRun(createField(sourceA, "A"));
519
713
  block.appendRun(createField(sourceB, "B"));
520
714
  const targetId = insertAnnotationElement(block);
521
- expectNumRelationships(2, targetId);
715
+ await expectNumRelationships(2, targetId);
522
716
  expect(imodel.relationships.tryGetInstance(ElementDrivesTextAnnotation.classFullName, { targetId, sourceId: sourceA })).not.to.be.undefined;
523
717
  expect(imodel.relationships.tryGetInstance(ElementDrivesTextAnnotation.classFullName, { targetId, sourceId: sourceB })).not.to.be.undefined;
524
718
  const target = imodel.elements.getElement(targetId);
@@ -528,14 +722,14 @@ describe("Field evaluation", () => {
528
722
  p1.children.shift();
529
723
  target.setAnnotation(anno);
530
724
  withEditTxn(imodel, (txn) => target.update(txn));
531
- expectNumRelationships(1, targetId);
725
+ await expectNumRelationships(1, targetId);
532
726
  expect(imodel.relationships.tryGetInstance(ElementDrivesTextAnnotation.classFullName, { targetId, sourceId: sourceA })).to.be.undefined;
533
727
  expect(imodel.relationships.tryGetInstance(ElementDrivesTextAnnotation.classFullName, { targetId, sourceId: sourceB })).not.to.be.undefined;
534
728
  anno.textBlock.children.length = 0;
535
729
  anno.textBlock.appendRun(createField(sourceA, "A2"));
536
730
  target.setAnnotation(anno);
537
731
  withEditTxn(imodel, (txn) => target.update(txn));
538
- expectNumRelationships(1, targetId);
732
+ await expectNumRelationships(1, targetId);
539
733
  expect(imodel.relationships.tryGetInstance(ElementDrivesTextAnnotation.classFullName, { targetId, sourceId: sourceA })).not.to.be.undefined;
540
734
  expect(imodel.relationships.tryGetInstance(ElementDrivesTextAnnotation.classFullName, { targetId, sourceId: sourceB })).to.be.undefined;
541
735
  anno.textBlock.children.length = 0;
@@ -545,18 +739,18 @@ describe("Field evaluation", () => {
545
739
  }));
546
740
  target.setAnnotation(anno);
547
741
  withEditTxn(imodel, (txn) => target.update(txn));
548
- expectNumRelationships(0, targetId);
742
+ await expectNumRelationships(0, targetId);
549
743
  expect(imodel.relationships.tryGetInstance(ElementDrivesTextAnnotation.classFullName, { targetId, sourceId: sourceA })).to.be.undefined;
550
744
  expect(imodel.relationships.tryGetInstance(ElementDrivesTextAnnotation.classFullName, { targetId, sourceId: sourceB })).to.be.undefined;
551
745
  });
552
- it("ignores invalid source element Ids", () => {
746
+ it("ignores invalid source element Ids", async () => {
553
747
  const source = withEditTxn(imodel, (editTxn) => insertTestElement(editTxn, model, category));
554
748
  const block = TextBlock.create();
555
749
  block.appendRun(createField(Id64.invalid, "invalid"));
556
750
  block.appendRun(createField("0xbaadf00d", "non-existent"));
557
751
  block.appendRun(createField(source, "valid"));
558
752
  const targetId = insertAnnotationElement(block);
559
- expectNumRelationships(1, targetId);
753
+ await expectNumRelationships(1, targetId);
560
754
  });
561
755
  });
562
756
  function expectText(expected, elemId, db) {