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]);