From 8c0f021bf7744bab8dd271a13cabfc9f96d2a6e7 Mon Sep 17 00:00:00 2001 From: Evan Johnson Date: Fri, 10 Jan 2025 11:45:46 -0500 Subject: [PATCH 01/42] Remove tab hotkey when in ROW FocusMode (#7143) Co-authored-by: svc-changelog --- .../table/changelog/@unreleased/pr-7143.v2.yml | 7 +++++++ packages/table/src/common/index.ts | 2 +- packages/table/src/index.ts | 1 + packages/table/src/table2Utils.ts | 14 -------------- 4 files changed, 9 insertions(+), 15 deletions(-) create mode 100644 packages/table/changelog/@unreleased/pr-7143.v2.yml diff --git a/packages/table/changelog/@unreleased/pr-7143.v2.yml b/packages/table/changelog/@unreleased/pr-7143.v2.yml new file mode 100644 index 0000000000..2a8ca2a915 --- /dev/null +++ b/packages/table/changelog/@unreleased/pr-7143.v2.yml @@ -0,0 +1,7 @@ +type: fix +fix: + description: Remove tab hotkeys when in ROW FocusMode. Focus can be moved with the + arrow key hotkeys. This prevents focus from being trapped in the table, requiring + a user to tab through every row to access later elements with the tab key. + links: + - https://github.com/palantir/blueprint/pull/7143 diff --git a/packages/table/src/common/index.ts b/packages/table/src/common/index.ts index 750fdb682b..30d746d887 100644 --- a/packages/table/src/common/index.ts +++ b/packages/table/src/common/index.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -export type { CellCoordinates, FocusedCellCoordinates } from "./cellTypes"; +export type { CellCoordinates, FocusedCellCoordinates, FocusMode } from "./cellTypes"; export { Clipboard } from "./clipboard"; export { Grid } from "./grid"; export { Rect, type AnyRect } from "./rect"; diff --git a/packages/table/src/index.ts b/packages/table/src/index.ts index f99dfa9a1c..bb8951866c 100644 --- a/packages/table/src/index.ts +++ b/packages/table/src/index.ts @@ -30,6 +30,7 @@ export { type AnyRect, type CellCoordinates, Clipboard, + type FocusMode, type FocusedCellCoordinates, Grid, Rect, diff --git a/packages/table/src/table2Utils.ts b/packages/table/src/table2Utils.ts index ded263c407..6cd4e7bb91 100644 --- a/packages/table/src/table2Utils.ts +++ b/packages/table/src/table2Utils.ts @@ -110,20 +110,6 @@ function getFocusHotkeys(focusMode: FocusMode | undefined, hotkeysImpl: TableHot label: "Move focus row down", onKeyDown: hotkeysImpl.handleFocusMoveDown, }, - { - allowInInput: true, - combo: "tab", - group: "Table", - label: "Move focus row tab", - onKeyDown: hotkeysImpl.handleFocusMoveDown, - }, - { - allowInInput: true, - combo: "shift+tab", - group: "Table", - label: "Move focus row shift+tab", - onKeyDown: hotkeysImpl.handleFocusMoveUp, - }, ]; case FocusMode.CELL: return [ From 19815c78391326b1074ac5063527c8c093f39b83 Mon Sep 17 00:00:00 2001 From: annawhittakermuratt Date: Fri, 10 Jan 2025 16:51:50 +0000 Subject: [PATCH 02/42] feat: new icon - subtract join (#7132) Co-authored-by: svc-changelog --- packages/icons/changelog/@unreleased/pr-7132.v2.yml | 5 +++++ packages/icons/icons.json | 7 +++++++ resources/icons/16px/subtract-right-join.svg | 3 +++ resources/icons/20px/subtract-right-join.svg | 4 ++++ 4 files changed, 19 insertions(+) create mode 100644 packages/icons/changelog/@unreleased/pr-7132.v2.yml create mode 100644 resources/icons/16px/subtract-right-join.svg create mode 100644 resources/icons/20px/subtract-right-join.svg diff --git a/packages/icons/changelog/@unreleased/pr-7132.v2.yml b/packages/icons/changelog/@unreleased/pr-7132.v2.yml new file mode 100644 index 0000000000..b45a292eb5 --- /dev/null +++ b/packages/icons/changelog/@unreleased/pr-7132.v2.yml @@ -0,0 +1,5 @@ +type: feature +feature: + description: 'feat: new icon - subtract join' + links: + - https://github.com/palantir/blueprint/pull/7132 diff --git a/packages/icons/icons.json b/packages/icons/icons.json index 87679c6cb1..9c1e0402ec 100644 --- a/packages/icons/icons.json +++ b/packages/icons/icons.json @@ -4597,5 +4597,12 @@ "tags": "data, source, data source, sync, cloud", "group": "data", "codepoint": 62353 + }, + { + "displayName": "Join: subtract right", + "iconName": "subtract-right-join", + "tags": "circles, combine, connect, add, subtract, part, slice, intersection, venn diagram, intersect", + "group": "action", + "codepoint": 62354 } ] diff --git a/resources/icons/16px/subtract-right-join.svg b/resources/icons/16px/subtract-right-join.svg new file mode 100644 index 0000000000..057d74a95a --- /dev/null +++ b/resources/icons/16px/subtract-right-join.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/icons/20px/subtract-right-join.svg b/resources/icons/20px/subtract-right-join.svg new file mode 100644 index 0000000000..79d8ffb5c9 --- /dev/null +++ b/resources/icons/20px/subtract-right-join.svg @@ -0,0 +1,4 @@ + + + + From 933856f09ce226ba9c539409e06c01a228ab26bb Mon Sep 17 00:00:00 2001 From: Greg Douglas Date: Fri, 10 Jan 2025 12:39:15 -0500 Subject: [PATCH 03/42] Replace types deprecated in React 18 (#7147) --- packages/core/src/common/utils/reactUtils.ts | 4 ++-- .../core/src/components/non-ideal-state/nonIdealState.tsx | 2 +- packages/core/src/components/overflow-list/overflowList.tsx | 2 +- packages/datetime/test/components/dateInputTests.tsx | 2 +- packages/datetime2/test/components/dateInput3Tests.tsx | 2 +- packages/docs-theme/src/components/navigator.tsx | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/core/src/common/utils/reactUtils.ts b/packages/core/src/common/utils/reactUtils.ts index 661686da38..6ec7a1ab18 100644 --- a/packages/core/src/common/utils/reactUtils.ts +++ b/packages/core/src/common/utils/reactUtils.ts @@ -92,12 +92,12 @@ export function isReactElement(child: React.ReactNode): child is React. ); } -function isReactFragment(child: React.ReactNode): child is React.ReactFragment { +function isReactFragment(child: React.ReactNode): child is Iterable { // bit hacky, but generally works return typeof (child as any).type === "symbol"; } -function isReactNodeArray(child: React.ReactNode): child is React.ReactNodeArray { +function isReactNodeArray(child: React.ReactNode): child is React.ReactNode[] { return Array.isArray(child); } diff --git a/packages/core/src/components/non-ideal-state/nonIdealState.tsx b/packages/core/src/components/non-ideal-state/nonIdealState.tsx index 3e3b588259..98c4b96528 100644 --- a/packages/core/src/components/non-ideal-state/nonIdealState.tsx +++ b/packages/core/src/components/non-ideal-state/nonIdealState.tsx @@ -44,7 +44,7 @@ export interface NonIdealStateProps extends Props { * A longer description of the non-ideal state. * A string or number value will be wrapped in a `
` to preserve margins. */ - description?: React.ReactChild; + description?: React.ReactNode; /** The name of a Blueprint icon or a JSX element (such as ``) to render above the title. */ icon?: IconName | MaybeElement; diff --git a/packages/core/src/components/overflow-list/overflowList.tsx b/packages/core/src/components/overflow-list/overflowList.tsx index 1352e76323..ae5cee90c9 100644 --- a/packages/core/src/components/overflow-list/overflowList.tsx +++ b/packages/core/src/components/overflow-list/overflowList.tsx @@ -99,7 +99,7 @@ export interface OverflowListProps extends Props { * Callback invoked to render each visible item. * Remember to set a `key` on the rendered element! */ - visibleItemRenderer: (item: T, index: number) => React.ReactChild; + visibleItemRenderer: (item: T, index: number) => React.ReactNode; } export interface OverflowListState { diff --git a/packages/datetime/test/components/dateInputTests.tsx b/packages/datetime/test/components/dateInputTests.tsx index a1fd64f452..d1a567a10a 100644 --- a/packages/datetime/test/components/dateInputTests.tsx +++ b/packages/datetime/test/components/dateInputTests.tsx @@ -861,7 +861,7 @@ describe("", () => { input.simulate("blur"); } - function changeSelectDropdown(wrapper: ReactWrapper, className: string, value: React.ReactText) { + function changeSelectDropdown(wrapper: ReactWrapper, className: string, value: string | number) { wrapper .find(`.${className}`) .find("select") diff --git a/packages/datetime2/test/components/dateInput3Tests.tsx b/packages/datetime2/test/components/dateInput3Tests.tsx index e7af32aca4..771de91f4a 100644 --- a/packages/datetime2/test/components/dateInput3Tests.tsx +++ b/packages/datetime2/test/components/dateInput3Tests.tsx @@ -941,7 +941,7 @@ describe("", () => { input.simulate("blur"); } - function changeSelectDropdown(wrapper: ReactWrapper, className: string, value: React.ReactText) { + function changeSelectDropdown(wrapper: ReactWrapper, className: string, value: string | number) { wrapper .find(`.${className}`) .find("select") diff --git a/packages/docs-theme/src/components/navigator.tsx b/packages/docs-theme/src/components/navigator.tsx index acc746a8db..e82614f4be 100644 --- a/packages/docs-theme/src/components/navigator.tsx +++ b/packages/docs-theme/src/components/navigator.tsx @@ -104,7 +104,7 @@ export class Navigator extends React.PureComponent { } // insert caret-right between each path element - const pathElements = section.path.reduce((elems, el) => { + const pathElements = section.path.reduce((elems, el) => { elems.push(el, ); return elems; }, []); From fc04fdaa8bc8b7c8cf7508b34b07780cbc756575 Mon Sep 17 00:00:00 2001 From: Greg Douglas Date: Fri, 10 Jan 2025 12:39:59 -0500 Subject: [PATCH 04/42] Patch upcoming React 18 SFC compile breaks in react-day-picker@7.4.9 (#7146) --- ...eact-day-picker-npm-7.4.9-8853eff118.patch | 31 +++++++++++++++++++ packages/datetime/package.json | 2 +- yarn.lock | 19 +++++++++--- 3 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 .yarn/patches/react-day-picker-npm-7.4.9-8853eff118.patch diff --git a/.yarn/patches/react-day-picker-npm-7.4.9-8853eff118.patch b/.yarn/patches/react-day-picker-npm-7.4.9-8853eff118.patch new file mode 100644 index 0000000000..a40ffecda0 --- /dev/null +++ b/.yarn/patches/react-day-picker-npm-7.4.9-8853eff118.patch @@ -0,0 +1,31 @@ +diff --git a/types/Props.d.ts b/types/Props.d.ts +index f8fcd45ab3048d9b34709eba1797597ac2facdec..3afa05a42f485ff23899f20df2ff9d85bf588eed 100644 +--- a/types/Props.d.ts ++++ b/types/Props.d.ts +@@ -44,7 +44,7 @@ export interface DayPickerProps { + captionElement?: + | React.ReactElement> + | React.ComponentClass +- | React.SFC; ++ | React.FC; + className?: string; + classNames?: ClassNames; + containerProps?: React.DetailedHTMLProps< +@@ -69,7 +69,7 @@ export interface DayPickerProps { + navbarElement?: + | React.ReactElement> + | React.ComponentClass +- | React.SFC; ++ | React.FC; + numberOfMonths?: number; + onBlur?: (e: React.FocusEvent) => void; + onCaptionClick?: (month: Date, e: React.MouseEvent) => void; +@@ -142,7 +142,7 @@ export interface DayPickerProps { + weekdayElement?: + | React.ReactElement> + | React.ComponentClass +- | React.SFC; ++ | React.FC; + weekdaysLong?: string[]; + weekdaysShort?: string[]; + tabIndex?: number; diff --git a/packages/datetime/package.json b/packages/datetime/package.json index 53bcfffcac..1727f7b4d6 100644 --- a/packages/datetime/package.json +++ b/packages/datetime/package.json @@ -46,7 +46,7 @@ "classnames": "^2.3.1", "date-fns": "^2.28.0", "date-fns-tz": "^2.0.0", - "react-day-picker": "7.4.9", + "react-day-picker": "patch:react-day-picker@npm%3A7.4.9#~/.yarn/patches/react-day-picker-npm-7.4.9-8853eff118.patch", "tslib": "~2.6.2" }, "peerDependencies": { diff --git a/yarn.lock b/yarn.lock index becfb2da2a..18dededfea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -540,7 +540,7 @@ __metadata: mocha: "npm:^10.2.0" npm-run-all: "npm:^4.1.5" react: "npm:^16.14.0" - react-day-picker: "npm:7.4.9" + react-day-picker: "patch:react-day-picker@npm%3A7.4.9#~/.yarn/patches/react-day-picker-npm-7.4.9-8853eff118.patch" react-dom: "npm:^16.14.0" react-test-renderer: "npm:^16.14.0" tslib: "npm:~2.6.2" @@ -13571,12 +13571,23 @@ __metadata: linkType: hard "react-day-picker@npm:^8.10.0": - version: 8.10.0 - resolution: "react-day-picker@npm:8.10.0" + version: 8.10.1 + resolution: "react-day-picker@npm:8.10.1" peerDependencies: date-fns: ^2.28.0 || ^3.0.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: e9868aced1e40b4cb7d6cf8d50e250226b38ec7ebea944b65aa9db20a0f36d0581b8a501297d09dcbf2d812de852168952b3fb27990915381629d6e314c2a4d8 + checksum: a0ff28c4b61b3882e6a825b19e5679e2fdf3256cf1be8eb0a0c028949815c1ae5a6561474c2c19d231c010c8e0e0b654d3a322610881e0655abca05a2e03d9df + languageName: node + linkType: hard + +"react-day-picker@patch:react-day-picker@npm%3A7.4.9#~/.yarn/patches/react-day-picker-npm-7.4.9-8853eff118.patch": + version: 7.4.9 + resolution: "react-day-picker@patch:react-day-picker@npm%3A7.4.9#~/.yarn/patches/react-day-picker-npm-7.4.9-8853eff118.patch::version=7.4.9&hash=5478a6" + dependencies: + prop-types: "npm:^15.6.2" + peerDependencies: + react: ~0.13.x || ~0.14.x || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 8a64e55f67a824d2a1bea0691ea082b710049ab07e747dbf9bd647e921ba22884597155514e08a6d60ff4231c788d51ac35870fa11573bd550aa803e7b223bc9 languageName: node linkType: hard From deb77177ac9c37f370729bbcc6818cafb28f7054 Mon Sep 17 00:00:00 2001 From: Greg Douglas Date: Fri, 10 Jan 2025 12:40:27 -0500 Subject: [PATCH 05/42] Wrap tests in TestUtils.act(...) (#7148) --- .../core/test/controls/numericInputTests.tsx | 12 ++- .../test/editable-text/editableTextTests.tsx | 13 ++- .../multistep-dialog/multistepDialogTests.tsx | 5 +- packages/core/test/popover/popoverTests.tsx | 5 +- packages/core/test/tabs/tabsTests.tsx | 9 +- .../core/test/tag-input/tagInputTests.tsx | 41 ++++++--- .../test/components/dateRangeInputTests.tsx | 81 ++++++++++++---- .../test/components/timezoneSelectTests.tsx | 5 +- .../test/components/dateRangeInput3Tests.tsx | 92 +++++++++++++++---- packages/select/test/multiSelectTests.tsx | 5 +- packages/select/test/queryListTests.tsx | 22 ++++- packages/select/test/selectTests.tsx | 5 +- packages/select/test/suggestTests.tsx | 9 +- packages/table/test/editableCell2Tests.tsx | 17 +++- packages/table/test/editableCellTests.tsx | 17 +++- packages/table/test/table2Tests.tsx | 12 ++- packages/table/test/tableTests.tsx | 19 +++- 17 files changed, 280 insertions(+), 89 deletions(-) diff --git a/packages/core/test/controls/numericInputTests.tsx b/packages/core/test/controls/numericInputTests.tsx index 3578c60152..afd7175efe 100644 --- a/packages/core/test/controls/numericInputTests.tsx +++ b/packages/core/test/controls/numericInputTests.tsx @@ -22,6 +22,7 @@ import { shallow as untypedShallow, } from "enzyme"; import * as React from "react"; +import * as TestUtils from "react-dom/test-utils"; import { type SinonStub, spy, stub } from "sinon"; import { dispatchMouseEvent } from "@blueprintjs/test-commons"; @@ -1097,8 +1098,11 @@ describe("", () => { incrementButton.simulate("mousedown", { shiftKey: true }); expect(component.find("input").prop("value")).to.equal("1.101"); - // one significant digit too many - setNextValue(component, "1.0001"); + TestUtils.act(() => { + // one significant digit too many + setNextValue(component, "1.0001"); + }); + incrementButton.simulate("mousedown", { altKey: true }); expect(component.find("input").prop("value")).to.equal("1.001"); }); @@ -1361,7 +1365,9 @@ describe("", () => { minorStepSize: null, }); - setNextValue(component, "3e2"); // i.e. 300 + TestUtils.act(() => { + setNextValue(component, "3e2"); // i.e. 300 + }); simulateIncrement(component); diff --git a/packages/core/test/editable-text/editableTextTests.tsx b/packages/core/test/editable-text/editableTextTests.tsx index 98bc498965..b86e6334f1 100644 --- a/packages/core/test/editable-text/editableTextTests.tsx +++ b/packages/core/test/editable-text/editableTextTests.tsx @@ -17,6 +17,7 @@ import { assert } from "chai"; import { mount, type ReactWrapper, shallow } from "enzyme"; import * as React from "react"; +import * as TestUtils from "react-dom/test-utils"; import { spy } from "sinon"; import { EditableText } from "../../src"; @@ -228,15 +229,21 @@ describe("", () => { const confirmSpy = spy(); const wrapper = mount(); simulateHelper(wrapper, "control", { ctrlKey: true, key: "Enter" }); - wrapper.setState({ isEditing: true }); + TestUtils.act(() => { + wrapper.setState({ isEditing: true }); + }); simulateHelper(wrapper, "meta", { metaKey: true, key: "Enter" }); - wrapper.setState({ isEditing: true }); + TestUtils.act(() => { + wrapper.setState({ isEditing: true }); + }); simulateHelper(wrapper, "shift", { key: "Enter", preventDefault: (): void => undefined, shiftKey: true, }); - wrapper.setState({ isEditing: true }); + TestUtils.act(() => { + wrapper.setState({ isEditing: true }); + }); simulateHelper(wrapper, "alt", { altKey: true, key: "Enter", diff --git a/packages/core/test/multistep-dialog/multistepDialogTests.tsx b/packages/core/test/multistep-dialog/multistepDialogTests.tsx index 8d15121bde..290d0f6bb4 100644 --- a/packages/core/test/multistep-dialog/multistepDialogTests.tsx +++ b/packages/core/test/multistep-dialog/multistepDialogTests.tsx @@ -17,6 +17,7 @@ import { assert } from "chai"; import { mount, type ReactWrapper } from "enzyme"; import * as React from "react"; +import * as TestUtils from "react-dom/test-utils"; import { dispatchTestKeyboardEvent } from "@blueprintjs/test-commons"; @@ -178,7 +179,9 @@ describe("", () => { assert.strictEqual(dialog.state("selectedIndex"), 1); const step = dialog.find(`.${Classes.DIALOG_STEP}`); step.at(0).simulate("focus"); - dispatchTestKeyboardEvent(step.at(0).getDOMNode(), "keydown", "Enter"); + TestUtils.act(() => { + dispatchTestKeyboardEvent(step.at(0).getDOMNode(), "keydown", "Enter"); + }); assert.strictEqual(dialog.state("selectedIndex"), 0); dialog.unmount(); testsContainerElement.remove(); diff --git a/packages/core/test/popover/popoverTests.tsx b/packages/core/test/popover/popoverTests.tsx index 074082748c..e9017418ed 100644 --- a/packages/core/test/popover/popoverTests.tsx +++ b/packages/core/test/popover/popoverTests.tsx @@ -17,6 +17,7 @@ import { assert } from "chai"; import { mount, type ReactWrapper, shallow } from "enzyme"; import * as React from "react"; +import * as TestUtils from "react-dom/test-utils"; import sinon from "sinon"; import { dispatchMouseEvent } from "@blueprintjs/test-commons"; @@ -153,7 +154,9 @@ describe("", () => { it("adds POPOVER_OPEN class to target when the popover is open", () => { wrapper = renderPopover(); assert.isFalse(wrapper.findClass(Classes.POPOVER_TARGET).hasClass(Classes.POPOVER_OPEN)); - wrapper.setState({ isOpen: true }); + TestUtils.act(() => { + wrapper?.setState({ isOpen: true }); + }); assert.isTrue(wrapper.findClass(Classes.POPOVER_TARGET).hasClass(Classes.POPOVER_OPEN)); }); diff --git a/packages/core/test/tabs/tabsTests.tsx b/packages/core/test/tabs/tabsTests.tsx index e23b35d271..f815915dc1 100644 --- a/packages/core/test/tabs/tabsTests.tsx +++ b/packages/core/test/tabs/tabsTests.tsx @@ -16,6 +16,7 @@ import { assert } from "chai"; import { mount, type ReactWrapper } from "enzyme"; import * as React from "react"; +import * as TestUtils from "react-dom/test-utils"; import { spy } from "sinon"; import { Classes } from "../../src/common"; @@ -81,7 +82,9 @@ describe("", () => { it("renders all Tab children, active is not aria-hidden", () => { const activeIndex = 1; const wrapper = mount({getTabsContents()}); - wrapper.setState({ selectedTabId: TAB_IDS[activeIndex] }); + TestUtils.act(() => { + wrapper.setState({ selectedTabId: TAB_IDS[activeIndex] }); + }); const tabPanels = wrapper.find(TAB_PANEL_SELECTOR); assert.lengthOf(tabPanels, 3); for (let i = 0; i < TAB_IDS.length; i++) { @@ -160,7 +163,9 @@ describe("", () => { , ); for (const selectedTabId of TAB_IDS) { - wrapper.setState({ selectedTabId }); + TestUtils.act(() => { + wrapper.setState({ selectedTabId }); + }); assert.lengthOf(wrapper.find("strong"), 1); } }); diff --git a/packages/core/test/tag-input/tagInputTests.tsx b/packages/core/test/tag-input/tagInputTests.tsx index f442fba142..d47271891f 100644 --- a/packages/core/test/tag-input/tagInputTests.tsx +++ b/packages/core/test/tag-input/tagInputTests.tsx @@ -17,6 +17,7 @@ import { assert, expect } from "chai"; import { type MountRendererProps, type ReactWrapper, mount as untypedMount } from "enzyme"; import * as React from "react"; +import * as TestUtils from "react-dom/test-utils"; import sinon from "sinon"; import { Button, Classes, Intent, Tag, TagInput, type TagInputProps } from "../../src"; @@ -211,24 +212,30 @@ describe("", () => { it("does not clear the input if onAdd returns false", () => { const onAdd = sinon.stub().returns(false); const wrapper = mountTagInput(onAdd); - wrapper.setState({ inputValue: NEW_VALUE }); - pressEnterInInput(wrapper, NEW_VALUE); + TestUtils.act(() => { + wrapper.setState({ inputValue: NEW_VALUE }); + pressEnterInInput(wrapper, NEW_VALUE); + }); assert.strictEqual(wrapper.state().inputValue, NEW_VALUE); }); it("clears the input if onAdd returns true", () => { const onAdd = sinon.stub().returns(true); const wrapper = mountTagInput(onAdd); - wrapper.setState({ inputValue: NEW_VALUE }); - pressEnterInInput(wrapper, NEW_VALUE); + TestUtils.act(() => { + wrapper.setState({ inputValue: NEW_VALUE }); + pressEnterInInput(wrapper, NEW_VALUE); + }); assert.strictEqual(wrapper.state().inputValue, ""); }); it("clears the input if onAdd returns nothing", () => { const onAdd = sinon.stub(); const wrapper = mountTagInput(onAdd); - wrapper.setState({ inputValue: NEW_VALUE }); - pressEnterInInput(wrapper, NEW_VALUE); + TestUtils.act(() => { + wrapper.setState({ inputValue: NEW_VALUE }); + pressEnterInInput(wrapper, NEW_VALUE); + }); assert.strictEqual(wrapper.state().inputValue, ""); }); @@ -382,24 +389,30 @@ describe("", () => { it("does not clear the input if onChange returns false", () => { const onChange = sinon.stub().returns(false); const wrapper = mount(); - wrapper.setState({ inputValue: NEW_VALUE }); - pressEnterInInput(wrapper, NEW_VALUE); + TestUtils.act(() => { + wrapper.setState({ inputValue: NEW_VALUE }); + pressEnterInInput(wrapper, NEW_VALUE); + }); assert.strictEqual(wrapper.state().inputValue, NEW_VALUE); }); it("clears the input if onChange returns true", () => { const onChange = sinon.stub().returns(true); const wrapper = mount(); - wrapper.setState({ inputValue: NEW_VALUE }); - pressEnterInInput(wrapper, NEW_VALUE); + TestUtils.act(() => { + wrapper.setState({ inputValue: NEW_VALUE }); + pressEnterInInput(wrapper, NEW_VALUE); + }); assert.strictEqual(wrapper.state().inputValue, ""); }); it("clears the input if onChange returns nothing", () => { const onChange = sinon.spy(); const wrapper = mount(); - wrapper.setState({ inputValue: NEW_VALUE }); - pressEnterInInput(wrapper, NEW_VALUE); + TestUtils.act(() => { + wrapper.setState({ inputValue: NEW_VALUE }); + pressEnterInInput(wrapper, NEW_VALUE); + }); assert.strictEqual(wrapper.state().inputValue, ""); }); @@ -610,7 +623,9 @@ function runKeyPressTest(callbackName: "onKeyDown" | "onKeyUp", startIndex: numb const inputProps = { [callbackName]: sinon.spy() }; const wrapper = mount(); - wrapper.setState({ activeIndex: startIndex }); + TestUtils.act(() => { + wrapper.setState({ activeIndex: startIndex }); + }); const eventName = callbackName === "onKeyDown" ? "keydown" : "keyup"; wrapper.find("input").simulate("focus").simulate(eventName, { key: "Enter" }); diff --git a/packages/datetime/test/components/dateRangeInputTests.tsx b/packages/datetime/test/components/dateRangeInputTests.tsx index 018d4aecab..607e19f3a9 100644 --- a/packages/datetime/test/components/dateRangeInputTests.tsx +++ b/packages/datetime/test/components/dateRangeInputTests.tsx @@ -139,7 +139,9 @@ describe("", () => { popoverProps={{ className: CLASS_2, usePortal: false }} />, ); - wrapper.setState({ isOpen: true }); + TestUtils.act(() => { + wrapper.setState({ isOpen: true }); + }); const popoverTarget = wrapper.find(`.${CoreClasses.POPOVER_TARGET}`).hostNodes(); expect(popoverTarget.hasClass(CLASS_1)).to.be.true; @@ -148,7 +150,9 @@ describe("", () => { it("inner DateRangePicker receives all supported props", () => { const component = mount(); - component.setState({ isOpen: true }); + TestUtils.act(() => { + component.setState({ isOpen: true }); + }); component.update(); const picker = component.find(DateRangePicker); expect(picker.prop("locale")).to.equal("uk"); @@ -168,7 +172,9 @@ describe("", () => { it(" should not lose focus on increment/decrement with up/down arrows", () => { const { root } = wrap(, true); - root.setState({ isOpen: true }); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); expect(root.find(Popover).prop("isOpen")).to.be.true; keyDownOnInput(Classes.TIMEPICKER_HOUR, "ArrowUp"); @@ -182,13 +188,17 @@ describe("", () => { true, ); - root.setState({ isOpen: true }); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); root.update(); getDayElement(1).simulate("click"); getDayElement(10).simulate("click"); - root.setState({ isOpen: true }); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); root.update(); keyDownOnInput(Classes.TIMEPICKER_HOUR, "ArrowUp"); @@ -199,7 +209,9 @@ describe("", () => { it("when timePrecision != null && closeOnSelection=true && end values is changed directly (without setting the selectedEnd date) - popover should not close", () => { const { root } = wrap(, true); - root.setState({ isOpen: true }); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); keyDownOnInput(Classes.TIMEPICKER_HOUR, "ArrowUp"); root.update(); keyDownOnInput(Classes.TIMEPICKER_HOUR, "ArrowUp", 1); @@ -372,7 +384,9 @@ describe("", () => { describe("closeOnSelection", () => { it("if closeOnSelection=false, popover stays open when full date range is selected", () => { const { root, getDayElement } = wrap(, true); - root.setState({ isOpen: true }); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); root.update(); getDayElement(1).simulate("click"); getDayElement(10).simulate("click"); @@ -382,7 +396,9 @@ describe("", () => { it("if closeOnSelection=true, popover closes when full date range is selected", () => { const { root, getDayElement } = wrap(, true); - root.setState({ isOpen: true }); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); root.update(); getDayElement(1).simulate("click"); getDayElement(10).simulate("click"); @@ -395,7 +411,9 @@ describe("", () => { , true, ); - root.setState({ isOpen: true }); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); root.update(); getDayElement(1).simulate("click"); getDayElement(10).simulate("click"); @@ -407,21 +425,27 @@ describe("", () => { it("accepts contiguousCalendarMonths prop and passes it to the date range picker", () => { const { root } = wrap(); - root.setState({ isOpen: true }); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); root.update(); expect(root.find(DateRangePicker).prop("contiguousCalendarMonths")).to.be.false; }); it("accepts singleMonthOnly prop and passes it to the date range picker", () => { const { root } = wrap(); - root.setState({ isOpen: true }); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); root.update(); expect(root.find(DateRangePicker).prop("singleMonthOnly")).to.be.false; }); it("accepts shortcuts prop and passes it to the date range picker", () => { const { root } = wrap(); - root.setState({ isOpen: true }); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); root.update(); expect(root.find(DateRangePicker).prop("shortcuts")).to.be.false; }); @@ -430,7 +454,9 @@ describe("", () => { const selectedShortcut = 1; const { root } = wrap(); - root.setState({ isOpen: true }); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); root.update(); root.find(DateRangePicker) .find(`.${Classes.DATERANGEPICKER_SHORTCUTS}`) @@ -491,7 +517,9 @@ describe("", () => { true, ); - root.setState({ isOpen: true }); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); // getDay is 0-indexed, but getDayElement is 1-indexed getDayElement(START_DATE_2.getDay() + 1).simulate("mouseenter"); @@ -577,7 +605,9 @@ describe("", () => { const startInputProps = { onKeyDown: sinon.spy() }; const endInputProps = { onKeyDown: sinon.spy() }; const { root } = wrap(); - root.setState({ isOpen: true }); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); // Don't save the input elements into variables; they can become // stale across React updates. @@ -604,7 +634,9 @@ describe("", () => { it("pressing Escape closes the popover", () => { const { root } = wrap(); - root.setState({ isOpen: true }); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); const startInput = getStartInput(root); startInput.simulate("focus"); @@ -629,7 +661,9 @@ describe("", () => { onChange={onChange} />, ); - root.setState({ isOpen: true }); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); root.update(); getDayElement(END_DAY).simulate("click"); @@ -1107,7 +1141,9 @@ describe("", () => { beforeEach(() => { // need to set wasLastFocusChangeDueToHover=false to fully reset state between tests. - root.setState({ isOpen: true, wasLastFocusChangeDueToHover: false }); + TestUtils.act(() => { + root.setState({ isOpen: true, wasLastFocusChangeDueToHover: false }); + }); // clear the inputs to start from a fresh state, but do so // *after* opening the popover so that the calendar doesn't // move away from the view we expect for these tests. @@ -2288,6 +2324,7 @@ describe("", () => { }); }); + // HERE describe("when controlled", () => { it("Setting value causes defaultValue to be ignored", () => { const { root } = wrap(); @@ -2321,7 +2358,9 @@ describe("", () => { it("Updating value changes the text accordingly in both fields", () => { const { root } = wrap(); - root.setState({ isOpen: true }); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); root.update(); root.setProps({ value: DATE_RANGE_2 }); root.update(); @@ -2333,7 +2372,9 @@ describe("", () => { it.skip("Pressing Enter saves the inputted date and closes the popover", () => { const onChange = sinon.spy(); const { root } = wrap(); - root.setState({ isOpen: true }); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); const startInput = getStartInput(root); startInput.simulate("focus"); diff --git a/packages/datetime/test/components/timezoneSelectTests.tsx b/packages/datetime/test/components/timezoneSelectTests.tsx index c250ab5d51..6f31865a3d 100644 --- a/packages/datetime/test/components/timezoneSelectTests.tsx +++ b/packages/datetime/test/components/timezoneSelectTests.tsx @@ -17,6 +17,7 @@ import { assert } from "chai"; import { mount, type ReactWrapper } from "enzyme"; import * as React from "react"; +import * as TestUtils from "react-dom/test-utils"; import * as sinon from "sinon"; import { @@ -78,7 +79,9 @@ describe("", () => { it("if query is not empty, shows all items", () => { const timezoneSelect = mountTS(); - timezoneSelect.setState({ query: "not empty" }); + TestUtils.act(() => { + timezoneSelect.setState({ query: "not empty" }); + }); timezoneSelect.update(); const items = timezoneSelect.find(Select).prop("items"); assert.lengthOf(items, TIMEZONE_ITEMS.length); diff --git a/packages/datetime2/test/components/dateRangeInput3Tests.tsx b/packages/datetime2/test/components/dateRangeInput3Tests.tsx index 67331a0e24..25ccd2ffa9 100644 --- a/packages/datetime2/test/components/dateRangeInput3Tests.tsx +++ b/packages/datetime2/test/components/dateRangeInput3Tests.tsx @@ -153,7 +153,9 @@ describe("", () => { popoverProps={{ className: CLASS_2, usePortal: false }} />, ); - wrapper.setState({ isOpen: true }); + TestUtils.act(() => { + wrapper.setState({ isOpen: true }); + }); const popoverTarget = wrapper.find(`.${CoreClasses.POPOVER_TARGET}`).hostNodes(); expect(popoverTarget.hasClass(CLASS_1)).to.be.true; @@ -162,7 +164,9 @@ describe("", () => { it("inner DateRangePicker3 receives all supported props", () => { const component = mount(); - component.setState({ isOpen: true }); + TestUtils.act(() => { + component.setState({ isOpen: true }); + }); component.update(); const picker = component.find(DateRangePicker3); expect(picker.prop("locale")).to.equal("uk"); @@ -182,7 +186,10 @@ describe("", () => { it(" should not lose focus on increment/decrement with up/down arrows", () => { const { root } = wrap(, true); - root.setState({ isOpen: true }).update(); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); + root.update(); expect(root.find(Popover).prop("isOpen"), "Popover isOpen").to.be.true; keyDownOnInput(DatetimeClasses.TIMEPICKER_HOUR, "ArrowUp"); @@ -196,12 +203,18 @@ describe("", () => { true, ); - root.setState({ isOpen: true }).update(); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); + root.update(); getDayElement(1).simulate("click"); getDayElement(10).simulate("click"); - root.setState({ isOpen: true }).update(); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); + root.update(); keyDownOnInput(DatetimeClasses.TIMEPICKER_HOUR, "ArrowUp"); root.update(); @@ -211,7 +224,9 @@ describe("", () => { it("when timePrecision != null && closeOnSelection=true && end values is changed directly (without setting the selectedEnd date) - popover should not close", () => { const { root } = wrap(, true); - root.setState({ isOpen: true }); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); keyDownOnInput(DatetimeClasses.TIMEPICKER_HOUR, "ArrowUp"); root.update(); keyDownOnInput(DatetimeClasses.TIMEPICKER_HOUR, "ArrowUp", 1); @@ -384,7 +399,10 @@ describe("", () => { describe("closeOnSelection", () => { it("if closeOnSelection=false, popover stays open when full date range is selected", () => { const { root, getDayElement } = wrap(, true); - root.setState({ isOpen: true }).update(); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); + root.update(); getDayElement(1).simulate("click"); getDayElement(10).simulate("click"); expect(root.state("isOpen")).to.be.true; @@ -393,7 +411,10 @@ describe("", () => { it("if closeOnSelection=true, popover closes when full date range is selected", () => { const { root, getDayElement } = wrap(, true); - root.setState({ isOpen: true }).update(); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); + root.update(); getDayElement(1).simulate("click"); getDayElement(10).simulate("click"); expect(root.state("isOpen")).to.be.false; @@ -405,7 +426,10 @@ describe("", () => { , true, ); - root.setState({ isOpen: true }).update(); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); + root.update(); getDayElement(1).simulate("click"); getDayElement(10).simulate("click"); root.update(); @@ -416,19 +440,28 @@ describe("", () => { it("accepts contiguousCalendarMonths prop and passes it to the date range picker", () => { const { root } = wrap(); - root.setState({ isOpen: true }).update(); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); + root.update(); expect(root.find(DateRangePicker3).prop("contiguousCalendarMonths")).to.be.false; }); it("accepts singleMonthOnly prop and passes it to the date range picker", () => { const { root } = wrap(); - root.setState({ isOpen: true }).update(); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); + root.update(); expect(root.find(DateRangePicker3).prop("singleMonthOnly")).to.be.false; }); it("accepts shortcuts prop and passes it to the date range picker", () => { const { root } = wrap(); - root.setState({ isOpen: true }).update(); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); + root.update(); expect(root.find(DateRangePicker3).prop("shortcuts")).to.be.false; }); @@ -436,7 +469,10 @@ describe("", () => { const selectedShortcut = 1; const { root } = wrap(); - root.setState({ isOpen: true }).update(); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); + root.update(); root.find(DateRangePicker3) .find(`.${DatetimeClasses.DATERANGEPICKER_SHORTCUTS}`) .find("a") @@ -496,7 +532,9 @@ describe("", () => { true, ); - root.setState({ isOpen: true }); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); // getDay is 0-indexed, but getDayElement is 1-indexed getDayElement(START_DATE_2.getDay() + 1).simulate("mouseenter"); @@ -582,7 +620,9 @@ describe("", () => { const startInputProps = { onKeyDown: sinon.spy() }; const endInputProps = { onKeyDown: sinon.spy() }; const { root } = wrap(); - root.setState({ isOpen: true }); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); // Don't save the input elements into variables; they can become // stale across React updates. @@ -619,7 +659,10 @@ describe("", () => { onChange={onChange} />, ); - root.setState({ isOpen: true }).update(); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); + root.update(); getDayElement(END_DAY).simulate("click"); assertDateRangesEqual(onChange.getCall(0).args[0], [START_STR, END_STR]); @@ -1381,7 +1424,9 @@ describe("", () => { beforeEach(() => { // need to set wasLastFocusChangeDueToHover=false to fully reset state between tests. - root.setState({ isOpen: true, wasLastFocusChangeDueToHover: false }); + TestUtils.act(() => { + root.setState({ isOpen: true, wasLastFocusChangeDueToHover: false }); + }); // clear the inputs to start from a fresh state, but do so // *after* opening the popover so that the calendar doesn't // move away from the view we expect for these tests. @@ -2595,7 +2640,10 @@ describe("", () => { it("Updating value changes the text accordingly in both fields", () => { const { root } = wrap(); - root.setState({ isOpen: true }).update(); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); + root.update(); root.setProps({ value: DATE_RANGE_2 }).update(); assertInputValuesEqual(root, START_STR_2, END_STR_2); }); @@ -2605,7 +2653,9 @@ describe("", () => { it.skip("Pressing Enter saves the inputted date and closes the popover", () => { const onChange = sinon.spy(); const { root } = wrap(); - root.setState({ isOpen: true }); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); const startInput = getStartInput(root); startInput.simulate("focus"); @@ -2631,7 +2681,9 @@ describe("", () => { it("pressing Escape closes the popover", () => { const { root } = wrap(); - root.setState({ isOpen: true }); + TestUtils.act(() => { + root.setState({ isOpen: true }); + }); const startInput = getStartInput(root); startInput.simulate("focus"); diff --git a/packages/select/test/multiSelectTests.tsx b/packages/select/test/multiSelectTests.tsx index a56cefb9e9..54ba3c3cd2 100644 --- a/packages/select/test/multiSelectTests.tsx +++ b/packages/select/test/multiSelectTests.tsx @@ -17,6 +17,7 @@ import { assert } from "chai"; import { type HTMLAttributes, mount, type ReactWrapper } from "enzyme"; import * as React from "react"; +import * as TestUtils from "react-dom/test-utils"; import sinon from "sinon"; import { Button, Classes as CoreClasses, Popover, Tag } from "@blueprintjs/core"; @@ -174,7 +175,9 @@ describe("", () => { , ); if (query !== undefined) { - wrapper.setState({ query }); + TestUtils.act(() => { + wrapper.setState({ query }); + }); } return wrapper; } diff --git a/packages/select/test/queryListTests.tsx b/packages/select/test/queryListTests.tsx index 87dccf112d..8c175fd715 100644 --- a/packages/select/test/queryListTests.tsx +++ b/packages/select/test/queryListTests.tsx @@ -17,6 +17,7 @@ import { assert } from "chai"; import { mount, type ReactWrapper, shallow } from "enzyme"; import * as React from "react"; +import * as TestUtils from "react-dom/test-utils"; import sinon from "sinon"; import { Menu } from "@blueprintjs/core"; @@ -117,8 +118,12 @@ describe("", () => { const filmQueryList = mount( {...testProps} items={[myItem]} activeItem={myItem} query="" />, ); - filmQueryList.setState({ query: "query" }); - filmQueryList.setState({ activeItem: undefined }); + TestUtils.act(() => { + filmQueryList.setState({ query: "query" }); + }); + TestUtils.act(() => { + filmQueryList.setState({ activeItem: undefined }); + }); assert.equal(testProps.onActiveItemChange.callCount, 0); }); @@ -272,7 +277,9 @@ describe("", () => { const pastedValue2 = item2.title; const pastedValue3 = item3.title; - handlePaste([pastedValue1, pastedValue2, pastedValue3]); + TestUtils.act(() => { + handlePaste([pastedValue1, pastedValue2, pastedValue3]); + }); assert.isTrue(onItemsPaste.calledOnce); // Emits all three items. @@ -293,7 +300,9 @@ describe("", () => { const pastedValue3 = "unrecognized2"; const pastedValue4 = item4.title; - handlePaste([pastedValue1, pastedValue2, pastedValue3, pastedValue4]); + TestUtils.act(() => { + handlePaste([pastedValue1, pastedValue2, pastedValue3, pastedValue4]); + }); assert.isTrue(onItemsPaste.calledOnce); // Emits just the 2 valid items. @@ -325,7 +334,10 @@ describe("", () => { // Paste this item last. const pastedValue3 = "unrecognized"; - handlePaste([pastedValue1, pastedValue2, pastedValue3]); + TestUtils.act(() => { + handlePaste([pastedValue1, pastedValue2, pastedValue3]); + }); + const createdItem = { title: "unrecognized", rank: createdRank, year: createdYear }; assert.isTrue(onItemsPaste.calledOnce); diff --git a/packages/select/test/selectTests.tsx b/packages/select/test/selectTests.tsx index c23c80b7e0..3736b8fdb9 100644 --- a/packages/select/test/selectTests.tsx +++ b/packages/select/test/selectTests.tsx @@ -17,6 +17,7 @@ import { assert } from "chai"; import { type HTMLAttributes, mount, type ReactWrapper } from "enzyme"; import * as React from "react"; +import * as TestUtils from "react-dom/test-utils"; import * as sinon from "sinon"; import { Button, Classes, InputGroup, MenuItem, Popover } from "@blueprintjs/core"; @@ -187,7 +188,9 @@ describe("", () => { }); it("inputProps value and onChange are ignored", () => { - const inputProps = { value: "nailed it", onChange: sinon.spy() }; + const inputProps = { onChange: sinon.spy(), value: "nailed it" }; // @ts-expect-error - value and onChange are now omitted from the props type const input = select({ inputProps }).find("input"); assert.notEqual(input.prop("onChange"), inputProps.onChange); @@ -106,7 +106,7 @@ describe("