diff --git a/.vscode/settings.json b/.vscode/settings.json
index 274741c5d..a0e8331a2 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -89,5 +89,6 @@
},
"terminal.integrated.env.windows": {
"NODE_ENV": "development"
- }
+ },
+ "terminal.integrated.scrollback": 5000,
}
diff --git a/apps/full-stack-tests/src/components/properties/ErrorHandling.test.tsx b/apps/full-stack-tests/src/components/properties/ErrorHandling.test.tsx
index 5ba8829e3..dff50774c 100644
--- a/apps/full-stack-tests/src/components/properties/ErrorHandling.test.tsx
+++ b/apps/full-stack-tests/src/components/properties/ErrorHandling.test.tsx
@@ -32,6 +32,8 @@ describe("Learning snippets", () => {
});
it("handles errors", async function () {
+ // stub console log to avoid ErrorBoundary warning in console
+ const consoleStub = sinon.stub(console, "error").callsFake(() => {});
if (Number.parseInt(PresentationRpcInterface.interfaceVersion.split(".")[0], 10) < 4) {
// property grid started supporting error boundaries since appui@4.0
this.skip();
@@ -87,6 +89,7 @@ describe("Learning snippets", () => {
// re-render the component, ensure we now get an error
rerender();
await ensureHasError(container, "Network error");
+ consoleStub.restore();
});
});
});
diff --git a/apps/full-stack-tests/src/components/properties/PresentationFilterBuilderValueRenderer.test.tsx b/apps/full-stack-tests/src/components/properties/PresentationFilterBuilderValueRenderer.test.tsx
index 0de713835..33e434728 100644
--- a/apps/full-stack-tests/src/components/properties/PresentationFilterBuilderValueRenderer.test.tsx
+++ b/apps/full-stack-tests/src/components/properties/PresentationFilterBuilderValueRenderer.test.tsx
@@ -133,7 +133,7 @@ describe("Presentation filter builder value renderer", () => {
},
];
- const { baseElement, getByRole, user } = render(
+ const { baseElement, findByRole, user } = render(
{}}
@@ -146,7 +146,7 @@ describe("Presentation filter builder value renderer", () => {
);
// trigger loadTargets function
- const combobox = await waitFor(() => getByRole("combobox"));
+ const combobox = await findByRole("combobox");
await user.click(combobox);
await waitFor(async () => {
expect(queryByText(baseElement, "Value1")).to.not.be.null;
@@ -249,7 +249,7 @@ describe("Presentation filter builder value renderer", () => {
},
];
- const { baseElement, getByRole, user } = render(
+ const { baseElement, findByRole, user } = render(
{}}
@@ -261,7 +261,7 @@ describe("Presentation filter builder value renderer", () => {
);
// trigger loadTargets function
- const combobox = await waitFor(() => getByRole("combobox"));
+ const combobox = await findByRole("combobox");
await user.click(combobox);
await waitFor(async () => {
expect(queryByText(baseElement, "Value1")).to.not.be.null;
diff --git a/apps/full-stack-tests/src/components/table/ErrorHandling.test.tsx b/apps/full-stack-tests/src/components/table/ErrorHandling.test.tsx
index f54aa24c2..8d562d40c 100644
--- a/apps/full-stack-tests/src/components/table/ErrorHandling.test.tsx
+++ b/apps/full-stack-tests/src/components/table/ErrorHandling.test.tsx
@@ -31,6 +31,8 @@ describe("Learning snippets", () => {
});
it("handles errors", async function () {
+ // stub console log to avoid ErrorBoundary warning in console
+ const consoleStub = sinon.stub(console, "error").callsFake(() => {});
// __PUBLISH_EXTRACT_START__ Presentation.Components.Table.ErrorHandling
/** Props for `MyTable` and `MyProtectedTable` components */
interface MyTableProps {
@@ -133,6 +135,7 @@ describe("Learning snippets", () => {
// re-render the component, ensure we now get an error
rerender();
await ensureHasError(container, "Network error");
+ consoleStub.restore();
});
});
});
diff --git a/apps/full-stack-tests/src/components/tree/ErrorHandling.test.tsx b/apps/full-stack-tests/src/components/tree/ErrorHandling.test.tsx
index 21ed19a9c..fe6c3a2c4 100644
--- a/apps/full-stack-tests/src/components/tree/ErrorHandling.test.tsx
+++ b/apps/full-stack-tests/src/components/tree/ErrorHandling.test.tsx
@@ -34,6 +34,8 @@ describe("Learning snippets", () => {
});
it("handles errors", async function () {
+ // stub console log to avoid expected network error in console
+ const consoleStub = sinon.stub(console, "error").callsFake(() => {});
// __PUBLISH_EXTRACT_START__ Presentation.Components.Tree.ErrorHandling
function MyTree(props: { imodel: IModelConnection }) {
const state = usePresentationTreeState({
@@ -98,6 +100,7 @@ describe("Learning snippets", () => {
expect(() => getNodeByLabel(container, `My Model A`)).to.throw();
expect(() => getNodeByLabel(container, `My Model B`)).to.throw();
expect(getByText("Èrrór ¢rëätíñg thë hìérärçhý lévêl")).is.not.null;
+ consoleStub.restore();
});
});
});
diff --git a/apps/full-stack-tests/src/components/tree/HierarchyLevelLimiting.test.tsx b/apps/full-stack-tests/src/components/tree/HierarchyLevelLimiting.test.tsx
index 4c591d25f..8bc8530b1 100644
--- a/apps/full-stack-tests/src/components/tree/HierarchyLevelLimiting.test.tsx
+++ b/apps/full-stack-tests/src/components/tree/HierarchyLevelLimiting.test.tsx
@@ -7,6 +7,7 @@
import { expect } from "chai";
import { insertPhysicalElement, insertPhysicalModelWithPartition, insertSpatialCategory } from "presentation-test-utilities";
import { useState } from "react";
+import sinon from "sinon";
import { SelectionMode, TreeRendererProps, UiComponents } from "@itwin/components-react";
import { IModelApp, IModelConnection } from "@itwin/core-frontend";
import { PresentationRpcInterface, Ruleset } from "@itwin/presentation-common";
@@ -31,6 +32,8 @@ describe("Learning snippets", () => {
});
it("limits hierarchy level size", async function () {
+ // stub console log to avoid hierarchy limit warning in console
+ const consoleStub = sinon.stub(console, "log").callsFake(() => {});
if (Number.parseInt(PresentationRpcInterface.interfaceVersion.split(".")[0], 10) < 4) {
// hierarchy level size limiting requires core libraries at least @4.0
this.skip();
@@ -98,6 +101,7 @@ describe("Learning snippets", () => {
expect(() => getNodeByLabel(container, `B element ${i + 1}`)).to.throw();
}
await waitFor(() => expect(getByText(`thèré ârë möré îtëms thâñ älløwèd límît õf ${hierarchyLevelSizeLimit}`, { exact: false })).is.not.null);
+ consoleStub.restore();
});
});
});
diff --git a/apps/full-stack-tests/src/components/tree/TreeDataProvider.test.ts b/apps/full-stack-tests/src/components/tree/TreeDataProvider.test.ts
index 148c708e6..7adbcca36 100644
--- a/apps/full-stack-tests/src/components/tree/TreeDataProvider.test.ts
+++ b/apps/full-stack-tests/src/components/tree/TreeDataProvider.test.ts
@@ -92,6 +92,8 @@ describe("TreeDataProvider", async () => {
});
it("creates error node when requesting root nodes with invalid paging", async () => {
+ // stub console log to avoid expected error in console
+ const consoleStub = sinon.stub(console, "error").callsFake(() => {});
provider.pagingSize = 5;
const nodes = await provider.getNodes(undefined, { start: 1, size: 5 });
if (nodes.length === 1) {
@@ -102,6 +104,7 @@ describe("TreeDataProvider", async () => {
// presentation-frontend@3.6 returns an empty list in case of invalid page options
expect(nodes).to.be.empty;
}
+ consoleStub.restore();
});
it("returns child nodes count", async () => {
@@ -124,6 +127,8 @@ describe("TreeDataProvider", async () => {
});
it("returns error node when requesting child nodes with invalid paging", async () => {
+ // stub console log to avoid expected error in console
+ const consoleStub = sinon.stub(console, "error").callsFake(() => {});
const rootNodes = await provider.getNodes();
provider.pagingSize = 5;
const nodes = await provider.getNodes(rootNodes[0], { start: 1, size: 5 });
@@ -135,6 +140,7 @@ describe("TreeDataProvider", async () => {
// presentation-frontend@3.6 returns an empty list in case of invalid page options
expect(nodes).to.be.empty;
}
+ consoleStub.restore();
});
it("requests backend only once to get first page", async () => {
diff --git a/apps/full-stack-tests/src/hierarchies-react/learning-snipptets/Localization.test.tsx b/apps/full-stack-tests/src/hierarchies-react/learning-snipptets/Localization.test.tsx
index 57178d0dc..733968d22 100644
--- a/apps/full-stack-tests/src/hierarchies-react/learning-snipptets/Localization.test.tsx
+++ b/apps/full-stack-tests/src/hierarchies-react/learning-snipptets/Localization.test.tsx
@@ -138,7 +138,7 @@ describe("Hierarchies React", () => {
type TreeProps = ComponentPropsWithoutRef>;
type TreeRendererProps = Props;
- function MyTreeRenderer(props: TreeRendererProps) {
+ function MyTreeRenderer({ rootNodes }: TreeRendererProps) {
const nodeRenderer = useCallback((nodeProps) => {
return {}} expandNode={() => {}} />;
}, []);
@@ -147,7 +147,7 @@ describe("Hierarchies React", () => {
return (
- {...props} data={props.rootNodes} nodeRenderer={nodeRenderer} getNode={getNode} />
+ data={rootNodes} nodeRenderer={nodeRenderer} getNode={getNode} enableVirtualization={true} />
);
}
diff --git a/apps/full-stack-tests/src/hierarchies-react/learning-snipptets/ReadmeExample.test.tsx b/apps/full-stack-tests/src/hierarchies-react/learning-snipptets/ReadmeExample.test.tsx
index f91ec02ae..6f619a840 100644
--- a/apps/full-stack-tests/src/hierarchies-react/learning-snipptets/ReadmeExample.test.tsx
+++ b/apps/full-stack-tests/src/hierarchies-react/learning-snipptets/ReadmeExample.test.tsx
@@ -183,7 +183,7 @@ describe("Hierarchies React", () => {
const { getByText } = render();
- expect(getByText("No data to display")).to.not.be.null;
+ await waitFor(() => expect(getByText("No data to display")).to.not.be.null);
});
});
});
diff --git a/apps/full-stack-tests/src/unified-selection/HiliteSet.test.ts b/apps/full-stack-tests/src/unified-selection/HiliteSet.test.ts
index eedd81dd1..ad0498705 100644
--- a/apps/full-stack-tests/src/unified-selection/HiliteSet.test.ts
+++ b/apps/full-stack-tests/src/unified-selection/HiliteSet.test.ts
@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import { expect } from "chai";
-import * as fs from "fs";
import path from "path";
import {
getDefaultSubcategoryKey,
@@ -28,6 +27,7 @@ import { ECSchemaRpcImpl } from "@itwin/ecschema-rpcinterface-impl";
import { buildTestIModel, initialize, terminate } from "@itwin/presentation-testing";
import { createHiliteSetProvider, SelectableInstanceKey, Selectables } from "@itwin/unified-selection";
import { createIModelAccess } from "../hierarchies/Utils.js";
+import { getSchemaFromPackage } from "./getSchema.js";
describe("HiliteSet", () => {
let iModel: IModelConnection;
@@ -201,6 +201,8 @@ describe("HiliteSet", () => {
// eslint-disable-next-line @typescript-eslint/no-deprecated
iModel = await buildTestIModel(this, async (builder) => {
const modelKey = insertPhysicalModelWithPartition({ builder, codeValue: "test model" });
+ const schema = await getSchemaFromPackage("functional-schema", "Functional.ecschema.xml");
+ await builder.importSchema(schema);
const categoryKey = insertSpatialCategory({ builder, codeValue: "test category" });
assemblyKey = insertPhysicalElement({ builder, userLabel: "element 1", modelId: modelKey.id, categoryId: categoryKey.id });
const element2 = insertPhysicalElement({
@@ -236,6 +238,8 @@ describe("HiliteSet", () => {
// eslint-disable-next-line @typescript-eslint/no-deprecated
iModel = await buildTestIModel(this, async (builder) => {
const modelKey = insertPhysicalModelWithPartition({ builder, codeValue: "test model" });
+ const schema = await getSchemaFromPackage("functional-schema", "Functional.ecschema.xml");
+ await builder.importSchema(schema);
const categoryKey = insertSpatialCategory({ builder, codeValue: "test category" });
elementKey = insertPhysicalElement({ builder, userLabel: "element", modelId: modelKey.id, categoryId: categoryKey.id });
});
@@ -252,6 +256,8 @@ describe("HiliteSet", () => {
// eslint-disable-next-line @typescript-eslint/no-deprecated
iModel = await buildTestIModel(this, async (builder) => {
const modelKey = insertPhysicalModelWithPartition({ builder, codeValue: "test model" });
+ const schema = await getSchemaFromPackage("functional-schema", "Functional.ecschema.xml");
+ await builder.importSchema(schema);
const categoryKey = insertSpatialCategory({ builder, codeValue: "test category" });
elementKeys = [
insertPhysicalElement({ builder, userLabel: "element", modelId: modelKey.id, categoryId: categoryKey.id }),
@@ -271,11 +277,6 @@ describe("HiliteSet", () => {
});
describe("Functional element", () => {
- async function getSchemaFromPackage(packageName: string, schemaFileName: string): Promise {
- const schemaFile = path.join(import.meta.dirname, "..", "..", "node_modules", "@bentley", packageName, schemaFileName);
- return fs.readFileSync(schemaFile, "utf8");
- }
-
it("hilites functional element related physical elements", async function () {
let functionalElement: SelectableInstanceKey;
let physicalElement: SelectableInstanceKey;
@@ -378,6 +379,8 @@ describe("HiliteSet", () => {
// eslint-disable-next-line @typescript-eslint/no-deprecated
iModel = await buildTestIModel(this, async (builder) => {
const groupModel = insertGroupInformationModelWithPartition({ builder, codeValue: "group information model" });
+ const schema = await getSchemaFromPackage("functional-schema", "Functional.ecschema.xml");
+ await builder.importSchema(schema);
const physicalModelKey = insertPhysicalModelWithPartition({ builder, codeValue: "test physical model" });
const categoryKey = insertSpatialCategory({ builder, codeValue: "test category" });
groupInformationElement = insertGroupInformationElement({
diff --git a/apps/full-stack-tests/src/unified-selection/SelectionScope.test.ts b/apps/full-stack-tests/src/unified-selection/SelectionScope.test.ts
index 3fd1c4436..756842040 100644
--- a/apps/full-stack-tests/src/unified-selection/SelectionScope.test.ts
+++ b/apps/full-stack-tests/src/unified-selection/SelectionScope.test.ts
@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import { expect } from "chai";
-import * as fs from "fs";
import path from "path";
import {
insertDrawingCategory,
@@ -24,6 +23,7 @@ import { createECSqlQueryExecutor } from "@itwin/presentation-core-interop";
import { Props } from "@itwin/presentation-shared";
import { buildTestIModel, initialize, terminate } from "@itwin/presentation-testing";
import { computeSelection, SelectableInstanceKey } from "@itwin/unified-selection";
+import { getSchemaFromPackage } from "./getSchema.js";
describe("SelectionScope", () => {
let iModel: IModelConnection;
@@ -43,11 +43,6 @@ describe("SelectionScope", () => {
await terminate();
});
- async function getSchemaFromPackage(packageName: string, schemaFileName: string): Promise {
- const schemaFile = path.join(import.meta.dirname, "..", "..", "node_modules", "@bentley", packageName, schemaFileName);
- return fs.readFileSync(schemaFile, "utf8");
- }
-
async function getSelection(keys: string[], scope: Props["scope"]): Promise {
const selectables: SelectableInstanceKey[] = [];
for await (const selectable of computeSelection({ queryExecutor: createECSqlQueryExecutor(iModel), elementIds: keys, scope })) {
diff --git a/apps/full-stack-tests/src/unified-selection/getSchema.ts b/apps/full-stack-tests/src/unified-selection/getSchema.ts
new file mode 100644
index 000000000..536ebd53b
--- /dev/null
+++ b/apps/full-stack-tests/src/unified-selection/getSchema.ts
@@ -0,0 +1,12 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
+ * See LICENSE.md in the project root for license terms and full copyright notice.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as fs from "fs";
+import path from "path";
+
+export async function getSchemaFromPackage(packageName: string, schemaFileName: string): Promise {
+ const schemaFile = path.join(import.meta.dirname, "..", "..", "node_modules", "@bentley", packageName, schemaFileName);
+ return fs.readFileSync(schemaFile, "utf8");
+}
diff --git a/packages/components/src/presentation-components/properties/inputs/UniquePropertyValuesSelector.tsx b/packages/components/src/presentation-components/properties/inputs/UniquePropertyValuesSelector.tsx
index 09c3301b6..6c0897545 100644
--- a/packages/components/src/presentation-components/properties/inputs/UniquePropertyValuesSelector.tsx
+++ b/packages/components/src/presentation-components/properties/inputs/UniquePropertyValuesSelector.tsx
@@ -35,7 +35,7 @@ export interface UniquePropertyValuesSelectorProps {
export function UniquePropertyValuesSelector(props: UniquePropertyValuesSelectorProps) {
const { imodel, descriptor, property, onChange, value, descriptorInputKeys, selectedClasses } = props;
const [field, setField] = useState(() => findField(descriptor, getInstanceFilterFieldName(property)));
- const [searchInput, setSearchInput] = useState();
+ const [searchInput, setSearchInput] = useState("");
const selectedValues = useMemo(() => getUniqueValueFromProperty(value)?.map((val) => val.displayValue), [value]);
const ruleset = useUniquePropertyValuesRuleset(descriptor.ruleset, field, descriptorInputKeys, selectedClasses);
const { selectOptions, loadedOptions, isLoading } = useUniquePropertyValuesLoader({
diff --git a/packages/components/src/presentation-components/table/CellRenderer.tsx b/packages/components/src/presentation-components/table/CellRenderer.tsx
index 198941933..df10c413c 100644
--- a/packages/components/src/presentation-components/table/CellRenderer.tsx
+++ b/packages/components/src/presentation-components/table/CellRenderer.tsx
@@ -10,7 +10,7 @@ import { useState } from "react";
import { ArrayValue, PropertyRecord, PropertyValueFormat } from "@itwin/appui-abstract";
import { NonPrimitivePropertyRenderer, PropertyValueRendererManager } from "@itwin/components-react";
import { Orientation } from "@itwin/core-react";
-import { Anchor, Modal } from "@itwin/itwinui-react";
+import { Anchor, Modal, ModalContent } from "@itwin/itwinui-react";
/**
* Props for [[TableCellRenderer]] component.
@@ -74,6 +74,7 @@ function NonPrimitiveCellRenderer(props: NonPrimitiveCellRendererProps) {
const { record, dialogLabel, buttonLabel, uniqueKey } = props;
const [isOpen, setIsOpen] = useState(false);
+ // modal window when opened causes findDOMNode warning https://github.com/iTwin/iTwinUI/issues/2199
return (
<>
setIsOpen(false)} className="presentation-components-non-primitive-value">
- {/* Can't change our import to `components-react`, because it was added there in a version later than our peer dependency */}
- {/* eslint-disable-next-line @typescript-eslint/no-deprecated */}
-
+
+ {/* Can't change our import to `components-react`, because it was added there in a version later than our peer dependency */}
+ {/* eslint-disable-next-line @typescript-eslint/no-deprecated */}
+
+
>
);
diff --git a/packages/components/src/test/instance-filter-builder/PresentationInstanceFilterDialog.test.tsx b/packages/components/src/test/instance-filter-builder/PresentationInstanceFilterDialog.test.tsx
index 741c7bd22..3340ba2d1 100644
--- a/packages/components/src/test/instance-filter-builder/PresentationInstanceFilterDialog.test.tsx
+++ b/packages/components/src/test/instance-filter-builder/PresentationInstanceFilterDialog.test.tsx
@@ -327,6 +327,8 @@ describe("PresentationInstanceFilterDialog", () => {
it("throws error when filter is missing presentation metadata", async () => {
const fromComponentsPropertyFilterStub = sinon.stub(PresentationInstanceFilter, "fromComponentsPropertyFilter").throws(new Error("Some Error"));
+ // stub console log to avoid expected error in console
+ const consoleErrorStub = sinon.stub(console, "error").callsFake(() => {});
const spy = sinon.spy();
const { baseElement, user } = render(, {
addThemeProvider: true,
@@ -350,6 +352,7 @@ describe("PresentationInstanceFilterDialog", () => {
await waitFor(() => expect(queryByText(baseElement, "general.error")).to.not.be.null);
fromComponentsPropertyFilterStub.restore();
+ consoleErrorStub.restore();
});
it("renders custom title", async () => {
@@ -383,6 +386,8 @@ describe("PresentationInstanceFilterDialog", () => {
});
it("renders error boundary if error is thrown", async () => {
+ // stub console log to avoid expected error in console
+ const consoleErrorStub = sinon.stub(console, "error").callsFake(() => {});
const propertiesSourceGetter = () => {
throw new Error("Cannot load descriptor");
};
@@ -392,6 +397,7 @@ describe("PresentationInstanceFilterDialog", () => {
);
await waitFor(() => expect(queryByText(baseElement, "general.error")).to.not.be.null);
+ consoleErrorStub.restore();
});
it("renders with lazy-loaded descriptor", async () => {
diff --git a/packages/components/src/test/table/CellRenderer.test.tsx b/packages/components/src/test/table/CellRenderer.test.tsx
index 4bb301777..e4ca83262 100644
--- a/packages/components/src/test/table/CellRenderer.test.tsx
+++ b/packages/components/src/test/table/CellRenderer.test.tsx
@@ -6,7 +6,7 @@
import { expect } from "chai";
import { ArrayValue, PrimitiveValue, PropertyDescription, PropertyRecord, PropertyValue, PropertyValueFormat, StructValue } from "@itwin/appui-abstract";
import { TableCellRenderer } from "../../presentation-components/table/CellRenderer.js";
-import { fireEvent, render, waitFor } from "../TestUtils.js";
+import { render, waitFor } from "../TestUtils.js";
describe("TableCellRenderer", () => {
function createRecord(value: PropertyValue, propDescription?: Partial) {
@@ -32,6 +32,7 @@ describe("TableCellRenderer", () => {
});
it("renders array value as button that opens dialog", async () => {
+ // needs fixing. Modal causes findDOMNode warning https://github.com/iTwin/iTwinUI/issues/2199
const arrayElementValue = "ArrayElement";
const value: ArrayValue = {
valueFormat: PropertyValueFormat.Array,
@@ -40,16 +41,17 @@ describe("TableCellRenderer", () => {
};
const record = createRecord(value, { typename: "array" });
- const { getByText, queryByText } = render();
+ const { getByText, queryByText, user } = render();
const buttonLabel = `${value.itemsTypeName}[1]`;
const button = getByText(buttonLabel);
- fireEvent.click(button);
+ await user.click(button);
const dialogLabel = `Array of type "${value.itemsTypeName}"`;
await waitFor(() => expect(queryByText(dialogLabel)).to.not.be.null);
});
it("renders empty array value as button that opens dialog", async () => {
+ // needs fixing. Modal causes findDOMNode warning https://github.com/iTwin/iTwinUI/issues/2199
const value: ArrayValue = {
valueFormat: PropertyValueFormat.Array,
itemsTypeName: "TestArrayTypeName",
@@ -57,16 +59,17 @@ describe("TableCellRenderer", () => {
};
const record = createRecord(value, { typename: "array" });
- const { getByText, queryByText } = render();
+ const { getByText, queryByText, user } = render();
const buttonLabel = `[]`;
const button = getByText(buttonLabel);
- fireEvent.click(button);
+ await user.click(button);
const dialogLabel = `Array of type "${value.itemsTypeName}"`;
await waitFor(() => expect(queryByText(dialogLabel)).to.not.be.null);
});
it("renders struct value as button that opens dialog", async () => {
+ // needs fixing. Modal causes findDOMNode warning https://github.com/iTwin/iTwinUI/issues/2199
const structMemberValue = "FirstMemberValue";
const value: StructValue = {
valueFormat: PropertyValueFormat.Struct,
@@ -76,11 +79,11 @@ describe("TableCellRenderer", () => {
};
const record = createRecord(value, { typename: "TestStruct" });
- const { getByText, queryByText } = render();
+ const { getByText, queryByText, user } = render();
const buttonLabel = `{${record.property.typename}}`;
const button = getByText(buttonLabel);
- fireEvent.click(button);
+ await user.click(button);
const dialogLabel = `Struct of type "${record.property.typename}"`;
await waitFor(() => expect(queryByText(dialogLabel)).to.not.be.null);
});
diff --git a/packages/components/src/test/table/UseColumns.test.tsx b/packages/components/src/test/table/UseColumns.test.tsx
index 13d7f1aa7..fb156d6a0 100644
--- a/packages/components/src/test/table/UseColumns.test.tsx
+++ b/packages/components/src/test/table/UseColumns.test.tsx
@@ -90,6 +90,8 @@ describe("useColumns", () => {
});
it("throws in React render loop on failure to get content descriptor", async () => {
+ // stub console error to avoid warnings/errors in console
+ const consoleErrorStub = sinon.stub(console, "error").callsFake(() => {});
presentationManager.getContentDescriptor.resolves(undefined).rejects(new Error("test error"));
const errorSpy = sinon.spy();
@@ -106,5 +108,6 @@ describe("useColumns", () => {
await waitFor(() => {
expect(errorSpy).to.be.calledOnce.and.calledWith(sinon.match((error: Error) => error.message === "test error"));
});
+ consoleErrorStub.restore();
});
});
diff --git a/packages/components/src/test/table/UseRows.test.tsx b/packages/components/src/test/table/UseRows.test.tsx
index 71fbf385d..6d514801b 100644
--- a/packages/components/src/test/table/UseRows.test.tsx
+++ b/packages/components/src/test/table/UseRows.test.tsx
@@ -224,6 +224,8 @@ describe("useRows", () => {
});
it("throws in React render loop on failure to get content", async () => {
+ // stub console error to avoid warnings/errors in console
+ const consoleErrorStub = sinon.stub(console, "error").callsFake(() => {});
getContentIteratorStub.throws(new Error("Failed to load"));
const errorSpy = sinon.spy();
@@ -240,6 +242,7 @@ describe("useRows", () => {
await waitFor(() => {
expect(errorSpy).to.be.calledOnce.and.calledWith(sinon.match((error: Error) => error.message === "Failed to load"));
});
+ consoleErrorStub.restore();
});
it("returns empty rows list if there are no content", async () => {
diff --git a/packages/components/src/test/tree/DataProvider.test.ts b/packages/components/src/test/tree/DataProvider.test.ts
index 236058ecc..24f340a10 100644
--- a/packages/components/src/test/tree/DataProvider.test.ts
+++ b/packages/components/src/test/tree/DataProvider.test.ts
@@ -506,6 +506,8 @@ describe("TreeDataProvider", () => {
});
it("returns info node on generic error", async () => {
+ // stub console log to avoid expected error in console
+ const consoleStub = sinon.stub(console, "error").callsFake(() => {});
presentationManager.getNodesIterator.callsFake(async () => {
throw new Error("test");
});
@@ -513,6 +515,7 @@ describe("TreeDataProvider", () => {
const actualResult = await provider.getNodes(undefined);
expect(actualResult).to.have.lengthOf(1);
expect((actualResult[0] as PresentationInfoTreeNodeItem).message).to.eq(translate("tree.unknown-error"));
+ consoleStub.restore();
});
it("returns empty result on cancellation", async () => {
diff --git a/packages/components/src/test/tree/controlled/PresentationTreeNodeRenderer.test.tsx b/packages/components/src/test/tree/controlled/PresentationTreeNodeRenderer.test.tsx
index 3438e7a7d..a6d43119a 100644
--- a/packages/components/src/test/tree/controlled/PresentationTreeNodeRenderer.test.tsx
+++ b/packages/components/src/test/tree/controlled/PresentationTreeNodeRenderer.test.tsx
@@ -61,11 +61,13 @@ describe("PresentationTreeNodeRenderer", () => {
{}} onClearFilterClick={() => {}} />,
);
- await waitFor(() => getByText(testLabel));
- expect(container.querySelector(".presentation-components-node")).to.be.null;
+ await waitFor(() => {
+ getByText(testLabel);
+ expect(container.querySelector(".presentation-components-node")).to.be.null;
+ });
});
- it("renders info tree node", () => {
+ it("renders info tree node", async () => {
const message = "Some info";
const item: PresentationInfoTreeNodeItem = {
id: "info_node_id",
@@ -79,7 +81,9 @@ describe("PresentationTreeNodeRenderer", () => {
const { getByText } = render( {}} onClearFilterClick={() => {}} />);
- getByText(message);
+ await waitFor(() => {
+ getByText(message);
+ });
});
it("renders presentation tree node", async () => {
@@ -95,17 +99,17 @@ describe("PresentationTreeNodeRenderer", () => {
expect(container.querySelector(".presentation-components-node")).to.not.be.null;
});
- it("renders node with filter button", () => {
+ it("renders node with filter button", async () => {
const nodeItem = createTreeNodeItem({ filtering: { descriptor: createTestContentDescriptor({ fields: [] }), ancestorFilters: [] } });
const node = createTreeModelNode(undefined, nodeItem);
const { container } = render( {}} onClearFilterClick={() => {}} />);
- const buttons = container.querySelectorAll(".presentation-components-node-action-buttons button");
+ const buttons = await waitFor(() => container.querySelectorAll(".presentation-components-node-action-buttons button"));
expect(buttons.length).to.eq(1);
});
- it("renders filtered node with filter and clear filter buttons", () => {
+ it("renders filtered node with filter and clear filter buttons", async () => {
const nodeItem = createTreeNodeItem({
filtering: {
descriptor: createTestContentDescriptor({ fields: [] }),
@@ -117,17 +121,17 @@ describe("PresentationTreeNodeRenderer", () => {
const { container } = render( {}} onClearFilterClick={() => {}} />);
- const buttons = container.querySelectorAll(".presentation-components-node-action-buttons button");
+ const buttons = await waitFor(() => container.querySelectorAll(".presentation-components-node-action-buttons button"));
expect(buttons.length).to.eq(2);
});
- it("renders without buttons when node is not filterable", () => {
+ it("renders without buttons when node is not filterable", async () => {
const nodeItem = createTreeNodeItem();
const node = createTreeModelNode(undefined, nodeItem);
const { container } = render( {}} onClearFilterClick={() => {}} />);
- const buttons = container.querySelectorAll(".presentation-components-node-action-buttons button");
+ const buttons = await waitFor(() => container.querySelectorAll(".presentation-components-node-action-buttons button"));
expect(buttons).to.be.empty;
});
@@ -174,20 +178,20 @@ describe("PresentationTreeNodeRenderer", () => {
expect(filterClickSpy).to.not.be.called;
});
- it("invokes 'onFilterClick' when filter button is clicked", () => {
+ it("invokes 'onFilterClick' when filter button is clicked", async () => {
const spy = sinon.spy();
const nodeItem = createTreeNodeItem({ filtering: { descriptor: createTestContentDescriptor({ fields: [] }), ancestorFilters: [] } });
const node = createTreeModelNode(undefined, nodeItem);
const { container } = render( {}} />);
- const buttons = container.querySelectorAll(".presentation-components-node-action-buttons button");
+ const buttons = await waitFor(() => container.querySelectorAll(".presentation-components-node-action-buttons button"));
expect(buttons.length).to.eq(1);
fireEvent.click(buttons[0]);
expect(spy).be.calledOnce;
});
- it("invokes 'onClearFilterClick' when clear button is clicked", () => {
+ it("invokes 'onClearFilterClick' when clear button is clicked", async () => {
const spy = sinon.spy();
const nodeItem = createTreeNodeItem({
filtering: {
@@ -200,7 +204,7 @@ describe("PresentationTreeNodeRenderer", () => {
const { container } = render( {}} onClearFilterClick={spy} />);
- const buttons = container.querySelectorAll(".presentation-components-node-action-buttons button");
+ const buttons = await waitFor(() => container.querySelectorAll(".presentation-components-node-action-buttons button"));
expect(buttons.length).to.eq(2);
fireEvent.click(buttons[0]);
expect(spy).be.calledOnce;
diff --git a/packages/components/src/test/tree/controlled/PresentationTreeRenderer.test.tsx b/packages/components/src/test/tree/controlled/PresentationTreeRenderer.test.tsx
index a4c590ef4..f4f4d5c31 100644
--- a/packages/components/src/test/tree/controlled/PresentationTreeRenderer.test.tsx
+++ b/packages/components/src/test/tree/controlled/PresentationTreeRenderer.test.tsx
@@ -31,7 +31,7 @@ import { IPresentationTreeDataProvider } from "../../../presentation-components/
import { PresentationTreeNodeItem } from "../../../presentation-components/tree/PresentationTreeNodeItem.js";
import { createTestPropertyInfo, stubDOMMatrix, stubGetBoundingClientRect, stubRaf } from "../../_helpers/Common.js";
import { createTestContentDescriptor, createTestPropertiesContentField } from "../../_helpers/Content.js";
-import { act, render, waitFor } from "../../TestUtils.js";
+import { act, cleanup, render, waitFor } from "../../TestUtils.js";
import { createTreeModelNodeInput } from "./Helpers.js";
describe("PresentationTreeRenderer", () => {
@@ -135,7 +135,7 @@ describe("PresentationTreeRenderer", () => {
);
});
- const { queryByText, container, baseElement, user } = render(
+ const { queryByText, container, baseElement, user, findByText } = render(
,
);
@@ -147,15 +147,13 @@ describe("PresentationTreeRenderer", () => {
await user.click(filterButton!);
// wait for dialog to be visible
- await waitFor(() => {
- expect(baseElement.querySelector(".presentation-instance-filter-dialog")).to.not.be.null;
- });
+ await findByText("instance-filter-builder.filter");
- const closeButton = baseElement.querySelector(".presentation-instance-filter-dialog-close-button");
+ const closeButton = await waitFor(() => baseElement.querySelector(".presentation-instance-filter-dialog-close-button"));
expect(closeButton).to.not.be.null;
await user.click(closeButton!);
- const dialog = baseElement.querySelector(".presentation-instance-filter-dialog");
+ const dialog = await waitFor(() => baseElement.querySelector(".presentation-instance-filter-dialog"));
expect(dialog).to.be.null;
});
@@ -169,14 +167,14 @@ describe("PresentationTreeRenderer", () => {
);
});
- const { queryByText, container, baseElement, user } = render(
+ const { findByText, container, baseElement, user } = render(
,
);
- await waitFor(() => expect(queryByText("A")).to.not.be.null);
+ await findByText("A");
expect(container.querySelector(".presentation-components-node")).to.not.be.null;
- const filterButton = container.querySelector(".presentation-components-node-action-buttons button");
+ const filterButton = await waitFor(() => container.querySelector(".presentation-components-node-action-buttons button"));
expect(filterButton).to.not.be.null;
await user.click(filterButton!);
@@ -215,7 +213,6 @@ describe("PresentationTreeRenderer", () => {
const { queryByText, baseElement, user, container } = render(
,
);
-
await waitFor(() => expect(queryByText("A")).to.not.be.null);
const filterButton = container.querySelector(".presentation-components-node-action-buttons button");
@@ -223,9 +220,9 @@ describe("PresentationTreeRenderer", () => {
await user.click(filterButton!);
// assert that dialog is not loaded
- await waitFor(() => {
- expect(baseElement.querySelector(".presentation-instance-filter-dialog")).to.be.null;
- });
+ const dialog = await waitFor(() => baseElement.querySelector(".presentation-instance-filter-dialog"));
+ expect(dialog).to.be.null;
+ cleanup();
});
it("applies filter and closes dialog", async () => {
@@ -308,6 +305,7 @@ describe("PresentationTreeRenderer", () => {
await applyFilter(result, propertyField.label);
await waitFor(() => expect(onFilterAppliedSpy).to.be.calledOnce);
+ cleanup();
});
it("does not call `onFilterApplied` when filter is cleared", async () => {
@@ -332,13 +330,10 @@ describe("PresentationTreeRenderer", () => {
);
});
- const result = render(
+ const { getByText, getByRole, user } = render(
,
);
-
- const { queryByText, user, getByRole } = result;
- await waitFor(() => expect(queryByText("A")).to.not.be.null);
-
+ await waitFor(() => getByText("A"));
// ensure that initially the filter is enabled
let nodeItem = modelSource.getModel().getNode("A")?.item as PresentationTreeNodeItem;
expect(nodeItem.filtering?.active).to.not.be.undefined;
@@ -352,6 +347,7 @@ describe("PresentationTreeRenderer", () => {
});
expect(onFilterAppliedSpy).to.not.be.called;
+ cleanup();
});
it("renders results count when filtering dialog has valid filter", async () => {
@@ -496,6 +492,7 @@ describe("PresentationTreeRenderer", () => {
nodeItem = modelSource.getModel().getNode("A")?.item as PresentationTreeNodeItem;
expect(nodeItem.filtering?.active).to.be.undefined;
+ cleanup();
});
});
@@ -519,7 +516,8 @@ async function applyFilter(result: ReturnType, propertyLabel: str
expect(propertySelector).to.not.be.null;
await user.click(propertySelector!);
// select property
- await user.click(getByTitle(propertyLabel));
+ const property = await waitFor(() => getByTitle(propertyLabel));
+ await user.click(property);
// wait until apply button is enabled
const applyButton = await waitFor(() => {
diff --git a/packages/hierarchies-react/README.md b/packages/hierarchies-react/README.md
index 3b17e15f8..0a63955f3 100644
--- a/packages/hierarchies-react/README.md
+++ b/packages/hierarchies-react/README.md
@@ -398,7 +398,7 @@ import {
type TreeProps = ComponentPropsWithoutRef>;
type TreeRendererProps = Props;
-function MyTreeRenderer(props: TreeRendererProps) {
+function MyTreeRenderer({ rootNodes }: TreeRendererProps) {
const nodeRenderer = useCallback((nodeProps) => {
return {}} expandNode={() => {}} />;
}, []);
@@ -407,7 +407,7 @@ function MyTreeRenderer(props: TreeRendererProps) {
return (
- {...props} data={props.rootNodes} nodeRenderer={nodeRenderer} getNode={getNode} />
+ data={rootNodes} nodeRenderer={nodeRenderer} getNode={getNode} enableVirtualization={true} />
);
}
diff --git a/packages/testing/src/presentation-testing/Helpers.ts b/packages/testing/src/presentation-testing/Helpers.ts
index db99055f2..7304457cf 100644
--- a/packages/testing/src/presentation-testing/Helpers.ts
+++ b/packages/testing/src/presentation-testing/Helpers.ts
@@ -9,10 +9,15 @@
import { join } from "path";
import * as rimraf from "rimraf";
import { IModelHost, IModelHostOptions } from "@itwin/core-backend";
-import { Guid } from "@itwin/core-bentley";
+import { Guid, Logger, LogLevel } from "@itwin/core-bentley";
import { IModelReadRpcInterface, RpcConfiguration, RpcDefaultConfiguration, RpcInterfaceDefinition, SnapshotIModelRpcInterface } from "@itwin/core-common";
import { IModelApp, IModelAppOptions, NoRenderApp } from "@itwin/core-frontend";
-import { HierarchyCacheMode, Presentation as PresentationBackend, PresentationManagerProps as PresentationBackendProps } from "@itwin/presentation-backend";
+import {
+ HierarchyCacheMode,
+ Presentation as PresentationBackend,
+ PresentationBackendNativeLoggerCategory,
+ PresentationManagerProps as PresentationBackendProps,
+} from "@itwin/presentation-backend";
import { PresentationRpcInterface } from "@itwin/presentation-common";
import { Presentation as PresentationFrontend, PresentationProps as PresentationFrontendProps } from "@itwin/presentation-frontend";
import { getTestOutputDir, setTestOutputDir } from "./FilenameUtils.js";
@@ -82,6 +87,12 @@ export const initialize = async (props?: PresentationTestingInitProps) => {
props = {};
}
+ Logger.initializeToConsole();
+ Logger.setLevelDefault(LogLevel.Warning);
+ Logger.setLevel("i18n", LogLevel.Error);
+ Logger.setLevel("SQLite", LogLevel.Error);
+ Logger.setLevel(PresentationBackendNativeLoggerCategory.ECObjects, LogLevel.Warning);
+
// set up rpc interfaces
initializeRpcInterfaces(props.rpcs ?? [SnapshotIModelRpcInterface, IModelReadRpcInterface, PresentationRpcInterface]);