@itwin/core-backend
Version:
iTwin.js backend components
576 lines (572 loc) • 31 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { expect } from "chai";
import { Code, FieldRun, SubCategoryAppearance, TextAnnotation, TextBlock, TextRun } from "@itwin/core-common";
import { StandaloneDb } from "../../IModelDb";
import { IModelTestUtils } from "../IModelTestUtils";
import { createUpdateContext, updateField, updateFields } from "../../internal/annotations/fields";
import { DbResult, Id64 } from "@itwin/core-bentley";
import { SpatialCategory } from "../../Category";
import { Point3d, YawPitchRollAngles } from "@itwin/core-geometry";
import { Schema, Schemas } from "../../Schema";
import { ClassRegistry } from "../../ClassRegistry";
import { PhysicalElement } from "../../Element";
import { ElementOwnsUniqueAspect, ElementUniqueAspect, FontFile, TextAnnotation3d } from "../../core-backend";
import { ElementDrivesTextAnnotation } from "../../annotations/ElementDrivesTextAnnotation";
describe("updateField", () => {
const mockElementId = "0x1";
const mockPath = {
propertyName: "mockProperty",
accessors: [0, "nestedProperty"],
};
const mockCachedContent = "cachedContent";
const mockUpdatedContent = "updatedContent";
const createMockContext = (elementId, propertyValue) => ({
hostElementId: elementId,
getProperty: (field) => {
const propertyPath = field.propertyPath;
if (propertyPath.propertyName === "mockProperty" &&
propertyPath.accessors?.[0] === 0 &&
propertyPath.accessors?.[1] === "nestedProperty" &&
propertyValue !== undefined) {
return { value: propertyValue, metadata: {} };
}
return undefined;
},
});
it("does nothing if hostElementId does not match", () => {
const fieldRun = FieldRun.create({
propertyHost: { elementId: mockElementId, schemaName: "TestSchema", className: "TestClass" },
propertyPath: mockPath,
cachedContent: mockCachedContent,
});
const context = createMockContext("0x2", mockUpdatedContent);
const result = updateField(fieldRun, context);
expect(result).to.be.false;
expect(fieldRun.cachedContent).to.equal(mockCachedContent);
});
it("produces invalid content indicator if property value is undefined", () => {
const fieldRun = FieldRun.create({
propertyHost: { elementId: mockElementId, schemaName: "TestSchema", className: "TestClass" },
propertyPath: mockPath,
cachedContent: mockCachedContent,
});
const context = createMockContext(mockElementId);
const result = updateField(fieldRun, context);
expect(result).to.be.true;
expect(fieldRun.cachedContent).to.equal(FieldRun.invalidContentIndicator);
});
it("returns false if cached content matches new content", () => {
const fieldRun = FieldRun.create({
propertyHost: { elementId: mockElementId, schemaName: "TestSchema", className: "TestClass" },
propertyPath: mockPath,
cachedContent: mockCachedContent,
});
const context = createMockContext(mockElementId, mockCachedContent);
const result = updateField(fieldRun, context);
expect(result).to.be.false;
expect(fieldRun.cachedContent).to.equal(mockCachedContent);
});
it("returns true and updates cached content if new content is different", () => {
const fieldRun = FieldRun.create({
propertyHost: { elementId: mockElementId, schemaName: "TestSchema", className: "TestClass" },
propertyPath: mockPath,
cachedContent: mockCachedContent,
});
const context = createMockContext(mockElementId, mockUpdatedContent);
const result = updateField(fieldRun, context);
expect(result).to.be.true;
expect(fieldRun.cachedContent).to.equal(mockUpdatedContent);
});
it("resolves to invalid content indicator if an exception occurs", () => {
const fieldRun = FieldRun.create({
propertyHost: { elementId: mockElementId, schemaName: "TestSchema", className: "TestClass" },
propertyPath: mockPath,
cachedContent: mockCachedContent,
});
const context = {
hostElementId: mockElementId,
getProperty: () => {
throw new Error("Test exception");
},
};
const result = updateField(fieldRun, context);
expect(result).to.be.true;
expect(fieldRun.cachedContent).to.equal(FieldRun.invalidContentIndicator);
});
});
const fieldsSchemaXml = `
xml version="1.0" encoding="UTF-8"
<ECSchema schemaName="Fields" alias="ts" version="01.00.00" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
<ECSchemaReference name="BisCore" version="01.00.04" alias="bis"/>
<ECStructClass typeName="InnerStruct" modifier="None">
<ECProperty propertyName="bool" typeName="boolean"/>
<ECArrayProperty propertyName="doubles" typeName="double" minOccurs="0" maxOccurs="unbounded"/>
</ECStructClass>
<ECStructClass typeName="OuterStruct" modifier="None">
<ECStructProperty propertyName="innerStruct" typeName="InnerStruct"/>
<ECStructArrayProperty propertyName="innerStructs" typeName="InnerStruct" minOccurs="0" maxOccurs="unbounded"/>
</ECStructClass>
<ECEntityClass typeName="TestElement" modifier="None">
<BaseClass>bis:PhysicalElement</BaseClass>
<ECProperty propertyName="intProp" typeName="int"/>
<ECProperty propertyName="point" typeName="point3d"/>
<ECProperty propertyName="maybeNull" typeName="int"/>
<ECArrayProperty propertyName="strings" typeName="string" minOccurs="0" maxOccurs="unbounded"/>
<ECStructProperty propertyName="outerStruct" typeName="OuterStruct"/>
<ECStructArrayProperty propertyName="outerStructs" typeName="OuterStruct" minOccurs="0" maxOccurs="unbounded"/>
</ECEntityClass>
<ECEntityClass typeName="TestAspect" modifier="None">
<BaseClass>bis:ElementUniqueAspect</BaseClass>
<ECProperty propertyName="aspectProp" typeName="int"/>
</ECEntityClass>
</ECSchema>
`;
class TestElement extends PhysicalElement {
static get className() { return "TestElement"; }
}
class TestAspect extends ElementUniqueAspect {
static get className() { return "TestAspect"; }
}
class FieldsSchema extends Schema {
static get schemaName() { return "Fields"; }
}
async function registerTestSchema(iModel) {
if (!Schemas.getRegisteredSchema("Fields")) {
Schemas.registerSchema(FieldsSchema);
ClassRegistry.register(TestElement, FieldsSchema);
ClassRegistry.register(TestAspect, FieldsSchema);
}
await iModel.importSchemaStrings([fieldsSchemaXml]);
iModel.saveChanges();
}
describe("Field evaluation", () => {
let imodel;
let model;
let category;
let sourceElementId;
before(async () => {
const iModelPath = IModelTestUtils.prepareOutputFile("UpdateFieldsContext", "test.bim");
imodel = StandaloneDb.createEmpty(iModelPath, { rootSubject: { name: "UpdateFieldsContext" }, allowEdit: JSON.stringify({ txns: true }) });
await registerTestSchema(imodel);
model = IModelTestUtils.createAndInsertPhysicalPartitionAndModel(imodel, Code.createEmpty(), true)[1];
category = SpatialCategory.insert(imodel, StandaloneDb.dictionaryId, "UpdateFieldsContextCategory", new SubCategoryAppearance());
sourceElementId = insertTestElement();
await imodel.fonts.embedFontFile({
file: FontFile.createFromTrueTypeFileName(IModelTestUtils.resolveFontFile("Karla-Regular.ttf"))
});
});
after(() => {
imodel.close();
});
function insertTestElement() {
const props = {
classFullName: "Fields:TestElement",
model,
category,
code: Code.createEmpty(),
intProp: 100,
point: { x: 1, y: 2, z: 3 },
strings: ["a", "b", `"name": "c"`],
outerStruct: {
innerStruct: { bool: false, doubles: [1, 2, 3] },
innerStructs: [{ bool: true, doubles: [] }, { bool: false, doubles: [5, 4, 3, 2, 1] }],
},
outerStructs: [{
innerStruct: { bool: true, doubles: [10, 9] },
innerStructs: [{ bool: false, doubles: [5] }],
}],
placement: {
origin: new Point3d(1, 2, 0),
angles: new YawPitchRollAngles(),
},
jsonProperties: {
stringProp: "abc",
ints: [10, 11, 12, 13],
zoo: {
address: {
zipcode: 12345,
},
birds: [
{ name: "duck", sound: "quack" },
{ name: "hawk", sound: "scree!" },
],
},
},
};
const id = imodel.elements.insertElement(props);
const aspectProps = {
classFullName: TestAspect.classFullName,
aspectProp: 999,
element: new ElementOwnsUniqueAspect(id),
};
imodel.elements.insertAspect(aspectProps);
imodel.saveChanges();
return id;
}
describe("getProperty", () => {
function expectValue(expected, propertyPath, propertyHost, deletedDependency = false) {
if (typeof propertyHost === "string") {
propertyHost = { schemaName: "Fields", className: "TestElement", elementId: propertyHost };
}
const field = FieldRun.create({
propertyPath,
propertyHost,
});
const context = createUpdateContext(propertyHost.elementId, imodel, deletedDependency);
const actual = context.getProperty(field);
expect(actual?.value).to.deep.equal(expected);
}
it("returns a primitive property value", () => {
expectValue(100, { propertyName: "intProp" }, sourceElementId);
});
it("treats points as primitive values", () => {
expectValue({ x: 1, y: 2, z: 3 }, { propertyName: "point" }, sourceElementId);
expectValue(undefined, { propertyName: "point", accessors: ["x"] }, sourceElementId);
});
it("returns a primitive array value", () => {
expectValue("a", { propertyName: "strings", accessors: [0] }, sourceElementId);
expectValue("b", { propertyName: "strings", accessors: [1] }, sourceElementId);
expectValue(`"name": "c"`, { propertyName: "strings", accessors: [2] }, sourceElementId);
});
it("supports negative array indices", () => {
expectValue("a", { propertyName: "strings", accessors: [-3] }, sourceElementId);
expectValue("b", { propertyName: "strings", accessors: [-2] }, sourceElementId);
expectValue(`"name": "c"`, { propertyName: "strings", accessors: [-1] }, sourceElementId);
});
it("returns undefined if the dependency was deleted", () => {
expectValue(undefined, { propertyName: "intProp" }, sourceElementId, true);
});
it("returns undefined if the host element does not exist", () => {
expectValue(undefined, { propertyName: "intProp" }, "0xbaadf00d");
});
it("returns undefined if the host element is not of the specified class or a subclass thereof", () => {
expectValue(undefined, { propertyName: "origin" }, { schemaName: "BisCore", className: "GeometricElement2d", elementId: sourceElementId });
});
it("returns undefined if an access string is specified for a non-object property", () => {
expectValue(undefined, { propertyName: "intProp", accessors: ["property"] }, sourceElementId);
});
it("returns undefined if the specified property does not exist", () => {
expectValue(undefined, { propertyName: "nonExistentProperty" }, sourceElementId);
});
it("returns undefined if the specified property is null", () => {
expectValue(undefined, { propertyName: "maybeNull" }, sourceElementId);
});
it("returns undefined if an array index is specified for a non-array property", () => {
expectValue(undefined, { propertyName: "intProp", accessors: [0] }, sourceElementId);
});
it("returns undefined if an array index is out of bounds", () => {
for (const index of [3, 4, -4, -5]) {
expectValue(undefined, { propertyName: "strings", accessors: [index] }, sourceElementId);
}
});
it("returns undefined for a non-primitive value", () => {
expectValue(undefined, { propertyName: "strings" }, sourceElementId);
expectValue(undefined, { propertyName: "outerStruct" }, sourceElementId);
expectValue(undefined, { propertyName: "outerStruct", accessors: ["innerStruct"] }, sourceElementId);
expectValue(undefined, { propertyName: "outerStructs" }, sourceElementId);
expectValue(undefined, { propertyName: "outerStructs", accessors: [0] }, sourceElementId);
expectValue(undefined, { propertyName: "outerStructs", accessors: [0, "innerStruct"] }, sourceElementId);
});
it("returns arbitrarily-nested properties of structs and struct arrays", () => {
expectValue(false, { propertyName: "outerStruct", accessors: ["innerStruct", "bool"] }, sourceElementId);
for (const index of [0, 1, 2]) {
expectValue(index + 1, { propertyName: "outerStruct", accessors: ["innerStruct", "doubles", index] }, sourceElementId);
expectValue(3 - index, { propertyName: "outerStruct", accessors: ["innerStruct", "doubles", -1 - index] }, sourceElementId);
}
expectValue(9, { propertyName: "outerStructs", accessors: [0, "innerStruct", "doubles", 1] }, sourceElementId);
expectValue(false, { propertyName: "outerStructs", accessors: [0, "innerStructs", -1, "bool"] }, sourceElementId);
expectValue(5, { propertyName: "outerStructs", accessors: [0, "innerStructs", 0, "doubles", 0] }, sourceElementId);
});
it("returns arbitrarily-nested JSON properties", () => {
expectValue("abc", { propertyName: "jsonProperties", jsonAccessors: ["stringProp"] }, sourceElementId);
expectValue(10, { propertyName: "jsonProperties", jsonAccessors: ["ints", 0] }, sourceElementId);
expectValue(13, { propertyName: "jsonProperties", jsonAccessors: ["ints", 3] }, sourceElementId);
expectValue(13, { propertyName: "jsonProperties", jsonAccessors: ["ints", -1] }, sourceElementId);
expectValue(11, { propertyName: "jsonProperties", jsonAccessors: ["ints", -3] }, sourceElementId);
expectValue(12345, { propertyName: "jsonProperties", jsonAccessors: ["zoo", "address", "zipcode"] }, sourceElementId);
expectValue("scree!", { propertyName: "jsonProperties", jsonAccessors: ["zoo", "birds", 1, "sound"] }, sourceElementId);
});
it("returns undefined if JSON accessors applied to non-JSON property", () => {
expectValue(undefined, { propertyName: "int", jsonAccessors: ["whatever"] }, sourceElementId);
expectValue(undefined, { propertyName: "strings", accessors: [2, "name"] }, sourceElementId);
expectValue(undefined, { propertyName: "outerStruct", accessors: ["innerStruct"], jsonAccessors: ["bool"] }, sourceElementId);
});
it("returns the value of a property of an aspect", () => {
expect(imodel.elements.getAspects(sourceElementId, "Fields:TestAspect").length).to.equal(1);
expectValue(999, { propertyName: "aspectProp" }, { elementId: sourceElementId, schemaName: "Fields", className: "TestAspect" });
});
});
describe("updateFields", () => {
it("recomputes cached content", () => {
const textBlock = TextBlock.create({ styleId: "0x123" });
const fieldRun = FieldRun.create({
propertyHost: { elementId: sourceElementId, schemaName: "Fields", className: "TestElement" },
propertyPath: { propertyName: "intProp" },
cachedContent: "oldValue",
});
textBlock.appendRun(fieldRun);
const context = createUpdateContext(sourceElementId, imodel, false);
const updatedCount = updateFields(textBlock, context);
expect(updatedCount).to.equal(1);
expect(fieldRun.cachedContent).to.equal("100"); // `intProp` value from the test element
});
it("does not update a field if recomputed content matches cached content", () => {
const textBlock = TextBlock.create({ styleId: "0x123" });
const fieldRun = FieldRun.create({
propertyHost: { elementId: sourceElementId, schemaName: "Fields", className: "TestElement" },
propertyPath: { propertyName: "intProp" },
cachedContent: "100",
});
textBlock.appendRun(fieldRun);
const context = createUpdateContext(sourceElementId, imodel, false);
const updatedCount = updateFields(textBlock, context);
expect(updatedCount).to.equal(0);
expect(fieldRun.cachedContent).to.equal("100");
});
it("returns the number of fields updated", () => {
const textBlock = TextBlock.create({ styleId: "0x123" });
const fieldRun1 = FieldRun.create({
propertyHost: { elementId: sourceElementId, schemaName: "Fields", className: "TestElement" },
propertyPath: { propertyName: "intProp" },
cachedContent: "100",
});
const fieldRun2 = FieldRun.create({
propertyHost: { elementId: sourceElementId, schemaName: "Fields", className: "TestElement" },
propertyPath: { propertyName: "strings", accessors: [0] },
cachedContent: "oldValue",
});
textBlock.appendRun(fieldRun1);
textBlock.appendRun(fieldRun2);
const context = createUpdateContext(sourceElementId, imodel, false);
const updatedCount = updateFields(textBlock, context);
expect(updatedCount).to.equal(1);
expect(fieldRun1.cachedContent).to.equal("100");
expect(fieldRun2.cachedContent).to.equal("a");
});
});
function insertAnnotationElement(textBlock) {
const elem = TextAnnotation3d.fromJSON({
model,
category,
code: Code.createEmpty(),
placement: {
origin: { x: 0, y: 0, z: 0 },
angles: YawPitchRollAngles.createDegrees(0, 0, 0).toJSON(),
},
classFullName: TextAnnotation3d.classFullName,
}, imodel);
if (textBlock) {
const annotation = TextAnnotation.fromJSON({ textBlock: textBlock.toJSON() });
elem.setAnnotation(annotation);
}
return elem.insert();
}
describe("ElementDrivesTextAnnotation", () => {
function expectNumRelationships(expected, targetId) {
const where = targetId ? ` WHERE TargetECInstanceId=${targetId}` : "";
const ecsql = `SELECT COUNT(*) FROM BisCore.ElementDrivesTextAnnotation ${where}`;
// eslint-disable-next-line @typescript-eslint/no-deprecated
imodel.withPreparedStatement(ecsql, (stmt) => {
expect(stmt.step()).to.equal(DbResult.BE_SQLITE_ROW);
expect(stmt.getValue(0).getInteger()).to.equal(expected);
});
}
it("can be inserted", () => {
expectNumRelationships(0);
const targetId = insertAnnotationElement(undefined);
expect(targetId).not.to.equal(Id64.invalid);
const target = imodel.elements.getElement(targetId);
expect(target.classFullName).to.equal("BisCore:TextAnnotation3d");
expect(target).instanceof(TextAnnotation3d);
const targetAnno = imodel.elements.getElement(targetId);
expect(targetAnno).instanceof(TextAnnotation3d);
const rel = ElementDrivesTextAnnotation.create(imodel, sourceElementId, targetId);
const relId = rel.insert();
expect(relId).not.to.equal(Id64.invalid);
expectNumRelationships(1);
const relationship = imodel.relationships.getInstance("BisCore:ElementDrivesTextAnnotation", relId);
expect(relationship.sourceId).to.equal(sourceElementId);
expect(relationship.targetId).to.equal(targetId);
});
function createField(propertyHost, cachedContent, propertyName = "intProp", accessors, jsonAccessors) {
if (typeof propertyHost === "string") {
propertyHost = { schemaName: "Fields", className: "TestElement", elementId: propertyHost };
}
return FieldRun.create({
styleOverrides: { fontName: "Karla" },
propertyHost,
cachedContent,
propertyPath: { propertyName, accessors, jsonAccessors },
});
}
describe("updateFieldDependencies", () => {
it("creates exactly one relationship for each unique source element on insert and update", () => {
const source1 = insertTestElement();
const block = TextBlock.create({ styleId: "0x123" });
block.appendRun(createField(source1, "1"));
const targetId = insertAnnotationElement(block);
imodel.saveChanges();
expectNumRelationships(1, targetId);
const source2 = insertTestElement();
const target = imodel.elements.getElement(targetId);
const anno = target.getAnnotation();
anno.textBlock.appendRun(createField(source2, "2a"));
target.setAnnotation(anno);
target.update();
imodel.saveChanges();
expectNumRelationships(2, targetId);
anno.textBlock.appendRun(createField(source2, "2b"));
target.setAnnotation(anno);
target.update();
imodel.saveChanges();
expectNumRelationships(2, targetId);
const source3 = insertTestElement();
anno.textBlock.appendRun(createField(source3, "3"));
target.setAnnotation(anno);
target.update();
imodel.saveChanges();
expectNumRelationships(3, targetId);
});
it("deletes stale relationships", () => {
const sourceA = insertTestElement();
const sourceB = insertTestElement();
const block = TextBlock.create({ styleId: "0x123" });
block.appendRun(createField(sourceA, "A"));
block.appendRun(createField(sourceB, "B"));
const targetId = insertAnnotationElement(block);
imodel.saveChanges();
expectNumRelationships(2, targetId);
expect(imodel.relationships.tryGetInstance(ElementDrivesTextAnnotation.classFullName, { targetId, sourceId: sourceA })).not.to.be.undefined;
expect(imodel.relationships.tryGetInstance(ElementDrivesTextAnnotation.classFullName, { targetId, sourceId: sourceB })).not.to.be.undefined;
const target = imodel.elements.getElement(targetId);
const anno = target.getAnnotation();
anno.textBlock.paragraphs[0].runs.shift();
target.setAnnotation(anno);
target.update();
imodel.saveChanges();
expectNumRelationships(1, targetId);
expect(imodel.relationships.tryGetInstance(ElementDrivesTextAnnotation.classFullName, { targetId, sourceId: sourceA })).to.be.undefined;
expect(imodel.relationships.tryGetInstance(ElementDrivesTextAnnotation.classFullName, { targetId, sourceId: sourceB })).not.to.be.undefined;
anno.textBlock.paragraphs.length = 0;
anno.textBlock.appendRun(createField(sourceA, "A2"));
target.setAnnotation(anno);
target.update();
imodel.saveChanges();
expectNumRelationships(1, targetId);
expect(imodel.relationships.tryGetInstance(ElementDrivesTextAnnotation.classFullName, { targetId, sourceId: sourceA })).not.to.be.undefined;
expect(imodel.relationships.tryGetInstance(ElementDrivesTextAnnotation.classFullName, { targetId, sourceId: sourceB })).to.be.undefined;
anno.textBlock.paragraphs.length = 0;
anno.textBlock.appendRun(TextRun.create({
styleOverrides: { fontName: "Karla" },
content: "not a field",
}));
target.setAnnotation(anno);
target.update();
imodel.saveChanges();
expectNumRelationships(0, targetId);
expect(imodel.relationships.tryGetInstance(ElementDrivesTextAnnotation.classFullName, { targetId, sourceId: sourceA })).to.be.undefined;
expect(imodel.relationships.tryGetInstance(ElementDrivesTextAnnotation.classFullName, { targetId, sourceId: sourceB })).to.be.undefined;
});
it("ignores invalid source element Ids", () => {
const source = insertTestElement();
const block = TextBlock.create({ styleId: "0x123" });
block.appendRun(createField(Id64.invalid, "invalid"));
block.appendRun(createField("0xbaadf00d", "non-existent"));
block.appendRun(createField(source, "valid"));
const targetId = insertAnnotationElement(block);
imodel.saveChanges();
expectNumRelationships(1, targetId);
});
});
function expectText(expected, elemId) {
const elem = imodel.elements.getElement(elemId);
const anno = elem.getAnnotation();
const actual = anno.textBlock.stringify();
expect(actual).to.equal(expected);
}
it("updates fields when source element is modified or deleted", () => {
const sourceId = insertTestElement();
const block = TextBlock.create({ styleId: "0x123" });
block.appendRun(createField(sourceId, "old value"));
;
const targetId = insertAnnotationElement(block);
imodel.saveChanges();
const target = imodel.elements.getElement(targetId);
expect(target.getAnnotation()).not.to.be.undefined;
expectText("100", targetId);
let source = imodel.elements.getElement(sourceId);
source.intProp = 50;
source.update();
expectText("100", targetId);
imodel.saveChanges();
source = imodel.elements.getElement(sourceId);
expect(source.intProp).to.equal(50);
expectText("50", targetId);
imodel.elements.deleteElement(sourceId);
expectText("50", targetId);
imodel.saveChanges();
expectText(FieldRun.invalidContentIndicator, targetId);
});
it("updates fields when source element aspect is modified, deleted, or recreated", () => {
const sourceId = insertTestElement();
const block = TextBlock.create({ styleId: "0x123" });
block.appendRun(createField({ elementId: sourceId, schemaName: "Fields", className: "TestAspect" }, "", "aspectProp"));
const targetId = insertAnnotationElement(block);
imodel.saveChanges();
expectText("999", targetId);
const aspects = imodel.elements.getAspects(sourceId, "Fields:TestAspect");
expect(aspects.length).to.equal(1);
const aspect = aspects[0];
expect(aspect.aspectProp).to.equal(999);
aspect.aspectProp = 12345;
imodel.elements.updateAspect(aspect.toJSON());
imodel.saveChanges();
expectText("12345", targetId);
imodel.elements.deleteAspect([aspect.id]);
imodel.saveChanges();
expectText(FieldRun.invalidContentIndicator, targetId);
const newAspect = {
element: new ElementOwnsUniqueAspect(sourceId),
classFullName: TestAspect.classFullName,
aspectProp: 42,
};
imodel.elements.insertAspect(newAspect);
imodel.saveChanges();
expectText("42", targetId);
});
it("updates only fields for specific modified element", () => {
const sourceA = insertTestElement();
const sourceB = insertTestElement();
const block = TextBlock.create({ styleId: "0x123" });
block.appendRun(createField(sourceA, "A"));
block.appendRun(createField(sourceB, "B"));
const targetId = insertAnnotationElement(block);
imodel.saveChanges();
expectText("100100", targetId);
const sourceElem = imodel.elements.getElement(sourceB);
sourceElem.intProp = 123;
sourceElem.update();
imodel.saveChanges();
expectText("100123", targetId);
});
it("supports complex property paths", () => {
const sourceId = insertTestElement();
const block = TextBlock.create({ styleId: "0x123" });
block.appendRun(createField(sourceId, "", "outerStruct", ["innerStructs", 1, "doubles", -2]));
block.appendRun(createField(sourceId, "", "jsonProperties", undefined, ["zoo", "birds", 0, "name"]));
const targetId = insertAnnotationElement(block);
imodel.saveChanges();
expectText("2duck", targetId);
const source = imodel.elements.getElement(sourceId);
source.outerStruct.innerStructs[1].doubles[3] = 12.5;
source.jsonProperties.zoo.birds[0].name = "parrot";
source.update();
imodel.saveChanges();
expectText("12.5parrot", targetId);
});
});
});
//# sourceMappingURL=Fields.test.js.map