Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
1082afa
Refactor imodels tests
MichaelSwigerAtBentley Jul 24, 2026
1f58634
Merge branch 'master' of https://github.com/iTwin/itwinjs-core into m…
MichaelSwigerAtBentley Jul 24, 2026
cca6f75
Rename
MichaelSwigerAtBentley Jul 24, 2026
2df4cec
Potential fix for pull request finding
MichaelSwigerAtBentley Jul 27, 2026
41dce10
Potential fix for pull request finding
MichaelSwigerAtBentley Jul 27, 2026
d386b84
Potential fix for pull request finding
MichaelSwigerAtBentley Jul 27, 2026
361f7c2
Potential fix for pull request finding 'Semicolon insertion'
MichaelSwigerAtBentley Jul 27, 2026
262717a
Potential fix for pull request finding 'Semicolon insertion'
MichaelSwigerAtBentley Jul 27, 2026
03ab10e
Potential fix for pull request finding 'Semicolon insertion'
MichaelSwigerAtBentley Jul 27, 2026
a26f188
Merge branch 'master' into mike/refactor-imodel-tests
MichaelSwigerAtBentley Jul 27, 2026
02235fe
Remove Math.random test
MichaelSwigerAtBentley Jul 27, 2026
9d12f20
Merge branch 'mike/refactor-imodel-tests' of https://github.com/iTwin…
MichaelSwigerAtBentley Jul 27, 2026
e86c961
Merge branch 'master' into mike/refactor-imodel-tests
MichaelSwigerAtBentley Jul 28, 2026
5ba31d8
Potential fix for pull request finding
MichaelSwigerAtBentley Jul 28, 2026
bab52c6
Merge branch 'master' into mike/refactor-imodel-tests
MichaelSwigerAtBentley Jul 28, 2026
e2dd2f6
Rush change
MichaelSwigerAtBentley Jul 28, 2026
fe95dc0
Merge branch 'mike/refactor-imodel-tests' of https://github.com/iTwin…
MichaelSwigerAtBentley Jul 28, 2026
39c793c
Fix formatting
MichaelSwigerAtBentley Jul 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,728 changes: 0 additions & 3,728 deletions core/backend/src/test/imodel/IModel.test.ts

This file was deleted.

349 changes: 349 additions & 0 deletions core/backend/src/test/imodel/IModelECSql.test.ts

Large diffs are not rendered by default.

1,083 changes: 1,083 additions & 0 deletions core/backend/src/test/imodel/IModelElements.test.ts

Large diffs are not rendered by default.

649 changes: 649 additions & 0 deletions core/backend/src/test/imodel/IModelGeolocation.test.ts

Large diffs are not rendered by default.

565 changes: 565 additions & 0 deletions core/backend/src/test/imodel/IModelLifecycle.test.ts

Large diffs are not rendered by default.

334 changes: 334 additions & 0 deletions core/backend/src/test/imodel/IModelMetadata.test.ts

Large diffs are not rendered by default.

352 changes: 352 additions & 0 deletions core/backend/src/test/imodel/IModelModels.test.ts

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions core/backend/src/test/imodel/IModelRelationships.test.ts

Large diffs are not rendered by default.

66 changes: 66 additions & 0 deletions core/backend/src/test/imodel/IModelTestFixtures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { assert, expect } from "chai";
import * as path from "path";
import { DbResult, IModelStatus } from "@itwin/core-bentley";
import { EntityProps, IModelError } from "@itwin/core-common";
import { Entity, SnapshotDb } from "../../core-backend";
import { KnownTestLocations } from "../KnownTestLocations";
import { IModelTestUtils } from "../IModelTestUtils";

/** Awaits a promise and returns the {@link IModelError} it rejected with, or `undefined` if it resolved. */
export async function getIModelError<T>(promise: Promise<T>): Promise<IModelError | undefined> {
try {
await promise;
return undefined;
} catch (err) {
return err instanceof IModelError ? err : undefined;
}
}

/** Asserts that `error` is an {@link IModelError} carrying the expected error number. */
export function expectIModelError(expectedErrorNumber: IModelStatus | DbResult, error: IModelError | undefined): void {
expect(error).not.to.be.undefined;
expect(error).instanceof(IModelError);
expect(error!.errorNumber).to.equal(expectedErrorNumber);
}

