From 495296602adc441f38c19325d74dc6a53260a78f Mon Sep 17 00:00:00 2001 From: Laurent Nguyen Date: Mon, 3 Jun 2024 15:23:09 +0200 Subject: [PATCH 01/20] Fix delete documents on mongo bug (#1852) --- src/Explorer/Tabs/DocumentsTabV2/DocumentsTabV2.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Explorer/Tabs/DocumentsTabV2/DocumentsTabV2.tsx b/src/Explorer/Tabs/DocumentsTabV2/DocumentsTabV2.tsx index 4a78919db..35c3f8221 100644 --- a/src/Explorer/Tabs/DocumentsTabV2/DocumentsTabV2.tsx +++ b/src/Explorer/Tabs/DocumentsTabV2/DocumentsTabV2.tsx @@ -845,8 +845,8 @@ export const DocumentsTabComponent: React.FunctionComponent { - const deletedRids = new Set(deletedIds.map((documentId) => documentId.rid)); - const newDocumentIds = [...documentIds.filter((documentId) => !deletedRids.has(documentId.rid))]; + const deletedIdsSet = new Set(deletedIds.map((documentId) => documentId.id)); + const newDocumentIds = [...documentIds.filter((documentId) => !deletedIdsSet.has(documentId.id))]; setDocumentIds(newDocumentIds); setSelectedDocumentContent(undefined); From b76d83d8e1112f57bf6ddb1e944d48319cb6e045 Mon Sep 17 00:00:00 2001 From: Laurent Nguyen Date: Mon, 3 Jun 2024 19:11:16 +0200 Subject: [PATCH 02/20] Disable table selection for Fabric/read-only (#1855) * Disable table selection for Fabric/read-only * Update unit tests * Fix format --------- Co-authored-by: Laurent Nguyen --- .../Tabs/DocumentsTabV2/DocumentsTabV2.tsx | 3 + .../DocumentsTableComponent.test.tsx | 8 + .../DocumentsTableComponent.tsx | 30 +- .../DocumentsTabV2.test.tsx.snap | 1 + .../DocumentsTableComponent.test.tsx.snap | 981 ++++++++++++++++++ 5 files changed, 1011 insertions(+), 12 deletions(-) diff --git a/src/Explorer/Tabs/DocumentsTabV2/DocumentsTabV2.tsx b/src/Explorer/Tabs/DocumentsTabV2/DocumentsTabV2.tsx index 35c3f8221..9cfc4301d 100644 --- a/src/Explorer/Tabs/DocumentsTabV2/DocumentsTabV2.tsx +++ b/src/Explorer/Tabs/DocumentsTabV2/DocumentsTabV2.tsx @@ -1779,6 +1779,9 @@ export const DocumentsTabComponent: React.FunctionComponent {tableItems.length > 0 && ( { idHeader: ID_HEADER, partitionKeyHeaders: [PARTITION_KEY_HEADER], }, + isSelectionDisabled: false, }); it("should render documents and partition keys in header", () => { @@ -31,4 +32,11 @@ describe("DocumentsTableComponent", () => { const wrapper = mount(); expect(wrapper).toMatchSnapshot(); }); + + it("should not render selection column when isSelectionDisabled is true", () => { + const props: IDocumentsTableComponentProps = createMockProps(); + props.isSelectionDisabled = true; + const wrapper = mount(); + expect(wrapper).toMatchSnapshot(); + }); }); diff --git a/src/Explorer/Tabs/DocumentsTabV2/DocumentsTableComponent.tsx b/src/Explorer/Tabs/DocumentsTabV2/DocumentsTableComponent.tsx index 4089b12bd..d66568875 100644 --- a/src/Explorer/Tabs/DocumentsTabV2/DocumentsTableComponent.tsx +++ b/src/Explorer/Tabs/DocumentsTabV2/DocumentsTableComponent.tsx @@ -41,6 +41,7 @@ export interface IDocumentsTableComponentProps { size: { height: number; width: number }; columnHeaders: ColumnHeaders; style?: React.CSSProperties; + isSelectionDisabled?: boolean; } interface TableRowData extends RowStateBase { @@ -61,6 +62,7 @@ export const DocumentsTableComponent: React.FC = style, size, columnHeaders, + isSelectionDisabled, }: IDocumentsTableComponentProps) => { const [activeItemIndex, setActiveItemIndex] = React.useState(undefined); @@ -127,12 +129,14 @@ export const DocumentsTableComponent: React.FC = const { item, selected, appearance, onClick, onKeyDown } = data[index]; return ( - + {!isSelectionDisabled && ( + + )} {columns.map((column) => ( = - + {!isSelectionDisabled && ( + + )} {columns.map((column /* index */) => ( diff --git a/src/Explorer/Tabs/DocumentsTabV2/__snapshots__/DocumentsTabV2.test.tsx.snap b/src/Explorer/Tabs/DocumentsTabV2/__snapshots__/DocumentsTabV2.test.tsx.snap index 0b4c7578e..52bc636a8 100644 --- a/src/Explorer/Tabs/DocumentsTabV2/__snapshots__/DocumentsTabV2.test.tsx.snap +++ b/src/Explorer/Tabs/DocumentsTabV2/__snapshots__/DocumentsTabV2.test.tsx.snap @@ -532,6 +532,7 @@ exports[`Documents tab (noSql API) when rendered should render the page 1`] = ` "partitionKeyHeaders": Array [], } } + isSelectionDisabled={true} items={Array []} onItemClicked={[Function]} onSelectedRowsChange={[Function]} diff --git a/src/Explorer/Tabs/DocumentsTabV2/__snapshots__/DocumentsTableComponent.test.tsx.snap b/src/Explorer/Tabs/DocumentsTabV2/__snapshots__/DocumentsTableComponent.test.tsx.snap index 6e80eac9f..44eb5db8b 100644 --- a/src/Explorer/Tabs/DocumentsTabV2/__snapshots__/DocumentsTableComponent.test.tsx.snap +++ b/src/Explorer/Tabs/DocumentsTabV2/__snapshots__/DocumentsTableComponent.test.tsx.snap @@ -1,5 +1,985 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`DocumentsTableComponent should not render selection column when isSelectionDisabled is true 1`] = ` + +
+
+ +
+ +
+ + + + + , + }, + } + } + > + + + } + className="documentsTableCell" + data-tabster="{\\"restorer\\":{\\"type\\":1}}" + id="menu17" + key="id" + onContextMenu={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + onMouseMove={[Function]} + style={ + Object { + "maxWidth": 50, + "minWidth": 50, + "width": 50, + } + } + > + + + + +
, + }, + } + } + > + + + + + + + +
+ +
+ + +
+ +
+
+ + +
+ +
+ +
+
+ + 1 + +
+
+
+
+
+ +
+ +
+
+ + pk1 + +
+
+
+
+
+
+
+
+ + +
+ +
+ +
+
+ + 2 + +
+
+
+
+
+ +
+ +
+
+ + pk2 + +
+
+
+
+
+
+
+
+ + +
+ +
+ +
+
+ + 3 + +
+
+
+
+
+ +
+ +
+
+ + pk3 + +
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+`; + exports[`DocumentsTableComponent should render documents and partition keys in header 1`] = ` Date: Wed, 5 Jun 2024 20:42:48 +0530 Subject: [PATCH 03/20] [Accessibility-3100036]: Close button is nested under 'Home' tab control under 'Data Explorer' pane. (#1818) * [3100036]: [Programmatic Access - Azure Cosmos DB - Data Explorer]: Close button is nested under 'Home' tab control under 'Data Explorer' pane. * Fabric-Less updated. * Added specific width for contentWrapper. * less update. * Fixed out-scope space issue --------- Co-authored-by: Satyapriya Bai --- less/Common/Constants.less | 1 + less/documentDB.less | 120 +++++++++++++++++++------------------ less/documentDBFabric.less | 10 ++-- src/Explorer/Tabs/Tabs.tsx | 76 ++++++++++++----------- 4 files changed, 109 insertions(+), 98 deletions(-) diff --git a/less/Common/Constants.less b/less/Common/Constants.less index 5b80d67f8..d79c3e4e0 100644 --- a/less/Common/Constants.less +++ b/less/Common/Constants.less @@ -130,6 +130,7 @@ @ActiveTabWidth: 141px; @TabsHeight: 30px; @TabsWidth: 140px; +@ContentWrapper: 111px; @StatusIconContainerSize: 18px; @LoadingErrorIconSize: 14px; @ErrorIconContainer: 16px; diff --git a/less/documentDB.less b/less/documentDB.less index 05ad22760..00ffe2b0c 100644 --- a/less/documentDB.less +++ b/less/documentDB.less @@ -2671,7 +2671,7 @@ a:link { width: @ActiveTabWidth; } -.nav-tabs > li.active > .tabNavContentContainer > .tab_Content > .tabNavText { +.nav-tabs > li.active > .tabNavContentContainer > .tab_Content > .contentWrapper> .tabNavText { font-weight: bolder; border-bottom: 2px solid rgba(0, 120, 212, 1); } @@ -2707,67 +2707,71 @@ a:link { width: @TabsWidth; border-right: @ButtonBorderWidth solid @BaseMedium; white-space: nowrap; - - .statusIconContainer { - width: @StatusIconContainerSize; - height: @StatusIconContainerSize; - margin-left: @SmallSpace; - display: inline-flex; - - .errorIconContainer { - width: @ErrorIconContainer; - height: @ErrorIconContainer; - margin-top: 1px; - - .errorIcon { - width: @ErrorIconWidth; - height: @LoadingErrorIconSize; - background-image: url(../images/error_no_outline.svg); - background-repeat: no-repeat; - background-position: center; - background-size: 3px; - display: block; - margin: 1px 0px 0px 6px; + .contentWrapper { + .flex-display(); + width: @ContentWrapper; + + .statusIconContainer { + width: @StatusIconContainerSize; + height: @StatusIconContainerSize; + margin-left: @SmallSpace; + display: inline-flex; + + .errorIconContainer { + width: @ErrorIconContainer; + height: @ErrorIconContainer; + margin-top: 1px; + + .errorIcon { + width: @ErrorIconWidth; + height: @LoadingErrorIconSize; + background-image: url(../images/error_no_outline.svg); + background-repeat: no-repeat; + background-position: center; + background-size: 3px; + display: block; + margin: 1px 0px 0px 6px; + } + } + + .errorIconContainer.actionsEnabled { + &:hover { + .hover(); + } + + &:focus { + .focus(); + } + + &:active { + .active(); + } + } + + .errorIconContainer[tabindex]:active { + outline: none; + } + + .loadingIcon { + width: @LoadingErrorIconSize; + height: @LoadingErrorIconSize; + margin: 0px 0px @SmallSpace @SmallSpace; + } + } + + .tabNavText { + margin-left: @SmallSpace; + margin-right: 2px; + color: @BaseDark; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + flex-grow: 1; } } - .errorIconContainer.actionsEnabled { - &:hover { - .hover(); - } - - &:focus { - .focus(); - } - - &:active { - .active(); - } - } - - .errorIconContainer[tabindex]:active { - outline: none; - } - - .loadingIcon { - width: @LoadingErrorIconSize; - height: @LoadingErrorIconSize; - margin: 0px 0px @SmallSpace @SmallSpace; - } - } - - .tabNavText { - margin-left: @SmallSpace; - margin-right: 2px; - color: @BaseDark; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - flex-grow: 1; - } - .tabIconSection { - width: 30px; + width: 29px; position: relative; padding-top: 2px; diff --git a/less/documentDBFabric.less b/less/documentDBFabric.less index be4e5c17e..ea4001780 100644 --- a/less/documentDBFabric.less +++ b/less/documentDBFabric.less @@ -75,7 +75,7 @@ a:focus { border-bottom: 2px solid @FabricAccentMedium; } -.nav-tabs>li.active>.tabNavContentContainer>.tab_Content>.tabNavText { +.nav-tabs>li.active>.tabNavContentContainer>.tab_Content>.contentWrapper>.tabNavText { border-bottom: 0px none transparent; } @@ -93,9 +93,11 @@ a:focus { width: calc(@TabsWidth - (@SmallSpace * 2)); padding-bottom: @SmallSpace; - .statusIconContainer { - margin-left: 0px; - } + .contentWrapper { + .statusIconContainer { + margin-left: 0px; + } + } .tabIconSection { .cancelButton { diff --git a/src/Explorer/Tabs/Tabs.tsx b/src/Explorer/Tabs/Tabs.tsx index b33f3d4af..64636f077 100644 --- a/src/Explorer/Tabs/Tabs.tsx +++ b/src/Explorer/Tabs/Tabs.tsx @@ -153,47 +153,51 @@ function TabNav({ tab, active, tabKind }: { tab?: Tab; active: boolean; tabKind?
  • setHovering(true)} onMouseLeave={() => setHovering(false)} - onClick={() => { - if (tab) { - tab.onTabClick(); - } else if (tabKind !== undefined) { - useTabs.getState().activateReactTab(tabKind); - } - }} - onKeyPress={({ nativeEvent: e }) => { - if (tab) { - tab.onKeyPressActivate(undefined, e); - } else if (tabKind !== undefined) { - onKeyPressReactTab(e, tabKind); - } - }} className={active ? "active tabList" : "tabList"} style={active ? { fontWeight: "bolder" } : {}} - title={useObservable(tab?.tabPath || ko.observable(""))} - aria-selected={active} - aria-expanded={active} - aria-controls={tabId} - tabIndex={0} - role="tab" - ref={focusTab} >
    - - {useObservable(tab?.isExecutionError || ko.observable(false)) && } - {isTabExecuting(tab, tabKind) && ( - Loading - )} - {isQueryErrorThrown(tab, tabKind) && ( - Error - )} + { + if (tab) { + tab.onTabClick(); + } else if (tabKind !== undefined) { + useTabs.getState().activateReactTab(tabKind); + } + }} + onKeyPress={({ nativeEvent: e }) => { + if (tab) { + tab.onKeyPressActivate(undefined, e); + } else if (tabKind !== undefined) { + onKeyPressReactTab(e, tabKind); + } + }} + title={useObservable(tab?.tabPath || ko.observable(""))} + aria-selected={active} + aria-expanded={active} + aria-controls={tabId} + tabIndex={0} + role="tab" + ref={focusTab} + > + + {useObservable(tab?.isExecutionError || ko.observable(false)) && } + {isTabExecuting(tab, tabKind) && ( + Loading + )} + {isQueryErrorThrown(tab, tabKind) && ( + Error + )} + + {useObservable(tab?.tabTitle || getReactTabTitle())} - {useObservable(tab?.tabTitle || getReactTabTitle())} @@ -280,7 +284,7 @@ function TabPane({ tab, active }: { tab: Tab; active: boolean }) { } const onKeyPressReactTab = (e: KeyboardEvent, tabKind: ReactTabKind): void => { - if (e.key === "Enter" || e.key === "Space") { + if (e.key === "Enter" || e.code === "Space") { useTabs.getState().activateReactTab(tabKind); e.stopPropagation(); } From 06e28ae3e7dae9098b32f4f3d78cb83e75326adb Mon Sep 17 00:00:00 2001 From: SATYA SB <107645008+satya07sb@users.noreply.github.com> Date: Wed, 5 Jun 2024 20:45:55 +0530 Subject: [PATCH 04/20] [Visual Requirement - Azure Cosmos DB - Add Table]: Luminosity ratio of links with surrounding text is less than required 3:1 under 'Add Table' pane. (#1840) --- src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx | 8 +++++++- .../__snapshots__/ThroughputInput.test.tsx.snap | 4 +++- .../CassandraAddCollectionPane.tsx | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx b/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx index 07b105642..527592b0e 100644 --- a/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx +++ b/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx @@ -223,6 +223,7 @@ export const ThroughputInput: FunctionComponent = ({ Estimate your required RU/s with{" "} = ({ Estimate your required RU/s with  - + capacity calculator . diff --git a/src/Explorer/Controls/ThroughputInput/__snapshots__/ThroughputInput.test.tsx.snap b/src/Explorer/Controls/ThroughputInput/__snapshots__/ThroughputInput.test.tsx.snap index 391d88845..110f15955 100644 --- a/src/Explorer/Controls/ThroughputInput/__snapshots__/ThroughputInput.test.tsx.snap +++ b/src/Explorer/Controls/ThroughputInput/__snapshots__/ThroughputInput.test.tsx.snap @@ -733,11 +733,13 @@ exports[`ThroughputInput Pane should render Default properly 1`] = ` Enter CQL command to create the table.{" "} - + Learn More From 7c5fb1b6972a52f6648f5c17dcc7d69c366ade76 Mon Sep 17 00:00:00 2001 From: SATYA SB <107645008+satya07sb@users.noreply.github.com> Date: Wed, 5 Jun 2024 20:46:35 +0530 Subject: [PATCH 05/20] [accessibility-3102730]:[Visual Requirement - Azure Cosmos DB - Data Explorer]: Luminosity ratio of 'Learn More' link with surrounding text is less than required 3:1 under 'Data Explorer' pane. (#1847) --- src/Explorer/Tabs/Tabs.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Explorer/Tabs/Tabs.tsx b/src/Explorer/Tabs/Tabs.tsx index 64636f077..d4588b19e 100644 --- a/src/Explorer/Tabs/Tabs.tsx +++ b/src/Explorer/Tabs/Tabs.tsx @@ -92,6 +92,7 @@ export const Tabs = ({ explorer }: TabsProps): JSX.Element => { {`To prevent queries from using excessive RUs, Data Explorer has a 5,000 RU default limit. To modify or remove the limit, go to the Settings cog on the right and find "RU Threshold".`} From 7002da0b516485afb62193c53ee66a53227daf7d Mon Sep 17 00:00:00 2001 From: Laurent Nguyen Date: Wed, 5 Jun 2024 17:47:27 +0200 Subject: [PATCH 06/20] Implement ctrl-shift click to select multiple documents (#1851) * Initial implementation of shift and ctrl click to select * Implement shift-ctrl selection * Fix snapshot, update selectionHelper comment * Fix missing type * Properly disable cursor selection * Update snapshots * Do not enable (multiselect) if readonly * Consider meta key for mac and ctrl for everything else --- .../DataTable/DataTableOperationManager.ts | 9 +- src/Explorer/Tables/Utilities.ts | 28 +----- .../DocumentsTableComponent.tsx | 65 +++++++++++-- .../Tabs/DocumentsTabV2/SelectionHelper.ts | 92 +++++++++++++++++++ .../DocumentsTableComponent.test.tsx.snap | 72 ++++++++++----- .../DocumentsTabV2/selectionHelper.test.ts | 84 +++++++++++++++++ src/Utils/KeyboardUtils.ts | 15 +++ 7 files changed, 304 insertions(+), 61 deletions(-) create mode 100644 src/Explorer/Tabs/DocumentsTabV2/SelectionHelper.ts create mode 100644 src/Explorer/Tabs/DocumentsTabV2/selectionHelper.test.ts create mode 100644 src/Utils/KeyboardUtils.ts diff --git a/src/Explorer/Tables/DataTable/DataTableOperationManager.ts b/src/Explorer/Tables/DataTable/DataTableOperationManager.ts index 47d202650..67ba1c6a7 100644 --- a/src/Explorer/Tables/DataTable/DataTableOperationManager.ts +++ b/src/Explorer/Tables/DataTable/DataTableOperationManager.ts @@ -1,5 +1,6 @@ import ko from "knockout"; +import { isEnvironmentAltPressed, isEnvironmentCtrlPressed, isEnvironmentShiftPressed } from "Utils/KeyboardUtils"; import * as Constants from "../Constants"; import * as Entities from "../Entities"; import * as Utilities from "../Utilities"; @@ -28,7 +29,7 @@ export default class DataTableOperationManager { var elem: JQuery = $(event.currentTarget); this.updateLastSelectedItem(elem, event.shiftKey); - if (Utilities.isEnvironmentCtrlPressed(event)) { + if (isEnvironmentCtrlPressed(event)) { this.applyCtrlSelection(elem); } else if (event.shiftKey) { this.applyShiftSelection(elem); @@ -74,9 +75,9 @@ export default class DataTableOperationManager { DataTableOperations.scrollToRowIfNeeded(dataTableRows, safeIndex, isUpArrowKey); } } else if ( - Utilities.isEnvironmentCtrlPressed(event) && - !Utilities.isEnvironmentShiftPressed(event) && - !Utilities.isEnvironmentAltPressed(event) && + isEnvironmentCtrlPressed(event) && + !isEnvironmentShiftPressed(event) && + !isEnvironmentAltPressed(event) && event.keyCode === Constants.keyCodes.A ) { this.applySelectAll(); diff --git a/src/Explorer/Tables/Utilities.ts b/src/Explorer/Tables/Utilities.ts index 4e3d11bc0..8d5ab453c 100644 --- a/src/Explorer/Tables/Utilities.ts +++ b/src/Explorer/Tables/Utilities.ts @@ -1,8 +1,8 @@ -import * as _ from "underscore"; import Q from "q"; +import * as _ from "underscore"; +import * as Constants from "./Constants"; import * as Entities from "./Entities"; import { CassandraTableKey } from "./TableDataClient"; -import * as Constants from "./Constants"; /** * Generates a pseudo-random GUID. @@ -180,30 +180,6 @@ export function onEsc( return onKey(event, Constants.keyCodes.Esc, action, metaKey, shiftKey, altKey); } -/** - * Is the environment 'ctrl' key press. This key is used for multi selection, like select one more item, select all. - * For Windows and Linux, it's ctrl. For Mac, it's command. - */ -export function isEnvironmentCtrlPressed(event: JQueryEventObject): boolean { - return isMac() ? event.metaKey : event.ctrlKey; -} - -export function isEnvironmentShiftPressed(event: JQueryEventObject): boolean { - return event.shiftKey; -} - -export function isEnvironmentAltPressed(event: JQueryEventObject): boolean { - return event.altKey; -} - -/** - * Returns whether the current platform is MacOS. - */ -export function isMac(): boolean { - var platform = navigator.platform.toUpperCase(); - return platform.indexOf("MAC") >= 0; -} - // MAX_SAFE_INTEGER and MIN_SAFE_INTEGER will be provided by ECMAScript 6's Number export var MAX_SAFE_INTEGER = Math.pow(2, 53) - 1; export var MIN_SAFE_INTEGER = -MAX_SAFE_INTEGER; diff --git a/src/Explorer/Tabs/DocumentsTabV2/DocumentsTableComponent.tsx b/src/Explorer/Tabs/DocumentsTabV2/DocumentsTableComponent.tsx index d66568875..58f486c3b 100644 --- a/src/Explorer/Tabs/DocumentsTabV2/DocumentsTableComponent.tsx +++ b/src/Explorer/Tabs/DocumentsTabV2/DocumentsTableComponent.tsx @@ -22,6 +22,9 @@ import { useTableFeatures, useTableSelection, } from "@fluentui/react-components"; +import { NormalizedEventKey } from "Common/Constants"; +import { selectionHelper } from "Explorer/Tabs/DocumentsTabV2/SelectionHelper"; +import { isEnvironmentCtrlPressed, isEnvironmentShiftPressed } from "Utils/KeyboardUtils"; import React, { useCallback, useEffect, useMemo } from "react"; import { FixedSizeList as List, ListChildComponentProps } from "react-window"; @@ -123,17 +126,63 @@ export const DocumentsTableComponent: React.FC = [columnHeaders], ); - const onIdClicked = useCallback((index: number) => onSelectedRowsChange(new Set([index])), [onSelectedRowsChange]); + const [selectionStartIndex, setSelectionStartIndex] = React.useState(undefined); + const onTableCellClicked = useCallback( + (e: React.MouseEvent, index: number) => { + if (isSelectionDisabled) { + // Only allow click + onSelectedRowsChange(new Set([index])); + setSelectionStartIndex(index); + return; + } + + const result = selectionHelper( + selectedRows as Set, + index, + isEnvironmentShiftPressed(e), + isEnvironmentCtrlPressed(e), + selectionStartIndex, + ); + onSelectedRowsChange(result.selection); + if (result.selectionStartIndex !== undefined) { + setSelectionStartIndex(result.selectionStartIndex); + } + }, + [isSelectionDisabled, selectedRows, selectionStartIndex, onSelectedRowsChange], + ); + + /** + * Callback for when: + * - a key has been pressed on the cell + * - a key is down and the cell is clicked by the mouse + */ + const onIdClicked = useCallback( + (e: React.KeyboardEvent, index: number) => { + if (e.key === NormalizedEventKey.Enter || e.key === NormalizedEventKey.Space) { + onSelectedRowsChange(new Set([index])); + } + }, + [onSelectedRowsChange], + ); const RenderRow = ({ index, style, data }: ReactWindowRenderFnProps) => { const { item, selected, appearance, onClick, onKeyDown } = data[index]; return ( - + {!isSelectionDisabled && ( { + setSelectionStartIndex(index); + onClick(e); + }} onKeyDown={onKeyDown} /> )} @@ -141,8 +190,9 @@ export const DocumentsTableComponent: React.FC = onSelectedRowsChange(new Set([index]))} - onKeyDown={() => onIdClicked(index)} + // When clicking on a cell with shift/ctrl key, onKeyDown is called instead of onClick. + onClick={(e: React.MouseEvent) => onTableCellClicked(e, index)} + onKeyPress={(e: React.KeyboardEvent) => onIdClicked(e, index)} {...columnSizing.getTableCellProps(column.columnId)} tabIndex={column.columnId === "id" ? 0 : -1} > @@ -166,7 +216,7 @@ export const DocumentsTableComponent: React.FC = [ useTableColumnSizing_unstable({ columnSizingOptions, onColumnResize }), useTableSelection({ - selectionMode: "multiselect", + selectionMode: isSelectionDisabled ? "single" : "multiselect", selectedItems: selectedRows, // eslint-disable-next-line react/prop-types onSelectionChange: (e, data) => onSelectedRowsChange(data.selectedItems), @@ -207,9 +257,10 @@ export const DocumentsTableComponent: React.FC = if (newActiveItemIndex !== activeItemIndex) { onItemClicked(newActiveItemIndex); setActiveItemIndex(newActiveItemIndex); + setSelectionStartIndex(newActiveItemIndex); } } - }, [selectedRows, items]); + }, [selectedRows, items, activeItemIndex, onItemClicked]); // Cell keyboard navigation const keyboardNavAttr = useArrowNavigationGroup({ axis: "grid" }); diff --git a/src/Explorer/Tabs/DocumentsTabV2/SelectionHelper.ts b/src/Explorer/Tabs/DocumentsTabV2/SelectionHelper.ts new file mode 100644 index 000000000..fe4426d4e --- /dev/null +++ b/src/Explorer/Tabs/DocumentsTabV2/SelectionHelper.ts @@ -0,0 +1,92 @@ +/** + * Utility class to help with selection. + * This emulates File Explorer selection behavior. + * ctrl: toggle selection of index. + * shift: select all rows between selectionStartIndex and index + * shift + ctrl: select or deselect all rows between selectionStartIndex and index depending on whether selectionStartIndex is selected + * No modifier only selects the clicked row + * ctrl: updates selection start index + * shift: do not update selection start index + * + * @param currentSelection current selection + * @param clickedIndex index of clicked row + * @param isShiftKey shift key is pressed + * @param isCtrlKey ctrl key is pressed + * @param selectionStartIndex index of current selected row + * @returns new selection and selection start + */ +export const selectionHelper = ( + currentSelection: Set, + clickedIndex: number, + isShiftKey: boolean, + isCtrlKey: boolean, + selectionStartIndex: number, +): { + selection: Set; + selectionStartIndex: number; +} => { + if (isShiftKey) { + // Shift is about selecting range of rows + if (isCtrlKey) { + // shift + ctrl + const isSelectionStartIndexSelected = currentSelection.has(selectionStartIndex); + const min = Math.min(clickedIndex, selectionStartIndex); + const max = Math.max(clickedIndex, selectionStartIndex); + + const newSelection = new Set(currentSelection); + for (let i = min; i <= max; i++) { + // Select or deselect range depending on how selectionStartIndex is selected + if (isSelectionStartIndexSelected) { + // Select range + newSelection.add(i); + } else { + // Deselect range + newSelection.delete(i); + } + } + + return { + selection: newSelection, + selectionStartIndex: undefined, + }; + } else { + // shift only + // Shift only: enable everything between lastClickedIndex and clickedIndex and disable everything else + const min = Math.min(clickedIndex, selectionStartIndex); + const max = Math.max(clickedIndex, selectionStartIndex); + const newSelection = new Set(); + for (let i = min; i <= max; i++) { + newSelection.add(i); + } + + return { + selection: newSelection, + selectionStartIndex: undefined, // do not change selection start + }; + } + } else { + if (isCtrlKey) { + // Ctrl only: toggle selection where we clicked + const isNotSelected = !currentSelection.has(clickedIndex); + if (isNotSelected) { + return { + selection: new Set(currentSelection.add(clickedIndex)), + selectionStartIndex: clickedIndex, + }; + } else { + // Remove + currentSelection.delete(clickedIndex); + return { + selection: new Set(currentSelection), + selectionStartIndex: clickedIndex, + }; + } + } else { + // If no modifier keys are pressed, select only the clicked row + return { + selection: new Set([clickedIndex]), + selectionStartIndex: clickedIndex, + }; + } + } +}; diff --git a/src/Explorer/Tabs/DocumentsTabV2/__snapshots__/DocumentsTableComponent.test.tsx.snap b/src/Explorer/Tabs/DocumentsTabV2/__snapshots__/DocumentsTableComponent.test.tsx.snap index 44eb5db8b..2fbeba2da 100644 --- a/src/Explorer/Tabs/DocumentsTabV2/__snapshots__/DocumentsTableComponent.test.tsx.snap +++ b/src/Explorer/Tabs/DocumentsTabV2/__snapshots__/DocumentsTableComponent.test.tsx.snap @@ -476,11 +476,13 @@ exports[`DocumentsTableComponent should not render selection column when isSelec key="1" style={ Object { + "cursor": "pointer", "height": 30, "left": 0, "position": "absolute", "right": undefined, "top": 0, + "userSelect": "none", "width": "100%", } } @@ -492,11 +494,13 @@ exports[`DocumentsTableComponent should not render selection column when isSelec role="row" style={ Object { + "cursor": "pointer", "height": 30, "left": 0, "position": "absolute", "right": undefined, "top": 0, + "userSelect": "none", "width": "100%", } } @@ -505,7 +509,7 @@ exports[`DocumentsTableComponent should not render selection column when isSelec className="documentsTableCell" key="id" onClick={[Function]} - onKeyDown={[Function]} + onKeyPress={[Function]} style={ Object { "maxWidth": 50, @@ -518,7 +522,7 @@ exports[`DocumentsTableComponent should not render selection column when isSelec
    { + describe("when shift:off", () => { + it("ctrl:off: should return clicked items and update selection start", () => { + const currentSelection = new Set([1, 2, 3]); + const clickedIndex = 4; + const isShiftKey = false; + const isCtrlKey = false; + const selectionStartIndex = 1; + const result = selectionHelper(currentSelection, clickedIndex, isShiftKey, isCtrlKey, selectionStartIndex); + expect(result.selection).toEqual(new Set([4])); + expect(result.selectionStartIndex).toEqual(4); + }); + + it("ctrl:on: should turn on selection and update selection start on not selected item", () => { + const currentSelection = new Set([1, 3]); + const clickedIndex = 2; + const isShiftKey = false; + const isCtrlKey = true; + const selectionStartIndex = 1; + const result = selectionHelper(currentSelection, clickedIndex, isShiftKey, isCtrlKey, selectionStartIndex); + expect(result.selection).toEqual(new Set([1, 2, 3])); + expect(result.selectionStartIndex).toEqual(2); + }); + + it("ctrl:on: should turn off selection and update selection start on selected item", () => { + const currentSelection = new Set([1, 2, 3]); + const clickedIndex = 2; + const isShiftKey = false; + const isCtrlKey = true; + const selectionStartIndex = 1; + const result = selectionHelper(currentSelection, clickedIndex, isShiftKey, isCtrlKey, selectionStartIndex); + expect(result.selection).toEqual(new Set([1, 3])); + expect(result.selectionStartIndex).toEqual(2); + }); + }); + + describe("when shift:on", () => { + it("ctrl:off: should only select between selection start and clicked index (selection start < clicked index)", () => { + const currentSelection = new Set([7, 8, 10]); + const clickedIndex = 9; + const isShiftKey = true; + const isCtrlKey = false; + const selectionStartIndex = 5; + const result = selectionHelper(currentSelection, clickedIndex, isShiftKey, isCtrlKey, selectionStartIndex); + expect(result.selection).toEqual(new Set([5, 6, 7, 8, 9])); + expect(result.selectionStartIndex).toEqual(undefined); + }); + + it("ctrl:off: should only select between selection start and clicked index (selection start > clicked index)", () => { + const currentSelection = new Set([4, 6, 8]); + const clickedIndex = 2; + const isShiftKey = true; + const isCtrlKey = false; + const selectionStartIndex = 5; + const result = selectionHelper(currentSelection, clickedIndex, isShiftKey, isCtrlKey, selectionStartIndex); + expect(result.selection).toEqual(new Set([2, 3, 4, 5])); + expect(result.selectionStartIndex).toEqual(undefined); + }); + + it("ctrl:on: selection start on selected item should keep current selection and select range, and not update selection start", () => { + const currentSelection = new Set([1, 4, 5, 7]); + const clickedIndex = 9; + const isShiftKey = true; + const isCtrlKey = true; + const selectionStartIndex = 5; + const result = selectionHelper(currentSelection, clickedIndex, isShiftKey, isCtrlKey, selectionStartIndex); + expect(result.selection).toEqual(new Set([1, 4, 5, 6, 7, 8, 9])); + expect(result.selectionStartIndex).toEqual(undefined); + }); + + it("ctrl:on: selection start on deselected item should deselect range, and not update selection start", () => { + const currentSelection = new Set([1, 4, 6, 7, 10]); + const clickedIndex = 9; + const isShiftKey = true; + const isCtrlKey = true; + const selectionStartIndex = 5; + const result = selectionHelper(currentSelection, clickedIndex, isShiftKey, isCtrlKey, selectionStartIndex); + expect(result.selection).toEqual(new Set([1, 4, 10])); + expect(result.selectionStartIndex).toEqual(undefined); + }); + }); +}); diff --git a/src/Utils/KeyboardUtils.ts b/src/Utils/KeyboardUtils.ts new file mode 100644 index 000000000..e7ef66ef9 --- /dev/null +++ b/src/Utils/KeyboardUtils.ts @@ -0,0 +1,15 @@ +/** + * Is the environment 'ctrl' key press. This key is used for multi selection, like select one more item, select all. + * For Windows and Linux, it's ctrl. For Mac, it's command. + */ +export const isEnvironmentCtrlPressed = (event: JQueryEventObject | React.MouseEvent): boolean => + isMac() ? event.metaKey : event.ctrlKey; + +export const isEnvironmentShiftPressed = (event: JQueryEventObject | React.MouseEvent): boolean => event.shiftKey; + +export const isEnvironmentAltPressed = (event: JQueryEventObject | React.MouseEvent): boolean => event.altKey; + +/** + * Returns whether the current platform is MacOS. + */ +export const isMac = (): boolean => navigator.platform.toUpperCase().indexOf("MAC") >= 0; From 9b1277515185809957d2c9bfcab41d2f69e058d6 Mon Sep 17 00:00:00 2001 From: Ashley Stanton-Nurse Date: Wed, 5 Jun 2024 12:16:28 -0700 Subject: [PATCH 07/20] Allow query result view to be toggled from command bar (#1833) * allow query result view to be toggled from command bar also provides a default results view option that's stored in the browser's local storage * update SettingsPane test snapshot --- .../CommandButton/CommandButtonComponent.tsx | 2 +- .../Menus/CommandBar/CommandBarUtil.tsx | 18 +++--- .../Panes/SettingsPane/SettingsPane.tsx | 37 +++++++++++ .../__snapshots__/SettingsPane.test.tsx.snap | 61 +++++++++++++++++++ .../Tabs/QueryTab/QueryTabComponent.tsx | 55 ++++++++++++++++- src/Shared/StorageUtility.ts | 10 +++ 6 files changed, 172 insertions(+), 11 deletions(-) diff --git a/src/Explorer/Controls/CommandButton/CommandButtonComponent.tsx b/src/Explorer/Controls/CommandButton/CommandButtonComponent.tsx index 6337f947e..b6e847d54 100644 --- a/src/Explorer/Controls/CommandButton/CommandButtonComponent.tsx +++ b/src/Explorer/Controls/CommandButton/CommandButtonComponent.tsx @@ -31,7 +31,7 @@ export interface CommandButtonComponentProps { /** * Click handler for command button click */ - onCommandClick: (e: React.SyntheticEvent | KeyboardEvent) => void; + onCommandClick?: (e: React.SyntheticEvent | KeyboardEvent) => void; /** * Label for the button diff --git a/src/Explorer/Menus/CommandBar/CommandBarUtil.tsx b/src/Explorer/Menus/CommandBar/CommandBarUtil.tsx index fc67ad894..0c9de46ce 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarUtil.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarUtil.tsx @@ -60,14 +60,16 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol imageProps: btn.iconSrc ? { src: btn.iconSrc, alt: btn.iconAlt } : undefined, iconName: btn.iconName, }, - onClick: (ev?: React.MouseEvent | React.KeyboardEvent) => { - btn.onCommandClick(ev); - let copilotEnabled = false; - if (useQueryCopilot.getState().copilotEnabled && useQueryCopilot.getState().copilotUserDBEnabled) { - copilotEnabled = useQueryCopilot.getState().copilotEnabledforExecution; - } - TelemetryProcessor.trace(Action.ClickCommandBarButton, ActionModifiers.Mark, { label, copilotEnabled }); - }, + onClick: btn.onCommandClick + ? (ev?: React.MouseEvent | React.KeyboardEvent) => { + btn.onCommandClick(ev); + let copilotEnabled = false; + if (useQueryCopilot.getState().copilotEnabled && useQueryCopilot.getState().copilotUserDBEnabled) { + copilotEnabled = useQueryCopilot.getState().copilotEnabledforExecution; + } + TelemetryProcessor.trace(Action.ClickCommandBarButton, ActionModifiers.Mark, { label, copilotEnabled }); + } + : undefined, key: `${btn.commandButtonLabel}${index}`, text: label, "data-test": label, diff --git a/src/Explorer/Panes/SettingsPane/SettingsPane.tsx b/src/Explorer/Panes/SettingsPane/SettingsPane.tsx index 9b2412c4b..ff96d6716 100644 --- a/src/Explorer/Panes/SettingsPane/SettingsPane.tsx +++ b/src/Explorer/Panes/SettingsPane/SettingsPane.tsx @@ -9,12 +9,14 @@ import { Toggle, } from "@fluentui/react"; import * as Constants from "Common/Constants"; +import { SplitterDirection } from "Common/Splitter"; import { InfoTooltip } from "Common/Tooltip/InfoTooltip"; import { configContext } from "ConfigContext"; import { DefaultRUThreshold, LocalStorageUtility, StorageKey, + getDefaultQueryResultsView, getRUThreshold, ruThresholdEnabled as isRUThresholdEnabled, } from "Shared/StorageUtility"; @@ -47,6 +49,9 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ LocalStorageUtility.getEntryBoolean(StorageKey.QueryTimeoutEnabled), ); const [queryTimeout, setQueryTimeout] = useState(LocalStorageUtility.getEntryNumber(StorageKey.QueryTimeout)); + const [defaultQueryResultsView, setDefaultQueryResultsView] = useState( + getDefaultQueryResultsView(), + ); const [automaticallyCancelQueryAfterTimeout, setAutomaticallyCancelQueryAfterTimeout] = useState( LocalStorageUtility.getEntryBoolean(StorageKey.AutomaticallyCancelQueryAfterTimeout), ); @@ -121,6 +126,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ LocalStorageUtility.setEntryNumber(StorageKey.MaxDegreeOfParellism, maxDegreeOfParallelism); LocalStorageUtility.setEntryString(StorageKey.PriorityLevel, priorityLevel.toString()); LocalStorageUtility.setEntryString(StorageKey.CopilotSampleDBEnabled, copilotSampleDBEnabled.toString()); + LocalStorageUtility.setEntryString(StorageKey.DefaultQueryResultsView, defaultQueryResultsView); if (shouldShowGraphAutoVizOption) { LocalStorageUtility.setEntryBoolean( @@ -197,6 +203,11 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ { key: Constants.PriorityLevel.High, text: "High" }, ]; + const defaultQueryResultsViewOptionList: IChoiceGroupOption[] = [ + { key: SplitterDirection.Vertical, text: "Vertical" }, + { key: SplitterDirection.Horizontal, text: "Horizontal" }, + ]; + const handleOnPriorityLevelOptionChange = ( ev: React.FormEvent, option: IChoiceGroupOption, @@ -234,6 +245,13 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ } }; + const handleOnDefaultQueryResultsViewChange = ( + ev: React.MouseEvent, + option: IChoiceGroupOption, + ): void => { + setDefaultQueryResultsView(option.key as SplitterDirection); + }; + const handleOnQueryRetryAttemptsSpinButtonChange = (ev: React.MouseEvent, newValue?: string): void => { const retryAttempts = Number(newValue); if (!isNaN(retryAttempts)) { @@ -438,6 +456,25 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ )}
    +
    +
    +
    + + Default Query Results View + + Select the default view to use when displaying query results. +
    +
    + +
    +
    +
    )}
    diff --git a/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap b/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap index 6f4ac0c06..f887a53dd 100644 --- a/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap +++ b/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap @@ -205,6 +205,67 @@ exports[`Settings Pane should render Default properly 1`] = `
    +
    +
    +
    + + Default Query Results View + + + Select the default view to use when displaying query results. + +
    +
    + +
    +
    +
    diff --git a/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx b/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx index 532ce4662..bfcaa9bda 100644 --- a/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx +++ b/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable no-console */ import { FeedOptions, QueryOperationOptions } from "@azure/cosmos"; +import { SplitterDirection } from "Common/Splitter"; import { Platform, configContext } from "ConfigContext"; import { useDialog } from "Explorer/Controls/Dialog"; import { QueryCopilotFeedbackModal } from "Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal"; @@ -12,7 +13,13 @@ import { QueryResultSection } from "Explorer/Tabs/QueryTab/QueryResultSection"; import { useSelectedNode } from "Explorer/useSelectedNode"; import { KeyboardAction } from "KeyboardShortcuts"; import { QueryConstants } from "Shared/Constants"; -import { LocalStorageUtility, StorageKey, getRUThreshold, ruThresholdEnabled } from "Shared/StorageUtility"; +import { + LocalStorageUtility, + StorageKey, + getDefaultQueryResultsView, + getRUThreshold, + ruThresholdEnabled, +} from "Shared/StorageUtility"; import { Action } from "Shared/Telemetry/TelemetryConstants"; import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot"; import { TabsState, useTabs } from "hooks/useTabs"; @@ -25,6 +32,7 @@ import LaunchCopilot from "../../../../images/CopilotTabIcon.svg"; import DownloadQueryIcon from "../../../../images/DownloadQuery.svg"; import CancelQueryIcon from "../../../../images/Entity_cancel.svg"; import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg"; +import CheckIcon from "../../../../images/check-1.svg"; import SaveQueryIcon from "../../../../images/save-cosmos.svg"; import { NormalizedEventKey } from "../../../Common/Constants"; import { getErrorMessage } from "../../../Common/ErrorHandlingUtils"; @@ -103,6 +111,7 @@ interface IQueryTabStates { cancelQueryTimeoutID: NodeJS.Timeout; copilotActive: boolean; currentTabActive: boolean; + queryResultsView: SplitterDirection; } export const QueryTabFunctionComponent = (props: IQueryTabComponentProps): any => { @@ -147,6 +156,7 @@ export default class QueryTabComponent extends React.Component this._setViewLayout(SplitterDirection.Vertical), + hasPopup: false, + }; + const horizontalButton: CommandButtonComponentProps = { + isSelected: this.state.queryResultsView === SplitterDirection.Horizontal, + iconSrc: this.state.queryResultsView === SplitterDirection.Horizontal ? CheckIcon : undefined, + commandButtonLabel: "Horizontal", + ariaLabel: "Horizontal", + onCommandClick: () => this._setViewLayout(SplitterDirection.Horizontal), + hasPopup: false, + }; + + return { + commandButtonLabel: "View", + ariaLabel: "View", + hasPopup: true, + children: [verticalButton, horizontalButton], + }; + } + private _setViewLayout(direction: SplitterDirection): void { + this.setState({ queryResultsView: direction }); + + // We'll need to refresh the context buttons to update the selected state of the view buttons + setTimeout(() => { + useCommandBar.getState().setContextButtons(this.getTabsButtons()); + }, 100); + } + private _toggleCopilot = (active: boolean) => { this.setState({ copilotActive: active }); useQueryCopilot.getState().setCopilotEnabledforExecution(active); @@ -634,7 +680,12 @@ export default class QueryTabComponent extends React.Component )}
    - +
    { @@ -51,4 +53,12 @@ export const getRUThreshold = (): number => { return DefaultRUThreshold; }; +export const getDefaultQueryResultsView = (): SplitterDirection => { + const defaultQueryResultsViewRaw = LocalStorageUtility.getEntryString(StorageKey.DefaultQueryResultsView); + if (defaultQueryResultsViewRaw === SplitterDirection.Horizontal) { + return SplitterDirection.Horizontal; + } + return SplitterDirection.Vertical; +}; + export const DefaultRUThreshold = 5000; From 736731474f9da69839766b63989d323bcdac5946 Mon Sep 17 00:00:00 2001 From: Ashley Stanton-Nurse Date: Wed, 5 Jun 2024 12:27:29 -0700 Subject: [PATCH 08/20] show an expand icon for nodes with non-null children arrray (#1862) --- .../TreeComponent/TreeNodeComponent.test.tsx | 14 +++++++ .../TreeComponent/TreeNodeComponent.tsx | 3 +- .../TreeNodeComponent.test.tsx.snap | 38 +++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/Explorer/Controls/TreeComponent/TreeNodeComponent.test.tsx b/src/Explorer/Controls/TreeComponent/TreeNodeComponent.test.tsx index 602edcb2a..e0cdf5700 100644 --- a/src/Explorer/Controls/TreeComponent/TreeNodeComponent.test.tsx +++ b/src/Explorer/Controls/TreeComponent/TreeNodeComponent.test.tsx @@ -124,6 +124,20 @@ describe("TreeNodeComponent", () => { expect(component).toMatchSnapshot(); }); + it("renders a node as expandable if it has empty, but defined, children array", () => { + const node = generateTestNode("root", { + isLoading: true, + children: [ + generateTestNode("child1", { + children: [], + }), + generateTestNode("child2"), + ], + }); + const component = shallow(); + expect(component).toMatchSnapshot(); + }); + it("does not render children if the node is loading", () => { const node = generateTestNode("root", { isLoading: true, diff --git a/src/Explorer/Controls/TreeComponent/TreeNodeComponent.tsx b/src/Explorer/Controls/TreeComponent/TreeNodeComponent.tsx index 1717530ec..354046222 100644 --- a/src/Explorer/Controls/TreeComponent/TreeNodeComponent.tsx +++ b/src/Explorer/Controls/TreeComponent/TreeNodeComponent.tsx @@ -100,7 +100,8 @@ export const TreeNodeComponent: React.FC = ({ return unsortedChildren; }; - const isBranch = node.children?.length > 0; + // A branch node is any node with a defined children array, even if the array is empty. + const isBranch = !!node.children; const onOpenChange = useCallback( (_: TreeOpenChangeEvent, data: TreeOpenChangeData) => { diff --git a/src/Explorer/Controls/TreeComponent/__snapshots__/TreeNodeComponent.test.tsx.snap b/src/Explorer/Controls/TreeComponent/__snapshots__/TreeNodeComponent.test.tsx.snap index 6678f29ad..a20da1f66 100644 --- a/src/Explorer/Controls/TreeComponent/__snapshots__/TreeNodeComponent.test.tsx.snap +++ b/src/Explorer/Controls/TreeComponent/__snapshots__/TreeNodeComponent.test.tsx.snap @@ -1534,6 +1534,44 @@ exports[`TreeNodeComponent renders a loading spinner if the node is loading: loa `; +exports[`TreeNodeComponent renders a node as expandable if it has empty, but defined, children array 1`] = ` + + + } + style={ + Object { + "backgroundColor": undefined, + } + } + > + rootLabel + + +`; + exports[`TreeNodeComponent renders a node with a menu 1`] = ` Date: Wed, 5 Jun 2024 12:46:32 -0700 Subject: [PATCH 09/20] Update Playwright, improve E2E test reliability, add scripts to deploy test resources (#1857) --- .editorconfig | 10 + .eslintignore | 2 + .github/workflows/ci.yml | 140 +- .gitignore | 6 +- jest-playwright.config.js | 13 - jest.config.playwright.js | 7 - package-lock.json | 1426 +---------------- package.json | 4 +- playwright.config.ts | 53 + .../TreeComponent/LegacyTreeComponent.tsx | 5 +- .../TreeComponent/TreeNodeComponent.tsx | 1 + .../LegacyTreeComponent.test.tsx.snap | 15 +- .../TreeNodeComponent.test.tsx.snap | 24 + .../Menus/CommandBar/CommandBarUtil.tsx | 2 +- ...teCollectionConfirmationPane.test.tsx.snap | 6 + .../Panes/DeleteDatabaseConfirmationPanel.tsx | 16 +- .../ExecuteSprocParamsPane.test.tsx.snap | 6 + src/Explorer/Panes/PanelFooterComponent.tsx | 1 + .../__snapshots__/RightPaneForm.test.tsx.snap | 6 + .../StringInputPane.test.tsx.snap | 6 + .../TableQuerySelectPanel.test.tsx.snap | 6 + .../AddTableEntityPanel.test.tsx.snap | 6 + .../EditTableEntityPanel.test.tsx.snap | 6 + ...eteDatabaseConfirmationPanel.test.tsx.snap | 29 +- src/HostedExplorer.tsx | 1 + src/Main.tsx | 2 +- .../Hosted/Components/ConnectExplorer.tsx | 2 +- src/SelfServe/SelfServeComponent.tsx | 2 +- .../SelfServeComponent.test.tsx.snap | 4 + test/README.md | 143 ++ test/cassandra/container.spec.ts | 78 +- test/fx.ts | 151 ++ test/graph/container.spec.ts | 58 - test/gremlin/container.spec.ts | 41 + test/mongo/container.spec.ts | 101 +- test/mongo/container32.spec.ts | 59 - test/notebooks/GettingStarted.ipynb | 110 -- test/notebooks/upload.spec.ts | 25 - test/playwrightEnv.js | 26 - test/resources/README.md | 4 + test/resources/account.bicep | 50 + test/resources/all-accounts.bicep | 31 + test/resources/create-resource-group.ps1 | 31 + test/resources/deploy.ps1 | 113 ++ test/scripts/check-test-accounts.ps1 | 22 + test/scripts/set-test-accounts.ps1 | 69 + test/selfServe/selfServeExample.spec.ts | 41 - test/sql/container.spec.ts | 77 +- test/sql/resourceToken.spec.ts | 51 +- test/sql/selfServeExample.spec.ts | 23 + test/tables/container.spec.ts | 50 +- test/testExplorer/TestExplorer.ts | 10 + test/utils.js | 21 - test/utils/shared.ts | 69 - test/utils/waitForExplorer.ts | 9 - webpack.config.js | 23 + 56 files changed, 1176 insertions(+), 2117 deletions(-) create mode 100644 .editorconfig delete mode 100644 jest-playwright.config.js delete mode 100644 jest.config.playwright.js create mode 100644 playwright.config.ts create mode 100644 test/README.md create mode 100644 test/fx.ts delete mode 100644 test/graph/container.spec.ts create mode 100644 test/gremlin/container.spec.ts delete mode 100644 test/mongo/container32.spec.ts delete mode 100644 test/notebooks/GettingStarted.ipynb delete mode 100644 test/notebooks/upload.spec.ts delete mode 100644 test/playwrightEnv.js create mode 100644 test/resources/README.md create mode 100644 test/resources/account.bicep create mode 100644 test/resources/all-accounts.bicep create mode 100644 test/resources/create-resource-group.ps1 create mode 100644 test/resources/deploy.ps1 create mode 100644 test/scripts/check-test-accounts.ps1 create mode 100644 test/scripts/set-test-accounts.ps1 delete mode 100644 test/selfServe/selfServeExample.spec.ts create mode 100644 test/sql/selfServeExample.spec.ts delete mode 100644 test/utils.js delete mode 100644 test/utils/shared.ts delete mode 100644 test/utils/waitForExplorer.ts diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..65096ce0b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +# NOTE: Prettier reads EditorConfig settings, so be careful adjusting settings here and assuming they'll only affect your editor ;). + +# top-most EditorConfig file +root = true + +[*.yml] +indent_size = 2 + +[*.{js,jsx,ts,tsx}] +indent_size = 2 \ No newline at end of file diff --git a/.eslintignore b/.eslintignore index f7ed8b5f9..2eeecaed5 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,5 @@ +playwright.config.ts + **/node_modules/ src/**/__mocks__/**/* dist/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fd097a194..15c555e31 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -104,79 +104,6 @@ jobs: run: az storage blob upload -c '$web' -f ./preview/config.json --account-name cosmosexplorerpreview --name "${{github.event.pull_request.head.sha || github.sha}}/config.json" --account-key="${PREVIEW_STORAGE_KEY}" --overwrite true env: PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }} - endtoendemulator: - name: "End To End Emulator Tests" - # Temporarily disabled. This test needs to be rewritten in playwright - if: false - runs-on: windows-latest - steps: - - uses: actions/checkout@v4 - - name: Use Node.js 18.x - uses: actions/setup-node@v4 - with: - node-version: 18.x - - uses: southpolesteve/cosmos-emulator-github-action@v1 - - name: End to End Tests - run: | - npm ci - npm start & - npm run wait-for-server - npx jest -c ./jest.config.e2e.js --detectOpenHandles test/sql/container.spec.ts - shell: bash - env: - DATA_EXPLORER_ENDPOINT: "https://localhost:1234/explorer.html?platform=Emulator" - PLATFORM: "Emulator" - NODE_TLS_REJECT_UNAUTHORIZED: 0 - - uses: actions/upload-artifact@v3 - if: failure() - with: - name: screenshots - path: failed-* - endtoend: - name: "E2E" - runs-on: ubuntu-latest - env: - NODE_TLS_REJECT_UNAUTHORIZED: 0 - AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - strategy: - fail-fast: false - matrix: - test-file: - - ./test/cassandra/container.spec.ts - - ./test/graph/container.spec.ts - - ./test/sql/container.spec.ts - - ./test/mongo/container.spec.ts - - ./test/mongo/container32.spec.ts - - ./test/selfServe/selfServeExample.spec.ts - - ./test/sql/resourceToken.spec.ts - - ./test/tables/container.spec.ts - steps: - - uses: actions/checkout@v4 - - - name: "Az CLI login" - uses: azure/login@v1 - with: - client-id: ${{ secrets.AZURE_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - - - name: Use Node.js 18.x - uses: actions/setup-node@v4 - with: - node-version: 18.x - - run: npm ci - - run: npm start & - - run: npm run wait-for-server - - name: ${{ matrix['test-file'] }} - run: | - # Run tests up to three times - for i in $(seq 1 3); do npx jest -c ./jest.config.playwright.js ${{ matrix['test-file'] }} && s=0 && break || s=$? && sleep 1; done; (exit $s) - shell: bash - - uses: actions/upload-artifact@v3 - if: failure() - with: - name: screenshots - path: screenshots/ nuget: name: Publish Nuget if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/') @@ -226,3 +153,70 @@ jobs: name: packages with: path: "*.nupkg" + + playwright-tests: + name: "Run Playwright Tests (Shard ${{ matrix.shardIndex }} of ${{ matrix.shardTotal }})" + runs-on: ubuntu-latest + env: + NODE_TLS_REJECT_UNAUTHORIZED: 0 + AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + strategy: + fail-fast: false + matrix: + shardIndex: [1, 2, 3, 4, 5, 6, 7, 8] + shardTotal: [8] + steps: + - uses: actions/checkout@v4 + - name: "Az CLI login" + uses: azure/login@v1 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + - name: Use Node.js 18.x + uses: actions/setup-node@v4 + with: + node-version: 18.x + - run: npm ci + - run: npx playwright install --with-deps + - name: Run test shard ${{ matrix['shardIndex'] }} of ${{ matrix['shardTotal']}} + run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} + - name: Upload blob report to GitHub Actions Artifacts + if: ${{ !cancelled() }} + uses: actions/upload-artifact@v4 + with: + name: blob-report-${{ matrix.shardIndex }} + path: blob-report + retention-days: 1 + + merge-playwright-reports: + name: "Merge Playwright Reports" + # Merge reports after playwright-tests, even if some shards have failed + if: ${{ !cancelled() }} + needs: [playwright-tests] + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 18 + - name: Install dependencies + run: npm ci + + - name: Download blob reports from GitHub Actions Artifacts + uses: actions/download-artifact@v4 + with: + path: all-blob-reports + pattern: blob-report-* + merge-multiple: true + + - name: Merge into HTML Report + run: npx playwright merge-reports --reporter html ./all-blob-reports + + - name: Upload HTML report + uses: actions/upload-artifact@v4 + with: + name: html-report--attempt-${{ github.run_attempt }} + path: playwright-report + retention-days: 14 \ No newline at end of file diff --git a/.gitignore b/.gitignore index be016240b..3617cf905 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,8 @@ Contracts/* .env failure.png screenshots/* -GettingStarted-ignore*.ipynb \ No newline at end of file +GettingStarted-ignore*.ipynb +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/jest-playwright.config.js b/jest-playwright.config.js deleted file mode 100644 index d66e8f221..000000000 --- a/jest-playwright.config.js +++ /dev/null @@ -1,13 +0,0 @@ -const isCI = require("is-ci"); - -module.exports = { - exitOnPageError: false, - launchOptions: { - headless: isCI, - slowMo: 10, - timeout: 60000, - }, - contextOptions: { - ignoreHTTPSErrors: true, - }, -}; diff --git a/jest.config.playwright.js b/jest.config.playwright.js deleted file mode 100644 index c452a5368..000000000 --- a/jest.config.playwright.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - preset: "jest-playwright-preset", - testMatch: ["/test/**/*.spec.[jt]s?(x)"], - setupFiles: ["dotenv/config"], - testEnvironment: "./test/playwrightEnv.js", - setupFilesAfterEnv: ["expect-playwright"], -}; diff --git a/package-lock.json b/package-lock.json index 31e3d37db..5a872d3fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -122,6 +122,7 @@ "@babel/preset-env": "7.9.0", "@babel/preset-react": "7.9.4", "@babel/preset-typescript": "7.9.0", + "@playwright/test": "1.44.0", "@testing-library/react": "11.2.3", "@types/applicationinsights-js": "1.0.7", "@types/codemirror": "0.0.56", @@ -166,7 +167,6 @@ "eslint-plugin-no-null": "1.0.2", "eslint-plugin-prefer-arrow": "1.2.3", "eslint-plugin-react-hooks": "4.6.0", - "expect-playwright": "0.3.3", "fast-glob": "3.2.5", "fs-extra": "7.0.0", "html-inline-css-webpack-plugin": "1.11.2", @@ -175,7 +175,6 @@ "html-webpack-plugin": "5.5.3", "jest": "26.6.3", "jest-canvas-mock": "2.3.1", - "jest-playwright-preset": "1.5.1", "jest-react-hooks-shallow": "1.5.1", "jest-trx-results-processor": "0.0.7", "less": "3.8.1", @@ -184,7 +183,6 @@ "mini-css-extract-plugin": "2.1.0", "monaco-editor-webpack-plugin": "7.1.0", "node-fetch": "2.6.7", - "playwright": "1.13.0", "prettier": "3.0.3", "process": "0.11.10", "querystring-es3": "0.2.1", @@ -10210,6 +10208,53 @@ "@phosphor/virtualdom": "^1.2.0" } }, + "node_modules/@playwright/test": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.44.0.tgz", + "integrity": "sha512-rNX5lbNidamSUorBhB4XZ9SQTjAqfe5M+p37Z8ic0jPFBMo5iCtQz1kRWkEMg+rYOKSlVycpQmpqjSFq7LXOfg==", + "dev": true, + "dependencies": { + "playwright": "1.44.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@playwright/test/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/@playwright/test/node_modules/playwright": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.44.0.tgz", + "integrity": "sha512-F9b3GUCLQ3Nffrfb6dunPOkE5Mh68tR7zN32L4jCk4FjQamgesGay7/dAAe1WaMEGV04DkdJfcJzjoCKygUaRQ==", + "dev": true, + "dependencies": { + "playwright-core": "1.44.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, "node_modules/@polka/url": { "version": "1.0.0-next.23", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.23.tgz", @@ -11806,6 +11851,7 @@ "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", "dev": true, + "peer": true, "dependencies": { "@hapi/hoek": "^9.0.0" } @@ -11814,13 +11860,15 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@sideway/pinpoint": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@sinclair/typebox": { "version": "0.27.8", @@ -13580,15 +13628,6 @@ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==" }, - "node_modules/@types/wait-on": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/@types/wait-on/-/wait-on-5.3.4.tgz", - "integrity": "sha512-EBsPjFMrFlMbbUFf9D1Fp+PAB2TwmUn7a3YtHyD9RLuTIk1jDd8SxXVAoez2Ciy+8Jsceo2MYEYZzJ/DvorOKw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/ws": { "version": "8.5.10", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", @@ -13611,16 +13650,6 @@ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "dev": true, - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/youtube-player": { "version": "5.5.6", "resolved": "https://registry.npmjs.org/@types/youtube-player/-/youtube-player-5.5.6.tgz", @@ -14314,19 +14343,6 @@ "node": ">= 6.0.0" } }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/airbnb-prop-types": { "version": "2.16.0", "resolved": "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.16.0.tgz", @@ -14553,18 +14569,6 @@ "dev": true, "peer": true }, - "node_modules/append-transform": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", - "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", - "dev": true, - "dependencies": { - "default-require-extensions": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/applicationinsights": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/applicationinsights/-/applicationinsights-1.8.0.tgz", @@ -14584,12 +14588,6 @@ "optional": true, "peer": true }, - "node_modules/archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", - "dev": true - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -15667,15 +15665,6 @@ "ieee754": "^1.1.4" } }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -15714,57 +15703,6 @@ "node": ">=0.10.0" } }, - "node_modules/caching-transform": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", - "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", - "dev": true, - "dependencies": { - "hasha": "^5.0.0", - "make-dir": "^3.0.0", - "package-hash": "^4.0.0", - "write-file-atomic": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/caching-transform/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/caching-transform/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/caching-transform/node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, "node_modules/call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", @@ -16210,15 +16148,6 @@ "node": ">=0.10.0" } }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/clean-webpack-plugin": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-4.0.0.tgz", @@ -17285,19 +17214,6 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, - "node_modules/cwd": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/cwd/-/cwd-0.10.0.tgz", - "integrity": "sha512-YGZxdTTL9lmLkCUTpg4j0zQ7IhRB5ZmqNBbGCl3Tg6MP/d5/6sY7L5mmTjzbc6JKgVZYiqTQTNhPFsbXNGlRaA==", - "dev": true, - "dependencies": { - "find-pkg": "^0.1.2", - "fs-exists-sync": "^0.1.0" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/d3": { "version": "7.8.5", "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz", @@ -18083,12 +17999,6 @@ "node": ">=8" } }, - "node_modules/dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "dev": true - }, "node_modules/deep-diff": { "version": "0.3.8", "resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-0.3.8.tgz", @@ -18191,21 +18101,6 @@ "node": ">=10.17.0" } }, - "node_modules/default-require-extensions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", - "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", - "dev": true, - "dependencies": { - "strip-bom": "^4.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -19167,12 +19062,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true - }, "node_modules/es6-templates": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/es6-templates/-/es6-templates-0.2.3.tgz", @@ -20079,15 +19968,6 @@ "node": ">= 10.14.2" } }, - "node_modules/expect-playwright": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/expect-playwright/-/expect-playwright-0.3.3.tgz", - "integrity": "sha512-uoeyx2D5LawJdziMdweOp6cnZzFOOPT9VvPG6gOh6YC7N9pU0k2KpVlRiz/Vc/fFBiGUNNeJq2Aq+9GJ65Nfrw==", - "dev": true, - "peerDependencies": { - "playwright-core": "^1.9.1" - } - }, "node_modules/expect/node_modules/@jest/types": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", @@ -20350,26 +20230,6 @@ "node": ">=0.10.0" } }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, "node_modules/extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -20558,15 +20418,6 @@ "bser": "2.1.1" } }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "dependencies": { - "pend": "~1.2.0" - } - }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -20685,124 +20536,6 @@ "node": ">=6" } }, - "node_modules/find-file-up": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/find-file-up/-/find-file-up-0.1.3.tgz", - "integrity": "sha512-mBxmNbVyjg1LQIIpgO8hN+ybWBgDQK8qjht+EbrTCGmmPV/sc7RF1i9stPTD6bpvXZywBdrwRYxhSdJv867L6A==", - "dev": true, - "dependencies": { - "fs-exists-sync": "^0.1.0", - "resolve-dir": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/find-pkg": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/find-pkg/-/find-pkg-0.1.2.tgz", - "integrity": "sha512-0rnQWcFwZr7eO0513HahrWafsc3CTFioEB7DRiEYCUM/70QXSY8f3mCST17HXLcPvEhzH/Ty/Bxd72ZZsr/yvw==", - "dev": true, - "dependencies": { - "find-file-up": "^0.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/find-process": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/find-process/-/find-process-1.4.7.tgz", - "integrity": "sha512-/U4CYp1214Xrp3u3Fqr9yNynUrr5Le4y0SsJh2lMDDSbpwYSz3M2SMWQC+wqcx79cN8PQtHQIL8KnuY9M66fdg==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "commander": "^5.1.0", - "debug": "^4.1.1" - }, - "bin": { - "find-process": "bin/find-process.js" - } - }, - "node_modules/find-process/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/find-process/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/find-process/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/find-process/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/find-process/node_modules/commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/find-process/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-process/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", @@ -20968,19 +20701,6 @@ "node": ">=0.10.0" } }, - "node_modules/foreground-child": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", - "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -21284,41 +21004,12 @@ "node": ">= 0.6" } }, - "node_modules/fromentries": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", - "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "optional": true }, - "node_modules/fs-exists-sync": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", - "integrity": "sha512-cR/vflFyPZtrN6b38ZyWxpWdhlXrzZEBawlpBQMq7033xVY7/kg0GDMBK5jg8lDYQckdJ5x/YC88lM3C7VMsLg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/fs-extra": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.0.tgz", @@ -21967,31 +21658,6 @@ "node": ">=0.10.0" } }, - "node_modules/hasha": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", - "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", - "dev": true, - "dependencies": { - "is-stream": "^2.0.0", - "type-fest": "^0.8.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/hasha/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/hasher": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/hasher/-/hasher-1.2.0.tgz", @@ -22106,18 +21772,6 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, - "node_modules/homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", - "dev": true, - "dependencies": { - "parse-passwd": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -23701,18 +23355,6 @@ "node": ">=6" } }, - "node_modules/istanbul-lib-hook": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", - "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", - "dev": true, - "dependencies": { - "append-transform": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/istanbul-lib-instrument": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", @@ -23738,44 +23380,6 @@ "semver": "bin/semver.js" } }, - "node_modules/istanbul-lib-processinfo": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", - "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", - "dev": true, - "dependencies": { - "archy": "^1.0.0", - "cross-spawn": "^7.0.3", - "istanbul-lib-coverage": "^3.2.0", - "p-map": "^3.0.0", - "rimraf": "^3.0.0", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-processinfo/node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-processinfo/node_modules/p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", @@ -24070,217 +23674,6 @@ "node": ">=8" } }, - "node_modules/jest-circus": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-26.6.3.tgz", - "integrity": "sha512-ACrpWZGcQMpbv13XbzRzpytEJlilP/Su0JtNCi5r/xLpOUhnaIJr8leYYpLEMgPFURZISEHrnnpmB54Q/UziPw==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/babel__traverse": "^7.0.4", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "expect": "^26.6.2", - "is-generator-fn": "^2.0.0", - "jest-each": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-runner": "^26.6.3", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2", - "stack-utils": "^2.0.2", - "throat": "^5.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-circus/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-circus/node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-circus/node_modules/@types/yargs": { - "version": "15.0.19", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", - "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-circus/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-circus/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-circus/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-circus/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-circus/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-circus/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/jest-circus/node_modules/jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-circus/node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/jest-circus/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-circus/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/jest-cli": { "version": "26.6.3", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz", @@ -26622,38 +26015,6 @@ "node": ">=8" } }, - "node_modules/jest-playwright-preset": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/jest-playwright-preset/-/jest-playwright-preset-1.5.1.tgz", - "integrity": "sha512-zsFAe61V72vSLkd1fCcf7YbHmbdAB82SLBdUuCUF43aODIojshQEDF88KdWL9P+4JQ+DvEABT+6sFX4sY0rR2w==", - "dev": true, - "dependencies": { - "expect-playwright": "^0.3.3", - "jest-circus": "^26.6.3", - "jest-environment-node": "^26.6.2", - "jest-process-manager": "^0.2.9", - "jest-runner": "^26.6.3", - "nyc": "^15.1.0", - "playwright-core": ">=1.2.0", - "rimraf": "^3.0.2", - "uuid": "^8.3.2" - } - }, - "node_modules/jest-playwright-preset/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/jest-pnp-resolver": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", @@ -26670,113 +26031,6 @@ } } }, - "node_modules/jest-process-manager": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/jest-process-manager/-/jest-process-manager-0.2.9.tgz", - "integrity": "sha512-IKVdOSz1NLwKg9HTeyEDn63waMvKK6wcS+tCarGquNIktlXt4zAW2cfJ9vAA/xBcidWYKOPXHvy1l1N8qfw3Ww==", - "dev": true, - "dependencies": { - "@types/wait-on": "^5.2.0", - "chalk": "^4.1.0", - "cwd": "^0.10.0", - "exit": "^0.1.2", - "find-process": "^1.4.4", - "prompts": "^2.4.0", - "signal-exit": "^3.0.3", - "spawnd": "^4.4.0", - "tree-kill": "^1.2.2", - "wait-on": "^5.2.1" - } - }, - "node_modules/jest-process-manager/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-process-manager/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-process-manager/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-process-manager/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-process-manager/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-process-manager/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-process-manager/node_modules/wait-on": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-5.3.0.tgz", - "integrity": "sha512-DwrHrnTK+/0QFaB9a8Ol5Lna3k7WvUR4jzSKmz0YaPBpuN2sACyiPVKVfj6ejnjcajAcvn3wlbTyMIn9AZouOg==", - "dev": true, - "dependencies": { - "axios": "^0.21.1", - "joi": "^17.3.0", - "lodash": "^4.17.21", - "minimist": "^1.2.5", - "rxjs": "^6.6.3" - }, - "bin": { - "wait-on": "bin/wait-on" - }, - "engines": { - "node": ">=8.9.0" - } - }, "node_modules/jest-react-hooks-shallow": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/jest-react-hooks-shallow/-/jest-react-hooks-shallow-1.5.1.tgz", @@ -28713,6 +27967,7 @@ "resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz", "integrity": "sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==", "dev": true, + "peer": true, "dependencies": { "@hapi/hoek": "^9.0.0", "@hapi/topo": "^5.0.0", @@ -28721,12 +27976,6 @@ "@sideway/pinpoint": "^2.0.0" } }, - "node_modules/jpeg-js": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", - "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", - "dev": true - }, "node_modules/jquery": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", @@ -32465,18 +31714,6 @@ "dev": true, "optional": true }, - "node_modules/node-preload": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", - "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", - "dev": true, - "dependencies": { - "process-on-spawn": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/node-releases": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", @@ -32591,205 +31828,6 @@ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==" }, - "node_modules/nyc": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", - "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", - "dev": true, - "dependencies": { - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "caching-transform": "^4.0.0", - "convert-source-map": "^1.7.0", - "decamelize": "^1.2.0", - "find-cache-dir": "^3.2.0", - "find-up": "^4.1.0", - "foreground-child": "^2.0.0", - "get-package-type": "^0.1.0", - "glob": "^7.1.6", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-hook": "^3.0.0", - "istanbul-lib-instrument": "^4.0.0", - "istanbul-lib-processinfo": "^2.0.2", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "make-dir": "^3.0.0", - "node-preload": "^0.2.1", - "p-map": "^3.0.0", - "process-on-spawn": "^1.0.0", - "resolve-from": "^5.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "spawn-wrap": "^2.0.0", - "test-exclude": "^6.0.0", - "yargs": "^15.0.2" - }, - "bin": { - "nyc": "bin/nyc.js" - }, - "engines": { - "node": ">=8.9" - } - }, - "node_modules/nyc/node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" - } - }, - "node_modules/nyc/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/nyc/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/nyc/node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", @@ -33198,15 +32236,6 @@ "node": ">=8" } }, - "node_modules/os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/os-name": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/os-name/-/os-name-3.1.0.tgz", @@ -33308,21 +32337,6 @@ "node": ">=6" } }, - "node_modules/package-hash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", - "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.15", - "hasha": "^5.0.0", - "lodash.flattendeep": "^4.4.0", - "release-zalgo": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/param-case": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", @@ -33374,15 +32388,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/parse-srcset": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", @@ -33718,12 +32723,6 @@ "node": ">=8" } }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true - }, "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -33803,39 +32802,10 @@ "node": ">=8" } }, - "node_modules/playwright": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.13.0.tgz", - "integrity": "sha512-GA5OyEeKx1v/pRcANmYncCT67Y7Y4N5zLRU5E690dn/Id10sooR5hQZmCDYsjXlutZb/1q0R3sITALnvhEjCjg==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "commander": "^6.1.0", - "debug": "^4.1.1", - "extract-zip": "^2.0.1", - "https-proxy-agent": "^5.0.0", - "jpeg-js": "^0.4.2", - "mime": "^2.4.6", - "pngjs": "^5.0.0", - "progress": "^2.0.3", - "proper-lockfile": "^4.1.1", - "proxy-from-env": "^1.1.0", - "rimraf": "^3.0.2", - "stack-utils": "^2.0.3", - "ws": "^7.4.6", - "yazl": "^2.5.1" - }, - "bin": { - "playwright": "lib/cli/cli.js" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/playwright-core": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.0.tgz", - "integrity": "sha512-fvKewVJpGeca8t0ipM56jkVSU6Eo0RmFvQ/MaCQNDYm+sdvKkMBBWTE1FdeMqIdumRaXXjZChWHvIzCGM/tA/Q==", + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.44.0.tgz", + "integrity": "sha512-ZTbkNpFfYcGWohvTTl+xewITm7EOuqIqex0c7dNZ+aXsbrLj0qI8XlGKfPpipjm0Wny/4Lt4CJsWJk1stVS5qQ==", "dev": true, "bin": { "playwright-core": "cli.js" @@ -33844,42 +32814,6 @@ "node": ">=16" } }, - "node_modules/playwright/node_modules/commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/playwright/node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/playwright/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/plotly.js-cartesian-dist-min": { "version": "1.52.3", "resolved": "https://registry.npmjs.org/plotly.js-cartesian-dist-min/-/plotly.js-cartesian-dist-min-1.52.3.tgz", @@ -33895,15 +32829,6 @@ "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==" }, - "node_modules/pngjs": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", - "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/polygon-offset": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/polygon-offset/-/polygon-offset-0.3.1.tgz", @@ -34315,27 +33240,6 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, - "node_modules/process-on-spawn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", - "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", - "dev": true, - "dependencies": { - "fromentries": "^1.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", @@ -34384,26 +33288,6 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, - "node_modules/proper-lockfile": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", - "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "retry": "^0.12.0", - "signal-exit": "^3.0.2" - } - }, - "node_modules/proper-lockfile/node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, "node_modules/property-information": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", @@ -34438,12 +33322,6 @@ "node": ">= 0.10" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true - }, "node_modules/prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -35806,18 +34684,6 @@ "node": ">= 0.10" } }, - "node_modules/release-zalgo": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", - "dev": true, - "dependencies": { - "es6-error": "^4.0.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/remark-parse": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-5.0.0.tgz", @@ -36153,80 +35019,6 @@ "node": ">=8" } }, - "node_modules/resolve-dir": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-0.1.1.tgz", - "integrity": "sha512-QxMPqI6le2u0dCLyiGzgy92kjkkL6zO0XyvHzjdTNH3zM6e5Hz3BwG6+aEyNgiQ5Xz6PwTwgQEj3U50dByPKIA==", - "dev": true, - "dependencies": { - "expand-tilde": "^1.2.2", - "global-modules": "^0.2.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-dir/node_modules/expand-tilde": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-1.2.2.tgz", - "integrity": "sha512-rtmc+cjLZqnu9dSYosX9EWmSJhTwpACgJQTfj4hgg2JjOD/6SIQalZrt4a3aQeh++oNxkazcaxrhPUj6+g5G/Q==", - "dev": true, - "dependencies": { - "os-homedir": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-dir/node_modules/global-modules": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-0.2.3.tgz", - "integrity": "sha512-JeXuCbvYzYXcwE6acL9V2bAOeSIGl4dD+iwLY9iUx2VBJJ80R18HCn+JCwHM9Oegdfya3lEkGCdaRkSyc10hDA==", - "dev": true, - "dependencies": { - "global-prefix": "^0.1.4", - "is-windows": "^0.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-dir/node_modules/global-prefix": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-0.1.5.tgz", - "integrity": "sha512-gOPiyxcD9dJGCEArAhF4Hd0BAqvAe/JzERP7tYumE4yIkmIedPUVXcJFWbV3/p/ovIIvKjkrTk+f1UVkq7vvbw==", - "dev": true, - "dependencies": { - "homedir-polyfill": "^1.0.0", - "ini": "^1.3.4", - "is-windows": "^0.2.0", - "which": "^1.2.12" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-dir/node_modules/is-windows": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", - "integrity": "sha512-n67eJYmXbniZB7RF4I/FTjK1s6RPOCTxhYrVYLRaCt3lF0mpWZPKr3T2LSZAqyjQsxR2qMmGYXXzK0YWwcPM1Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-dir/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -37397,59 +36189,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/spawn-wrap": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", - "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", - "dev": true, - "dependencies": { - "foreground-child": "^2.0.0", - "is-windows": "^1.0.2", - "make-dir": "^3.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "which": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/spawn-wrap/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/spawn-wrap/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/spawnd": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/spawnd/-/spawnd-4.4.0.tgz", - "integrity": "sha512-jLPOfB6QOEgMOQY15Z6+lwZEhH3F5ncXxIaZ7WHPIapwNNLyjrs61okj3VJ3K6tmP5TZ6cO0VAu9rEY4MD4YQg==", - "dev": true, - "dependencies": { - "exit": "^0.1.2", - "signal-exit": "^3.0.2", - "tree-kill": "^1.2.2", - "wait-port": "^0.2.7" - } - }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -38589,15 +37328,6 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, - "bin": { - "tree-kill": "cli.js" - } - }, "node_modules/trim": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", @@ -39789,29 +38519,6 @@ "node": ">=8.9.0" } }, - "node_modules/wait-port": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-0.2.14.tgz", - "integrity": "sha512-kIzjWcr6ykl7WFbZd0TMae8xovwqcqbx6FM9l+7agOgUByhzdjfzZBPK2CPufldTOMxbUivss//Sh9MFawmPRQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "commander": "^3.0.2", - "debug": "^4.1.1" - }, - "bin": { - "wait-port": "bin/wait-port.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wait-port/node_modules/commander": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", - "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", - "dev": true - }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -40951,25 +39658,6 @@ "node": ">=8" } }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "node_modules/yazl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", - "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", - "dev": true, - "dependencies": { - "buffer-crc32": "~0.2.3" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index d27273af9..d29fdebd6 100644 --- a/package.json +++ b/package.json @@ -117,6 +117,7 @@ "@babel/preset-env": "7.9.0", "@babel/preset-react": "7.9.4", "@babel/preset-typescript": "7.9.0", + "@playwright/test": "1.44.0", "@testing-library/react": "11.2.3", "@types/applicationinsights-js": "1.0.7", "@types/codemirror": "0.0.56", @@ -161,7 +162,6 @@ "eslint-plugin-no-null": "1.0.2", "eslint-plugin-prefer-arrow": "1.2.3", "eslint-plugin-react-hooks": "4.6.0", - "expect-playwright": "0.3.3", "fast-glob": "3.2.5", "fs-extra": "7.0.0", "html-inline-css-webpack-plugin": "1.11.2", @@ -170,7 +170,6 @@ "html-webpack-plugin": "5.5.3", "jest": "26.6.3", "jest-canvas-mock": "2.3.1", - "jest-playwright-preset": "1.5.1", "jest-react-hooks-shallow": "1.5.1", "jest-trx-results-processor": "0.0.7", "less": "3.8.1", @@ -179,7 +178,6 @@ "mini-css-extract-plugin": "2.1.0", "monaco-editor-webpack-plugin": "7.1.0", "node-fetch": "2.6.7", - "playwright": "1.13.0", "prettier": "3.0.3", "process": "0.11.10", "querystring-es3": "0.2.1", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 000000000..074e7e5f5 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,53 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: 'test', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 3 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: process.env.CI ? 'blob' : 'html', + timeout: 10 * 60 * 1000, + use: { + actionTimeout: 5 * 60 * 1000, + trace: 'off', + video: 'off', + screenshot: 'on', + testIdAttribute: 'data-test', + contextOptions: { + ignoreHTTPSErrors: true, + }, + }, + + expect: { + timeout: 5 * 60 * 1000, + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + ], + + webServer: { + command: 'npm run start', + url: 'https://127.0.0.1:1234/_ready', + timeout: 120 * 1000, + ignoreHTTPSErrors: true, + reuseExistingServer: !process.env.CI, + }, +}); diff --git a/src/Explorer/Controls/TreeComponent/LegacyTreeComponent.tsx b/src/Explorer/Controls/TreeComponent/LegacyTreeComponent.tsx index 80a682dda..854818138 100644 --- a/src/Explorer/Controls/TreeComponent/LegacyTreeComponent.tsx +++ b/src/Explorer/Controls/TreeComponent/LegacyTreeComponent.tsx @@ -166,6 +166,7 @@ export class LegacyTreeNodeComponent extends React.Component< return (
    ) => this.onNodeClick(event, node)} onKeyPress={(event: React.KeyboardEvent) => this.onNodeKeyPress(event, node)} @@ -174,9 +175,9 @@ export class LegacyTreeNodeComponent extends React.Component< >
    {this.renderCollapseExpandIcon(node)} {node.iconSrc && } @@ -264,7 +265,7 @@ export class LegacyTreeNodeComponent extends React.Component< onMenuDismissed: (contextualMenu?: IContextualMenuProps) => this.setState({ isMenuShowing: false }), contextualMenuItemAs: (props: IContextualMenuItemProps) => (
    e.target.dispatchEvent(LegacyTreeNodeComponent.createClickEvent())} > diff --git a/src/Explorer/Controls/TreeComponent/TreeNodeComponent.tsx b/src/Explorer/Controls/TreeComponent/TreeNodeComponent.tsx index 354046222..fb60aab2e 100644 --- a/src/Explorer/Controls/TreeComponent/TreeNodeComponent.tsx +++ b/src/Explorer/Controls/TreeComponent/TreeNodeComponent.tsx @@ -147,6 +147,7 @@ export const TreeNodeComponent: React.FC = ({ const treeItem = (
    => { if (selectedDatabase?.id() && databaseInput !== selectedDatabase.id()) { setFormError( - `Input database name "${databaseInput}" does not match the selected database "${selectedDatabase.id()}"`, + `Input ${getDatabaseName()} name "${databaseInput}" does not match the selected ${getDatabaseName()} "${selectedDatabase.id()}"`, ); - logConsoleError(`Error while deleting collection ${selectedDatabase && selectedDatabase.id()}`); + logConsoleError(`Error while deleting ${getDatabaseName()} ${selectedDatabase && selectedDatabase.id()}`); logConsoleError( - `Input database name "${databaseInput}" does not match the selected database "${selectedDatabase.id()}"`, + `Input ${getDatabaseName()} name "${databaseInput}" does not match the selected ${getDatabaseName()} "${selectedDatabase.id()}"`, ); return; } @@ -123,17 +124,18 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent {!formError && }
    * - Confirm by typing the database id + Confirm by typing the {getDatabaseName()} id { @@ -149,7 +151,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent - What is the reason why you are deleting this database? + What is the reason why you are deleting this {getDatabaseName()}? = ( - Confirm by typing the database id + Confirm by typing the + Database + id - What is the reason why you are deleting this database? + What is the reason why you are deleting this + Database + ?