From 7cf395c753915473820ca3e46f7d53752110bde3 Mon Sep 17 00:00:00 2001 From: Jonas <100586436+JonasDov@users.noreply.github.com> Date: Fri, 31 Jan 2025 20:00:12 +0200 Subject: [PATCH 01/38] Add definitionContainers to categories tree --- .../itwin/tree-widget/src/test/IModelUtils.ts | 18 + .../CategoriesTreeDefinition.test.ts | 217 ++- .../CategoriesTreeFiltering.test.ts | 190 ++- .../internal/CategoriesTreeIdsCache.test.ts | 717 ++++++++ .../internal/CategoriesTreeNode.test.ts | 49 + .../CategoriesVisibilityHandler.test.ts | 1469 +++++++++++++++++ .../trees/categories-tree/internal/Utils.ts | 193 +++ .../internal/VisibilityValidation.ts | 93 ++ .../ModelsTreeVisibilityHandler.test.ts | 2 +- .../CategoriesTreeDefinition.ts | 360 ++-- .../CategoriesVisibilityHandler.ts | 116 -- .../categories-tree/UseCategoriesTree.tsx | 109 +- .../internal/CategoriesTreeIdsCache.ts | 374 +++++ .../internal/CategoriesTreeNode.ts | 34 + .../internal/CategoriesVisibilityHandler.ts | 239 +++ .../trees/common/CategoriesVisibilityUtils.ts | 2 +- 16 files changed, 3923 insertions(+), 259 deletions(-) create mode 100644 packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeIdsCache.test.ts create mode 100644 packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeNode.test.ts create mode 100644 packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts create mode 100644 packages/itwin/tree-widget/src/test/trees/categories-tree/internal/Utils.ts create mode 100644 packages/itwin/tree-widget/src/test/trees/categories-tree/internal/VisibilityValidation.ts delete mode 100644 packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesVisibilityHandler.ts create mode 100644 packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts create mode 100644 packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeNode.ts create mode 100644 packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.ts diff --git a/packages/itwin/tree-widget/src/test/IModelUtils.ts b/packages/itwin/tree-widget/src/test/IModelUtils.ts index 2c4e7a7e0..4741db8bb 100644 --- a/packages/itwin/tree-widget/src/test/IModelUtils.ts +++ b/packages/itwin/tree-widget/src/test/IModelUtils.ts @@ -265,6 +265,24 @@ export function insertSpatialCategory( return { className, id }; } +export function insertDefinitionContainer( + props: BaseInstanceInsertProps & { codeValue: string; modelId?: Id64String; isPrivate?: boolean } & Partial< + Omit + >, +) { + const { builder, classFullName, modelId, codeValue, ...elementProps } = props; + const defaultClassName = `BisCore${props.fullClassNameSeparator ?? "."}DefinitionContainer`; + const className = classFullName ?? defaultClassName; + const model = modelId ?? IModel.dictionaryId; + const id = builder.insertElement({ + classFullName: className, + model, + code: builder.createCode(model, BisCodeSpec.nullCodeSpec, codeValue), + ...elementProps, + }); + return { className, id }; +} + export function insertDrawingCategory( props: BaseInstanceInsertProps & { codeValue: string; modelId?: Id64String } & Partial>, ) { diff --git a/packages/itwin/tree-widget/src/test/trees/categories-tree/CategoriesTreeDefinition.test.ts b/packages/itwin/tree-widget/src/test/trees/categories-tree/CategoriesTreeDefinition.test.ts index a38809e77..a4db22fb0 100644 --- a/packages/itwin/tree-widget/src/test/trees/categories-tree/CategoriesTreeDefinition.test.ts +++ b/packages/itwin/tree-widget/src/test/trees/categories-tree/CategoriesTreeDefinition.test.ts @@ -8,13 +8,20 @@ import { ECSchemaRpcInterface } from "@itwin/ecschema-rpcinterface-common"; import { ECSchemaRpcImpl } from "@itwin/ecschema-rpcinterface-impl"; import { PresentationRpcInterface } from "@itwin/presentation-common"; import { createIModelHierarchyProvider } from "@itwin/presentation-hierarchies"; -import { - HierarchyCacheMode, initialize as initializePresentationTesting, terminate as terminatePresentationTesting, -} from "@itwin/presentation-testing"; +import { HierarchyCacheMode, initialize as initializePresentationTesting, terminate as terminatePresentationTesting } from "@itwin/presentation-testing"; import { CategoriesTreeDefinition } from "../../../tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.js"; +import { CategoriesTreeIdsCache } from "../../../tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.js"; import { - buildIModel, insertDrawingCategory, insertDrawingGraphic, insertDrawingModelWithPartition, insertPhysicalElement, insertPhysicalModelWithPartition, - insertSpatialCategory, insertSubCategory, + buildIModel, + insertDefinitionContainer, + insertDrawingCategory, + insertDrawingGraphic, + insertDrawingModelWithPartition, + insertPhysicalElement, + insertPhysicalModelWithPartition, + insertSpatialCategory, + insertSubCategory, + insertSubModel, } from "../../IModelUtils.js"; import { createIModelAccess } from "../Common.js"; import { NodeValidators, validateHierarchy } from "../HierarchyValidation.js"; @@ -66,6 +73,204 @@ describe("Categories tree", () => { }); }); + it("does not show definitionContainer when it doesn't contain category", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer" }); + insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainer.id }); + + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory" }); + + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + + return { category }; + }); + await validateHierarchy({ + provider: createCategoryTreeProvider(imodel, "3d"), + expect: [ + NodeValidators.createForInstanceNode({ + instanceKeys: [keys.category], + supportsFiltering: true, + children: false, + }), + ], + }); + }); + + it("does not show definitionContainer when it contains definitionContainer without categories", async function () { + const { imodel } = await buildIModel(this, async (builder) => { + insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer" }); + const definitionModel = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainer.id }); + const definitionContainerChild = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerChild", modelId: definitionModel.id }); + insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + }); + await validateHierarchy({ + provider: createCategoryTreeProvider(imodel, "3d"), + expect: [], + }); + }); + + it("does not show definitionContainer or category when category is private", async function () { + const { imodel } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer" }); + const definitionModel = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainer.id }); + + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory", modelId: definitionModel.id, isPrivate: true }); + + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + }); + await validateHierarchy({ + provider: createCategoryTreeProvider(imodel, "3d"), + expect: [], + }); + }); + + it("does not show definitionContainer or category when definitionContainer is private", async function () { + const { imodel } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer", isPrivate: true }); + const definitionModel = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainer.id }); + + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory", modelId: definitionModel.id }); + + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + }); + await validateHierarchy({ + provider: createCategoryTreeProvider(imodel, "3d"), + expect: [], + }); + }); + + it("does not show definitionContainers or categories when definitionContainer contains another definitionContainer that is private", async function () { + const { imodel } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer" }); + const definitionModel = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainer.id }); + const definitionContainerChild = insertDefinitionContainer({ + builder, + codeValue: "DefinitionContainerChild", + isPrivate: true, + modelId: definitionModel.id, + }); + const defintionModelChild = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory", modelId: defintionModelChild.id }); + + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + }); + await validateHierarchy({ + provider: createCategoryTreeProvider(imodel, "3d"), + expect: [], + }); + }); + + it("shows definitionContainer when it contains category", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer" }); + const definitionModel = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainer.id }); + + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory", modelId: definitionModel.id }); + + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + + return { definitionContainer, category }; + }); + await validateHierarchy({ + provider: createCategoryTreeProvider(imodel, "3d"), + expect: [ + NodeValidators.createForInstanceNode({ + instanceKeys: [keys.definitionContainer], + supportsFiltering: true, + children: [ + NodeValidators.createForInstanceNode({ + instanceKeys: [keys.category], + label: "SpatialCategory", + children: false, + }), + ], + }), + ], + }); + }); + + it("shows all definitionContainers when they contain category directly or indirectly", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer" }); + const definitionModel = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainer.id }); + const definitionContainerChild = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerChild", modelId: definitionModel.id }); + const definitionModelChild = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory", modelId: definitionModelChild.id }); + + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + + return { definitionContainer, definitionContainerChild, category }; + }); + await validateHierarchy({ + provider: createCategoryTreeProvider(imodel, "3d"), + expect: [ + NodeValidators.createForInstanceNode({ + instanceKeys: [keys.definitionContainer], + supportsFiltering: true, + children: [ + NodeValidators.createForInstanceNode({ + instanceKeys: [keys.definitionContainerChild], + label: "DcChild", + children: [ + NodeValidators.createForInstanceNode({ + instanceKeys: [keys.category], + label: "SpatialCategory", + children: false, + }), + ], + }), + ], + }), + ], + }); + }); + + it("shows root categories and definitionContainer", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer" }); + const definitionModel = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainer.id }); + + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory" }); + const childCategory = insertSpatialCategory({ builder, codeValue: "ScChild", modelId: definitionModel.id }); + + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: childCategory.id }); + + return { category, definitionContainer, childCategory }; + }); + await validateHierarchy({ + provider: createCategoryTreeProvider(imodel, "3d"), + expect: [ + NodeValidators.createForInstanceNode({ + instanceKeys: [keys.definitionContainer], + supportsFiltering: true, + children: [ + NodeValidators.createForInstanceNode({ + instanceKeys: [keys.childCategory], + label: "ScChild", + children: false, + }), + ], + }), + NodeValidators.createForInstanceNode({ + instanceKeys: [keys.category], + supportsFiltering: true, + children: false, + }), + ], + }); + }); + it("does not show private 3d subCategories", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); @@ -162,6 +367,6 @@ function createCategoryTreeProvider(imodel: IModelConnection, viewType: "2d" | " const imodelAccess = createIModelAccess(imodel); return createIModelHierarchyProvider({ imodelAccess, - hierarchyDefinition: new CategoriesTreeDefinition({ imodelAccess, viewType }), + hierarchyDefinition: new CategoriesTreeDefinition({ imodelAccess, viewType, idsCache: new CategoriesTreeIdsCache(imodelAccess, viewType) }), }); } diff --git a/packages/itwin/tree-widget/src/test/trees/categories-tree/CategoriesTreeFiltering.test.ts b/packages/itwin/tree-widget/src/test/trees/categories-tree/CategoriesTreeFiltering.test.ts index 29a2551b8..adb0e8dd1 100644 --- a/packages/itwin/tree-widget/src/test/trees/categories-tree/CategoriesTreeFiltering.test.ts +++ b/packages/itwin/tree-widget/src/test/trees/categories-tree/CategoriesTreeFiltering.test.ts @@ -10,8 +10,10 @@ import { ECSchemaRpcImpl } from "@itwin/ecschema-rpcinterface-impl"; import { PresentationRpcInterface } from "@itwin/presentation-common"; import { HierarchyCacheMode, initialize as initializePresentationTesting, terminate as terminatePresentationTesting } from "@itwin/presentation-testing"; import { CategoriesTreeDefinition } from "../../../tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.js"; +import { CategoriesTreeIdsCache } from "../../../tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.js"; import { buildIModel, + insertDefinitionContainer, insertDrawingCategory, insertDrawingGraphic, insertDrawingModelWithPartition, @@ -19,6 +21,7 @@ import { insertPhysicalModelWithPartition, insertSpatialCategory, insertSubCategory, + insertSubModel, } from "../../IModelUtils.js"; import { createIModelAccess } from "../Common.js"; @@ -43,6 +46,129 @@ describe("Categories tree", () => { await terminatePresentationTesting(); }); + it("finds definitionContainer by label", async function () { + const { imodel, keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer", userLabel: "Test" }); + const definitionModel = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainer.id }); + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory", modelId: definitionModel.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + + return { keys: { definitionContainer } }; + }); + const imodelAccess = createIModelAccess(imodel); + const viewType = "3d"; + const idsCache = new CategoriesTreeIdsCache(imodelAccess, viewType); + expect( + await CategoriesTreeDefinition.createInstanceKeyPaths({ + imodelAccess, + label: "Test", + viewType, + idsCache, + }), + ).to.deep.eq([{ path: [keys.definitionContainer], options: { autoExpand: true } }]); + }); + + it("finds definitionContainer by label when it is contained by another definitionContainer", async function () { + const { imodel, keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer" }); + const definitionModel = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainer.id }); + const definitionContainerChild = insertDefinitionContainer({ + builder, + codeValue: "DefinitionContainerChild", + userLabel: "Test", + modelId: definitionModel.id, + }); + const definitionModelChild = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory", modelId: definitionModelChild.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + + return { keys: { definitionContainer, definitionContainerChild } }; + }); + const imodelAccess = createIModelAccess(imodel); + const viewType = "3d"; + const idsCache = new CategoriesTreeIdsCache(imodelAccess, viewType); + expect( + await CategoriesTreeDefinition.createInstanceKeyPaths({ + imodelAccess, + label: "Test", + viewType, + idsCache, + }), + ).to.deep.eq([{ path: [keys.definitionContainer, keys.definitionContainerChild], options: { autoExpand: true } }]); + }); + + it("does not find definitionContainer by label when it doesn't contain categories", async function () { + const { imodel } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer", userLabel: "Test" }); + insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainer.id }); + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + + return { keys: { definitionContainer } }; + }); + const imodelAccess = createIModelAccess(imodel); + const viewType = "3d"; + const idsCache = new CategoriesTreeIdsCache(imodelAccess, viewType); + expect( + await CategoriesTreeDefinition.createInstanceKeyPaths({ + imodelAccess, + label: "Test", + viewType, + idsCache, + }), + ).to.deep.eq([]); + }); + + it("finds category by label when it is contained by definitionContainer", async function () { + const { imodel, keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer" }); + const definitionModel = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainer.id }); + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory", userLabel: "Test", modelId: definitionModel.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + + return { keys: { definitionContainer, category } }; + }); + const imodelAccess = createIModelAccess(imodel); + const viewType = "3d"; + const idsCache = new CategoriesTreeIdsCache(imodelAccess, viewType); + expect( + await CategoriesTreeDefinition.createInstanceKeyPaths({ + imodelAccess, + label: "Test", + viewType, + idsCache, + }), + ).to.deep.eq([{ path: [keys.definitionContainer, keys.category], options: { autoExpand: true } }]); + }); + + it("finds subCategory by label when its parent category is contained by definitionContainer", async function () { + const { imodel, keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer" }); + const definitionModel = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainer.id }); + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory", modelId: definitionModel.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + const subCategory1 = insertSubCategory({ builder, codeValue: "SubCategory1", parentCategoryId: category.id, modelId: definitionModel.id }); + + return { keys: { definitionContainer, category, subCategory1 } }; + }); + const imodelAccess = createIModelAccess(imodel); + const viewType = "3d"; + const idsCache = new CategoriesTreeIdsCache(imodelAccess, viewType); + expect( + await CategoriesTreeDefinition.createInstanceKeyPaths({ + imodelAccess, + label: "SubCategory1", + viewType, + idsCache, + }), + ).to.deep.eq([{ path: [keys.definitionContainer, keys.category, keys.subCategory1], options: { autoExpand: true } }]); + }); + it("finds 3d categories by label containing special SQLite characters", async function () { const { imodel, keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); @@ -61,19 +187,25 @@ describe("Categories tree", () => { }; }); + const imodelAccess = createIModelAccess(imodel); + const viewType = "3d"; + const idsCache = new CategoriesTreeIdsCache(imodelAccess, viewType); + expect( await CategoriesTreeDefinition.createInstanceKeyPaths({ - imodelAccess: createIModelAccess(imodel), + imodelAccess, label: "_", viewType: "3d", + idsCache, }), ).to.deep.eq([{ path: [keys.category1], options: { autoExpand: true } }]); expect( await CategoriesTreeDefinition.createInstanceKeyPaths({ - imodelAccess: createIModelAccess(imodel), + imodelAccess, label: "%", viewType: "3d", + idsCache, }), ).to.deep.eq([{ path: [keys.category2], options: { autoExpand: true } }]); }); @@ -97,19 +229,25 @@ describe("Categories tree", () => { }; }); + const imodelAccess = createIModelAccess(imodel); + const viewType = "3d"; + const idsCache = new CategoriesTreeIdsCache(imodelAccess, viewType); + expect( await CategoriesTreeDefinition.createInstanceKeyPaths({ - imodelAccess: createIModelAccess(imodel), + imodelAccess, label: "_", viewType: "3d", + idsCache, }), ).to.deep.eq([{ path: [keys.category, keys.subCategory1], options: { autoExpand: true } }]); expect( await CategoriesTreeDefinition.createInstanceKeyPaths({ - imodelAccess: createIModelAccess(imodel), + imodelAccess, label: "%", viewType: "3d", + idsCache, }), ).to.deep.eq([{ path: [keys.category, keys.subCategory2], options: { autoExpand: true } }]); }); @@ -127,19 +265,26 @@ describe("Categories tree", () => { }, }; }); + + const imodelAccess = createIModelAccess(imodel); + const viewType = "3d"; + const idsCache = new CategoriesTreeIdsCache(imodelAccess, viewType); + expect( await CategoriesTreeDefinition.createInstanceKeyPaths({ - imodelAccess: createIModelAccess(imodel), + imodelAccess, label: "Test", viewType: "3d", + idsCache, }), ).to.deep.eq([{ path: [keys.category], options: { autoExpand: true } }]); expect( await CategoriesTreeDefinition.createInstanceKeyPaths({ - imodelAccess: createIModelAccess(imodel), + imodelAccess, label: "SpatialCategory", viewType: "3d", + idsCache, }), ).to.deep.eq([]); }); @@ -164,27 +309,34 @@ describe("Categories tree", () => { }; }); + const imodelAccess = createIModelAccess(imodel); + const viewType = "3d"; + const idsCache = new CategoriesTreeIdsCache(imodelAccess, viewType); + expect( await CategoriesTreeDefinition.createInstanceKeyPaths({ - imodelAccess: createIModelAccess(imodel), + imodelAccess, label: "Test", viewType: "3d", + idsCache, }), ).to.deep.eq([{ path: [keys.category], options: { autoExpand: true } }]); expect( await CategoriesTreeDefinition.createInstanceKeyPaths({ - imodelAccess: createIModelAccess(imodel), + imodelAccess, label: "SubCategory1", viewType: "3d", + idsCache, }), ).to.deep.eq([{ path: [keys.category, keys.subCategory1], options: { autoExpand: true } }]); expect( await CategoriesTreeDefinition.createInstanceKeyPaths({ - imodelAccess: createIModelAccess(imodel), + imodelAccess, label: "SubCategory2", viewType: "3d", + idsCache, }), ).to.deep.eq([{ path: [keys.category, keys.subCategory2], options: { autoExpand: true } }]); }); @@ -207,19 +359,25 @@ describe("Categories tree", () => { }; }); + const imodelAccess = createIModelAccess(imodel); + const viewType = "3d"; + const idsCache = new CategoriesTreeIdsCache(imodelAccess, viewType); + expect( await CategoriesTreeDefinition.createInstanceKeyPaths({ - imodelAccess: createIModelAccess(imodel), + imodelAccess, label: "_", viewType: "2d", + idsCache, }), ).to.deep.eq([{ path: [keys.category1], options: { autoExpand: true } }]); expect( await CategoriesTreeDefinition.createInstanceKeyPaths({ - imodelAccess: createIModelAccess(imodel), + imodelAccess, label: "%", viewType: "2d", + idsCache, }), ).to.deep.eq([{ path: [keys.category2], options: { autoExpand: true } }]); }); @@ -243,19 +401,25 @@ describe("Categories tree", () => { }; }); + const imodelAccess = createIModelAccess(imodel); + const viewType = "3d"; + const idsCache = new CategoriesTreeIdsCache(imodelAccess, viewType); + expect( await CategoriesTreeDefinition.createInstanceKeyPaths({ - imodelAccess: createIModelAccess(imodel), + imodelAccess, label: "_", viewType: "2d", + idsCache, }), ).to.deep.eq([{ path: [keys.category, keys.subCategory1], options: { autoExpand: true } }]); expect( await CategoriesTreeDefinition.createInstanceKeyPaths({ - imodelAccess: createIModelAccess(imodel), + imodelAccess, label: "%", viewType: "2d", + idsCache, }), ).to.deep.eq([{ path: [keys.category, keys.subCategory2], options: { autoExpand: true } }]); }); diff --git a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeIdsCache.test.ts b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeIdsCache.test.ts new file mode 100644 index 000000000..18b13f8e2 --- /dev/null +++ b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeIdsCache.test.ts @@ -0,0 +1,717 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { join } from "path"; +import { IModelReadRpcInterface, SnapshotIModelRpcInterface } from "@itwin/core-common"; +import { ECSchemaRpcInterface } from "@itwin/ecschema-rpcinterface-common"; +import { ECSchemaRpcImpl } from "@itwin/ecschema-rpcinterface-impl"; +import { PresentationRpcInterface } from "@itwin/presentation-common"; +import { HierarchyCacheMode, initialize as initializePresentationTesting, terminate as terminatePresentationTesting } from "@itwin/presentation-testing"; +import { CategoriesTreeIdsCache } from "../../../../tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.js"; +import { + buildIModel, + insertDefinitionContainer, + insertPhysicalElement, + insertPhysicalModelWithPartition, + insertSpatialCategory, + insertSubCategory, + insertSubModel, +} from "../../../IModelUtils.js"; +import { createIModelAccess } from "../../Common.js"; + +describe("CategoriesTreeIdsCache", () => { + before(async function () { + await initializePresentationTesting({ + backendProps: { + caching: { + hierarchies: { + mode: HierarchyCacheMode.Memory, + }, + }, + }, + testOutputDir: join(__dirname, "output"), + backendHostProps: { + cacheDir: join(__dirname, "cache"), + }, + rpcs: [SnapshotIModelRpcInterface, IModelReadRpcInterface, PresentationRpcInterface, ECSchemaRpcInterface], + }); + // eslint-disable-next-line @itwin/no-internal + ECSchemaRpcImpl.register(); + }); + + after(async function () { + await terminatePresentationTesting(); + }); + + describe("getDirectChildDefinitionContainersAndCategories", () => { + it("when definitionContainer contains nothing", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainer = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); + insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainer.id }); + const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + + return { definitionContainer }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getDirectChildDefinitionContainersAndCategories([keys.definitionContainer.id])).to.deep.eq({ + categories: [], + definitionContainers: [], + }); + }); + + it("when definitionContainer contains definitionContainer", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + const definitionContainerChild = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer", modelId: definitionModelRoot.id }); + insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + return { definitionContainerRoot }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getDirectChildDefinitionContainersAndCategories([keys.definitionContainerRoot.id])).to.deep.eq({ + categories: [], + definitionContainers: [], + }); + }); + + it("when definitionContainer contains definitionContainer, that has categories", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + const definitionContainerChild = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer", modelId: definitionModelRoot.id }); + const definitionModelChild = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory", modelId: definitionModelChild.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + + return { definitionContainerRoot, definitionContainerChild }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getDirectChildDefinitionContainersAndCategories([keys.definitionContainerRoot.id])).to.deep.eq({ + categories: [], + definitionContainers: [keys.definitionContainerChild.id], + }); + }); + + it("when definitionContainer contains categories", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory", modelId: definitionModelRoot.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + + return { definitionContainerRoot, category }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getDirectChildDefinitionContainersAndCategories([keys.definitionContainerRoot.id])).to.deep.eq({ + categories: [keys.category.id], + definitionContainers: [], + }); + }); + + it("when definitionContainer contains categories and definitionContainers that contain nothing", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + const definitionContainerChild = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer", modelId: definitionModelRoot.id }); + insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory", modelId: definitionModelRoot.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + + return { definitionContainerRoot, category }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getDirectChildDefinitionContainersAndCategories([keys.definitionContainerRoot.id])).to.deep.eq({ + categories: [keys.category.id], + definitionContainers: [], + }); + }); + + it("when definitionContainer contains categories and definitionContainers that contain categories", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + const directCategory = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory", modelId: definitionModelRoot.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: directCategory.id }); + + const definitionContainerChild = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer", modelId: definitionModelRoot.id }); + const definitionModelChild = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + const indirectCategory = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory", modelId: definitionModelChild.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: indirectCategory.id }); + + return { definitionContainerRoot, directCategory, definitionModelChild }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getDirectChildDefinitionContainersAndCategories([keys.definitionContainerRoot.id])).to.deep.eq({ + categories: [keys.directCategory.id], + definitionContainers: [keys.definitionModelChild.id], + }); + }); + + it("when definitionContainer with categories is contained by definitionContainer", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + + const definitionContainerChild = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer", modelId: definitionModelRoot.id }); + const definitionModelChild = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + const indirectCategory = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory", modelId: definitionModelChild.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: indirectCategory.id }); + + return { definitionModelChild, indirectCategory }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getDirectChildDefinitionContainersAndCategories([keys.definitionModelChild.id])).to.deep.eq({ + categories: [keys.indirectCategory.id], + definitionContainers: [], + }); + }); + }); + + describe("getAllContainedCategories", () => { + it("when definitionContainer contains nothing", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainer = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); + insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainer.id }); + + return { definitionContainer }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getAllContainedCategories(keys.definitionContainer.id)).to.deep.eq([]); + }); + + it("when definitionContainer contains definitionContainer that has categories", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + const definitionContainerChild = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer", modelId: definitionModelRoot.id }); + const definitionModelChild = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory", modelId: definitionModelChild.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + + return { definitionContainerRoot, category }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getAllContainedCategories(keys.definitionContainerRoot.id)).to.deep.eq([keys.category.id]); + }); + + it("when definitionContainer contains categories", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainer = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); + const definitionModel = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainer.id }); + const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory", modelId: definitionModel.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + + return { definitionContainer, category }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getAllContainedCategories(keys.definitionContainer.id)).to.deep.eq([keys.category.id]); + }); + + it("when definitionContainer contains categories and definitionContainers that contain categories", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + const directCategory = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory", modelId: definitionModelRoot.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: directCategory.id }); + + const definitionContainerChild = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer", modelId: definitionModelRoot.id }); + const definitionModelChild = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + const indirectCategory = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory", modelId: definitionModelChild.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: indirectCategory.id }); + + return { definitionContainerRoot, directCategory, indirectCategory }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + const result = await idsCache.getAllContainedCategories(keys.definitionContainerRoot.id); + const expectedResult = [keys.indirectCategory.id, keys.directCategory.id]; + expect(result.every((id) => expectedResult.includes(id))).to.be.true; + }); + }); + + describe("getInstanceKeyPaths", () => { + describe("from subCategory", () => { + it("when subcategory doesn't exist", async function () { + const { imodel } = await buildIModel(this, async (builder) => { + insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getInstanceKeyPaths({ subCategoryId: "0x123" })).to.deep.eq([]); + }); + + it("with category > subCategory hierarchy", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + + const subCategory = insertSubCategory({ builder, parentCategoryId: category.id, codeValue: "Test SpatialSubCategory" }); + return { subCategory, category }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getInstanceKeyPaths({ subCategoryId: keys.subCategory.id })).to.deep.eq([keys.category, keys.subCategory]); + }); + + it("with definitionContainer > category > subCategory hierarchy", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainer = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); + const definitionModel = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainer.id }); + const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory", modelId: definitionModel.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + + const subCategory = insertSubCategory({ builder, parentCategoryId: category.id, codeValue: "Test SpatialSubCategory", modelId: definitionModel.id }); + return { subCategory, category, definitionContainer }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getInstanceKeyPaths({ subCategoryId: keys.subCategory.id })).to.deep.eq([ + keys.definitionContainer, + keys.category, + keys.subCategory, + ]); + }); + + it("with definitionContainer > definitionContainer > category > subCategory hierarchy", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + const definitionContainerChild = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer", modelId: definitionModelRoot.id }); + const definitionModelChild = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory", modelId: definitionModelChild.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + + const subCategory = insertSubCategory({ + builder, + parentCategoryId: category.id, + codeValue: "Test SpatialSubCategory", + modelId: definitionModelChild.id, + }); + return { subCategory, category, definitionContainerChild, definitionContainerRoot }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getInstanceKeyPaths({ subCategoryId: keys.subCategory.id })).to.deep.eq([ + keys.definitionContainerRoot, + keys.definitionContainerChild, + keys.category, + keys.subCategory, + ]); + }); + }); + + describe("from category", () => { + it("when category doesn't exist", async function () { + const { imodel } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getInstanceKeyPaths({ categoryId: "0x123" })).to.deep.eq([]); + }); + + it("with only category in hierarchy", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + + return { category }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getInstanceKeyPaths({ categoryId: keys.category.id })).to.deep.eq([keys.category]); + }); + + it("with definitionContainer > category hierarchy", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainer = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); + const definitionModel = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainer.id }); + const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory", modelId: definitionModel.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + + return { category, definitionContainer }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getInstanceKeyPaths({ categoryId: keys.category.id })).to.deep.eq([keys.definitionContainer, keys.category]); + }); + + it("with definitionContainer > definitionContainer > category hierarchy", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + const definitionContainerChild = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer", modelId: definitionModelRoot.id }); + const definitionModelChild = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory", modelId: definitionModelChild.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + + return { category, definitionContainerChild, definitionContainerRoot }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getInstanceKeyPaths({ categoryId: keys.category.id })).to.deep.eq([ + keys.definitionContainerRoot, + keys.definitionContainerChild, + keys.category, + ]); + }); + }); + + describe("from definitionContainer", () => { + it("when definitionContainer doesn't exist", async function () { + const { imodel } = await buildIModel(this, async (builder) => { + insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getInstanceKeyPaths({ definitionContainerId: "0x123" })).to.deep.eq([]); + }); + + it("when only a single definitionContainer exists", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainer = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); + const definitionModel = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainer.id }); + const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory", modelId: definitionModel.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + + return { category, definitionContainer }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getInstanceKeyPaths({ definitionContainerId: keys.definitionContainer.id })).to.deep.eq([keys.definitionContainer]); + }); + + it("with definitionContainer > definitionContainer hierarchy", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + const definitionContainerChild = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer", modelId: definitionModelRoot.id }); + const definitionModelChild = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory", modelId: definitionModelChild.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + + return { category, definitionContainerChild, definitionContainerRoot }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getInstanceKeyPaths({ definitionContainerId: keys.definitionContainerChild.id })).to.deep.eq([ + keys.definitionContainerRoot, + keys.definitionContainerChild, + ]); + }); + }); + }); + + describe("getAllDefinitionContainersAndCategories", () => { + it("hierarchy without categories or definitionContainers", async function () { + const { imodel } = await buildIModel(this, async (builder) => { + insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getAllDefinitionContainersAndCategories()).to.deep.eq({ categories: [], definitionContainers: [] }); + }); + + it("with category and empty definitionContainer", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainer = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); + insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainer.id }); + const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + + return { category }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getAllDefinitionContainersAndCategories()).to.deep.eq({ + categories: [keys.category.id], + definitionContainers: [], + }); + }); + + it("with category and definitionContainers (that dont contain categories)", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + const definitionContainerChild = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer", modelId: definitionModelRoot.id }); + insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + + return { category }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getAllDefinitionContainersAndCategories()).to.deep.eq({ + categories: [keys.category.id], + definitionContainers: [], + }); + }); + + it("with definitionContainer that contains definitionContainer that contains categories", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + const definitionContainerChild = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer", modelId: definitionModelRoot.id }); + const definitionModelChild = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory", modelId: definitionModelChild.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + + return { definitionContainerRoot, definitionContainerChild, category }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + const result = await idsCache.getAllDefinitionContainersAndCategories(); + const expectedResult = { + categories: [keys.category.id], + definitionContainers: [keys.definitionContainerRoot.id, keys.definitionContainerChild.id], + }; + expect(result.categories).to.deep.eq(expectedResult.categories); + expect(result.definitionContainers.every((dc) => expectedResult.definitionContainers.includes(dc))).to.be.true; + }); + + it("with definitionContainer that contains category", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory", modelId: definitionModelRoot.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + + return { definitionContainerRoot, category }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getAllDefinitionContainersAndCategories()).to.deep.eq({ + categories: [keys.category.id], + definitionContainers: [keys.definitionContainerRoot.id], + }); + }); + + it("with definitionContainer that contains category and definitionContainer that doesn't contain category", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + const definitionContainerChild = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer", modelId: definitionModelRoot.id }); + insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory", modelId: definitionModelRoot.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + + return { definitionContainerRoot, category }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getAllDefinitionContainersAndCategories()).to.deep.eq({ + categories: [keys.category.id], + definitionContainers: [keys.definitionContainerRoot.id], + }); + }); + + it("with definitionContainer that contains categories and definitionContainers that contain categories", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + const directCategory = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory", modelId: definitionModelRoot.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: directCategory.id }); + + const definitionContainerChild = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer", modelId: definitionModelRoot.id }); + const definitionModelChild = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + const indirectCategory = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory", modelId: definitionModelChild.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: indirectCategory.id }); + + return { definitionContainerRoot, directCategory, definitionModelChild, indirectCategory }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + const result = await idsCache.getAllDefinitionContainersAndCategories(); + const expectedResult = { + categories: [keys.directCategory.id, keys.indirectCategory.id], + definitionContainers: [keys.definitionModelChild.id, keys.definitionContainerRoot.id], + }; + expect(result.categories.every((c) => expectedResult.categories.includes(c))).to.be.true; + expect(result.definitionContainers.every((dc) => expectedResult.definitionContainers.includes(dc))).to.be.true; + }); + }); + + describe("getRootDefinitionContainersAndCategories", () => { + it("hierarchy without categories or definitionContainers", async function () { + const { imodel } = await buildIModel(this, async (builder) => { + insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getRootDefinitionContainersAndCategories()).to.deep.eq({ categories: [], definitionContainers: [] }); + }); + + it("with category and definitionContainer that doesn't contain anything", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainer = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); + insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainer.id }); + const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + + return { category }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getRootDefinitionContainersAndCategories()).to.deep.eq({ + categories: [keys.category.id], + definitionContainers: [], + }); + }); + + it("with category and definitionContainers that contains definitionContainer that doesn't contain categories", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + const definitionContainerChild = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer", modelId: definitionModelRoot.id }); + insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + + return { category }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getRootDefinitionContainersAndCategories()).to.deep.eq({ + categories: [keys.category.id], + definitionContainers: [], + }); + }); + + it("with definitionContainer that contains definitionContainer that contains categories", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + const definitionContainerChild = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer", modelId: definitionModelRoot.id }); + const definitionModelChild = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory", modelId: definitionModelChild.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + + return { definitionContainerRoot }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getRootDefinitionContainersAndCategories()).to.deep.eq({ + categories: [], + definitionContainers: [keys.definitionContainerRoot.id], + }); + }); + + it("with definitionContainer that containts category", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory", modelId: definitionModelRoot.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + + return { definitionContainerRoot }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getRootDefinitionContainersAndCategories()).to.deep.eq({ + categories: [], + definitionContainers: [keys.definitionContainerRoot.id], + }); + }); + + it("with definitionContainers and categories", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + + const definitionContainerRootNoChildren = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainerNoChild" }); + insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRootNoChildren.id }); + + const definitionContainerRoot2 = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer2" }); + const definitionModelRoot2 = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot2.id }); + + const rootCategory1 = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: rootCategory1.id }); + const rootCategory2 = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory2" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: rootCategory2.id }); + + const childCategory = insertSpatialCategory({ builder, codeValue: "Test SpatialCategoryChild", modelId: definitionModelRoot.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: childCategory.id }); + const childCategory2 = insertSpatialCategory({ builder, codeValue: "Test SpatialCategoryChild2", modelId: definitionModelRoot2.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: childCategory2.id }); + + return { definitionContainerRoot, rootCategory1, definitionContainerRoot2, rootCategory2 }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + const result = await idsCache.getRootDefinitionContainersAndCategories(); + const expectedResult = { + categories: [keys.rootCategory1.id, keys.rootCategory2.id], + definitionContainers: [keys.definitionContainerRoot.id, keys.definitionContainerRoot2.id], + }; + expect(result.categories.every((c) => expectedResult.categories.includes(c))).to.be.true; + expect(result.definitionContainers.every((dc) => expectedResult.definitionContainers.includes(dc))).to.be.true; + }); + }); + + describe("getSubCategories", () => { + it("when category doesn't exist", async function () { + const { imodel } = await buildIModel(this, async (builder) => { + insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getSubCategories("0x123")).to.deep.eq([]); + }); + + it("when category has one subCategory", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + return { category }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + expect(await idsCache.getSubCategories(keys.category.id)).to.deep.eq([]); + }); + + it("when category has multiple subCategories", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + const subCategory = insertSubCategory({ builder, parentCategoryId: category.id, codeValue: "subc 1" }); + + return { subCategory, category }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + const result = await idsCache.getSubCategories(keys.category.id); + expect(result.includes(keys.subCategory.id)).to.be.true; + expect(result.length).to.be.eq(2); + }); + + it("when multiple categories have multiple subCategories", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + insertSubCategory({ builder, parentCategoryId: category.id, codeValue: "subc 1" }); + + const category2 = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory2" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category2.id }); + const subCategory2 = insertSubCategory({ builder, parentCategoryId: category2.id, codeValue: "subc 2" }); + + return { subCategory2, category2 }; + }); + const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); + const result = await idsCache.getSubCategories(keys.category2.id); + expect(result.includes(keys.subCategory2.id)).to.be.true; + expect(result.length).to.be.eq(2); + }); + }); +}); diff --git a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeNode.test.ts b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeNode.test.ts new file mode 100644 index 000000000..ca800e1b6 --- /dev/null +++ b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeNode.test.ts @@ -0,0 +1,49 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { CategoriesTreeNode } from "../../../../tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeNode.js"; + +describe("CategoriesTreeNode", () => { + const randomNode = { + extendedData: {}, + }; + const categoryNode = { + extendedData: { + isCategory: 1, + }, + }; + const subCategoryNode = { + extendedData: { + isSubCategory: 1, + }, + }; + const definitionContainerNode = { + extendedData: { + isDefinitionContainer: 1, + }, + }; + + it("isCategoryNode", () => { + expect(CategoriesTreeNode.isCategoryNode(randomNode)).to.be.false; + expect(CategoriesTreeNode.isCategoryNode(categoryNode)).to.be.true; + expect(CategoriesTreeNode.isCategoryNode(subCategoryNode)).to.be.false; + expect(CategoriesTreeNode.isCategoryNode(definitionContainerNode)).to.be.false; + }); + + it("isSubCategoryNode", () => { + expect(CategoriesTreeNode.isSubCategoryNode(randomNode)).to.be.false; + expect(CategoriesTreeNode.isSubCategoryNode(categoryNode)).to.be.false; + expect(CategoriesTreeNode.isSubCategoryNode(subCategoryNode)).to.be.true; + expect(CategoriesTreeNode.isSubCategoryNode(definitionContainerNode)).to.be.false; + }); + + it("isDefinitionContainerNode", () => { + expect(CategoriesTreeNode.isDefinitionContainerNode(randomNode)).to.be.false; + expect(CategoriesTreeNode.isDefinitionContainerNode(categoryNode)).to.be.false; + expect(CategoriesTreeNode.isDefinitionContainerNode(subCategoryNode)).to.be.false; + expect(CategoriesTreeNode.isDefinitionContainerNode(definitionContainerNode)).to.be.true; + }); +}); diff --git a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts new file mode 100644 index 000000000..fb80ec1c2 --- /dev/null +++ b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts @@ -0,0 +1,1469 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ + +import { join } from "path"; +import { IModelReadRpcInterface, SnapshotIModelRpcInterface } from "@itwin/core-common"; +import { IModelApp, NoRenderApp } from "@itwin/core-frontend"; +import { ECSchemaRpcInterface } from "@itwin/ecschema-rpcinterface-common"; +import { ECSchemaRpcImpl } from "@itwin/ecschema-rpcinterface-impl"; +import { PresentationRpcInterface } from "@itwin/presentation-common"; +import { createIModelHierarchyProvider } from "@itwin/presentation-hierarchies"; +import { HierarchyCacheMode, initialize as initializePresentationTesting, terminate as terminatePresentationTesting } from "@itwin/presentation-testing"; +import { CategoriesTreeDefinition } from "../../../../tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.js"; +import { CategoriesTreeIdsCache } from "../../../../tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.js"; +import { CategoriesVisibilityHandler } from "../../../../tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.js"; +import { + buildIModel, + insertDefinitionContainer, + insertPhysicalElement, + insertPhysicalModelWithPartition, + insertSpatialCategory, + insertSubCategory, + insertSubModel, +} from "../../../IModelUtils.js"; +import { TestUtils } from "../../../TestUtils.js"; +import { createIModelAccess } from "../../Common.js"; +import { createCategoryHierarchyNode, createDefinitionContainerHierarchyNode, createSubCategoryHierarchyNode, ViewportMock } from "./Utils.js"; +import { validateHierarchyVisibility, VisibilityExpectations } from "./VisibilityValidation.js"; + +import type { IModelConnection } from "@itwin/core-frontend"; +import type { HierarchyNodeIdentifiersPath } from "@itwin/presentation-hierarchies"; + +describe("CategoriesVisibilityHandler", () => { + before(async () => { + await NoRenderApp.startup(); + await TestUtils.initialize(); + await initializePresentationTesting({ + backendProps: { + caching: { + hierarchies: { + mode: HierarchyCacheMode.Memory, + }, + }, + }, + testOutputDir: join(__dirname, "output"), + backendHostProps: { + cacheDir: join(__dirname, "cache"), + }, + rpcs: [SnapshotIModelRpcInterface, IModelReadRpcInterface, PresentationRpcInterface, ECSchemaRpcInterface], + }); + // eslint-disable-next-line @itwin/no-internal + ECSchemaRpcImpl.register(); + }); + + after(async () => { + TestUtils.terminate(); + await IModelApp.shutdown(); + await terminatePresentationTesting(); + }); + + async function createCommonProps(imodel: IModelConnection) { + const imodelAccess = createIModelAccess(imodel); + const idsCache = new CategoriesTreeIdsCache(imodelAccess, "3d"); + + const viewportMock = new ViewportMock(idsCache); + const viewport = await viewportMock.createViewportStub(); + return { + imodelAccess, + viewport, + idsCache, + }; + } + + function createProvider(props: { + idsCache: CategoriesTreeIdsCache; + imodelAccess: ReturnType; + filterPaths?: HierarchyNodeIdentifiersPath[]; + }) { + return createIModelHierarchyProvider({ + hierarchyDefinition: new CategoriesTreeDefinition({ ...props, viewType: "3d" }), + imodelAccess: props.imodelAccess, + ...(props.filterPaths ? { filtering: { paths: props.filterPaths } } : undefined), + }); + } + + async function createVisibilityTestData({ imodel }: { imodel: IModelConnection }) { + const commonProps = await createCommonProps(imodel); + const handler = new CategoriesVisibilityHandler(commonProps); + const provider = createProvider({ ...commonProps }); + return { + handler, + provider, + ...commonProps, + [Symbol.dispose]() { + handler[Symbol.dispose](); + }, + }; + } + + it("by default everything is hidden", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer" }); + const definitionModel = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainer.id }); + + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory", modelId: definitionModel.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + const subCategory = insertSubCategory({ builder, parentCategoryId: category.id, codeValue: "subCategory", modelId: definitionModel.id }); + return { definitionContainer, category, subCategory }; + }); + const nodesToExpect = [keys.category.id, keys.definitionContainer.id, keys.subCategory.id]; + using visibilityTestData = await createVisibilityTestData({ imodel }); + const { handler, provider, viewport } = visibilityTestData; + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: VisibilityExpectations.all("hidden"), + nodesToExpect, + }); + ViewportMock.validateViewsCalls(viewport, [keys.category.id], [keys.subCategory.id]); + }); + + describe("enabling visibility", () => { + describe("definitionContainers", () => { + it("showing definitionContainer makes it and all of its contained elements visible", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + + const definitionContainerChild = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerChild", modelId: definitionModelRoot.id }); + const definitionModelChild = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + + const directCategory = insertSpatialCategory({ builder, codeValue: "SpatialCategory1", modelId: definitionModelRoot.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: directCategory.id }); + const indirectCategory = insertSpatialCategory({ builder, codeValue: "SpatialCategory2", modelId: definitionModelChild.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: indirectCategory.id }); + const indirectSubCategory = insertSubCategory({ + builder, + parentCategoryId: indirectCategory.id, + codeValue: "subCategory", + modelId: definitionModelChild.id, + }); + return { definitionContainerRoot, definitionContainerChild, directCategory, indirectCategory, indirectSubCategory }; + }); + + const nodesToExpect = [ + keys.definitionContainerRoot.id, + keys.definitionContainerChild.id, + keys.directCategory.id, + keys.indirectCategory.id, + keys.indirectSubCategory.id, + ]; + using visibilityTestData = await createVisibilityTestData({ imodel }); + const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerRoot.id), true); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: VisibilityExpectations.all("visible"), + nodesToExpect, + }); + ViewportMock.validateViewsCalls(viewport, [keys.directCategory.id, keys.indirectCategory.id], [keys.indirectSubCategory.id]); + ViewportMock.validateChangesCalls( + viewport, + [{ categoriesToChange: [keys.directCategory.id, keys.indirectCategory.id], isVisible: true, enableAllSubCategories: true }], + [], + ); + }); + + it("showing definitionContainer makes it and all of its contained elements visible and doesn't affect non contained definitionContainers", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + const definitionContainerChild = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerChild", modelId: definitionModelRoot.id }); + const definitionModelChild = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + const indirectCategory = insertSpatialCategory({ builder, codeValue: "SpatialCategory", modelId: definitionModelChild.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: indirectCategory.id }); + const indirectSubCategory = insertSubCategory({ + builder, + parentCategoryId: indirectCategory.id, + codeValue: "subCategory", + modelId: definitionModelChild.id, + }); + + const definitionContainerRoot2 = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot2" }); + const definitionModelRoot2 = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot2.id }); + const category2 = insertSpatialCategory({ builder, codeValue: "SpatialCategory2", modelId: definitionModelRoot2.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category2.id }); + const subCategory2 = insertSubCategory({ builder, parentCategoryId: category2.id, codeValue: "subCategory2", modelId: definitionModelRoot2.id }); + + return { + definitionContainerRoot, + definitionContainerChild, + indirectCategory, + indirectSubCategory, + definitionContainerRoot2, + category2, + subCategory2, + }; + }); + + const nodesToExpect = [ + keys.definitionContainerRoot.id, + keys.definitionContainerChild.id, + keys.definitionContainerRoot2.id, + keys.category2.id, + keys.indirectCategory.id, + keys.indirectSubCategory.id, + keys.subCategory2.id, + ]; + using visibilityTestData = await createVisibilityTestData({ imodel }); + const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerRoot.id), true); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: { + category: (categoryId) => (categoryId === keys.category2.id ? "hidden" : "visible"), + subCategory: (parentCategoryId) => (parentCategoryId === keys.category2.id ? "hidden" : "visible"), + definitionContainer: (definitionContainerId) => (definitionContainerId === keys.definitionContainerRoot2.id ? "hidden" : "visible"), + }, + nodesToExpect, + }); + ViewportMock.validateViewsCalls(viewport, [keys.category2.id, keys.indirectCategory.id], [keys.subCategory2.id, keys.indirectSubCategory.id]); + ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.indirectCategory.id], isVisible: true, enableAllSubCategories: true }], []); + }); + + it("showing definitionContainer makes it and all of its contained elements visible, and parent container partially visible if it has more direct child categories", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + + const definitionContainerChild = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerChild", modelId: definitionModelRoot.id }); + const definitionModelChild = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + + const directCategory = insertSpatialCategory({ builder, codeValue: "SpatialCategory1", modelId: definitionModelRoot.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: directCategory.id }); + const indirectCategory = insertSpatialCategory({ builder, codeValue: "SpatialCategory2", modelId: definitionModelChild.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: indirectCategory.id }); + return { definitionContainerRoot, definitionContainerChild, directCategory, indirectCategory }; + }); + + const nodesToExpect = [keys.definitionContainerRoot.id, keys.definitionContainerChild.id, keys.directCategory.id, keys.indirectCategory.id]; + + using visibilityTestData = await createVisibilityTestData({ imodel }); + const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerChild.id), true); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: { + category: (categoryId) => (categoryId === keys.directCategory.id ? "hidden" : "visible"), + definitionContainer: (definitionContainerId) => (definitionContainerId === keys.definitionContainerRoot.id ? "partial" : "visible"), + }, + nodesToExpect, + }); + ViewportMock.validateViewsCalls(viewport, [keys.directCategory.id, keys.indirectCategory.id], []); + ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.indirectCategory.id], isVisible: true, enableAllSubCategories: true }], []); + }); + + it("showing definitionContainer makes it and all of its contained elements visible, and parent container partially visible if it has more definitionContainers", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + + const definitionContainerChild = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerChild", modelId: definitionModelRoot.id }); + const definitionModelChild = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + const indirectCategory = insertSpatialCategory({ builder, codeValue: "SpatialCategory", modelId: definitionModelChild.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: indirectCategory.id }); + + const definitionContainerChild2 = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerChild2", modelId: definitionModelRoot.id }); + const definitionModelChild2 = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild2.id }); + const indirectCategory2 = insertSpatialCategory({ builder, codeValue: "SpatialCategory2", modelId: definitionModelChild2.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: indirectCategory2.id }); + return { definitionContainerRoot, definitionContainerChild, indirectCategory2, indirectCategory, definitionContainerChild2 }; + }); + + const nodesToExpect = [ + keys.definitionContainerRoot.id, + keys.definitionContainerChild.id, + keys.indirectCategory.id, + keys.definitionContainerChild2.id, + keys.indirectCategory2.id, + ]; + + using visibilityTestData = await createVisibilityTestData({ imodel }); + const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerChild.id), true); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: { + category: (categoryId) => (categoryId === keys.indirectCategory2.id ? "hidden" : "visible"), + definitionContainer: (definitionContainerId) => { + if (definitionContainerId === keys.definitionContainerRoot.id) { + return "partial"; + } + if (definitionContainerId === keys.definitionContainerChild2.id) { + return "hidden"; + } + return "visible"; + }, + }, + nodesToExpect, + }); + + ViewportMock.validateViewsCalls(viewport, [keys.indirectCategory2.id, keys.indirectCategory.id], []); + ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.indirectCategory.id], isVisible: true, enableAllSubCategories: true }], []); + }); + + it("showing child definitionContainer makes it, all of its contained elements and its parent definitionContainer visible", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + const definitionContainerChild = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerChild", modelId: definitionModelRoot.id }); + const definitionModelChild = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + const indirectCategory = insertSpatialCategory({ builder, codeValue: "SpatialCategory", modelId: definitionModelChild.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: indirectCategory.id }); + const indirectSubCategory = insertSubCategory({ + builder, + parentCategoryId: indirectCategory.id, + codeValue: "subCategory", + modelId: definitionModelChild.id, + }); + + return { definitionContainerRoot, definitionContainerChild, indirectCategory, indirectSubCategory }; + }); + + const nodesToExpect = [keys.definitionContainerRoot.id, keys.definitionContainerChild.id, keys.indirectCategory.id, keys.indirectSubCategory.id]; + + using visibilityTestData = await createVisibilityTestData({ imodel }); + const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerChild.id), true); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: VisibilityExpectations.all("visible"), + nodesToExpect, + }); + + ViewportMock.validateViewsCalls(viewport, [keys.indirectCategory.id, keys.indirectCategory.id], [keys.indirectSubCategory.id]); + ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.indirectCategory.id], isVisible: true, enableAllSubCategories: true }], []); + }); + }); + + describe("categories", () => { + it("showing category makes it and all of its subCategories visible", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + const subCategory = insertSubCategory({ + builder, + parentCategoryId: category.id, + codeValue: "subCategory", + }); + return { category, subCategory }; + }); + + const nodesToExpect = [keys.category.id, keys.subCategory.id]; + using visibilityTestData = await createVisibilityTestData({ imodel }); + const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), true); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: VisibilityExpectations.all("visible"), + nodesToExpect, + }); + ViewportMock.validateViewsCalls(viewport, [keys.category.id], [keys.subCategory.id]); + ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: true }], []); + }); + + it("showing category makes it, all of its contained subCategories visible and doesn't affect other categories", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + const subCategory = insertSubCategory({ + builder, + parentCategoryId: category.id, + codeValue: "subCategory", + }); + const category2 = insertSpatialCategory({ builder, codeValue: "SpatialCategory2" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category2.id }); + const subCategory2 = insertSubCategory({ + builder, + parentCategoryId: category2.id, + codeValue: "subCategory2", + }); + + return { category, category2, subCategory, subCategory2 }; + }); + + const nodesToExpect = [keys.category.id, keys.category2.id, keys.subCategory.id, keys.subCategory2.id]; + using visibilityTestData = await createVisibilityTestData({ imodel }); + const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), true); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: { + category: (categoryId) => (categoryId === keys.category2.id ? "hidden" : "visible"), + subCategory: (parentCategoryId) => (parentCategoryId === keys.category2.id ? "hidden" : "visible"), + }, + nodesToExpect, + }); + ViewportMock.validateViewsCalls(viewport, [keys.category.id, keys.category2.id], [keys.subCategory.id, keys.subCategory2.id]); + ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: true }], []); + }); + + it("showing category makes it, all of its contained subCategories visible and doesn't affect non related definitionContainer", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + const subCategory = insertSubCategory({ + builder, + parentCategoryId: category.id, + codeValue: "subCategory", + }); + + const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); + const definitionModel = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainer.id }); + const category2 = insertSpatialCategory({ builder, codeValue: "SpatialCategory2", modelId: definitionModel.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category2.id }); + const subCategory2 = insertSubCategory({ + builder, + parentCategoryId: category2.id, + codeValue: "subCategory2", + modelId: definitionContainer.id, + }); + + return { definitionContainer, category, category2, subCategory, subCategory2 }; + }); + + const nodesToExpect = [keys.definitionContainer.id, keys.category.id, keys.category2.id, keys.subCategory.id, keys.subCategory2.id]; + using visibilityTestData = await createVisibilityTestData({ imodel }); + const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), true); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: { + category: (categoryId) => (categoryId === keys.category2.id ? "hidden" : "visible"), + subCategory: (parentCategoryId) => (parentCategoryId === keys.category2.id ? "hidden" : "visible"), + definitionContainer: () => "hidden", + }, + nodesToExpect, + }); + ViewportMock.validateViewsCalls(viewport, [keys.category.id, keys.category2.id], [keys.subCategory.id, keys.subCategory2.id]); + ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: true }], []); + }); + + it("showing category makes it and all of its subcategories visible, and parent container partially visible if it has more direct child categories", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory", modelId: definitionModelRoot.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + const subCategory = insertSubCategory({ + builder, + parentCategoryId: category.id, + codeValue: "subCategory", + modelId: definitionModelRoot.id, + }); + const category2 = insertSpatialCategory({ builder, codeValue: "SpatialCategory2", modelId: definitionModelRoot.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category2.id }); + const subCategory2 = insertSubCategory({ + builder, + parentCategoryId: category2.id, + codeValue: "subCategory2", + modelId: definitionModelRoot.id, + }); + return { definitionContainerRoot, category, category2, subCategory, subCategory2 }; + }); + + const nodesToExpect = [keys.definitionContainerRoot.id, keys.category.id, keys.category2.id, keys.subCategory.id, keys.subCategory2.id]; + + using visibilityTestData = await createVisibilityTestData({ imodel }); + const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), true); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: { + category: (categoryId) => (categoryId === keys.category2.id ? "hidden" : "visible"), + subCategory: (parentCategoryId) => (parentCategoryId === keys.category2.id ? "hidden" : "visible"), + definitionContainer: () => "partial", + }, + nodesToExpect, + }); + ViewportMock.validateViewsCalls(viewport, [keys.category.id, keys.category2.id], [keys.subCategory.id, keys.subCategory2.id]); + ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: true }], []); + }); + + it("showing category makes it and all of its subCategories visible, and parent container partially visible if it has more definitionContainers", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + + const definitionContainerChild = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerChild", modelId: definitionModelRoot.id }); + const definitionModelChild = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + const indirectCategory = insertSpatialCategory({ builder, codeValue: "SpatialCategory", modelId: definitionModelChild.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: indirectCategory.id }); + + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory2", modelId: definitionModelRoot.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + const subCategory = insertSubCategory({ + builder, + parentCategoryId: category.id, + codeValue: "subCategory", + modelId: definitionModelRoot.id, + }); + return { definitionContainerRoot, definitionContainerChild, category, indirectCategory, subCategory }; + }); + + const nodesToExpect = [ + keys.definitionContainerRoot.id, + keys.definitionContainerChild.id, + keys.indirectCategory.id, + keys.category.id, + keys.subCategory.id, + ]; + + using visibilityTestData = await createVisibilityTestData({ imodel }); + const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), true); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: { + category: (categoryId) => (categoryId === keys.indirectCategory.id ? "hidden" : "visible"), + subCategory: () => "visible", + definitionContainer: (definitionContainerId) => { + if (definitionContainerId === keys.definitionContainerRoot.id) { + return "partial"; + } + return "hidden"; + }, + }, + nodesToExpect, + }); + ViewportMock.validateViewsCalls(viewport, [keys.category.id, keys.indirectCategory.id], [keys.subCategory.id]); + ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: true }], []); + }); + }); + + describe("subCategories", () => { + it("showing subCategory makes it visible and its parent category partially visible, and doesn't affect other subCategories", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + const subCategory = insertSubCategory({ + builder, + parentCategoryId: category.id, + codeValue: "subCategory", + }); + const subCategory2 = insertSubCategory({ + builder, + parentCategoryId: category.id, + codeValue: "subCategory2", + }); + return { category, subCategory, subCategory2 }; + }); + + const nodesToExpect = [keys.category.id, keys.subCategory.id, keys.subCategory2.id]; + using visibilityTestData = await createVisibilityTestData({ imodel }); + const { handler, provider, viewport } = visibilityTestData; + + await handler.changeVisibility(createSubCategoryHierarchyNode(keys.subCategory.id, keys.category.id), true); + + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: { + category: () => "partial", + subCategory: (_, subCategoryId) => (subCategoryId === keys.subCategory.id ? "visible" : "hidden"), + }, + nodesToExpect, + }); + ViewportMock.validateViewsCalls(viewport, [keys.category.id], [keys.subCategory.id, keys.subCategory2.id]); + ViewportMock.validateChangesCalls( + viewport, + [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: false }], + [{ subCategoryId: keys.subCategory.id, isVisible: true }], + ); + }); + + it("showing subCategory makes it visible and its parent category partially visible, and doesn't affect other categories", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + const subCategory = insertSubCategory({ + builder, + parentCategoryId: category.id, + codeValue: "subCategory", + }); + const category2 = insertSpatialCategory({ builder, codeValue: "SpatialCategory2" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category2.id }); + return { category, subCategory, category2 }; + }); + + const nodesToExpect = [keys.category.id, keys.subCategory.id, keys.category2.id]; + using visibilityTestData = await createVisibilityTestData({ imodel }); + const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createSubCategoryHierarchyNode(keys.subCategory.id, keys.category.id), true); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: { + category: (categoryId) => (categoryId === keys.category.id ? "partial" : "hidden"), + subCategory: (_, subCategoryId) => (subCategoryId === keys.subCategory.id ? "visible" : "hidden"), + }, + nodesToExpect, + }); + ViewportMock.validateViewsCalls(viewport, [keys.category.id, keys.category2.id], [keys.subCategory.id]); + ViewportMock.validateChangesCalls( + viewport, + [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: false }], + [{ subCategoryId: keys.subCategory.id, isVisible: true }], + ); + }); + + it("showing subCategory makes it visible and parents partially visible", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory", modelId: definitionModelRoot.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + const subCategory = insertSubCategory({ + builder, + parentCategoryId: category.id, + codeValue: "subCategory", + modelId: definitionModelRoot.id, + }); + return { category, subCategory, definitionContainerRoot }; + }); + + const nodesToExpect = [keys.category.id, keys.subCategory.id, keys.definitionContainerRoot.id]; + using visibilityTestData = await createVisibilityTestData({ imodel }); + const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createSubCategoryHierarchyNode(keys.subCategory.id, keys.category.id), true); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: { + category: () => "partial", + subCategory: (_, subCategoryId) => (subCategoryId === keys.subCategory.id ? "visible" : "hidden"), + definitionContainer: () => "partial", + }, + nodesToExpect, + }); + ViewportMock.validateViewsCalls(viewport, [keys.category.id], [keys.subCategory.id]); + ViewportMock.validateChangesCalls( + viewport, + [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: false }], + [{ subCategoryId: keys.subCategory.id, isVisible: true }], + ); + }); + + it("showing subCategory makes it visible and doesn't affect non related definitionContainers", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + const subCategory = insertSubCategory({ + builder, + parentCategoryId: category.id, + codeValue: "subCategory", + }); + const categoryOfDefinitionContainer = insertSpatialCategory({ builder, codeValue: "SpatialCategory2", modelId: definitionModelRoot.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: categoryOfDefinitionContainer.id }); + const subCategoryOfDefinitionContainer = insertSubCategory({ + builder, + parentCategoryId: categoryOfDefinitionContainer.id, + codeValue: "subCategory2", + modelId: definitionModelRoot.id, + }); + return { category, subCategory, definitionContainerRoot, categoryOfDefinitionContainer, subCategoryOfDefinitionContainer }; + }); + + const nodesToExpect = [ + keys.category.id, + keys.subCategory.id, + keys.definitionContainerRoot.id, + keys.categoryOfDefinitionContainer.id, + keys.subCategoryOfDefinitionContainer.id, + ]; + using visibilityTestData = await createVisibilityTestData({ imodel }); + const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createSubCategoryHierarchyNode(keys.subCategory.id, keys.category.id), true); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: { + category: (categoryId) => (categoryId === keys.category.id ? "partial" : "hidden"), + subCategory: (_, subCategoryId) => (subCategoryId === keys.subCategory.id ? "visible" : "hidden"), + definitionContainer: () => "hidden", + }, + nodesToExpect, + }); + ViewportMock.validateViewsCalls( + viewport, + [keys.category.id, keys.categoryOfDefinitionContainer.id], + [keys.subCategory.id, keys.subCategoryOfDefinitionContainer.id], + ); + ViewportMock.validateChangesCalls( + viewport, + [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: false }], + [{ subCategoryId: keys.subCategory.id, isVisible: true }], + ); + }); + }); + }); + + describe("disabling visibility", () => { + describe("definitionContainers", () => { + it("hiding definitionContainer makes it and all of its contained elements hidden", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + + const definitionContainerChild = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerChild", modelId: definitionModelRoot.id }); + const definitionModelChild = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + + const directCategory = insertSpatialCategory({ builder, codeValue: "SpatialCategory1", modelId: definitionModelRoot.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: directCategory.id }); + const indirectCategory = insertSpatialCategory({ builder, codeValue: "SpatialCategory2", modelId: definitionModelChild.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: indirectCategory.id }); + const indirectSubCategory = insertSubCategory({ + builder, + parentCategoryId: indirectCategory.id, + codeValue: "subCategory", + modelId: definitionModelChild.id, + }); + return { definitionContainerRoot, definitionContainerChild, directCategory, indirectCategory, indirectSubCategory }; + }); + + const nodesToExpect = [ + keys.definitionContainerRoot.id, + keys.definitionContainerChild.id, + keys.directCategory.id, + keys.indirectCategory.id, + keys.indirectSubCategory.id, + ]; + using visibilityTestData = await createVisibilityTestData({ imodel }); + const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerRoot.id), true); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: VisibilityExpectations.all("visible"), + nodesToExpect, + }); + await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerRoot.id), false); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: VisibilityExpectations.all("hidden"), + nodesToExpect, + }); + ViewportMock.validateChangesCalls( + viewport, + [{ categoriesToChange: [keys.directCategory.id, keys.indirectCategory.id], isVisible: false, enableAllSubCategories: false }], + [], + ); + }); + + it("hiding definitionContainer makes it and all of its contained elements hidden and doesn't affect non contained definitionContainers", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + const definitionContainerChild = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerChild", modelId: definitionModelRoot.id }); + const definitionModelChild = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + const indirectCategory = insertSpatialCategory({ builder, codeValue: "SpatialCategory", modelId: definitionModelChild.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: indirectCategory.id }); + const indirectSubCategory = insertSubCategory({ + builder, + parentCategoryId: indirectCategory.id, + codeValue: "subCategory", + modelId: definitionModelChild.id, + }); + + const definitionContainerRoot2 = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot2" }); + const definitionModelRoot2 = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot2.id }); + const category2 = insertSpatialCategory({ builder, codeValue: "SpatialCategory2", modelId: definitionModelRoot2.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category2.id }); + const subCategory2 = insertSubCategory({ builder, parentCategoryId: category2.id, codeValue: "subCategory2", modelId: definitionModelRoot2.id }); + + return { + definitionContainerRoot, + definitionContainerChild, + indirectCategory, + indirectSubCategory, + definitionContainerRoot2, + category2, + subCategory2, + }; + }); + + const nodesToExpect = [ + keys.definitionContainerRoot.id, + keys.definitionContainerChild.id, + keys.definitionContainerRoot2.id, + keys.category2.id, + keys.indirectCategory.id, + keys.indirectSubCategory.id, + keys.subCategory2.id, + ]; + using visibilityTestData = await createVisibilityTestData({ imodel }); + const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerRoot.id), true); + await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerRoot2.id), true); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: VisibilityExpectations.all("visible"), + nodesToExpect, + }); + await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerRoot.id), false); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: { + category: (categoryId) => (categoryId === keys.indirectCategory.id ? "hidden" : "visible"), + subCategory: (parentCategoryId) => (parentCategoryId === keys.indirectCategory.id ? "hidden" : "visible"), + definitionContainer: (definitionContainerId) => (definitionContainerId !== keys.definitionContainerRoot2.id ? "hidden" : "visible"), + }, + nodesToExpect, + }); + ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.indirectCategory.id], isVisible: false, enableAllSubCategories: false }], []); + }); + + it("hiding definitionContainer makes it and all of its contained elements hidden, and parent container partially visible if it has more direct child categories", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + + const definitionContainerChild = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerChild", modelId: definitionModelRoot.id }); + const definitionModelChild = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + + const directCategory = insertSpatialCategory({ builder, codeValue: "SpatialCategory1", modelId: definitionModelRoot.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: directCategory.id }); + const indirectCategory = insertSpatialCategory({ builder, codeValue: "SpatialCategory2", modelId: definitionModelChild.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: indirectCategory.id }); + return { definitionContainerRoot, definitionContainerChild, directCategory, indirectCategory }; + }); + + const nodesToExpect = [keys.definitionContainerRoot.id, keys.definitionContainerChild.id, keys.directCategory.id, keys.indirectCategory.id]; + + using visibilityTestData = await createVisibilityTestData({ imodel }); + const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerRoot.id), true); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: VisibilityExpectations.all("visible"), + nodesToExpect, + }); + + await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerChild.id), false); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: { + category: (categoryId) => (categoryId === keys.indirectCategory.id ? "hidden" : "visible"), + definitionContainer: (definitionContainerId) => (definitionContainerId === keys.definitionContainerRoot.id ? "partial" : "hidden"), + }, + nodesToExpect, + }); + ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.indirectCategory.id], isVisible: false, enableAllSubCategories: false }], []); + }); + + it("hiding definitionContainer makes it and all of its contained elements hidden, and parent container partially visible if it has more definitionContainers", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + + const definitionContainerChild = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerChild", modelId: definitionModelRoot.id }); + const definitionModelChild = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + const indirectCategory = insertSpatialCategory({ builder, codeValue: "SpatialCategory", modelId: definitionModelChild.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: indirectCategory.id }); + + const definitionContainerChild2 = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerChild2", modelId: definitionModelRoot.id }); + const definitionModelChild2 = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild2.id }); + const indirectCategory2 = insertSpatialCategory({ builder, codeValue: "SpatialCategory2", modelId: definitionModelChild2.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: indirectCategory2.id }); + return { definitionContainerRoot, definitionContainerChild, indirectCategory2, indirectCategory, definitionContainerChild2 }; + }); + + const nodesToExpect = [ + keys.definitionContainerRoot.id, + keys.definitionContainerChild.id, + keys.indirectCategory.id, + keys.definitionContainerChild2.id, + keys.indirectCategory2.id, + ]; + + using visibilityTestData = await createVisibilityTestData({ imodel }); + const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerRoot.id), true); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: VisibilityExpectations.all("visible"), + nodesToExpect, + }); + + await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerChild.id), false); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: { + category: (categoryId) => (categoryId === keys.indirectCategory.id ? "hidden" : "visible"), + definitionContainer: (definitionContainerId) => { + if (definitionContainerId === keys.definitionContainerRoot.id) { + return "partial"; + } + if (definitionContainerId === keys.definitionContainerChild.id) { + return "hidden"; + } + return "visible"; + }, + }, + nodesToExpect, + }); + ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.indirectCategory.id], isVisible: false, enableAllSubCategories: false }], []); + }); + + it("hiding child definitionContainer makes it, all of its contained elements and its parent definitionContainer hidden", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + const definitionContainerChild = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerChild", modelId: definitionModelRoot.id }); + const definitionModelChild = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + const indirectCategory = insertSpatialCategory({ builder, codeValue: "SpatialCategory", modelId: definitionModelChild.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: indirectCategory.id }); + const indirectSubCategory = insertSubCategory({ + builder, + parentCategoryId: indirectCategory.id, + codeValue: "subCategory", + modelId: definitionModelChild.id, + }); + + return { definitionContainerRoot, definitionContainerChild, indirectCategory, indirectSubCategory }; + }); + + const nodesToExpect = [keys.definitionContainerRoot.id, keys.definitionContainerChild.id, keys.indirectCategory.id, keys.indirectSubCategory.id]; + + using visibilityTestData = await createVisibilityTestData({ imodel }); + const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerChild.id), true); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: VisibilityExpectations.all("visible"), + nodesToExpect, + }); + + await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerChild.id), false); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: VisibilityExpectations.all("hidden"), + nodesToExpect, + }); + ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.indirectCategory.id], isVisible: false, enableAllSubCategories: false }], []); + }); + }); + + describe("categories", () => { + it("hiding category makes it and all of its subCategories hidden", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + const subCategory = insertSubCategory({ + builder, + parentCategoryId: category.id, + codeValue: "subCategory", + }); + return { category, subCategory }; + }); + + const nodesToExpect = [keys.category.id, keys.subCategory.id]; + using visibilityTestData = await createVisibilityTestData({ imodel }); + const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), true); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: VisibilityExpectations.all("visible"), + nodesToExpect, + }); + + await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), false); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: VisibilityExpectations.all("hidden"), + nodesToExpect, + }); + ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.category.id], isVisible: false, enableAllSubCategories: false }], []); + }); + + it("hiding category makes it, all of its contained subCategories hidden and doesn't affect other categories", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + const subCategory = insertSubCategory({ + builder, + parentCategoryId: category.id, + codeValue: "subCategory", + }); + const category2 = insertSpatialCategory({ builder, codeValue: "SpatialCategory2" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category2.id }); + const subCategory2 = insertSubCategory({ + builder, + parentCategoryId: category2.id, + codeValue: "subCategory2", + }); + + return { category, category2, subCategory, subCategory2 }; + }); + + const nodesToExpect = [keys.category.id, keys.category2.id, keys.subCategory.id, keys.subCategory2.id]; + using visibilityTestData = await createVisibilityTestData({ imodel }); + const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), true); + await handler.changeVisibility(createCategoryHierarchyNode(keys.category2.id), true); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: VisibilityExpectations.all("visible"), + nodesToExpect, + }); + + await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), false); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: { + category: (categoryId) => (categoryId === keys.category.id ? "hidden" : "visible"), + subCategory: (parentCategoryId) => (parentCategoryId === keys.category.id ? "hidden" : "visible"), + }, + nodesToExpect, + }); + ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.category.id], isVisible: false, enableAllSubCategories: false }], []); + }); + + it("hiding category makes it, all of its contained subCategories hidden and doesn't affect non related definitionContainer", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + const subCategory = insertSubCategory({ + builder, + parentCategoryId: category.id, + codeValue: "subCategory", + }); + + const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); + const definitionModel = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainer.id }); + const category2 = insertSpatialCategory({ builder, codeValue: "SpatialCategory2", modelId: definitionModel.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category2.id }); + const subCategory2 = insertSubCategory({ + builder, + parentCategoryId: category2.id, + codeValue: "subCategory2", + modelId: definitionContainer.id, + }); + + return { definitionContainer, category, category2, subCategory, subCategory2 }; + }); + + const nodesToExpect = [keys.definitionContainer.id, keys.category.id, keys.category2.id, keys.subCategory.id, keys.subCategory2.id]; + using visibilityTestData = await createVisibilityTestData({ imodel }); + const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), true); + await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainer.id), true); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: VisibilityExpectations.all("visible"), + nodesToExpect, + }); + + await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), false); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: { + category: (categoryId) => (categoryId === keys.category.id ? "hidden" : "visible"), + subCategory: (parentCategoryId) => (parentCategoryId === keys.category.id ? "hidden" : "visible"), + definitionContainer: () => "visible", + }, + nodesToExpect, + }); + ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.category.id], isVisible: false, enableAllSubCategories: false }], []); + }); + + it("hiding category makes it and all of its subcategories hidden, and parent container partially visible if it has more direct child categories", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory", modelId: definitionModelRoot.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + const subCategory = insertSubCategory({ + builder, + parentCategoryId: category.id, + codeValue: "subCategory", + modelId: definitionModelRoot.id, + }); + const category2 = insertSpatialCategory({ builder, codeValue: "SpatialCategory2", modelId: definitionModelRoot.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category2.id }); + const subCategory2 = insertSubCategory({ + builder, + parentCategoryId: category2.id, + codeValue: "subCategory2", + modelId: definitionModelRoot.id, + }); + return { definitionContainerRoot, category, category2, subCategory, subCategory2 }; + }); + + const nodesToExpect = [keys.definitionContainerRoot.id, keys.category.id, keys.category2.id, keys.subCategory.id, keys.subCategory2.id]; + + using visibilityTestData = await createVisibilityTestData({ imodel }); + const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerRoot.id), true); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: VisibilityExpectations.all("visible"), + nodesToExpect, + }); + + await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), false); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: { + category: (categoryId) => (categoryId === keys.category.id ? "hidden" : "visible"), + subCategory: (parentCategoryId) => (parentCategoryId === keys.category.id ? "hidden" : "visible"), + definitionContainer: () => "partial", + }, + nodesToExpect, + }); + ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.category.id], isVisible: false, enableAllSubCategories: false }], []); + }); + + it("hiding category makes it and all of its subCategories hidden, and parent container partially visible if it has more definitionContainers", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + + const definitionContainerChild = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerChild", modelId: definitionModelRoot.id }); + const definitionModelChild = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerChild.id }); + const indirectCategory = insertSpatialCategory({ builder, codeValue: "SpatialCategory", modelId: definitionModelChild.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: indirectCategory.id }); + + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory2", modelId: definitionModelRoot.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + const subCategory = insertSubCategory({ + builder, + parentCategoryId: category.id, + codeValue: "subCategory", + modelId: definitionModelRoot.id, + }); + return { definitionContainerRoot, definitionContainerChild, category, indirectCategory, subCategory }; + }); + + const nodesToExpect = [ + keys.definitionContainerRoot.id, + keys.definitionContainerChild.id, + keys.indirectCategory.id, + keys.category.id, + keys.subCategory.id, + ]; + + using visibilityTestData = await createVisibilityTestData({ imodel }); + const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerRoot.id), true); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: VisibilityExpectations.all("visible"), + nodesToExpect, + }); + + await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), false); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: { + category: (categoryId) => (categoryId === keys.category.id ? "hidden" : "visible"), + subCategory: () => "hidden", + definitionContainer: (definitionContainerId) => { + if (definitionContainerId === keys.definitionContainerRoot.id) { + return "partial"; + } + return "visible"; + }, + }, + nodesToExpect, + }); + ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.category.id], isVisible: false, enableAllSubCategories: false }], []); + }); + }); + + describe("subCategories", () => { + it("hiding subCategory makes it hidden and its parent category partially visible, and doesn't affect other subCategories", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + const subCategory = insertSubCategory({ + builder, + parentCategoryId: category.id, + codeValue: "subCategory", + }); + const subCategory2 = insertSubCategory({ + builder, + parentCategoryId: category.id, + codeValue: "subCategory2", + }); + return { category, subCategory, subCategory2 }; + }); + + const nodesToExpect = [keys.category.id, keys.subCategory.id, keys.subCategory2.id]; + using visibilityTestData = await createVisibilityTestData({ imodel }); + const { handler, provider, viewport } = visibilityTestData; + + await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), true); + + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: VisibilityExpectations.all("visible"), + nodesToExpect, + }); + + await handler.changeVisibility(createSubCategoryHierarchyNode(keys.subCategory.id, keys.category.id), false); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: { + category: () => "partial", + subCategory: (_, subCategoryId) => (subCategoryId === keys.subCategory.id ? "hidden" : "visible"), + }, + nodesToExpect, + }); + ViewportMock.validateChangesCalls(viewport, [], [{ subCategoryId: keys.subCategory.id, isVisible: false }]); + }); + + it("showing subCategory makes it visible and its parent category partially visible, and doesn't affect other categories", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + const subCategory = insertSubCategory({ + builder, + parentCategoryId: category.id, + codeValue: "subCategory", + }); + const category2 = insertSpatialCategory({ builder, codeValue: "SpatialCategory2" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category2.id }); + return { category, subCategory, category2 }; + }); + + const nodesToExpect = [keys.category.id, keys.subCategory.id, keys.category2.id]; + using visibilityTestData = await createVisibilityTestData({ imodel }); + const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), true); + await handler.changeVisibility(createCategoryHierarchyNode(keys.category2.id), true); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: VisibilityExpectations.all("visible"), + nodesToExpect, + }); + + await handler.changeVisibility(createSubCategoryHierarchyNode(keys.subCategory.id, keys.category.id), false); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: { + category: (categoryId) => (categoryId === keys.category.id ? "partial" : "visible"), + subCategory: (_, subCategoryId) => (subCategoryId === keys.subCategory.id ? "hidden" : "visible"), + }, + nodesToExpect, + }); + ViewportMock.validateChangesCalls(viewport, [], [{ subCategoryId: keys.subCategory.id, isVisible: false }]); + }); + + it("hiding subCategory makes it hidden and parents partially visible", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory", modelId: definitionModelRoot.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + const subCategory = insertSubCategory({ + builder, + parentCategoryId: category.id, + codeValue: "subCategory", + modelId: definitionModelRoot.id, + }); + return { category, subCategory, definitionContainerRoot }; + }); + + const nodesToExpect = [keys.category.id, keys.subCategory.id, keys.definitionContainerRoot.id]; + using visibilityTestData = await createVisibilityTestData({ imodel }); + const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerRoot.id), true); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: VisibilityExpectations.all("visible"), + nodesToExpect, + }); + + await handler.changeVisibility(createSubCategoryHierarchyNode(keys.subCategory.id, keys.category.id), false); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: { + category: () => "partial", + subCategory: (_, subCategoryId) => (subCategoryId === keys.subCategory.id ? "hidden" : "visible"), + definitionContainer: () => "partial", + }, + nodesToExpect, + }); + ViewportMock.validateChangesCalls(viewport, [], [{ subCategoryId: keys.subCategory.id, isVisible: false }]); + }); + + it("hiding subCategory makes it hidden and doesn't affect non related definitionContainers", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); + const definitionModelRoot = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainerRoot.id }); + + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory" }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + const subCategory = insertSubCategory({ + builder, + parentCategoryId: category.id, + codeValue: "subCategory", + }); + const categoryOfDefinitionContainer = insertSpatialCategory({ builder, codeValue: "SpatialCategory2", modelId: definitionModelRoot.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: categoryOfDefinitionContainer.id }); + const subCategoryOfDefinitionContainer = insertSubCategory({ + builder, + parentCategoryId: categoryOfDefinitionContainer.id, + codeValue: "subCategory2", + modelId: definitionModelRoot.id, + }); + return { category, subCategory, definitionContainerRoot, categoryOfDefinitionContainer, subCategoryOfDefinitionContainer }; + }); + + const nodesToExpect = [ + keys.category.id, + keys.subCategory.id, + keys.definitionContainerRoot.id, + keys.categoryOfDefinitionContainer.id, + keys.subCategoryOfDefinitionContainer.id, + ]; + using visibilityTestData = await createVisibilityTestData({ imodel }); + const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), true); + await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerRoot.id), true); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: VisibilityExpectations.all("visible"), + nodesToExpect, + }); + + await handler.changeVisibility(createSubCategoryHierarchyNode(keys.subCategory.id, keys.category.id), false); + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: { + category: (categoryId) => (categoryId === keys.category.id ? "partial" : "visible"), + subCategory: (_, subCategoryId) => (subCategoryId === keys.subCategory.id ? "hidden" : "visible"), + definitionContainer: () => "visible", + }, + nodesToExpect, + }); + ViewportMock.validateChangesCalls(viewport, [], [{ subCategoryId: keys.subCategory.id, isVisible: false }]); + }); + }); + }); +}); diff --git a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/Utils.ts b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/Utils.ts new file mode 100644 index 000000000..21472ac1f --- /dev/null +++ b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/Utils.ts @@ -0,0 +1,193 @@ +/*--------------------------------------------------------------------------------------------- + * 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 sinon from "sinon"; +import { BeEvent } from "@itwin/core-bentley"; +import { PerModelCategoryVisibility } from "@itwin/core-frontend"; + +import type { Id64Array, Id64String } from "@itwin/core-bentley"; +import type { Viewport } from "@itwin/core-frontend"; +import type { NonGroupingHierarchyNode } from "@itwin/presentation-hierarchies"; +import type { CategoriesTreeIdsCache } from "../../../../tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.js"; + +/** @internal */ +export function createCategoryHierarchyNode(categoryId: Id64String, hasChildren = false): NonGroupingHierarchyNode { + return { + key: { + type: "instances", + instanceKeys: [{ className: "bis:SpatialCategory", id: categoryId }], + }, + children: hasChildren, + label: "", + parentKeys: [], + extendedData: { + isCategory: true, + }, + }; +} + +/** @internal */ +export function createSubCategoryHierarchyNode(subCategoryId: Id64String, categoryId: Id64String): NonGroupingHierarchyNode { + return { + key: { + type: "instances", + instanceKeys: [{ className: "bis:SubCategory", id: subCategoryId }], + }, + children: false, + label: "", + parentKeys: [], + extendedData: { + isSubCategory: true, + categoryId, + }, + }; +} + +/** @internal */ +export function createDefinitionContainerHierarchyNode(definitionContainerId: Id64String): NonGroupingHierarchyNode { + return { + key: { + type: "instances", + instanceKeys: [{ className: "bis:DefinitionContainer", id: definitionContainerId }], + }, + children: true, + label: "", + parentKeys: [], + extendedData: { + isDefinitionContainer: true, + }, + }; +} + +/** + * Class that has necessary utilities for testing CategoriesTree visibility. + * It allows stubbing `Viewport` and validating if specific functions get called with appropriate params. + * @internal + */ +export class ViewportMock { + private _idsCache: CategoriesTreeIdsCache; + private _subCategories: Map; + + private _categories: Map< + Id64String, + { + subCategories: Id64Array; + isVisible: boolean; + } + >; + + constructor(idsCache: CategoriesTreeIdsCache) { + this._idsCache = idsCache; + this._categories = new Map(); + this._subCategories = new Map(); + } + + /** + * Creates a stubbed `Viewport` with that has only necessary properties defined for determening CategoriesTree visibility. + * + * This stub allows changing and saving the display of categories and subcategories + * @returns stubbed `Viewport` + */ + public async createViewportStub(): Promise { + const { categories } = await this._idsCache.getAllDefinitionContainersAndCategories(); + for (const category of categories) { + const subCategories = await this._idsCache.getSubCategories(category); + subCategories.forEach((subCategoryId) => { + this._subCategories.set(subCategoryId, false); + }); + this._categories.set(category, { isVisible: false, subCategories }); + } + + return { + isSubCategoryVisible: sinon.stub().callsFake((subCategoryId: Id64String) => !!this._subCategories.get(subCategoryId)), + iModel: { + categories: { + getCategoryInfo: sinon.stub().callsFake(async (ids: Id64Array) => { + const subCategories = []; + for (const id of ids) { + const subCategoriesToUse = this._categories.get(id); + if (subCategoriesToUse !== undefined) { + subCategories.push(...subCategoriesToUse.subCategories); + } + } + return [ + { + subCategories: subCategories.map((subCategory) => { + return { + id: subCategory, + }; + }), + }, + ]; + }), + }, + }, + view: { + viewsCategory: sinon.stub().callsFake((categoryId: Id64String) => !!this._categories.get(categoryId)?.isVisible), + }, + changeSubCategoryDisplay: sinon.stub().callsFake((subCategoryId: Id64String, isVisible: boolean) => { + this._subCategories.set(subCategoryId, isVisible); + }), + changeCategoryDisplay: sinon.stub().callsFake((categoriesToChange: Id64Array, isVisible: boolean, enableAllSubCategories: boolean) => { + for (const category of categoriesToChange) { + const value = this._categories.get(category); + if (value) { + value.isVisible = isVisible; + if (enableAllSubCategories) { + for (const subCategory of value.subCategories) { + this._subCategories.set(subCategory, true); + } + } + } + } + }), + perModelCategoryVisibility: { + getOverride: sinon.fake.returns(PerModelCategoryVisibility.Override.None), + setOverride: sinon.fake(), + clearOverrides: sinon.fake(), + *[Symbol.iterator]() {}, + }, + onDisplayStyleChanged: new BeEvent<() => void>(), + onViewedCategoriesChanged: new BeEvent<() => void>(), + } as unknown as Viewport; + } + + /** + * Checks if `view.viewsCategory` and `isSubCategoryVisible` get called with appropriate params + * + * @param stubbedViewport viewport created using `ViewportMock.createViewPortStub()` + * @param categories categories that `stubbedViewport.view.viewsCategory` should be called with + * @param subCategories subcategories that `stubbedViewport.isSubCategoryVisible` should be called with + */ + public static validateViewsCalls(stubbedViewport: Viewport, categories: Id64Array, subCategories: Id64Array) { + for (const category of categories) { + expect(stubbedViewport.view.viewsCategory).to.be.calledWith(category); + } + for (const subCategory of subCategories) { + expect(stubbedViewport.isSubCategoryVisible).to.be.calledWith(subCategory); + } + } + + /** + * Checks if `changeCategoryDisplay` and `changeSubCategoryDisplay` get called with appropriate params + * + * @param stubbedViewport viewport created using `ViewportMock.createViewPortStub()` + * @param categories categories parameters that `stubbedViewport.changeCategoryDisplay` should be called with + * @param subCategories subcategories parameters that `stubbedViewport.changeSubCategoryDisplay` should be called with + */ + public static validateChangesCalls( + stubbedViewport: Viewport, + categories: { categoriesToChange: Id64Array; isVisible: boolean; enableAllSubCategories: boolean }[], + subCategories: { subCategoryId: Id64String; isVisible: boolean }[], + ) { + for (const category of categories) { + expect(stubbedViewport.changeCategoryDisplay).to.be.calledWith(category.categoriesToChange, category.isVisible, category.enableAllSubCategories); + } + for (const subCategory of subCategories) { + expect(stubbedViewport.changeSubCategoryDisplay).to.be.calledWith(subCategory.subCategoryId, subCategory.isVisible); + } + } +} diff --git a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/VisibilityValidation.ts b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/VisibilityValidation.ts new file mode 100644 index 000000000..c41b7ed75 --- /dev/null +++ b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/VisibilityValidation.ts @@ -0,0 +1,93 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { EMPTY, expand, from, mergeMap, tap } from "rxjs"; +import { HierarchyNode } from "@itwin/presentation-hierarchies"; +import { CategoriesTreeNode } from "../../../../tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeNode.js"; +import { toVoidPromise } from "../../../../tree-widget-react/components/trees/common/Rxjs.js"; + +import type { Id64Array, Id64String } from "@itwin/core-bentley"; +import type { Viewport } from "@itwin/core-frontend"; +import type { HierarchyProvider } from "@itwin/presentation-hierarchies"; +import type { Visibility } from "../../../../tree-widget-react/components/trees/common/Tooltip.js"; +import type { HierarchyVisibilityHandler } from "../../../../tree-widget-react/components/trees/common/UseHierarchyVisibility.js"; + +interface VisibilityExpectations { + subCategory?(parentCategoryId: Id64String, subCategoryId?: Id64String): Omit; + definitionContainer?(id: Id64String): Visibility; + category(id: Id64String): Visibility; +} + +export namespace VisibilityExpectations { + export function all(visibility: "visible" | "hidden"): VisibilityExpectations { + return { + subCategory: () => visibility, + category: () => visibility, + definitionContainer: () => visibility, + }; + } +} + +export interface ValidateNodeProps { + handler: HierarchyVisibilityHandler; + viewport: Viewport; + visibilityExpectations: VisibilityExpectations; + nodesToExpect: Id64Array; +} + +export async function validateNodeVisibility({ node, handler, visibilityExpectations }: ValidateNodeProps & { node: HierarchyNode }) { + const actualVisibility = await handler.getVisibilityStatus(node); + if (!HierarchyNode.isInstancesNode(node)) { + throw new Error(`Expected hierarchy to only have instance nodes, got ${JSON.stringify(node)}`); + } + + const { id } = node.key.instanceKeys[0]; + + if (CategoriesTreeNode.isCategoryNode(node)) { + expect(actualVisibility.state).to.eq(visibilityExpectations.category(id)); + return; + } + if (CategoriesTreeNode.isSubCategoryNode(node)) { + const parentCategoryId = node.extendedData?.categoryId; + if (visibilityExpectations.subCategory === undefined) { + throw new Error(`Expected hierarchy to not have subCategory nodes, got ${JSON.stringify(node)}`); + } + expect(actualVisibility.state).to.eq(visibilityExpectations.subCategory(parentCategoryId, id)); + return; + } + if (CategoriesTreeNode.isDefinitionContainerNode(node)) { + if (visibilityExpectations.definitionContainer === undefined) { + throw new Error(`Expected hierarchy to not have definitionContainer nodes, got ${JSON.stringify(node)}`); + } + expect(actualVisibility.state).to.eq(visibilityExpectations.definitionContainer(id)); + return; + } + + throw new Error(`Expected hierarchy to contain only definitionContainers, categories and subcategories, got ${JSON.stringify(node)}`); +} + +export async function validateHierarchyVisibility({ + provider, + ...props +}: Omit & { + visibilityExpectations: VisibilityExpectations; + provider: HierarchyProvider; +}) { + const nodesFound = new Array(); + await toVoidPromise( + from(provider.getNodes({ parentNode: undefined })).pipe( + expand((node) => (node.children ? provider.getNodes({ parentNode: node }) : EMPTY)), + tap((node) => { + if (!HierarchyNode.isInstancesNode(node)) { + throw new Error(`Expected hierarchy to contain only instance nodes, got ${JSON.stringify(node)}`); + } + nodesFound.push(node.key.instanceKeys[0].id); + }), + mergeMap(async (node) => validateNodeVisibility({ ...props, node })), + ), + ); + expect(props.nodesToExpect.every((nodeId) => nodesFound.includes(nodeId))).to.be.true; +} diff --git a/packages/itwin/tree-widget/src/test/trees/models-tree/internal/ModelsTreeVisibilityHandler.test.ts b/packages/itwin/tree-widget/src/test/trees/models-tree/internal/ModelsTreeVisibilityHandler.test.ts index 35913a1a8..bc216574b 100644 --- a/packages/itwin/tree-widget/src/test/trees/models-tree/internal/ModelsTreeVisibilityHandler.test.ts +++ b/packages/itwin/tree-widget/src/test/trees/models-tree/internal/ModelsTreeVisibilityHandler.test.ts @@ -3080,7 +3080,7 @@ describe("HierarchyBasedVisibilityHandler", () => { }); }); -/** Copied from https://github.com/iTwin/appui/blob/master/test-apps/appui-test-app/appui-test-handlers/src/createBlankConnection.ts#L26 */ +/** Copied from https://github.com/iTwin/appui/blob/c3683b8acef46572c661c4fa1b7933747a76d3c1/apps/test-providers/src/createBlankConnection.ts#L26 */ function createBlankViewState(iModel: IModelConnection) { const ext = iModel.projectExtents; const viewState = SpatialViewState.createBlank(iModel, ext.low, ext.high.minus(ext.low)); diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts index 067323b97..34db2681b 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts @@ -3,15 +3,21 @@ * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ +import { defer, EMPTY, from, lastValueFrom, map, mergeMap, toArray } from "rxjs"; import { createNodesQueryClauseFactory, createPredicateBasedHierarchyDefinition } from "@itwin/presentation-hierarchies"; import { createBisInstanceLabelSelectClauseFactory, ECSql } from "@itwin/presentation-shared"; +import { collect } from "../common/Rxjs.js"; import { FilterLimitExceededError } from "../common/TreeErrors.js"; +import { DEFINITION_CONTAINER_CLASS, getClassesByView, SUB_CATEGORY_CLASS } from "./internal/CategoriesTreeIdsCache.js"; -import type { ECClassHierarchyInspector, ECSchemaProvider, IInstanceLabelSelectClauseFactory } from "@itwin/presentation-shared"; +import type { Id64Array } from "@itwin/core-bentley"; +import type { Observable } from "rxjs"; +import type { CategoriesTreeIdsCache } from "./internal/CategoriesTreeIdsCache.js"; +import type { ECClassHierarchyInspector, ECSchemaProvider, IInstanceLabelSelectClauseFactory, InstanceKey } from "@itwin/presentation-shared"; import type { DefineHierarchyLevelProps, DefineInstanceNodeChildHierarchyLevelProps, - DefineRootHierarchyLevelProps, + GenericInstanceFilter, HierarchyDefinition, HierarchyFilteringPath, HierarchyLevelDefinition, @@ -20,36 +26,50 @@ import type { } from "@itwin/presentation-hierarchies"; const MAX_FILTERING_INSTANCE_KEY_COUNT = 100; +const DEFINITION_ELEMENT_CLASS = "BisCore.DefinitionElement"; interface CategoriesTreeDefinitionProps { - imodelAccess: ECSchemaProvider & ECClassHierarchyInspector; + imodelAccess: ECSchemaProvider & ECClassHierarchyInspector & LimitingECSqlQueryExecutor; viewType: "2d" | "3d"; + idsCache: CategoriesTreeIdsCache; } interface CategoriesTreeInstanceKeyPathsFromInstanceLabelProps { imodelAccess: ECClassHierarchyInspector & LimitingECSqlQueryExecutor; label: string; viewType: "2d" | "3d"; + limit?: number | "unbounded"; + idsCache: CategoriesTreeIdsCache; } export class CategoriesTreeDefinition implements HierarchyDefinition { private _impl: HierarchyDefinition; private _selectQueryFactory: NodesQueryClauseFactory; private _nodeLabelSelectClauseFactory: IInstanceLabelSelectClauseFactory; + private _idsCache: CategoriesTreeIdsCache; public constructor(props: CategoriesTreeDefinitionProps) { this._impl = createPredicateBasedHierarchyDefinition({ classHierarchyInspector: props.imodelAccess, hierarchy: { - rootNodes: async (requestProps) => this.createRootHierarchyLevelDefinition({ ...requestProps, viewType: props.viewType }), + rootNodes: async (requestProps) => this.createDefinitionContainersAndCategoriesQuery({ ...requestProps, viewType: props.viewType }), childNodes: [ { parentInstancesNodePredicate: "BisCore.Category", definitions: async (requestProps: DefineInstanceNodeChildHierarchyLevelProps) => this.createSubcategoryQuery(requestProps), }, + { + parentInstancesNodePredicate: DEFINITION_CONTAINER_CLASS, + definitions: async (requestProps: DefineInstanceNodeChildHierarchyLevelProps) => + this.createDefinitionContainersAndCategoriesQuery({ + ...requestProps, + viewType: props.viewType, + }), + }, ], }, }); + this._idsCache = props.idsCache; this._nodeLabelSelectClauseFactory = createBisInstanceLabelSelectClauseFactory({ classHierarchyInspector: props.imodelAccess }); this._selectQueryFactory = createNodesQueryClauseFactory({ imodelAccess: props.imodelAccess, @@ -61,18 +81,64 @@ export class CategoriesTreeDefinition implements HierarchyDefinition { return this._impl.defineHierarchyLevel(props); } - private async createRootHierarchyLevelDefinition(props: DefineRootHierarchyLevelProps & { viewType: "2d" | "3d" }): Promise { - const { categoryClass, categoryElementClass } = getClassesByView(props.viewType); - const instanceFilterClauses = await this._selectQueryFactory.createFilterClauses({ - filter: props.instanceFilter, + private async createDefinitionContainersAndCategoriesQuery(props: { + parentNodeInstanceIds?: Id64Array; + instanceFilter?: GenericInstanceFilter; + viewType: "2d" | "3d"; + }): Promise { + const { parentNodeInstanceIds, instanceFilter, viewType } = props; + const { definitionContainers, categories } = + parentNodeInstanceIds === undefined + ? await this._idsCache.getRootDefinitionContainersAndCategories() + : await this._idsCache.getDirectChildDefinitionContainersAndCategories(parentNodeInstanceIds); + if (categories.length === 0 && definitionContainers.length === 0) { + return []; + } + + const { categoryClass } = getClassesByView(viewType); + + const categoriesInstanceFilterClauses = await this._selectQueryFactory.createFilterClauses({ + filter: instanceFilter, contentClass: { fullName: categoryClass, alias: "this" }, }); - return [ - { - fullClassName: categoryClass, - query: { - ecsql: ` - SELECT + const definitionContainersInstanceFilterClauses = await this._selectQueryFactory.createFilterClauses({ + filter: instanceFilter, + contentClass: { fullName: DEFINITION_CONTAINER_CLASS, alias: "this" }, + }); + + const definitionContainersQuery = + definitionContainers.length > 0 + ? ` + SELECT + ${await this._selectQueryFactory.createSelectClause({ + ecClassId: { selector: ECSql.createRawPropertyValueSelector("this", "ECClassId") }, + ecInstanceId: { selector: "this.ECInstanceId" }, + nodeLabel: { + selector: await this._nodeLabelSelectClauseFactory.createSelectClause({ + classAlias: "this", + className: DEFINITION_CONTAINER_CLASS, + }), + }, + extendedData: { + isDefinitionContainer: true, + imageId: "icon-archive", + }, + hasChildren: true, + supportsFiltering: true, + })} + FROM + ${definitionContainersInstanceFilterClauses.from} this + ${definitionContainersInstanceFilterClauses.joins} + WHERE + this.ECInstanceId IN (${definitionContainers.join(", ")}) + ${definitionContainersInstanceFilterClauses.where ? `AND ${definitionContainersInstanceFilterClauses.where}` : ""} + ` + : undefined; + + const categoriesQuery = + categories.length > 0 + ? ` + SELECT ${await this._selectQueryFactory.createSelectClause({ ecClassId: { selector: ECSql.createRawPropertyValueSelector("this", "ECClassId") }, ecInstanceId: { selector: "this.ECInstanceId" }, @@ -97,18 +163,25 @@ export class CategoriesTreeDefinition implements HierarchyDefinition { }, extendedData: { description: { selector: "this.Description" }, + isCategory: true, + imageId: "icon-layers", }, supportsFiltering: true, })} - FROM ${instanceFilterClauses.from} this - ${instanceFilterClauses.joins} - JOIN BisCore.Model m ON m.ECInstanceId = this.Model.Id + FROM + ${categoriesInstanceFilterClauses.from} this + ${categoriesInstanceFilterClauses.joins} WHERE - NOT this.IsPrivate - AND (NOT m.IsPrivate OR m.ECClassId IS (BisCore.DictionaryModel)) - AND EXISTS (SELECT 1 FROM ${categoryElementClass} e WHERE e.Category.Id = this.ECInstanceId) - ${instanceFilterClauses.where ? `AND ${instanceFilterClauses.where}` : ""} - `, + this.ECInstanceId IN (${categories.join(", ")}) + ${categoriesInstanceFilterClauses.where ? `AND ${categoriesInstanceFilterClauses.where}` : ""} + ` + : undefined; + const queries = [categoriesQuery, definitionContainersQuery].filter((query) => query !== undefined); + return [ + { + fullClassName: DEFINITION_ELEMENT_CLASS, + query: { + ecsql: queries.join(" UNION ALL "), }, }, ]; @@ -120,11 +193,11 @@ export class CategoriesTreeDefinition implements HierarchyDefinition { }: DefineInstanceNodeChildHierarchyLevelProps): Promise { const instanceFilterClauses = await this._selectQueryFactory.createFilterClauses({ filter: instanceFilter, - contentClass: { fullName: "BisCore.SubCategory", alias: "this" }, + contentClass: { fullName: SUB_CATEGORY_CLASS, alias: "this" }, }); return [ { - fullClassName: "BisCore.SubCategory", + fullClassName: SUB_CATEGORY_CLASS, query: { ecsql: ` SELECT @@ -134,11 +207,13 @@ export class CategoriesTreeDefinition implements HierarchyDefinition { nodeLabel: { selector: await this._nodeLabelSelectClauseFactory.createSelectClause({ classAlias: "this", - className: "BisCore.SubCategory", + className: SUB_CATEGORY_CLASS, }), }, extendedData: { categoryId: { selector: "printf('0x%x', this.Parent.Id)" }, + isSubCategory: true, + imageId: "icon-layers-isolate", }, supportsFiltering: false, })} @@ -154,93 +229,170 @@ export class CategoriesTreeDefinition implements HierarchyDefinition { ]; } - public static async createInstanceKeyPaths(props: CategoriesTreeInstanceKeyPathsFromInstanceLabelProps) { + public static async createInstanceKeyPaths(props: CategoriesTreeInstanceKeyPathsFromInstanceLabelProps): Promise { const labelsFactory = createBisInstanceLabelSelectClauseFactory({ classHierarchyInspector: props.imodelAccess }); - return createInstanceKeyPathsFromInstanceLabel({ ...props, labelsFactory }); + return createInstanceKeyPathsFromInstanceLabel({ ...props, labelsFactory, cache: props.idsCache }); } } -function getClassesByView(viewType: "2d" | "3d") { - return viewType === "2d" - ? { categoryClass: "BisCore.DrawingCategory", categoryElementClass: "BisCore:GeometricElement2d" } - : { categoryClass: "BisCore.SpatialCategory", categoryElementClass: "BisCore:GeometricElement3d" }; -} - async function createInstanceKeyPathsFromInstanceLabel( - props: CategoriesTreeInstanceKeyPathsFromInstanceLabelProps & { labelsFactory: IInstanceLabelSelectClauseFactory }, -) { - const { categoryClass, categoryElementClass } = getClassesByView(props.viewType); + props: CategoriesTreeInstanceKeyPathsFromInstanceLabelProps & { labelsFactory: IInstanceLabelSelectClauseFactory; cache: CategoriesTreeIdsCache }, +): Promise { + const { definitionContainers, categories } = await props.cache.getAllDefinitionContainersAndCategories(); + if (categories.length === 0) { + return []; + } + + const { categoryClass } = getClassesByView(props.viewType); const adjustedLabel = props.label.replace(/[%_\\]/g, "\\$&"); - const reader = props.imodelAccess.createQueryReader( - { - ctes: [ - `RootCategoriesWithLabels(ClassName, ECInstanceId, ChildCount, DisplayLabel) as ( - SELECT - ec_classname(this.ECClassId, 's.c'), - this.ECInstanceId, - COUNT(sc.ECInstanceId), - ${await props.labelsFactory.createSelectClause({ - classAlias: "this", - className: categoryClass, - })} - FROM ${categoryClass} this - JOIN BisCore.Model m ON m.ECInstanceId = this.Model.Id - JOIN BisCore.SubCategory sc ON sc.Parent.Id = this.ECInstanceId - WHERE - NOT this.IsPrivate - AND (NOT m.IsPrivate OR m.ECClassId IS (BisCore.DictionaryModel)) - AND EXISTS (SELECT 1 FROM ${categoryElementClass} e WHERE e.Category.Id = this.ECInstanceId) - GROUP BY this.ECInstanceId - )`, - `SubCategoriesWithLabels(ClassName, ECInstanceId, ParentId, DisplayLabel) as ( - SELECT - ec_classname(this.ECClassId, 's.c'), - this.ECInstanceId, - this.Parent.Id, - ${await props.labelsFactory.createSelectClause({ - classAlias: "this", - className: "BisCore.SubCategory", - })} - FROM BisCore.SubCategory this - WHERE NOT this.IsPrivate - )`, - ], - ecsql: ` - SELECT * FROM ( - SELECT - c.ClassName AS CategoryClass, - c.ECInstanceId AS CategoryId, - sc.ClassName AS SubcategoryClass, - sc.ECInstanceId AS SubcategoryId - FROM RootCategoriesWithLabels c - JOIN SubCategoriesWithLabels sc ON sc.ParentId = c.ECInstanceId - WHERE c.ChildCount > 1 AND sc.DisplayLabel LIKE '%' || ? || '%' ESCAPE '\\' - UNION ALL - SELECT - c.ClassName AS CategoryClass, - c.ECInstanceId AS CategoryId, - CAST(NULL AS TEXT) AS SubcategoryClass, - CAST(NULL AS TEXT) AS SubcategoryId - FROM RootCategoriesWithLabels c - WHERE c.DisplayLabel LIKE '%' || ? || '%' ESCAPE '\\' + + const definitionContainersCteName = "DefinitionContainersWithLabels"; + const categoriesCteName = "CategoriesWithLabels"; + const subCategoriesCteName = "SubCategoriesWithLabels"; + const [categoryLabelSelectClause, subCategoryLabelSelectClause, definitionContainerLabelSelectClause] = await Promise.all([ + props.labelsFactory.createSelectClause({ + classAlias: "this", + className: categoryClass, + }), + props.labelsFactory.createSelectClause({ + classAlias: "this", + className: SUB_CATEGORY_CLASS, + }), + props.labelsFactory.createSelectClause({ + classAlias: "this", + className: DEFINITION_CONTAINER_CLASS, + }), + ]); + return lastValueFrom( + defer(() => { + const ctes = [ + `${categoriesCteName}(ClassName, ECInstanceId, ChildCount, DisplayLabel) as ( + SELECT + 'c', + this.ECInstanceId, + COUNT(sc.ECInstanceId), + ${categoryLabelSelectClause} + FROM + ${categoryClass} this + JOIN ${SUB_CATEGORY_CLASS} sc ON sc.Parent.Id = this.ECInstanceId + WHERE + this.ECInstanceId IN (${categories.join(", ")}) + GROUP BY this.ECInstanceId + )`, + `${subCategoriesCteName}(ClassName, ECInstanceId, ParentId, DisplayLabel) as ( + SELECT + 'sc', + this.ECInstanceId, + this.Parent.Id, + ${subCategoryLabelSelectClause} + FROM + ${SUB_CATEGORY_CLASS} this + WHERE + NOT this.IsPrivate + AND this.Parent.Id IN (${categories.join(", ")}) + )`, + ...(definitionContainers.length > 0 + ? [ + `${definitionContainersCteName}(ClassName, ECInstanceId, DisplayLabel) as ( + SELECT + 'dc', + this.ECInstanceId, + ${definitionContainerLabelSelectClause} + FROM + ${DEFINITION_CONTAINER_CLASS} this + WHERE + this.ECInstanceId IN (${definitionContainers.join(", ")}) + )`, + ] + : []), + ]; + const ecsql = ` + SELECT * FROM ( + SELECT + sc.ClassName AS ClassName, + sc.ECInstanceId AS ECInstanceId + FROM + ${categoriesCteName} c + JOIN ${subCategoriesCteName} sc ON sc.ParentId = c.ECInstanceId + WHERE + c.ChildCount > 1 + AND sc.DisplayLabel LIKE '%' || ? || '%' ESCAPE '\\' + + UNION ALL + + SELECT + c.ClassName AS ClassName, + c.ECInstanceId AS ECInstanceId + FROM + ${categoriesCteName} c + WHERE + c.DisplayLabel LIKE '%' || ? || '%' ESCAPE '\\' + + ${ + definitionContainers.length > 0 + ? ` + UNION ALL + SELECT + dc.ClassName AS ClassName, + dc.ECInstanceId AS ECInstanceId + FROM + ${definitionContainersCteName} dc + WHERE + dc.DisplayLabel LIKE '%' || ? || '%' ESCAPE '\\' + ` + : "" + } ) - LIMIT ${MAX_FILTERING_INSTANCE_KEY_COUNT + 1} - `, - bindings: [ - { type: "string", value: adjustedLabel }, - { type: "string", value: adjustedLabel }, - ], - }, - { restartToken: "tree-widget/categories-tree/filter-by-label-query" }, + ${props.limit === undefined ? `LIMIT ${MAX_FILTERING_INSTANCE_KEY_COUNT + 1}` : props.limit !== "unbounded" ? `LIMIT ${props.limit}` : ""} + `; + const bindings = [ + { type: "string" as const, value: adjustedLabel }, + { type: "string" as const, value: adjustedLabel }, + ...(definitionContainers.length > 0 ? [{ type: "string" as const, value: adjustedLabel }] : []), + ]; + return props.imodelAccess.createQueryReader( + { ctes, ecsql, bindings }, + { restartToken: "tree-widget/categories-tree/filter-by-label-query", limit: props.limit }, + ); + }).pipe( + map( + (row): InstanceKey => ({ + className: row.ClassName === "c" ? categoryClass : row.ClassName === "sc" ? SUB_CATEGORY_CLASS : DEFINITION_CONTAINER_CLASS, + id: row.ECInstanceId, + }), + ), + toArray(), + mergeMap(async (targetItems) => { + return collect(createInstanceKeyPathsFromTargetItems({ ...props, targetItems })); + }), + ), ); - const paths: HierarchyFilteringPath[] = []; - for await (const row of reader) { - const path = { path: [{ className: row.CategoryClass, id: row.CategoryId }], options: { autoExpand: true } }; - row.SubcategoryId && path.path.push({ className: row.SubcategoryClass, id: row.SubcategoryId }); - paths.push(path); +} + +function createInstanceKeyPathsFromTargetItems( + props: Pick & { + targetItems: InstanceKey[]; + }, +): Observable { + const { limit, targetItems, viewType, idsCache } = props; + if (limit !== "unbounded" && targetItems.length > (limit ?? MAX_FILTERING_INSTANCE_KEY_COUNT)) { + throw new FilterLimitExceededError(limit ?? MAX_FILTERING_INSTANCE_KEY_COUNT); } - if (paths.length > MAX_FILTERING_INSTANCE_KEY_COUNT) { - throw new FilterLimitExceededError(MAX_FILTERING_INSTANCE_KEY_COUNT); + + if (targetItems.length === 0) { + return EMPTY; } - return paths; + + const { categoryClass } = getClassesByView(viewType); + return from(targetItems).pipe( + mergeMap(async (targetItem) => { + if (targetItem.className === SUB_CATEGORY_CLASS) { + return { path: await idsCache.getInstanceKeyPaths({ subCategoryId: targetItem.id }), options: { autoExpand: true } }; + } + if (targetItem.className === categoryClass) { + return { path: await idsCache.getInstanceKeyPaths({ categoryId: targetItem.id }), options: { autoExpand: true } }; + } + return { path: await idsCache.getInstanceKeyPaths({ definitionContainerId: targetItem.id }), options: { autoExpand: true } }; + }), + ); } diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesVisibilityHandler.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesVisibilityHandler.ts deleted file mode 100644 index 589758e30..000000000 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesVisibilityHandler.ts +++ /dev/null @@ -1,116 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Bentley Systems, Incorporated. All rights reserved. - * See LICENSE.md in the project root for license terms and full copyright notice. - *--------------------------------------------------------------------------------------------*/ - -import { BeEvent } from "@itwin/core-bentley"; -import { HierarchyNode } from "@itwin/presentation-hierarchies"; -import { enableCategoryDisplay, enableSubCategoryDisplay } from "../common/CategoriesVisibilityUtils.js"; -import { createVisibilityStatus } from "../common/Tooltip.js"; - -import type { Viewport } from "@itwin/core-frontend"; -import type { HierarchyVisibilityHandler, VisibilityStatus } from "../common/UseHierarchyVisibility.js"; - -interface CategoriesVisibilityHandlerProps { - viewport: Viewport; -} - -/** @internal */ -export class CategoriesVisibilityHandler implements HierarchyVisibilityHandler { - private _pendingVisibilityChange: any; - private _viewport: Viewport; - - constructor(props: CategoriesVisibilityHandlerProps) { - this._viewport = props.viewport; - this._viewport.onDisplayStyleChanged.addListener(this.onDisplayStyleChanged); - this._viewport.onViewedCategoriesChanged.addListener(this.onViewedCategoriesChanged); - } - - public dispose() { - this._viewport.onDisplayStyleChanged.removeListener(this.onDisplayStyleChanged); - this._viewport.onViewedCategoriesChanged.removeListener(this.onViewedCategoriesChanged); - clearTimeout(this._pendingVisibilityChange); - } - - public onVisibilityChange = new BeEvent(); - - /** Returns visibility status of the tree node. */ - public getVisibilityStatus(node: HierarchyNode): Promise | VisibilityStatus { - if (!HierarchyNode.isInstancesNode(node)) { - return { state: "hidden", isDisabled: true }; - } - return createVisibilityStatus(node.parentKeys.length ? this.getSubCategoryVisibility(node) : this.getCategoryVisibility(node)); - } - - public async changeVisibility(node: HierarchyNode, on: boolean) { - if (!HierarchyNode.isInstancesNode(node)) { - return; - } - - // handle subcategory visibility change - if (node.parentKeys.length) { - const childId = CategoriesVisibilityHandler.getInstanceIdFromHierarchyNode(node); - const parentCategoryId = node.extendedData?.categoryId; - - // make sure parent category is enabled - if (on && parentCategoryId) { - await this.enableCategory([parentCategoryId], true, false); - } - - this.enableSubCategory(childId, on); - return; - } - - const instanceId = CategoriesVisibilityHandler.getInstanceIdFromHierarchyNode(node); - await this.enableCategory([instanceId], on, true); - } - - public getSubCategoryVisibility(node: HierarchyNode) { - const parentCategoryId = node.extendedData?.categoryId; - if (!parentCategoryId) { - return "hidden"; - } - - const subcategoryId = CategoriesVisibilityHandler.getInstanceIdFromHierarchyNode(node); - const isVisible = this._viewport.view.viewsCategory(parentCategoryId) && this._viewport.isSubCategoryVisible(subcategoryId); - return isVisible ? "visible" : "hidden"; - } - - public getCategoryVisibility(node: HierarchyNode) { - const instanceId = CategoriesVisibilityHandler.getInstanceIdFromHierarchyNode(node); - return this._viewport.view.viewsCategory(instanceId) ? "visible" : "hidden"; - } - - // eslint-disable-next-line @typescript-eslint/naming-convention - private onDisplayStyleChanged = () => { - this.onVisibilityChangeInternal(); - }; - - // eslint-disable-next-line @typescript-eslint/naming-convention - private onViewedCategoriesChanged = () => { - this.onVisibilityChangeInternal(); - }; - - private onVisibilityChangeInternal() { - if (this._pendingVisibilityChange) { - return; - } - - this._pendingVisibilityChange = setTimeout(() => { - this.onVisibilityChange.raiseEvent(); - this._pendingVisibilityChange = undefined; - }, 0); - } - - public static getInstanceIdFromHierarchyNode(node: HierarchyNode) { - return HierarchyNode.isInstancesNode(node) && node.key.instanceKeys.length > 0 ? node.key.instanceKeys[0].id : ""; - } - - public async enableCategory(ids: string[], enabled: boolean, enableAllSubCategories = true) { - await enableCategoryDisplay(this._viewport, ids, enabled, enableAllSubCategories); - } - - public enableSubCategory(key: string, enabled: boolean) { - enableSubCategoryDisplay(this._viewport, key, enabled); - } -} diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx index fb7702b81..8f3ca02e6 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx @@ -3,16 +3,19 @@ * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ -import { useCallback, useMemo, useState } from "react"; -import { SvgLayers } from "@itwin/itwinui-icons-react"; +import { useCallback, useMemo, useRef, useState } from "react"; +import { SvgArchive, SvgLayers } from "@itwin/itwinui-icons-react"; import { Text } from "@itwin/itwinui-react"; +import { createECSqlQueryExecutor } from "@itwin/presentation-core-interop"; import { HierarchyNodeIdentifier } from "@itwin/presentation-hierarchies"; import { TreeWidget } from "../../../TreeWidget.js"; import { FilterLimitExceededError } from "../common/TreeErrors.js"; import { useTelemetryContext } from "../common/UseTelemetryContext.js"; import { CategoriesTreeDefinition } from "./CategoriesTreeDefinition.js"; -import { CategoriesVisibilityHandler } from "./CategoriesVisibilityHandler.js"; +import { CategoriesTreeIdsCache, DEFINITION_CONTAINER_CLASS, SUB_CATEGORY_CLASS } from "./internal/CategoriesTreeIdsCache.js"; +import { CategoriesVisibilityHandler } from "./internal/CategoriesVisibilityHandler.js"; +import type { ReactElement } from "react"; import type { HierarchyNode } from "@itwin/presentation-hierarchies"; import type { VisibilityTreeProps } from "../common/components/VisibilityTree.js"; import type { Viewport } from "@itwin/core-frontend"; @@ -46,9 +49,22 @@ interface UseCategoriesTreeResult { */ export function useCategoriesTree({ filter, activeView, onCategoriesFiltered }: UseCategoriesTreeProps): UseCategoriesTreeResult { const [filteringError, setFilteringError] = useState(); + const idsCacheRef = useRef(); + const activeViewRef = useRef(); + + const getCategoriesTreeIdsCache = useCallback(() => { + if (activeViewRef.current !== activeView || !idsCacheRef.current) { + const viewType = activeView.view.is2d() ? "2d" : "3d"; + activeViewRef.current = activeView; + idsCacheRef.current = new CategoriesTreeIdsCache(createECSqlQueryExecutor(activeView.iModel), viewType); + } + return idsCacheRef.current; + }, [activeView]); + const visibilityHandlerFactory = useCallback(() => { const visibilityHandler = new CategoriesVisibilityHandler({ viewport: activeView, + idsCache: getCategoriesTreeIdsCache(), }); return { getVisibilityStatus: async (node: HierarchyNode) => visibilityHandler.getVisibilityStatus(node), @@ -56,14 +72,15 @@ export function useCategoriesTree({ filter, activeView, onCategoriesFiltered }: onVisibilityChange: visibilityHandler.onVisibilityChange, dispose: () => visibilityHandler.dispose(), }; - }, [activeView]); + }, [activeView, getCategoriesTreeIdsCache]); const { onFeatureUsed } = useTelemetryContext(); const getHierarchyDefinition = useCallback( (props) => { - return new CategoriesTreeDefinition({ ...props, viewType: activeView.view.is2d() ? "2d" : "3d" }); + const viewType = activeView.view.is2d() ? "2d" : "3d"; + return new CategoriesTreeDefinition({ ...props, viewType, idsCache: getCategoriesTreeIdsCache() }); }, - [activeView], + [activeView, getCategoriesTreeIdsCache], ); const getFilteredPaths = useMemo(() => { @@ -75,8 +92,9 @@ export function useCategoriesTree({ filter, activeView, onCategoriesFiltered }: return async ({ imodelAccess }) => { onFeatureUsed({ featureId: "filtering", reportInteraction: true }); try { - const paths = await CategoriesTreeDefinition.createInstanceKeyPaths({ imodelAccess, label: filter, viewType: activeView.view.is2d() ? "2d" : "3d" }); - onCategoriesFiltered?.(getCategories(paths)); + const viewType = activeView.view.is2d() ? "2d" : "3d"; + const paths = await CategoriesTreeDefinition.createInstanceKeyPaths({ imodelAccess, label: filter, viewType, idsCache: getCategoriesTreeIdsCache() }); + onCategoriesFiltered?.(getCategoriesFromPaths(paths)); return paths; } catch (e) { const newError = e instanceof FilterLimitExceededError ? "tooManyFilterMatches" : "unknownFilterError"; @@ -88,7 +106,7 @@ export function useCategoriesTree({ filter, activeView, onCategoriesFiltered }: return []; } }; - }, [filter, activeView, onFeatureUsed, onCategoriesFiltered]); + }, [filter, activeView, onFeatureUsed, onCategoriesFiltered, getCategoriesTreeIdsCache]); return { categoriesTreeProps: { @@ -106,7 +124,7 @@ export function useCategoriesTree({ filter, activeView, onCategoriesFiltered }: }; } -function getCategories(paths: HierarchyFilteringPaths): CategoryInfo[] | undefined { +function getCategoriesFromPaths(paths: HierarchyFilteringPaths): CategoryInfo[] | undefined { if (!paths) { return undefined; } @@ -114,18 +132,41 @@ function getCategories(paths: HierarchyFilteringPaths): CategoryInfo[] | undefin const categories = new Map(); for (const path of paths) { const currPath = Array.isArray(path) ? path : path.path; - const [category, subCategory] = currPath; + if (currPath.length === 0) { + continue; + } + + let category: HierarchyNodeIdentifier; + let subCategory: HierarchyNodeIdentifier | undefined; + const lastNode = currPath[currPath.length - 1]; - if (!HierarchyNodeIdentifier.isInstanceNodeIdentifier(category)) { + if (!HierarchyNodeIdentifier.isInstanceNodeIdentifier(lastNode) || lastNode.className === DEFINITION_CONTAINER_CLASS) { continue; } - if (!categories.has(category.id)) { - categories.set(category.id, []); + if (lastNode.className === SUB_CATEGORY_CLASS) { + const secondToLastNode = currPath.length > 1 ? currPath[currPath.length - 2] : undefined; + if ( + secondToLastNode === undefined || + !HierarchyNodeIdentifier.isInstanceNodeIdentifier(secondToLastNode) || + secondToLastNode.className === DEFINITION_CONTAINER_CLASS + ) { + continue; + } + subCategory = lastNode; + category = secondToLastNode; + } else { + category = lastNode; + } + + let entry = categories.get(category.id); + if (entry === undefined) { + entry = []; + categories.set(category.id, entry); } - if (subCategory && HierarchyNodeIdentifier.isInstanceNodeIdentifier(subCategory)) { - categories.get(category.id)!.push(subCategory.id); + if (subCategory) { + entry.push(subCategory.id); } } @@ -145,8 +186,40 @@ function getNoDataMessage(filter: string, error?: CategoriesTreeFilteringError) return undefined; } -function getIcon() { - return ; +function SvgLayersIsolate() { + return ( + + + + + + + + + + + + + + + ); +} + +function getIcon(node: PresentationHierarchyNode): ReactElement | undefined { + if (node.extendedData?.imageId === undefined) { + return undefined; + } + + switch (node.extendedData.imageId) { + case "icon-layers": + return ; + case "icon-layers-isolate": + return ; + case "icon-archive": + return ; + } + + return undefined; } function getSublabel(node: PresentationHierarchyNode) { diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts new file mode 100644 index 000000000..000413118 --- /dev/null +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts @@ -0,0 +1,374 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ + +import type { Id64Array, Id64String } from "@itwin/core-bentley"; +import type { LimitingECSqlQueryExecutor } from "@itwin/presentation-hierarchies"; +import type { InstanceKey } from "@itwin/presentation-shared"; + +interface DefinitionContainerInfo { + modelId: Id64String; + parentDefinitionContainerExists: boolean; + childCategories: Id64Array; + childDefinitionContainers: Id64Array; +} + +interface CategoriesInfo { + childCategories: CategoryInfo[]; + parentDefinitionContainerExists: boolean; +} + +interface CategoryInfo { + id: Id64String; + childCound: number; +} + +interface SubCategoryInfo { + categoryId: Id64String; +} + +/** @internal */ +export const SUB_CATEGORY_CLASS = "BisCore.SubCategory"; +/** @internal */ +export const DEFINITION_CONTAINER_CLASS = "BisCore.DefinitionContainer"; + +/** @internal */ +export class CategoriesTreeIdsCache { + private _definitionContainersInfo: Promise> | undefined; + private _modelsCategoriesInfo: Promise> | undefined; + private _subCategoriesInfo: Promise> | undefined; + private _categoryClass: string; + private _categoryElementClass: string; + + constructor( + private _queryExecutor: LimitingECSqlQueryExecutor, + viewType: "3d" | "2d", + ) { + const { categoryClass, categoryElementClass } = getClassesByView(viewType); + this._categoryClass = categoryClass; + this._categoryElementClass = categoryElementClass; + } + + private async *queryCategories(): AsyncIterableIterator<{ + id: Id64String; + modelId: Id64String; + parentDefinitionContainerExists: boolean; + childCount: number; + }> { + const categoriesCteName = "AllVisibleCategories"; + const categoriesWithChildCountCteName = "CategoriesWithChildCount"; + + const ctes = [ + `${categoriesWithChildCountCteName}(ECInstanceId, ChildCount, ModelId) as ( + SELECT + this.ECInstanceId, + COUNT(sc.ECInstanceId), + this.Model.Id + FROM + ${this._categoryClass} this + JOIN ${SUB_CATEGORY_CLASS} sc ON sc.Parent.Id = this.ECInstanceId + JOIN BisCore.Model m ON m.ECInstanceId = this.Model.Id + WHERE + NOT this.IsPrivate + AND (NOT m.IsPrivate OR m.ECClassId IS (BisCore.DictionaryModel)) + AND EXISTS (SELECT 1 FROM ${this._categoryElementClass} e WHERE e.Category.Id = this.ECInstanceId) + GROUP BY this.ECInstanceId + )`, + `${categoriesCteName}(ECInstanceId, CategoryModelId, CurrentModelId, ParentDefinitionContainerExists, ChildCount) AS ( + SELECT + this.ECInstanceId, + this.ModelId, + this.ModelId, + false, + this.ChildCount + FROM + ${categoriesWithChildCountCteName} this + + UNION ALL + + SELECT + ce.ECInstanceId, + ce.CategoryModelId, + pe.Model.Id, + true, + ce.ChildCount + FROM + ${categoriesCteName} ce + JOIN ${DEFINITION_CONTAINER_CLASS} pe ON ce.CurrentModelId = pe.ECInstanceId + WHERE + NOT pe.IsPrivate + )`, + ]; + const categoriesQuery = ` + SELECT + this.ECInstanceId id, + this.CategoryModelId modelId, + this.ParentDefinitionContainerExists parentDefinitionContainerExists, + this.ChildCount childCound + FROM + ${categoriesCteName} this + WHERE + this.CurrentModelId NOT IN (SELECT dm.ECInstanceId FROM ${DEFINITION_CONTAINER_CLASS} dm) + `; + for await (const row of this._queryExecutor.createQueryReader({ ctes, ecsql: categoriesQuery }, { rowFormat: "ECSqlPropertyNames", limit: "unbounded" })) { + yield { id: row.id, modelId: row.modelId, parentDefinitionContainerExists: row.parentDefinitionContainerExists, childCount: row.childCound }; + } + } + + private async *queryDefinitionContainers(categoriesModelIds: Id64Array): AsyncIterableIterator<{ id: Id64String; modelId: Id64String }> { + // DefinitionModel ECInstanceId will always be the same as modeled DefinitionContainer ECInstanceId, if this wasn't the case, we would need to do something like: + // JOIN BisCore.DefinitionModel dm ON dm.ECInstanceId = ${modelIdAccessor} + // JOIN BisCore.DefinitionModelBreaksDownDefinitionContainer dr ON dr.SourceECInstanceId = dm.ECInstanceId + // JOIN BisCore.DefinitionContainer dc ON dc.ECInstanceId = dr.TargetECInstanceId + const definitionContainersCteName = "DefinitionContainers"; + const ctes = [ + ` + ${definitionContainersCteName}(ECInstanceId, ModelId) AS ( + SELECT + dc.ECInstanceId, + dc.Model.Id + FROM + ${DEFINITION_CONTAINER_CLASS} dc + WHERE + dc.ECInstanceId IN (${categoriesModelIds.join(", ")}) + AND NOT dc.IsPrivate + + UNION ALL + + SELECT + pdc.ECInstanceId, + pdc.Model.Id + FROM + ${definitionContainersCteName} cdc + JOIN ${DEFINITION_CONTAINER_CLASS} pdc ON pdc.ECInstanceId = cdc.ModelId + WHERE + NOT pdc.IsPrivate + ) + `, + ]; + const definitionsQuery = ` + SELECT dc.ECInstanceId id, dc.ModelId modelId FROM ${definitionContainersCteName} dc GROUP BY dc.ECInstanceId + `; + for await (const row of this._queryExecutor.createQueryReader({ ctes, ecsql: definitionsQuery }, { rowFormat: "ECSqlPropertyNames", limit: "unbounded" })) { + yield { id: row.id, modelId: row.modelId }; + } + } + + private async *queryVisibleSubCategories(categoriesInfo: Id64Array): AsyncIterableIterator<{ id: Id64String; parentId: Id64String }> { + const definitionsQuery = ` + SELECT + sc.ECInstanceId id, + sc.Parent.Id categoryId + FROM + ${SUB_CATEGORY_CLASS} sc + WHERE + NOT sc.IsPrivate + AND sc.Parent.Id IN (${categoriesInfo.join(",")}) + `; + for await (const row of this._queryExecutor.createQueryReader({ ecsql: definitionsQuery }, { rowFormat: "ECSqlPropertyNames", limit: "unbounded" })) { + yield { id: row.id, parentId: row.categoryId }; + } + } + + private async getModelsCategoriesInfo() { + this._modelsCategoriesInfo ??= (async () => { + const allModelsCategories = new Map(); + for await (const queriedCategory of this.queryCategories()) { + let modelCategories = allModelsCategories.get(queriedCategory.modelId); + if (modelCategories === undefined) { + modelCategories = { parentDefinitionContainerExists: queriedCategory.parentDefinitionContainerExists, childCategories: [] }; + allModelsCategories.set(queriedCategory.modelId, modelCategories); + } + modelCategories.childCategories.push({ id: queriedCategory.id, childCound: queriedCategory.childCount }); + } + return allModelsCategories; + })(); + return this._modelsCategoriesInfo; + } + + private async getSubCategoriesInfo() { + this._subCategoriesInfo ??= (async () => { + const allSubCategories = new Map(); + const modelsCategoriesInfo = await this.getModelsCategoriesInfo(); + const categoriesWithMoreThanOneSubCategory = new Array(); + for (const modelCategoriesInfo of modelsCategoriesInfo.values()) { + categoriesWithMoreThanOneSubCategory.push( + ...modelCategoriesInfo.childCategories.filter((categoryInfo) => categoryInfo.childCound > 1).map((categoryInfo) => categoryInfo.id), + ); + } + + if (categoriesWithMoreThanOneSubCategory.length === 0) { + return allSubCategories; + } + + for await (const queriedSubCategory of this.queryVisibleSubCategories(categoriesWithMoreThanOneSubCategory)) { + allSubCategories.set(queriedSubCategory.id, { categoryId: queriedSubCategory.parentId }); + } + return allSubCategories; + })(); + return this._subCategoriesInfo; + } + + private async getDefinitionContainersInfo() { + this._definitionContainersInfo ??= (async () => { + const modelsCategoriesInfo = await this.getModelsCategoriesInfo(); + const definitionContainersInfo = new Map(); + if (modelsCategoriesInfo.size === 0) { + return definitionContainersInfo; + } + + const categoriesModelIds = [...modelsCategoriesInfo.keys()]; + + for await (const queriedDefinitionContainer of this.queryDefinitionContainers(categoriesModelIds)) { + const modelCategoriesInfo = modelsCategoriesInfo.get(queriedDefinitionContainer.id); + + definitionContainersInfo.set(queriedDefinitionContainer.id, { + childCategories: modelCategoriesInfo?.childCategories.map((childCategory) => childCategory.id) ?? [], + modelId: queriedDefinitionContainer.modelId, + childDefinitionContainers: [], + parentDefinitionContainerExists: false, + }); + } + + for (const [definitionContainerId, definitionContainerInfo] of definitionContainersInfo) { + const parentDefinitionContainer = definitionContainersInfo.get(definitionContainerInfo.modelId); + if (parentDefinitionContainer !== undefined) { + parentDefinitionContainer.childDefinitionContainers.push(definitionContainerId); + definitionContainerInfo.parentDefinitionContainerExists = true; + } + } + + return definitionContainersInfo; + })(); + return this._definitionContainersInfo; + } + + public async getDirectChildDefinitionContainersAndCategories( + parentDefinitionContainerIds: Id64Array, + ): Promise<{ categories: Id64Array; definitionContainers: Id64Array }> { + const definitionContainersInfo = await this.getDefinitionContainersInfo(); + + const result = { definitionContainers: new Array(), categories: new Array() }; + + parentDefinitionContainerIds.forEach((parentDefinitionContainerId) => { + const parentDefinitionContainerInfo = definitionContainersInfo.get(parentDefinitionContainerId); + if (parentDefinitionContainerInfo !== undefined) { + result.definitionContainers.push(...parentDefinitionContainerInfo.childDefinitionContainers); + result.categories.push(...parentDefinitionContainerInfo.childCategories); + } + }); + return result; + } + + public async getAllContainedCategories(definitionContainerId: Id64String): Promise { + const result = new Array(); + + const definitionContainersInfo = await this.getDefinitionContainersInfo(); + const definitionContainerInfo = definitionContainersInfo.get(definitionContainerId); + if (definitionContainerInfo === undefined) { + return result; + } + + const directChildCategories = definitionContainerInfo.childCategories; + const directChildDefinitionContainers = definitionContainerInfo.childDefinitionContainers; + + result.push(...directChildCategories); + for (const definitionContainer of directChildDefinitionContainers) { + const definitionContainerResult = await this.getAllContainedCategories(definitionContainer); + result.push(...definitionContainerResult); + } + + return result; + } + + public async getInstanceKeyPaths( + props: { categoryId: Id64String } | { definitionContainerId: Id64String } | { subCategoryId: Id64String }, + ): Promise { + if ("subCategoryId" in props) { + const subCategoriesInfo = await this.getSubCategoriesInfo(); + const subCategoryInfo = subCategoriesInfo.get(props.subCategoryId); + if (subCategoryInfo === undefined) { + return []; + } + return [...(await this.getInstanceKeyPaths({ categoryId: subCategoryInfo.categoryId })), { id: props.subCategoryId, className: SUB_CATEGORY_CLASS }]; + } + + if ("categoryId" in props) { + const modelsCategoriesInfo = await this.getModelsCategoriesInfo(); + for (const [modelId, modelCategoriesInfo] of modelsCategoriesInfo) { + if (modelCategoriesInfo.childCategories.find((childCategory) => childCategory.id === props.categoryId)) { + if (!modelCategoriesInfo.parentDefinitionContainerExists) { + return [{ id: props.categoryId, className: this._categoryClass }]; + } + + return [...(await this.getInstanceKeyPaths({ definitionContainerId: modelId })), { id: props.categoryId, className: this._categoryClass }]; + } + } + return []; + } + + const definitionContainersInfo = await this.getDefinitionContainersInfo(); + const definitionContainerInfo = definitionContainersInfo.get(props.definitionContainerId); + if (definitionContainerInfo === undefined) { + return []; + } + + if (!definitionContainerInfo.parentDefinitionContainerExists) { + return [{ id: props.definitionContainerId, className: DEFINITION_CONTAINER_CLASS }]; + } + + return [ + ...(await this.getInstanceKeyPaths({ definitionContainerId: definitionContainerInfo.modelId })), + { id: props.definitionContainerId, className: DEFINITION_CONTAINER_CLASS }, + ]; + } + + public async getAllDefinitionContainersAndCategories(): Promise<{ categories: Id64Array; definitionContainers: Id64Array }> { + const modelsCategoriesInfo = await this.getModelsCategoriesInfo(); + const definitionContainersInfo = await this.getDefinitionContainersInfo(); + const result = { definitionContainers: [...definitionContainersInfo.keys()], categories: new Array() }; + for (const modelCategoriesInfo of modelsCategoriesInfo.values()) { + result.categories.push(...modelCategoriesInfo.childCategories.map((childCategory) => childCategory.id)); + } + + return result; + } + + public async getRootDefinitionContainersAndCategories(): Promise<{ categories: Id64Array; definitionContainers: Id64Array }> { + const modelsCategoriesInfo = await this.getModelsCategoriesInfo(); + const result = { definitionContainers: new Array(), categories: new Array() }; + for (const modelCategoriesInfo of modelsCategoriesInfo.values()) { + if (!modelCategoriesInfo.parentDefinitionContainerExists) { + result.categories.push(...modelCategoriesInfo.childCategories.map((childCategory) => childCategory.id)); + } + } + + const definitionContainersInfo = await this.getDefinitionContainersInfo(); + + for (const [definitionContainerId, definitionContainerInfo] of definitionContainersInfo) { + if (!definitionContainerInfo.parentDefinitionContainerExists) { + result.definitionContainers.push(definitionContainerId); + } + } + return result; + } + + public async getSubCategories(categoryId: Id64String): Promise { + const subCategoriesInfo = await this.getSubCategoriesInfo(); + const result = new Array(); + for (const [subCategoryId, subCategoryInfo] of subCategoriesInfo) { + if (subCategoryInfo.categoryId === categoryId) { + result.push(subCategoryId); + } + } + return result; + } +} + +/** @internal */ +export function getClassesByView(viewType: "2d" | "3d") { + return viewType === "2d" + ? { categoryClass: "BisCore.DrawingCategory", categoryElementClass: "BisCore:GeometricElement2d" } + : { categoryClass: "BisCore.SpatialCategory", categoryElementClass: "BisCore:GeometricElement3d" }; +} diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeNode.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeNode.ts new file mode 100644 index 000000000..f6be2bfef --- /dev/null +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeNode.ts @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ + +import type { HierarchyNodeKey } from "@itwin/presentation-hierarchies"; + +interface CategoriesTreeNode { + key: HierarchyNodeKey; + extendedData?: { [id: string]: any }; +} + +/** + * @internal + */ +export namespace CategoriesTreeNode { + /** + * Determines if node represents a definitionContainer. + */ + export const isDefinitionContainerNode = (node: Pick) => + node.extendedData && "isDefinitionContainer" in node.extendedData && !!node.extendedData.isDefinitionContainer; + + /** + * Determines if node represents a category. + */ + export const isCategoryNode = (node: Pick) => + node.extendedData && "isCategory" in node.extendedData && !!node.extendedData.isCategory; + + /** + * Determines if node represents a subCategory. + */ + export const isSubCategoryNode = (node: Pick) => + node.extendedData && "isSubCategory" in node.extendedData && !!node.extendedData.isSubCategory; +} diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.ts new file mode 100644 index 000000000..acedd21b5 --- /dev/null +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.ts @@ -0,0 +1,239 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ + +import { BeEvent } from "@itwin/core-bentley"; +import { PerModelCategoryVisibility } from "@itwin/core-frontend"; +import { HierarchyNode } from "@itwin/presentation-hierarchies"; +import { enableCategoryDisplay, enableSubCategoryDisplay } from "../../common/CategoriesVisibilityUtils.js"; +import { createVisibilityStatus } from "../../common/Tooltip.js"; +import { CategoriesTreeNode } from "./CategoriesTreeNode.js"; + +import type { Id64String } from "@itwin/core-bentley"; +import type { Viewport } from "@itwin/core-frontend"; +import type { HierarchyVisibilityHandler, VisibilityStatus } from "../../common/UseHierarchyVisibility.js"; +import type { CategoriesTreeIdsCache } from "./CategoriesTreeIdsCache.js"; + +/** @internal */ +export interface CategoriesVisibilityHandlerProps { + viewport: Viewport; + idsCache: CategoriesTreeIdsCache; +} + +/** @internal */ +export class CategoriesVisibilityHandler implements HierarchyVisibilityHandler { + private _pendingVisibilityChange: any; + private _viewport: Viewport; + private _idsCache: CategoriesTreeIdsCache; + + constructor(props: CategoriesVisibilityHandlerProps) { + this._idsCache = props.idsCache; + this._viewport = props.viewport; + this._viewport.onDisplayStyleChanged.addListener(this.onDisplayStyleChanged); + this._viewport.onViewedCategoriesChanged.addListener(this.onViewedCategoriesChanged); + } + + public dispose() { + this[Symbol.dispose](); + } + + public [Symbol.dispose]() { + this._viewport.onDisplayStyleChanged.removeListener(this.onDisplayStyleChanged); + this._viewport.onViewedCategoriesChanged.removeListener(this.onViewedCategoriesChanged); + clearTimeout(this._pendingVisibilityChange); + } + + public onVisibilityChange = new BeEvent(); + + /** Returns visibility status of the tree node. */ + public async getVisibilityStatus(node: HierarchyNode): Promise { + if (!HierarchyNode.isInstancesNode(node)) { + return { state: "hidden", isDisabled: true }; + } + + if (CategoriesTreeNode.isSubCategoryNode(node)) { + return createVisibilityStatus(this.getSubCategoryVisibility(node)); + } + + if (CategoriesTreeNode.isCategoryNode(node)) { + return createVisibilityStatus(await this.getCategoryVisibility(CategoriesVisibilityHandler.getInstanceIdFromHierarchyNode(node))); + } + + if (CategoriesTreeNode.isDefinitionContainerNode(node)) { + return createVisibilityStatus(await this.getDefinitionContainerVisibility(node)); + } + + return { state: "hidden", isDisabled: true }; + } + + public async changeVisibility(node: HierarchyNode, on: boolean) { + if (!HierarchyNode.isInstancesNode(node)) { + return; + } + + if (CategoriesTreeNode.isCategoryNode(node)) { + return this.changeCategoryVisibility(node, on); + } + + if (CategoriesTreeNode.isSubCategoryNode(node)) { + return this.changeSubCategoryVisibility(node, on); + } + + if (CategoriesTreeNode.isDefinitionContainerNode(node)) { + return this.changeDefinitionContainerVisibility(node, on); + } + } + + private getSubCategoryVisibility(node: HierarchyNode): VisibilityStatus["state"] { + const parentCategoryId = node.extendedData?.categoryId; + if (!parentCategoryId) { + return "hidden"; + } + + const subcategoryId = CategoriesVisibilityHandler.getInstanceIdFromHierarchyNode(node); + const categoryOverrideResult = this.getCategoryVisibilityFromOverrides(parentCategoryId); + if (categoryOverrideResult === "hidden" || categoryOverrideResult === "visible") { + return categoryOverrideResult; + } + + const isVisible = this._viewport.isSubCategoryVisible(subcategoryId) && this._viewport.view.viewsCategory(parentCategoryId); + return isVisible ? "visible" : "hidden"; + } + + private async getDefinitionContainerVisibility(node: HierarchyNode): Promise { + const childrenResult = this._idsCache.getAllContainedCategories(CategoriesVisibilityHandler.getInstanceIdFromHierarchyNode(node)); + let hiddenCount = 0; + let visibleCount = 0; + for (const categoryId of await childrenResult) { + const categoryVisibility = await this.getCategoryVisibility(categoryId); + if (categoryVisibility === "partial") { + return "partial"; + } + + if (categoryVisibility === "hidden") { + ++hiddenCount; + } else { + ++visibleCount; + } + + if (hiddenCount > 0 && visibleCount > 0) { + return "partial"; + } + } + + return hiddenCount > 0 ? "hidden" : "visible"; + } + + private async getCategoryVisibility(categoryId: Id64String): Promise { + const overrideResult = this.getCategoryVisibilityFromOverrides(categoryId); + if (overrideResult !== "none") { + return overrideResult; + } + + if (!this._viewport.view.viewsCategory(categoryId)) { + return "hidden"; + } + + const subCategories = await this._idsCache.getSubCategories(categoryId); + let visibleSubCategoryCount = 0; + let hiddenSubCategoryCount = 0; + + for (const subCategory of subCategories) { + const isVisible = this._viewport.isSubCategoryVisible(subCategory); + if (isVisible) { + ++visibleSubCategoryCount; + } else { + ++hiddenSubCategoryCount; + } + if (hiddenSubCategoryCount > 0 && visibleSubCategoryCount > 0) { + return "partial"; + } + } + + return hiddenSubCategoryCount > 0 ? "hidden" : "visible"; + } + + private getCategoryVisibilityFromOverrides(categoryId: Id64String): VisibilityStatus["state"] | "none" { + let showOverrides = 0; + let hideOverrides = 0; + + for (const currentOverride of this._viewport.perModelCategoryVisibility) { + if (currentOverride.categoryId === categoryId) { + const currentVisibilityOverride = this._viewport.perModelCategoryVisibility.getOverride(currentOverride.modelId, currentOverride.categoryId); + + if (currentVisibilityOverride === PerModelCategoryVisibility.Override.Hide) { + ++hideOverrides; + } else if (currentVisibilityOverride === PerModelCategoryVisibility.Override.Show) { + ++showOverrides; + } + + if (showOverrides > 0 && hideOverrides > 0) { + return "partial"; + } + } + } + + if (showOverrides === 0 && hideOverrides === 0) { + return "none"; + } + + return showOverrides > 0 ? "visible" : "hidden"; + } + + private async changeSubCategoryVisibility(node: HierarchyNode, on: boolean) { + const subCategoryId = CategoriesVisibilityHandler.getInstanceIdFromHierarchyNode(node); + const parentCategoryId = node.extendedData?.categoryId; + + // make sure parent category is enabled + if (on && parentCategoryId) { + await this.changeCategoryState([parentCategoryId], true, false); + } + + this.changeSubCategoryState(subCategoryId, on); + } + + private async changeCategoryVisibility(node: HierarchyNode, on: boolean) { + const categoryId = CategoriesVisibilityHandler.getInstanceIdFromHierarchyNode(node); + return this.changeCategoryState([categoryId], on, on); + } + + private async changeDefinitionContainerVisibility(node: HierarchyNode, on: boolean) { + const definitionContainerId = CategoriesVisibilityHandler.getInstanceIdFromHierarchyNode(node); + const childCategories = await this._idsCache.getAllContainedCategories(definitionContainerId); + return this.changeCategoryState(childCategories, on, on); + } + + // eslint-disable-next-line @typescript-eslint/naming-convention + private onDisplayStyleChanged = () => { + this.onVisibilityChangeInternal(); + }; + + // eslint-disable-next-line @typescript-eslint/naming-convention + private onViewedCategoriesChanged = () => { + this.onVisibilityChangeInternal(); + }; + + private onVisibilityChangeInternal() { + if (this._pendingVisibilityChange) { + return; + } + + this._pendingVisibilityChange = setTimeout(() => { + this.onVisibilityChange.raiseEvent(); + this._pendingVisibilityChange = undefined; + }, 0); + } + + private static getInstanceIdFromHierarchyNode(node: HierarchyNode) { + return HierarchyNode.isInstancesNode(node) && node.key.instanceKeys.length > 0 ? node.key.instanceKeys[0].id : /* istanbul ignore next */ ""; + } + + private async changeCategoryState(ids: string[], enabled: boolean, enableAllSubCategories: boolean) { + await enableCategoryDisplay(this._viewport, ids, enabled, enableAllSubCategories); + } + + private changeSubCategoryState(key: string, enabled: boolean) { + enableSubCategoryDisplay(this._viewport, key, enabled); + } +} diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/common/CategoriesVisibilityUtils.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/common/CategoriesVisibilityUtils.ts index 766e3a6b5..b311d4596 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/common/CategoriesVisibilityUtils.ts +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/common/CategoriesVisibilityUtils.ts @@ -32,7 +32,7 @@ export async function toggleAllCategories(viewport: Viewport, display: boolean) /** * Gets ids of all categories from specified imodel and viewport. */ -export async function getCategories(viewport: Viewport) { +async function getCategories(viewport: Viewport) { const categories = await loadCategoriesFromViewport(viewport); return categories.map((category) => category.categoryId); } From 20f6c135ca9f8f74ab456eadad464fecf6007dea Mon Sep 17 00:00:00 2001 From: Jonas <100586436+JonasDov@users.noreply.github.com> Date: Fri, 31 Jan 2025 21:15:48 +0200 Subject: [PATCH 02/38] Update tests --- .../trees/categories-tree/CategoriesTreeDefinition.test.ts | 2 +- .../categories-tree/internal/CategoriesTreeIdsCache.test.ts | 5 ----- .../internal/CategoriesVisibilityHandler.test.ts | 6 +----- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/packages/itwin/tree-widget/src/test/trees/categories-tree/CategoriesTreeDefinition.test.ts b/packages/itwin/tree-widget/src/test/trees/categories-tree/CategoriesTreeDefinition.test.ts index a4db22fb0..9e6d6b249 100644 --- a/packages/itwin/tree-widget/src/test/trees/categories-tree/CategoriesTreeDefinition.test.ts +++ b/packages/itwin/tree-widget/src/test/trees/categories-tree/CategoriesTreeDefinition.test.ts @@ -219,7 +219,7 @@ describe("Categories tree", () => { children: [ NodeValidators.createForInstanceNode({ instanceKeys: [keys.definitionContainerChild], - label: "DcChild", + label: "DefinitionContainerChild", children: [ NodeValidators.createForInstanceNode({ instanceKeys: [keys.category], diff --git a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeIdsCache.test.ts b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeIdsCache.test.ts index 18b13f8e2..40759d986 100644 --- a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeIdsCache.test.ts +++ b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeIdsCache.test.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { expect } from "chai"; -import { join } from "path"; import { IModelReadRpcInterface, SnapshotIModelRpcInterface } from "@itwin/core-common"; import { ECSchemaRpcInterface } from "@itwin/ecschema-rpcinterface-common"; import { ECSchemaRpcImpl } from "@itwin/ecschema-rpcinterface-impl"; @@ -32,10 +31,6 @@ describe("CategoriesTreeIdsCache", () => { }, }, }, - testOutputDir: join(__dirname, "output"), - backendHostProps: { - cacheDir: join(__dirname, "cache"), - }, rpcs: [SnapshotIModelRpcInterface, IModelReadRpcInterface, PresentationRpcInterface, ECSchemaRpcInterface], }); // eslint-disable-next-line @itwin/no-internal diff --git a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts index fb80ec1c2..c962bad29 100644 --- a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts +++ b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts @@ -3,7 +3,7 @@ * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ -import { join } from "path"; + import { IModelReadRpcInterface, SnapshotIModelRpcInterface } from "@itwin/core-common"; import { IModelApp, NoRenderApp } from "@itwin/core-frontend"; import { ECSchemaRpcInterface } from "@itwin/ecschema-rpcinterface-common"; @@ -43,10 +43,6 @@ describe("CategoriesVisibilityHandler", () => { }, }, }, - testOutputDir: join(__dirname, "output"), - backendHostProps: { - cacheDir: join(__dirname, "cache"), - }, rpcs: [SnapshotIModelRpcInterface, IModelReadRpcInterface, PresentationRpcInterface, ECSchemaRpcInterface], }); // eslint-disable-next-line @itwin/no-internal From 27ff7689fe684cb270a06a602035905273a0a039 Mon Sep 17 00:00:00 2001 From: Jonas <100586436+JonasDov@users.noreply.github.com> Date: Fri, 31 Jan 2025 22:20:04 +0200 Subject: [PATCH 03/38] Fix tests --- .../trees/categories-tree/CategoriesTreeFiltering.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/itwin/tree-widget/src/test/trees/categories-tree/CategoriesTreeFiltering.test.ts b/packages/itwin/tree-widget/src/test/trees/categories-tree/CategoriesTreeFiltering.test.ts index adb0e8dd1..fe7203028 100644 --- a/packages/itwin/tree-widget/src/test/trees/categories-tree/CategoriesTreeFiltering.test.ts +++ b/packages/itwin/tree-widget/src/test/trees/categories-tree/CategoriesTreeFiltering.test.ts @@ -360,7 +360,7 @@ describe("Categories tree", () => { }); const imodelAccess = createIModelAccess(imodel); - const viewType = "3d"; + const viewType = "2d"; const idsCache = new CategoriesTreeIdsCache(imodelAccess, viewType); expect( @@ -402,7 +402,7 @@ describe("Categories tree", () => { }); const imodelAccess = createIModelAccess(imodel); - const viewType = "3d"; + const viewType = "2d"; const idsCache = new CategoriesTreeIdsCache(imodelAccess, viewType); expect( From 2f5ffa3d2bed4724150fe4f1f0de750da8277aee Mon Sep 17 00:00:00 2001 From: Jonas <100586436+JonasDov@users.noreply.github.com> Date: Mon, 3 Feb 2025 15:35:27 +0200 Subject: [PATCH 04/38] Fix typo: childCound -> childCount --- .../categories-tree/internal/CategoriesTreeIdsCache.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts index 000413118..34e640656 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts @@ -21,7 +21,7 @@ interface CategoriesInfo { interface CategoryInfo { id: Id64String; - childCound: number; + childCount: number; } interface SubCategoryInfo { @@ -105,14 +105,14 @@ export class CategoriesTreeIdsCache { this.ECInstanceId id, this.CategoryModelId modelId, this.ParentDefinitionContainerExists parentDefinitionContainerExists, - this.ChildCount childCound + this.ChildCount childCount FROM ${categoriesCteName} this WHERE this.CurrentModelId NOT IN (SELECT dm.ECInstanceId FROM ${DEFINITION_CONTAINER_CLASS} dm) `; for await (const row of this._queryExecutor.createQueryReader({ ctes, ecsql: categoriesQuery }, { rowFormat: "ECSqlPropertyNames", limit: "unbounded" })) { - yield { id: row.id, modelId: row.modelId, parentDefinitionContainerExists: row.parentDefinitionContainerExists, childCount: row.childCound }; + yield { id: row.id, modelId: row.modelId, parentDefinitionContainerExists: row.parentDefinitionContainerExists, childCount: row.childCount }; } } @@ -180,7 +180,7 @@ export class CategoriesTreeIdsCache { modelCategories = { parentDefinitionContainerExists: queriedCategory.parentDefinitionContainerExists, childCategories: [] }; allModelsCategories.set(queriedCategory.modelId, modelCategories); } - modelCategories.childCategories.push({ id: queriedCategory.id, childCound: queriedCategory.childCount }); + modelCategories.childCategories.push({ id: queriedCategory.id, childCount: queriedCategory.childCount }); } return allModelsCategories; })(); @@ -194,7 +194,7 @@ export class CategoriesTreeIdsCache { const categoriesWithMoreThanOneSubCategory = new Array(); for (const modelCategoriesInfo of modelsCategoriesInfo.values()) { categoriesWithMoreThanOneSubCategory.push( - ...modelCategoriesInfo.childCategories.filter((categoryInfo) => categoryInfo.childCound > 1).map((categoryInfo) => categoryInfo.id), + ...modelCategoriesInfo.childCategories.filter((categoryInfo) => categoryInfo.childCount > 1).map((categoryInfo) => categoryInfo.id), ); } From 0c35d45026830bddba25f7cff374812bd55bc93a Mon Sep 17 00:00:00 2001 From: Jonas <100586436+JonasDov@users.noreply.github.com> Date: Mon, 3 Feb 2025 15:44:18 +0200 Subject: [PATCH 05/38] Rename cte constants to Upper case for readability --- .../CategoriesTreeDefinition.ts | 20 ++++++++--------- .../internal/CategoriesTreeIdsCache.ts | 22 +++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts index 34db2681b..a56056f3c 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts @@ -246,9 +246,9 @@ async function createInstanceKeyPathsFromInstanceLabel( const { categoryClass } = getClassesByView(props.viewType); const adjustedLabel = props.label.replace(/[%_\\]/g, "\\$&"); - const definitionContainersCteName = "DefinitionContainersWithLabels"; - const categoriesCteName = "CategoriesWithLabels"; - const subCategoriesCteName = "SubCategoriesWithLabels"; + const CATEGORIES_WITH_LABELS_CTE = "CategoriesWithLabels"; + const SUBCATEGORIES_WITH_LABELS_CTE = "SubCategoriesWithLabels"; + const DEFINITION_CONTAINERS_WITH_LABELS_CTE = "DefinitionContainersWithLabels"; const [categoryLabelSelectClause, subCategoryLabelSelectClause, definitionContainerLabelSelectClause] = await Promise.all([ props.labelsFactory.createSelectClause({ classAlias: "this", @@ -266,7 +266,7 @@ async function createInstanceKeyPathsFromInstanceLabel( return lastValueFrom( defer(() => { const ctes = [ - `${categoriesCteName}(ClassName, ECInstanceId, ChildCount, DisplayLabel) as ( + `${CATEGORIES_WITH_LABELS_CTE}(ClassName, ECInstanceId, ChildCount, DisplayLabel) as ( SELECT 'c', this.ECInstanceId, @@ -279,7 +279,7 @@ async function createInstanceKeyPathsFromInstanceLabel( this.ECInstanceId IN (${categories.join(", ")}) GROUP BY this.ECInstanceId )`, - `${subCategoriesCteName}(ClassName, ECInstanceId, ParentId, DisplayLabel) as ( + `${SUBCATEGORIES_WITH_LABELS_CTE}(ClassName, ECInstanceId, ParentId, DisplayLabel) as ( SELECT 'sc', this.ECInstanceId, @@ -293,7 +293,7 @@ async function createInstanceKeyPathsFromInstanceLabel( )`, ...(definitionContainers.length > 0 ? [ - `${definitionContainersCteName}(ClassName, ECInstanceId, DisplayLabel) as ( + `${DEFINITION_CONTAINERS_WITH_LABELS_CTE}(ClassName, ECInstanceId, DisplayLabel) as ( SELECT 'dc', this.ECInstanceId, @@ -312,8 +312,8 @@ async function createInstanceKeyPathsFromInstanceLabel( sc.ClassName AS ClassName, sc.ECInstanceId AS ECInstanceId FROM - ${categoriesCteName} c - JOIN ${subCategoriesCteName} sc ON sc.ParentId = c.ECInstanceId + ${CATEGORIES_WITH_LABELS_CTE} c + JOIN ${SUBCATEGORIES_WITH_LABELS_CTE} sc ON sc.ParentId = c.ECInstanceId WHERE c.ChildCount > 1 AND sc.DisplayLabel LIKE '%' || ? || '%' ESCAPE '\\' @@ -324,7 +324,7 @@ async function createInstanceKeyPathsFromInstanceLabel( c.ClassName AS ClassName, c.ECInstanceId AS ECInstanceId FROM - ${categoriesCteName} c + ${CATEGORIES_WITH_LABELS_CTE} c WHERE c.DisplayLabel LIKE '%' || ? || '%' ESCAPE '\\' @@ -336,7 +336,7 @@ async function createInstanceKeyPathsFromInstanceLabel( dc.ClassName AS ClassName, dc.ECInstanceId AS ECInstanceId FROM - ${definitionContainersCteName} dc + ${DEFINITION_CONTAINERS_WITH_LABELS_CTE} dc WHERE dc.DisplayLabel LIKE '%' || ? || '%' ESCAPE '\\' ` diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts index 34e640656..a0d412d47 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts @@ -56,11 +56,11 @@ export class CategoriesTreeIdsCache { parentDefinitionContainerExists: boolean; childCount: number; }> { - const categoriesCteName = "AllVisibleCategories"; - const categoriesWithChildCountCteName = "CategoriesWithChildCount"; + const CATEGORIES_CTE = "AllVisibleCategories"; + const CATEGORIES_WITH_CHILD_COUNT_CTE = "CategoriesWithChildCount"; const ctes = [ - `${categoriesWithChildCountCteName}(ECInstanceId, ChildCount, ModelId) as ( + `${CATEGORIES_WITH_CHILD_COUNT_CTE}(ECInstanceId, ChildCount, ModelId) as ( SELECT this.ECInstanceId, COUNT(sc.ECInstanceId), @@ -75,7 +75,7 @@ export class CategoriesTreeIdsCache { AND EXISTS (SELECT 1 FROM ${this._categoryElementClass} e WHERE e.Category.Id = this.ECInstanceId) GROUP BY this.ECInstanceId )`, - `${categoriesCteName}(ECInstanceId, CategoryModelId, CurrentModelId, ParentDefinitionContainerExists, ChildCount) AS ( + `${CATEGORIES_CTE}(ECInstanceId, CategoryModelId, CurrentModelId, ParentDefinitionContainerExists, ChildCount) AS ( SELECT this.ECInstanceId, this.ModelId, @@ -83,7 +83,7 @@ export class CategoriesTreeIdsCache { false, this.ChildCount FROM - ${categoriesWithChildCountCteName} this + ${CATEGORIES_WITH_CHILD_COUNT_CTE} this UNION ALL @@ -94,7 +94,7 @@ export class CategoriesTreeIdsCache { true, ce.ChildCount FROM - ${categoriesCteName} ce + ${CATEGORIES_CTE} ce JOIN ${DEFINITION_CONTAINER_CLASS} pe ON ce.CurrentModelId = pe.ECInstanceId WHERE NOT pe.IsPrivate @@ -107,7 +107,7 @@ export class CategoriesTreeIdsCache { this.ParentDefinitionContainerExists parentDefinitionContainerExists, this.ChildCount childCount FROM - ${categoriesCteName} this + ${CATEGORIES_CTE} this WHERE this.CurrentModelId NOT IN (SELECT dm.ECInstanceId FROM ${DEFINITION_CONTAINER_CLASS} dm) `; @@ -121,10 +121,10 @@ export class CategoriesTreeIdsCache { // JOIN BisCore.DefinitionModel dm ON dm.ECInstanceId = ${modelIdAccessor} // JOIN BisCore.DefinitionModelBreaksDownDefinitionContainer dr ON dr.SourceECInstanceId = dm.ECInstanceId // JOIN BisCore.DefinitionContainer dc ON dc.ECInstanceId = dr.TargetECInstanceId - const definitionContainersCteName = "DefinitionContainers"; + const DEFINITION_CONTAINERS_CTE = "DefinitionContainers"; const ctes = [ ` - ${definitionContainersCteName}(ECInstanceId, ModelId) AS ( + ${DEFINITION_CONTAINERS_CTE}(ECInstanceId, ModelId) AS ( SELECT dc.ECInstanceId, dc.Model.Id @@ -140,7 +140,7 @@ export class CategoriesTreeIdsCache { pdc.ECInstanceId, pdc.Model.Id FROM - ${definitionContainersCteName} cdc + ${DEFINITION_CONTAINERS_CTE} cdc JOIN ${DEFINITION_CONTAINER_CLASS} pdc ON pdc.ECInstanceId = cdc.ModelId WHERE NOT pdc.IsPrivate @@ -148,7 +148,7 @@ export class CategoriesTreeIdsCache { `, ]; const definitionsQuery = ` - SELECT dc.ECInstanceId id, dc.ModelId modelId FROM ${definitionContainersCteName} dc GROUP BY dc.ECInstanceId + SELECT dc.ECInstanceId id, dc.ModelId modelId FROM ${DEFINITION_CONTAINERS_CTE} dc GROUP BY dc.ECInstanceId `; for await (const row of this._queryExecutor.createQueryReader({ ctes, ecsql: definitionsQuery }, { rowFormat: "ECSqlPropertyNames", limit: "unbounded" })) { yield { id: row.id, modelId: row.modelId }; From 9ab2b2d7db80b0fdda5037c1ce9ee924bb2bca27 Mon Sep 17 00:00:00 2001 From: Jonas <100586436+JonasDov@users.noreply.github.com> Date: Mon, 3 Feb 2025 16:38:54 +0200 Subject: [PATCH 06/38] change getAllContainedCategories to accept Id64Array --- .../internal/CategoriesTreeIdsCache.test.ts | 8 ++-- .../internal/CategoriesTreeIdsCache.ts | 28 ++++++------- .../internal/CategoriesVisibilityHandler.ts | 42 ++++++++++--------- 3 files changed, 41 insertions(+), 37 deletions(-) diff --git a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeIdsCache.test.ts b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeIdsCache.test.ts index 40759d986..614524dbe 100644 --- a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeIdsCache.test.ts +++ b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeIdsCache.test.ts @@ -183,7 +183,7 @@ describe("CategoriesTreeIdsCache", () => { return { definitionContainer }; }); const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); - expect(await idsCache.getAllContainedCategories(keys.definitionContainer.id)).to.deep.eq([]); + expect(await idsCache.getAllContainedCategories([keys.definitionContainer.id])).to.deep.eq([]); }); it("when definitionContainer contains definitionContainer that has categories", async function () { @@ -199,7 +199,7 @@ describe("CategoriesTreeIdsCache", () => { return { definitionContainerRoot, category }; }); const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); - expect(await idsCache.getAllContainedCategories(keys.definitionContainerRoot.id)).to.deep.eq([keys.category.id]); + expect(await idsCache.getAllContainedCategories([keys.definitionContainerRoot.id])).to.deep.eq([keys.category.id]); }); it("when definitionContainer contains categories", async function () { @@ -213,7 +213,7 @@ describe("CategoriesTreeIdsCache", () => { return { definitionContainer, category }; }); const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); - expect(await idsCache.getAllContainedCategories(keys.definitionContainer.id)).to.deep.eq([keys.category.id]); + expect(await idsCache.getAllContainedCategories([keys.definitionContainer.id])).to.deep.eq([keys.category.id]); }); it("when definitionContainer contains categories and definitionContainers that contain categories", async function () { @@ -232,7 +232,7 @@ describe("CategoriesTreeIdsCache", () => { return { definitionContainerRoot, directCategory, indirectCategory }; }); const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); - const result = await idsCache.getAllContainedCategories(keys.definitionContainerRoot.id); + const result = await idsCache.getAllContainedCategories([keys.definitionContainerRoot.id]); const expectedResult = [keys.indirectCategory.id, keys.directCategory.id]; expect(result.every((id) => expectedResult.includes(id))).to.be.true; }); diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts index a0d412d47..ccf0b322a 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts @@ -102,7 +102,7 @@ export class CategoriesTreeIdsCache { ]; const categoriesQuery = ` SELECT - this.ECInstanceId id, + this.ECInstanceId id, this.CategoryModelId modelId, this.ParentDefinitionContainerExists parentDefinitionContainerExists, this.ChildCount childCount @@ -261,22 +261,22 @@ export class CategoriesTreeIdsCache { return result; } - public async getAllContainedCategories(definitionContainerId: Id64String): Promise { + public async getAllContainedCategories(definitionContainerIds: Id64Array): Promise { const result = new Array(); const definitionContainersInfo = await this.getDefinitionContainersInfo(); - const definitionContainerInfo = definitionContainersInfo.get(definitionContainerId); - if (definitionContainerInfo === undefined) { - return result; - } - - const directChildCategories = definitionContainerInfo.childCategories; - const directChildDefinitionContainers = definitionContainerInfo.childDefinitionContainers; - - result.push(...directChildCategories); - for (const definitionContainer of directChildDefinitionContainers) { - const definitionContainerResult = await this.getAllContainedCategories(definitionContainer); - result.push(...definitionContainerResult); + const indirectCategories = await Promise.all( + definitionContainerIds.map(async (definitionContainerId) => { + const definitionContainerInfo = definitionContainersInfo.get(definitionContainerId); + if (definitionContainerInfo === undefined) { + return []; + } + result.push(...definitionContainerInfo.childCategories); + return this.getAllContainedCategories(definitionContainerInfo.childDefinitionContainers); + }), + ); + for (const categories of indirectCategories) { + result.push(...categories); } return result; diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.ts index acedd21b5..60e683051 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.ts +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.ts @@ -10,7 +10,7 @@ import { enableCategoryDisplay, enableSubCategoryDisplay } from "../../common/Ca import { createVisibilityStatus } from "../../common/Tooltip.js"; import { CategoriesTreeNode } from "./CategoriesTreeNode.js"; -import type { Id64String } from "@itwin/core-bentley"; +import type { Id64Array } from "@itwin/core-bentley"; import type { Viewport } from "@itwin/core-frontend"; import type { HierarchyVisibilityHandler, VisibilityStatus } from "../../common/UseHierarchyVisibility.js"; import type { CategoriesTreeIdsCache } from "./CategoriesTreeIdsCache.js"; @@ -57,7 +57,7 @@ export class CategoriesVisibilityHandler implements HierarchyVisibilityHandler { } if (CategoriesTreeNode.isCategoryNode(node)) { - return createVisibilityStatus(await this.getCategoryVisibility(CategoriesVisibilityHandler.getInstanceIdFromHierarchyNode(node))); + return createVisibilityStatus(await this.getCategoriesVisibility(CategoriesVisibilityHandler.getInstanceIdsFromHierarchyNode(node))); } if (CategoriesTreeNode.isDefinitionContainerNode(node)) { @@ -91,22 +91,22 @@ export class CategoriesVisibilityHandler implements HierarchyVisibilityHandler { return "hidden"; } - const subcategoryId = CategoriesVisibilityHandler.getInstanceIdFromHierarchyNode(node); const categoryOverrideResult = this.getCategoryVisibilityFromOverrides(parentCategoryId); if (categoryOverrideResult === "hidden" || categoryOverrideResult === "visible") { return categoryOverrideResult; } - const isVisible = this._viewport.isSubCategoryVisible(subcategoryId) && this._viewport.view.viewsCategory(parentCategoryId); + const subcategoryIds = CategoriesVisibilityHandler.getInstanceIdsFromHierarchyNode(node); + const isVisible = subcategoryIds.every((id) => this._viewport.isSubCategoryVisible(id)) && this._viewport.view.viewsCategory(parentCategoryId); return isVisible ? "visible" : "hidden"; } private async getDefinitionContainerVisibility(node: HierarchyNode): Promise { - const childrenResult = this._idsCache.getAllContainedCategories(CategoriesVisibilityHandler.getInstanceIdFromHierarchyNode(node)); + const childrenResult = this._idsCache.getAllContainedCategories(CategoriesVisibilityHandler.getInstanceIdsFromHierarchyNode(node)); let hiddenCount = 0; let visibleCount = 0; for (const categoryId of await childrenResult) { - const categoryVisibility = await this.getCategoryVisibility(categoryId); + const categoryVisibility = await this.getCategoriesVisibility([categoryId]); if (categoryVisibility === "partial") { return "partial"; } @@ -125,17 +125,17 @@ export class CategoriesVisibilityHandler implements HierarchyVisibilityHandler { return hiddenCount > 0 ? "hidden" : "visible"; } - private async getCategoryVisibility(categoryId: Id64String): Promise { - const overrideResult = this.getCategoryVisibilityFromOverrides(categoryId); + private async getCategoriesVisibility(categoryIds: Id64Array): Promise { + const overrideResult = this.getCategoryVisibilityFromOverrides(categoryIds); if (overrideResult !== "none") { return overrideResult; } - if (!this._viewport.view.viewsCategory(categoryId)) { + if (!categoryIds.every((id) => this._viewport.view.viewsCategory(id))) { return "hidden"; } - const subCategories = await this._idsCache.getSubCategories(categoryId); + const subCategories = (await Promise.all(categoryIds.map(async (id) => this._idsCache.getSubCategories(id)))).reduce((acc, val) => acc.concat(val), []); let visibleSubCategoryCount = 0; let hiddenSubCategoryCount = 0; @@ -154,12 +154,12 @@ export class CategoriesVisibilityHandler implements HierarchyVisibilityHandler { return hiddenSubCategoryCount > 0 ? "hidden" : "visible"; } - private getCategoryVisibilityFromOverrides(categoryId: Id64String): VisibilityStatus["state"] | "none" { + private getCategoryVisibilityFromOverrides(categoryIds: Id64Array): VisibilityStatus["state"] | "none" { let showOverrides = 0; let hideOverrides = 0; for (const currentOverride of this._viewport.perModelCategoryVisibility) { - if (currentOverride.categoryId === categoryId) { + if (categoryIds.includes(currentOverride.categoryId)) { const currentVisibilityOverride = this._viewport.perModelCategoryVisibility.getOverride(currentOverride.modelId, currentOverride.categoryId); if (currentVisibilityOverride === PerModelCategoryVisibility.Override.Hide) { @@ -182,7 +182,6 @@ export class CategoriesVisibilityHandler implements HierarchyVisibilityHandler { } private async changeSubCategoryVisibility(node: HierarchyNode, on: boolean) { - const subCategoryId = CategoriesVisibilityHandler.getInstanceIdFromHierarchyNode(node); const parentCategoryId = node.extendedData?.categoryId; // make sure parent category is enabled @@ -190,16 +189,19 @@ export class CategoriesVisibilityHandler implements HierarchyVisibilityHandler { await this.changeCategoryState([parentCategoryId], true, false); } - this.changeSubCategoryState(subCategoryId, on); + const subCategoryIds = CategoriesVisibilityHandler.getInstanceIdsFromHierarchyNode(node); + subCategoryIds.forEach((id) => { + this.changeSubCategoryState(id, on); + }); } private async changeCategoryVisibility(node: HierarchyNode, on: boolean) { - const categoryId = CategoriesVisibilityHandler.getInstanceIdFromHierarchyNode(node); - return this.changeCategoryState([categoryId], on, on); + const categoryIds = CategoriesVisibilityHandler.getInstanceIdsFromHierarchyNode(node); + return this.changeCategoryState(categoryIds, on, on); } private async changeDefinitionContainerVisibility(node: HierarchyNode, on: boolean) { - const definitionContainerId = CategoriesVisibilityHandler.getInstanceIdFromHierarchyNode(node); + const definitionContainerId = CategoriesVisibilityHandler.getInstanceIdsFromHierarchyNode(node); const childCategories = await this._idsCache.getAllContainedCategories(definitionContainerId); return this.changeCategoryState(childCategories, on, on); } @@ -225,8 +227,10 @@ export class CategoriesVisibilityHandler implements HierarchyVisibilityHandler { }, 0); } - private static getInstanceIdFromHierarchyNode(node: HierarchyNode) { - return HierarchyNode.isInstancesNode(node) && node.key.instanceKeys.length > 0 ? node.key.instanceKeys[0].id : /* istanbul ignore next */ ""; + private static getInstanceIdsFromHierarchyNode(node: HierarchyNode) { + return HierarchyNode.isInstancesNode(node) && node.key.instanceKeys.length > 0 + ? node.key.instanceKeys.map((instanceKey) => instanceKey.id) + : /* istanbul ignore next */ []; } private async changeCategoryState(ids: string[], enabled: boolean, enableAllSubCategories: boolean) { From 7542a02d8eaa426b4b12e354bd0e75bcfa561eef Mon Sep 17 00:00:00 2001 From: JonasDov <100586436+JonasDov@users.noreply.github.com> Date: Mon, 3 Feb 2025 16:39:16 +0200 Subject: [PATCH 07/38] Update packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts Co-authored-by: Grigas <35135765+grigasp@users.noreply.github.com> --- .../categories-tree/internal/CategoriesTreeIdsCache.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts index 000413118..ed6278ed3 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts @@ -325,8 +325,10 @@ export class CategoriesTreeIdsCache { } public async getAllDefinitionContainersAndCategories(): Promise<{ categories: Id64Array; definitionContainers: Id64Array }> { - const modelsCategoriesInfo = await this.getModelsCategoriesInfo(); - const definitionContainersInfo = await this.getDefinitionContainersInfo(); + const [modelsCategoriesInfo, definitionContainersInfo] = await Promise.all([ + this.getModelsCategoriesInfo(), + this.getDefinitionContainersInfo(), + ]); const result = { definitionContainers: [...definitionContainersInfo.keys()], categories: new Array() }; for (const modelCategoriesInfo of modelsCategoriesInfo.values()) { result.categories.push(...modelCategoriesInfo.childCategories.map((childCategory) => childCategory.id)); From 3a396eb4504e9ec1fd6fb2b9a7a72de2dfd2b046 Mon Sep 17 00:00:00 2001 From: JonasDov <100586436+JonasDov@users.noreply.github.com> Date: Mon, 3 Feb 2025 16:39:25 +0200 Subject: [PATCH 08/38] Update packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts Co-authored-by: Grigas <35135765+grigasp@users.noreply.github.com> --- .../trees/categories-tree/internal/CategoriesTreeIdsCache.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts index ed6278ed3..744fd5d52 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts @@ -102,7 +102,7 @@ export class CategoriesTreeIdsCache { ]; const categoriesQuery = ` SELECT - this.ECInstanceId id, + this.ECInstanceId id, this.CategoryModelId modelId, this.ParentDefinitionContainerExists parentDefinitionContainerExists, this.ChildCount childCound From 8b0833177ad76f43748a3fce1350ecd3a9a9b072 Mon Sep 17 00:00:00 2001 From: JonasDov <100586436+JonasDov@users.noreply.github.com> Date: Mon, 3 Feb 2025 16:39:43 +0200 Subject: [PATCH 09/38] Update packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts Co-authored-by: Grigas <35135765+grigasp@users.noreply.github.com> --- .../trees/categories-tree/internal/CategoriesTreeIdsCache.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts index 744fd5d52..bf236a83d 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts @@ -371,6 +371,6 @@ export class CategoriesTreeIdsCache { /** @internal */ export function getClassesByView(viewType: "2d" | "3d") { return viewType === "2d" - ? { categoryClass: "BisCore.DrawingCategory", categoryElementClass: "BisCore:GeometricElement2d" } - : { categoryClass: "BisCore.SpatialCategory", categoryElementClass: "BisCore:GeometricElement3d" }; + ? { categoryClass: "BisCore.DrawingCategory", categoryElementClass: "BisCore.GeometricElement2d" } + : { categoryClass: "BisCore.SpatialCategory", categoryElementClass: "BisCore.GeometricElement3d" }; } From 502a66ed8eef6d6b49b297c113adae7a3b1472d0 Mon Sep 17 00:00:00 2001 From: JonasDov <100586436+JonasDov@users.noreply.github.com> Date: Mon, 3 Feb 2025 16:40:00 +0200 Subject: [PATCH 10/38] Update packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeNode.ts Co-authored-by: Grigas <35135765+grigasp@users.noreply.github.com> --- .../trees/categories-tree/internal/CategoriesTreeNode.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeNode.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeNode.ts index f6be2bfef..c048fb9b8 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeNode.ts +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeNode.ts @@ -15,7 +15,7 @@ interface CategoriesTreeNode { */ export namespace CategoriesTreeNode { /** - * Determines if node represents a definitionContainer. + * Determines if node represents a definition container. */ export const isDefinitionContainerNode = (node: Pick) => node.extendedData && "isDefinitionContainer" in node.extendedData && !!node.extendedData.isDefinitionContainer; From 58504d7a3ebfd9c349012bfed2f9a4f0663c429a Mon Sep 17 00:00:00 2001 From: JonasDov <100586436+JonasDov@users.noreply.github.com> Date: Mon, 3 Feb 2025 16:40:17 +0200 Subject: [PATCH 11/38] Update packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeNode.ts Co-authored-by: Grigas <35135765+grigasp@users.noreply.github.com> --- .../trees/categories-tree/internal/CategoriesTreeNode.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeNode.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeNode.ts index c048fb9b8..c8279c94f 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeNode.ts +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeNode.ts @@ -27,7 +27,7 @@ export namespace CategoriesTreeNode { node.extendedData && "isCategory" in node.extendedData && !!node.extendedData.isCategory; /** - * Determines if node represents a subCategory. + * Determines if node represents a sub-category. */ export const isSubCategoryNode = (node: Pick) => node.extendedData && "isSubCategory" in node.extendedData && !!node.extendedData.isSubCategory; From 4195e852b230cb1647b5f6933942166006bc03ea Mon Sep 17 00:00:00 2001 From: JonasDov <100586436+JonasDov@users.noreply.github.com> Date: Mon, 3 Feb 2025 16:41:35 +0200 Subject: [PATCH 12/38] Update packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeIdsCache.test.ts Co-authored-by: Grigas <35135765+grigasp@users.noreply.github.com> --- .../categories-tree/internal/CategoriesTreeIdsCache.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeIdsCache.test.ts b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeIdsCache.test.ts index 40759d986..fdbee6b0a 100644 --- a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeIdsCache.test.ts +++ b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeIdsCache.test.ts @@ -59,7 +59,7 @@ describe("CategoriesTreeIdsCache", () => { }); }); - it("when definitionContainer contains definitionContainer", async function () { + it("returns empty lists when definition container contains empty definition container", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); From c40294ed1f2071e950dc321fbcc06881139064be Mon Sep 17 00:00:00 2001 From: JonasDov <100586436+JonasDov@users.noreply.github.com> Date: Mon, 3 Feb 2025 16:50:32 +0200 Subject: [PATCH 13/38] Update packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts Co-authored-by: Grigas <35135765+grigasp@users.noreply.github.com> --- .../categories-tree/CategoriesTreeDefinition.ts | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts index 34db2681b..f5ab125fa 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts @@ -250,19 +250,10 @@ async function createInstanceKeyPathsFromInstanceLabel( const categoriesCteName = "CategoriesWithLabels"; const subCategoriesCteName = "SubCategoriesWithLabels"; const [categoryLabelSelectClause, subCategoryLabelSelectClause, definitionContainerLabelSelectClause] = await Promise.all([ - props.labelsFactory.createSelectClause({ - classAlias: "this", - className: categoryClass, - }), - props.labelsFactory.createSelectClause({ - classAlias: "this", - className: SUB_CATEGORY_CLASS, - }), - props.labelsFactory.createSelectClause({ - classAlias: "this", - className: DEFINITION_CONTAINER_CLASS, - }), - ]); + categoryClass, + SUB_CATEGORY_CLASS, + DEFINITION_CONTAINER_CLASS, + ].map(async (className) => props.labelsFactory.createSelectClause({ classAlias: "this", className }))); return lastValueFrom( defer(() => { const ctes = [ From 2a34afe7edc505aebd0672b3d018b29641e1849e Mon Sep 17 00:00:00 2001 From: Jonas <100586436+JonasDov@users.noreply.github.com> Date: Mon, 3 Feb 2025 16:54:45 +0200 Subject: [PATCH 14/38] Use Promise.all in getRootDefinitionContainersAndCategories --- .../trees/categories-tree/internal/CategoriesTreeIdsCache.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts index 5f81b8ccd..affbc98ec 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts @@ -338,7 +338,7 @@ export class CategoriesTreeIdsCache { } public async getRootDefinitionContainersAndCategories(): Promise<{ categories: Id64Array; definitionContainers: Id64Array }> { - const modelsCategoriesInfo = await this.getModelsCategoriesInfo(); + const [modelsCategoriesInfo, definitionContainersInfo] = await Promise.all([this.getModelsCategoriesInfo(), this.getDefinitionContainersInfo()]); const result = { definitionContainers: new Array(), categories: new Array() }; for (const modelCategoriesInfo of modelsCategoriesInfo.values()) { if (!modelCategoriesInfo.parentDefinitionContainerExists) { @@ -346,8 +346,6 @@ export class CategoriesTreeIdsCache { } } - const definitionContainersInfo = await this.getDefinitionContainersInfo(); - for (const [definitionContainerId, definitionContainerInfo] of definitionContainersInfo) { if (!definitionContainerInfo.parentDefinitionContainerExists) { result.definitionContainers.push(definitionContainerId); From 7e04bc6f6aa7c38d83b96664d4d9d0a61de90f60 Mon Sep 17 00:00:00 2001 From: Jonas <100586436+JonasDov@users.noreply.github.com> Date: Mon, 3 Feb 2025 16:57:36 +0200 Subject: [PATCH 15/38] Make as upper case --- .../trees/categories-tree/CategoriesTreeDefinition.ts | 6 +++--- .../categories-tree/internal/CategoriesTreeIdsCache.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts index d3b2a8b1f..3aba2be9a 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts @@ -257,7 +257,7 @@ async function createInstanceKeyPathsFromInstanceLabel( return lastValueFrom( defer(() => { const ctes = [ - `${CATEGORIES_WITH_LABELS_CTE}(ClassName, ECInstanceId, ChildCount, DisplayLabel) as ( + `${CATEGORIES_WITH_LABELS_CTE}(ClassName, ECInstanceId, ChildCount, DisplayLabel) AS ( SELECT 'c', this.ECInstanceId, @@ -270,7 +270,7 @@ async function createInstanceKeyPathsFromInstanceLabel( this.ECInstanceId IN (${categories.join(", ")}) GROUP BY this.ECInstanceId )`, - `${SUBCATEGORIES_WITH_LABELS_CTE}(ClassName, ECInstanceId, ParentId, DisplayLabel) as ( + `${SUBCATEGORIES_WITH_LABELS_CTE}(ClassName, ECInstanceId, ParentId, DisplayLabel) AS ( SELECT 'sc', this.ECInstanceId, @@ -284,7 +284,7 @@ async function createInstanceKeyPathsFromInstanceLabel( )`, ...(definitionContainers.length > 0 ? [ - `${DEFINITION_CONTAINERS_WITH_LABELS_CTE}(ClassName, ECInstanceId, DisplayLabel) as ( + `${DEFINITION_CONTAINERS_WITH_LABELS_CTE}(ClassName, ECInstanceId, DisplayLabel) AS ( SELECT 'dc', this.ECInstanceId, diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts index affbc98ec..c2453beb7 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts @@ -60,7 +60,7 @@ export class CategoriesTreeIdsCache { const CATEGORIES_WITH_CHILD_COUNT_CTE = "CategoriesWithChildCount"; const ctes = [ - `${CATEGORIES_WITH_CHILD_COUNT_CTE}(ECInstanceId, ChildCount, ModelId) as ( + `${CATEGORIES_WITH_CHILD_COUNT_CTE}(ECInstanceId, ChildCount, ModelId) AS ( SELECT this.ECInstanceId, COUNT(sc.ECInstanceId), From 903311680f92a715cbd13ce691b9f4daa6a2e372 Mon Sep 17 00:00:00 2001 From: Jonas <100586436+JonasDov@users.noreply.github.com> Date: Mon, 3 Feb 2025 16:58:46 +0200 Subject: [PATCH 16/38] Move await in propper position --- .../categories-tree/internal/CategoriesVisibilityHandler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.ts index 60e683051..33d63fab3 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.ts +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.ts @@ -102,10 +102,10 @@ export class CategoriesVisibilityHandler implements HierarchyVisibilityHandler { } private async getDefinitionContainerVisibility(node: HierarchyNode): Promise { - const childrenResult = this._idsCache.getAllContainedCategories(CategoriesVisibilityHandler.getInstanceIdsFromHierarchyNode(node)); + const childrenResult = await this._idsCache.getAllContainedCategories(CategoriesVisibilityHandler.getInstanceIdsFromHierarchyNode(node)); let hiddenCount = 0; let visibleCount = 0; - for (const categoryId of await childrenResult) { + for (const categoryId of childrenResult) { const categoryVisibility = await this.getCategoriesVisibility([categoryId]); if (categoryVisibility === "partial") { return "partial"; From 3c30e77e0b6245b79891c55b370016757688a6c8 Mon Sep 17 00:00:00 2001 From: Jonas <100586436+JonasDov@users.noreply.github.com> Date: Mon, 3 Feb 2025 18:24:47 +0200 Subject: [PATCH 17/38] change collect -> mergeMap + toArray --- .../categories-tree/CategoriesTreeDefinition.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts index 3aba2be9a..5f03eebd2 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts @@ -6,7 +6,6 @@ import { defer, EMPTY, from, lastValueFrom, map, mergeMap, toArray } from "rxjs"; import { createNodesQueryClauseFactory, createPredicateBasedHierarchyDefinition } from "@itwin/presentation-hierarchies"; import { createBisInstanceLabelSelectClauseFactory, ECSql } from "@itwin/presentation-shared"; -import { collect } from "../common/Rxjs.js"; import { FilterLimitExceededError } from "../common/TreeErrors.js"; import { DEFINITION_CONTAINER_CLASS, getClassesByView, SUB_CATEGORY_CLASS } from "./internal/CategoriesTreeIdsCache.js"; @@ -249,11 +248,11 @@ async function createInstanceKeyPathsFromInstanceLabel( const CATEGORIES_WITH_LABELS_CTE = "CategoriesWithLabels"; const SUBCATEGORIES_WITH_LABELS_CTE = "SubCategoriesWithLabels"; const DEFINITION_CONTAINERS_WITH_LABELS_CTE = "DefinitionContainersWithLabels"; - const [categoryLabelSelectClause, subCategoryLabelSelectClause, definitionContainerLabelSelectClause] = await Promise.all([ - categoryClass, - SUB_CATEGORY_CLASS, - DEFINITION_CONTAINER_CLASS, - ].map(async (className) => props.labelsFactory.createSelectClause({ classAlias: "this", className }))); + const [categoryLabelSelectClause, subCategoryLabelSelectClause, definitionContainerLabelSelectClause] = await Promise.all( + [categoryClass, SUB_CATEGORY_CLASS, DEFINITION_CONTAINER_CLASS].map(async (className) => + props.labelsFactory.createSelectClause({ classAlias: "this", className }), + ), + ); return lastValueFrom( defer(() => { const ctes = [ @@ -353,9 +352,8 @@ async function createInstanceKeyPathsFromInstanceLabel( }), ), toArray(), - mergeMap(async (targetItems) => { - return collect(createInstanceKeyPathsFromTargetItems({ ...props, targetItems })); - }), + mergeMap((targetItems): Observable => createInstanceKeyPathsFromTargetItems({ ...props, targetItems })), + toArray(), ), ); } From c5a6120fa1254d39972b2e35fa146e10f199b9e1 Mon Sep 17 00:00:00 2001 From: Jonas <100586436+JonasDov@users.noreply.github.com> Date: Mon, 3 Feb 2025 18:39:28 +0200 Subject: [PATCH 18/38] Remove validateViewsCalls --- .../CategoriesVisibilityHandler.test.ts | 21 ------------------- .../trees/categories-tree/internal/Utils.ts | 16 -------------- 2 files changed, 37 deletions(-) diff --git a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts index c962bad29..8a952da43 100644 --- a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts +++ b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts @@ -115,7 +115,6 @@ describe("CategoriesVisibilityHandler", () => { visibilityExpectations: VisibilityExpectations.all("hidden"), nodesToExpect, }); - ViewportMock.validateViewsCalls(viewport, [keys.category.id], [keys.subCategory.id]); }); describe("enabling visibility", () => { @@ -159,7 +158,6 @@ describe("CategoriesVisibilityHandler", () => { visibilityExpectations: VisibilityExpectations.all("visible"), nodesToExpect, }); - ViewportMock.validateViewsCalls(viewport, [keys.directCategory.id, keys.indirectCategory.id], [keys.indirectSubCategory.id]); ViewportMock.validateChangesCalls( viewport, [{ categoriesToChange: [keys.directCategory.id, keys.indirectCategory.id], isVisible: true, enableAllSubCategories: true }], @@ -223,7 +221,6 @@ describe("CategoriesVisibilityHandler", () => { }, nodesToExpect, }); - ViewportMock.validateViewsCalls(viewport, [keys.category2.id, keys.indirectCategory.id], [keys.subCategory2.id, keys.indirectSubCategory.id]); ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.indirectCategory.id], isVisible: true, enableAllSubCategories: true }], []); }); @@ -258,7 +255,6 @@ describe("CategoriesVisibilityHandler", () => { }, nodesToExpect, }); - ViewportMock.validateViewsCalls(viewport, [keys.directCategory.id, keys.indirectCategory.id], []); ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.indirectCategory.id], isVisible: true, enableAllSubCategories: true }], []); }); @@ -309,8 +305,6 @@ describe("CategoriesVisibilityHandler", () => { }, nodesToExpect, }); - - ViewportMock.validateViewsCalls(viewport, [keys.indirectCategory2.id, keys.indirectCategory.id], []); ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.indirectCategory.id], isVisible: true, enableAllSubCategories: true }], []); }); @@ -345,8 +339,6 @@ describe("CategoriesVisibilityHandler", () => { visibilityExpectations: VisibilityExpectations.all("visible"), nodesToExpect, }); - - ViewportMock.validateViewsCalls(viewport, [keys.indirectCategory.id, keys.indirectCategory.id], [keys.indirectSubCategory.id]); ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.indirectCategory.id], isVisible: true, enableAllSubCategories: true }], []); }); }); @@ -377,7 +369,6 @@ describe("CategoriesVisibilityHandler", () => { visibilityExpectations: VisibilityExpectations.all("visible"), nodesToExpect, }); - ViewportMock.validateViewsCalls(viewport, [keys.category.id], [keys.subCategory.id]); ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: true }], []); }); @@ -416,7 +407,6 @@ describe("CategoriesVisibilityHandler", () => { }, nodesToExpect, }); - ViewportMock.validateViewsCalls(viewport, [keys.category.id, keys.category2.id], [keys.subCategory.id, keys.subCategory2.id]); ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: true }], []); }); @@ -460,7 +450,6 @@ describe("CategoriesVisibilityHandler", () => { }, nodesToExpect, }); - ViewportMock.validateViewsCalls(viewport, [keys.category.id, keys.category2.id], [keys.subCategory.id, keys.subCategory2.id]); ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: true }], []); }); @@ -505,7 +494,6 @@ describe("CategoriesVisibilityHandler", () => { }, nodesToExpect, }); - ViewportMock.validateViewsCalls(viewport, [keys.category.id, keys.category2.id], [keys.subCategory.id, keys.subCategory2.id]); ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: true }], []); }); @@ -558,7 +546,6 @@ describe("CategoriesVisibilityHandler", () => { }, nodesToExpect, }); - ViewportMock.validateViewsCalls(viewport, [keys.category.id, keys.indirectCategory.id], [keys.subCategory.id]); ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: true }], []); }); }); @@ -599,7 +586,6 @@ describe("CategoriesVisibilityHandler", () => { }, nodesToExpect, }); - ViewportMock.validateViewsCalls(viewport, [keys.category.id], [keys.subCategory.id, keys.subCategory2.id]); ViewportMock.validateChangesCalls( viewport, [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: false }], @@ -637,7 +623,6 @@ describe("CategoriesVisibilityHandler", () => { }, nodesToExpect, }); - ViewportMock.validateViewsCalls(viewport, [keys.category.id, keys.category2.id], [keys.subCategory.id]); ViewportMock.validateChangesCalls( viewport, [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: false }], @@ -677,7 +662,6 @@ describe("CategoriesVisibilityHandler", () => { }, nodesToExpect, }); - ViewportMock.validateViewsCalls(viewport, [keys.category.id], [keys.subCategory.id]); ViewportMock.validateChangesCalls( viewport, [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: false }], @@ -730,11 +714,6 @@ describe("CategoriesVisibilityHandler", () => { }, nodesToExpect, }); - ViewportMock.validateViewsCalls( - viewport, - [keys.category.id, keys.categoryOfDefinitionContainer.id], - [keys.subCategory.id, keys.subCategoryOfDefinitionContainer.id], - ); ViewportMock.validateChangesCalls( viewport, [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: false }], diff --git a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/Utils.ts b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/Utils.ts index 21472ac1f..e50afe1b2 100644 --- a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/Utils.ts +++ b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/Utils.ts @@ -155,22 +155,6 @@ export class ViewportMock { } as unknown as Viewport; } - /** - * Checks if `view.viewsCategory` and `isSubCategoryVisible` get called with appropriate params - * - * @param stubbedViewport viewport created using `ViewportMock.createViewPortStub()` - * @param categories categories that `stubbedViewport.view.viewsCategory` should be called with - * @param subCategories subcategories that `stubbedViewport.isSubCategoryVisible` should be called with - */ - public static validateViewsCalls(stubbedViewport: Viewport, categories: Id64Array, subCategories: Id64Array) { - for (const category of categories) { - expect(stubbedViewport.view.viewsCategory).to.be.calledWith(category); - } - for (const subCategory of subCategories) { - expect(stubbedViewport.isSubCategoryVisible).to.be.calledWith(subCategory); - } - } - /** * Checks if `changeCategoryDisplay` and `changeSubCategoryDisplay` get called with appropriate params * From b5a53cab3af9a928fabaabe6ce5afe11a38227ce Mon Sep 17 00:00:00 2001 From: Jonas <100586436+JonasDov@users.noreply.github.com> Date: Mon, 3 Feb 2025 19:53:39 +0200 Subject: [PATCH 19/38] Adjust catgory visibility --- .../internal/CategoriesVisibilityHandler.ts | 45 ++++++++++++++----- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.ts index 33d63fab3..37196c7dd 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.ts +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { BeEvent } from "@itwin/core-bentley"; -import { PerModelCategoryVisibility } from "@itwin/core-frontend"; import { HierarchyNode } from "@itwin/presentation-hierarchies"; import { enableCategoryDisplay, enableSubCategoryDisplay } from "../../common/CategoriesVisibilityUtils.js"; import { createVisibilityStatus } from "../../common/Tooltip.js"; @@ -96,9 +95,24 @@ export class CategoriesVisibilityHandler implements HierarchyVisibilityHandler { return categoryOverrideResult; } - const subcategoryIds = CategoriesVisibilityHandler.getInstanceIdsFromHierarchyNode(node); - const isVisible = subcategoryIds.every((id) => this._viewport.isSubCategoryVisible(id)) && this._viewport.view.viewsCategory(parentCategoryId); - return isVisible ? "visible" : "hidden"; + if (!this._viewport.view.viewsCategory(parentCategoryId)) { + return "hidden"; + } + const subCategoryIds = CategoriesVisibilityHandler.getInstanceIdsFromHierarchyNode(node); + let visibleCount = 0; + let hiddenCount = 0; + for (const subCategoryId of subCategoryIds) { + const isVisible = this._viewport.isSubCategoryVisible(subCategoryId); + if (isVisible) { + ++visibleCount; + } else { + ++hiddenCount; + } + if (visibleCount > 0 && hiddenCount > 0) { + return "partial"; + } + } + return visibleCount > 0 ? "visible" : "hidden"; } private async getDefinitionContainerVisibility(node: HierarchyNode): Promise { @@ -130,8 +144,21 @@ export class CategoriesVisibilityHandler implements HierarchyVisibilityHandler { if (overrideResult !== "none") { return overrideResult; } + let visibleCount = 0; + let hiddenCount = 0; + for (const categoryId of categoryIds) { + const isVisible = this._viewport.view.viewsCategory(categoryId); + if (isVisible) { + ++visibleCount; + } else { + ++hiddenCount; + } + if (visibleCount > 0 && hiddenCount > 0) { + return "partial"; + } + } - if (!categoryIds.every((id) => this._viewport.view.viewsCategory(id))) { + if (hiddenCount > 0) { return "hidden"; } @@ -160,12 +187,10 @@ export class CategoriesVisibilityHandler implements HierarchyVisibilityHandler { for (const currentOverride of this._viewport.perModelCategoryVisibility) { if (categoryIds.includes(currentOverride.categoryId)) { - const currentVisibilityOverride = this._viewport.perModelCategoryVisibility.getOverride(currentOverride.modelId, currentOverride.categoryId); - - if (currentVisibilityOverride === PerModelCategoryVisibility.Override.Hide) { - ++hideOverrides; - } else if (currentVisibilityOverride === PerModelCategoryVisibility.Override.Show) { + if (currentOverride.visible) { ++showOverrides; + } else { + ++hideOverrides; } if (showOverrides > 0 && hideOverrides > 0) { From 1679c5c3fdc77e53180fd5f0df5b4a5eda2756ef Mon Sep 17 00:00:00 2001 From: Jonas <100586436+JonasDov@users.noreply.github.com> Date: Mon, 3 Feb 2025 19:57:39 +0200 Subject: [PATCH 20/38] Create a separate file for class name definitions --- .../categories-tree/CategoriesTreeDefinition.ts | 4 ++-- .../trees/categories-tree/UseCategoriesTree.tsx | 3 ++- .../internal/CategoriesTreeIdsCache.ts | 6 +----- .../internal/ClassNameDefinitions.ts | 13 +++++++++++++ 4 files changed, 18 insertions(+), 8 deletions(-) create mode 100644 packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/ClassNameDefinitions.ts diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts index 5f03eebd2..dba0a5da1 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts @@ -7,7 +7,8 @@ import { defer, EMPTY, from, lastValueFrom, map, mergeMap, toArray } from "rxjs" import { createNodesQueryClauseFactory, createPredicateBasedHierarchyDefinition } from "@itwin/presentation-hierarchies"; import { createBisInstanceLabelSelectClauseFactory, ECSql } from "@itwin/presentation-shared"; import { FilterLimitExceededError } from "../common/TreeErrors.js"; -import { DEFINITION_CONTAINER_CLASS, getClassesByView, SUB_CATEGORY_CLASS } from "./internal/CategoriesTreeIdsCache.js"; +import { getClassesByView } from "./internal/CategoriesTreeIdsCache.js"; +import { DEFINITION_CONTAINER_CLASS, DEFINITION_ELEMENT_CLASS, SUB_CATEGORY_CLASS } from "./internal/ClassNameDefinitions.js"; import type { Id64Array } from "@itwin/core-bentley"; import type { Observable } from "rxjs"; @@ -25,7 +26,6 @@ import type { } from "@itwin/presentation-hierarchies"; const MAX_FILTERING_INSTANCE_KEY_COUNT = 100; -const DEFINITION_ELEMENT_CLASS = "BisCore.DefinitionElement"; interface CategoriesTreeDefinitionProps { imodelAccess: ECSchemaProvider & ECClassHierarchyInspector & LimitingECSqlQueryExecutor; diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx index 8f3ca02e6..ba59adf70 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx @@ -12,8 +12,9 @@ import { TreeWidget } from "../../../TreeWidget.js"; import { FilterLimitExceededError } from "../common/TreeErrors.js"; import { useTelemetryContext } from "../common/UseTelemetryContext.js"; import { CategoriesTreeDefinition } from "./CategoriesTreeDefinition.js"; -import { CategoriesTreeIdsCache, DEFINITION_CONTAINER_CLASS, SUB_CATEGORY_CLASS } from "./internal/CategoriesTreeIdsCache.js"; +import { CategoriesTreeIdsCache } from "./internal/CategoriesTreeIdsCache.js"; import { CategoriesVisibilityHandler } from "./internal/CategoriesVisibilityHandler.js"; +import { DEFINITION_CONTAINER_CLASS, SUB_CATEGORY_CLASS } from "./internal/ClassNameDefinitions.js"; import type { ReactElement } from "react"; import type { HierarchyNode } from "@itwin/presentation-hierarchies"; diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts index c2453beb7..3f7aefb02 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts @@ -6,6 +6,7 @@ import type { Id64Array, Id64String } from "@itwin/core-bentley"; import type { LimitingECSqlQueryExecutor } from "@itwin/presentation-hierarchies"; import type { InstanceKey } from "@itwin/presentation-shared"; +import { DEFINITION_CONTAINER_CLASS, SUB_CATEGORY_CLASS } from "./ClassNameDefinitions.js"; interface DefinitionContainerInfo { modelId: Id64String; @@ -28,11 +29,6 @@ interface SubCategoryInfo { categoryId: Id64String; } -/** @internal */ -export const SUB_CATEGORY_CLASS = "BisCore.SubCategory"; -/** @internal */ -export const DEFINITION_CONTAINER_CLASS = "BisCore.DefinitionContainer"; - /** @internal */ export class CategoriesTreeIdsCache { private _definitionContainersInfo: Promise> | undefined; diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/ClassNameDefinitions.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/ClassNameDefinitions.ts new file mode 100644 index 000000000..e5d720e9f --- /dev/null +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/ClassNameDefinitions.ts @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ + +/** @internal */ +export const SUB_CATEGORY_CLASS = "BisCore.SubCategory"; + +/** @internal */ +export const DEFINITION_CONTAINER_CLASS = "BisCore.DefinitionContainer"; + +/** @internal */ +export const DEFINITION_ELEMENT_CLASS = "BisCore.DefinitionElement"; From 0865c2cd91607629f682f85d98794d13728c77bb Mon Sep 17 00:00:00 2001 From: Jonas <100586436+JonasDov@users.noreply.github.com> Date: Mon, 3 Feb 2025 20:00:33 +0200 Subject: [PATCH 21/38] Remove activeView dependency --- .../trees/categories-tree/UseCategoriesTree.tsx | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx index ba59adf70..f7cace9d0 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx @@ -51,16 +51,15 @@ interface UseCategoriesTreeResult { export function useCategoriesTree({ filter, activeView, onCategoriesFiltered }: UseCategoriesTreeProps): UseCategoriesTreeResult { const [filteringError, setFilteringError] = useState(); const idsCacheRef = useRef(); - const activeViewRef = useRef(); + const viewType = activeView.view.is2d() ? "2d" : "3d"; + const iModel = activeView.iModel; const getCategoriesTreeIdsCache = useCallback(() => { - if (activeViewRef.current !== activeView || !idsCacheRef.current) { - const viewType = activeView.view.is2d() ? "2d" : "3d"; - activeViewRef.current = activeView; - idsCacheRef.current = new CategoriesTreeIdsCache(createECSqlQueryExecutor(activeView.iModel), viewType); + if (!idsCacheRef.current) { + idsCacheRef.current = new CategoriesTreeIdsCache(createECSqlQueryExecutor(iModel), viewType); } return idsCacheRef.current; - }, [activeView]); + }, [viewType, iModel]); const visibilityHandlerFactory = useCallback(() => { const visibilityHandler = new CategoriesVisibilityHandler({ @@ -78,10 +77,9 @@ export function useCategoriesTree({ filter, activeView, onCategoriesFiltered }: const getHierarchyDefinition = useCallback( (props) => { - const viewType = activeView.view.is2d() ? "2d" : "3d"; return new CategoriesTreeDefinition({ ...props, viewType, idsCache: getCategoriesTreeIdsCache() }); }, - [activeView, getCategoriesTreeIdsCache], + [viewType, getCategoriesTreeIdsCache], ); const getFilteredPaths = useMemo(() => { @@ -93,7 +91,6 @@ export function useCategoriesTree({ filter, activeView, onCategoriesFiltered }: return async ({ imodelAccess }) => { onFeatureUsed({ featureId: "filtering", reportInteraction: true }); try { - const viewType = activeView.view.is2d() ? "2d" : "3d"; const paths = await CategoriesTreeDefinition.createInstanceKeyPaths({ imodelAccess, label: filter, viewType, idsCache: getCategoriesTreeIdsCache() }); onCategoriesFiltered?.(getCategoriesFromPaths(paths)); return paths; @@ -107,7 +104,7 @@ export function useCategoriesTree({ filter, activeView, onCategoriesFiltered }: return []; } }; - }, [filter, activeView, onFeatureUsed, onCategoriesFiltered, getCategoriesTreeIdsCache]); + }, [filter, viewType, onFeatureUsed, onCategoriesFiltered, getCategoriesTreeIdsCache]); return { categoriesTreeProps: { From 589760d36e369cb3e1a30240e35ab99d051928d1 Mon Sep 17 00:00:00 2001 From: Jonas <100586436+JonasDov@users.noreply.github.com> Date: Mon, 3 Feb 2025 20:01:26 +0200 Subject: [PATCH 22/38] Use HierarchyFilteringPath.normalize(path) --- .../components/trees/categories-tree/UseCategoriesTree.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx index f7cace9d0..44f11ace1 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx @@ -7,7 +7,7 @@ import { useCallback, useMemo, useRef, useState } from "react"; import { SvgArchive, SvgLayers } from "@itwin/itwinui-icons-react"; import { Text } from "@itwin/itwinui-react"; import { createECSqlQueryExecutor } from "@itwin/presentation-core-interop"; -import { HierarchyNodeIdentifier } from "@itwin/presentation-hierarchies"; +import { HierarchyFilteringPath, HierarchyNodeIdentifier } from "@itwin/presentation-hierarchies"; import { TreeWidget } from "../../../TreeWidget.js"; import { FilterLimitExceededError } from "../common/TreeErrors.js"; import { useTelemetryContext } from "../common/UseTelemetryContext.js"; @@ -129,7 +129,7 @@ function getCategoriesFromPaths(paths: HierarchyFilteringPaths): CategoryInfo[] const categories = new Map(); for (const path of paths) { - const currPath = Array.isArray(path) ? path : path.path; + const currPath = HierarchyFilteringPath.normalize(path).path; if (currPath.length === 0) { continue; } From 18d904f034f08b458012375816e275679850e4ba Mon Sep 17 00:00:00 2001 From: Jonas <100586436+JonasDov@users.noreply.github.com> Date: Mon, 3 Feb 2025 20:11:46 +0200 Subject: [PATCH 23/38] Adjust getCategoriesFromPaths --- .../trees/categories-tree/UseCategoriesTree.tsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx index 44f11ace1..d70636312 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx @@ -92,7 +92,7 @@ export function useCategoriesTree({ filter, activeView, onCategoriesFiltered }: onFeatureUsed({ featureId: "filtering", reportInteraction: true }); try { const paths = await CategoriesTreeDefinition.createInstanceKeyPaths({ imodelAccess, label: filter, viewType, idsCache: getCategoriesTreeIdsCache() }); - onCategoriesFiltered?.(getCategoriesFromPaths(paths)); + onCategoriesFiltered?.(await getCategoriesFromPaths(paths, getCategoriesTreeIdsCache())); return paths; } catch (e) { const newError = e instanceof FilterLimitExceededError ? "tooManyFilterMatches" : "unknownFilterError"; @@ -122,7 +122,7 @@ export function useCategoriesTree({ filter, activeView, onCategoriesFiltered }: }; } -function getCategoriesFromPaths(paths: HierarchyFilteringPaths): CategoryInfo[] | undefined { +async function getCategoriesFromPaths(paths: HierarchyFilteringPaths, idsCache: CategoriesTreeIdsCache): Promise { if (!paths) { return undefined; } @@ -138,7 +138,18 @@ function getCategoriesFromPaths(paths: HierarchyFilteringPaths): CategoryInfo[] let subCategory: HierarchyNodeIdentifier | undefined; const lastNode = currPath[currPath.length - 1]; - if (!HierarchyNodeIdentifier.isInstanceNodeIdentifier(lastNode) || lastNode.className === DEFINITION_CONTAINER_CLASS) { + if (!HierarchyNodeIdentifier.isInstanceNodeIdentifier(lastNode)) { + continue; + } + + if (lastNode.className === DEFINITION_CONTAINER_CLASS) { + const definitionContainerCategories = await idsCache.getAllContainedCategories([lastNode.id]); + for (const categoryId of definitionContainerCategories) { + const value = categories.get(categoryId); + if (value === undefined) { + categories.set(categoryId, []); + } + } continue; } From 82b585a1815153ea9c44902eb4496e750a2244d8 Mon Sep 17 00:00:00 2001 From: Jonas <100586436+JonasDov@users.noreply.github.com> Date: Mon, 3 Feb 2025 20:21:06 +0200 Subject: [PATCH 24/38] Rename definitionContainer to definition container in test names --- .../CategoriesTreeDefinition.test.ts | 16 ++--- .../CategoriesTreeFiltering.test.ts | 10 ++-- .../internal/CategoriesTreeIdsCache.test.ts | 60 +++++++++---------- .../CategoriesVisibilityHandler.test.ts | 32 +++++----- 4 files changed, 59 insertions(+), 59 deletions(-) diff --git a/packages/itwin/tree-widget/src/test/trees/categories-tree/CategoriesTreeDefinition.test.ts b/packages/itwin/tree-widget/src/test/trees/categories-tree/CategoriesTreeDefinition.test.ts index 9e6d6b249..938c9bdf0 100644 --- a/packages/itwin/tree-widget/src/test/trees/categories-tree/CategoriesTreeDefinition.test.ts +++ b/packages/itwin/tree-widget/src/test/trees/categories-tree/CategoriesTreeDefinition.test.ts @@ -73,7 +73,7 @@ describe("Categories tree", () => { }); }); - it("does not show definitionContainer when it doesn't contain category", async function () { + it("does not show definition container when it doesn't contain category", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer" }); @@ -97,7 +97,7 @@ describe("Categories tree", () => { }); }); - it("does not show definitionContainer when it contains definitionContainer without categories", async function () { + it("does not show definition container when it contains definition container without categories", async function () { const { imodel } = await buildIModel(this, async (builder) => { insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer" }); @@ -111,7 +111,7 @@ describe("Categories tree", () => { }); }); - it("does not show definitionContainer or category when category is private", async function () { + it("does not show definition container or category when category is private", async function () { const { imodel } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer" }); @@ -127,7 +127,7 @@ describe("Categories tree", () => { }); }); - it("does not show definitionContainer or category when definitionContainer is private", async function () { + it("does not show definition container or category when definition container is private", async function () { const { imodel } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer", isPrivate: true }); @@ -143,7 +143,7 @@ describe("Categories tree", () => { }); }); - it("does not show definitionContainers or categories when definitionContainer contains another definitionContainer that is private", async function () { + it("does not show definition containers or categories when definition container contains another definition container that is private", async function () { const { imodel } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer" }); @@ -166,7 +166,7 @@ describe("Categories tree", () => { }); }); - it("shows definitionContainer when it contains category", async function () { + it("shows definition container when it contains category", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer" }); @@ -196,7 +196,7 @@ describe("Categories tree", () => { }); }); - it("shows all definitionContainers when they contain category directly or indirectly", async function () { + it("shows all definition containers when they contain category directly or indirectly", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer" }); @@ -234,7 +234,7 @@ describe("Categories tree", () => { }); }); - it("shows root categories and definitionContainer", async function () { + it("shows root categories and definition container", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer" }); diff --git a/packages/itwin/tree-widget/src/test/trees/categories-tree/CategoriesTreeFiltering.test.ts b/packages/itwin/tree-widget/src/test/trees/categories-tree/CategoriesTreeFiltering.test.ts index fe7203028..b11289c36 100644 --- a/packages/itwin/tree-widget/src/test/trees/categories-tree/CategoriesTreeFiltering.test.ts +++ b/packages/itwin/tree-widget/src/test/trees/categories-tree/CategoriesTreeFiltering.test.ts @@ -46,7 +46,7 @@ describe("Categories tree", () => { await terminatePresentationTesting(); }); - it("finds definitionContainer by label", async function () { + it("finds definition container by label", async function () { const { imodel, keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer", userLabel: "Test" }); @@ -69,7 +69,7 @@ describe("Categories tree", () => { ).to.deep.eq([{ path: [keys.definitionContainer], options: { autoExpand: true } }]); }); - it("finds definitionContainer by label when it is contained by another definitionContainer", async function () { + it("finds definition container by label when it is contained by another definition container", async function () { const { imodel, keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer" }); @@ -99,7 +99,7 @@ describe("Categories tree", () => { ).to.deep.eq([{ path: [keys.definitionContainer, keys.definitionContainerChild], options: { autoExpand: true } }]); }); - it("does not find definitionContainer by label when it doesn't contain categories", async function () { + it("does not find definition container by label when it doesn't contain categories", async function () { const { imodel } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer", userLabel: "Test" }); @@ -122,7 +122,7 @@ describe("Categories tree", () => { ).to.deep.eq([]); }); - it("finds category by label when it is contained by definitionContainer", async function () { + it("finds category by label when it is contained by definition container", async function () { const { imodel, keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer" }); @@ -145,7 +145,7 @@ describe("Categories tree", () => { ).to.deep.eq([{ path: [keys.definitionContainer, keys.category], options: { autoExpand: true } }]); }); - it("finds subCategory by label when its parent category is contained by definitionContainer", async function () { + it("finds subCategory by label when its parent category is contained by definition container", async function () { const { imodel, keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer" }); diff --git a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeIdsCache.test.ts b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeIdsCache.test.ts index 8912320da..519197eb2 100644 --- a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeIdsCache.test.ts +++ b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeIdsCache.test.ts @@ -42,7 +42,7 @@ describe("CategoriesTreeIdsCache", () => { }); describe("getDirectChildDefinitionContainersAndCategories", () => { - it("when definitionContainer contains nothing", async function () { + it("when definition container contains nothing", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainer = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -75,7 +75,7 @@ describe("CategoriesTreeIdsCache", () => { }); }); - it("when definitionContainer contains definitionContainer, that has categories", async function () { + it("when definition container contains definition container, that has categories", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -94,7 +94,7 @@ describe("CategoriesTreeIdsCache", () => { }); }); - it("when definitionContainer contains categories", async function () { + it("when definition container contains categories", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -111,7 +111,7 @@ describe("CategoriesTreeIdsCache", () => { }); }); - it("when definitionContainer contains categories and definitionContainers that contain nothing", async function () { + it("when definition container contains categories and definition containers that contain nothing", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -130,7 +130,7 @@ describe("CategoriesTreeIdsCache", () => { }); }); - it("when definitionContainer contains categories and definitionContainers that contain categories", async function () { + it("when definition container contains categories and definition containers that contain categories", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -152,7 +152,7 @@ describe("CategoriesTreeIdsCache", () => { }); }); - it("when definitionContainer with categories is contained by definitionContainer", async function () { + it("when definition container with categories is contained by definition container", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -174,7 +174,7 @@ describe("CategoriesTreeIdsCache", () => { }); describe("getAllContainedCategories", () => { - it("when definitionContainer contains nothing", async function () { + it("when definition container contains nothing", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainer = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -186,7 +186,7 @@ describe("CategoriesTreeIdsCache", () => { expect(await idsCache.getAllContainedCategories([keys.definitionContainer.id])).to.deep.eq([]); }); - it("when definitionContainer contains definitionContainer that has categories", async function () { + it("when definition container contains definition container that has categories", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -202,7 +202,7 @@ describe("CategoriesTreeIdsCache", () => { expect(await idsCache.getAllContainedCategories([keys.definitionContainerRoot.id])).to.deep.eq([keys.category.id]); }); - it("when definitionContainer contains categories", async function () { + it("when definition container contains categories", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainer = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -216,7 +216,7 @@ describe("CategoriesTreeIdsCache", () => { expect(await idsCache.getAllContainedCategories([keys.definitionContainer.id])).to.deep.eq([keys.category.id]); }); - it("when definitionContainer contains categories and definitionContainers that contain categories", async function () { + it("when definition container contains categories and definition containers that contain categories", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -261,7 +261,7 @@ describe("CategoriesTreeIdsCache", () => { expect(await idsCache.getInstanceKeyPaths({ subCategoryId: keys.subCategory.id })).to.deep.eq([keys.category, keys.subCategory]); }); - it("with definitionContainer > category > subCategory hierarchy", async function () { + it("with definition container > category > subCategory hierarchy", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainer = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -280,7 +280,7 @@ describe("CategoriesTreeIdsCache", () => { ]); }); - it("with definitionContainer > definitionContainer > category > subCategory hierarchy", async function () { + it("with definition container > definition container > category > subCategory hierarchy", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -331,7 +331,7 @@ describe("CategoriesTreeIdsCache", () => { expect(await idsCache.getInstanceKeyPaths({ categoryId: keys.category.id })).to.deep.eq([keys.category]); }); - it("with definitionContainer > category hierarchy", async function () { + it("with definition container > category hierarchy", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainer = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -345,7 +345,7 @@ describe("CategoriesTreeIdsCache", () => { expect(await idsCache.getInstanceKeyPaths({ categoryId: keys.category.id })).to.deep.eq([keys.definitionContainer, keys.category]); }); - it("with definitionContainer > definitionContainer > category hierarchy", async function () { + it("with definition container > definition container > category hierarchy", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -366,8 +366,8 @@ describe("CategoriesTreeIdsCache", () => { }); }); - describe("from definitionContainer", () => { - it("when definitionContainer doesn't exist", async function () { + describe("from definition container", () => { + it("when definition container doesn't exist", async function () { const { imodel } = await buildIModel(this, async (builder) => { insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); }); @@ -375,7 +375,7 @@ describe("CategoriesTreeIdsCache", () => { expect(await idsCache.getInstanceKeyPaths({ definitionContainerId: "0x123" })).to.deep.eq([]); }); - it("when only a single definitionContainer exists", async function () { + it("when only a single definition container exists", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainer = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -389,7 +389,7 @@ describe("CategoriesTreeIdsCache", () => { expect(await idsCache.getInstanceKeyPaths({ definitionContainerId: keys.definitionContainer.id })).to.deep.eq([keys.definitionContainer]); }); - it("with definitionContainer > definitionContainer hierarchy", async function () { + it("with definition container > definition container hierarchy", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -411,7 +411,7 @@ describe("CategoriesTreeIdsCache", () => { }); describe("getAllDefinitionContainersAndCategories", () => { - it("hierarchy without categories or definitionContainers", async function () { + it("hierarchy without categories or definition containers", async function () { const { imodel } = await buildIModel(this, async (builder) => { insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); }); @@ -436,7 +436,7 @@ describe("CategoriesTreeIdsCache", () => { }); }); - it("with category and definitionContainers (that dont contain categories)", async function () { + it("with category and definition containers (that dont contain categories)", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -455,7 +455,7 @@ describe("CategoriesTreeIdsCache", () => { }); }); - it("with definitionContainer that contains definitionContainer that contains categories", async function () { + it("with definition container that contains definition container that contains categories", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -477,7 +477,7 @@ describe("CategoriesTreeIdsCache", () => { expect(result.definitionContainers.every((dc) => expectedResult.definitionContainers.includes(dc))).to.be.true; }); - it("with definitionContainer that contains category", async function () { + it("with definition container that contains category", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -494,7 +494,7 @@ describe("CategoriesTreeIdsCache", () => { }); }); - it("with definitionContainer that contains category and definitionContainer that doesn't contain category", async function () { + it("with definition container that contains category and definition container that doesn't contain category", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -513,7 +513,7 @@ describe("CategoriesTreeIdsCache", () => { }); }); - it("with definitionContainer that contains categories and definitionContainers that contain categories", async function () { + it("with definition container that contains categories and definition containers that contain categories", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -540,7 +540,7 @@ describe("CategoriesTreeIdsCache", () => { }); describe("getRootDefinitionContainersAndCategories", () => { - it("hierarchy without categories or definitionContainers", async function () { + it("hierarchy without categories or definition containers", async function () { const { imodel } = await buildIModel(this, async (builder) => { insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); }); @@ -548,7 +548,7 @@ describe("CategoriesTreeIdsCache", () => { expect(await idsCache.getRootDefinitionContainersAndCategories()).to.deep.eq({ categories: [], definitionContainers: [] }); }); - it("with category and definitionContainer that doesn't contain anything", async function () { + it("with category and definition container that doesn't contain anything", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainer = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -565,7 +565,7 @@ describe("CategoriesTreeIdsCache", () => { }); }); - it("with category and definitionContainers that contains definitionContainer that doesn't contain categories", async function () { + it("with category and definition containers that contains definition container that doesn't contain categories", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -584,7 +584,7 @@ describe("CategoriesTreeIdsCache", () => { }); }); - it("with definitionContainer that contains definitionContainer that contains categories", async function () { + it("with definition container that contains definition container that contains categories", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -603,7 +603,7 @@ describe("CategoriesTreeIdsCache", () => { }); }); - it("with definitionContainer that containts category", async function () { + it("with definition container that containts category", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -620,7 +620,7 @@ describe("CategoriesTreeIdsCache", () => { }); }); - it("with definitionContainers and categories", async function () { + it("with definition containers and categories", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); diff --git a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts index 8a952da43..46ee95bbb 100644 --- a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts +++ b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts @@ -119,7 +119,7 @@ describe("CategoriesVisibilityHandler", () => { describe("enabling visibility", () => { describe("definitionContainers", () => { - it("showing definitionContainer makes it and all of its contained elements visible", async function () { + it("showing definition container makes it and all of its contained elements visible", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); @@ -165,7 +165,7 @@ describe("CategoriesVisibilityHandler", () => { ); }); - it("showing definitionContainer makes it and all of its contained elements visible and doesn't affect non contained definitionContainers", async function () { + it("showing definition container makes it and all of its contained elements visible and doesn't affect non contained definition containers", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); @@ -224,7 +224,7 @@ describe("CategoriesVisibilityHandler", () => { ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.indirectCategory.id], isVisible: true, enableAllSubCategories: true }], []); }); - it("showing definitionContainer makes it and all of its contained elements visible, and parent container partially visible if it has more direct child categories", async function () { + it("showing definition container makes it and all of its contained elements visible, and parent container partially visible if it has more direct child categories", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); @@ -258,7 +258,7 @@ describe("CategoriesVisibilityHandler", () => { ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.indirectCategory.id], isVisible: true, enableAllSubCategories: true }], []); }); - it("showing definitionContainer makes it and all of its contained elements visible, and parent container partially visible if it has more definitionContainers", async function () { + it("showing definition container makes it and all of its contained elements visible, and parent container partially visible if it has more definition containers", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); @@ -308,7 +308,7 @@ describe("CategoriesVisibilityHandler", () => { ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.indirectCategory.id], isVisible: true, enableAllSubCategories: true }], []); }); - it("showing child definitionContainer makes it, all of its contained elements and its parent definitionContainer visible", async function () { + it("showing child definition container makes it, all of its contained elements and its parent definition container visible", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); @@ -410,7 +410,7 @@ describe("CategoriesVisibilityHandler", () => { ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: true }], []); }); - it("showing category makes it, all of its contained subCategories visible and doesn't affect non related definitionContainer", async function () { + it("showing category makes it, all of its contained subCategories visible and doesn't affect non related definition container", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory" }); @@ -497,7 +497,7 @@ describe("CategoriesVisibilityHandler", () => { ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: true }], []); }); - it("showing category makes it and all of its subCategories visible, and parent container partially visible if it has more definitionContainers", async function () { + it("showing category makes it and all of its subCategories visible, and parent container partially visible if it has more definition containers", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); @@ -669,7 +669,7 @@ describe("CategoriesVisibilityHandler", () => { ); }); - it("showing subCategory makes it visible and doesn't affect non related definitionContainers", async function () { + it("showing subCategory makes it visible and doesn't affect non related definition containers", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); @@ -725,7 +725,7 @@ describe("CategoriesVisibilityHandler", () => { describe("disabling visibility", () => { describe("definitionContainers", () => { - it("hiding definitionContainer makes it and all of its contained elements hidden", async function () { + it("hiding definition container makes it and all of its contained elements hidden", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); @@ -779,7 +779,7 @@ describe("CategoriesVisibilityHandler", () => { ); }); - it("hiding definitionContainer makes it and all of its contained elements hidden and doesn't affect non contained definitionContainers", async function () { + it("hiding definition container makes it and all of its contained elements hidden and doesn't affect non contained definition containers", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); @@ -847,7 +847,7 @@ describe("CategoriesVisibilityHandler", () => { ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.indirectCategory.id], isVisible: false, enableAllSubCategories: false }], []); }); - it("hiding definitionContainer makes it and all of its contained elements hidden, and parent container partially visible if it has more direct child categories", async function () { + it("hiding definition container makes it and all of its contained elements hidden, and parent container partially visible if it has more direct child categories", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); @@ -890,7 +890,7 @@ describe("CategoriesVisibilityHandler", () => { ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.indirectCategory.id], isVisible: false, enableAllSubCategories: false }], []); }); - it("hiding definitionContainer makes it and all of its contained elements hidden, and parent container partially visible if it has more definitionContainers", async function () { + it("hiding definition container makes it and all of its contained elements hidden, and parent container partially visible if it has more definition containers", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); @@ -949,7 +949,7 @@ describe("CategoriesVisibilityHandler", () => { ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.indirectCategory.id], isVisible: false, enableAllSubCategories: false }], []); }); - it("hiding child definitionContainer makes it, all of its contained elements and its parent definitionContainer hidden", async function () { + it("hiding child definition container makes it, all of its contained elements and its parent definition container hidden", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); @@ -1079,7 +1079,7 @@ describe("CategoriesVisibilityHandler", () => { ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.category.id], isVisible: false, enableAllSubCategories: false }], []); }); - it("hiding category makes it, all of its contained subCategories hidden and doesn't affect non related definitionContainer", async function () { + it("hiding category makes it, all of its contained subCategories hidden and doesn't affect non related definition container", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory" }); @@ -1185,7 +1185,7 @@ describe("CategoriesVisibilityHandler", () => { ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.category.id], isVisible: false, enableAllSubCategories: false }], []); }); - it("hiding category makes it and all of its subCategories hidden, and parent container partially visible if it has more definitionContainers", async function () { + it("hiding category makes it and all of its subCategories hidden, and parent container partially visible if it has more definition containers", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); @@ -1382,7 +1382,7 @@ describe("CategoriesVisibilityHandler", () => { ViewportMock.validateChangesCalls(viewport, [], [{ subCategoryId: keys.subCategory.id, isVisible: false }]); }); - it("hiding subCategory makes it hidden and doesn't affect non related definitionContainers", async function () { + it("hiding subCategory makes it hidden and doesn't affect non related definition containers", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "DefinitionContainerRoot" }); From 704539a04cad4d2258017086ad83f2d7a0cd9302 Mon Sep 17 00:00:00 2001 From: Jonas <100586436+JonasDov@users.noreply.github.com> Date: Tue, 4 Feb 2025 10:34:07 +0200 Subject: [PATCH 25/38] Rename tests --- .../internal/CategoriesTreeIdsCache.test.ts | 82 +++++++++---------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeIdsCache.test.ts b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeIdsCache.test.ts index 519197eb2..1b16bcb9d 100644 --- a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeIdsCache.test.ts +++ b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeIdsCache.test.ts @@ -42,7 +42,7 @@ describe("CategoriesTreeIdsCache", () => { }); describe("getDirectChildDefinitionContainersAndCategories", () => { - it("when definition container contains nothing", async function () { + it("retruns empty list when definition container contains nothing", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainer = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -75,7 +75,7 @@ describe("CategoriesTreeIdsCache", () => { }); }); - it("when definition container contains definition container, that has categories", async function () { + it("returns child definition container when definition container contains definition container, that has categories", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -94,7 +94,7 @@ describe("CategoriesTreeIdsCache", () => { }); }); - it("when definition container contains categories", async function () { + it("returns child categories when definition container contains categories", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -111,7 +111,7 @@ describe("CategoriesTreeIdsCache", () => { }); }); - it("when definition container contains categories and definition containers that contain nothing", async function () { + it("returns only categories when definition container contains categories and definition containers that contain nothing", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -130,7 +130,7 @@ describe("CategoriesTreeIdsCache", () => { }); }); - it("when definition container contains categories and definition containers that contain categories", async function () { + it("returns child definition container and category when definition container contains categories and definition containers that contain categories", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -152,7 +152,7 @@ describe("CategoriesTreeIdsCache", () => { }); }); - it("when definition container with categories is contained by definition container", async function () { + it("returns child categories when definition container with categories is contained by definition container", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -174,7 +174,7 @@ describe("CategoriesTreeIdsCache", () => { }); describe("getAllContainedCategories", () => { - it("when definition container contains nothing", async function () { + it("returns empty list when definition container contains nothing", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainer = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -186,7 +186,7 @@ describe("CategoriesTreeIdsCache", () => { expect(await idsCache.getAllContainedCategories([keys.definitionContainer.id])).to.deep.eq([]); }); - it("when definition container contains definition container that has categories", async function () { + it("returns indirectly contained categories when definition container contains definition container that has categories", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -202,7 +202,7 @@ describe("CategoriesTreeIdsCache", () => { expect(await idsCache.getAllContainedCategories([keys.definitionContainerRoot.id])).to.deep.eq([keys.category.id]); }); - it("when definition container contains categories", async function () { + it("returns child categories when definition container contains categories", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainer = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -216,7 +216,7 @@ describe("CategoriesTreeIdsCache", () => { expect(await idsCache.getAllContainedCategories([keys.definitionContainer.id])).to.deep.eq([keys.category.id]); }); - it("when definition container contains categories and definition containers that contain categories", async function () { + it("returns direct and indirect categories when definition container contains categories and definition containers that contain categories", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -239,8 +239,8 @@ describe("CategoriesTreeIdsCache", () => { }); describe("getInstanceKeyPaths", () => { - describe("from subCategory", () => { - it("when subcategory doesn't exist", async function () { + describe("from subCategory id", () => { + it("returns empty list when subcategory doesn't exist", async function () { const { imodel } = await buildIModel(this, async (builder) => { insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); }); @@ -248,7 +248,7 @@ describe("CategoriesTreeIdsCache", () => { expect(await idsCache.getInstanceKeyPaths({ subCategoryId: "0x123" })).to.deep.eq([]); }); - it("with category > subCategory hierarchy", async function () { + it("returns path to subCategory when category has subCategory", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory" }); @@ -261,7 +261,7 @@ describe("CategoriesTreeIdsCache", () => { expect(await idsCache.getInstanceKeyPaths({ subCategoryId: keys.subCategory.id })).to.deep.eq([keys.category, keys.subCategory]); }); - it("with definition container > category > subCategory hierarchy", async function () { + it("returns path to subCategory when definition container contains category that has subCategory", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainer = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -280,7 +280,7 @@ describe("CategoriesTreeIdsCache", () => { ]); }); - it("with definition container > definition container > category > subCategory hierarchy", async function () { + it("returns path to subCategory when definition container contains definition container that contains category that has subCategory", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -308,8 +308,8 @@ describe("CategoriesTreeIdsCache", () => { }); }); - describe("from category", () => { - it("when category doesn't exist", async function () { + describe("from category id", () => { + it("returns empty list when category doesn't exist", async function () { const { imodel } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory" }); @@ -319,7 +319,7 @@ describe("CategoriesTreeIdsCache", () => { expect(await idsCache.getInstanceKeyPaths({ categoryId: "0x123" })).to.deep.eq([]); }); - it("with only category in hierarchy", async function () { + it("returns only category when only category exists", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory" }); @@ -331,7 +331,7 @@ describe("CategoriesTreeIdsCache", () => { expect(await idsCache.getInstanceKeyPaths({ categoryId: keys.category.id })).to.deep.eq([keys.category]); }); - it("with definition container > category hierarchy", async function () { + it("returns path to category when definition container contains category", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainer = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -345,7 +345,7 @@ describe("CategoriesTreeIdsCache", () => { expect(await idsCache.getInstanceKeyPaths({ categoryId: keys.category.id })).to.deep.eq([keys.definitionContainer, keys.category]); }); - it("with definition container > definition container > category hierarchy", async function () { + it("returns path to category when definition container contains definition container that contains category", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -366,8 +366,8 @@ describe("CategoriesTreeIdsCache", () => { }); }); - describe("from definition container", () => { - it("when definition container doesn't exist", async function () { + describe("from definition container id", () => { + it("returns empty list when definition container doesn't exist", async function () { const { imodel } = await buildIModel(this, async (builder) => { insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); }); @@ -375,7 +375,7 @@ describe("CategoriesTreeIdsCache", () => { expect(await idsCache.getInstanceKeyPaths({ definitionContainerId: "0x123" })).to.deep.eq([]); }); - it("when only a single definition container exists", async function () { + it("returns definition container when definition container contains category", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainer = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -389,7 +389,7 @@ describe("CategoriesTreeIdsCache", () => { expect(await idsCache.getInstanceKeyPaths({ definitionContainerId: keys.definitionContainer.id })).to.deep.eq([keys.definitionContainer]); }); - it("with definition container > definition container hierarchy", async function () { + it("returns path to definition container when definition container is contained by definition container", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -411,7 +411,7 @@ describe("CategoriesTreeIdsCache", () => { }); describe("getAllDefinitionContainersAndCategories", () => { - it("hierarchy without categories or definition containers", async function () { + it("returns empty list when no categories or definition containers exist", async function () { const { imodel } = await buildIModel(this, async (builder) => { insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); }); @@ -419,7 +419,7 @@ describe("CategoriesTreeIdsCache", () => { expect(await idsCache.getAllDefinitionContainersAndCategories()).to.deep.eq({ categories: [], definitionContainers: [] }); }); - it("with category and empty definitionContainer", async function () { + it("returns category when only category and empty definition container exist", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainer = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -436,7 +436,7 @@ describe("CategoriesTreeIdsCache", () => { }); }); - it("with category and definition containers (that dont contain categories)", async function () { + it("returns category when category and definition containers (that dont contain categories) exist", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -455,7 +455,7 @@ describe("CategoriesTreeIdsCache", () => { }); }); - it("with definition container that contains definition container that contains categories", async function () { + it("returns both definition containers and their contained category when definition container contains definition container that contains categories", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -477,7 +477,7 @@ describe("CategoriesTreeIdsCache", () => { expect(result.definitionContainers.every((dc) => expectedResult.definitionContainers.includes(dc))).to.be.true; }); - it("with definition container that contains category", async function () { + it("returns definition container and category when definition container contains category", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -494,7 +494,7 @@ describe("CategoriesTreeIdsCache", () => { }); }); - it("with definition container that contains category and definition container that doesn't contain category", async function () { + it("returns definition container and category when definition container contains category and definition container that doesn't contain category", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -513,7 +513,7 @@ describe("CategoriesTreeIdsCache", () => { }); }); - it("with definition container that contains categories and definition containers that contain categories", async function () { + it("returns both definition containers and categories when definition container contains categories and definition container that contain categories", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -540,7 +540,7 @@ describe("CategoriesTreeIdsCache", () => { }); describe("getRootDefinitionContainersAndCategories", () => { - it("hierarchy without categories or definition containers", async function () { + it("returns empty list when no categories or definition containers exist", async function () { const { imodel } = await buildIModel(this, async (builder) => { insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); }); @@ -548,7 +548,7 @@ describe("CategoriesTreeIdsCache", () => { expect(await idsCache.getRootDefinitionContainersAndCategories()).to.deep.eq({ categories: [], definitionContainers: [] }); }); - it("with category and definition container that doesn't contain anything", async function () { + it("returns category when category and definition container that doesn't contain anything exist", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainer = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -565,7 +565,7 @@ describe("CategoriesTreeIdsCache", () => { }); }); - it("with category and definition containers that contains definition container that doesn't contain categories", async function () { + it("returns category when category and definition container that contains empty definition container exist", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -584,7 +584,7 @@ describe("CategoriesTreeIdsCache", () => { }); }); - it("with definition container that contains definition container that contains categories", async function () { + it("returns only the root definition container when definition container contains definition container that contains categories", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -603,7 +603,7 @@ describe("CategoriesTreeIdsCache", () => { }); }); - it("with definition container that containts category", async function () { + it("returns definition container when definition container containts category", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -620,7 +620,7 @@ describe("CategoriesTreeIdsCache", () => { }); }); - it("with definition containers and categories", async function () { + it("returns root categories and definition containers when root categories and definition containers exist", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainerRoot = insertDefinitionContainer({ builder, codeValue: "Test DefinitionContainer" }); @@ -656,7 +656,7 @@ describe("CategoriesTreeIdsCache", () => { }); describe("getSubCategories", () => { - it("when category doesn't exist", async function () { + it("returns empty list when category doesn't exist", async function () { const { imodel } = await buildIModel(this, async (builder) => { insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); }); @@ -664,7 +664,7 @@ describe("CategoriesTreeIdsCache", () => { expect(await idsCache.getSubCategories("0x123")).to.deep.eq([]); }); - it("when category has one subCategory", async function () { + it("returns empty list when category has one subCategory", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory" }); @@ -675,7 +675,7 @@ describe("CategoriesTreeIdsCache", () => { expect(await idsCache.getSubCategories(keys.category.id)).to.deep.eq([]); }); - it("when category has multiple subCategories", async function () { + it("returns subCategories when category has multiple subCategories", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory" }); @@ -690,7 +690,7 @@ describe("CategoriesTreeIdsCache", () => { expect(result.length).to.be.eq(2); }); - it("when multiple categories have multiple subCategories", async function () { + it("returns only child subCategories when multiple categories have multiple subCategories", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const category = insertSpatialCategory({ builder, codeValue: "Test SpatialCategory" }); From 47418fae4644e0d30648fc27cf9c8438b5d06aa5 Mon Sep 17 00:00:00 2001 From: Jonas <100586436+JonasDov@users.noreply.github.com> Date: Tue, 4 Feb 2025 10:47:40 +0200 Subject: [PATCH 26/38] Adjust expect calls --- .../internal/CategoriesTreeIdsCache.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeIdsCache.test.ts b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeIdsCache.test.ts index 1b16bcb9d..3b37babd0 100644 --- a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeIdsCache.test.ts +++ b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesTreeIdsCache.test.ts @@ -234,7 +234,7 @@ describe("CategoriesTreeIdsCache", () => { const idsCache = new CategoriesTreeIdsCache(createIModelAccess(imodel), "3d"); const result = await idsCache.getAllContainedCategories([keys.definitionContainerRoot.id]); const expectedResult = [keys.indirectCategory.id, keys.directCategory.id]; - expect(result.every((id) => expectedResult.includes(id))).to.be.true; + expect(expectedResult.every((id) => result.includes(id))).to.be.true; }); }); @@ -474,7 +474,7 @@ describe("CategoriesTreeIdsCache", () => { definitionContainers: [keys.definitionContainerRoot.id, keys.definitionContainerChild.id], }; expect(result.categories).to.deep.eq(expectedResult.categories); - expect(result.definitionContainers.every((dc) => expectedResult.definitionContainers.includes(dc))).to.be.true; + expect(expectedResult.definitionContainers.every((dc) => result.definitionContainers.includes(dc))).to.be.true; }); it("returns definition container and category when definition container contains category", async function () { @@ -534,8 +534,8 @@ describe("CategoriesTreeIdsCache", () => { categories: [keys.directCategory.id, keys.indirectCategory.id], definitionContainers: [keys.definitionModelChild.id, keys.definitionContainerRoot.id], }; - expect(result.categories.every((c) => expectedResult.categories.includes(c))).to.be.true; - expect(result.definitionContainers.every((dc) => expectedResult.definitionContainers.includes(dc))).to.be.true; + expect(expectedResult.categories.every((c) => result.categories.includes(c))).to.be.true; + expect(expectedResult.definitionContainers.every((dc) => result.definitionContainers.includes(dc))).to.be.true; }); }); @@ -650,8 +650,8 @@ describe("CategoriesTreeIdsCache", () => { categories: [keys.rootCategory1.id, keys.rootCategory2.id], definitionContainers: [keys.definitionContainerRoot.id, keys.definitionContainerRoot2.id], }; - expect(result.categories.every((c) => expectedResult.categories.includes(c))).to.be.true; - expect(result.definitionContainers.every((dc) => expectedResult.definitionContainers.includes(dc))).to.be.true; + expect(expectedResult.categories.every((c) => result.categories.includes(c))).to.be.true; + expect(expectedResult.definitionContainers.every((dc) => result.definitionContainers.includes(dc))).to.be.true; }); }); From 05988d0aa35d37455d6ad7ffd7391b77137df28a Mon Sep 17 00:00:00 2001 From: Jonas <100586436+JonasDov@users.noreply.github.com> Date: Tue, 4 Feb 2025 10:48:10 +0200 Subject: [PATCH 27/38] Dispose provider --- .../categories-tree/internal/CategoriesVisibilityHandler.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts index 46ee95bbb..d59f7f5d9 100644 --- a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts +++ b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts @@ -90,6 +90,7 @@ describe("CategoriesVisibilityHandler", () => { ...commonProps, [Symbol.dispose]() { handler[Symbol.dispose](); + provider[Symbol.dispose](); }, }; } From c226c0e2359eb3f2a6fdcc6f0a85b7f931794684 Mon Sep 17 00:00:00 2001 From: Jonas <100586436+JonasDov@users.noreply.github.com> Date: Tue, 4 Feb 2025 11:18:43 +0200 Subject: [PATCH 28/38] Adjust viewport mock creation --- .../CategoriesVisibilityHandler.test.ts | 431 +++++++----------- .../trees/categories-tree/internal/Utils.ts | 188 ++++---- .../internal/VisibilityValidation.ts | 4 +- 3 files changed, 258 insertions(+), 365 deletions(-) diff --git a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts index d59f7f5d9..d110b8325 100644 --- a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts +++ b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts @@ -25,7 +25,7 @@ import { } from "../../../IModelUtils.js"; import { TestUtils } from "../../../TestUtils.js"; import { createIModelAccess } from "../../Common.js"; -import { createCategoryHierarchyNode, createDefinitionContainerHierarchyNode, createSubCategoryHierarchyNode, ViewportMock } from "./Utils.js"; +import { createCategoryHierarchyNode, createDefinitionContainerHierarchyNode, createSubCategoryHierarchyNode, createViewportStub } from "./Utils.js"; import { validateHierarchyVisibility, VisibilityExpectations } from "./VisibilityValidation.js"; import type { IModelConnection } from "@itwin/core-frontend"; @@ -55,12 +55,11 @@ describe("CategoriesVisibilityHandler", () => { await terminatePresentationTesting(); }); - async function createCommonProps(imodel: IModelConnection) { + async function createCommonProps(imodel: IModelConnection, isVisibleOnInitialize: boolean) { const imodelAccess = createIModelAccess(imodel); const idsCache = new CategoriesTreeIdsCache(imodelAccess, "3d"); - const viewportMock = new ViewportMock(idsCache); - const viewport = await viewportMock.createViewportStub(); + const viewport = await createViewportStub(idsCache, isVisibleOnInitialize); return { imodelAccess, viewport, @@ -80,8 +79,8 @@ describe("CategoriesVisibilityHandler", () => { }); } - async function createVisibilityTestData({ imodel }: { imodel: IModelConnection }) { - const commonProps = await createCommonProps(imodel); + async function createVisibilityTestData({ imodel, isVisibleOnInitialize }: { imodel: IModelConnection; isVisibleOnInitialize: boolean }) { + const commonProps = await createCommonProps(imodel, isVisibleOnInitialize); const handler = new CategoriesVisibilityHandler(commonProps); const provider = createProvider({ ...commonProps }); return { @@ -95,30 +94,31 @@ describe("CategoriesVisibilityHandler", () => { }; } - it("by default everything is hidden", async function () { - const { imodel, ...keys } = await buildIModel(this, async (builder) => { - const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); - const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer" }); - const definitionModel = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainer.id }); - - const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory", modelId: definitionModel.id }); - insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); - const subCategory = insertSubCategory({ builder, parentCategoryId: category.id, codeValue: "subCategory", modelId: definitionModel.id }); - return { definitionContainer, category, subCategory }; - }); - const nodesToExpect = [keys.category.id, keys.definitionContainer.id, keys.subCategory.id]; - using visibilityTestData = await createVisibilityTestData({ imodel }); - const { handler, provider, viewport } = visibilityTestData; - await validateHierarchyVisibility({ - provider, - handler, - viewport, - visibilityExpectations: VisibilityExpectations.all("hidden"), - nodesToExpect, - }); - }); - describe("enabling visibility", () => { + const isVisibleOnInitialize = false; + + it("by default everything is hidden", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer" }); + const definitionModel = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainer.id }); + + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory", modelId: definitionModel.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + const subCategory = insertSubCategory({ builder, parentCategoryId: category.id, codeValue: "subCategory", modelId: definitionModel.id }); + return { definitionContainer, category, subCategory }; + }); + const expectedIds = [keys.category.id, keys.definitionContainer.id, keys.subCategory.id]; + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); + const { handler, provider, viewport } = visibilityTestData; + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: VisibilityExpectations.all("hidden"), + expectedIds, + }); + }); describe("definitionContainers", () => { it("showing definition container makes it and all of its contained elements visible", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { @@ -142,14 +142,14 @@ describe("CategoriesVisibilityHandler", () => { return { definitionContainerRoot, definitionContainerChild, directCategory, indirectCategory, indirectSubCategory }; }); - const nodesToExpect = [ + const expectedIds = [ keys.definitionContainerRoot.id, keys.definitionContainerChild.id, keys.directCategory.id, keys.indirectCategory.id, keys.indirectSubCategory.id, ]; - using visibilityTestData = await createVisibilityTestData({ imodel }); + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerRoot.id), true); await validateHierarchyVisibility({ @@ -157,10 +157,9 @@ describe("CategoriesVisibilityHandler", () => { handler, viewport, visibilityExpectations: VisibilityExpectations.all("visible"), - nodesToExpect, + expectedIds, }); - ViewportMock.validateChangesCalls( - viewport, + viewport.validateChangesCalls( [{ categoriesToChange: [keys.directCategory.id, keys.indirectCategory.id], isVisible: true, enableAllSubCategories: true }], [], ); @@ -199,7 +198,7 @@ describe("CategoriesVisibilityHandler", () => { }; }); - const nodesToExpect = [ + const expectedIds = [ keys.definitionContainerRoot.id, keys.definitionContainerChild.id, keys.definitionContainerRoot2.id, @@ -208,7 +207,7 @@ describe("CategoriesVisibilityHandler", () => { keys.indirectSubCategory.id, keys.subCategory2.id, ]; - using visibilityTestData = await createVisibilityTestData({ imodel }); + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerRoot.id), true); await validateHierarchyVisibility({ @@ -220,9 +219,9 @@ describe("CategoriesVisibilityHandler", () => { subCategory: (parentCategoryId) => (parentCategoryId === keys.category2.id ? "hidden" : "visible"), definitionContainer: (definitionContainerId) => (definitionContainerId === keys.definitionContainerRoot2.id ? "hidden" : "visible"), }, - nodesToExpect, + expectedIds, }); - ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.indirectCategory.id], isVisible: true, enableAllSubCategories: true }], []); + viewport.validateChangesCalls([{ categoriesToChange: [keys.indirectCategory.id], isVisible: true, enableAllSubCategories: true }], []); }); it("showing definition container makes it and all of its contained elements visible, and parent container partially visible if it has more direct child categories", async function () { @@ -241,9 +240,9 @@ describe("CategoriesVisibilityHandler", () => { return { definitionContainerRoot, definitionContainerChild, directCategory, indirectCategory }; }); - const nodesToExpect = [keys.definitionContainerRoot.id, keys.definitionContainerChild.id, keys.directCategory.id, keys.indirectCategory.id]; + const expectedIds = [keys.definitionContainerRoot.id, keys.definitionContainerChild.id, keys.directCategory.id, keys.indirectCategory.id]; - using visibilityTestData = await createVisibilityTestData({ imodel }); + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerChild.id), true); await validateHierarchyVisibility({ @@ -254,9 +253,9 @@ describe("CategoriesVisibilityHandler", () => { category: (categoryId) => (categoryId === keys.directCategory.id ? "hidden" : "visible"), definitionContainer: (definitionContainerId) => (definitionContainerId === keys.definitionContainerRoot.id ? "partial" : "visible"), }, - nodesToExpect, + expectedIds, }); - ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.indirectCategory.id], isVisible: true, enableAllSubCategories: true }], []); + viewport.validateChangesCalls([{ categoriesToChange: [keys.indirectCategory.id], isVisible: true, enableAllSubCategories: true }], []); }); it("showing definition container makes it and all of its contained elements visible, and parent container partially visible if it has more definition containers", async function () { @@ -277,7 +276,7 @@ describe("CategoriesVisibilityHandler", () => { return { definitionContainerRoot, definitionContainerChild, indirectCategory2, indirectCategory, definitionContainerChild2 }; }); - const nodesToExpect = [ + const expectedIds = [ keys.definitionContainerRoot.id, keys.definitionContainerChild.id, keys.indirectCategory.id, @@ -285,7 +284,7 @@ describe("CategoriesVisibilityHandler", () => { keys.indirectCategory2.id, ]; - using visibilityTestData = await createVisibilityTestData({ imodel }); + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerChild.id), true); await validateHierarchyVisibility({ @@ -304,9 +303,9 @@ describe("CategoriesVisibilityHandler", () => { return "visible"; }, }, - nodesToExpect, + expectedIds, }); - ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.indirectCategory.id], isVisible: true, enableAllSubCategories: true }], []); + viewport.validateChangesCalls([{ categoriesToChange: [keys.indirectCategory.id], isVisible: true, enableAllSubCategories: true }], []); }); it("showing child definition container makes it, all of its contained elements and its parent definition container visible", async function () { @@ -328,9 +327,9 @@ describe("CategoriesVisibilityHandler", () => { return { definitionContainerRoot, definitionContainerChild, indirectCategory, indirectSubCategory }; }); - const nodesToExpect = [keys.definitionContainerRoot.id, keys.definitionContainerChild.id, keys.indirectCategory.id, keys.indirectSubCategory.id]; + const expectedIds = [keys.definitionContainerRoot.id, keys.definitionContainerChild.id, keys.indirectCategory.id, keys.indirectSubCategory.id]; - using visibilityTestData = await createVisibilityTestData({ imodel }); + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerChild.id), true); await validateHierarchyVisibility({ @@ -338,9 +337,9 @@ describe("CategoriesVisibilityHandler", () => { handler, viewport, visibilityExpectations: VisibilityExpectations.all("visible"), - nodesToExpect, + expectedIds, }); - ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.indirectCategory.id], isVisible: true, enableAllSubCategories: true }], []); + viewport.validateChangesCalls([{ categoriesToChange: [keys.indirectCategory.id], isVisible: true, enableAllSubCategories: true }], []); }); }); @@ -359,8 +358,8 @@ describe("CategoriesVisibilityHandler", () => { return { category, subCategory }; }); - const nodesToExpect = [keys.category.id, keys.subCategory.id]; - using visibilityTestData = await createVisibilityTestData({ imodel }); + const expectedIds = [keys.category.id, keys.subCategory.id]; + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), true); await validateHierarchyVisibility({ @@ -368,9 +367,9 @@ describe("CategoriesVisibilityHandler", () => { handler, viewport, visibilityExpectations: VisibilityExpectations.all("visible"), - nodesToExpect, + expectedIds, }); - ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: true }], []); + viewport.validateChangesCalls([{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: true }], []); }); it("showing category makes it, all of its contained subCategories visible and doesn't affect other categories", async function () { @@ -394,8 +393,8 @@ describe("CategoriesVisibilityHandler", () => { return { category, category2, subCategory, subCategory2 }; }); - const nodesToExpect = [keys.category.id, keys.category2.id, keys.subCategory.id, keys.subCategory2.id]; - using visibilityTestData = await createVisibilityTestData({ imodel }); + const expectedIds = [keys.category.id, keys.category2.id, keys.subCategory.id, keys.subCategory2.id]; + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), true); await validateHierarchyVisibility({ @@ -406,9 +405,9 @@ describe("CategoriesVisibilityHandler", () => { category: (categoryId) => (categoryId === keys.category2.id ? "hidden" : "visible"), subCategory: (parentCategoryId) => (parentCategoryId === keys.category2.id ? "hidden" : "visible"), }, - nodesToExpect, + expectedIds, }); - ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: true }], []); + viewport.validateChangesCalls([{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: true }], []); }); it("showing category makes it, all of its contained subCategories visible and doesn't affect non related definition container", async function () { @@ -436,8 +435,8 @@ describe("CategoriesVisibilityHandler", () => { return { definitionContainer, category, category2, subCategory, subCategory2 }; }); - const nodesToExpect = [keys.definitionContainer.id, keys.category.id, keys.category2.id, keys.subCategory.id, keys.subCategory2.id]; - using visibilityTestData = await createVisibilityTestData({ imodel }); + const expectedIds = [keys.definitionContainer.id, keys.category.id, keys.category2.id, keys.subCategory.id, keys.subCategory2.id]; + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), true); await validateHierarchyVisibility({ @@ -449,9 +448,9 @@ describe("CategoriesVisibilityHandler", () => { subCategory: (parentCategoryId) => (parentCategoryId === keys.category2.id ? "hidden" : "visible"), definitionContainer: () => "hidden", }, - nodesToExpect, + expectedIds, }); - ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: true }], []); + viewport.validateChangesCalls([{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: true }], []); }); it("showing category makes it and all of its subcategories visible, and parent container partially visible if it has more direct child categories", async function () { @@ -479,9 +478,9 @@ describe("CategoriesVisibilityHandler", () => { return { definitionContainerRoot, category, category2, subCategory, subCategory2 }; }); - const nodesToExpect = [keys.definitionContainerRoot.id, keys.category.id, keys.category2.id, keys.subCategory.id, keys.subCategory2.id]; + const expectedIds = [keys.definitionContainerRoot.id, keys.category.id, keys.category2.id, keys.subCategory.id, keys.subCategory2.id]; - using visibilityTestData = await createVisibilityTestData({ imodel }); + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), true); await validateHierarchyVisibility({ @@ -493,9 +492,9 @@ describe("CategoriesVisibilityHandler", () => { subCategory: (parentCategoryId) => (parentCategoryId === keys.category2.id ? "hidden" : "visible"), definitionContainer: () => "partial", }, - nodesToExpect, + expectedIds, }); - ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: true }], []); + viewport.validateChangesCalls([{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: true }], []); }); it("showing category makes it and all of its subCategories visible, and parent container partially visible if it has more definition containers", async function () { @@ -520,7 +519,7 @@ describe("CategoriesVisibilityHandler", () => { return { definitionContainerRoot, definitionContainerChild, category, indirectCategory, subCategory }; }); - const nodesToExpect = [ + const expectedIds = [ keys.definitionContainerRoot.id, keys.definitionContainerChild.id, keys.indirectCategory.id, @@ -528,7 +527,7 @@ describe("CategoriesVisibilityHandler", () => { keys.subCategory.id, ]; - using visibilityTestData = await createVisibilityTestData({ imodel }); + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), true); await validateHierarchyVisibility({ @@ -545,9 +544,9 @@ describe("CategoriesVisibilityHandler", () => { return "hidden"; }, }, - nodesToExpect, + expectedIds, }); - ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: true }], []); + viewport.validateChangesCalls([{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: true }], []); }); }); @@ -571,8 +570,8 @@ describe("CategoriesVisibilityHandler", () => { return { category, subCategory, subCategory2 }; }); - const nodesToExpect = [keys.category.id, keys.subCategory.id, keys.subCategory2.id]; - using visibilityTestData = await createVisibilityTestData({ imodel }); + const expectedIds = [keys.category.id, keys.subCategory.id, keys.subCategory2.id]; + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; await handler.changeVisibility(createSubCategoryHierarchyNode(keys.subCategory.id, keys.category.id), true); @@ -585,10 +584,9 @@ describe("CategoriesVisibilityHandler", () => { category: () => "partial", subCategory: (_, subCategoryId) => (subCategoryId === keys.subCategory.id ? "visible" : "hidden"), }, - nodesToExpect, + expectedIds, }); - ViewportMock.validateChangesCalls( - viewport, + viewport.validateChangesCalls( [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: false }], [{ subCategoryId: keys.subCategory.id, isVisible: true }], ); @@ -610,8 +608,8 @@ describe("CategoriesVisibilityHandler", () => { return { category, subCategory, category2 }; }); - const nodesToExpect = [keys.category.id, keys.subCategory.id, keys.category2.id]; - using visibilityTestData = await createVisibilityTestData({ imodel }); + const expectedIds = [keys.category.id, keys.subCategory.id, keys.category2.id]; + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; await handler.changeVisibility(createSubCategoryHierarchyNode(keys.subCategory.id, keys.category.id), true); await validateHierarchyVisibility({ @@ -622,10 +620,9 @@ describe("CategoriesVisibilityHandler", () => { category: (categoryId) => (categoryId === keys.category.id ? "partial" : "hidden"), subCategory: (_, subCategoryId) => (subCategoryId === keys.subCategory.id ? "visible" : "hidden"), }, - nodesToExpect, + expectedIds, }); - ViewportMock.validateChangesCalls( - viewport, + viewport.validateChangesCalls( [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: false }], [{ subCategoryId: keys.subCategory.id, isVisible: true }], ); @@ -648,8 +645,8 @@ describe("CategoriesVisibilityHandler", () => { return { category, subCategory, definitionContainerRoot }; }); - const nodesToExpect = [keys.category.id, keys.subCategory.id, keys.definitionContainerRoot.id]; - using visibilityTestData = await createVisibilityTestData({ imodel }); + const expectedIds = [keys.category.id, keys.subCategory.id, keys.definitionContainerRoot.id]; + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; await handler.changeVisibility(createSubCategoryHierarchyNode(keys.subCategory.id, keys.category.id), true); await validateHierarchyVisibility({ @@ -661,10 +658,9 @@ describe("CategoriesVisibilityHandler", () => { subCategory: (_, subCategoryId) => (subCategoryId === keys.subCategory.id ? "visible" : "hidden"), definitionContainer: () => "partial", }, - nodesToExpect, + expectedIds, }); - ViewportMock.validateChangesCalls( - viewport, + viewport.validateChangesCalls( [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: false }], [{ subCategoryId: keys.subCategory.id, isVisible: true }], ); @@ -694,14 +690,14 @@ describe("CategoriesVisibilityHandler", () => { return { category, subCategory, definitionContainerRoot, categoryOfDefinitionContainer, subCategoryOfDefinitionContainer }; }); - const nodesToExpect = [ + const expectedIds = [ keys.category.id, keys.subCategory.id, keys.definitionContainerRoot.id, keys.categoryOfDefinitionContainer.id, keys.subCategoryOfDefinitionContainer.id, ]; - using visibilityTestData = await createVisibilityTestData({ imodel }); + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; await handler.changeVisibility(createSubCategoryHierarchyNode(keys.subCategory.id, keys.category.id), true); await validateHierarchyVisibility({ @@ -713,10 +709,9 @@ describe("CategoriesVisibilityHandler", () => { subCategory: (_, subCategoryId) => (subCategoryId === keys.subCategory.id ? "visible" : "hidden"), definitionContainer: () => "hidden", }, - nodesToExpect, + expectedIds, }); - ViewportMock.validateChangesCalls( - viewport, + viewport.validateChangesCalls( [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: false }], [{ subCategoryId: keys.subCategory.id, isVisible: true }], ); @@ -725,6 +720,30 @@ describe("CategoriesVisibilityHandler", () => { }); describe("disabling visibility", () => { + const isVisibleOnInitialize = true; + + it("by default everything is visible", async function () { + const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); + const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer" }); + const definitionModel = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainer.id }); + + const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory", modelId: definitionModel.id }); + insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); + const subCategory = insertSubCategory({ builder, parentCategoryId: category.id, codeValue: "subCategory", modelId: definitionModel.id }); + return { definitionContainer, category, subCategory }; + }); + const expectedIds = [keys.category.id, keys.definitionContainer.id, keys.subCategory.id]; + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); + const { handler, provider, viewport } = visibilityTestData; + await validateHierarchyVisibility({ + provider, + handler, + viewport, + visibilityExpectations: VisibilityExpectations.all("visible"), + expectedIds, + }); + }); describe("definitionContainers", () => { it("hiding definition container makes it and all of its contained elements hidden", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { @@ -748,33 +767,24 @@ describe("CategoriesVisibilityHandler", () => { return { definitionContainerRoot, definitionContainerChild, directCategory, indirectCategory, indirectSubCategory }; }); - const nodesToExpect = [ + const expectedIds = [ keys.definitionContainerRoot.id, keys.definitionContainerChild.id, keys.directCategory.id, keys.indirectCategory.id, keys.indirectSubCategory.id, ]; - using visibilityTestData = await createVisibilityTestData({ imodel }); + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; - await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerRoot.id), true); - await validateHierarchyVisibility({ - provider, - handler, - viewport, - visibilityExpectations: VisibilityExpectations.all("visible"), - nodesToExpect, - }); await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerRoot.id), false); await validateHierarchyVisibility({ provider, handler, viewport, visibilityExpectations: VisibilityExpectations.all("hidden"), - nodesToExpect, + expectedIds, }); - ViewportMock.validateChangesCalls( - viewport, + viewport.validateChangesCalls( [{ categoriesToChange: [keys.directCategory.id, keys.indirectCategory.id], isVisible: false, enableAllSubCategories: false }], [], ); @@ -813,7 +823,7 @@ describe("CategoriesVisibilityHandler", () => { }; }); - const nodesToExpect = [ + const expectedIds = [ keys.definitionContainerRoot.id, keys.definitionContainerChild.id, keys.definitionContainerRoot2.id, @@ -822,17 +832,8 @@ describe("CategoriesVisibilityHandler", () => { keys.indirectSubCategory.id, keys.subCategory2.id, ]; - using visibilityTestData = await createVisibilityTestData({ imodel }); + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; - await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerRoot.id), true); - await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerRoot2.id), true); - await validateHierarchyVisibility({ - provider, - handler, - viewport, - visibilityExpectations: VisibilityExpectations.all("visible"), - nodesToExpect, - }); await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerRoot.id), false); await validateHierarchyVisibility({ provider, @@ -843,9 +844,9 @@ describe("CategoriesVisibilityHandler", () => { subCategory: (parentCategoryId) => (parentCategoryId === keys.indirectCategory.id ? "hidden" : "visible"), definitionContainer: (definitionContainerId) => (definitionContainerId !== keys.definitionContainerRoot2.id ? "hidden" : "visible"), }, - nodesToExpect, + expectedIds, }); - ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.indirectCategory.id], isVisible: false, enableAllSubCategories: false }], []); + viewport.validateChangesCalls([{ categoriesToChange: [keys.indirectCategory.id], isVisible: false, enableAllSubCategories: false }], []); }); it("hiding definition container makes it and all of its contained elements hidden, and parent container partially visible if it has more direct child categories", async function () { @@ -864,18 +865,10 @@ describe("CategoriesVisibilityHandler", () => { return { definitionContainerRoot, definitionContainerChild, directCategory, indirectCategory }; }); - const nodesToExpect = [keys.definitionContainerRoot.id, keys.definitionContainerChild.id, keys.directCategory.id, keys.indirectCategory.id]; + const expectedIds = [keys.definitionContainerRoot.id, keys.definitionContainerChild.id, keys.directCategory.id, keys.indirectCategory.id]; - using visibilityTestData = await createVisibilityTestData({ imodel }); + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; - await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerRoot.id), true); - await validateHierarchyVisibility({ - provider, - handler, - viewport, - visibilityExpectations: VisibilityExpectations.all("visible"), - nodesToExpect, - }); await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerChild.id), false); await validateHierarchyVisibility({ @@ -886,9 +879,9 @@ describe("CategoriesVisibilityHandler", () => { category: (categoryId) => (categoryId === keys.indirectCategory.id ? "hidden" : "visible"), definitionContainer: (definitionContainerId) => (definitionContainerId === keys.definitionContainerRoot.id ? "partial" : "hidden"), }, - nodesToExpect, + expectedIds, }); - ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.indirectCategory.id], isVisible: false, enableAllSubCategories: false }], []); + viewport.validateChangesCalls([{ categoriesToChange: [keys.indirectCategory.id], isVisible: false, enableAllSubCategories: false }], []); }); it("hiding definition container makes it and all of its contained elements hidden, and parent container partially visible if it has more definition containers", async function () { @@ -909,7 +902,7 @@ describe("CategoriesVisibilityHandler", () => { return { definitionContainerRoot, definitionContainerChild, indirectCategory2, indirectCategory, definitionContainerChild2 }; }); - const nodesToExpect = [ + const expectedIds = [ keys.definitionContainerRoot.id, keys.definitionContainerChild.id, keys.indirectCategory.id, @@ -917,16 +910,8 @@ describe("CategoriesVisibilityHandler", () => { keys.indirectCategory2.id, ]; - using visibilityTestData = await createVisibilityTestData({ imodel }); + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; - await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerRoot.id), true); - await validateHierarchyVisibility({ - provider, - handler, - viewport, - visibilityExpectations: VisibilityExpectations.all("visible"), - nodesToExpect, - }); await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerChild.id), false); await validateHierarchyVisibility({ @@ -945,9 +930,9 @@ describe("CategoriesVisibilityHandler", () => { return "visible"; }, }, - nodesToExpect, + expectedIds, }); - ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.indirectCategory.id], isVisible: false, enableAllSubCategories: false }], []); + viewport.validateChangesCalls([{ categoriesToChange: [keys.indirectCategory.id], isVisible: false, enableAllSubCategories: false }], []); }); it("hiding child definition container makes it, all of its contained elements and its parent definition container hidden", async function () { @@ -969,18 +954,10 @@ describe("CategoriesVisibilityHandler", () => { return { definitionContainerRoot, definitionContainerChild, indirectCategory, indirectSubCategory }; }); - const nodesToExpect = [keys.definitionContainerRoot.id, keys.definitionContainerChild.id, keys.indirectCategory.id, keys.indirectSubCategory.id]; + const expectedIds = [keys.definitionContainerRoot.id, keys.definitionContainerChild.id, keys.indirectCategory.id, keys.indirectSubCategory.id]; - using visibilityTestData = await createVisibilityTestData({ imodel }); + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; - await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerChild.id), true); - await validateHierarchyVisibility({ - provider, - handler, - viewport, - visibilityExpectations: VisibilityExpectations.all("visible"), - nodesToExpect, - }); await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerChild.id), false); await validateHierarchyVisibility({ @@ -988,9 +965,9 @@ describe("CategoriesVisibilityHandler", () => { handler, viewport, visibilityExpectations: VisibilityExpectations.all("hidden"), - nodesToExpect, + expectedIds, }); - ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.indirectCategory.id], isVisible: false, enableAllSubCategories: false }], []); + viewport.validateChangesCalls([{ categoriesToChange: [keys.indirectCategory.id], isVisible: false, enableAllSubCategories: false }], []); }); }); @@ -1009,17 +986,9 @@ describe("CategoriesVisibilityHandler", () => { return { category, subCategory }; }); - const nodesToExpect = [keys.category.id, keys.subCategory.id]; - using visibilityTestData = await createVisibilityTestData({ imodel }); + const expectedIds = [keys.category.id, keys.subCategory.id]; + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; - await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), true); - await validateHierarchyVisibility({ - provider, - handler, - viewport, - visibilityExpectations: VisibilityExpectations.all("visible"), - nodesToExpect, - }); await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), false); await validateHierarchyVisibility({ @@ -1027,9 +996,9 @@ describe("CategoriesVisibilityHandler", () => { handler, viewport, visibilityExpectations: VisibilityExpectations.all("hidden"), - nodesToExpect, + expectedIds, }); - ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.category.id], isVisible: false, enableAllSubCategories: false }], []); + viewport.validateChangesCalls([{ categoriesToChange: [keys.category.id], isVisible: false, enableAllSubCategories: false }], []); }); it("hiding category makes it, all of its contained subCategories hidden and doesn't affect other categories", async function () { @@ -1053,18 +1022,9 @@ describe("CategoriesVisibilityHandler", () => { return { category, category2, subCategory, subCategory2 }; }); - const nodesToExpect = [keys.category.id, keys.category2.id, keys.subCategory.id, keys.subCategory2.id]; - using visibilityTestData = await createVisibilityTestData({ imodel }); + const expectedIds = [keys.category.id, keys.category2.id, keys.subCategory.id, keys.subCategory2.id]; + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; - await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), true); - await handler.changeVisibility(createCategoryHierarchyNode(keys.category2.id), true); - await validateHierarchyVisibility({ - provider, - handler, - viewport, - visibilityExpectations: VisibilityExpectations.all("visible"), - nodesToExpect, - }); await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), false); await validateHierarchyVisibility({ @@ -1075,9 +1035,9 @@ describe("CategoriesVisibilityHandler", () => { category: (categoryId) => (categoryId === keys.category.id ? "hidden" : "visible"), subCategory: (parentCategoryId) => (parentCategoryId === keys.category.id ? "hidden" : "visible"), }, - nodesToExpect, + expectedIds, }); - ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.category.id], isVisible: false, enableAllSubCategories: false }], []); + viewport.validateChangesCalls([{ categoriesToChange: [keys.category.id], isVisible: false, enableAllSubCategories: false }], []); }); it("hiding category makes it, all of its contained subCategories hidden and doesn't affect non related definition container", async function () { @@ -1105,18 +1065,9 @@ describe("CategoriesVisibilityHandler", () => { return { definitionContainer, category, category2, subCategory, subCategory2 }; }); - const nodesToExpect = [keys.definitionContainer.id, keys.category.id, keys.category2.id, keys.subCategory.id, keys.subCategory2.id]; - using visibilityTestData = await createVisibilityTestData({ imodel }); + const expectedIds = [keys.definitionContainer.id, keys.category.id, keys.category2.id, keys.subCategory.id, keys.subCategory2.id]; + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; - await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), true); - await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainer.id), true); - await validateHierarchyVisibility({ - provider, - handler, - viewport, - visibilityExpectations: VisibilityExpectations.all("visible"), - nodesToExpect, - }); await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), false); await validateHierarchyVisibility({ @@ -1128,9 +1079,9 @@ describe("CategoriesVisibilityHandler", () => { subCategory: (parentCategoryId) => (parentCategoryId === keys.category.id ? "hidden" : "visible"), definitionContainer: () => "visible", }, - nodesToExpect, + expectedIds, }); - ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.category.id], isVisible: false, enableAllSubCategories: false }], []); + viewport.validateChangesCalls([{ categoriesToChange: [keys.category.id], isVisible: false, enableAllSubCategories: false }], []); }); it("hiding category makes it and all of its subcategories hidden, and parent container partially visible if it has more direct child categories", async function () { @@ -1158,18 +1109,10 @@ describe("CategoriesVisibilityHandler", () => { return { definitionContainerRoot, category, category2, subCategory, subCategory2 }; }); - const nodesToExpect = [keys.definitionContainerRoot.id, keys.category.id, keys.category2.id, keys.subCategory.id, keys.subCategory2.id]; + const expectedIds = [keys.definitionContainerRoot.id, keys.category.id, keys.category2.id, keys.subCategory.id, keys.subCategory2.id]; - using visibilityTestData = await createVisibilityTestData({ imodel }); + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; - await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerRoot.id), true); - await validateHierarchyVisibility({ - provider, - handler, - viewport, - visibilityExpectations: VisibilityExpectations.all("visible"), - nodesToExpect, - }); await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), false); await validateHierarchyVisibility({ @@ -1181,9 +1124,9 @@ describe("CategoriesVisibilityHandler", () => { subCategory: (parentCategoryId) => (parentCategoryId === keys.category.id ? "hidden" : "visible"), definitionContainer: () => "partial", }, - nodesToExpect, + expectedIds, }); - ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.category.id], isVisible: false, enableAllSubCategories: false }], []); + viewport.validateChangesCalls([{ categoriesToChange: [keys.category.id], isVisible: false, enableAllSubCategories: false }], []); }); it("hiding category makes it and all of its subCategories hidden, and parent container partially visible if it has more definition containers", async function () { @@ -1208,7 +1151,7 @@ describe("CategoriesVisibilityHandler", () => { return { definitionContainerRoot, definitionContainerChild, category, indirectCategory, subCategory }; }); - const nodesToExpect = [ + const expectedIds = [ keys.definitionContainerRoot.id, keys.definitionContainerChild.id, keys.indirectCategory.id, @@ -1216,16 +1159,8 @@ describe("CategoriesVisibilityHandler", () => { keys.subCategory.id, ]; - using visibilityTestData = await createVisibilityTestData({ imodel }); + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; - await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerRoot.id), true); - await validateHierarchyVisibility({ - provider, - handler, - viewport, - visibilityExpectations: VisibilityExpectations.all("visible"), - nodesToExpect, - }); await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), false); await validateHierarchyVisibility({ @@ -1242,9 +1177,9 @@ describe("CategoriesVisibilityHandler", () => { return "visible"; }, }, - nodesToExpect, + expectedIds, }); - ViewportMock.validateChangesCalls(viewport, [{ categoriesToChange: [keys.category.id], isVisible: false, enableAllSubCategories: false }], []); + viewport.validateChangesCalls([{ categoriesToChange: [keys.category.id], isVisible: false, enableAllSubCategories: false }], []); }); }); @@ -1268,20 +1203,10 @@ describe("CategoriesVisibilityHandler", () => { return { category, subCategory, subCategory2 }; }); - const nodesToExpect = [keys.category.id, keys.subCategory.id, keys.subCategory2.id]; - using visibilityTestData = await createVisibilityTestData({ imodel }); + const expectedIds = [keys.category.id, keys.subCategory.id, keys.subCategory2.id]; + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; - await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), true); - - await validateHierarchyVisibility({ - provider, - handler, - viewport, - visibilityExpectations: VisibilityExpectations.all("visible"), - nodesToExpect, - }); - await handler.changeVisibility(createSubCategoryHierarchyNode(keys.subCategory.id, keys.category.id), false); await validateHierarchyVisibility({ provider, @@ -1291,9 +1216,9 @@ describe("CategoriesVisibilityHandler", () => { category: () => "partial", subCategory: (_, subCategoryId) => (subCategoryId === keys.subCategory.id ? "hidden" : "visible"), }, - nodesToExpect, + expectedIds, }); - ViewportMock.validateChangesCalls(viewport, [], [{ subCategoryId: keys.subCategory.id, isVisible: false }]); + viewport.validateChangesCalls([], [{ subCategoryId: keys.subCategory.id, isVisible: false }]); }); it("showing subCategory makes it visible and its parent category partially visible, and doesn't affect other categories", async function () { @@ -1312,18 +1237,9 @@ describe("CategoriesVisibilityHandler", () => { return { category, subCategory, category2 }; }); - const nodesToExpect = [keys.category.id, keys.subCategory.id, keys.category2.id]; - using visibilityTestData = await createVisibilityTestData({ imodel }); + const expectedIds = [keys.category.id, keys.subCategory.id, keys.category2.id]; + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; - await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), true); - await handler.changeVisibility(createCategoryHierarchyNode(keys.category2.id), true); - await validateHierarchyVisibility({ - provider, - handler, - viewport, - visibilityExpectations: VisibilityExpectations.all("visible"), - nodesToExpect, - }); await handler.changeVisibility(createSubCategoryHierarchyNode(keys.subCategory.id, keys.category.id), false); await validateHierarchyVisibility({ @@ -1334,9 +1250,9 @@ describe("CategoriesVisibilityHandler", () => { category: (categoryId) => (categoryId === keys.category.id ? "partial" : "visible"), subCategory: (_, subCategoryId) => (subCategoryId === keys.subCategory.id ? "hidden" : "visible"), }, - nodesToExpect, + expectedIds, }); - ViewportMock.validateChangesCalls(viewport, [], [{ subCategoryId: keys.subCategory.id, isVisible: false }]); + viewport.validateChangesCalls([], [{ subCategoryId: keys.subCategory.id, isVisible: false }]); }); it("hiding subCategory makes it hidden and parents partially visible", async function () { @@ -1356,17 +1272,9 @@ describe("CategoriesVisibilityHandler", () => { return { category, subCategory, definitionContainerRoot }; }); - const nodesToExpect = [keys.category.id, keys.subCategory.id, keys.definitionContainerRoot.id]; - using visibilityTestData = await createVisibilityTestData({ imodel }); + const expectedIds = [keys.category.id, keys.subCategory.id, keys.definitionContainerRoot.id]; + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; - await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerRoot.id), true); - await validateHierarchyVisibility({ - provider, - handler, - viewport, - visibilityExpectations: VisibilityExpectations.all("visible"), - nodesToExpect, - }); await handler.changeVisibility(createSubCategoryHierarchyNode(keys.subCategory.id, keys.category.id), false); await validateHierarchyVisibility({ @@ -1378,9 +1286,9 @@ describe("CategoriesVisibilityHandler", () => { subCategory: (_, subCategoryId) => (subCategoryId === keys.subCategory.id ? "hidden" : "visible"), definitionContainer: () => "partial", }, - nodesToExpect, + expectedIds, }); - ViewportMock.validateChangesCalls(viewport, [], [{ subCategoryId: keys.subCategory.id, isVisible: false }]); + viewport.validateChangesCalls([], [{ subCategoryId: keys.subCategory.id, isVisible: false }]); }); it("hiding subCategory makes it hidden and doesn't affect non related definition containers", async function () { @@ -1407,24 +1315,15 @@ describe("CategoriesVisibilityHandler", () => { return { category, subCategory, definitionContainerRoot, categoryOfDefinitionContainer, subCategoryOfDefinitionContainer }; }); - const nodesToExpect = [ + const expectedIds = [ keys.category.id, keys.subCategory.id, keys.definitionContainerRoot.id, keys.categoryOfDefinitionContainer.id, keys.subCategoryOfDefinitionContainer.id, ]; - using visibilityTestData = await createVisibilityTestData({ imodel }); + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; - await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), true); - await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerRoot.id), true); - await validateHierarchyVisibility({ - provider, - handler, - viewport, - visibilityExpectations: VisibilityExpectations.all("visible"), - nodesToExpect, - }); await handler.changeVisibility(createSubCategoryHierarchyNode(keys.subCategory.id, keys.category.id), false); await validateHierarchyVisibility({ @@ -1436,9 +1335,9 @@ describe("CategoriesVisibilityHandler", () => { subCategory: (_, subCategoryId) => (subCategoryId === keys.subCategory.id ? "hidden" : "visible"), definitionContainer: () => "visible", }, - nodesToExpect, + expectedIds, }); - ViewportMock.validateChangesCalls(viewport, [], [{ subCategoryId: keys.subCategory.id, isVisible: false }]); + viewport.validateChangesCalls([], [{ subCategoryId: keys.subCategory.id, isVisible: false }]); }); }); }); diff --git a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/Utils.ts b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/Utils.ts index e50afe1b2..b8c034b1a 100644 --- a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/Utils.ts +++ b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/Utils.ts @@ -62,116 +62,110 @@ export function createDefinitionContainerHierarchyNode(definitionContainerId: Id }; } + +interface ViewportStubValidation { + /** + * Checks if `changeCategoryDisplay` and `changeSubCategoryDisplay` get called with appropriate params + * + * @param categories categories parameters that `changeCategoryDisplay` should be called with + * @param subCategories subcategories parameters that `changeSubCategoryDisplay` should be called with + */ + validateChangesCalls: ( + categories: { categoriesToChange: Id64Array; isVisible: boolean; enableAllSubCategories: boolean }[], + subCategories: { subCategoryId: Id64String; isVisible: boolean }[], + ) => void; +} + /** - * Class that has necessary utilities for testing CategoriesTree visibility. - * It allows stubbing `Viewport` and validating if specific functions get called with appropriate params. - * @internal + * Creates a stubbed `Viewport` with that has only necessary properties defined for determening CategoriesTree visibility. + * + * This stub allows changing and saving the display of categories and subcategories + * @returns stubbed `Viewport` */ -export class ViewportMock { - private _idsCache: CategoriesTreeIdsCache; - private _subCategories: Map; +export async function createViewportStub(idsCache: CategoriesTreeIdsCache, isVisibleOnInitialize = false): Promise { + const subCategoriesMap = new Map(); - private _categories: Map< + const categoriesMap = new Map< Id64String, { subCategories: Id64Array; isVisible: boolean; } - >; + >(); - constructor(idsCache: CategoriesTreeIdsCache) { - this._idsCache = idsCache; - this._categories = new Map(); - this._subCategories = new Map(); + const { categories: categoriesFromCache } = await idsCache.getAllDefinitionContainersAndCategories(); + for (const category of categoriesFromCache) { + const subCategoriesFromCache = await idsCache.getSubCategories(category); + subCategoriesFromCache.forEach((subCategoryId) => { + subCategoriesMap.set(subCategoryId, isVisibleOnInitialize); + }); + categoriesMap.set(category, { isVisible: isVisibleOnInitialize, subCategories: subCategoriesFromCache }); } - - /** - * Creates a stubbed `Viewport` with that has only necessary properties defined for determening CategoriesTree visibility. - * - * This stub allows changing and saving the display of categories and subcategories - * @returns stubbed `Viewport` - */ - public async createViewportStub(): Promise { - const { categories } = await this._idsCache.getAllDefinitionContainersAndCategories(); - for (const category of categories) { - const subCategories = await this._idsCache.getSubCategories(category); - subCategories.forEach((subCategoryId) => { - this._subCategories.set(subCategoryId, false); - }); - this._categories.set(category, { isVisible: false, subCategories }); + const changeCategoryDisplayStub = sinon.stub().callsFake((categoriesToChange: Id64Array, isVisible: boolean, enableAllSubCategories: boolean) => { + for (const category of categoriesToChange) { + const value = categoriesMap.get(category); + if (value) { + value.isVisible = isVisible; + if (enableAllSubCategories) { + for (const subCategory of value.subCategories) { + subCategoriesMap.set(subCategory, true); + } + } + } } + }); - return { - isSubCategoryVisible: sinon.stub().callsFake((subCategoryId: Id64String) => !!this._subCategories.get(subCategoryId)), - iModel: { - categories: { - getCategoryInfo: sinon.stub().callsFake(async (ids: Id64Array) => { - const subCategories = []; - for (const id of ids) { - const subCategoriesToUse = this._categories.get(id); - if (subCategoriesToUse !== undefined) { - subCategories.push(...subCategoriesToUse.subCategories); - } - } - return [ - { - subCategories: subCategories.map((subCategory) => { - return { - id: subCategory, - }; - }), - }, - ]; - }), - }, - }, - view: { - viewsCategory: sinon.stub().callsFake((categoryId: Id64String) => !!this._categories.get(categoryId)?.isVisible), - }, - changeSubCategoryDisplay: sinon.stub().callsFake((subCategoryId: Id64String, isVisible: boolean) => { - this._subCategories.set(subCategoryId, isVisible); - }), - changeCategoryDisplay: sinon.stub().callsFake((categoriesToChange: Id64Array, isVisible: boolean, enableAllSubCategories: boolean) => { - for (const category of categoriesToChange) { - const value = this._categories.get(category); - if (value) { - value.isVisible = isVisible; - if (enableAllSubCategories) { - for (const subCategory of value.subCategories) { - this._subCategories.set(subCategory, true); - } + const changeSubCategoryDisplayStub = sinon.stub().callsFake((subCategoryId: Id64String, isVisible: boolean) => { + subCategoriesMap.set(subCategoryId, isVisible); + }); + + return { + isSubCategoryVisible: sinon.stub().callsFake((subCategoryId: Id64String) => !!subCategoriesMap.get(subCategoryId)), + iModel: { + categories: { + getCategoryInfo: sinon.stub().callsFake(async (ids: Id64Array) => { + const subCategories = []; + for (const id of ids) { + const subCategoriesToUse = categoriesMap.get(id); + if (subCategoriesToUse !== undefined) { + subCategories.push(...subCategoriesToUse.subCategories); } } - } - }), - perModelCategoryVisibility: { - getOverride: sinon.fake.returns(PerModelCategoryVisibility.Override.None), - setOverride: sinon.fake(), - clearOverrides: sinon.fake(), - *[Symbol.iterator]() {}, + return [ + { + subCategories: subCategories.map((subCategory) => { + return { + id: subCategory, + }; + }), + }, + ]; + }), }, - onDisplayStyleChanged: new BeEvent<() => void>(), - onViewedCategoriesChanged: new BeEvent<() => void>(), - } as unknown as Viewport; - } - - /** - * Checks if `changeCategoryDisplay` and `changeSubCategoryDisplay` get called with appropriate params - * - * @param stubbedViewport viewport created using `ViewportMock.createViewPortStub()` - * @param categories categories parameters that `stubbedViewport.changeCategoryDisplay` should be called with - * @param subCategories subcategories parameters that `stubbedViewport.changeSubCategoryDisplay` should be called with - */ - public static validateChangesCalls( - stubbedViewport: Viewport, - categories: { categoriesToChange: Id64Array; isVisible: boolean; enableAllSubCategories: boolean }[], - subCategories: { subCategoryId: Id64String; isVisible: boolean }[], - ) { - for (const category of categories) { - expect(stubbedViewport.changeCategoryDisplay).to.be.calledWith(category.categoriesToChange, category.isVisible, category.enableAllSubCategories); - } - for (const subCategory of subCategories) { - expect(stubbedViewport.changeSubCategoryDisplay).to.be.calledWith(subCategory.subCategoryId, subCategory.isVisible); - } - } + }, + view: { + viewsCategory: sinon.stub().callsFake((categoryId: Id64String) => !!categoriesMap.get(categoryId)?.isVisible), + }, + changeSubCategoryDisplay: changeSubCategoryDisplayStub, + changeCategoryDisplay: changeCategoryDisplayStub, + perModelCategoryVisibility: { + getOverride: sinon.fake.returns(PerModelCategoryVisibility.Override.None), + setOverride: sinon.fake(), + clearOverrides: sinon.fake(), + *[Symbol.iterator]() {}, + }, + onDisplayStyleChanged: new BeEvent<() => void>(), + onViewedCategoriesChanged: new BeEvent<() => void>(), + validateChangesCalls( + categoriesToValidate: { categoriesToChange: Id64Array; isVisible: boolean; enableAllSubCategories: boolean }[], + subCategories: { subCategoryId: Id64String; isVisible: boolean }[], + ) { + for (const category of categoriesToValidate) { + expect(changeCategoryDisplayStub).to.be.calledWith(category.categoriesToChange, category.isVisible, category.enableAllSubCategories); + } + for (const subCategory of subCategories) { + expect(changeSubCategoryDisplayStub).to.be.calledWith(subCategory.subCategoryId, subCategory.isVisible); + } + }, + } as unknown as Viewport & ViewportStubValidation; } diff --git a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/VisibilityValidation.ts b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/VisibilityValidation.ts index c41b7ed75..b48f9ef1c 100644 --- a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/VisibilityValidation.ts +++ b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/VisibilityValidation.ts @@ -35,7 +35,7 @@ export interface ValidateNodeProps { handler: HierarchyVisibilityHandler; viewport: Viewport; visibilityExpectations: VisibilityExpectations; - nodesToExpect: Id64Array; + expectedIds: Id64Array; } export async function validateNodeVisibility({ node, handler, visibilityExpectations }: ValidateNodeProps & { node: HierarchyNode }) { @@ -89,5 +89,5 @@ export async function validateHierarchyVisibility({ mergeMap(async (node) => validateNodeVisibility({ ...props, node })), ), ); - expect(props.nodesToExpect.every((nodeId) => nodesFound.includes(nodeId))).to.be.true; + expect(props.expectedIds.every((nodeId) => nodesFound.includes(nodeId))).to.be.true; } From afc99b1ec96e0849ead97628c7032687d36b61eb Mon Sep 17 00:00:00 2001 From: Jonas <100586436+JonasDov@users.noreply.github.com> Date: Tue, 4 Feb 2025 12:04:47 +0200 Subject: [PATCH 29/38] Adjust visibilityExpectations API --- .../CategoriesVisibilityHandler.test.ts | 208 ++++++++++-------- .../internal/VisibilityValidation.ts | 36 ++- 2 files changed, 129 insertions(+), 115 deletions(-) diff --git a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts index d110b8325..f501e3448 100644 --- a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts +++ b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts @@ -26,7 +26,7 @@ import { import { TestUtils } from "../../../TestUtils.js"; import { createIModelAccess } from "../../Common.js"; import { createCategoryHierarchyNode, createDefinitionContainerHierarchyNode, createSubCategoryHierarchyNode, createViewportStub } from "./Utils.js"; -import { validateHierarchyVisibility, VisibilityExpectations } from "./VisibilityValidation.js"; +import { validateHierarchyVisibility } from "./VisibilityValidation.js"; import type { IModelConnection } from "@itwin/core-frontend"; import type { HierarchyNodeIdentifiersPath } from "@itwin/presentation-hierarchies"; @@ -115,8 +115,9 @@ describe("CategoriesVisibilityHandler", () => { provider, handler, viewport, - visibilityExpectations: VisibilityExpectations.all("hidden"), + visibilityExpectations: {}, expectedIds, + all: "hidden", }); }); describe("definitionContainers", () => { @@ -156,8 +157,9 @@ describe("CategoriesVisibilityHandler", () => { provider, handler, viewport, - visibilityExpectations: VisibilityExpectations.all("visible"), + visibilityExpectations: {}, expectedIds, + all: "visible", }); viewport.validateChangesCalls( [{ categoriesToChange: [keys.directCategory.id, keys.indirectCategory.id], isVisible: true, enableAllSubCategories: true }], @@ -215,9 +217,13 @@ describe("CategoriesVisibilityHandler", () => { handler, viewport, visibilityExpectations: { - category: (categoryId) => (categoryId === keys.category2.id ? "hidden" : "visible"), - subCategory: (parentCategoryId) => (parentCategoryId === keys.category2.id ? "hidden" : "visible"), - definitionContainer: (definitionContainerId) => (definitionContainerId === keys.definitionContainerRoot2.id ? "hidden" : "visible"), + [keys.definitionContainerRoot2.id]: "hidden", + [keys.definitionContainerRoot.id]: "visible", + [keys.definitionContainerChild.id]: "visible", + [keys.category2.id]: "hidden", + [keys.indirectCategory.id]: "visible", + [keys.subCategory2.id]: "hidden", + [keys.indirectSubCategory.id]: "visible", }, expectedIds, }); @@ -250,8 +256,10 @@ describe("CategoriesVisibilityHandler", () => { handler, viewport, visibilityExpectations: { - category: (categoryId) => (categoryId === keys.directCategory.id ? "hidden" : "visible"), - definitionContainer: (definitionContainerId) => (definitionContainerId === keys.definitionContainerRoot.id ? "partial" : "visible"), + [keys.definitionContainerRoot.id]: "partial", + [keys.definitionContainerChild.id]: "visible", + [keys.directCategory.id]: "hidden", + [keys.indirectCategory.id]: "visible", }, expectedIds, }); @@ -292,16 +300,11 @@ describe("CategoriesVisibilityHandler", () => { handler, viewport, visibilityExpectations: { - category: (categoryId) => (categoryId === keys.indirectCategory2.id ? "hidden" : "visible"), - definitionContainer: (definitionContainerId) => { - if (definitionContainerId === keys.definitionContainerRoot.id) { - return "partial"; - } - if (definitionContainerId === keys.definitionContainerChild2.id) { - return "hidden"; - } - return "visible"; - }, + [keys.definitionContainerRoot.id]: "partial", + [keys.definitionContainerChild.id]: "visible", + [keys.definitionContainerChild2.id]: "hidden", + [keys.indirectCategory2.id]: "hidden", + [keys.indirectCategory.id]: "visible", }, expectedIds, }); @@ -336,8 +339,9 @@ describe("CategoriesVisibilityHandler", () => { provider, handler, viewport, - visibilityExpectations: VisibilityExpectations.all("visible"), + visibilityExpectations: {}, expectedIds, + all: "visible", }); viewport.validateChangesCalls([{ categoriesToChange: [keys.indirectCategory.id], isVisible: true, enableAllSubCategories: true }], []); }); @@ -366,8 +370,9 @@ describe("CategoriesVisibilityHandler", () => { provider, handler, viewport, - visibilityExpectations: VisibilityExpectations.all("visible"), + visibilityExpectations: {}, expectedIds, + all: "visible", }); viewport.validateChangesCalls([{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: true }], []); }); @@ -402,8 +407,10 @@ describe("CategoriesVisibilityHandler", () => { handler, viewport, visibilityExpectations: { - category: (categoryId) => (categoryId === keys.category2.id ? "hidden" : "visible"), - subCategory: (parentCategoryId) => (parentCategoryId === keys.category2.id ? "hidden" : "visible"), + [keys.category2.id]: "hidden", + [keys.category.id]: "visible", + [keys.subCategory2.id]: "hidden", + [keys.subCategory.id]: "visible", }, expectedIds, }); @@ -444,9 +451,11 @@ describe("CategoriesVisibilityHandler", () => { handler, viewport, visibilityExpectations: { - category: (categoryId) => (categoryId === keys.category2.id ? "hidden" : "visible"), - subCategory: (parentCategoryId) => (parentCategoryId === keys.category2.id ? "hidden" : "visible"), - definitionContainer: () => "hidden", + [keys.definitionContainer.id]: "hidden", + [keys.category2.id]: "hidden", + [keys.category.id]: "visible", + [keys.subCategory2.id]: "hidden", + [keys.subCategory.id]: "visible", }, expectedIds, }); @@ -488,9 +497,11 @@ describe("CategoriesVisibilityHandler", () => { handler, viewport, visibilityExpectations: { - category: (categoryId) => (categoryId === keys.category2.id ? "hidden" : "visible"), - subCategory: (parentCategoryId) => (parentCategoryId === keys.category2.id ? "hidden" : "visible"), - definitionContainer: () => "partial", + [keys.definitionContainerRoot.id]: "partial", + [keys.category2.id]: "hidden", + [keys.category.id]: "visible", + [keys.subCategory2.id]: "hidden", + [keys.subCategory.id]: "visible", }, expectedIds, }); @@ -535,14 +546,11 @@ describe("CategoriesVisibilityHandler", () => { handler, viewport, visibilityExpectations: { - category: (categoryId) => (categoryId === keys.indirectCategory.id ? "hidden" : "visible"), - subCategory: () => "visible", - definitionContainer: (definitionContainerId) => { - if (definitionContainerId === keys.definitionContainerRoot.id) { - return "partial"; - } - return "hidden"; - }, + [keys.definitionContainerRoot.id]: "partial", + [keys.definitionContainerChild.id]: "hidden", + [keys.indirectCategory.id]: "hidden", + [keys.category.id]: "visible", + [keys.subCategory.id]: "visible", }, expectedIds, }); @@ -581,8 +589,9 @@ describe("CategoriesVisibilityHandler", () => { handler, viewport, visibilityExpectations: { - category: () => "partial", - subCategory: (_, subCategoryId) => (subCategoryId === keys.subCategory.id ? "visible" : "hidden"), + [keys.category.id]: "partial", + [keys.subCategory.id]: "visible", + [keys.subCategory2.id]: "hidden", }, expectedIds, }); @@ -617,8 +626,9 @@ describe("CategoriesVisibilityHandler", () => { handler, viewport, visibilityExpectations: { - category: (categoryId) => (categoryId === keys.category.id ? "partial" : "hidden"), - subCategory: (_, subCategoryId) => (subCategoryId === keys.subCategory.id ? "visible" : "hidden"), + [keys.category2.id]: "hidden", + [keys.category.id]: "partial", + [keys.subCategory.id]: "visible", }, expectedIds, }); @@ -654,9 +664,9 @@ describe("CategoriesVisibilityHandler", () => { handler, viewport, visibilityExpectations: { - category: () => "partial", - subCategory: (_, subCategoryId) => (subCategoryId === keys.subCategory.id ? "visible" : "hidden"), - definitionContainer: () => "partial", + [keys.definitionContainerRoot.id]: "partial", + [keys.category.id]: "partial", + [keys.subCategory.id]: "visible", }, expectedIds, }); @@ -705,9 +715,11 @@ describe("CategoriesVisibilityHandler", () => { handler, viewport, visibilityExpectations: { - category: (categoryId) => (categoryId === keys.category.id ? "partial" : "hidden"), - subCategory: (_, subCategoryId) => (subCategoryId === keys.subCategory.id ? "visible" : "hidden"), - definitionContainer: () => "hidden", + [keys.definitionContainerRoot.id]: "hidden", + [keys.categoryOfDefinitionContainer.id]: "hidden", + [keys.subCategoryOfDefinitionContainer.id]: "hidden", + [keys.category.id]: "partial", + [keys.subCategory.id]: "visible", }, expectedIds, }); @@ -740,8 +752,9 @@ describe("CategoriesVisibilityHandler", () => { provider, handler, viewport, - visibilityExpectations: VisibilityExpectations.all("visible"), + visibilityExpectations: {}, expectedIds, + all: "visible", }); }); describe("definitionContainers", () => { @@ -781,8 +794,9 @@ describe("CategoriesVisibilityHandler", () => { provider, handler, viewport, - visibilityExpectations: VisibilityExpectations.all("hidden"), + visibilityExpectations: {}, expectedIds, + all: "hidden", }); viewport.validateChangesCalls( [{ categoriesToChange: [keys.directCategory.id, keys.indirectCategory.id], isVisible: false, enableAllSubCategories: false }], @@ -840,9 +854,13 @@ describe("CategoriesVisibilityHandler", () => { handler, viewport, visibilityExpectations: { - category: (categoryId) => (categoryId === keys.indirectCategory.id ? "hidden" : "visible"), - subCategory: (parentCategoryId) => (parentCategoryId === keys.indirectCategory.id ? "hidden" : "visible"), - definitionContainer: (definitionContainerId) => (definitionContainerId !== keys.definitionContainerRoot2.id ? "hidden" : "visible"), + [keys.definitionContainerRoot2.id]: "visible", + [keys.definitionContainerRoot.id]: "hidden", + [keys.definitionContainerChild.id]: "hidden", + [keys.indirectCategory.id]: "hidden", + [keys.category2.id]: "visible", + [keys.indirectSubCategory.id]: "hidden", + [keys.subCategory2.id]: "visible", }, expectedIds, }); @@ -876,8 +894,10 @@ describe("CategoriesVisibilityHandler", () => { handler, viewport, visibilityExpectations: { - category: (categoryId) => (categoryId === keys.indirectCategory.id ? "hidden" : "visible"), - definitionContainer: (definitionContainerId) => (definitionContainerId === keys.definitionContainerRoot.id ? "partial" : "hidden"), + [keys.definitionContainerRoot.id]: "partial", + [keys.definitionContainerChild.id]: "hidden", + [keys.indirectCategory.id]: "hidden", + [keys.directCategory.id]: "visible", }, expectedIds, }); @@ -919,16 +939,11 @@ describe("CategoriesVisibilityHandler", () => { handler, viewport, visibilityExpectations: { - category: (categoryId) => (categoryId === keys.indirectCategory.id ? "hidden" : "visible"), - definitionContainer: (definitionContainerId) => { - if (definitionContainerId === keys.definitionContainerRoot.id) { - return "partial"; - } - if (definitionContainerId === keys.definitionContainerChild.id) { - return "hidden"; - } - return "visible"; - }, + [keys.definitionContainerRoot.id]: "partial", + [keys.definitionContainerChild.id]: "hidden", + [keys.definitionContainerChild2.id]: "visible", + [keys.indirectCategory.id]: "hidden", + [keys.indirectCategory2.id]: "visible", }, expectedIds, }); @@ -964,8 +979,9 @@ describe("CategoriesVisibilityHandler", () => { provider, handler, viewport, - visibilityExpectations: VisibilityExpectations.all("hidden"), + visibilityExpectations: {}, expectedIds, + all: "hidden", }); viewport.validateChangesCalls([{ categoriesToChange: [keys.indirectCategory.id], isVisible: false, enableAllSubCategories: false }], []); }); @@ -995,8 +1011,9 @@ describe("CategoriesVisibilityHandler", () => { provider, handler, viewport, - visibilityExpectations: VisibilityExpectations.all("hidden"), + visibilityExpectations: {}, expectedIds, + all: "hidden", }); viewport.validateChangesCalls([{ categoriesToChange: [keys.category.id], isVisible: false, enableAllSubCategories: false }], []); }); @@ -1032,8 +1049,10 @@ describe("CategoriesVisibilityHandler", () => { handler, viewport, visibilityExpectations: { - category: (categoryId) => (categoryId === keys.category.id ? "hidden" : "visible"), - subCategory: (parentCategoryId) => (parentCategoryId === keys.category.id ? "hidden" : "visible"), + [keys.category.id]: "hidden", + [keys.category2.id]: "visible", + [keys.subCategory2.id]: "visible", + [keys.subCategory.id]: "hidden", }, expectedIds, }); @@ -1075,9 +1094,11 @@ describe("CategoriesVisibilityHandler", () => { handler, viewport, visibilityExpectations: { - category: (categoryId) => (categoryId === keys.category.id ? "hidden" : "visible"), - subCategory: (parentCategoryId) => (parentCategoryId === keys.category.id ? "hidden" : "visible"), - definitionContainer: () => "visible", + [keys.definitionContainer.id]: "visible", + [keys.category2.id]: "visible", + [keys.category.id]: "hidden", + [keys.subCategory2.id]: "visible", + [keys.subCategory.id]: "hidden", }, expectedIds, }); @@ -1120,9 +1141,11 @@ describe("CategoriesVisibilityHandler", () => { handler, viewport, visibilityExpectations: { - category: (categoryId) => (categoryId === keys.category.id ? "hidden" : "visible"), - subCategory: (parentCategoryId) => (parentCategoryId === keys.category.id ? "hidden" : "visible"), - definitionContainer: () => "partial", + [keys.definitionContainerRoot.id]: "partial", + [keys.category.id]: "hidden", + [keys.category2.id]: "visible", + [keys.subCategory.id]: "hidden", + [keys.subCategory2.id]: "visible", }, expectedIds, }); @@ -1168,14 +1191,11 @@ describe("CategoriesVisibilityHandler", () => { handler, viewport, visibilityExpectations: { - category: (categoryId) => (categoryId === keys.category.id ? "hidden" : "visible"), - subCategory: () => "hidden", - definitionContainer: (definitionContainerId) => { - if (definitionContainerId === keys.definitionContainerRoot.id) { - return "partial"; - } - return "visible"; - }, + [keys.definitionContainerRoot.id]: "partial", + [keys.definitionContainerChild.id]: "visible", + [keys.category.id]: "hidden", + [keys.indirectCategory.id]: "visible", + [keys.subCategory.id]: "hidden", }, expectedIds, }); @@ -1213,15 +1233,16 @@ describe("CategoriesVisibilityHandler", () => { handler, viewport, visibilityExpectations: { - category: () => "partial", - subCategory: (_, subCategoryId) => (subCategoryId === keys.subCategory.id ? "hidden" : "visible"), + [keys.category.id]: "partial", + [keys.subCategory.id]: "hidden", + [keys.subCategory2.id]: "visible", }, expectedIds, }); viewport.validateChangesCalls([], [{ subCategoryId: keys.subCategory.id, isVisible: false }]); }); - it("showing subCategory makes it visible and its parent category partially visible, and doesn't affect other categories", async function () { + it("hiding subCategory makes it hidden and its parent category partially visible, and doesn't affect other categories", async function () { const { imodel, ...keys } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); @@ -1247,8 +1268,9 @@ describe("CategoriesVisibilityHandler", () => { handler, viewport, visibilityExpectations: { - category: (categoryId) => (categoryId === keys.category.id ? "partial" : "visible"), - subCategory: (_, subCategoryId) => (subCategoryId === keys.subCategory.id ? "hidden" : "visible"), + [keys.category.id]: "partial", + [keys.category2.id]: "visible", + [keys.subCategory.id]: "hidden", }, expectedIds, }); @@ -1282,9 +1304,9 @@ describe("CategoriesVisibilityHandler", () => { handler, viewport, visibilityExpectations: { - category: () => "partial", - subCategory: (_, subCategoryId) => (subCategoryId === keys.subCategory.id ? "hidden" : "visible"), - definitionContainer: () => "partial", + [keys.definitionContainerRoot.id]: "partial", + [keys.category.id]: "partial", + [keys.subCategory.id]: "hidden", }, expectedIds, }); @@ -1331,9 +1353,11 @@ describe("CategoriesVisibilityHandler", () => { handler, viewport, visibilityExpectations: { - category: (categoryId) => (categoryId === keys.category.id ? "partial" : "visible"), - subCategory: (_, subCategoryId) => (subCategoryId === keys.subCategory.id ? "hidden" : "visible"), - definitionContainer: () => "visible", + [keys.definitionContainerRoot.id]: "visible", + [keys.categoryOfDefinitionContainer.id]: "visible", + [keys.subCategoryOfDefinitionContainer.id]: "visible", + [keys.category.id]: "partial", + [keys.subCategory.id]: "hidden", }, expectedIds, }); diff --git a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/VisibilityValidation.ts b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/VisibilityValidation.ts index b48f9ef1c..3b3fe3dff 100644 --- a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/VisibilityValidation.ts +++ b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/VisibilityValidation.ts @@ -16,19 +16,7 @@ import type { Visibility } from "../../../../tree-widget-react/components/trees/ import type { HierarchyVisibilityHandler } from "../../../../tree-widget-react/components/trees/common/UseHierarchyVisibility.js"; interface VisibilityExpectations { - subCategory?(parentCategoryId: Id64String, subCategoryId?: Id64String): Omit; - definitionContainer?(id: Id64String): Visibility; - category(id: Id64String): Visibility; -} - -export namespace VisibilityExpectations { - export function all(visibility: "visible" | "hidden"): VisibilityExpectations { - return { - subCategory: () => visibility, - category: () => visibility, - definitionContainer: () => visibility, - }; - } + [id: string]: Visibility; } export interface ValidateNodeProps { @@ -36,33 +24,35 @@ export interface ValidateNodeProps { viewport: Viewport; visibilityExpectations: VisibilityExpectations; expectedIds: Id64Array; + all?: "visible" | "hidden"; } -export async function validateNodeVisibility({ node, handler, visibilityExpectations }: ValidateNodeProps & { node: HierarchyNode }) { +export async function validateNodeVisibility({ node, handler, visibilityExpectations, all }: ValidateNodeProps & { node: HierarchyNode }) { const actualVisibility = await handler.getVisibilityStatus(node); if (!HierarchyNode.isInstancesNode(node)) { throw new Error(`Expected hierarchy to only have instance nodes, got ${JSON.stringify(node)}`); } + if (all !== undefined) { + expect(actualVisibility.state).to.eq(all); + return; + } + const { id } = node.key.instanceKeys[0]; if (CategoriesTreeNode.isCategoryNode(node)) { - expect(actualVisibility.state).to.eq(visibilityExpectations.category(id)); + expect(actualVisibility.state).to.eq(visibilityExpectations[id]); return; } if (CategoriesTreeNode.isSubCategoryNode(node)) { - const parentCategoryId = node.extendedData?.categoryId; - if (visibilityExpectations.subCategory === undefined) { - throw new Error(`Expected hierarchy to not have subCategory nodes, got ${JSON.stringify(node)}`); + // One subCategory gets added when category is inserted + if (visibilityExpectations[id] !== undefined) { + expect(actualVisibility.state).to.eq(visibilityExpectations[id]); } - expect(actualVisibility.state).to.eq(visibilityExpectations.subCategory(parentCategoryId, id)); return; } if (CategoriesTreeNode.isDefinitionContainerNode(node)) { - if (visibilityExpectations.definitionContainer === undefined) { - throw new Error(`Expected hierarchy to not have definitionContainer nodes, got ${JSON.stringify(node)}`); - } - expect(actualVisibility.state).to.eq(visibilityExpectations.definitionContainer(id)); + expect(actualVisibility.state).to.eq(visibilityExpectations[id]); return; } From c21bd26f812b57404261efde409811c033a885c9 Mon Sep 17 00:00:00 2001 From: Jonas <100586436+JonasDov@users.noreply.github.com> Date: Tue, 4 Feb 2025 12:49:05 +0200 Subject: [PATCH 30/38] Update queryCategories implementation --- .../internal/CategoriesTreeIdsCache.ts | 37 ++++--------------- 1 file changed, 7 insertions(+), 30 deletions(-) diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts index 3f7aefb02..84825420e 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts @@ -53,14 +53,17 @@ export class CategoriesTreeIdsCache { childCount: number; }> { const CATEGORIES_CTE = "AllVisibleCategories"; - const CATEGORIES_WITH_CHILD_COUNT_CTE = "CategoriesWithChildCount"; const ctes = [ - `${CATEGORIES_WITH_CHILD_COUNT_CTE}(ECInstanceId, ChildCount, ModelId) AS ( + `${CATEGORIES_CTE}(ECInstanceId, ChildCount, ModelId, ParentDefinitionContainerExists) AS ( SELECT this.ECInstanceId, COUNT(sc.ECInstanceId), - this.Model.Id + this.Model.Id, + IIF(this.Model.Id IN (SELECT dc.ECInstanceId FROM ${DEFINITION_CONTAINER_CLASS} dc), + true, + false + ) FROM ${this._categoryClass} this JOIN ${SUB_CATEGORY_CLASS} sc ON sc.Parent.Id = this.ECInstanceId @@ -71,41 +74,15 @@ export class CategoriesTreeIdsCache { AND EXISTS (SELECT 1 FROM ${this._categoryElementClass} e WHERE e.Category.Id = this.ECInstanceId) GROUP BY this.ECInstanceId )`, - `${CATEGORIES_CTE}(ECInstanceId, CategoryModelId, CurrentModelId, ParentDefinitionContainerExists, ChildCount) AS ( - SELECT - this.ECInstanceId, - this.ModelId, - this.ModelId, - false, - this.ChildCount - FROM - ${CATEGORIES_WITH_CHILD_COUNT_CTE} this - - UNION ALL - - SELECT - ce.ECInstanceId, - ce.CategoryModelId, - pe.Model.Id, - true, - ce.ChildCount - FROM - ${CATEGORIES_CTE} ce - JOIN ${DEFINITION_CONTAINER_CLASS} pe ON ce.CurrentModelId = pe.ECInstanceId - WHERE - NOT pe.IsPrivate - )`, ]; const categoriesQuery = ` SELECT this.ECInstanceId id, - this.CategoryModelId modelId, + this.ModelId modelId, this.ParentDefinitionContainerExists parentDefinitionContainerExists, this.ChildCount childCount FROM ${CATEGORIES_CTE} this - WHERE - this.CurrentModelId NOT IN (SELECT dm.ECInstanceId FROM ${DEFINITION_CONTAINER_CLASS} dm) `; for await (const row of this._queryExecutor.createQueryReader({ ctes, ecsql: categoriesQuery }, { rowFormat: "ECSqlPropertyNames", limit: "unbounded" })) { yield { id: row.id, modelId: row.modelId, parentDefinitionContainerExists: row.parentDefinitionContainerExists, childCount: row.childCount }; From 05cdb52b80e0105a0de1dfd49cd3ea7a10b72023 Mon Sep 17 00:00:00 2001 From: Jonas <100586436+JonasDov@users.noreply.github.com> Date: Tue, 4 Feb 2025 15:27:43 +0200 Subject: [PATCH 31/38] Fix e2e tests --- .../CategoriesTreeDefinition.ts | 16 ++--- .../internal/CategoriesTreeIdsCache.ts | 58 ++++++++++++++----- 2 files changed, 53 insertions(+), 21 deletions(-) diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts index dba0a5da1..8735e46e0 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts @@ -96,14 +96,14 @@ export class CategoriesTreeDefinition implements HierarchyDefinition { const { categoryClass } = getClassesByView(viewType); - const categoriesInstanceFilterClauses = await this._selectQueryFactory.createFilterClauses({ - filter: instanceFilter, - contentClass: { fullName: categoryClass, alias: "this" }, - }); - const definitionContainersInstanceFilterClauses = await this._selectQueryFactory.createFilterClauses({ - filter: instanceFilter, - contentClass: { fullName: DEFINITION_CONTAINER_CLASS, alias: "this" }, - }); + const [categoriesInstanceFilterClauses, definitionContainersInstanceFilterClauses] = await Promise.all( + [categoryClass, DEFINITION_CONTAINER_CLASS].map(async (className) => + this._selectQueryFactory.createFilterClauses({ + filter: instanceFilter, + contentClass: { fullName: className, alias: "this" }, + }), + ), + ); const definitionContainersQuery = definitionContainers.length > 0 diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts index 84825420e..62aba26df 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts @@ -36,6 +36,7 @@ export class CategoriesTreeIdsCache { private _subCategoriesInfo: Promise> | undefined; private _categoryClass: string; private _categoryElementClass: string; + private _isDefinitionContainerSupported: Promise | undefined; constructor( private _queryExecutor: LimitingECSqlQueryExecutor, @@ -53,17 +54,22 @@ export class CategoriesTreeIdsCache { childCount: number; }> { const CATEGORIES_CTE = "AllVisibleCategories"; - + const isDefinitionContainerSupported = await this.getIsDefinitionContainerSupported(); const ctes = [ - `${CATEGORIES_CTE}(ECInstanceId, ChildCount, ModelId, ParentDefinitionContainerExists) AS ( + `${CATEGORIES_CTE}(ECInstanceId, ChildCount, ModelId ${isDefinitionContainerSupported ? ", ParentDefinitionContainerExists" : ""}) AS ( SELECT this.ECInstanceId, COUNT(sc.ECInstanceId), - this.Model.Id, - IIF(this.Model.Id IN (SELECT dc.ECInstanceId FROM ${DEFINITION_CONTAINER_CLASS} dc), - true, - false - ) + this.Model.Id + ${ + isDefinitionContainerSupported + ? `, + IIF(this.Model.Id IN (SELECT dc.ECInstanceId FROM ${DEFINITION_CONTAINER_CLASS} dc), + true, + false + )` + : "" + } FROM ${this._categoryClass} this JOIN ${SUB_CATEGORY_CLASS} sc ON sc.Parent.Id = this.ECInstanceId @@ -79,7 +85,7 @@ export class CategoriesTreeIdsCache { SELECT this.ECInstanceId id, this.ModelId modelId, - this.ParentDefinitionContainerExists parentDefinitionContainerExists, + ${isDefinitionContainerSupported ? "this.ParentDefinitionContainerExists" : "false"} parentDefinitionContainerExists, this.ChildCount childCount FROM ${CATEGORIES_CTE} this @@ -89,6 +95,24 @@ export class CategoriesTreeIdsCache { } } + private async queryIsDefinitionContainersSupported(): Promise { + const query = ` + SELECT + 1 + FROM + ECDbMeta.ECSchemaDef s + JOIN ECDbMeta.ECClassDef c ON c.Schema.Id = s.ECInstanceId + WHERE + s.Name = 'BisCore' + AND c.Name = 'DefinitionContainer' + `; + + for await (const _row of this._queryExecutor.createQueryReader({ ecsql: query })) { + return true; + } + return false; + } + private async *queryDefinitionContainers(categoriesModelIds: Id64Array): AsyncIterableIterator<{ id: Id64String; modelId: Id64String }> { // DefinitionModel ECInstanceId will always be the same as modeled DefinitionContainer ECInstanceId, if this wasn't the case, we would need to do something like: // JOIN BisCore.DefinitionModel dm ON dm.ECInstanceId = ${modelIdAccessor} @@ -185,8 +209,14 @@ export class CategoriesTreeIdsCache { private async getDefinitionContainersInfo() { this._definitionContainersInfo ??= (async () => { - const modelsCategoriesInfo = await this.getModelsCategoriesInfo(); const definitionContainersInfo = new Map(); + const isDefinitionContainerSupported = await this.getIsDefinitionContainerSupported(); + + if (!isDefinitionContainerSupported) { + return definitionContainersInfo; + } + + const modelsCategoriesInfo = await this.getModelsCategoriesInfo(); if (modelsCategoriesInfo.size === 0) { return definitionContainersInfo; } @@ -298,10 +328,7 @@ export class CategoriesTreeIdsCache { } public async getAllDefinitionContainersAndCategories(): Promise<{ categories: Id64Array; definitionContainers: Id64Array }> { - const [modelsCategoriesInfo, definitionContainersInfo] = await Promise.all([ - this.getModelsCategoriesInfo(), - this.getDefinitionContainersInfo(), - ]); + const [modelsCategoriesInfo, definitionContainersInfo] = await Promise.all([this.getModelsCategoriesInfo(), this.getDefinitionContainersInfo()]); const result = { definitionContainers: [...definitionContainersInfo.keys()], categories: new Array() }; for (const modelCategoriesInfo of modelsCategoriesInfo.values()) { result.categories.push(...modelCategoriesInfo.childCategories.map((childCategory) => childCategory.id)); @@ -337,6 +364,11 @@ export class CategoriesTreeIdsCache { } return result; } + + private async getIsDefinitionContainerSupported(): Promise { + this._isDefinitionContainerSupported ??= this.queryIsDefinitionContainersSupported(); + return this._isDefinitionContainerSupported; + } } /** @internal */ From e3001ce0c8c3cccc482fa411af0761cbbe951f46 Mon Sep 17 00:00:00 2001 From: Jonas <100586436+JonasDov@users.noreply.github.com> Date: Tue, 4 Feb 2025 15:40:08 +0200 Subject: [PATCH 32/38] Add changeset --- ...-widget-react-944faa0b-268a-40c9-9ae5-49d45b91b7c7.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@itwin-tree-widget-react-944faa0b-268a-40c9-9ae5-49d45b91b7c7.json diff --git a/change/@itwin-tree-widget-react-944faa0b-268a-40c9-9ae5-49d45b91b7c7.json b/change/@itwin-tree-widget-react-944faa0b-268a-40c9-9ae5-49d45b91b7c7.json new file mode 100644 index 000000000..a4b34e80f --- /dev/null +++ b/change/@itwin-tree-widget-react-944faa0b-268a-40c9-9ae5-49d45b91b7c7.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "`CategoriesTree` component rendered `Categories` as a flat list, where each `Category` had zero or more child `SubCategories`. Some iTwin.js applications started to group `Categories` under `DefinitionContainers` and wanted to see them displayed in `CategoriesTree` component. Added `DefinitionContainers` to `CategoriesTree` component. This change doesn't affect applications that don't have `DefinitionContainers`.", + "packageName": "@itwin/tree-widget-react", + "email": "100586436+JonasDov@users.noreply.github.com", + "dependentChangeType": "patch" +} From 971d6c73f4c19b713d85d5c3c25f17d8fe090ab2 Mon Sep 17 00:00:00 2001 From: Jonas <100586436+JonasDov@users.noreply.github.com> Date: Tue, 4 Feb 2025 16:23:54 +0200 Subject: [PATCH 33/38] Use function for DefinitionContainers in parentInstancesNodePredicate --- .../trees/categories-tree/CategoriesTreeDefinition.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts index 8735e46e0..b7fa22109 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts @@ -58,7 +58,7 @@ export class CategoriesTreeDefinition implements HierarchyDefinition { definitions: async (requestProps: DefineInstanceNodeChildHierarchyLevelProps) => this.createSubcategoryQuery(requestProps), }, { - parentInstancesNodePredicate: DEFINITION_CONTAINER_CLASS, + parentInstancesNodePredicate: async (instanceKey) => instanceKey.instanceKeys[0].className === DEFINITION_CONTAINER_CLASS, definitions: async (requestProps: DefineInstanceNodeChildHierarchyLevelProps) => this.createDefinitionContainersAndCategoriesQuery({ ...requestProps, @@ -97,7 +97,7 @@ export class CategoriesTreeDefinition implements HierarchyDefinition { const { categoryClass } = getClassesByView(viewType); const [categoriesInstanceFilterClauses, definitionContainersInstanceFilterClauses] = await Promise.all( - [categoryClass, DEFINITION_CONTAINER_CLASS].map(async (className) => + [categoryClass, ...(definitionContainers.length > 0 ? [DEFINITION_CONTAINER_CLASS] : [])].map(async (className) => this._selectQueryFactory.createFilterClauses({ filter: instanceFilter, contentClass: { fullName: className, alias: "this" }, @@ -249,7 +249,7 @@ async function createInstanceKeyPathsFromInstanceLabel( const SUBCATEGORIES_WITH_LABELS_CTE = "SubCategoriesWithLabels"; const DEFINITION_CONTAINERS_WITH_LABELS_CTE = "DefinitionContainersWithLabels"; const [categoryLabelSelectClause, subCategoryLabelSelectClause, definitionContainerLabelSelectClause] = await Promise.all( - [categoryClass, SUB_CATEGORY_CLASS, DEFINITION_CONTAINER_CLASS].map(async (className) => + [categoryClass, SUB_CATEGORY_CLASS, ...(definitionContainers.length > 0 ? [DEFINITION_CONTAINER_CLASS] : [])].map(async (className) => props.labelsFactory.createSelectClause({ classAlias: "this", className }), ), ); From ef53d442c5baa71a07d6181ba1f968fbdf301146 Mon Sep 17 00:00:00 2001 From: Jonas <100586436+JonasDov@users.noreply.github.com> Date: Tue, 4 Feb 2025 17:07:34 +0200 Subject: [PATCH 34/38] Adjust categoriesTreeDefinition --- .../CategoriesTreeDefinition.ts | 30 +++++++++++++++---- .../internal/CategoriesTreeIdsCache.ts | 2 +- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts index b7fa22109..2d4de3448 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts @@ -17,6 +17,7 @@ import type { ECClassHierarchyInspector, ECSchemaProvider, IInstanceLabelSelectC import type { DefineHierarchyLevelProps, DefineInstanceNodeChildHierarchyLevelProps, + DefineRootHierarchyLevelProps, GenericInstanceFilter, HierarchyDefinition, HierarchyFilteringPath, @@ -42,23 +43,38 @@ interface CategoriesTreeInstanceKeyPathsFromInstanceLabelProps { } export class CategoriesTreeDefinition implements HierarchyDefinition { - private _impl: HierarchyDefinition; + private _implWithoutDefinitionContainers: HierarchyDefinition; + private _implWithDefinitionContainers: HierarchyDefinition; private _selectQueryFactory: NodesQueryClauseFactory; private _nodeLabelSelectClauseFactory: IInstanceLabelSelectClauseFactory; private _idsCache: CategoriesTreeIdsCache; public constructor(props: CategoriesTreeDefinitionProps) { - this._impl = createPredicateBasedHierarchyDefinition({ + this._implWithoutDefinitionContainers = createPredicateBasedHierarchyDefinition({ classHierarchyInspector: props.imodelAccess, hierarchy: { - rootNodes: async (requestProps) => this.createDefinitionContainersAndCategoriesQuery({ ...requestProps, viewType: props.viewType }), + rootNodes: async (requestProps: DefineRootHierarchyLevelProps) => + this.createDefinitionContainersAndCategoriesQuery({ ...requestProps, viewType: props.viewType }), childNodes: [ { parentInstancesNodePredicate: "BisCore.Category", definitions: async (requestProps: DefineInstanceNodeChildHierarchyLevelProps) => this.createSubcategoryQuery(requestProps), }, + ], + }, + }); + this._implWithDefinitionContainers = createPredicateBasedHierarchyDefinition({ + classHierarchyInspector: props.imodelAccess, + hierarchy: { + rootNodes: async (requestProps: DefineRootHierarchyLevelProps) => + this.createDefinitionContainersAndCategoriesQuery({ ...requestProps, viewType: props.viewType }), + childNodes: [ { - parentInstancesNodePredicate: async (instanceKey) => instanceKey.instanceKeys[0].className === DEFINITION_CONTAINER_CLASS, + parentInstancesNodePredicate: "BisCore.Category", + definitions: async (requestProps: DefineInstanceNodeChildHierarchyLevelProps) => this.createSubcategoryQuery(requestProps), + }, + { + parentInstancesNodePredicate: DEFINITION_CONTAINER_CLASS, definitions: async (requestProps: DefineInstanceNodeChildHierarchyLevelProps) => this.createDefinitionContainersAndCategoriesQuery({ ...requestProps, @@ -77,7 +93,11 @@ export class CategoriesTreeDefinition implements HierarchyDefinition { } public async defineHierarchyLevel(props: DefineHierarchyLevelProps) { - return this._impl.defineHierarchyLevel(props); + const isDefinitionContainerSupported = await this._idsCache.getIsDefinitionContainerSupported(); + + return isDefinitionContainerSupported + ? this._implWithDefinitionContainers.defineHierarchyLevel(props) + : this._implWithoutDefinitionContainers.defineHierarchyLevel(props); } private async createDefinitionContainersAndCategoriesQuery(props: { diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts index 62aba26df..c5b81bfb0 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts @@ -365,7 +365,7 @@ export class CategoriesTreeIdsCache { return result; } - private async getIsDefinitionContainerSupported(): Promise { + public async getIsDefinitionContainerSupported(): Promise { this._isDefinitionContainerSupported ??= this.queryIsDefinitionContainersSupported(); return this._isDefinitionContainerSupported; } From 80788d9d7857362997e18939d1ceb852c46cc7ab Mon Sep 17 00:00:00 2001 From: Jonas <100586436+JonasDov@users.noreply.github.com> Date: Tue, 4 Feb 2025 17:19:55 +0200 Subject: [PATCH 35/38] Update screenshots --- ...lt-expanded-tree-node-1-chromium-linux.png | Bin 57590 -> 57704 bytes ...with-active-filtering-1-chromium-linux.png | Bin 60694 -> 60867 bytes ...ed-expanded-tree-node-1-chromium-linux.png | Bin 56628 -> 56582 bytes ...with-active-filtering-1-chromium-linux.png | Bin 58974 -> 58948 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/itwin/tree-widget/src/e2e-tests/CategoriesTree.test.ts-snapshots/Categories-tree-Density-default-expanded-tree-node-1-chromium-linux.png b/packages/itwin/tree-widget/src/e2e-tests/CategoriesTree.test.ts-snapshots/Categories-tree-Density-default-expanded-tree-node-1-chromium-linux.png index 19a18985ef822249c6516b7a67ca4b2bade09e7f..2fd8fc662f84da3801755574b97470a996a0ac06 100644 GIT binary patch literal 57704 zcmb?@2RN7g`}UV4D->mhQdXH+g`|kAGD2ilLI~M2*`tIc8I{PEviDAsGLxCnplq@- z-qZ6t{_pXB$M0RodmqnnJU#M!zxVz5T%YSYuk$>wJLICe;%*9N3Ic(!`@E98CV@aC zK_C!YkrCr3wIvVQ@h_s=nu=!#xgS|42m}tod3iZ)*M!MLmn(G5Uz=`3Upzxjo8lt> zL|Z{yUhXulhDJ@`K1vgY$)toK(-ZspGlF-@s7t?=XxKq7r;&GeH?4dotvmVilv6(8 z#nsZH2kBpY{M*QGV19#JeQC|ezLpWlNJfux#2linqt@glJ~;V5 z|Ko#`Cz?!zhN`^c6B7*$4F@9>&Tx1iO4rV;s;bJ!$Vf@Krf8XW-&{RnE~x23al zQB{?<@qQ}b)1Gq1#sLPCOihtK-T zYu)Mo6VIPN-$l(j9>j8x)=T-uj7quE`SZ;MmYr|!u`G|bbMnQ!H_v~1$}985i%t!9 z@%X8qKV7yqHStZn3>&bXy=L*e#B?=im6Gucybz!cy(AOg+TlOMBAwfZgnH z?S1t*Eq#4F=ki2n+N+ld(ryl_s#Fn8Dk>`ZZ{E~JC@Ltpxw(y$Yo)2*3l7FoWa+*d zD!j=c&B)F^Gd})qY)+E(=`Wgd zt+?>?R9t*~PghsXSh>rLQIR!ML{t3n8|qj~-BVn|?fdr9(dG2d;|Om+@JFT7R<@XY1Twd?cT$O z^n_=)GBYV|HAE?;96TE+?X}j`-Mx>%ar9`IMA53-=6Y#(_=i8gdq!*(6n0iLR=6#Q zTYs6w0$k2FuN4;}Zl{}^ob>ecoE1DyFqs}GeW4gJZCWIAi8n@hA06G_#fj+5Osgy{ z>@?;6V{Ps2uZxOwbaeJJFx-;ZMd~NVLHk&hYv6!{Eo;I;f!#=*+rp0=!*U&c{k2-Z zUFFVG#DqM(V}~Niq>^cwnBrc()%%X{*8@|{aSi^7eUB-eP!QKoki(SNtqAj5B2u-8C7}qe*Rq4 zo#C^&s;H(m_2uCb_>M5JA zMG#{0^XJbFk_pB>8-=a zE31tA!$z~(&kpYHW##1_Kab!CqCxfKyLSf!;MW9>9U~!JyMDbklAD~IoQa8PlaJY# zmXX-kZn(y`K8S`cq`{~;k71~Rk&$tGVS927v1i(Rg|A<0=;$nAXKw%e=(RW=Sy@>b z92{I&_y=KO;?B>XLr%w~`dCO#Lqk|p)DcGp`Dw7ko-?)u@ySZfee9U3kx`QTA^Xw+ zWA9Z3dHIywTxz!BH*fl|Sg)!#Pg-^HPz2POy}r*TlbMpz%V{f`kZU{duqyS{#$cHgFI5&uU}SLTAet*f7UHIXe%QKoazVKySf%v7k>8j>2La7 zx^#((^o7mn+S(d^)ibu``=gkcOWN9YlB{eIiZ9wfeE8tt;4n)fCntyV`lGs{px|N4 zcjqjBF)^{!)Kpb2I@yjdUshHZZk$XU%JHNPHcF#ua$Vhn2$l-$YOiCqw6xqbq^hUi zw{LC`r9xO(n4h12T}exeZtLL=OUntJ6OKEPVRRJVT@#5@owhwiChyY`&o`x@`p+dT zBa@SpbAnYJdzGF(-u^H)j=qCb!kgB z5%ANUSP&F|ojZ5FeEIS~>O|WF9igqQZ5F9LA%V4PQGsKPj4$S-MH>fdXGv0u;YlrC zRB+M@_Hu?ift2ReX2@I7zrGL zg0i=Ne84`EPv#v-$)7@9PfbZNaSfXm@m#SZ)S%G(eRhScK{q`+`wG&3+J&b$p4!^d z4&TV8mxlQLUpE@)>i+!wn}w0_OGn3iPRrfXjytG>c5u?*aSxpGTp6nLc>Vfy5cL5~ zZS7-%f;41gcYe$aruJ`_rG=U5Er5>l?Ck6}Z}tZ6yQHBJCuyG?7N%1Cp#qy@ZEfx7 z=xAxld*xv6Rv!#5DBNNhW<206N8tr=*R!v8J)6h?Qt>!Kg{kfS1+1b|pMK<`N$jC_f zLwi0uXliP@77B22?Pp@*6%?#?`u#aHH1uOWGDlW+_9RX_1Iqo)(kD-zq^iX3-?uL# zGm}l$`^x3ZaZO|V##I$SMGM)VUm4%JcFl35Zs*CwD!`)(XKKEr!Pw~_qF$`k7p_`PxUJvilk(WAE` zc7Qi0DVhh#4KMLp_tvrR0D7&ftNZHxcM9?CN-QNMMYxlm&J$>wxTXI<=`gYHom~fP zEiCkktS`&S`345Q&Cp6)`J2ob==F#5wcasC5zn?~NZ1-V$%pGQ_niy2VyU3_9XB-j zGQ+8Ozu@G@u{o1YcUe}213 z4`p7;LP^HSeqR*zxuYYwa{?D*pwp;J=;=j@eDhWuTO{kn<>eqTzKA9dfZC`dD#+ag zf{~FC@wNVY!b6CGY{PQrnma_bwYBHYox>^cSYHv2R4RD=+TPCY-G>iU6cm~1>5_KC zcTfd>UjF{$hk!xRhfkkofVXk0I1{F(ra~Od5lvD~zqGZrA7aG-jO_=V<*lrK1@30S za{%q0besS7`0--^-5rE8XV2nnGqbQP%+H$yv7Mt+vm6u>GZFrK4FS(uqKGcxv&`Hzl{A_Jj{xVf!XdMqPe9FJtp z&(C9>07h+5o{Nf#X1m9KetLRbM8rb04}}*XqkLs)pAQ}9p+lp?!?Y|clms#wnpZhF z*G1xh_K@at&c)CMSNYYNAW`7()86k(PftgouI=r08+&_ib#?W}j~_^kQ2Ivh&e)TmzTLuV1x%5UFc_!yqWZk=$jV&oL zQ7x?#X)kCui@5i?E9#bQOsJ2}4p!4sj^ngXy6>zM(K9gQ_gZ`Em^e8VK7DFzYWh4Q zV`8K}sNCqbXS`blDkqDCEtY_4_wK1bfjm!|!f5p~Gc!HCyu!l5cI{^F`Xjg{5pVP9 zy8{zdAYrbennIKJ%wwE&{R&t6^XEhI^KaYP+35SGTAr}}l2`SP z$#?t&))G;OCW(xUY<%>{nKFmnzNd%D#@>sO5ZBWDFZ$B|RLuU}ABim*MT2QnR9cEG zbB!wdJ{On+0RdgvRn9!y`qZ0$BR)RHC(MpF8MV0_@UCPqw?eEvh(B_I}qNgvg=@$ni9y%&H zT29h)LtegNeRVNL||EGR14RZ~;b*q9pR zBDYTPycD<25wmn6UfjmP$?0Brc!lfS=v04kwUa&VS9YaTV{}yBf0y_N*ns)p#~kf{ z+1%QiC~=EzYtR0$#_Dp5_CwM?e&rgLqh%l>&{JGr_|XbLi@tm-)1H^OeSb=a;N$rC zF~74HRW4t?EJR8lqa2VFNI^k?USPQT7n1gR`KeQJ-pW&!x->)sMaVV)3F!) zXs!CFnVp6R=lC}6;ut;_zPb{DqEq3yx|f+(E=H4we%G#Do1!_%G5vjgMNH(fR=|n;0_97#pzgNKb^6eixOrQk@p zShYyWN}cvy_Oa?qlQ^L`RcSiLj$aKSbcjFHd;;OdjT;DQufI!woO}1kO3Hd0QgAvC zZ;l?=!?t}<7Xz5j{&9DKY4?qmy)?}XB8gq@iej+_^NblAI7&tE}7VQy}&M7il6OWO12 zbNrP4rwJT2;~Ej#YYA@`%Pwo^ed02!QI!)deq{LR+vttL>q~HVAeU(p)x1nH_?=Y2CG#G+w4P^A~ zf=r=wuNPPO-bXcDZr^ugcVYnPj1&4o^VTQb)9b!OJ4in~5u*&`SLCQ-Akm%(_2@oK zRX&!Ha!@UbzE)f&TV^?F8*&1q1}x?V}`~tK=AF6eJZQ5Lj8Ch$z(w=2{u49`U)$>wb-JhjC9m z!Q{5ohcrQs=;qSWQUIs>_wOS}5oLCtJ-{tIKO4kDADWb8RK1_fcbtj%KHI+b-nBFp z(&os$H~3V-%<^q@jaqFRK7AB=DE*V`!j1XK7ZxYPCMO#+nL9R?-=`tJf@8?Y;5~TI zSLtwBX(<|b@F>(lW5dIzxuTIZ(Q5t8ktUNrx@H;pEKrp9t(JX9;rvKs>Xxc@$z-k_ zZF<{k6r7p=-a;UFczD>@ zOqGPKBq*t?d;j?rgRaNV?<{+xngU0eQgxN! zPC{NuMf!)gv&Fju{#Y~n)qf|tem=Blbn3;+QSh+t@eR=m+%I!~7p)U#o0>1apwVR4c zKQXo4BIo5FE^hA52}f5x35lK+*>vq+HNHF0Kcb@;k^GFL0|5PZ`07FLL*DDXONQ5w zrSG<~K8%hAi!&-a`r`*6W~p`W8zg1$94~Bzl7rS)oGiCc5j88R9n3S`QDtLlTI)lx_rL)e^AC^F zE{zbM;~*g|bnS0?`__CE$%$Kio9&n`q+vhxFe++(VId1Z!dmRab~Us7o1U2ot^n6) z(jCK;s;bSt!kb9a^+MJobpZh5t*xzII;ss9h^*$kiDqJ>n!m|-EIDqR+4Rl`3TncQ za;ZHbxvFXb{AgfcT;clK;M@aw6e@fsra*>cIas`sek_1*~@s# zUahnFVe-*i??qZzq|OJP<~Uiaq*2$R+U$}NSjKuekM*RsC4Q$M*iliYbYaNv-owKl z7nMjx)41}(?fS?kA=ai#} zBW9in8xaV4`R4bYWb$p^`0_UQ`R|p#s|t#W%T^O*a?Z|6#bsj2Gp83^tA~HiM7&b0 z7phh37BDJX%=zHmYrS-Lb>V4Bif42==NdIC9Bqiaz5SfduFJVg4zOZr{F`zjH5MzWk9;dK>2%p&!9|0?}LKv*k^w0rff(?$bb1Q>@J! z-@3W?xIrG%);9GDCT&>%QI$z=M=DCs0>iQNtQ^A%3k`k!C%|8L7T^eAUAvf5-s>gl z>13IUp4U>s!^06-=*Kr##%S=x8EkB7Z{Ea<+kSA}@>V-dP^EJkspBy)xJ?=6^YLj! zwM#_3`Gq(U1&#BTpN1;IZK=fa?;s3RdHpT^>TQ6YF;3M8Pt}pC!oaWJa81Nz^2^+C ztshXLfxi9_=%gRhgTNRPqN229B!?o?laq;k9M{I^gUn{(K7YEU>vz0hc0l1EijTu*Xk{fyguCmK|59CNMEG)bbF!J9|%jLK0 zQ{3?z?W2ut*#PO*I4^BG>)LBax+Xe7Z{5)yEb;c5cynEvZ3g#CNlxy2DZQe?v*w8y zpbUY4ezLp%>f3N80-=YTE}Uw&rRWnNi2VG8)pts_Zq2ZrqVUg~Yt@FV&=Gd|&y`d# z;3d0X-Mvd}ZG)UPdaj?dZ07jOHD^Zuh z3|<^bJ#r)t81r$#dJ5KeOsN(&Tkq&p;Uef=p1R(oxcn9#84!~WQZA$P^b!r~75Zts)A z8tqug=w@T{+yVz0g)n~~>ng+Q0)}cmuZ07bG%f{Ht-cn;`M&t($Z@! zp1s`wTUl9YU%FK8>H=Dgs-8t&%I#A$da5uZte(fH}K*^@YEG19sGGYoo_;=lo z+nyd&p$)f{Je2sNB4>t{v#FXOovOY1i#Qb{l+T|>eFoeFdHhi@BPR#xHeiQRu+g`5 z*{zzY^FPs{e46&N_ht9dIcq8>XDU~#8=Xi-$&#K$x%Hv-g9+WTtKOGe)f?tEHZl$k zWP`pt<{EHNXz3W(PhNZXxcx;B8evoq=LJz=;nl~{S8Z%;#2m-5B@wJLeK}D#&?!M2;4x+t$;l80{ohx`yx>}Hqfl?QOK`ct^?U_^YJm)~vB_ z-S$zTan-Um!4A=d7sms&%#n3KJ1W-30yQ*W6JfnGm4KeOVHeGcM@+!Zoj!{47FJe$ z5eNDWbI$k{icPLtogWr_hPIaQvPu%jaNSSOIN;24GEYHyMLj)zHX6$QJZ~>=Ip3#G z*|&JvB3w{2gU+9Cs;?u-BCQG?<{x5Vk5A0imUvUbsjYB!l7~0{TmF73j&)tP`xp1+ z+?Z#5rQP(+Y3yDLwj6El8{AD%8kna?{02+?x2nCwqMMqVaa*%0^l5o{vopcL_-{kO zy~IQsy1KGXYm1g&QIyA8dx-T*r74JmM(K-SRQ9K+dedBo2dsRT%{ zVa4^r+D4|b;@kJ{{Yg==vCE6|%zwTnJhwf_#|OcT4}wYKA{Q4|f6*<5;d5lq6*M*H zkx#*#v~012%q}lL+mLphH7V|s8dNwPLwdOVJfpLh$R)!khtCtsU0~j9wtm8HpxZzH zz4f3%_%peKhVh4kh^zcW@DG0QjW{iZ%rLU{SH1vD~ z0>wUMdP18*$eG(~>lGDwV@;)3raP{;K(E9VD_{Tyt?oYir%x?NsdU4foU~slh!i8J zznqUt%6j6R#`W3niT*JMgrOzE39^Wai9 zHa21fjX!>OaFaIbIYLptdH;3(OwR4_GO1 z_!ALb$`nv56TbX$!MXLZ$|axDcS%nA@%edqejwEi?0KYYEv;qxPr5(%!B z3>BBZ0(^XYU@c~smv<2oz)Bmu5*9fw!Y1RM^8ERTn2o-iTx~0hgvpIdT3QQW-83}X zw`GLBFOJ1ZWDUhtb@TQZ&x)NO=&*6IH+~PfRLsL*kbD%aj)(2miwh zz|eJD`*4U$+WX5#!zdofW^2qb95lR9&tnomh5uG-pG)imuQJa`fnWw%uY zskPyJw7%M1JX``>rQm_g!%?LsHQ#AkeXfhA#patW7?emqJA7U{mdp0dqj_;!hN)}% zXBlW+U&aGYkVq7daGsM^Qj(-&1JhVgSQxvqRwOkWcAD&*x3~A!KziDOs{+~M;yu0n zN~fiUPbnA~4c+W37)?GzxO37tVWx;x%DtdsJ*+zZamfnKWSP^8ss19hKeIwxxBf7V z-WWFWTnBKFuo)VlSzepF(eILb;T>QaFcQ!aDPi1J&y;Y-@5PIA4AUJ)*O}?*X&Jx# z=_v|6H3~)Gl*6J$Pwa(7e|FeJR z&!2yCCJyX^gu6D`u&C_-$jP;-n4w|=y3QR>t4@9RB@5usb>T@@M~8@#u}{a$HyUi1Ox@KFfqvhQ}>na`zW$7zi4e`E!x&&TC+K;^OGD zhxV{i5iCqadaaAH6Ie4xJ&E%;Vl7AKqt%ts>=~=AWt@C!&x- z0Hv3+FB9$>>|PU2<}=U5+ZGJ_5(_qrWvA=2JrGOe;fK8pd}slSrhbrZU~*nH>DIjl4qSqaHk@ z*H61(NwHIztWUCLO2@WOth?JrFlFnfH3=a-Bg6Ol7<*)0rL+Cv!-vg3CX^Pn{xT(c z`f&ezZLqDoJAkrEn988oyRxY(W5+J)Z*#8bIBXV#MMa*7 z-Q0iqinN5p^1Db*!_q$&k7kuF{5T;cb<@J4tNXLNtLrg?s)`p43qL+RmAY{s9u`>! zfyGHW&MFr7U+ME#JHLE^FbbrqMoQzo{*7Gp?>9S1fW#^03|1Kj|K@AhdSDYcQCx27 zg*BR?;y@MhcCx>6_Uw+JdO0GlDk=A`8jVUChw{VcqIW4;_fQ2W9d`caL_!!Qx?}n{ z*{t|%b!c#~qy4#*huX`Z84vpVeh>+aOuqB>rIx=R)@;y$r=q(_6?(cvAC)jhs$6 zov6it#dU*!0ZPXEmx3%E$H-MGPh`0%@T@3yO^(3Xyn z!|mJ93ElS!@0nX@(~5ahI_!C_zSkwZ&fS^U&yVQYA%(8w2cD`a759#Km^bzR1XO5j zYN|gS4h<5;7!(#YHT8jjac~axP$lLJp@$U|h}POxT6Jf_uH)|RPNBcVHa9n>2;K3P z!%csJV~In~QRy_{N?RTJ&-o#YLRz$~IoErRZ!wgfHwbjJ3B8+|d}nCMOYO>)D-IsM zs^lbf*_ZRFsQTjU3r*`u`C^!9X$daERDMG|#1{<>)t+Bmvmp2Qnb&I#A_<^rc`Ikn zgX!EWoVE<2Z+{d{z75fB5vJ1P3$e|6M9wZabL(K`jGGL7<-I3*!il$ksri-OO+R&s zF;Ncu6Al0}>x!Qz{9za3#np6miw9NE@`0d5V|MmXBJTrl+m8=%xXFCIW9!op&ya^e z>fD3tX$jZMM%Kw&!ExIqF z+R*Vc3-JGjg07>?dpn=KEG&OfVM=fRyw4(`4kb%n-4+k;?Cgx*dTn;(Z1EfYd~>yH z*HX|YL5X~Gy=4h~bz2)D>o<%+(wDJ<13ouzBE^|dZM{x08ws)w~QG?d}t*=}51 znIQ&OR7gW4#$pKS7rg7~(bd%KfSmx8+sMd>P%qXXmfzsz^XI~@v%?_8*Eg2G&IVnD z2MPf8Mdt*BCnPA4+(ZN+Mk=cMVEJSywQt@y81EXAU!))|D$yCp;xhem;KGsJmQg8W zMH8m{iOuB;86mRo^SA=&r=CknM-~0@{jP{@6890o5KV^~;NP&KcBx~Lz4 zX!_x?@C!Qj*70$R@mk8tNS)j(s}KsHdqdc+AIc65RYqkjR@Q{`YtJ5{u}V}qe*FTA z&Gl8gjADpXaHtRnxLaFWTU659&dzUD-dtxS|%#r$fWS`q!G!OnA7t)!K}Inf=kq0xO7T zjU|xhAU}Dw!bnkJp;(|P=<&C2->&aIs3)}TwWE8b%gLThDE%;UeMAEC+f^|i` zV~Ccx7rbO?g)7@ah%=3=;ycR^Z%4;l19f0owdw<>H2Da$#OeR z#`3bRme$|()!XLg=2z1M=Ks;0o)IhFV+MYwJ8)q4?*bSWheHYr3X-JVh&P-dV@(g1 z_t++Zt;HkwIT>15q(jd>+U#@}p&lLGiTHbV%uSljdd=DUJY2JivP@(LQxppmV%}IJ zJ=NDXzq-;NSRnFa@PgVEwu_GrTU`t`P<;PE?9rs@!x3-NTces8UHA1Xa9~%4`4YNM zKfg%LeyYNL>@CDDQ%2Gt&4W>BGa z_6d;D{POCi%VIhmc%$cvxgjCVa1)xeFcB_Zx)fllr?{{!XTuCb$HLOz)g_{&PowzGKH z^%bM>NM(b@kPNHQ&&NxjTJ_=|8j5%yuPcAO8be0rTp@$HEl(RN+M5vwVf*<YNm)!Zw+oj4hmFe>}=oZAI zuGsP#c78ZuR}kE?Lr7iKd|%5Fdtc*u3c6hsw)7n?;V3_9YWxBMxoEg#(Hu=pPKJBv z6$7Ld?aqeT&vu|>ZfVK@%<+laKhW9Se*cgcT@Sokkd)!A6xuFDUS5@9VU+9l;BJP& z6}IG-4<8Eh@=82c9nPMuN0wndaKLm8T>zr?2p3nC_lCZL!gT}-XbBW((7edq&<#oI zVyRp3P%{Ii_18p@$tl&Q<5Yj3OagJceSaSV-%>_a)~6?8D$2^C>S;j%0br;%R)0Pv z+}TOF-*LI=5Fg+B-rf^pVt*j%(^E9U{B-Um5g{clt*Nn5n2`$Dm4Sf)W=I%2Z(O^k z8ZW+^i|5#}3LK2O07`@JagmX8uzoY&1L_j@T63fbfJ18AcMKbk?AAIg#(hmqKfkxc zvQjq<4Y3(H;6R}uQ`JLI9abbINV_jWsJq{44zC%Q&J&W7;f%t`22a%`-Zi>%gdU{r$uD@0G`EN*ul`V>MuzUt4n@f3&i>;j!uLprNs+nvPE$ zWe+Zyq9XMR7lOWLXJyer9b#aB{WUO9v$?q$+@a70EQ0VtrM=j`@{t-~5ULr&U)t$j zxNrfDIb1a8wFfuuoFV%sWF1++eU5+oF)}iTQi~dl>|5zNSJ%a3?aOEBNE;0&u>J4fhwlFyc|dbUgU_Th2>>g zyW!JWloBx8m^Fq%0_Tb@MRL1x>*L3dScSskVqn8jcAouv_ME_`ptvLAORnT%Wl=3= ztHx=_v5m3_VK!t1bl2Mk7OyWr=7E8v3JQ()3mV4eP#Q9{`LcaiVU(QYDLIF#9H}&U zE#bTc_JjmWIdKp!%avPnvPdC0q)z%oEX`9%cXv!o5gS*oQ?0+7A|V-VB4iZA;`eDQ zsjcRgL=Zp8J^w#ay5+66A(j>v&)O}H?azu0{d~$=JpSAZ$TUt#;k71h76nCF>o9OW z-!ft7?3`Hi4t`c|_8Q6U*YDqd*UoJRvZ`Q1$cP;PWWoubO;0B$u{hP-j11x9$EDon zua&QgiiyFv3!En7vsp1%4y_{MfYhU{cVNDrX{bQCgue{D;9-7#II%F7p_8rG?i?o@ z+cF4s4H+ElK}pF9$iY8<{yZOXLI?!a`@Ze?zwpc9MZGV|p%r?@yY$}*V6 zwX|--URGi#v-ttUK5!x6i`4BOs_VQMZzv8mT0pvhguU%wF!Y$7p{M8m;X}sCZQh-E z2_^C1!Glmc^XAX1soCfzg9L-yu8EW0sSbtEZr4Q%!N*BSFuSg!Kb`K)S3GwvuxaeN zxp|#dP6>QW_$9Veo=VRz;$FAGKIl9+^Dzwb;{y|86R(Jf0}O;X=p@9?Uo4`j!BauC zJ4j20190Q?>C;$;^3>gwl=+2)0sU>_M|gPNb$27T?|XaaOQlC~e7u1{h`5Brr-T!o zZEZrr!WwW_){iv2eVeOa$R{dFA2BfU`*%4Ea%E*@Fzs6plxP?kc@LD>w~=rP3ck8@ zEfaan>HD0wG1<`Q|B*ST>t?{zCAe}WaGyGFW&8U2!Waanm6BZ(7|FETp)z9-W)Cvb z5sFsj;huDU%zZ%A#JSwEn!{0=W2u$g1FM%l+LmA4SL{KS3Zxs3~SWL{|+<6$WJ*a~R>B zPB|Y9$6RQdsYo31BwWf{zs5GIPTQNgGjsny)xju{;o`d|r@Yp_HAnB{u|Z3WZ~Mun zBZ-Y`V`gVqfK@?1wbXg)Nn9M0?2gEv9_ zgHQjGsW3b;0)H{&?LKjtK+_UGrm)8O#YG@r()iXuMKA_xs;VbnE3c^|S(GtwbIHoe zo;t;5dz6eB5zixD9ZdHrvd>p?i2@LCfc*q&(u z#XJCp#u=K8gG1flxEvnb!AU2pEI%+0@`vWo?ygUduu}g7YXC;rpDj60%ErnH(hnTU zBs;ndt32IMa)rveB1l5%Ix8iGQGMD*nVz&_!R$+~RQM^UbwXz+BCIS#bm z9+&)jvTLMB4wJ`Mq#0ftG?g_pas?TRqHMcFAeDlN+ zP4zo(fet=+a69OspdXNOhDcXqEu~CDPC)^YDrjkeigZWBh>fi+bt@6N(OYI_#5D8E zd*LF+h|e+r16Iny{++Qlge2!NHP|fRWqyj7D{-9Ag!+sA0ani}G7ea3fQ(^7_J8>k z844a?>*W(Z)hJwpVRXk$>-@oJEwDq5`_$SR&<_VFTJ$@{2VB3w<<@rWYkz+^XG{^x zv+qFp>l&sgFc|2Xn^}X?3XISun38ep{d9CYJfhG?{QjI19Tf!|NBz;{va*vEP9~T< zV<{dQ8UnF`y@VNS>aYyK1c?p_3u)YUa15MKTiZofK1oT&%&vxp6dQ|t7(Z}cQPvgXGbUbo845uOB42}hJxciq-;Ezp1I=blW z>|@wBJz+%TywvpB?bPtdhI?XpvUdS}Cb)?)qK5?q1%Mk{-84H1D`OF|Xfr>wwgQlB zkM~%196o+LUq=B2Jt8zzzsmDOHSw*%GEiA}wyc6fL!C$2R8utp{ca)K0a52lPtEKB zF!8WA>k|Rs<%=l;al$CZxU}f*Wws+yX`slaALJOmkf#3iY1zNdbRg}fKExdtvg?N@ zG(>(Bv?O>CRPtGKuQ4_M;@M z<#o4>V01_1^$$gE4Mx3qF@_3R4A~iu)e7n>nCL_-Ur0Pzh$FB`X?o=)qNuOG9|NFp z<-nQECgn`!^S5swT7`zj#yO+65xce_l{+JMV9R%C893@_aCPhzJs24GLYRs=^L&Sbq`^Tic=E6%Q+!0sQ@IAk)VUw5 zV*#q=ww_|Y>$G}%dp9>X(a>qVR7IP}ByO$OPu^?S+xo8=vUISffI|Wo6rH z?kl58D-SpLZeZa9rxp|xgccUL0w2<~6i34*{iU^)pN}tei?`4)a0&Y*=SS)^TDiD` zGev-6gGQv!rnumpg)ZZ1I0YZH+m@#)oGY&*Xj%qwycoQH`ds-2)wcPLX{v})q;DVT z1Lf*F;m(omxovmhws|E1ufZQ#!W;&qmHAbgiJD&YSPWpg_kf?}{8V-=Ck~{EpjV z>Fs=U&?`%Cczx|#8n?azhkMxO!q~n(yn?>M=v$uT>O zn;;<-O3j%dd=aZ}^7DUxn&+UE0bN*hZ>RUPOubau8=GTxR3=%|#nr(n5w2hv;PZd= z>Q#BU)G0SL@WU|jVkj~%PY9`9w#D!C;sbV}{gQF2A6r}1R8(#|I#Q95)x020OG{%v z=@^U{{q$Up!=D{OedxILM&}W~Z%QG@V^S4fUoJjs&`2!%-`a9Fm@G>E_om#H8^woc zH&k5|BfM+$hS6ht%nth^!xtC3pp`}LAJJy~&n8=EQAJi?+B5EpTt9Z+JMrCp^(X25 z{Q;udm$h{R1DAj<~c9=#5u8p&EUjq_AvT89Ag5{8uhAli^d{zrB zJ|>W-rUsX^x9zp%y{7ensQFd>1FY^wTUIr#1I z3(`U0QK~&xJL>Cq2$ZZ^%4q}|ph52~l{fX!!;=YY-lZ#t_Y$QGux@)G-_Q^!1Y=pB zm+F8E4oJJw8lM`XV7~V#KK|x9`$hb$vJazvNbMNdg8?kQ^(}Yr*Z}GRN((3=EIL|` zHltaXWs$RiQb8|LQd0-YzCw-Qj#P4VaS`O<0SwuDWfeZ(7U8RJ!EzemMF4=l#{4nJ z8)&@?>@fnqjr^r#enUH{-rAH}?-r7fz_W-YFhr<^X;OF>>I_SW%l94I|9g9+MB!;>U9sQA9kKRw0>mRtSqY3&w3mkhEMiJ0;N@tpOLF>ntViN(2t zTwLbBDQV|bRSSxXf7-1%9qNl9f-}MH=f?x-63{+ii$vEs8W!^}hLDH=!DrBk3JhLD ztPZLQl(gZu)OcsZ-E$?|N_eqkofiuSi0P7AW8hJAfe8r%=&z(Mke360#Z3tafE{bP z`2+Ba=MS&e%azsD)`S!JQ<=+{Pxg-9^$v`>9YZQ24X(tKiEIoEKSxK2k6w^8I1U5e z`}d(O->Y2b%+PhMf4mbcuw53weXS_?$G{<}cl-A7D`{LXms}G;%QN%s+akUj>s{OU z2td&`{u2TQHE(u~04U;_8I7Rf;aRJ-0gNES49uS$wE6bA zNZ;)pDtC!!`M#Ho1XUUrbr7hpyQW`o6rM-Kg@?l{$_buB3=&Sj{rl8OJK|J}^71gX z3JzIYLt~`48zZFf|NF5(4qRMVsE_}gqul)nd?NZ1gaO^j#~*-Ck@E5|@2iP+2Va7v zTfbcdrP73$a0h6v+^B&uu=mRs(6c;JQvHL2l`LB{B<$(NeW#F9Fz9R1eb9eQ0BiuR z15S$Q4o!&%3?H%M5b?#VEr0bXb`tW{wY0J_V>CB21Gc9d=YA9yhabQye7DCu&n*Lc z_2LE6Inl1CNb*qhc;LA+H1xuW#yl?YpYv;N*pku0VO$KlR-(xD_h@1;mk5hRg*;FJ z#>Be2|KJxu1kXmHB||YPdbDKoIzK-wBH~qg`if=Zga>t|O8*UP8zqB)wDTmRbwI}i zPG9qWbne{9 zqWd#{I>~82Wb>!wWx@Ra9_XY4Y(l3#o1DaDDY}Dh8=osTI*qF5Lsk z@Uu1E1%ZzYHhe#YK)7(>6O{5@q@)7Tqx3Vp9A!b2TkBjB7$t zb~8JW4O9DgYH(%!IRPYr^&@Q)jtFBgocF7KWB+hs*O%dJHp%Zr%36=LD;+>rrnBoW z8N$cdSP)9R`{NDre?|hIdH)~EJxlb|L1$mIgU_orWkSbdA*$bBb7LLKHuJsDOMhIq z4cUYdL!!54zrD|wNBZDpFvVV(XGmyj$8ym4BH^wpBJ7SGJ2t$8gS+_81p~pk2)0Ua zDnEXhyYh*MbfBXIb^7j|1=JA?eppLv0X5_GAy{W8?K{cIhh$`Aqz!^2BDRg}!%I++ zO_;55&mjOX#ny*e zldkk_9F4w)eCrb0T45of8oC82C@65!F4F_fvMuP?|E%1Juy1`L#v*QA(+RuxWdj3L zyEjwc5$xdCaFeh>ONfa%-M&5MlX+l6aCirv&^pFIN9PeZo`{GGBUt_<;tfZp5w#wgnV<#DcuMJjPLOmkuC$idpETWo3O(hGm}KMDUz~{ttUDm@zyU z;ES$;!gpwkKsl-^DtHYFj;&4Xu9|nLKmkDa2LsN8pohiOG3s-esUEW4_L9|=!CV8V zhT*QgWd2Caa4w)?7O)sG5$wdKyubrfoB@G>qxP)*8^$%RQ{Xwl|9o>%sD`(fMyQ3o(6yW> zPL&NW6T;jOo_81FU2`)?xxAtxQUc%=?yyKD)l|<&R<_!i!P$x7pI1-l=3W6Io)8s9 z+%k-l_WkhDqy5u|VxULh%{R29TnWNg&p{_cGI&l-`JZULy1Fw?PJiy#-I7JiEG)c- zxx|SiQi=XAO^I!ia;k0b8}OdN&DUP0q)7X0vcBcDZTzpK9_0=+=6JymXdn!JMCkAu zYzUh4T432VGwG%xP~TCed~LyF?d-LJ&)o4j?`l` z?)!KF?=3>gH^`BhpPj}2SzO=m=fhfY_}KoI&d!DH*BNX{pE@+~ z)n0%1e?=CskhrX_t`0ujv9_zTvmb0ZX3H^$jH#=U$;l7ZT`GkA7)E(|7Nm&*%_b5t zjz*h>C`=}V8h~)!Y&}y9cYqQE%r0tfHG7SD@KxPg$bzHw3fLWZ_tsYCUO+KdjNz7r z0J*%hBrYnt4tj~Zd4>H47!JGW;^AQv?(Hk>z8NL-GF5c?-Ev6eha$IMKr%2!TDMhw z=cb>kn%eho-vFH;fbk0Dgk&$E`W7-1SrxjvKVYyxy+^UdxXCa4(}zGxcwMozCL0OE3>AXDmt%> z)HrAXqXMZ9fTJQe=t1TO&4$~+&@d~K#Y?sLA0HFr0k$q(+Swo1w(-En0@~z=i?F!( zqX!Rw8LE+~@v;N#-pSSHA@}d+;w>I|CURM0HnOk4KE%c{NVgcrpb^(sSDyySQF4{e zM+2W;GT?m3XV1U5zhC1%M04}=Wtgl2GqODcfBZP`A;ts0v~#Z+oyM3jHpb6yM^$`M z(np`q@80p?C05P_`i4B*+;}sH6eJaPcF)g@``l;DNw`g3?D}*#LFmpt*aZf z{Pg(|!DE>q8PMXw9o5fo2RsRG>ye_hzD1Bg*!&;4z!7hW#C5@EO?~K_AiE%aZNGzHx~!R;TPfA>uF~PU_SpNh zbaa@1zr%Ub(DLLzK%>k#iwc!4(V!0OMo3s#Y)s5fQqp;L31Q)8Sk**D{r6d#o4-Wn z&AN$tyJ}EpV=A;6QF)xkV)WPM=3%(iNO~D|5?!u8bn`OF-E3lt{k)+%Ug-v3M~!qd zV;-4x_bC^qI}zouq-QH+-%+L~vL}kk;n;cijKBODB{F~2`AvJTAI*Z~!NX_lPx1cR#} zJ%xl#AIbh`Au4pQ?58|kG55{sa||K_pk)`#b(R!d1UjE%Hn0F@w%w}yf!+&N4#=rivBu4le zGBwYipTLU)pzmXSw>{Y-=qg}|^J{S9B>BMmAYhKfBs-Lk_@HC%qiFrs)Ym%?Tw&ZH z=Po~>9d#vqxBbD(t9~!9_+h_Br?c zxc#+${Z`g>eZQaK{eDgF+OI#S=**lqZ%cbvsojmF>^Aj-1Cy)AyLINiCSJak;p?Sx$6Z4{L@>MCP?@gdHsjTft~_?=l!<|P&WVFx7x!*=(2Yk^ zn5;gSuL^mU6`9^~RV5?gl!5P%h{A=*nQo~XnL}fqZi?D?H2kVjOrfgJh&t^d&OIi2 zZRzz)Mf%+LU;U*f>v`gGE!1%@B{>< zoP@08U^VO4K%jce2m)hF7_$Q#p=1rSgee$R&UVC1K(TKH=2YoC$os(l2Y(=zNzktz7 z-8>c=|9W|N|M|{Q$p={^KcCLOm)joo?S+0<^V`!&4!v(!Y{>BQ?!1wAYLondzG-8s z9a`3G_A?VA?9fDH(@1vfHe%#RR;qZWGVmZ4aeNn)QS$Ylc7~Y9_p9EY^xM1hMfSZ0 zH6Oi+_a^pTm#!C4Vl{PWVQAluz3Rb-ObkBE9OiVb=Wd51moEL_+DE4VE%Z9fhiaSp za{#%2TensoY0>R6D1HCXNLMN2;kSeSH`2~@^EMc&WSx7rhmn-DPD7xS6N{>TzK8nl%|8vX320t8Nn9cMRb;;RO}n_I7qgtua_n zPquAai7V0?_#ooof(=@HER386D@b#{Rk`S6Wdg;jZ}FkCq-=5i%MmNbqen-xv!|pv z6py?U{$3?vQc106RlZN^ZR1R=Fwu>-;DqYo{vRi3lEU9)#y~ z&Kxnz5d?-o?C`ocFe%WRe|C`1!0|*R@dP%jW%rW8+vC6Bj>t-~b8{+cgUfHbZB#5};c6Eo3l-2pTIayRgI1 z>TdM(Y^OAA^#0X6;CDkxAPt>+=_aVp<*QbOs*G&DoqqW6hK~02&A%7(muJt`c1hQW zZRB6fC!mh+#v2mI%5RKh`7|K+mp%-zrJ%ufDrUL=;U;89N|E z;N_yOg=g-s0%Rf9592|^(^7ZT2-@k84UUc^2 zMdi&W5VzC0Bc}V_+-!#Sf$CZN7VnTy!7us0<=f>J%$)jqqqx`K!hGY61Cccpy_o@tA(?~beA zP$=Wpm}HoL8mY&%=uPeBtdh4{Uj!t!J(kDA%r~lTU{`qK@&7k19-wO%~ z5N<^jp%HOA^8{@Ii=J>a+A!9{E)AINM1`lIt z1kXK(MvXAlzJ2U~hRnQiD&a0FNVs2r{b@3A$n~6-F0S6PW*w>CqVS`-=a(0&(q};R36=fW|7hPhDMCjLPO2xa61*gM<1yi9CSbE$8Sjd}w8X!q zn{w2E{7b06yv?_wXi|~Wv9PcJ{6G@*X8QG-nm`Vh6#rHkAG?Ru)f*5e#%||?moQ0Y zS6bxh{V0=MnXzsPK6OML4<9^mRw;EhTC(-q!_jMY71jEGy*rX~lSYgcf__(V;IKi` z`%FBj4WAN;w`UK|1gW?NItmyhq_tqIRM=^4FHxwb@7f7nt`dD`s-gR$Ndg5L1QN|I ztbJ*IdU+cM7*gG=ED_Z%5!%kXBWr|;Mmly(SnlVZlH4R>=(VJKmjuHQk;tE)=NonaD&npYf4;uXOJy7T@7pLH;gH~i(WnfHSDK`8~yqGUuzC_=k zeXf&B4Tkv{scBjAcvX}4?bCQDMmtCsc>TNX87?{-%bPHxrk78$Mw_(Fq zK%a?D@#cV~OdM9Fqo}^DSWoyk@kM1+V%iRN7%LlVGcjq50cVGMDbnq0NtGT(< z<6Ol%EFY$I_I)jSZ~w^Y=@(J`0v=J`ryc4j5PJ}_*>+0G*gx=BfVeCV0|WW+TJAJj z*{ho5lcrA>MlrMXX@v6sd|%+< zQIFPGEcm+X87-UZqU4is(&laapd4un+PeV47!g)jp|g+0VixaiTXSTD5O0WX$Rdm= zqC>6oG_x9Da{#;jcSzX4Ki?iVi|TVwJ_iLdlgtn1CY6(yH!w7m+NWsQ+3vP{dDi)J zYf-&~bn8BJ*f4H5j1$f(`I|mH<9Ou1{*n6Y*3FJ3RtngqD_^|mXlt6eJ~k7CaMz#m zK+pUAzVv7BgQP&bb$8#vDMyk}P~s@%C~h<1m7IS9!vY(4Nx;wgC!>Q3oq6>n^c+5M zqPvDO01#rD;89cenNBHR`izNG(5&}pU4?g|4r3W@!Fer3bplnO(AedxABKK8@7EBXqge^KCA z9qA<>LE;GA8tXI0c4A~#x4FJ_$b&UC#fKC%d};YFYc;2lf5TbdYiA!TpjmJeJG%()EpJB! zWqc%`5QRYOw=F0DcJ90@v3v9QWqCPND1IQxt%Dpfs&x%=6+*Fy^VCz@uK302$t1q=EpDNQ?MF=WFpL8sB8g$o~L z&lBIpr9zw{KM}Wv-GB9}vnexW(g6U$qFPVbwM6^b2Au^W!IP-+F^fSyH73FOuD~Pq z#XIrCA1)Ssq4K6+AQd$0w}0z0cp_{IF=bqHh?Z=QNCvGJ{tZqU5~NT;!@tZy!o1sz z0!v-bJn!&f@$5lhd%TarayXssJv9qDf1PL_hVTrnFhx4`K6PfdcWD_J35O0Lk`d-! z2L%O@96oGu-sSxKL*?!vTaHq5Z#%!oUdv|wpty1_3abiGvF@A?oc+&?0QE5xcz$h-z0x0Qy_$_UAz5}c zIW18o8j0b*mWXI*YLZ7u%U;hN}AT(@m z@lGBeMK9U;E2d1j1ER%)--W*X@nb3L-FFIm923?KRu-yfX2^zVEVk4QYDR>H2~}^% zkcs{GAUUVBPMAc#?0A&A0Rdy@Zb5fNdTyk>*7vqHfw<%J={-$E#1aoQVwppced*Gr;!msp8f|PWT!-)gzfn==|9sb9I;8mO zN)L|_i0&ReniJW7w7I#YlvJ;TWS7pq3KDk@j{bqm5j^$I&70EQyNjQA6UbGyOBi&m z?mTQKO$D9tlgcfh9`yS7NO0qvrtQRuD}0`6$%avwwPORMwB3<%>C73jQ>D`N{w`1m zxSjB3vB1QlBWfm(3kl@g>0}r`+N=39PeJ0-({p9i4Qxf>DJUaDTfB=~%GPPCzdy7Q zNK{FK5@;%y$IYBO&0XCoMBvO=lK}rQr`_rXdjDA7MyDwDX^WschW5cTML)h2E%KZ= zQRaSlU)eBH3~mtkx2b&HTlhr_Xk0*$VZ&Rdc(4Q-`EJ-gl-0f z{47_$31Xy&rv&_JB6(c$%dCbw$>+GTleL9X44on?@ne9JT#8(ay;6Tx1a`tuY@~;b zoSgw`h|mMI36Fidtwux>Sqk z`i6>hmFAnHd)~UP-rl|Z)2~*wNRg+LQ=+{V6B{}lT5SN# z?k**Yy>{l{A3uKJG^PYyFOyEcrW}=#oo(Ue;+N*|89_s+ZWiI|$?+3Fi3&q%E9?E2mqJ*(cNVdbdhfkE|GFK^xPb=T@X-RcjT z`0C$X-MApBOz_57`F(5mlksi?X6614+gETwX1s!gwR&8i_er7Rr**gPdh~I{o?T17 zDCs5m(qX#DB4=>ajsNjPQz_oq_}0tA7Z(@4@edy)<8)=#botQaDaFKPQV(If?kCIhC->{He_4;9S;*p>cTIKcQm%9n>Jo~UtS!Kio^tdZbpP{BE<*(OB zQ;HBwGth@s%S}yxOW!Jpa{S!lDv*<~cHXf3RC3jEv3Gkqy6PfEJdPK!-Yq5{8Y~F@ z7#;9MRxJ2FIq8tq4Gov@s5ig64v~kuWeDjF8j)pFQd6S?CtNXD^Px{t%JPvjyym8#AdzWU7?R~uRcHf^zD*IpU*5mfK9&~0AQ0nR9D(VO};OQ zJc^N6EL>E+fBQx*K9R#k>msOd^(PegdB;7n9DHtO%=Xci3vSQr8+hiBU;V&CYRbJ= z+9WMIVJr3Fg+1)u?(VZ39PU4R zW=>a+$;I zgNvTFE~btmT8;v?*(UGEk+*DDG(nWWUxr=jE_-W%OZMU(p*D{57uA#xR#RPI z)o{b6*O3#^!v(dSZsQhCnNu3*a7j`!)RE8e;>A$KNV+QkoxxUFgKpW~EUtKGd$`i- z=4~SnT6tQS*b=(mRg)*vSP*C~vVJ<94edV9PEeYD?%cW5)Yoi&Ek!Uf8TY1mBxvE& zGDEHGWDvk}PV_RY<|LxP1Y%F$F6YxKsZs*I|h5hbp{{Aq!6VF)-6&}8w! zW)>SaF`w|w=0YP(=JT1i(eRbZ5o|Houi7C7O_URAI~nC}!NM|_P<4`CRNe*tFg7CT zp+}D%5hERq21?#*6fr*tDkanLfS^ihEI2P1<4qr*6gGTingkvMI>=>r_u*{O3DC6C zQba?Lar7vXm+4D4(3-vpaP%KLFt!^#LoyQAu&{11WzTar>Nmfmb)ZC-m6P-K@_OTS zMezb-7Z4yrol8kcA*~~1KsqGVva2J`R)fpzabaO?;*tOm$#EN_?=BT@IRYt`{OT*< z5#5BKv`5ETpei-X0JV;MP(M(fiAh=ZTEY?*-NI2eO>9xum$8yM`Ir8v6xMoD2}B66)NG`X<7o(P>T799gb1(`G%Y}EL(Z5`_sko0 zJP**a*4g>>+qb+*+GN5FX9LZBd}1tEt95cU3LdD}p0f%#NkDhRyd?`~Q*@XlzaT{X z#0mX#ZU~)N90(AKyvw0aUYx9Y_>d;y{N>9}ii+xBKTy%_)m_4npIXoS-=9an9u5{c z$j}gc?I&Q;ZT}-&Vy_RQArOde#Tmt&Ajt2~T=pg=V_h#fNln&TPEG|{lN-e)2qWg6 zUtX#5aQ5zZ0%;RtV_^>DlmX&K8Fm^0(K~l)yGvleXPrwMBs@I>$bo#DDh$<-wA?C9 zO7OFRyo_^1?nt*90=HtGH+hkzK&; zYLoppuP{}OD1u_+nnw#HHD8Z5Z(%{eRtJaOKyuqAh^9{y(A_PBuY= zujNoBS%P$htu0TXt_AB_?|}mi2dwI(W)oWgGADLsX`IefFI1CAfzG&wd0!Z+JlXZ| zTFP{rw9}U^^$jhwx18ZH0iB$+b=s9HsHiTVKmPzE>8?QD>SS9VLIrLf`3xs(wNW(7 z58wv>GOQbYk=SIETu;s|n@&%{|K!VIh1-N01k6-AWUs*@y#d`=fQ{L1}srVImpCyJ}{CiGRh+hJ2e|Xz!##|Bzb7%bI#?-5{hg&uziphmRQKc z?DpOcj*hXrM@aM-qN@w&o*bj}x~`6ZLYQbE24OJDgT&tS5vv>s0@*%m0(#LJC})sZ zQZz8qismPp95!Iv(*yLdNni5Zm4ENf6LSQROt)dffM=H6xFL3DI13y`iUZ0jncIiI z@m85)2#B7dkzp7PM-yneSsNO^ayf6*Eyw3uCn| zw~hmef-?_JkpF5=xD^;Xp&(2yST#G)QvLqMzcDqJ_Sx~%fz(vOfGhA$@#9_^(py@> zbGEO{kNH8f2AgrBe{O2RjYA58cpqUFzqvV;XlQL4axo4L=X)}1d&!1@@b9zO((3SS z^r%t61C}~?Jv+Z5=hGU{xP#MvaBs%ilU4wkpeRJr7@75Z`;);*vtn#5V616h`EjwW z8f$a^OxsS%YfHG!efw19id5v@oyf`e&AdxZyDtCd1O&${IdB(V>zl*c64p2h(j0V& z)q^e*mcp(6RHo`Nnd#{q8XiQ+N9X$?0R{2quP#}VLix=d;@$86cOY$-?fZp*8gIw8MX4qyu}UI@p~xTubXMKAm+J9GWtMYS2u~6l$44Bz>ZzJ?mhqftv}9c z07$c02IJye{wg6MfnlAyMF?7iq@p@!UL5SC z5Dm!Be??O!Go!A>hszg*40dNsnxa*M(XE)8n8^0*DW$5h{re0|{At)(0gsI44>qV#>@Oc5H|0NlkY`#&? zY|HQOYpB|pUqmlUL&nM^Ba-gu(#4BspMIoIx%OjOw2ANiemQbz0-GJytzA17;gwnP z-o4)+IxQI!(DCHny;F#UI79^nV^S)XoL^3MK?3dW&IwV`(L`e@NZ@{N$C#NdU$f>QyxWwE1N-KIuyFeXs}`$}2g=XVJUfi8#?pvemKiM5_Y0!0XPk%VH%9zJ|S`5tXRot$IG05d`G z7N1|<>wcZ8daS)E7~uBp($XOzp`q9E@~FfBT&?pOp?QL`gY)t_zz1-QaGRm@@qwu@ zO8nm1N?n71NK|EoFsEV1j@@(LQ+SLrGZPu(7$4ZTaqh1jR2b;B>5w>zk#iP4f4(3$ zx?L4OD>FY|-M*c98xOidN?Z#s8p%gY28Spw9~5gpdGFAt=U4Pr96eUfzPGJL<ObB7sSA^w7vD$D7s^E_jmtgwt5&&V2%I8ayM?Oz~Eb$^PAA!3$v_;U3bxBuG3N z@2k;k=&sf;-7Zu)y>4lL9PS-uXo6Zl=<;u59o)EcPR<{3XD6r#fv#40^yiV?S5C@1 z)YRB$rIf|LxO>}8#i>lpqJievM?3n~LaqqzZTtJdx3-BMY{?g;-4`1@fj{DAu&Now z{16IX_G;=K))S#}NixX6J%wqP0L&u4oUuZn&@wmc`Q58zKa&haWhVfd^7@i73!5!j zyjb2ABl|DlaI0!+Hee?(CMgeg!&+|G;R7U9=9e|51-GUpwYv?u`K@`*ge21CSdc~{ z28;7lj?8B|+$-1~1a)CHgOsDfT-tFRa6PeU(XTR;DcA)K)hGjldxeFfA*oaM|5Z|2 z$`jx!6H>QfVUZ(wfUg@G1{)d*jh&=jzukEoED3WXDYi^@;Bo_RzW!GgesJL!C)BP` zILKsh8ZFno@R@TAM5lvVgL|s+!==E!|L2{2FB2qPT*t_(y*F+=mX#Hhm@W;x17I9g zLa(a1iIahP5vGVWR2&uv$WEdgy353>w_aCxoz(aUgOI{-m(Cm1g}jocgmm36pFcwa zRVh9}^=+p1`GVj0)J4E1vAToOq6e0m?tOLmtLBE><*P;I#h@S>yFEht z^285a-{ti;oZ1`uSAfKbYaYd)dgJ5p!oq0$>^R~vXCsquh8~B=kB%R5CbGm;yJ}$M zNNQlit$jL@| zO?rX(9T(fXEpT?4hWyM6v!*-s>wAzccgr8Y8<(UJ=$WuhLsfJ3J=bT~|7iOzUFkI_adcNxFaNruzV!na z`~EpG%Ld)d*>@`+8cQvzs1o0)WEc^%eA4$hFoOXo#=>kUD2CuMc)F9!Y*1i!#<5w) zX&sWM1g!gGZQW1v@WRCZ1SI%}`vm{10%uv9pOb?0HaKgVXEeIZ!XffAG*KBsjFJHw z*TVQO*<0<);@$qgSK#Q)6v*B(b8lPcy0+?9gZrlUz1BA@TQ%y#8^cE%ayp9Qid=1` zN`JkR@VWA5_piCnMtLsY_I=8@l3ovfJi1Jv3;rjJe}teupwC^nP|kk|-6)MXXgQki zruzDCW3O%tF;OmMR?66emLpGm9VK;W(w+@fCHu2(xfnb2p4|MpTS10hYG(B9U$bt{ z{xU36y`K#Ak?>Z@&PMYE04E`;0V@L|(hQ@uu6&V$!z)?0Z!1-lc2vFpHEnF|{U>7P zeU*0z7I_>HmH9ES{~On{CY_NT5|enZ1Eef;YhWyYSjrM{Si`F?X9GBO=rOh7w?cE=pF(u zXKrtu#u?kRpP8yb`~AQn4o;Jj!WS0hpi3O=FUG`CPYsgxg*!kzh@(c5haqZa%j{f;@3?LG1FZHR6db(=xfHN4d8Q#Fji%gzP?-(KjuqVe=H{%1s^E(MWyJ5trn%>=}igj zq$?9K3CkM}z4O@UvO1<1XjKmccJYJSLcc(ZZJjNDWowdo$#jhdTFl~0 zVGIaUEch$hw)ZVtu|eow!-mq`)F>G;yp21S*KeThR|2>~gXIeh%WTXG&D!JR5G(x?}N zLZRjE>z};y(A97@+*MG}#j>JuE5-45j6yg75&~g8WCg^aG+mJRbxa) z-Wzz8V)x>uOFh5EIxdcp9zK-kIVU;4Kz0Gj?2`$2Lh8ve zx^;A1`3qQgA7A*+w6wH01Ca^cO`UI&tfr{w#SBHy{F5i|lBh!qrg#Cwikh>uxLEF+ z&tSc*gdIGR0|yV%$LlFbGr@7^1OkCY5;=Crf5W5)MPMjn{NMX91@4#mRz!xSTtr_?t#CLKI@x8dt}jb14gBr%&5{GhtTi}k%%DP*;rj&=?hgcv3wA| ze8{>z!ZET8(o>zN4dTK<)JIyPXf6u-rEolLKLMqL)Tw5sDG<_4GaV(_F%}lQufdMt zh0fs9y!@T%c@jL2S$ea7bpVKU^2mk&;1Lg#DE^E12`$+EqH*Z~tKA0Yf0$eTp8CVM^T zVa_CL|IZ%GfO`Juk)(<1_OCQ|G>e1u?253$#YkLmHB$MH8UcIbYb$>CIt%Gcn@c9c z(ag-A3FnZO`Ec?pTA#*ghk8P>-~HIngXH({fmu1X@75r_anDo3U-@nuZ zBB}Sx!;zR8FhL{s;<}oAm!fd&(rH68uU-37J>4?F>{w=IL{TlA_9_11z&4k5Ku+jS z7%@Uhw)-wF2>2tk;@&#|HOLRD3uW1`H!iN+ zbHqpx+ys69)L50`bCIx0`|+k>3$aB*ZmDYYa<5o4SOCRuw{#S%-~8F=@3l1 z9&T>p({>ELksH{R59Y~Dz^uXR$>5U;H;|+7#0^hXg$EVYGIHCi2`YlF>*I$HxR9sW z8xPl>`R`f)`{wW8l?~PU^&`o!wxQt%06I}PL?bl~sVEa!nUQn7u7#VNpWoj+y(EOnlQ_Wl)0Pqo3a+M z2mxU5DoA8%vhtA4ct?;@uN5sNJr!ho44cu!o@ zxtk~VeGY=V>cl?tjmIzF$uy&$=BTB^Q;;Ac2zG^&lfg#$7YBiG964gIMTf}bv)IcJ zIhwKDllFh&`$6M?XNh+o zFZ1p6$PDzxS?|UvJb3yvIWbX66(8mTh>ab6^wH|?yfZDrYjyj_a~1UZLB)=~zUJ}~ z8`iJq>gJ2xG&O+|;cnozQL4{C1E|(+=xpQ5ciJ51Ou+ zF@1VWW@ZDXz3Ums&B1jr=2MK)aHAojgS5@+oXU()a)yil&fBB4;{fJZkV=GTGsZ{7 z$puJw{Ma$k@qOMNH{-Q(d@0G2QToR7GJ&kviy6a<(3T9cS$1}uqIL}10Gx2n^Q~^J z^8Z<%c=HT?3#>W_WhfeV$8iqJvW&ZT=YJ^{koogp`fS8|)%CJTb07tx*VEdVxHA)i zyKlRHW&X64BN9uMy**vzB$|4ypE=RX_^;wH_xhguyn1km>kl1jn{6r(II9esE*T2t zl!~Rg)z1F@^VCrsTPTxIy0ZYjoU_!`b#ssR%TtjSw^n$fwr|zZ z^T&g(Xmxd6UVC?hHj=`iB4>fUpr~8@02_e54 zoW_ED!Z85<;AAmVGcYxsrKMQk&`@N=(L7n8K*LzPXaW5GsKYg5awmRBiFbNrRV8y8BS$ht|8N9NcEgu!UZ*GW`Vf(N#Vq-iOKFXYb z%D2el*;c!@@822Jb{LIqWo56tafSruw>PJ8mU74J+tIharey9lRmrgQ^SSwP)6GmT z2Hgt_)3A5yx6JCspR(}FN!ig|scU=4NghViaOKK8Eyd)dBne5$fCH70x?P)e0wiU3 z|0H(NGs%Q!gF6Sv4krk=tbNFR?s?`^gv1A~aI2yiviCHY#@U3?bN^k&#W3C7x?`MGn2Ov|*iP!`>jMdt zOqi5Gbxh-`F#?W_6HY|t&G>#Yy=$Jo;!FAT{`BDk3@n9a%7|4&K=j*l;sf}!m^}Vsvuz>DDC#5T7p?1fWgW!T>xbeU{P0F+e=xQ z%Iq8nuBh89w4)a;V4CqHBnJRgAgwoS*r)gJiGSJ4Q09PuP8No?9#B2ao}jBQ*{ec{ zmBkjtn;k-_*XYgn@GQPLMZbYuhASLajUC#+~iz;pH8IZLZb8l>Aw_AV`9qscfLZ_kEUhW(xva-zRfGy z*W~?+aiVkTAv1L2T{J8vEE>LZ!nhD0B_$;YAuR(XiK1q} z|Lbq(XUbwaOZ6s&REu1h8>m9&1aET0)%{2UyGJ3 z85mwm7@>3sx^0xL87K5OZg3uq{R3rx_(Zb3R*284AS;20TMiybn5}t3=g^kmA%{)C z>oVzvQM6noM1#;p5n6Z|=Pcy5vf*NXlz+P&)RvF1oiG7E;Y9DThK3c-pN}T|h&o3x z1>k7>{B9`~4VuHynSe#$)8@_Xup5B%-~dn{_xrJ9<&0W;_r3qEX1hhPZ)>h+a$jeic%LLJ(m61$)YkvVm+u5Lp-oTYtQ7W=u4d$Jd2rcw)De21@{Q1k5 zzFk96q7o;tdQWie9~uhs+_i#VD#?N#o@TCskkTI(tUzBwl$5@q zA+hJwZ^F5Gl-L&PU1URvKP{xWpuC3FLL=DqqpLH;yJq*sArjWKz<@4?u$ewl!+X_^ z^MROoZKWc5Jz;V=Xrt(mbHKM~UU$A71n2ct=oV)d?XEx_D#E)TTY#vb&;T z!1YMfUX3^AC0Zk8h@_%h@DZPIBD4?(U#knbq8RrLSb=ioDGS6fc-O z+{lPOThd?aZoOBNcdoZnKe_Pp(>sjnV<(gx{9R+twT#bY+*bGa$6u`vFDUDdToxZU z1G9P2{Ge2~Hu%FT1SisnE5|tMZUP7W6EVoRhd4D|U0ho5vh)5_GT zAGfesuwXykIEb>&xgv0pqpsOxp_bMC{Pl~oM&R)eSzIDs+>3cSn4PazbG^dN_UXEb z;zsfz9X|nl{A$4>6UKuQrE9x}wZ?u$vpa6gt4P_E8R@Jo00k~Wv5BJO^w2&U;9KOf zXXOOvFzE}UZtM!7RuG(sCK9X9z+02R!(ExAwz_9{ElW2vZGo2JK_V!J2CAw)tF9Iw%DVve3K_T^Ww!%~eZ$d`S-o<}Y845#BHa`CW4lZC zc@9W&K0b3v5vj){`sOxV2^k3kfze{hh^J(BaSw;3c*Qui`7m|?)_qV^4W`mW1TYnzUQuB$J>V7&=^PR0tZP8z{!< zT7{4t@_|9p=Kk%cK}hZ-UEsZ0DYV@-EC_NIpq$00*1I=glX~PH>S~<8hz^9AGG^jM z%?CzpD6T8{=^sFNBXg$0o8H@YyD-C^8fw9U(PPFedgISCp{ciBc+=q@dyB~EfwE*C zBA?jZYuo|$Io?azUY;t4@JX?ob`|DGN>A@I_#aPfTO{l$n?O(C4 zva+(C#5fq1riX$^CM6BIkw{xwtU}faxC3|fsO2jx znLLYu%RHr3{Uxjw+}EuOgrmWXbBLktz+{k*v^evy<3%WWa?8 z!0Ch_=tHk73vKtU#>+r9I6_Mln#d8Jv8)&dFztXD{ub>@2Xt0;c4b^iSSJGrW^W0< zK5JpeeiYQZ3yDM~h+DKiZD2sV3(qNaesVpcd|>inX_xh1{sVf2+4qS0uOP3}PGral zLBn>f@|EoA)$q4{_RF)#N>%A@x^{X zDZ+pSLPDy1wFoC}u%Ho54dWmcd#w3)UrS?CQ}7McRL<+WXDj_jbjxEe zJri8~pDU7)kY*fjVnq{sRV9lf3^)>W3NsdkpNumoHLbg>#L~0b63f%_Yl) z{FLQ4nFfwdP=8LgLf>(!x6ag_s~Ai4vZ%>VG-d?1mz0DtYAhjPQ1wkVweVo^v01tW zYvN&7?ldP`BZeL)AKX>OTBxbxloIxDGs5;A)c)tHq|o z;-cTA%C<=ZCEDyg=D&%Doq4*N&f@<50T#3cdUdzi&jaW~iaMQaq0}Un(ImgVp2Msc%bNkGeuxVU4 zZ19DBlij+TUpA5qY1@AJnAR1Q{%>r~wn|OCB1tDjrJ*s0se*!2z4b*y3(1Rgzz(h; zUFAg4@psIONu;e}P1BZ{|B_B)ax$#Mm2?d((zS5|zWuw==5KH;pi=K#hhJz#!*Jmh z{3`H6?;zuOO=HY0O){fOm0ug}KB$}@sqOr=>(a?(NAwa$Io-DYEuP(8@@eLQ$ItZs z7GliV_w@cHqZsRAx1kT}`_3OyW}fB!qx*=!RlRozq(dxp^dA^YNjW?IQakyy+C6&O z7y19UwwhO~zN^cIDGnGg?Cb2Sv|ZlbrYqA)I+?H{<3!ZrTP{XU@0wJyZVtKacJZa_ z!DF=scic`(F23tCQFiXQJqs6a`%Cgh_B8{|z3*Q-GL-RfC8INF5=rZln+CK+_%#yy zJZPDNYO745a?*&}@;%gVJ!b)537 zoVvwCcE-h4HW&1b{5mY5V%y5L*70Lkw3V*4F7nzcp77(xtxV|fbLZm9>JYXw3zS^F z9k%Hm9sVleWKn?DLzP@!rO%64)^^oyMo6+uq~{A9!}c3RB^Gm?U!$6UhkqZ}RmpPw;<*j}fYGeV^(WLPCvxR!a z%a@C8*yUh-2S5UbO7zS^(arcvyH1dfO-yOvzkco77_$wezEa#rUj9ougmZ|p{mJT! z!20M0-wYJfD|R-bH$HpeLbuK&L|3!)Mo=e{3vmqO;2qo$bYjtHr?lz2zN7(@0VsGa}`U*6gWmm`7l&+dr$z>zfpWNynj!InOhWr~b z$zUr5h2sK63DJ2dsf^O|;rE=h(d;Ikk6&R~(Sp;AELhb^KmF# z`}KSJ>Qw;KGo(Yv0islIs;k3n_y&Uok4ahLD~40Ja$GCa!GS;P`+6`geF*DMIjThO z9+gCitMRAJXs1YL4uIPhe0_`!w6kN2cig&oz%61bF_w>yQU<=z($RT!WrNhl?E>k8 z(;im~!MEet*%B+{=$5I%ZcU@4qIy0wWY6}rJ0A+3b>bRoEhZ|DLpek+tD4DzXHdUT zbld*uLfYz30BZS&fC_S0QIX+dPrSY8;_7}1roWIS5?#%WBk_GmxIov$q_v>yk6~8O z{{7D>$E{8+#YOW>L>LYU0S>EQIrkDUq3*ihCzvp;uFk^igs4PRKHkpmMD#0y%&3Q$ zJSe9u!yqs4v2qHbpU6vsc105pvwX+hJ9lVq3Of#Pcu>wynId;uwBNZWdDm0wzpa~` z0-QDixMPH@2(iI(va-JKl&{H2y2Z=ItoK`bX6|yu7!Bo6=^GP=D~yA%nQ`yldBH-@|KF@$~7uEdwkQCeg6JKPasdUU@2* zUltmQD`HH$FkBLWl632g$LZH3ogHOiBbm0uw`MRAFGo`DfuElr3p?1Fj}2hO4>$Jh zmS|a95%5LCt}6Ay2rN^?$KQB0cNRtv9XixKj= zbA>CF_4^I$&=gyJ>SlUTnA*B(g=4M6_-AC~SPZZbEfFUc}^yeJ0v z7vV1PobcNjc{{#rnuHkhr4#nP z?^_Rm)>9L3BJGNbnz=<;G4jZ%eTK32_os=*l`Bb0a3h=3zK94JEYxVU_cv!HqAKP8=jHfWMt9*(lU^v zwjLsC@)0&^sevR|a#6fP%m}cXoc!(k_mEZ^FlSstFywVS*wZIV3p&5$kdyt|KmO5$ z^mNh{PZ7_{3*zt4#onq&Cu8u_vk_r7!ArBab8y&r?YuXq zbIxNH+v_T&Yi3~gRak_419;+=;$RW6U}x&eWy_@U^WTFBI9qANmVSEr=#fz1bM_0CKyXDx!kP7^nf!8Rn?}GEyMVG6FUT|%MgUy@XlJHYS^$fK0b4Q&ZFSG>F$M3KI`HX9drcXk!}+|E;Ul&K;Sk)Z!qwAmqcAx@P!No zhnx%tUFnePMsn{Q%iUXrK5!oD-#OHJh=LSl9FA@Olk@LdfL~i=4iTyVc}(ZU7h?!% zqcG6%&YdYZ_ZbvtWk6pr-FWFCUL!_;tU5>=8{k9rA ztG1N-oN;#C5o}ypSel`t(tE(rNybJx=5ym+ou_!8)-Ac7)c9}zce;|eTYBjDA(ExG z!ln4y>k3)bb=}>Qr<5)|x5eVs*P3qD3XF|yJ^8R#R0$~y$rGz=(stindcrB8s5UDn za$t%2qFtkBY+WopMG0{QAAQQWDm}KR`@CQNu1w`*G{$O=4b&U9Na>Wdp1wUii$)Pkm&&CEnf{74@~P zO^%@VD_$9;vcEBTC(JDfxMr^<%dcbd!OEG77J(LwfqM=Z#e6yBN9}Em-i&*{+^QLf z67%}7ofCkQ-;lx;)+wV{qTv;d|AV7o~bQdKD%MD3m$>O2P z5!<$@kqawo<8>HTRM5!Fjt1fWM$E0B0~$+MfZ3yB0}nyZ4e^sux;BY_;sq_5;JBLj+__< zKq*uul}vPVBTFrPicWv+NxV-grw6x*@}ZS9y?Ed~Va10(aiITbX%VVQW((st5Z5m- z+U1_YMG9?Y`E?T~E2e_?^=J=-BQ9*xNUePa5nH-Sn1~+|a%9FauiCE89|hPPLfuR! ziFW;ZTz~{ZG@Yh#1Nsl9wOLi{D0y?~l`q&zQ;+Bws~5`7V}|zr{Wyk?#S`Am*ryY3 z!=fd2PI!LCP0Ha%KN-}Jm6f8KiXGk&F@hLc_3(BMb13xJq==DVS~W9)#&`Y3joqqb zHJ{>VWY#VTrr`BMZeNIwCFix1gMoVsatBsUboIHFmYbPY1I;1vRROT!@XUn?O;? zNeTbMMxtf{kCYs;lIBv|pv!fBb+OkSi`#;Ph| zjl5cz(3B8YSEF`XH!SGEB4&Vq)d8v`vM>%D_}J7mB=L4a!3B0Sr#Y7jJMj4N9p&y~ zymWOmSPCUtYezi$`m=+Z8E_7iq`QO=yQZEnQISiK9j9=f z$)B@U&fTJk0?8bnojYXnt1VLTgkFUBdsB4b_TPD9Ig|-r=rL3tn;2~hbp;~J3_ZkT z9(7bXj7TJ|7!G2rPqToQ;M(o4I!H;@;fDeY`3DbS_X}-arKRuq23Z}Csl{OG7!7j< zp{+F0nmeehdRXtidiA%xBMxt{MNwhU&-KJZHfh-lE`0_XXL<#a{JAzZA>32M=xNX>Xsa z5o<<;Pj(Zf{t2fiB_-pveFV~=HVkXvO5eF-M=l zCzY3P`SEh|!@CYm*Bq0~H@*i;8gU5UDr^oG7s+Q!h^nLS^IqQfh0{PzYb)@X-@6xX4o>EYz_S8{~#6Wy~bKi)f zPcN^MM%{vn&^c8)eni<;ikzbqRGu&XPFWOLD^VK3Hkz$GPIZM#5mMKKq};o=f@ETA zPfSdNPd;^O!qr&BblfwnU(KiD#-9;71kXD_=rVX*aBYe?6C*lCr;hs#4anjHbNf4L zJ(E{_Yzf3MU^McvO-4z!3R=vCCq|xx9kt&P%6JX~r$0%m0P1MzS z4NQCMVfKyVeEL39VJH+Y8Pc+%AXkk3r22{YKiVFi&j3|bR+|~)3RXQf@Yb6fh6O3L4166`#dLtUiVC~# z{I~i;I1_l2uxKS6h~dJ^r!mWs0STC@AWcDrq~ETxpHoDkGqCfDciQE!) z#)xpJ(k%cXxSPzYvu#u%lR2*x)^>c2xh}kzK#}yOs>b~Tdi3mxY!6S1u9M`+BWK0_ z=ze#$cIENfCv0)F*HSb{>^}Bd!DoXULlEl9+t2o}*Ph6r1&1?LGKg&)ZWA-tX22 zjq}%O8vA-u$##uZvGa<(dz`FZV#hJed8;eQ!(+6P_9JQWx+^LTA05k5KVH+gr(|-f zbF2Oul|#0K+_-0*7+>1;&Y`E&$h>tPD}oj{-hZy`bL{F5d`ox%m-|2DUFM=)9%^gc zxj1_Gq>+ohREO8z@KBDM_)OBg`QDD;4LffniQNlozoc$B&uNwHysOT~M#&Yf&rtO` z)@hEyg4k?2AJBvq&dwsTWQ_pjKR&qt$secd;-y=Sj1I-Ezt?g@LC#tsNo~GsPCZ07Qs_DpS=o;jCqTzSw@7FAroOS0j{R7q{wt|^-)7i4eQ`OUrR zwqJd9O-)U8wWeYu01sCjSCLH`Lwjr#v;+c#9Kv^MxQ2rIb#&nOM!QGN=?B#HH9p&H zOU~5p)R;N9{e=C3?(&y3hwe}78ooerCQr~xcJIE5l-B5LzDtJE_aLvW6Y|bBHV{(R z-;Cz;L7LR3Z(o`wATAd$O0GdAFOh&VC;{IqzWFMP<1Q@;BFR zo~-`WeU9Yi7lBE4FZy=-J)63ccnCze=qG|UH_|~vOfxXBCo||aMVGbvwd9>pJ>oU~ zU1-|y9}+|JqlekPmAzRdGBP>(_w}%L^}8ZnlM+X87NJ`L^A8hxIs2-qbu3*v*tO&3 zmOq;}a0Dnw>^#42a4NbIns`{JDLPD;k0QaQl_ME9chv2AYF=bhNEueX7)XV1=fw-P zsb72t9pV>)qwK#c^pMENh$d$VRZzUVxkDvy55KNWRe{=xhKA$l)~Ys+b5AVNpjxV` z$InWG-Rv!QPYMs^okP z!MyuX=<>~i?kr(#Z~<0M&380dl7E2opTm{dn%v#o;*yeli0ODRC`90GzKD!6Onzt2 zDjqQw`j6t{bw^WCLXY?!wbv-^s+&DIH_sP@_A;kw!VM1az>bzOjo9UFtC(+!PAfSs zPW-R(0LMd5SAVCiz1~9hlrVPvfr@a1`tAY1=FGg7dq0ZoUfmyC+bZp>CQi`=-+TWr zI<>4Il}Q*?P~gRPtEQ$E3P`nH#t%TA1h=fOw1XWEH6o;i`iI%H01VMdta+I``|vHL zQv1BlHBH0uPLr*iH9_<>#uXrHU*C$PgY@`^-F>2;505IbiSt1zhM_Y)EJQ&C)q8&s0^Y0EwyM z2WQ7MwTdh&2`dbch$0+6!$l)_BDkD`ZT3*db3iRzl4KV1_WAZym;X<5XC9aHp7;N> zNsp{d z_4m(hdv+i!tc&I5P8&QKw0?)ecz04j9wbH=ymt4-ktm#At{vmu-Zb+DX#)k|u530$ z7n1tPTaafRzQJ$#GULeT=R8nBpQ`x<%d+ z#eXES-hk;JB9Ham*!U`}JtM*DFf}>tX##9=%VEf0PH%vnrD)p=L`)7$sb{IUE*Fuw zRjMz&amdFRpy-mYITT_tDD}kKnL~P>otr1pk&z4}gasVM{Si}JF;6$~sc0DhUkl$& z48NCuq`5mqH5rJMctFw|a`u}i^AlC}_>LzHCUsx zQZ9lifTFsU9bVg(sexaC>FIg^0m4YLB)841 za-2XP(TzwM19J2XyH?#~bteWSP3^+FSFgm{wfzt9S(6+Do2IYNNoG{{g%2M#A!w;k zci^f^NO7uPzb1;LXXq1Aa-h0kJ2E?Lo>C>ASV$8S_`l`FZ zcY$=?AAn;21`uYLiI*k|SqqxZsfQ`bS^oLbGVYK*eS)gU6+&hVYc1O+G9K`9?t>%8 z#3%8Vb|009321FpBHq4x=MAhC^&O5!S65fxx7Bq>FpwBe1j+!mUrtrtv1S{}9}uq4 zC$JokFkxjxrt;UPZZ;H%_>HlQKS*@I;C#}W^4Tf_1SXcbb6ZhuC1rMn#no!}b=-m{Z=sx6kRBD*jIEsC6sIl{&rv zxbMxI>u=oik6i%T@O%TZuii5ZObO|kLQG#~dLn+wscGv9_;(n$mSut^K`$6v?!9X! zlu(e&7e#A<;bFPc5UY`q0xk0wChW*0ESN^@{C7dHWG4_|!$tP0L@qQox_@rO2ifVT zy*w4HrmM3jfQ@Kqm}&0TRQ^%t-?4}_TGupDsXpDmeGR&LHFH2ncB0MXNi?6d)!I^< zb^+hU^E{2~g~{ZGC1+)M(@|&cjhANg=F8>|H_fQL<>jOa#cD0E=ZqPh%X>_V1s}`@ zcnu#Ui`1z?@xQ}g&Z*^~6h^CC%O8V2&M|0@0ae+(yZ69>>G$4(TyRQj?r0PXp&mX= z(h>wq7HD6JIBuf}<7VgJaL8hq6yL~DZa(R3(qzQ>y#hHyZzPit zVf+2aHz$jiU$hZaG*oOePbM@qeeq>@nHGRL!|3VEn=!j}sn_kko?H27rIF@t#cth( zyBzMc>t>!CrZFZ7wl<@}XTmjHE+Z!#5Vs0MDZtTXH*SIwQIv-wKmq=vz0ThSId9TU zT&C7@R-8L?h5#VV)fRF=NQ)67B{nL%XC1`ZuMxvf$FdR3WybXBIkC6YZ5w+?8%09s z{V}k$;R>p8R!StxJiKUW5uxugU=YRZp;AG(ug(6o=qS;U5&Fj6CI-w%;C6#NL^S#c z9^#ED;~cLqw+L$bN$#(=HAYb7kpJ_*)h8`AN(;A}Ccdk!1xDw~>;OTWghI=*K{XC6 zMl6ius&x)gms@zzRb6xZ$?P8U2}2_fi#>XBUAB}t(3jCvQnu6h$lJyyDJX8G8O(SN zkIdq1FSnH|Zz8)Vdq{j_Fsr=huwa*D%0(F|r=Va{*O=V}G`OFa{#5V~7*x<>pd)_3 z=QA?A+nR@KcATSIfoB-kiP(1^#N~U0Aslw_)2FG{F%*CF`qS)yDnPUPNQ79pUsygV48ncs{y85J7LiqQ~o>#Q|NmXkHRJ|eeQTyg=?Pba@)hg zmTC)`_>pf8Y){3Ohz#A6CQvKQ50#Zidg=?Lxi(_<>|uN}ojzR*lII7>sJ$c`%uwbe zuHv<8$upcc%D1gK)kbrANYs4Pipt7>7UPzmKpsAXNjp8gj!yw>L)KuZi<)iBaW;gu zicK$!1g{B{9~KSt{oL=%V$ZQQJ}yc}XUF-#b4MIwbd{2nLIlL*iDg@A zwjXj>MlDpEo6r%`uGaJwx&M?Cx3Mc>h!-lzqDX$_3Zhts5ll{+F9lNP3~k>HUd*Iq z92h@6LFU%XriKRZqfX`QO{G|2X;o|sPTpaTlpH^qg<*;Y%Z;^>hhRj&h!N9H?b zUsGA?sIOdgaLRjOOwNL9vBrhJ=DH+zD~R5vD;K2TdG2QGi~jFly$t=5rq#`LguKsX z>Cv8F2FM0yfJjB8KLM0QSR5FlQTSqqo9VlwwSG^u?JwST()sDBVdv*|V_Z*J3FqeZ z6?8nR8d|%0miiiHt?^scbx$=XzqPK&^*?;w3BAK8s%i7`B$QNyLX0=37G3z|x)U2y z&h0iwNBuul9jppb2RhZBboQEqzu%Zm%X-Xs?AT>D5zI7CsGA*K+NEfT+VLE4 z?@_6c|A(lQKRCtcOmm^Iq+ml%SgP~PJ+E6lAMN&dO`f)Hq;R!rWW}dF`fqQY zRB+Ae&_1y4vwX`5SbHsGq3P*g>gy%yO7ZRxN?7&j(*S`$yJ*)qh43ynn@+8cEb1NG zJ@oFgUZ-1i&I_VOsLhmmCeryNrChmroYeObiy!5^a>ZuPRS&WZnMLl>f!Mfh4V$Vz zzqmutmyuxH{VQ<+ygJ}lgo|V6Tk}XuFHW%tqRWFzf7D*S1Q!HvwY4YQ{Lc~(!X`E@ zr#8fkJcGvCGs%C^0x*EBo$&Pe^Qi>+zK(ClYR9hZlp)kAF&zEwa>ydFsc93u z6of8iQ{pRpb=wP3obDgV@x zY_A?c@n8|qjcu*1S`OK*SOOsBW%d9W((sDo)F>T8`mTaHYE~fCXDpk?O`K?}b+rAL zLx-LN+t6GjCHg^r4QS=g^^Fnkj4hZb>HZ@@wqL&kxUyg#;uU*H6U2-VA07eNQ!OF^ z`+Am>;fy2X*TfDA6L#!eF!EjFdu+9@n7?e~8REn;>>VaY?xp9rRhhDJ&z`MVKIGeU z@8y|~ad$}FARL`yW(jL&4Yn-9jb;?l5$RM??awWvvKWqf{$f3?p+l$V)T8ZVW-=@? zn;ygzkD2$BU8bp>b{YY?v_Bc%pY?btOogv+wsAsAYAX2_&q+)?-RVgPfe&O)pD^7$ zJUq&){ptM`1Q_8uvWk$*gXKg<+1z|+%;Y2 z0SM1CmllLZy)9tqLrngGI?%Sc64?GOUA{~f5eyc)R?%Z_hDnnrk4Uc~3`SbgD5qDa z0_`W`ONo?rbwz^M*&hpX0iBvg4(}J9EtAkPS05CG5`vlVHqJzxWdVUcE-vW|ZrUk? z75w%K7p%{uLso%uNaC+pYDgzO<0?W*v%TOIN7~d(Nv7|?5y{B1!D;wasFGTQ!zk5FdWw$Jml;DL*NRtp&)uYYr2@1+>fP!ZVdp z6Qzd|k6uN4xdLr&9S6B{T=e3)qYExe{QPoI{5ED%>$l|c##srL@wr{`@MdQAiW4<{ z{yeYyX4f?o{DP1$pJtrx5WfHS#aH9Swur!IZ_T=gtX`Tp^qR~+3wT|IL&A`wXJ5bn%@KtEKH0UzKT$~{1qJk&SMHKtN&I? z6`jm+sG$TivD3xyv;Lv{b5KMp|1>wjlI-e{BhwaKyxHSFrlH!|e5zkX|BtaPiQk|B zlkbbQoCEq+J{ndg zWb~k$Qg3Ef+(C@!mX6GiWvw~6T!>y?x)c;?C?N@Dfkx9dWkmF?A5@-TO;U=uAN66z zm*C3k>P;YCuU>Tel_&(_cPrhqUOrfK+A;7_W3aKfZw1Cd?6@pa(1|^H`gG}?JKcH* ziEXrsT;P@(jME&E03~j2W44LQ)gap-q%OGV7sI;`y|9jQIvh*dL zhvH~oc(z6i3`Up?B2&=zqU$S-!}YcDAU6L1ObXB)_>Z(y-&Y4T+eB7vggz5nth^6S z+SRVHrSK}|EPs@H$irbuT3fv$CZK;ojALmRlglhb{rj>&p%Rj1t%0dFm&k}mAP4jf zsA5jY=W~m+`CN^>%`hzd1z58Q2%N1Z2xx@OfvCDFIxjkYF;_J6>=Pz7{H~&|j7Fs9xg$26kjr+13vGI(|sX#+*-nn`6u8VX6 zU+-kM<#ji`z=OMc*a2LNQD*j6e7zZs@iBK?S6;$k)QFN|{MfN`v#gL-f(3E$F!4~@ zb;NgYY3~I;j%q5&m)d@Ok!ygMZMu>2#d#M7rrPIR6-bXH8DY1|{O5AKDR*z*KFJ~* zx9NGOpAB}WB-%&iac=U&69x`0pLLIEj;Mj1 zH_+Nh76KPtyOzZGi4^Ft{||4%@PtEM*lElw^c=M8?_Ryf4l z%j;X7x*SVNl8F6IY6d@0;-k@|&NYXp5cd}_vJZZ|)B4me=$ktfYf86}5XgsYC^Jk~ zw;Vqu=-+!~sBi)*S(8*X($wX4?cKYn@>&>w{+IS?xI+I77d4i)&#J;vf$tb8iKcPd zY*-YQ0v0X!V=w(fV0gH4@>5aP_LTb>kRAAuMF+%aZ!s`k;eu!e1&qrOOJJ+$c@Qq8 z+hxigY$LM{!-zQ6j$M=F?dm2p?mqKl`2hp1QBFE2iU=DL!>4XuOK%Iu3jbRL*eIatN(y{3C4TNMgkuk9eE=ews#H+{AMALW~`g1p#+Yl zl8D}yY99Qr_a;tO88BdgP{=tdbY5n9rXzDp;Z~DDuf+G|x7tsQDoyrQZHQZTzbLLO zcGZ5pI@CoqvSFydMeVGy5AWJJyD>L+b6Z&b_6aC3Svi+*vhX`|y{7hSR|r zv%{nFO*dJ0oPO4PLNQAcN5_g?pQKLNglnAX6Fc3atW>Yv6E{AM(7!kdguCN*`0$qs+x%-A zc6pvV8hNhkp8W-a{tJ$KTNN+4#xr22^@zg;MjyeFVM1V_kx2BK-+=) z;JB+$y7@F~#9GXEFbobH)6lp2;#p_Q2~|%=ZQkAz;lFTKef{Cc>>-A<%GvNE=B?O3FzGF`hN{oDbS^ zw{^wNEuwCti{51nsvKpq&wF9zXq}Ld-0f%2y>y*?^-SM3#Asvq040+gQ@?&6UQ&MI z%XMozJ8_Z@0jA@J3`t5$>!x*upo7B`)-7a&2OCEASWP9Z&WWD`8}#+d4(!uAFZyZy zoMRHP=8q073%fA(;iI60N1YyjTz|{?(Cs3Lz2CZLKAxZMm|MiGMz;=O1-xGMia8w2 z3<4-70BqA;qkAiayVd{yBwV_8zs-4qLxQDf*)mqWz>kKgvQIhW;N-_+b#J_nN>?km z6q1(OR!|~3bLI}_4d_G-qA|&yvAPu;tX9LB15)lg^#ubSi~Zt(lKro0uN8?*jy=ft zxy^COaEnd@lgGnpSb138Vbb*4Dm4|BIduHm0@TxYiV5H@rCOZPq(iQxII2(lG=T@= zW9nd*lP<}P!265(<^AJ%l*E$^hMM~;ve6|ctGOy)xn4we%s&NuqX1-U8{dv))bMRO;mG{dYlgkhzy&pX)~w`vD=Bb3lacY0 zg$1d-ithzR0=+mQM=7wUjIPUlA$~oEwU{_2G$cw><_>{sEb~9tK9=OdFbt`-wnAy6 zl%rN0!89V0;FNmo%hQK@0qWZrI>dta!Ry{mUXNR|!hX~yd>8_0dK0#wW5V(lKj~vl z4N-~H4%`5ZV|AFc;cbBZ9j-w(oz+@4S=WgMh`G>?LXIa5UR~S{49NvdGtab+Ye?Jz zg&=&j1yd5CD2?PdTxokhB7U$lY{h;jgeF*WwGFBn(s1+l>Ls<-uuyBl==v4je}oW~ z8me?5)6hKh#>FX=9*zr}0rO9ni=50)$@f{kP;AdmOioK1x$^$_K2xLNjd|uF{9`TN zQr7I=!rj2kR1~y|i2`ZxDxq z@<|#q>tt^362DJ(kYl9e1Qeb}x<;t?lqsqn10@t>rLq;Gi^VlH&+RJGi$ZVJ58iHuoG zjTnT|r)^JC10>8A$PNm|Ey>4V$e)D9~z~Hz7;QX?Wpi1XR<}akuY?ov3>9M%OMO#vVxz zpFEL2GD1x)J|V%#u2P!bXOzzT$}3x-Djy=LfB^a86rgh6?;yKp3@Nby17=C^^v$ge zRTv}0^znrYUkF9#K_*sDtUy|L7X^~GdQyhqpX!YeRCN9oK_<&8##F7a3ewb=o~S^} z*1L*TaNY20A|1GDpe=!P;L_HD?gr96ng1$8)TKcCcu}{GKk(YA$=$b2F~&!Ml2rG3 zhVu+ka>U`kuLXjakidVdC%U#7NyuO8iA=oB`&P|qXiHx$>n~dmzZR_`d)qHqXdL_cqeSY`Um0$d82i>2wLjRNHuB|hZYQZbV(UD_@so2IC zs|5p>mY0-_rj6mwosX>U@!9C`WRA;c+qwu{6#2jO3=^>c5ezFX0T`Y8-5NoUP$)MW zkHH1_;^`GN*L@1SHsoEuaU=J=UbYi}GS)NC*7~K9;maBDuwPkfAgH)|*kE+@EwOO;0_bBv!787)VegomOF+x_ReD4Baj9nMb!B#0V$Z_i=MMp zh^vGl>rR_47`2y)`dNOy(VRKyXU+h0B--g>P0CQjBMwAk%?ACRSf|i^+;04>M~ET6c<|=mEvjun=D`# z~*cZ7S z?ioN?Ax%4BsUeVVXlcQrWu3L-%mw~!RHUgk@;_qClWjCV^W77SO>7hu>D7$MF__#w ze@-I^&>vwo!-Be8W0`MHVXq!qU*#mNH-Vstsq0Oj?DG;DD6V4!qy8Lf9w zwKE`$HPBVAbA?NQaAm*`l!+kit@qIZg9hs+!ov~dZ; z0Y9<&R)dT#dOm4wiv!MMxucVpXB{04D@cjb#A{x0c~7k88Bd#bL${u0@Sb7P?Y-pU z0|HOkXp(I&omdogO6Eba+Fq5M`Km-62}9A)%qaU&=oOLwL22_D zmcY?hS|!IF=Lqxg8}q#XFsB&AwFp5~YD^3acJk2C^P?fl@jB4AJ}@(LY`HZ_HE6U1 z2cMi?S^$B>Z66?eOrA*re>@Sm2!H`tNJ%xBqpI&krX9kG&Cr+(osNz3ANL!u^Mx23 zCA>h(Rv-`nUCvnLB^IWGh_Wj(p|e&;^t>-jA6jxEWl;3A>I2fLUqZC5d`J}ps^8qc z?ST2CUp&N`@s0DrKVv_t?AkSvRdisLDWBDBwboK`QQc%?JBTf{8$d7&^^pimK1~79 zB#SUT+4>!TK*vKN1rsN2GmNW3ul`@JXqR$t3S%d%d!j4(8FhzNpEZbm`1u3Y#+T;h zsd0Pq8>CaFrmph+oS)}4G`z`gxI#Wx)2)P|Ed*R-jo6`EBs()jS}w@LOY0#6|+G}k?ufb>^ApH?R_=RYii-7Q6GgdcUsREA3EY! zb$HJZ{p?ElYU?I}&&I68{4CdKqp+(6(G~YcuW@jm5Q41sX~OdddY+2uLO#renA?w}+jjs-{Qn*%Lb8#E0gD|I^*A%I@xjPpiaQ+kyVy z-H5*6g+P4c*kplZ=)XHo^vW%VF>ZPrE6JS9RP{^sVT@e2KbaWWoS!f(It%26L?B7$CyDx8Md%_{D&5d z1>S+CukcyOO1Oa>Puxe?=CN@jCRX;GuP2D%iM4b`X~^a5wRSa9yUiW#;b_ xSX>)0T2?kxr&q>}9f5%f3Vr^e<^QX}h~oE`*WP|m$#*94)AZRUr;Htg{tKbjB&+}c literal 57590 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�qq?`Xg|%8= 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 diff --git a/packages/itwin/tree-widget/src/e2e-tests/CategoriesTree.test.ts-snapshots/Categories-tree-Density-default-node-with-active-filtering-1-chromium-linux.png b/packages/itwin/tree-widget/src/e2e-tests/CategoriesTree.test.ts-snapshots/Categories-tree-Density-default-node-with-active-filtering-1-chromium-linux.png index a666ebe736bd3e74eeeeb8942f9530d2c112c556..2243fbec54566a130395474aab2fc9045de440e9 100644 GIT binary patch literal 60867 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 literal 60694 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⪅U&zwDr)Hdq6 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 RMwSq-=~HJ+IcMe^_;0Quy14)V diff --git a/packages/itwin/tree-widget/src/e2e-tests/CategoriesTree.test.ts-snapshots/Categories-tree-Density-enlarged-expanded-tree-node-1-chromium-linux.png b/packages/itwin/tree-widget/src/e2e-tests/CategoriesTree.test.ts-snapshots/Categories-tree-Density-enlarged-expanded-tree-node-1-chromium-linux.png index b096f3d9974953c260e4b9183fa81d4378e59ad4..a13b451387ec6c09a5f93c830aaa2e75ed4c59a0 100644 GIT binary patch literal 56582 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 diff --git a/packages/itwin/tree-widget/src/e2e-tests/CategoriesTree.test.ts-snapshots/Categories-tree-Density-enlarged-node-with-active-filtering-1-chromium-linux.png b/packages/itwin/tree-widget/src/e2e-tests/CategoriesTree.test.ts-snapshots/Categories-tree-Density-enlarged-node-with-active-filtering-1-chromium-linux.png index 9806d1856c0e3a0a9f25fb01df794c0aba306eb8..7bdb1b022f6c93f27f90e314283405e6a18e16f8 100644 GIT binary patch literal 58948 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 literal 58974 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�XaDGE+fi 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 Date: Wed, 5 Feb 2025 11:05:02 +0200 Subject: [PATCH 36/38] Create new CategoriesTreeIdsCache when imodel or viewport change --- .../components/trees/categories-tree/UseCategoriesTree.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx index d70636312..f244cd58d 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx @@ -55,9 +55,7 @@ export function useCategoriesTree({ filter, activeView, onCategoriesFiltered }: const viewType = activeView.view.is2d() ? "2d" : "3d"; const iModel = activeView.iModel; const getCategoriesTreeIdsCache = useCallback(() => { - if (!idsCacheRef.current) { - idsCacheRef.current = new CategoriesTreeIdsCache(createECSqlQueryExecutor(iModel), viewType); - } + idsCacheRef.current = new CategoriesTreeIdsCache(createECSqlQueryExecutor(iModel), viewType); return idsCacheRef.current; }, [viewType, iModel]); From 96a150fc3fea12ab524064bc946082a671079496 Mon Sep 17 00:00:00 2001 From: Jonas <100586436+JonasDov@users.noreply.github.com> Date: Wed, 5 Feb 2025 11:17:07 +0200 Subject: [PATCH 37/38] Use useMemo for creating idsCache --- .../categories-tree/UseCategoriesTree.tsx | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx index f244cd58d..aeff0f591 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx @@ -3,7 +3,7 @@ * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ -import { useCallback, useMemo, useRef, useState } from "react"; +import { useCallback, useMemo, useState } from "react"; import { SvgArchive, SvgLayers } from "@itwin/itwinui-icons-react"; import { Text } from "@itwin/itwinui-react"; import { createECSqlQueryExecutor } from "@itwin/presentation-core-interop"; @@ -50,19 +50,18 @@ interface UseCategoriesTreeResult { */ export function useCategoriesTree({ filter, activeView, onCategoriesFiltered }: UseCategoriesTreeProps): UseCategoriesTreeResult { const [filteringError, setFilteringError] = useState(); - const idsCacheRef = useRef(); const viewType = activeView.view.is2d() ? "2d" : "3d"; const iModel = activeView.iModel; - const getCategoriesTreeIdsCache = useCallback(() => { - idsCacheRef.current = new CategoriesTreeIdsCache(createECSqlQueryExecutor(iModel), viewType); - return idsCacheRef.current; + + const idsCache = useMemo(() => { + return new CategoriesTreeIdsCache(createECSqlQueryExecutor(iModel), viewType); }, [viewType, iModel]); const visibilityHandlerFactory = useCallback(() => { const visibilityHandler = new CategoriesVisibilityHandler({ viewport: activeView, - idsCache: getCategoriesTreeIdsCache(), + idsCache, }); return { getVisibilityStatus: async (node: HierarchyNode) => visibilityHandler.getVisibilityStatus(node), @@ -70,14 +69,14 @@ export function useCategoriesTree({ filter, activeView, onCategoriesFiltered }: onVisibilityChange: visibilityHandler.onVisibilityChange, dispose: () => visibilityHandler.dispose(), }; - }, [activeView, getCategoriesTreeIdsCache]); + }, [activeView, idsCache]); const { onFeatureUsed } = useTelemetryContext(); const getHierarchyDefinition = useCallback( (props) => { - return new CategoriesTreeDefinition({ ...props, viewType, idsCache: getCategoriesTreeIdsCache() }); + return new CategoriesTreeDefinition({ ...props, viewType, idsCache }); }, - [viewType, getCategoriesTreeIdsCache], + [viewType, idsCache], ); const getFilteredPaths = useMemo(() => { @@ -89,8 +88,8 @@ export function useCategoriesTree({ filter, activeView, onCategoriesFiltered }: return async ({ imodelAccess }) => { onFeatureUsed({ featureId: "filtering", reportInteraction: true }); try { - const paths = await CategoriesTreeDefinition.createInstanceKeyPaths({ imodelAccess, label: filter, viewType, idsCache: getCategoriesTreeIdsCache() }); - onCategoriesFiltered?.(await getCategoriesFromPaths(paths, getCategoriesTreeIdsCache())); + const paths = await CategoriesTreeDefinition.createInstanceKeyPaths({ imodelAccess, label: filter, viewType, idsCache }); + onCategoriesFiltered?.(await getCategoriesFromPaths(paths, idsCache)); return paths; } catch (e) { const newError = e instanceof FilterLimitExceededError ? "tooManyFilterMatches" : "unknownFilterError"; @@ -102,7 +101,7 @@ export function useCategoriesTree({ filter, activeView, onCategoriesFiltered }: return []; } }; - }, [filter, viewType, onFeatureUsed, onCategoriesFiltered, getCategoriesTreeIdsCache]); + }, [filter, viewType, onFeatureUsed, onCategoriesFiltered, idsCache]); return { categoriesTreeProps: { From e9ee67f37bc8d0bf24bcc55b8058884e35b2b2b4 Mon Sep 17 00:00:00 2001 From: Jonas <100586436+JonasDov@users.noreply.github.com> Date: Fri, 7 Feb 2025 16:41:35 +0200 Subject: [PATCH 38/38] Adress comments --- .../CategoriesVisibilityHandler.test.ts | 232 ++++-------------- .../trees/categories-tree/internal/Utils.ts | 13 +- .../internal/VisibilityValidation.ts | 32 +-- .../CategoriesTreeDefinition.ts | 79 +++--- .../categories-tree/UseCategoriesTree.tsx | 12 +- .../internal/CategoriesTreeIdsCache.ts | 21 +- .../internal/CategoriesVisibilityHandler.ts | 4 +- 7 files changed, 125 insertions(+), 268 deletions(-) diff --git a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts index f501e3448..9c23c176d 100644 --- a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts +++ b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/CategoriesVisibilityHandler.test.ts @@ -59,7 +59,7 @@ describe("CategoriesVisibilityHandler", () => { const imodelAccess = createIModelAccess(imodel); const idsCache = new CategoriesTreeIdsCache(imodelAccess, "3d"); - const viewport = await createViewportStub(idsCache, isVisibleOnInitialize); + const viewport = await createViewportStub({ idsCache, isVisibleOnInitialize }); return { imodelAccess, viewport, @@ -98,26 +98,24 @@ describe("CategoriesVisibilityHandler", () => { const isVisibleOnInitialize = false; it("by default everything is hidden", async function () { - const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const { imodel } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer" }); const definitionModel = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainer.id }); const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory", modelId: definitionModel.id }); insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); - const subCategory = insertSubCategory({ builder, parentCategoryId: category.id, codeValue: "subCategory", modelId: definitionModel.id }); - return { definitionContainer, category, subCategory }; + insertSubCategory({ builder, parentCategoryId: category.id, codeValue: "subCategory", modelId: definitionModel.id }); }); - const expectedIds = [keys.category.id, keys.definitionContainer.id, keys.subCategory.id]; + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; + await validateHierarchyVisibility({ provider, handler, viewport, - visibilityExpectations: {}, - expectedIds, - all: "hidden", + expectations: "all-hidden", }); }); describe("definitionContainers", () => { @@ -143,23 +141,15 @@ describe("CategoriesVisibilityHandler", () => { return { definitionContainerRoot, definitionContainerChild, directCategory, indirectCategory, indirectSubCategory }; }); - const expectedIds = [ - keys.definitionContainerRoot.id, - keys.definitionContainerChild.id, - keys.directCategory.id, - keys.indirectCategory.id, - keys.indirectSubCategory.id, - ]; using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerRoot.id), true); await validateHierarchyVisibility({ provider, handler, viewport, - visibilityExpectations: {}, - expectedIds, - all: "visible", + expectations: "all-visible", }); viewport.validateChangesCalls( [{ categoriesToChange: [keys.directCategory.id, keys.indirectCategory.id], isVisible: true, enableAllSubCategories: true }], @@ -200,23 +190,15 @@ describe("CategoriesVisibilityHandler", () => { }; }); - const expectedIds = [ - keys.definitionContainerRoot.id, - keys.definitionContainerChild.id, - keys.definitionContainerRoot2.id, - keys.category2.id, - keys.indirectCategory.id, - keys.indirectSubCategory.id, - keys.subCategory2.id, - ]; using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerRoot.id), true); await validateHierarchyVisibility({ provider, handler, viewport, - visibilityExpectations: { + expectations: { [keys.definitionContainerRoot2.id]: "hidden", [keys.definitionContainerRoot.id]: "visible", [keys.definitionContainerChild.id]: "visible", @@ -225,7 +207,6 @@ describe("CategoriesVisibilityHandler", () => { [keys.subCategory2.id]: "hidden", [keys.indirectSubCategory.id]: "visible", }, - expectedIds, }); viewport.validateChangesCalls([{ categoriesToChange: [keys.indirectCategory.id], isVisible: true, enableAllSubCategories: true }], []); }); @@ -246,22 +227,20 @@ describe("CategoriesVisibilityHandler", () => { return { definitionContainerRoot, definitionContainerChild, directCategory, indirectCategory }; }); - const expectedIds = [keys.definitionContainerRoot.id, keys.definitionContainerChild.id, keys.directCategory.id, keys.indirectCategory.id]; - using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerChild.id), true); await validateHierarchyVisibility({ provider, handler, viewport, - visibilityExpectations: { + expectations: { [keys.definitionContainerRoot.id]: "partial", [keys.definitionContainerChild.id]: "visible", [keys.directCategory.id]: "hidden", [keys.indirectCategory.id]: "visible", }, - expectedIds, }); viewport.validateChangesCalls([{ categoriesToChange: [keys.indirectCategory.id], isVisible: true, enableAllSubCategories: true }], []); }); @@ -284,29 +263,21 @@ describe("CategoriesVisibilityHandler", () => { return { definitionContainerRoot, definitionContainerChild, indirectCategory2, indirectCategory, definitionContainerChild2 }; }); - const expectedIds = [ - keys.definitionContainerRoot.id, - keys.definitionContainerChild.id, - keys.indirectCategory.id, - keys.definitionContainerChild2.id, - keys.indirectCategory2.id, - ]; - using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerChild.id), true); await validateHierarchyVisibility({ provider, handler, viewport, - visibilityExpectations: { + expectations: { [keys.definitionContainerRoot.id]: "partial", [keys.definitionContainerChild.id]: "visible", [keys.definitionContainerChild2.id]: "hidden", [keys.indirectCategory2.id]: "hidden", [keys.indirectCategory.id]: "visible", }, - expectedIds, }); viewport.validateChangesCalls([{ categoriesToChange: [keys.indirectCategory.id], isVisible: true, enableAllSubCategories: true }], []); }); @@ -330,18 +301,15 @@ describe("CategoriesVisibilityHandler", () => { return { definitionContainerRoot, definitionContainerChild, indirectCategory, indirectSubCategory }; }); - const expectedIds = [keys.definitionContainerRoot.id, keys.definitionContainerChild.id, keys.indirectCategory.id, keys.indirectSubCategory.id]; - using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerChild.id), true); await validateHierarchyVisibility({ provider, handler, viewport, - visibilityExpectations: {}, - expectedIds, - all: "visible", + expectations: "all-visible", }); viewport.validateChangesCalls([{ categoriesToChange: [keys.indirectCategory.id], isVisible: true, enableAllSubCategories: true }], []); }); @@ -362,17 +330,15 @@ describe("CategoriesVisibilityHandler", () => { return { category, subCategory }; }); - const expectedIds = [keys.category.id, keys.subCategory.id]; using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), true); await validateHierarchyVisibility({ provider, handler, viewport, - visibilityExpectations: {}, - expectedIds, - all: "visible", + expectations: "all-visible", }); viewport.validateChangesCalls([{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: true }], []); }); @@ -398,21 +364,20 @@ describe("CategoriesVisibilityHandler", () => { return { category, category2, subCategory, subCategory2 }; }); - const expectedIds = [keys.category.id, keys.category2.id, keys.subCategory.id, keys.subCategory2.id]; using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), true); await validateHierarchyVisibility({ provider, handler, viewport, - visibilityExpectations: { + expectations: { [keys.category2.id]: "hidden", [keys.category.id]: "visible", [keys.subCategory2.id]: "hidden", [keys.subCategory.id]: "visible", }, - expectedIds, }); viewport.validateChangesCalls([{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: true }], []); }); @@ -442,22 +407,21 @@ describe("CategoriesVisibilityHandler", () => { return { definitionContainer, category, category2, subCategory, subCategory2 }; }); - const expectedIds = [keys.definitionContainer.id, keys.category.id, keys.category2.id, keys.subCategory.id, keys.subCategory2.id]; using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), true); await validateHierarchyVisibility({ provider, handler, viewport, - visibilityExpectations: { + expectations: { [keys.definitionContainer.id]: "hidden", [keys.category2.id]: "hidden", [keys.category.id]: "visible", [keys.subCategory2.id]: "hidden", [keys.subCategory.id]: "visible", }, - expectedIds, }); viewport.validateChangesCalls([{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: true }], []); }); @@ -487,23 +451,21 @@ describe("CategoriesVisibilityHandler", () => { return { definitionContainerRoot, category, category2, subCategory, subCategory2 }; }); - const expectedIds = [keys.definitionContainerRoot.id, keys.category.id, keys.category2.id, keys.subCategory.id, keys.subCategory2.id]; - using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), true); await validateHierarchyVisibility({ provider, handler, viewport, - visibilityExpectations: { + expectations: { [keys.definitionContainerRoot.id]: "partial", [keys.category2.id]: "hidden", [keys.category.id]: "visible", [keys.subCategory2.id]: "hidden", [keys.subCategory.id]: "visible", }, - expectedIds, }); viewport.validateChangesCalls([{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: true }], []); }); @@ -530,29 +492,21 @@ describe("CategoriesVisibilityHandler", () => { return { definitionContainerRoot, definitionContainerChild, category, indirectCategory, subCategory }; }); - const expectedIds = [ - keys.definitionContainerRoot.id, - keys.definitionContainerChild.id, - keys.indirectCategory.id, - keys.category.id, - keys.subCategory.id, - ]; - using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createCategoryHierarchyNode(keys.category.id), true); await validateHierarchyVisibility({ provider, handler, viewport, - visibilityExpectations: { + expectations: { [keys.definitionContainerRoot.id]: "partial", [keys.definitionContainerChild.id]: "hidden", [keys.indirectCategory.id]: "hidden", [keys.category.id]: "visible", [keys.subCategory.id]: "visible", }, - expectedIds, }); viewport.validateChangesCalls([{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: true }], []); }); @@ -578,7 +532,6 @@ describe("CategoriesVisibilityHandler", () => { return { category, subCategory, subCategory2 }; }); - const expectedIds = [keys.category.id, keys.subCategory.id, keys.subCategory2.id]; using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; @@ -588,12 +541,11 @@ describe("CategoriesVisibilityHandler", () => { provider, handler, viewport, - visibilityExpectations: { + expectations: { [keys.category.id]: "partial", [keys.subCategory.id]: "visible", [keys.subCategory2.id]: "hidden", }, - expectedIds, }); viewport.validateChangesCalls( [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: false }], @@ -617,20 +569,19 @@ describe("CategoriesVisibilityHandler", () => { return { category, subCategory, category2 }; }); - const expectedIds = [keys.category.id, keys.subCategory.id, keys.category2.id]; using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createSubCategoryHierarchyNode(keys.subCategory.id, keys.category.id), true); await validateHierarchyVisibility({ provider, handler, viewport, - visibilityExpectations: { + expectations: { [keys.category2.id]: "hidden", [keys.category.id]: "partial", [keys.subCategory.id]: "visible", }, - expectedIds, }); viewport.validateChangesCalls( [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: false }], @@ -655,20 +606,19 @@ describe("CategoriesVisibilityHandler", () => { return { category, subCategory, definitionContainerRoot }; }); - const expectedIds = [keys.category.id, keys.subCategory.id, keys.definitionContainerRoot.id]; using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createSubCategoryHierarchyNode(keys.subCategory.id, keys.category.id), true); await validateHierarchyVisibility({ provider, handler, viewport, - visibilityExpectations: { + expectations: { [keys.definitionContainerRoot.id]: "partial", [keys.category.id]: "partial", [keys.subCategory.id]: "visible", }, - expectedIds, }); viewport.validateChangesCalls( [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: false }], @@ -700,28 +650,21 @@ describe("CategoriesVisibilityHandler", () => { return { category, subCategory, definitionContainerRoot, categoryOfDefinitionContainer, subCategoryOfDefinitionContainer }; }); - const expectedIds = [ - keys.category.id, - keys.subCategory.id, - keys.definitionContainerRoot.id, - keys.categoryOfDefinitionContainer.id, - keys.subCategoryOfDefinitionContainer.id, - ]; using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createSubCategoryHierarchyNode(keys.subCategory.id, keys.category.id), true); await validateHierarchyVisibility({ provider, handler, viewport, - visibilityExpectations: { + expectations: { [keys.definitionContainerRoot.id]: "hidden", [keys.categoryOfDefinitionContainer.id]: "hidden", [keys.subCategoryOfDefinitionContainer.id]: "hidden", [keys.category.id]: "partial", [keys.subCategory.id]: "visible", }, - expectedIds, }); viewport.validateChangesCalls( [{ categoriesToChange: [keys.category.id], isVisible: true, enableAllSubCategories: false }], @@ -735,26 +678,24 @@ describe("CategoriesVisibilityHandler", () => { const isVisibleOnInitialize = true; it("by default everything is visible", async function () { - const { imodel, ...keys } = await buildIModel(this, async (builder) => { + const { imodel } = await buildIModel(this, async (builder) => { const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" }); const definitionContainer = insertDefinitionContainer({ builder, codeValue: "DefinitionContainer" }); const definitionModel = insertSubModel({ builder, classFullName: "BisCore.DefinitionModel", modeledElementId: definitionContainer.id }); const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory", modelId: definitionModel.id }); insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id }); - const subCategory = insertSubCategory({ builder, parentCategoryId: category.id, codeValue: "subCategory", modelId: definitionModel.id }); - return { definitionContainer, category, subCategory }; + insertSubCategory({ builder, parentCategoryId: category.id, codeValue: "subCategory", modelId: definitionModel.id }); }); - const expectedIds = [keys.category.id, keys.definitionContainer.id, keys.subCategory.id]; + using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; + await validateHierarchyVisibility({ provider, handler, viewport, - visibilityExpectations: {}, - expectedIds, - all: "visible", + expectations: "all-visible", }); }); describe("definitionContainers", () => { @@ -780,13 +721,6 @@ describe("CategoriesVisibilityHandler", () => { return { definitionContainerRoot, definitionContainerChild, directCategory, indirectCategory, indirectSubCategory }; }); - const expectedIds = [ - keys.definitionContainerRoot.id, - keys.definitionContainerChild.id, - keys.directCategory.id, - keys.indirectCategory.id, - keys.indirectSubCategory.id, - ]; using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerRoot.id), false); @@ -794,9 +728,7 @@ describe("CategoriesVisibilityHandler", () => { provider, handler, viewport, - visibilityExpectations: {}, - expectedIds, - all: "hidden", + expectations: "all-hidden", }); viewport.validateChangesCalls( [{ categoriesToChange: [keys.directCategory.id, keys.indirectCategory.id], isVisible: false, enableAllSubCategories: false }], @@ -837,23 +769,15 @@ describe("CategoriesVisibilityHandler", () => { }; }); - const expectedIds = [ - keys.definitionContainerRoot.id, - keys.definitionContainerChild.id, - keys.definitionContainerRoot2.id, - keys.category2.id, - keys.indirectCategory.id, - keys.indirectSubCategory.id, - keys.subCategory2.id, - ]; using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; + await handler.changeVisibility(createDefinitionContainerHierarchyNode(keys.definitionContainerRoot.id), false); await validateHierarchyVisibility({ provider, handler, viewport, - visibilityExpectations: { + expectations: { [keys.definitionContainerRoot2.id]: "visible", [keys.definitionContainerRoot.id]: "hidden", [keys.definitionContainerChild.id]: "hidden", @@ -862,7 +786,6 @@ describe("CategoriesVisibilityHandler", () => { [keys.indirectSubCategory.id]: "hidden", [keys.subCategory2.id]: "visible", }, - expectedIds, }); viewport.validateChangesCalls([{ categoriesToChange: [keys.indirectCategory.id], isVisible: false, enableAllSubCategories: false }], []); }); @@ -883,8 +806,6 @@ describe("CategoriesVisibilityHandler", () => { return { definitionContainerRoot, definitionContainerChild, directCategory, indirectCategory }; }); - const expectedIds = [keys.definitionContainerRoot.id, keys.definitionContainerChild.id, keys.directCategory.id, keys.indirectCategory.id]; - using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; @@ -893,13 +814,12 @@ describe("CategoriesVisibilityHandler", () => { provider, handler, viewport, - visibilityExpectations: { + expectations: { [keys.definitionContainerRoot.id]: "partial", [keys.definitionContainerChild.id]: "hidden", [keys.indirectCategory.id]: "hidden", [keys.directCategory.id]: "visible", }, - expectedIds, }); viewport.validateChangesCalls([{ categoriesToChange: [keys.indirectCategory.id], isVisible: false, enableAllSubCategories: false }], []); }); @@ -922,14 +842,6 @@ describe("CategoriesVisibilityHandler", () => { return { definitionContainerRoot, definitionContainerChild, indirectCategory2, indirectCategory, definitionContainerChild2 }; }); - const expectedIds = [ - keys.definitionContainerRoot.id, - keys.definitionContainerChild.id, - keys.indirectCategory.id, - keys.definitionContainerChild2.id, - keys.indirectCategory2.id, - ]; - using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; @@ -938,14 +850,13 @@ describe("CategoriesVisibilityHandler", () => { provider, handler, viewport, - visibilityExpectations: { + expectations: { [keys.definitionContainerRoot.id]: "partial", [keys.definitionContainerChild.id]: "hidden", [keys.definitionContainerChild2.id]: "visible", [keys.indirectCategory.id]: "hidden", [keys.indirectCategory2.id]: "visible", }, - expectedIds, }); viewport.validateChangesCalls([{ categoriesToChange: [keys.indirectCategory.id], isVisible: false, enableAllSubCategories: false }], []); }); @@ -969,8 +880,6 @@ describe("CategoriesVisibilityHandler", () => { return { definitionContainerRoot, definitionContainerChild, indirectCategory, indirectSubCategory }; }); - const expectedIds = [keys.definitionContainerRoot.id, keys.definitionContainerChild.id, keys.indirectCategory.id, keys.indirectSubCategory.id]; - using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; @@ -979,9 +888,7 @@ describe("CategoriesVisibilityHandler", () => { provider, handler, viewport, - visibilityExpectations: {}, - expectedIds, - all: "hidden", + expectations: "all-hidden", }); viewport.validateChangesCalls([{ categoriesToChange: [keys.indirectCategory.id], isVisible: false, enableAllSubCategories: false }], []); }); @@ -1002,7 +909,6 @@ describe("CategoriesVisibilityHandler", () => { return { category, subCategory }; }); - const expectedIds = [keys.category.id, keys.subCategory.id]; using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; @@ -1011,9 +917,7 @@ describe("CategoriesVisibilityHandler", () => { provider, handler, viewport, - visibilityExpectations: {}, - expectedIds, - all: "hidden", + expectations: "all-hidden", }); viewport.validateChangesCalls([{ categoriesToChange: [keys.category.id], isVisible: false, enableAllSubCategories: false }], []); }); @@ -1039,7 +943,6 @@ describe("CategoriesVisibilityHandler", () => { return { category, category2, subCategory, subCategory2 }; }); - const expectedIds = [keys.category.id, keys.category2.id, keys.subCategory.id, keys.subCategory2.id]; using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; @@ -1048,13 +951,12 @@ describe("CategoriesVisibilityHandler", () => { provider, handler, viewport, - visibilityExpectations: { + expectations: { [keys.category.id]: "hidden", [keys.category2.id]: "visible", [keys.subCategory2.id]: "visible", [keys.subCategory.id]: "hidden", }, - expectedIds, }); viewport.validateChangesCalls([{ categoriesToChange: [keys.category.id], isVisible: false, enableAllSubCategories: false }], []); }); @@ -1084,7 +986,6 @@ describe("CategoriesVisibilityHandler", () => { return { definitionContainer, category, category2, subCategory, subCategory2 }; }); - const expectedIds = [keys.definitionContainer.id, keys.category.id, keys.category2.id, keys.subCategory.id, keys.subCategory2.id]; using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; @@ -1093,14 +994,13 @@ describe("CategoriesVisibilityHandler", () => { provider, handler, viewport, - visibilityExpectations: { + expectations: { [keys.definitionContainer.id]: "visible", [keys.category2.id]: "visible", [keys.category.id]: "hidden", [keys.subCategory2.id]: "visible", [keys.subCategory.id]: "hidden", }, - expectedIds, }); viewport.validateChangesCalls([{ categoriesToChange: [keys.category.id], isVisible: false, enableAllSubCategories: false }], []); }); @@ -1130,8 +1030,6 @@ describe("CategoriesVisibilityHandler", () => { return { definitionContainerRoot, category, category2, subCategory, subCategory2 }; }); - const expectedIds = [keys.definitionContainerRoot.id, keys.category.id, keys.category2.id, keys.subCategory.id, keys.subCategory2.id]; - using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; @@ -1140,14 +1038,13 @@ describe("CategoriesVisibilityHandler", () => { provider, handler, viewport, - visibilityExpectations: { + expectations: { [keys.definitionContainerRoot.id]: "partial", [keys.category.id]: "hidden", [keys.category2.id]: "visible", [keys.subCategory.id]: "hidden", [keys.subCategory2.id]: "visible", }, - expectedIds, }); viewport.validateChangesCalls([{ categoriesToChange: [keys.category.id], isVisible: false, enableAllSubCategories: false }], []); }); @@ -1174,14 +1071,6 @@ describe("CategoriesVisibilityHandler", () => { return { definitionContainerRoot, definitionContainerChild, category, indirectCategory, subCategory }; }); - const expectedIds = [ - keys.definitionContainerRoot.id, - keys.definitionContainerChild.id, - keys.indirectCategory.id, - keys.category.id, - keys.subCategory.id, - ]; - using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; @@ -1190,14 +1079,13 @@ describe("CategoriesVisibilityHandler", () => { provider, handler, viewport, - visibilityExpectations: { + expectations: { [keys.definitionContainerRoot.id]: "partial", [keys.definitionContainerChild.id]: "visible", [keys.category.id]: "hidden", [keys.indirectCategory.id]: "visible", [keys.subCategory.id]: "hidden", }, - expectedIds, }); viewport.validateChangesCalls([{ categoriesToChange: [keys.category.id], isVisible: false, enableAllSubCategories: false }], []); }); @@ -1223,7 +1111,6 @@ describe("CategoriesVisibilityHandler", () => { return { category, subCategory, subCategory2 }; }); - const expectedIds = [keys.category.id, keys.subCategory.id, keys.subCategory2.id]; using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; @@ -1232,12 +1119,11 @@ describe("CategoriesVisibilityHandler", () => { provider, handler, viewport, - visibilityExpectations: { + expectations: { [keys.category.id]: "partial", [keys.subCategory.id]: "hidden", [keys.subCategory2.id]: "visible", }, - expectedIds, }); viewport.validateChangesCalls([], [{ subCategoryId: keys.subCategory.id, isVisible: false }]); }); @@ -1258,7 +1144,6 @@ describe("CategoriesVisibilityHandler", () => { return { category, subCategory, category2 }; }); - const expectedIds = [keys.category.id, keys.subCategory.id, keys.category2.id]; using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; @@ -1267,12 +1152,11 @@ describe("CategoriesVisibilityHandler", () => { provider, handler, viewport, - visibilityExpectations: { + expectations: { [keys.category.id]: "partial", [keys.category2.id]: "visible", [keys.subCategory.id]: "hidden", }, - expectedIds, }); viewport.validateChangesCalls([], [{ subCategoryId: keys.subCategory.id, isVisible: false }]); }); @@ -1294,7 +1178,6 @@ describe("CategoriesVisibilityHandler", () => { return { category, subCategory, definitionContainerRoot }; }); - const expectedIds = [keys.category.id, keys.subCategory.id, keys.definitionContainerRoot.id]; using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; @@ -1303,12 +1186,11 @@ describe("CategoriesVisibilityHandler", () => { provider, handler, viewport, - visibilityExpectations: { + expectations: { [keys.definitionContainerRoot.id]: "partial", [keys.category.id]: "partial", [keys.subCategory.id]: "hidden", }, - expectedIds, }); viewport.validateChangesCalls([], [{ subCategoryId: keys.subCategory.id, isVisible: false }]); }); @@ -1337,13 +1219,6 @@ describe("CategoriesVisibilityHandler", () => { return { category, subCategory, definitionContainerRoot, categoryOfDefinitionContainer, subCategoryOfDefinitionContainer }; }); - const expectedIds = [ - keys.category.id, - keys.subCategory.id, - keys.definitionContainerRoot.id, - keys.categoryOfDefinitionContainer.id, - keys.subCategoryOfDefinitionContainer.id, - ]; using visibilityTestData = await createVisibilityTestData({ imodel, isVisibleOnInitialize }); const { handler, provider, viewport } = visibilityTestData; @@ -1352,14 +1227,13 @@ describe("CategoriesVisibilityHandler", () => { provider, handler, viewport, - visibilityExpectations: { + expectations: { [keys.definitionContainerRoot.id]: "visible", [keys.categoryOfDefinitionContainer.id]: "visible", [keys.subCategoryOfDefinitionContainer.id]: "visible", [keys.category.id]: "partial", [keys.subCategory.id]: "hidden", }, - expectedIds, }); viewport.validateChangesCalls([], [{ subCategoryId: keys.subCategory.id, isVisible: false }]); }); diff --git a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/Utils.ts b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/Utils.ts index b8c034b1a..9c06673e3 100644 --- a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/Utils.ts +++ b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/Utils.ts @@ -82,7 +82,10 @@ interface ViewportStubValidation { * This stub allows changing and saving the display of categories and subcategories * @returns stubbed `Viewport` */ -export async function createViewportStub(idsCache: CategoriesTreeIdsCache, isVisibleOnInitialize = false): Promise { +export async function createViewportStub(props: { + idsCache: CategoriesTreeIdsCache; + isVisibleOnInitialize: boolean; +}): Promise { const subCategoriesMap = new Map(); const categoriesMap = new Map< @@ -93,13 +96,13 @@ export async function createViewportStub(idsCache: CategoriesTreeIdsCache, isVis } >(); - const { categories: categoriesFromCache } = await idsCache.getAllDefinitionContainersAndCategories(); + const { categories: categoriesFromCache } = await props.idsCache.getAllDefinitionContainersAndCategories(); for (const category of categoriesFromCache) { - const subCategoriesFromCache = await idsCache.getSubCategories(category); + const subCategoriesFromCache = await props.idsCache.getSubCategories(category); subCategoriesFromCache.forEach((subCategoryId) => { - subCategoriesMap.set(subCategoryId, isVisibleOnInitialize); + subCategoriesMap.set(subCategoryId, props.isVisibleOnInitialize); }); - categoriesMap.set(category, { isVisible: isVisibleOnInitialize, subCategories: subCategoriesFromCache }); + categoriesMap.set(category, { isVisible: props.isVisibleOnInitialize, subCategories: subCategoriesFromCache }); } const changeCategoryDisplayStub = sinon.stub().callsFake((categoriesToChange: Id64Array, isVisible: boolean, enableAllSubCategories: boolean) => { for (const category of categoriesToChange) { diff --git a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/VisibilityValidation.ts b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/VisibilityValidation.ts index 3b3fe3dff..5d92cf8c2 100644 --- a/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/VisibilityValidation.ts +++ b/packages/itwin/tree-widget/src/test/trees/categories-tree/internal/VisibilityValidation.ts @@ -4,12 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { expect } from "chai"; -import { EMPTY, expand, from, mergeMap, tap } from "rxjs"; +import { EMPTY, expand, from, mergeMap } from "rxjs"; import { HierarchyNode } from "@itwin/presentation-hierarchies"; import { CategoriesTreeNode } from "../../../../tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeNode.js"; import { toVoidPromise } from "../../../../tree-widget-react/components/trees/common/Rxjs.js"; -import type { Id64Array, Id64String } from "@itwin/core-bentley"; import type { Viewport } from "@itwin/core-frontend"; import type { HierarchyProvider } from "@itwin/presentation-hierarchies"; import type { Visibility } from "../../../../tree-widget-react/components/trees/common/Tooltip.js"; @@ -22,37 +21,35 @@ interface VisibilityExpectations { export interface ValidateNodeProps { handler: HierarchyVisibilityHandler; viewport: Viewport; - visibilityExpectations: VisibilityExpectations; - expectedIds: Id64Array; - all?: "visible" | "hidden"; + expectations: "all-visible" | "all-hidden" | VisibilityExpectations; } -export async function validateNodeVisibility({ node, handler, visibilityExpectations, all }: ValidateNodeProps & { node: HierarchyNode }) { +export async function validateNodeVisibility({ node, handler, expectations }: ValidateNodeProps & { node: HierarchyNode }) { const actualVisibility = await handler.getVisibilityStatus(node); if (!HierarchyNode.isInstancesNode(node)) { throw new Error(`Expected hierarchy to only have instance nodes, got ${JSON.stringify(node)}`); } - if (all !== undefined) { - expect(actualVisibility.state).to.eq(all); + if (expectations === "all-hidden" || expectations === "all-visible") { + expect(actualVisibility.state).to.eq(expectations === "all-hidden" ? "hidden" : "visible"); return; } const { id } = node.key.instanceKeys[0]; if (CategoriesTreeNode.isCategoryNode(node)) { - expect(actualVisibility.state).to.eq(visibilityExpectations[id]); + expect(actualVisibility.state).to.eq(expectations[id]); return; } if (CategoriesTreeNode.isSubCategoryNode(node)) { // One subCategory gets added when category is inserted - if (visibilityExpectations[id] !== undefined) { - expect(actualVisibility.state).to.eq(visibilityExpectations[id]); + if (expectations[id] !== undefined) { + expect(actualVisibility.state).to.eq(expectations[id]); } return; } if (CategoriesTreeNode.isDefinitionContainerNode(node)) { - expect(actualVisibility.state).to.eq(visibilityExpectations[id]); + expect(actualVisibility.state).to.eq(expectations[id]); return; } @@ -62,22 +59,13 @@ export async function validateNodeVisibility({ node, handler, visibilityExpectat export async function validateHierarchyVisibility({ provider, ...props -}: Omit & { - visibilityExpectations: VisibilityExpectations; +}: ValidateNodeProps & { provider: HierarchyProvider; }) { - const nodesFound = new Array(); await toVoidPromise( from(provider.getNodes({ parentNode: undefined })).pipe( expand((node) => (node.children ? provider.getNodes({ parentNode: node }) : EMPTY)), - tap((node) => { - if (!HierarchyNode.isInstancesNode(node)) { - throw new Error(`Expected hierarchy to contain only instance nodes, got ${JSON.stringify(node)}`); - } - nodesFound.push(node.key.instanceKeys[0].id); - }), mergeMap(async (node) => validateNodeVisibility({ ...props, node })), ), ); - expect(props.expectedIds.every((nodeId) => nodesFound.includes(nodeId))).to.be.true; } diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts index 2d4de3448..a96abdd32 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.ts @@ -43,47 +43,16 @@ interface CategoriesTreeInstanceKeyPathsFromInstanceLabelProps { } export class CategoriesTreeDefinition implements HierarchyDefinition { - private _implWithoutDefinitionContainers: HierarchyDefinition; - private _implWithDefinitionContainers: HierarchyDefinition; + private _impl: Promise | undefined; private _selectQueryFactory: NodesQueryClauseFactory; private _nodeLabelSelectClauseFactory: IInstanceLabelSelectClauseFactory; private _idsCache: CategoriesTreeIdsCache; + private _viewType: "2d" | "3d"; + private _iModelAccess: ECSchemaProvider & ECClassHierarchyInspector & LimitingECSqlQueryExecutor; public constructor(props: CategoriesTreeDefinitionProps) { - this._implWithoutDefinitionContainers = createPredicateBasedHierarchyDefinition({ - classHierarchyInspector: props.imodelAccess, - hierarchy: { - rootNodes: async (requestProps: DefineRootHierarchyLevelProps) => - this.createDefinitionContainersAndCategoriesQuery({ ...requestProps, viewType: props.viewType }), - childNodes: [ - { - parentInstancesNodePredicate: "BisCore.Category", - definitions: async (requestProps: DefineInstanceNodeChildHierarchyLevelProps) => this.createSubcategoryQuery(requestProps), - }, - ], - }, - }); - this._implWithDefinitionContainers = createPredicateBasedHierarchyDefinition({ - classHierarchyInspector: props.imodelAccess, - hierarchy: { - rootNodes: async (requestProps: DefineRootHierarchyLevelProps) => - this.createDefinitionContainersAndCategoriesQuery({ ...requestProps, viewType: props.viewType }), - childNodes: [ - { - parentInstancesNodePredicate: "BisCore.Category", - definitions: async (requestProps: DefineInstanceNodeChildHierarchyLevelProps) => this.createSubcategoryQuery(requestProps), - }, - { - parentInstancesNodePredicate: DEFINITION_CONTAINER_CLASS, - definitions: async (requestProps: DefineInstanceNodeChildHierarchyLevelProps) => - this.createDefinitionContainersAndCategoriesQuery({ - ...requestProps, - viewType: props.viewType, - }), - }, - ], - }, - }); + this._iModelAccess = props.imodelAccess; + this._viewType = props.viewType; this._idsCache = props.idsCache; this._nodeLabelSelectClauseFactory = createBisInstanceLabelSelectClauseFactory({ classHierarchyInspector: props.imodelAccess }); this._selectQueryFactory = createNodesQueryClauseFactory({ @@ -92,12 +61,40 @@ export class CategoriesTreeDefinition implements HierarchyDefinition { }); } - public async defineHierarchyLevel(props: DefineHierarchyLevelProps) { - const isDefinitionContainerSupported = await this._idsCache.getIsDefinitionContainerSupported(); + private async getHierarchyDefinition(): Promise { + this._impl ??= (async () => { + const isDefinitionContainerSupported = await this._idsCache.getIsDefinitionContainerSupported(); + return createPredicateBasedHierarchyDefinition({ + classHierarchyInspector: this._iModelAccess, + hierarchy: { + rootNodes: async (requestProps: DefineRootHierarchyLevelProps) => + this.createDefinitionContainersAndCategoriesQuery({ ...requestProps, viewType: this._viewType }), + childNodes: [ + { + parentInstancesNodePredicate: "BisCore.Category", + definitions: async (requestProps: DefineInstanceNodeChildHierarchyLevelProps) => this.createSubcategoryQuery(requestProps), + }, + ...(isDefinitionContainerSupported + ? [ + { + parentInstancesNodePredicate: DEFINITION_CONTAINER_CLASS, + definitions: async (requestProps: DefineInstanceNodeChildHierarchyLevelProps) => + this.createDefinitionContainersAndCategoriesQuery({ + ...requestProps, + viewType: this._viewType, + }), + }, + ] + : []), + ], + }, + }); + })(); + return this._impl; + } - return isDefinitionContainerSupported - ? this._implWithDefinitionContainers.defineHierarchyLevel(props) - : this._implWithoutDefinitionContainers.defineHierarchyLevel(props); + public async defineHierarchyLevel(props: DefineHierarchyLevelProps) { + return (await this.getHierarchyDefinition()).defineHierarchyLevel(props); } private async createDefinitionContainersAndCategoriesQuery(props: { diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx index aeff0f591..a67629889 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.tsx @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { useCallback, useMemo, useState } from "react"; +import { assert } from "@itwin/core-bentley"; import { SvgArchive, SvgLayers } from "@itwin/itwinui-icons-react"; import { Text } from "@itwin/itwinui-react"; import { createECSqlQueryExecutor } from "@itwin/presentation-core-interop"; @@ -16,13 +17,13 @@ import { CategoriesTreeIdsCache } from "./internal/CategoriesTreeIdsCache.js"; import { CategoriesVisibilityHandler } from "./internal/CategoriesVisibilityHandler.js"; import { DEFINITION_CONTAINER_CLASS, SUB_CATEGORY_CLASS } from "./internal/ClassNameDefinitions.js"; +import type { Id64String } from "@itwin/core-bentley"; import type { ReactElement } from "react"; import type { HierarchyNode } from "@itwin/presentation-hierarchies"; import type { VisibilityTreeProps } from "../common/components/VisibilityTree.js"; import type { Viewport } from "@itwin/core-frontend"; import type { PresentationHierarchyNode } from "@itwin/presentation-hierarchies-react"; import type { VisibilityTreeRendererProps } from "../common/components/VisibilityTreeRenderer.js"; -import type { Id64String } from "@itwin/core-bentley"; import type { CategoryInfo } from "../common/CategoriesVisibilityUtils.js"; type CategoriesTreeFilteringError = "tooManyFilterMatches" | "unknownFilterError"; @@ -152,13 +153,8 @@ async function getCategoriesFromPaths(paths: HierarchyFilteringPaths, idsCache: if (lastNode.className === SUB_CATEGORY_CLASS) { const secondToLastNode = currPath.length > 1 ? currPath[currPath.length - 2] : undefined; - if ( - secondToLastNode === undefined || - !HierarchyNodeIdentifier.isInstanceNodeIdentifier(secondToLastNode) || - secondToLastNode.className === DEFINITION_CONTAINER_CLASS - ) { - continue; - } + assert(secondToLastNode !== undefined && HierarchyNodeIdentifier.isInstanceNodeIdentifier(secondToLastNode)); + subCategory = lastNode; category = secondToLastNode; } else { diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts index c5b81bfb0..19f0d915b 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.ts @@ -3,10 +3,11 @@ * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ +import { DEFINITION_CONTAINER_CLASS, SUB_CATEGORY_CLASS } from "./ClassNameDefinitions.js"; + import type { Id64Array, Id64String } from "@itwin/core-bentley"; import type { LimitingECSqlQueryExecutor } from "@itwin/presentation-hierarchies"; import type { InstanceKey } from "@itwin/presentation-shared"; -import { DEFINITION_CONTAINER_CLASS, SUB_CATEGORY_CLASS } from "./ClassNameDefinitions.js"; interface DefinitionContainerInfo { modelId: Id64String; @@ -56,19 +57,19 @@ export class CategoriesTreeIdsCache { const CATEGORIES_CTE = "AllVisibleCategories"; const isDefinitionContainerSupported = await this.getIsDefinitionContainerSupported(); const ctes = [ - `${CATEGORIES_CTE}(ECInstanceId, ChildCount, ModelId ${isDefinitionContainerSupported ? ", ParentDefinitionContainerExists" : ""}) AS ( + `${CATEGORIES_CTE}(ECInstanceId, ChildCount, ModelId, ParentDefinitionContainerExists) AS ( SELECT this.ECInstanceId, COUNT(sc.ECInstanceId), - this.Model.Id + this.Model.Id, ${ isDefinitionContainerSupported - ? `, - IIF(this.Model.Id IN (SELECT dc.ECInstanceId FROM ${DEFINITION_CONTAINER_CLASS} dc), - true, - false - )` - : "" + ? ` + IIF(this.Model.Id IN (SELECT dc.ECInstanceId FROM ${DEFINITION_CONTAINER_CLASS} dc), + true, + false + )` + : "false" } FROM ${this._categoryClass} this @@ -85,7 +86,7 @@ export class CategoriesTreeIdsCache { SELECT this.ECInstanceId id, this.ModelId modelId, - ${isDefinitionContainerSupported ? "this.ParentDefinitionContainerExists" : "false"} parentDefinitionContainerExists, + this.ParentDefinitionContainerExists parentDefinitionContainerExists, this.ChildCount childCount FROM ${CATEGORIES_CTE} this diff --git a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.ts b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.ts index 37196c7dd..cbf5d40fb 100644 --- a/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.ts +++ b/packages/itwin/tree-widget/src/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.ts @@ -253,9 +253,7 @@ export class CategoriesVisibilityHandler implements HierarchyVisibilityHandler { } private static getInstanceIdsFromHierarchyNode(node: HierarchyNode) { - return HierarchyNode.isInstancesNode(node) && node.key.instanceKeys.length > 0 - ? node.key.instanceKeys.map((instanceKey) => instanceKey.id) - : /* istanbul ignore next */ []; + return HierarchyNode.isInstancesNode(node) ? node.key.instanceKeys.map((instanceKey) => instanceKey.id) : /* istanbul ignore next */ []; } private async changeCategoryState(ids: string[], enabled: boolean, enableAllSubCategories: boolean) {