/** Roundtrip an entity through a JSON string and back to a new entity, asserting the serialization is stable. */
export function roundtripThroughJson(entity1: Entity): Entity {
const string1 = JSON.stringify(entity1);
const props1 = JSON.parse(string1) as EntityProps;
const entity2 = new (entity1.constructor as any)(props1, entity1.iModel); // create a new entity from the EntityProps
const string2 = JSON.stringify(entity2);
assert.equal(string1, string2);
return entity2;
}

/**
* Create a fresh writable snapshot from a seed asset and import the `TestBim` schema into it.
* Use this for tests that mutate the iModel so each gets its own isolated copy.
*/
export async function generateTestSnapshot(targetFileName: string, seedAssetName: string): Promise<SnapshotDb> {
const seedFile = IModelTestUtils.resolveAssetFile(seedAssetName);
const snapshotFile = IModelTestUtils.prepareOutputFile("IModel", targetFileName);
const imodel = IModelTestUtils.createSnapshotFromSeed(snapshotFile, seedFile);
const schemaPathname = path.join(KnownTestLocations.assetsDir, "TestBim.ecschema.xml");
await imodel.importSchemas([schemaPathname]);
return imodel;
}

/**
* Create a fresh writable snapshot copied from a seed asset. Use in `beforeEach` for mutating
* tests (hybrid isolation) so state never leaks between tests.
*/
export function createIModelFromSeed(targetFileName: string, seedAssetName: string): SnapshotDb {
const seedFile = IModelTestUtils.resolveAssetFile(seedAssetName);
const snapshotFile = IModelTestUtils.prepareOutputFile("IModel", targetFileName);
return IModelTestUtils.createSnapshotFromSeed(snapshotFile, seedFile);
}

/** Open a seed asset directly as a read-only snapshot. Use for shared, pure-read fixtures. */
export function openSeedReadonly(seedAssetName: string): SnapshotDb {
return SnapshotDb.openFile(IModelTestUtils.resolveAssetFile(seedAssetName));
}
255 changes: 255 additions & 0 deletions core/backend/src/test/imodel/IModelViews.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { assert, expect } from "chai";
import {
BisCodeSpec, ColorDef, DisplayStyleProps, DisplayStyleSettings, DisplayStyleSettingsProps, IModel, MapImageryProps,
RenderMode, SpatialViewDefinitionProps, ViewDefinitionProps, ViewFlagProps, ViewFlags,
} from "@itwin/core-common";
import { EditTxn, withEditTxn } from "../../EditTxn";
import { DictionaryModel, DisplayStyle3d, DisplayStyleCreationOptions, IModelDb, SnapshotDb, ViewDefinition } from "../../core-backend";
import { IModelTestUtils } from "../IModelTestUtils";
import { TestUtils } from "../TestUtils";
import { createIModelFromSeed } from "./IModelTestFixtures";

// spell-checker: disable
Comment thread
hl662 marked this conversation as resolved.

