@itwin/core-backend 5.1.0-dev.64 → 5.1.0-dev.66
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.
- package/lib/cjs/BisCoreSchema.d.ts.map +1 -1
- package/lib/cjs/BisCoreSchema.js +3 -1
- package/lib/cjs/BisCoreSchema.js.map +1 -1
- package/lib/cjs/Entity.d.ts +2 -0
- package/lib/cjs/Entity.d.ts.map +1 -1
- package/lib/cjs/Entity.js +14 -0
- package/lib/cjs/Entity.js.map +1 -1
- package/lib/cjs/IModelDb.d.ts +6 -2
- package/lib/cjs/IModelDb.d.ts.map +1 -1
- package/lib/cjs/IModelDb.js +12 -5
- package/lib/cjs/IModelDb.js.map +1 -1
- package/lib/cjs/annotations/ElementDrivesTextAnnotation.d.ts +61 -0
- package/lib/cjs/annotations/ElementDrivesTextAnnotation.d.ts.map +1 -0
- package/lib/cjs/annotations/ElementDrivesTextAnnotation.js +110 -0
- package/lib/cjs/annotations/ElementDrivesTextAnnotation.js.map +1 -0
- package/lib/cjs/annotations/TextAnnotationElement.d.ts +18 -1
- package/lib/cjs/annotations/TextAnnotationElement.d.ts.map +1 -1
- package/lib/cjs/annotations/TextAnnotationElement.js +57 -2
- package/lib/cjs/annotations/TextAnnotationElement.js.map +1 -1
- package/lib/cjs/annotations/TextAnnotationGeometry.js +1 -0
- package/lib/cjs/annotations/TextAnnotationGeometry.js.map +1 -1
- package/lib/cjs/annotations/TextBlockGeometry.d.ts.map +1 -1
- package/lib/cjs/annotations/TextBlockGeometry.js +15 -1
- package/lib/cjs/annotations/TextBlockGeometry.js.map +1 -1
- package/lib/cjs/annotations/TextBlockLayout.d.ts +2 -2
- package/lib/cjs/annotations/TextBlockLayout.d.ts.map +1 -1
- package/lib/cjs/annotations/TextBlockLayout.js +14 -2
- package/lib/cjs/annotations/TextBlockLayout.js.map +1 -1
- package/lib/cjs/core-backend.d.ts +1 -0
- package/lib/cjs/core-backend.d.ts.map +1 -1
- package/lib/cjs/core-backend.js +1 -0
- package/lib/cjs/core-backend.js.map +1 -1
- package/lib/cjs/internal/annotations/fields.d.ts +22 -0
- package/lib/cjs/internal/annotations/fields.d.ts.map +1 -0
- package/lib/cjs/internal/annotations/fields.js +237 -0
- package/lib/cjs/internal/annotations/fields.js.map +1 -0
- package/lib/esm/BisCoreSchema.d.ts.map +1 -1
- package/lib/esm/BisCoreSchema.js +3 -1
- package/lib/esm/BisCoreSchema.js.map +1 -1
- package/lib/esm/Entity.d.ts +2 -0
- package/lib/esm/Entity.d.ts.map +1 -1
- package/lib/esm/Entity.js +14 -0
- package/lib/esm/Entity.js.map +1 -1
- package/lib/esm/IModelDb.d.ts +6 -2
- package/lib/esm/IModelDb.d.ts.map +1 -1
- package/lib/esm/IModelDb.js +13 -6
- package/lib/esm/IModelDb.js.map +1 -1
- package/lib/esm/annotations/ElementDrivesTextAnnotation.d.ts +61 -0
- package/lib/esm/annotations/ElementDrivesTextAnnotation.d.ts.map +1 -0
- package/lib/esm/annotations/ElementDrivesTextAnnotation.js +105 -0
- package/lib/esm/annotations/ElementDrivesTextAnnotation.js.map +1 -0
- package/lib/esm/annotations/TextAnnotationElement.d.ts +18 -1
- package/lib/esm/annotations/TextAnnotationElement.d.ts.map +1 -1
- package/lib/esm/annotations/TextAnnotationElement.js +57 -2
- package/lib/esm/annotations/TextAnnotationElement.js.map +1 -1
- package/lib/esm/annotations/TextAnnotationGeometry.js +1 -0
- package/lib/esm/annotations/TextAnnotationGeometry.js.map +1 -1
- package/lib/esm/annotations/TextBlockGeometry.d.ts.map +1 -1
- package/lib/esm/annotations/TextBlockGeometry.js +15 -1
- package/lib/esm/annotations/TextBlockGeometry.js.map +1 -1
- package/lib/esm/annotations/TextBlockLayout.d.ts +2 -2
- package/lib/esm/annotations/TextBlockLayout.d.ts.map +1 -1
- package/lib/esm/annotations/TextBlockLayout.js +14 -2
- package/lib/esm/annotations/TextBlockLayout.js.map +1 -1
- package/lib/esm/core-backend.d.ts +1 -0
- package/lib/esm/core-backend.d.ts.map +1 -1
- package/lib/esm/core-backend.js +1 -0
- package/lib/esm/core-backend.js.map +1 -1
- package/lib/esm/internal/annotations/fields.d.ts +22 -0
- package/lib/esm/internal/annotations/fields.d.ts.map +1 -0
- package/lib/esm/internal/annotations/fields.js +231 -0
- package/lib/esm/internal/annotations/fields.js.map +1 -0
- package/lib/esm/test/IModelTestUtils.d.ts.map +1 -1
- package/lib/esm/test/IModelTestUtils.js +1 -0
- package/lib/esm/test/IModelTestUtils.js.map +1 -1
- package/lib/esm/test/annotations/Fields.test.d.ts +2 -0
- package/lib/esm/test/annotations/Fields.test.d.ts.map +1 -0
- package/lib/esm/test/annotations/Fields.test.js +576 -0
- package/lib/esm/test/annotations/Fields.test.js.map +1 -0
- package/lib/esm/test/annotations/TextAnnotation.test.js.map +1 -1
- package/lib/esm/test/annotations/TextBlock.test.js.map +1 -1
- package/package.json +14 -14
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Fields.test.d.ts","sourceRoot":"","sources":["../../../../src/test/annotations/Fields.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,576 @@
|
|
|
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 { Code, FieldRun, SubCategoryAppearance, TextAnnotation, TextBlock, TextRun } from "@itwin/core-common";
|
|
7
|
+
import { StandaloneDb } from "../../IModelDb";
|
|
8
|
+
import { IModelTestUtils } from "../IModelTestUtils";
|
|
9
|
+
import { createUpdateContext, updateField, updateFields } from "../../internal/annotations/fields";
|
|
10
|
+
import { DbResult, Id64 } from "@itwin/core-bentley";
|
|
11
|
+
import { SpatialCategory } from "../../Category";
|
|
12
|
+
import { Point3d, YawPitchRollAngles } from "@itwin/core-geometry";
|
|
13
|
+
import { Schema, Schemas } from "../../Schema";
|
|
14
|
+
import { ClassRegistry } from "../../ClassRegistry";
|
|
15
|
+
import { PhysicalElement } from "../../Element";
|
|
16
|
+
import { ElementOwnsUniqueAspect, ElementUniqueAspect, FontFile, TextAnnotation3d } from "../../core-backend";
|
|
17
|
+
import { ElementDrivesTextAnnotation } from "../../annotations/ElementDrivesTextAnnotation";
|
|
18
|
+
describe("updateField", () => {
|
|
19
|
+
const mockElementId = "0x1";
|
|
20
|
+
const mockPath = {
|
|
21
|
+
propertyName: "mockProperty",
|
|
22
|
+
accessors: [0, "nestedProperty"],
|
|
23
|
+
};
|
|
24
|
+
const mockCachedContent = "cachedContent";
|
|
25
|
+
const mockUpdatedContent = "updatedContent";
|
|
26
|
+
const createMockContext = (elementId, propertyValue) => ({
|
|
27
|
+
hostElementId: elementId,
|
|
28
|
+
getProperty: (field) => {
|
|
29
|
+
const propertyPath = field.propertyPath;
|
|
30
|
+
if (propertyPath.propertyName === "mockProperty" &&
|
|
31
|
+
propertyPath.accessors?.[0] === 0 &&
|
|
32
|
+
propertyPath.accessors?.[1] === "nestedProperty" &&
|
|
33
|
+
propertyValue !== undefined) {
|
|
34
|
+
return { value: propertyValue, metadata: {} };
|
|
35
|
+
}
|
|
36
|
+
return undefined;
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
it("does nothing if hostElementId does not match", () => {
|
|
40
|
+
const fieldRun = FieldRun.create({
|
|
41
|
+
propertyHost: { elementId: mockElementId, schemaName: "TestSchema", className: "TestClass" },
|
|
42
|
+
propertyPath: mockPath,
|
|
43
|
+
cachedContent: mockCachedContent,
|
|
44
|
+
});
|
|
45
|
+
const context = createMockContext("0x2", mockUpdatedContent);
|
|
46
|
+
const result = updateField(fieldRun, context);
|
|
47
|
+
expect(result).to.be.false;
|
|
48
|
+
expect(fieldRun.cachedContent).to.equal(mockCachedContent);
|
|
49
|
+
});
|
|
50
|
+
it("produces invalid content indicator if property value is undefined", () => {
|
|
51
|
+
const fieldRun = FieldRun.create({
|
|
52
|
+
propertyHost: { elementId: mockElementId, schemaName: "TestSchema", className: "TestClass" },
|
|
53
|
+
propertyPath: mockPath,
|
|
54
|
+
cachedContent: mockCachedContent,
|
|
55
|
+
});
|
|
56
|
+
const context = createMockContext(mockElementId);
|
|
57
|
+
const result = updateField(fieldRun, context);
|
|
58
|
+
expect(result).to.be.true;
|
|
59
|
+
expect(fieldRun.cachedContent).to.equal(FieldRun.invalidContentIndicator);
|
|
60
|
+
});
|
|
61
|
+
it("returns false if cached content matches new content", () => {
|
|
62
|
+
const fieldRun = FieldRun.create({
|
|
63
|
+
propertyHost: { elementId: mockElementId, schemaName: "TestSchema", className: "TestClass" },
|
|
64
|
+
propertyPath: mockPath,
|
|
65
|
+
cachedContent: mockCachedContent,
|
|
66
|
+
});
|
|
67
|
+
const context = createMockContext(mockElementId, mockCachedContent);
|
|
68
|
+
const result = updateField(fieldRun, context);
|
|
69
|
+
expect(result).to.be.false;
|
|
70
|
+
expect(fieldRun.cachedContent).to.equal(mockCachedContent);
|
|
71
|
+
});
|
|
72
|
+
it("returns true and updates cached content if new content is different", () => {
|
|
73
|
+
const fieldRun = FieldRun.create({
|
|
74
|
+
propertyHost: { elementId: mockElementId, schemaName: "TestSchema", className: "TestClass" },
|
|
75
|
+
propertyPath: mockPath,
|
|
76
|
+
cachedContent: mockCachedContent,
|
|
77
|
+
});
|
|
78
|
+
const context = createMockContext(mockElementId, mockUpdatedContent);
|
|
79
|
+
const result = updateField(fieldRun, context);
|
|
80
|
+
expect(result).to.be.true;
|
|
81
|
+
expect(fieldRun.cachedContent).to.equal(mockUpdatedContent);
|
|
82
|
+
});
|
|
83
|
+
it("resolves to invalid content indicator if an exception occurs", () => {
|
|
84
|
+
const fieldRun = FieldRun.create({
|
|
85
|
+
propertyHost: { elementId: mockElementId, schemaName: "TestSchema", className: "TestClass" },
|
|
86
|
+
propertyPath: mockPath,
|
|
87
|
+
cachedContent: mockCachedContent,
|
|
88
|
+
});
|
|
89
|
+
const context = {
|
|
90
|
+
hostElementId: mockElementId,
|
|
91
|
+
getProperty: () => {
|
|
92
|
+
throw new Error("Test exception");
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
const result = updateField(fieldRun, context);
|
|
96
|
+
expect(result).to.be.true;
|
|
97
|
+
expect(fieldRun.cachedContent).to.equal(FieldRun.invalidContentIndicator);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
const fieldsSchemaXml = `
|
|
101
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
102
|
+
<ECSchema schemaName="Fields" alias="ts" version="01.00.00" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
|
|
103
|
+
<ECSchemaReference name="BisCore" version="01.00.04" alias="bis"/>
|
|
104
|
+
|
|
105
|
+
<ECStructClass typeName="InnerStruct" modifier="None">
|
|
106
|
+
<ECProperty propertyName="bool" typeName="boolean"/>
|
|
107
|
+
<ECArrayProperty propertyName="doubles" typeName="double" minOccurs="0" maxOccurs="unbounded"/>
|
|
108
|
+
</ECStructClass>
|
|
109
|
+
|
|
110
|
+
<ECStructClass typeName="OuterStruct" modifier="None">
|
|
111
|
+
<ECStructProperty propertyName="innerStruct" typeName="InnerStruct"/>
|
|
112
|
+
<ECStructArrayProperty propertyName="innerStructs" typeName="InnerStruct" minOccurs="0" maxOccurs="unbounded"/>
|
|
113
|
+
</ECStructClass>
|
|
114
|
+
|
|
115
|
+
<ECEntityClass typeName="TestElement" modifier="None">
|
|
116
|
+
<BaseClass>bis:PhysicalElement</BaseClass>
|
|
117
|
+
<ECProperty propertyName="intProp" typeName="int"/>
|
|
118
|
+
<ECProperty propertyName="point" typeName="point3d"/>
|
|
119
|
+
<ECProperty propertyName="maybeNull" typeName="int"/>
|
|
120
|
+
<ECArrayProperty propertyName="strings" typeName="string" minOccurs="0" maxOccurs="unbounded"/>
|
|
121
|
+
<ECStructProperty propertyName="outerStruct" typeName="OuterStruct"/>
|
|
122
|
+
<ECStructArrayProperty propertyName="outerStructs" typeName="OuterStruct" minOccurs="0" maxOccurs="unbounded"/>
|
|
123
|
+
</ECEntityClass>
|
|
124
|
+
|
|
125
|
+
<ECEntityClass typeName="TestAspect" modifier="None">
|
|
126
|
+
<BaseClass>bis:ElementUniqueAspect</BaseClass>
|
|
127
|
+
<ECProperty propertyName="aspectProp" typeName="int"/>
|
|
128
|
+
</ECEntityClass>
|
|
129
|
+
</ECSchema>
|
|
130
|
+
`;
|
|
131
|
+
class TestElement extends PhysicalElement {
|
|
132
|
+
static get className() { return "TestElement"; }
|
|
133
|
+
}
|
|
134
|
+
class TestAspect extends ElementUniqueAspect {
|
|
135
|
+
static get className() { return "TestAspect"; }
|
|
136
|
+
}
|
|
137
|
+
class FieldsSchema extends Schema {
|
|
138
|
+
static get schemaName() { return "Fields"; }
|
|
139
|
+
}
|
|
140
|
+
async function registerTestSchema(iModel) {
|
|
141
|
+
if (!Schemas.getRegisteredSchema("Fields")) {
|
|
142
|
+
Schemas.registerSchema(FieldsSchema);
|
|
143
|
+
ClassRegistry.register(TestElement, FieldsSchema);
|
|
144
|
+
ClassRegistry.register(TestAspect, FieldsSchema);
|
|
145
|
+
}
|
|
146
|
+
await iModel.importSchemaStrings([fieldsSchemaXml]);
|
|
147
|
+
iModel.saveChanges();
|
|
148
|
+
}
|
|
149
|
+
describe("Field evaluation", () => {
|
|
150
|
+
let imodel;
|
|
151
|
+
let model;
|
|
152
|
+
let category;
|
|
153
|
+
let sourceElementId;
|
|
154
|
+
before(async () => {
|
|
155
|
+
const iModelPath = IModelTestUtils.prepareOutputFile("UpdateFieldsContext", "test.bim");
|
|
156
|
+
imodel = StandaloneDb.createEmpty(iModelPath, { rootSubject: { name: "UpdateFieldsContext" }, allowEdit: JSON.stringify({ txns: true }) });
|
|
157
|
+
await registerTestSchema(imodel);
|
|
158
|
+
model = IModelTestUtils.createAndInsertPhysicalPartitionAndModel(imodel, Code.createEmpty(), true)[1];
|
|
159
|
+
category = SpatialCategory.insert(imodel, StandaloneDb.dictionaryId, "UpdateFieldsContextCategory", new SubCategoryAppearance());
|
|
160
|
+
sourceElementId = insertTestElement();
|
|
161
|
+
await imodel.fonts.embedFontFile({
|
|
162
|
+
file: FontFile.createFromTrueTypeFileName(IModelTestUtils.resolveFontFile("Karla-Regular.ttf"))
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
after(() => {
|
|
166
|
+
imodel.close();
|
|
167
|
+
});
|
|
168
|
+
function insertTestElement() {
|
|
169
|
+
const props = {
|
|
170
|
+
classFullName: "Fields:TestElement",
|
|
171
|
+
model,
|
|
172
|
+
category,
|
|
173
|
+
code: Code.createEmpty(),
|
|
174
|
+
intProp: 100,
|
|
175
|
+
point: { x: 1, y: 2, z: 3 },
|
|
176
|
+
strings: ["a", "b", `"name": "c"`],
|
|
177
|
+
outerStruct: {
|
|
178
|
+
innerStruct: { bool: false, doubles: [1, 2, 3] },
|
|
179
|
+
innerStructs: [{ bool: true, doubles: [] }, { bool: false, doubles: [5, 4, 3, 2, 1] }],
|
|
180
|
+
},
|
|
181
|
+
outerStructs: [{
|
|
182
|
+
innerStruct: { bool: true, doubles: [10, 9] },
|
|
183
|
+
innerStructs: [{ bool: false, doubles: [5] }],
|
|
184
|
+
}],
|
|
185
|
+
placement: {
|
|
186
|
+
origin: new Point3d(1, 2, 0),
|
|
187
|
+
angles: new YawPitchRollAngles(),
|
|
188
|
+
},
|
|
189
|
+
jsonProperties: {
|
|
190
|
+
stringProp: "abc",
|
|
191
|
+
ints: [10, 11, 12, 13],
|
|
192
|
+
zoo: {
|
|
193
|
+
address: {
|
|
194
|
+
zipcode: 12345,
|
|
195
|
+
},
|
|
196
|
+
birds: [
|
|
197
|
+
{ name: "duck", sound: "quack" },
|
|
198
|
+
{ name: "hawk", sound: "scree!" },
|
|
199
|
+
],
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
const id = imodel.elements.insertElement(props);
|
|
204
|
+
const aspectProps = {
|
|
205
|
+
classFullName: TestAspect.classFullName,
|
|
206
|
+
aspectProp: 999,
|
|
207
|
+
element: new ElementOwnsUniqueAspect(id),
|
|
208
|
+
};
|
|
209
|
+
imodel.elements.insertAspect(aspectProps);
|
|
210
|
+
imodel.saveChanges();
|
|
211
|
+
return id;
|
|
212
|
+
}
|
|
213
|
+
describe("getProperty", () => {
|
|
214
|
+
function expectValue(expected, propertyPath, propertyHost, deletedDependency = false) {
|
|
215
|
+
if (typeof propertyHost === "string") {
|
|
216
|
+
propertyHost = { schemaName: "Fields", className: "TestElement", elementId: propertyHost };
|
|
217
|
+
}
|
|
218
|
+
const field = FieldRun.create({
|
|
219
|
+
propertyPath,
|
|
220
|
+
propertyHost,
|
|
221
|
+
});
|
|
222
|
+
const context = createUpdateContext(propertyHost.elementId, imodel, deletedDependency);
|
|
223
|
+
const actual = context.getProperty(field);
|
|
224
|
+
expect(actual?.value).to.deep.equal(expected);
|
|
225
|
+
}
|
|
226
|
+
it("returns a primitive property value", () => {
|
|
227
|
+
expectValue(100, { propertyName: "intProp" }, sourceElementId);
|
|
228
|
+
});
|
|
229
|
+
it("treats points as primitive values", () => {
|
|
230
|
+
expectValue({ x: 1, y: 2, z: 3 }, { propertyName: "point" }, sourceElementId);
|
|
231
|
+
expectValue(undefined, { propertyName: "point", accessors: ["x"] }, sourceElementId);
|
|
232
|
+
});
|
|
233
|
+
it("returns a primitive array value", () => {
|
|
234
|
+
expectValue("a", { propertyName: "strings", accessors: [0] }, sourceElementId);
|
|
235
|
+
expectValue("b", { propertyName: "strings", accessors: [1] }, sourceElementId);
|
|
236
|
+
expectValue(`"name": "c"`, { propertyName: "strings", accessors: [2] }, sourceElementId);
|
|
237
|
+
});
|
|
238
|
+
it("supports negative array indices", () => {
|
|
239
|
+
expectValue("a", { propertyName: "strings", accessors: [-3] }, sourceElementId);
|
|
240
|
+
expectValue("b", { propertyName: "strings", accessors: [-2] }, sourceElementId);
|
|
241
|
+
expectValue(`"name": "c"`, { propertyName: "strings", accessors: [-1] }, sourceElementId);
|
|
242
|
+
});
|
|
243
|
+
it("returns undefined if the dependency was deleted", () => {
|
|
244
|
+
expectValue(undefined, { propertyName: "intProp" }, sourceElementId, true);
|
|
245
|
+
});
|
|
246
|
+
it("returns undefined if the host element does not exist", () => {
|
|
247
|
+
expectValue(undefined, { propertyName: "intProp" }, "0xbaadf00d");
|
|
248
|
+
});
|
|
249
|
+
it("returns undefined if the host element is not of the specified class or a subclass thereof", () => {
|
|
250
|
+
expectValue(undefined, { propertyName: "origin" }, { schemaName: "BisCore", className: "GeometricElement2d", elementId: sourceElementId });
|
|
251
|
+
});
|
|
252
|
+
it("returns undefined if an access string is specified for a non-object property", () => {
|
|
253
|
+
expectValue(undefined, { propertyName: "intProp", accessors: ["property"] }, sourceElementId);
|
|
254
|
+
});
|
|
255
|
+
it("returns undefined if the specified property does not exist", () => {
|
|
256
|
+
expectValue(undefined, { propertyName: "nonExistentProperty" }, sourceElementId);
|
|
257
|
+
});
|
|
258
|
+
it("returns undefined if the specified property is null", () => {
|
|
259
|
+
expectValue(undefined, { propertyName: "maybeNull" }, sourceElementId);
|
|
260
|
+
});
|
|
261
|
+
it("returns undefined if an array index is specified for a non-array property", () => {
|
|
262
|
+
expectValue(undefined, { propertyName: "intProp", accessors: [0] }, sourceElementId);
|
|
263
|
+
});
|
|
264
|
+
it("returns undefined if an array index is out of bounds", () => {
|
|
265
|
+
for (const index of [3, 4, -4, -5]) {
|
|
266
|
+
expectValue(undefined, { propertyName: "strings", accessors: [index] }, sourceElementId);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
it("returns undefined for a non-primitive value", () => {
|
|
270
|
+
expectValue(undefined, { propertyName: "strings" }, sourceElementId);
|
|
271
|
+
expectValue(undefined, { propertyName: "outerStruct" }, sourceElementId);
|
|
272
|
+
expectValue(undefined, { propertyName: "outerStruct", accessors: ["innerStruct"] }, sourceElementId);
|
|
273
|
+
expectValue(undefined, { propertyName: "outerStructs" }, sourceElementId);
|
|
274
|
+
expectValue(undefined, { propertyName: "outerStructs", accessors: [0] }, sourceElementId);
|
|
275
|
+
expectValue(undefined, { propertyName: "outerStructs", accessors: [0, "innerStruct"] }, sourceElementId);
|
|
276
|
+
});
|
|
277
|
+
it("returns arbitrarily-nested properties of structs and struct arrays", () => {
|
|
278
|
+
expectValue(false, { propertyName: "outerStruct", accessors: ["innerStruct", "bool"] }, sourceElementId);
|
|
279
|
+
for (const index of [0, 1, 2]) {
|
|
280
|
+
expectValue(index + 1, { propertyName: "outerStruct", accessors: ["innerStruct", "doubles", index] }, sourceElementId);
|
|
281
|
+
expectValue(3 - index, { propertyName: "outerStruct", accessors: ["innerStruct", "doubles", -1 - index] }, sourceElementId);
|
|
282
|
+
}
|
|
283
|
+
expectValue(9, { propertyName: "outerStructs", accessors: [0, "innerStruct", "doubles", 1] }, sourceElementId);
|
|
284
|
+
expectValue(false, { propertyName: "outerStructs", accessors: [0, "innerStructs", -1, "bool"] }, sourceElementId);
|
|
285
|
+
expectValue(5, { propertyName: "outerStructs", accessors: [0, "innerStructs", 0, "doubles", 0] }, sourceElementId);
|
|
286
|
+
});
|
|
287
|
+
it("returns arbitrarily-nested JSON properties", () => {
|
|
288
|
+
expectValue("abc", { propertyName: "jsonProperties", jsonAccessors: ["stringProp"] }, sourceElementId);
|
|
289
|
+
expectValue(10, { propertyName: "jsonProperties", jsonAccessors: ["ints", 0] }, sourceElementId);
|
|
290
|
+
expectValue(13, { propertyName: "jsonProperties", jsonAccessors: ["ints", 3] }, sourceElementId);
|
|
291
|
+
expectValue(13, { propertyName: "jsonProperties", jsonAccessors: ["ints", -1] }, sourceElementId);
|
|
292
|
+
expectValue(11, { propertyName: "jsonProperties", jsonAccessors: ["ints", -3] }, sourceElementId);
|
|
293
|
+
expectValue(12345, { propertyName: "jsonProperties", jsonAccessors: ["zoo", "address", "zipcode"] }, sourceElementId);
|
|
294
|
+
expectValue("scree!", { propertyName: "jsonProperties", jsonAccessors: ["zoo", "birds", 1, "sound"] }, sourceElementId);
|
|
295
|
+
});
|
|
296
|
+
it("returns undefined if JSON accessors applied to non-JSON property", () => {
|
|
297
|
+
expectValue(undefined, { propertyName: "int", jsonAccessors: ["whatever"] }, sourceElementId);
|
|
298
|
+
expectValue(undefined, { propertyName: "strings", accessors: [2, "name"] }, sourceElementId);
|
|
299
|
+
expectValue(undefined, { propertyName: "outerStruct", accessors: ["innerStruct"], jsonAccessors: ["bool"] }, sourceElementId);
|
|
300
|
+
});
|
|
301
|
+
it("returns the value of a property of an aspect", () => {
|
|
302
|
+
expect(imodel.elements.getAspects(sourceElementId, "Fields:TestAspect").length).to.equal(1);
|
|
303
|
+
expectValue(999, { propertyName: "aspectProp" }, { elementId: sourceElementId, schemaName: "Fields", className: "TestAspect" });
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
describe("updateFields", () => {
|
|
307
|
+
it("recomputes cached content", () => {
|
|
308
|
+
const textBlock = TextBlock.create({ styleId: "0x123" });
|
|
309
|
+
const fieldRun = FieldRun.create({
|
|
310
|
+
propertyHost: { elementId: sourceElementId, schemaName: "Fields", className: "TestElement" },
|
|
311
|
+
propertyPath: { propertyName: "intProp" },
|
|
312
|
+
cachedContent: "oldValue",
|
|
313
|
+
});
|
|
314
|
+
textBlock.appendRun(fieldRun);
|
|
315
|
+
const context = createUpdateContext(sourceElementId, imodel, false);
|
|
316
|
+
const updatedCount = updateFields(textBlock, context);
|
|
317
|
+
expect(updatedCount).to.equal(1);
|
|
318
|
+
expect(fieldRun.cachedContent).to.equal("100"); // `intProp` value from the test element
|
|
319
|
+
});
|
|
320
|
+
it("does not update a field if recomputed content matches cached content", () => {
|
|
321
|
+
const textBlock = TextBlock.create({ styleId: "0x123" });
|
|
322
|
+
const fieldRun = FieldRun.create({
|
|
323
|
+
propertyHost: { elementId: sourceElementId, schemaName: "Fields", className: "TestElement" },
|
|
324
|
+
propertyPath: { propertyName: "intProp" },
|
|
325
|
+
cachedContent: "100",
|
|
326
|
+
});
|
|
327
|
+
textBlock.appendRun(fieldRun);
|
|
328
|
+
const context = createUpdateContext(sourceElementId, imodel, false);
|
|
329
|
+
const updatedCount = updateFields(textBlock, context);
|
|
330
|
+
expect(updatedCount).to.equal(0);
|
|
331
|
+
expect(fieldRun.cachedContent).to.equal("100");
|
|
332
|
+
});
|
|
333
|
+
it("returns the number of fields updated", () => {
|
|
334
|
+
const textBlock = TextBlock.create({ styleId: "0x123" });
|
|
335
|
+
const fieldRun1 = FieldRun.create({
|
|
336
|
+
propertyHost: { elementId: sourceElementId, schemaName: "Fields", className: "TestElement" },
|
|
337
|
+
propertyPath: { propertyName: "intProp" },
|
|
338
|
+
cachedContent: "100",
|
|
339
|
+
});
|
|
340
|
+
const fieldRun2 = FieldRun.create({
|
|
341
|
+
propertyHost: { elementId: sourceElementId, schemaName: "Fields", className: "TestElement" },
|
|
342
|
+
propertyPath: { propertyName: "strings", accessors: [0] },
|
|
343
|
+
cachedContent: "oldValue",
|
|
344
|
+
});
|
|
345
|
+
textBlock.appendRun(fieldRun1);
|
|
346
|
+
textBlock.appendRun(fieldRun2);
|
|
347
|
+
const context = createUpdateContext(sourceElementId, imodel, false);
|
|
348
|
+
const updatedCount = updateFields(textBlock, context);
|
|
349
|
+
expect(updatedCount).to.equal(1);
|
|
350
|
+
expect(fieldRun1.cachedContent).to.equal("100");
|
|
351
|
+
expect(fieldRun2.cachedContent).to.equal("a");
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
function insertAnnotationElement(textBlock) {
|
|
355
|
+
const elem = TextAnnotation3d.fromJSON({
|
|
356
|
+
model,
|
|
357
|
+
category,
|
|
358
|
+
code: Code.createEmpty(),
|
|
359
|
+
placement: {
|
|
360
|
+
origin: { x: 0, y: 0, z: 0 },
|
|
361
|
+
angles: YawPitchRollAngles.createDegrees(0, 0, 0).toJSON(),
|
|
362
|
+
},
|
|
363
|
+
classFullName: TextAnnotation3d.classFullName,
|
|
364
|
+
}, imodel);
|
|
365
|
+
if (textBlock) {
|
|
366
|
+
const annotation = TextAnnotation.fromJSON({ textBlock: textBlock.toJSON() });
|
|
367
|
+
elem.setAnnotation(annotation);
|
|
368
|
+
}
|
|
369
|
+
return elem.insert();
|
|
370
|
+
}
|
|
371
|
+
describe("ElementDrivesTextAnnotation", () => {
|
|
372
|
+
function expectNumRelationships(expected, targetId) {
|
|
373
|
+
const where = targetId ? ` WHERE TargetECInstanceId=${targetId}` : "";
|
|
374
|
+
const ecsql = `SELECT COUNT(*) FROM BisCore.ElementDrivesTextAnnotation ${where}`;
|
|
375
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
376
|
+
imodel.withPreparedStatement(ecsql, (stmt) => {
|
|
377
|
+
expect(stmt.step()).to.equal(DbResult.BE_SQLITE_ROW);
|
|
378
|
+
expect(stmt.getValue(0).getInteger()).to.equal(expected);
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
it("can be inserted", () => {
|
|
382
|
+
expectNumRelationships(0);
|
|
383
|
+
const targetId = insertAnnotationElement(undefined);
|
|
384
|
+
expect(targetId).not.to.equal(Id64.invalid);
|
|
385
|
+
const target = imodel.elements.getElement(targetId);
|
|
386
|
+
expect(target.classFullName).to.equal("BisCore:TextAnnotation3d");
|
|
387
|
+
expect(target).instanceof(TextAnnotation3d);
|
|
388
|
+
const targetAnno = imodel.elements.getElement(targetId);
|
|
389
|
+
expect(targetAnno).instanceof(TextAnnotation3d);
|
|
390
|
+
const rel = ElementDrivesTextAnnotation.create(imodel, sourceElementId, targetId);
|
|
391
|
+
const relId = rel.insert();
|
|
392
|
+
expect(relId).not.to.equal(Id64.invalid);
|
|
393
|
+
expectNumRelationships(1);
|
|
394
|
+
const relationship = imodel.relationships.getInstance("BisCore:ElementDrivesTextAnnotation", relId);
|
|
395
|
+
expect(relationship.sourceId).to.equal(sourceElementId);
|
|
396
|
+
expect(relationship.targetId).to.equal(targetId);
|
|
397
|
+
});
|
|
398
|
+
function createField(propertyHost, cachedContent, propertyName = "intProp", accessors, jsonAccessors) {
|
|
399
|
+
if (typeof propertyHost === "string") {
|
|
400
|
+
propertyHost = { schemaName: "Fields", className: "TestElement", elementId: propertyHost };
|
|
401
|
+
}
|
|
402
|
+
return FieldRun.create({
|
|
403
|
+
styleOverrides: { fontName: "Karla" },
|
|
404
|
+
propertyHost,
|
|
405
|
+
cachedContent,
|
|
406
|
+
propertyPath: { propertyName, accessors, jsonAccessors },
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
describe("updateFieldDependencies", () => {
|
|
410
|
+
it("creates exactly one relationship for each unique source element on insert and update", () => {
|
|
411
|
+
const source1 = insertTestElement();
|
|
412
|
+
const block = TextBlock.create({ styleId: "0x123" });
|
|
413
|
+
block.appendRun(createField(source1, "1"));
|
|
414
|
+
const targetId = insertAnnotationElement(block);
|
|
415
|
+
imodel.saveChanges();
|
|
416
|
+
expectNumRelationships(1, targetId);
|
|
417
|
+
const source2 = insertTestElement();
|
|
418
|
+
const target = imodel.elements.getElement(targetId);
|
|
419
|
+
const anno = target.getAnnotation();
|
|
420
|
+
anno.textBlock.appendRun(createField(source2, "2a"));
|
|
421
|
+
target.setAnnotation(anno);
|
|
422
|
+
target.update();
|
|
423
|
+
imodel.saveChanges();
|
|
424
|
+
expectNumRelationships(2, targetId);
|
|
425
|
+
anno.textBlock.appendRun(createField(source2, "2b"));
|
|
426
|
+
target.setAnnotation(anno);
|
|
427
|
+
target.update();
|
|
428
|
+
imodel.saveChanges();
|
|
429
|
+
expectNumRelationships(2, targetId);
|
|
430
|
+
const source3 = insertTestElement();
|
|
431
|
+
anno.textBlock.appendRun(createField(source3, "3"));
|
|
432
|
+
target.setAnnotation(anno);
|
|
433
|
+
target.update();
|
|
434
|
+
imodel.saveChanges();
|
|
435
|
+
expectNumRelationships(3, targetId);
|
|
436
|
+
});
|
|
437
|
+
it("deletes stale relationships", () => {
|
|
438
|
+
const sourceA = insertTestElement();
|
|
439
|
+
const sourceB = insertTestElement();
|
|
440
|
+
const block = TextBlock.create({ styleId: "0x123" });
|
|
441
|
+
block.appendRun(createField(sourceA, "A"));
|
|
442
|
+
block.appendRun(createField(sourceB, "B"));
|
|
443
|
+
const targetId = insertAnnotationElement(block);
|
|
444
|
+
imodel.saveChanges();
|
|
445
|
+
expectNumRelationships(2, targetId);
|
|
446
|
+
expect(imodel.relationships.tryGetInstance(ElementDrivesTextAnnotation.classFullName, { targetId, sourceId: sourceA })).not.to.be.undefined;
|
|
447
|
+
expect(imodel.relationships.tryGetInstance(ElementDrivesTextAnnotation.classFullName, { targetId, sourceId: sourceB })).not.to.be.undefined;
|
|
448
|
+
const target = imodel.elements.getElement(targetId);
|
|
449
|
+
const anno = target.getAnnotation();
|
|
450
|
+
anno.textBlock.paragraphs[0].runs.shift();
|
|
451
|
+
target.setAnnotation(anno);
|
|
452
|
+
target.update();
|
|
453
|
+
imodel.saveChanges();
|
|
454
|
+
expectNumRelationships(1, targetId);
|
|
455
|
+
expect(imodel.relationships.tryGetInstance(ElementDrivesTextAnnotation.classFullName, { targetId, sourceId: sourceA })).to.be.undefined;
|
|
456
|
+
expect(imodel.relationships.tryGetInstance(ElementDrivesTextAnnotation.classFullName, { targetId, sourceId: sourceB })).not.to.be.undefined;
|
|
457
|
+
anno.textBlock.paragraphs.length = 0;
|
|
458
|
+
anno.textBlock.appendRun(createField(sourceA, "A2"));
|
|
459
|
+
target.setAnnotation(anno);
|
|
460
|
+
target.update();
|
|
461
|
+
imodel.saveChanges();
|
|
462
|
+
expectNumRelationships(1, targetId);
|
|
463
|
+
expect(imodel.relationships.tryGetInstance(ElementDrivesTextAnnotation.classFullName, { targetId, sourceId: sourceA })).not.to.be.undefined;
|
|
464
|
+
expect(imodel.relationships.tryGetInstance(ElementDrivesTextAnnotation.classFullName, { targetId, sourceId: sourceB })).to.be.undefined;
|
|
465
|
+
anno.textBlock.paragraphs.length = 0;
|
|
466
|
+
anno.textBlock.appendRun(TextRun.create({
|
|
467
|
+
styleOverrides: { fontName: "Karla" },
|
|
468
|
+
content: "not a field",
|
|
469
|
+
}));
|
|
470
|
+
target.setAnnotation(anno);
|
|
471
|
+
target.update();
|
|
472
|
+
imodel.saveChanges();
|
|
473
|
+
expectNumRelationships(0, targetId);
|
|
474
|
+
expect(imodel.relationships.tryGetInstance(ElementDrivesTextAnnotation.classFullName, { targetId, sourceId: sourceA })).to.be.undefined;
|
|
475
|
+
expect(imodel.relationships.tryGetInstance(ElementDrivesTextAnnotation.classFullName, { targetId, sourceId: sourceB })).to.be.undefined;
|
|
476
|
+
});
|
|
477
|
+
it("ignores invalid source element Ids", () => {
|
|
478
|
+
const source = insertTestElement();
|
|
479
|
+
const block = TextBlock.create({ styleId: "0x123" });
|
|
480
|
+
block.appendRun(createField(Id64.invalid, "invalid"));
|
|
481
|
+
block.appendRun(createField("0xbaadf00d", "non-existent"));
|
|
482
|
+
block.appendRun(createField(source, "valid"));
|
|
483
|
+
const targetId = insertAnnotationElement(block);
|
|
484
|
+
imodel.saveChanges();
|
|
485
|
+
expectNumRelationships(1, targetId);
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
function expectText(expected, elemId) {
|
|
489
|
+
const elem = imodel.elements.getElement(elemId);
|
|
490
|
+
const anno = elem.getAnnotation();
|
|
491
|
+
const actual = anno.textBlock.stringify();
|
|
492
|
+
expect(actual).to.equal(expected);
|
|
493
|
+
}
|
|
494
|
+
it("updates fields when source element is modified or deleted", () => {
|
|
495
|
+
const sourceId = insertTestElement();
|
|
496
|
+
const block = TextBlock.create({ styleId: "0x123" });
|
|
497
|
+
block.appendRun(createField(sourceId, "old value"));
|
|
498
|
+
;
|
|
499
|
+
const targetId = insertAnnotationElement(block);
|
|
500
|
+
imodel.saveChanges();
|
|
501
|
+
const target = imodel.elements.getElement(targetId);
|
|
502
|
+
expect(target.getAnnotation()).not.to.be.undefined;
|
|
503
|
+
expectText("100", targetId);
|
|
504
|
+
let source = imodel.elements.getElement(sourceId);
|
|
505
|
+
source.intProp = 50;
|
|
506
|
+
source.update();
|
|
507
|
+
expectText("100", targetId);
|
|
508
|
+
imodel.saveChanges();
|
|
509
|
+
source = imodel.elements.getElement(sourceId);
|
|
510
|
+
expect(source.intProp).to.equal(50);
|
|
511
|
+
expectText("50", targetId);
|
|
512
|
+
imodel.elements.deleteElement(sourceId);
|
|
513
|
+
expectText("50", targetId);
|
|
514
|
+
imodel.saveChanges();
|
|
515
|
+
expectText(FieldRun.invalidContentIndicator, targetId);
|
|
516
|
+
});
|
|
517
|
+
it("updates fields when source element aspect is modified, deleted, or recreated", () => {
|
|
518
|
+
const sourceId = insertTestElement();
|
|
519
|
+
const block = TextBlock.create({ styleId: "0x123" });
|
|
520
|
+
block.appendRun(createField({ elementId: sourceId, schemaName: "Fields", className: "TestAspect" }, "", "aspectProp"));
|
|
521
|
+
const targetId = insertAnnotationElement(block);
|
|
522
|
+
imodel.saveChanges();
|
|
523
|
+
expectText("999", targetId);
|
|
524
|
+
const aspects = imodel.elements.getAspects(sourceId, "Fields:TestAspect");
|
|
525
|
+
expect(aspects.length).to.equal(1);
|
|
526
|
+
const aspect = aspects[0];
|
|
527
|
+
expect(aspect.aspectProp).to.equal(999);
|
|
528
|
+
aspect.aspectProp = 12345;
|
|
529
|
+
imodel.elements.updateAspect(aspect.toJSON());
|
|
530
|
+
imodel.saveChanges();
|
|
531
|
+
expectText("12345", targetId);
|
|
532
|
+
imodel.elements.deleteAspect([aspect.id]);
|
|
533
|
+
imodel.saveChanges();
|
|
534
|
+
expectText(FieldRun.invalidContentIndicator, targetId);
|
|
535
|
+
const newAspect = {
|
|
536
|
+
element: new ElementOwnsUniqueAspect(sourceId),
|
|
537
|
+
classFullName: TestAspect.classFullName,
|
|
538
|
+
aspectProp: 42,
|
|
539
|
+
};
|
|
540
|
+
imodel.elements.insertAspect(newAspect);
|
|
541
|
+
imodel.saveChanges();
|
|
542
|
+
expectText("42", targetId);
|
|
543
|
+
});
|
|
544
|
+
it("updates only fields for specific modified element", () => {
|
|
545
|
+
const sourceA = insertTestElement();
|
|
546
|
+
const sourceB = insertTestElement();
|
|
547
|
+
const block = TextBlock.create({ styleId: "0x123" });
|
|
548
|
+
block.appendRun(createField(sourceA, "A"));
|
|
549
|
+
block.appendRun(createField(sourceB, "B"));
|
|
550
|
+
const targetId = insertAnnotationElement(block);
|
|
551
|
+
imodel.saveChanges();
|
|
552
|
+
expectText("100100", targetId);
|
|
553
|
+
const sourceElem = imodel.elements.getElement(sourceB);
|
|
554
|
+
sourceElem.intProp = 123;
|
|
555
|
+
sourceElem.update();
|
|
556
|
+
imodel.saveChanges();
|
|
557
|
+
expectText("100123", targetId);
|
|
558
|
+
});
|
|
559
|
+
it("supports complex property paths", () => {
|
|
560
|
+
const sourceId = insertTestElement();
|
|
561
|
+
const block = TextBlock.create({ styleId: "0x123" });
|
|
562
|
+
block.appendRun(createField(sourceId, "", "outerStruct", ["innerStructs", 1, "doubles", -2]));
|
|
563
|
+
block.appendRun(createField(sourceId, "", "jsonProperties", undefined, ["zoo", "birds", 0, "name"]));
|
|
564
|
+
const targetId = insertAnnotationElement(block);
|
|
565
|
+
imodel.saveChanges();
|
|
566
|
+
expectText("2duck", targetId);
|
|
567
|
+
const source = imodel.elements.getElement(sourceId);
|
|
568
|
+
source.outerStruct.innerStructs[1].doubles[3] = 12.5;
|
|
569
|
+
source.jsonProperties.zoo.birds[0].name = "parrot";
|
|
570
|
+
source.update();
|
|
571
|
+
imodel.saveChanges();
|
|
572
|
+
expectText("12.5parrot", targetId);
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
});
|
|
576
|
+
//# sourceMappingURL=Fields.test.js.map
|