describe("iModel views", () => {
let compatibilityReadonly: SnapshotDb;
const mutableIModels: SnapshotDb[] = [];

before(async () => {
await TestUtils.startBackend();
IModelTestUtils.registerTestBimSchema();

const compatibilityWritable = createIModelFromSeed("views-CompatibilityTestSeed.bim", "CompatibilityTestSeed.bim");
const compatibilityPath = compatibilityWritable.pathName;
compatibilityWritable.close();
compatibilityReadonly = SnapshotDb.openFile(compatibilityPath);
});

after(async () => {
if (compatibilityReadonly !== undefined && compatibilityReadonly.isOpen)
compatibilityReadonly.close();
for (const imodel of mutableIModels.splice(0)) {
if (imodel.isOpen)
imodel.close();
}
});

afterEach(() => {
for (const imodel of mutableIModels.splice(0)) {
if (imodel.isOpen)
imodel.close();
}
});

const trackMutableIModel = (imodel: SnapshotDb): SnapshotDb => {
mutableIModels.push(imodel);
return imodel;
};

it("should insert a DisplayStyle", () => {
const imodel2 = trackMutableIModel(createIModelFromSeed("views-insert-display-style.bim", "CompatibilityTestSeed.bim"));
const model = imodel2.models.getModel<DictionaryModel>(IModel.dictionaryId);
expect(model).not.to.be.undefined;

const settings: DisplayStyleSettingsProps = {
backgroundColor: ColorDef.blue.toJSON(),
viewflags: ViewFlags.fromJSON({
renderMode: RenderMode.SolidFill,
}),
};

const props: DisplayStyleProps = {
classFullName: DisplayStyle3d.classFullName,
model: IModel.dictionaryId,
code: { spec: BisCodeSpec.displayStyle, scope: IModel.dictionaryId, value: "test style" },
isPrivate: false,
jsonProperties: {
styles: settings,
},
};

const txn = new EditTxn(imodel2, "insert and update DisplayStyle");
txn.start();
const styleId = txn.insertElement(props);
let style = imodel2.elements.getElement<DisplayStyle3d>(styleId);
expect(style instanceof DisplayStyle3d).to.be.true;
expect(style.code.spec).equal(imodel2.codeSpecs.getByName(BisCodeSpec.displayStyle).id);

expect(style.settings.viewFlags.renderMode).to.equal(RenderMode.SolidFill);
expect(style.settings.backgroundColor.equals(ColorDef.blue)).to.be.true;

const newFlags = style.settings.viewFlags.copy({ renderMode: RenderMode.SmoothShade });
style.settings.viewFlags = newFlags;
style.settings.backgroundColor = ColorDef.red;
style.settings.monochromeColor = ColorDef.green;
expect(style.jsonProperties.styles.viewflags.renderMode).to.equal(RenderMode.SmoothShade);

txn.updateElement(style.toJSON());
txn.end();
style = imodel2.elements.getElement<DisplayStyle3d>(styleId);
expect(style instanceof DisplayStyle3d).to.be.true;

expect(style.settings.viewFlags.renderMode).to.equal(RenderMode.SmoothShade);
expect(style.settings.backgroundColor.equals(ColorDef.red)).to.be.true;
expect(style.settings.monochromeColor.equals(ColorDef.green)).to.be.true;
});

it("should create display styles", () => {
const imodel2 = trackMutableIModel(createIModelFromSeed("views-create-display-styles.bim", "CompatibilityTestSeed.bim"));
const defaultViewFlags = new ViewFlags().toJSON();
const defaultMapImagery = new DisplayStyleSettings({}).toJSON().mapImagery;

const viewFlags = new ViewFlags({ patterns: false, visibleEdges: true });
const viewflags: ViewFlagProps = { noWhiteOnWhiteReversal: true, shadows: true, noTransp: true };

const mapImagery: MapImageryProps = {
backgroundBase: ColorDef.red.tbgr,
backgroundLayers: [{
name: "x",
url: "y",
transparency: 0.5,
formatId: "WMS",
visible: true,
}],
};

const props: DisplayStyleSettingsProps = {
mapImagery,
excludedElements: ["0x123", "0xfed"],
timePoint: 42,
backgroundColor: ColorDef.green.tbgr,
};

type TestCase = [DisplayStyleCreationOptions | undefined, ViewFlagProps, boolean];
const testCases: TestCase[] = [
[undefined, defaultViewFlags, false],
[{ viewFlags }, viewFlags.toJSON(), false],
[{ viewflags }, viewflags, false],
[{ viewflags, viewFlags }, viewFlags.toJSON(), false],
[props, defaultViewFlags, false],
[{ ...props, viewflags }, viewflags, false],
[{ backgroundColor: ColorDef.blue }, defaultViewFlags, false],
[{ backgroundColor: ColorDef.from(1, 2, 3, 4) }, defaultViewFlags, false],
[{ backgroundColor: ColorDef.blue.tbgr }, defaultViewFlags, false],
[{ backgroundColor: ColorDef.from(1, 2, 3, 4).tbgr }, defaultViewFlags, false],
];

let suffix = 123;
withEditTxn(imodel2, (txn) => {
for (const test of testCases) {
const expected = test[0] ?? {};
const styleId = DisplayStyle3d.insert(txn, IModel.dictionaryId, `TestStyle${suffix++}`, expected);
const style = imodel2.elements.getElement<DisplayStyle3d>(styleId).toJSON();
expect(style.jsonProperties.styles).not.to.be.undefined;

expect(style.jsonProperties).not.to.be.undefined;
expect(style.jsonProperties.styles).not.to.be.undefined;
const actual = style.jsonProperties.styles!;

expect(actual.viewflags).not.to.be.undefined;
const expectedVf = ViewFlags.fromJSON(test[1]);
const actualVf = ViewFlags.fromJSON(actual.viewflags);
expect(actualVf.toJSON()).to.deep.equal(expectedVf.toJSON());

const expectedBGColor = expected.backgroundColor instanceof ColorDef ? expected.backgroundColor.toJSON() : expected.backgroundColor;
expect(actual.backgroundColor).to.equal(expectedBGColor);

// DisplayStyleSettings constructor always initializes json.mapImagery.
expect(actual.mapImagery).to.deep.equal(expected.mapImagery ?? defaultMapImagery);
expect(actual.excludedElements).to.deep.equal(expected.excludedElements);
expect(actual.timePoint).to.deep.equal(expected.timePoint);
}
});
});

it("should be able to query for ViewDefinitionProps", () => {
const imodel2 = compatibilityReadonly;
const viewDefinitionProps: ViewDefinitionProps[] = imodel2.views.queryViewDefinitionProps(); // query for all ViewDefinitions
assert.isAtLeast(viewDefinitionProps.length, 3);
assert.isTrue(viewDefinitionProps[0].classFullName.includes("ViewDefinition"));
assert.isFalse(viewDefinitionProps[1].isPrivate);

const spatialViewDefinitionProps = imodel2.views.queryViewDefinitionProps("BisCore.SpatialViewDefinition") as SpatialViewDefinitionProps[]; // limit query to SpatialViewDefinitions
assert.isAtLeast(spatialViewDefinitionProps.length, 3);
assert.exists(spatialViewDefinitionProps[2].modelSelector?.id);
});

it("should iterate ViewDefinitions", () => {
const imodel2 = compatibilityReadonly;
// imodel2 contains 3 SpatialViewDefinitions and no other views.
let numViews = 0;
let result = imodel2.views.iterateViews(IModelDb.Views.defaultQueryParams, (_view: ViewDefinition) => {
++numViews;
return true;
});

expect(result).to.be.true;
expect(numViews).to.equal(3);

// Query specifically for spatial views
numViews = 0;
result = imodel2.views.iterateViews({ from: "BisCore.SpatialViewDefinition" }, (view: ViewDefinition) => {
if (view.isSpatialView())
++numViews;

return view.isSpatialView();
});
expect(result).to.be.true;
expect(numViews).to.equal(3);

// Query specifically for 2d views
numViews = 0;
result = imodel2.views.iterateViews({ from: "BisCore.ViewDefinition2d" }, (_view: ViewDefinition) => {
++numViews;
return true;
});

expect(result).to.be.true;
expect(numViews).to.equal(0);

// Terminate iteration on first view
numViews = 0;
result = imodel2.views.iterateViews(IModelDb.Views.defaultQueryParams, (_view: ViewDefinition) => {
++numViews;
return false;
});

expect(result).to.be.false;
expect(numViews).to.equal(1);
});

it("read view thumbnail", () => {
const imodel5 = trackMutableIModel(createIModelFromSeed("views-mirukuru-thumbnail.bim", "mirukuru.ibim"));
const viewId = "0x24";
const thumbnail = imodel5.views.getThumbnail(viewId);
assert.exists(thumbnail);
if (!thumbnail)
return;
assert.equal(thumbnail.format, "jpeg");
assert.equal(thumbnail.height, 768);
assert.equal(thumbnail.width, 768);
assert.equal(thumbnail.image.length, 18062);

thumbnail.width = 100;
thumbnail.height = 200;
thumbnail.format = "png";
thumbnail.image = new Uint8Array(200);
thumbnail.image.fill(12);
const stat = imodel5.views.saveThumbnail(viewId, thumbnail);
assert.equal(stat, 0, "save thumbnail");

const thumbnail2 = imodel5.views.getThumbnail(viewId);
assert.exists(thumbnail2);
if (!thumbnail2)
return;
assert.equal(thumbnail2.format, "png");
assert.equal(thumbnail2.height, 200);
assert.equal(thumbnail2.width, 100);
assert.equal(thumbnail2.image.length, 200);
assert.equal(thumbnail2.image[0], 12);
});
});
Loading