fix(sidebar): align group selection with visible tree
This commit is contained in:
1 parent
c09dfbc813
commit
a40dc02825
6 files changed
+297
-40
No files matched your search
@@ -668,6 +668,19 @@ const filteredNodes = computed(() => {
|
||||
return nodes;
|
||||
});
|
||||
|
||||
const projectedConnectionIds = computed<ReadonlySet<string> | null>(() => {
|
||||
if (!isRootListPartial.value && !deferredSearchQuery.value) return null;
|
||||
const connectionIds = new Set<string>();
|
||||
const visit = (nodes: readonly TreeNode[]) => {
|
||||
for (const node of nodes) {
|
||||
if (node.type === "connection" && node.connectionId) connectionIds.add(node.connectionId);
|
||||
if (node.children?.length) visit(node.children);
|
||||
}
|
||||
};
|
||||
visit(filteredNodes.value);
|
||||
return connectionIds;
|
||||
});
|
||||
|
||||
const flatNodes = computed<FlatTreeNode[]>(() =>
|
||||
insertSidebarTableSearchControls(flattenTree(filteredNodes.value), {
|
||||
enabled: settingsStore.editorSettings.sidebarTableSearchEnabled && !isTreeSearchFiltering.value,
|
||||
@@ -1124,6 +1137,7 @@ const pasteHandlerRegistry = createSidebarPasteHandlerRegistry();
|
||||
provide(sidebarTreeContextKey, {
|
||||
getVisibleNodes: () => selectableVisibleNodes.value,
|
||||
getVisibleNodeIndex: (id: string) => selectableVisibleNodeIndexById.value.get(id) ?? -1,
|
||||
getProjectedConnectionIds: () => projectedConnectionIds.value,
|
||||
// Cover both sides of the input debounce: the immediate query prevents a
|
||||
// collapse while a projection is about to start, and the deferred query
|
||||
// keeps the currently rendered projection alive while clearing settles.
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
Lock,
|
||||
Archive,
|
||||
Square,
|
||||
Minus,
|
||||
X,
|
||||
CircleX,
|
||||
RefreshCw,
|
||||
@@ -61,6 +62,7 @@ import { sidebarDisplayTableName } from "@/lib/sidebar/sidebarTableNameDisplay";
|
||||
import { shouldMeasureSidebarLabelOverflow } from "@/lib/sidebar/sidebarLabelTooltip";
|
||||
import { filterSidebarModifierSelectionIds, supportsSidebarModifierSelection, treeSelectionRangeIdsByIndex, treeSelectionRangeIds } from "@/lib/sidebar/sidebarTreeSelection";
|
||||
import { applyConnectionMultiSelection, applyTreeNodeSelection, connectionMultiSelectionAfterToggle } from "@/lib/sidebar/sidebarConnectionMultiSelect";
|
||||
import { connectionBearingGroupIdsUnder, connectionIdsUnderGroup } from "@/lib/sidebar/sidebarLayout";
|
||||
import { isSidebarDatabaseOpenForVisual } from "@/lib/sidebar/sidebarDatabaseOpenState";
|
||||
import { sidebarTreeContextKey } from "@/lib/sidebar/sidebarTreeContext";
|
||||
import { connectionCanConfigureSidebarVisibleDatabases } from "@/lib/sidebar/sidebarVisibleFilterMenu";
|
||||
@@ -645,37 +647,53 @@ function toggleConnectionMultiSelection(event: MouseEvent) {
|
||||
rowRef.value?.focus({ preventScroll: true });
|
||||
}
|
||||
|
||||
function selectedConnectionGroupIdsForAction(): string[] {
|
||||
const groupIds = connectionGroupIdsForSelection();
|
||||
return connectionStore.selectedTreeNodeIds.filter((id) => groupIds.has(id));
|
||||
function connectionIdsForActiveGroupSelection(): string[] {
|
||||
if (activeNode.value.type !== "connection-group") return [];
|
||||
const groupConnectionIds = connectionIdsUnderGroup(connectionStore.sidebarLayout, activeNode.value.id);
|
||||
const projectedConnectionIds = sidebarTreeContext?.getProjectedConnectionIds?.();
|
||||
return projectedConnectionIds ? groupConnectionIds.filter((id) => projectedConnectionIds.has(id)) : groupConnectionIds;
|
||||
}
|
||||
|
||||
function isConnectionGroupSelectionChecked(): boolean {
|
||||
if (!isConnectionGroupMultiSelectActive() || activeNode.value.type !== "connection-group") return false;
|
||||
return connectionStore.selectedTreeNodeIds.includes(activeNode.value.id);
|
||||
}
|
||||
const connectionGroupSelectionState = computed<"none" | "partial" | "all">(() => {
|
||||
const groupConnectionIds = connectionIdsForActiveGroupSelection();
|
||||
if (groupConnectionIds.length === 0) return "none";
|
||||
const selectedIds = connectionStore.selectedTreeNodeIdsSet;
|
||||
const selectedCount = groupConnectionIds.filter((id) => selectedIds.has(id)).length;
|
||||
if (selectedCount === 0) return "none";
|
||||
if (selectedCount === groupConnectionIds.length) return "all";
|
||||
return "partial";
|
||||
});
|
||||
|
||||
function toggleConnectionGroupMultiSelection(event: MouseEvent) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (activeNode.value.type !== "connection-group") return;
|
||||
|
||||
const selectedGroupIds = connectionStore.connectionMultiSelectActive ? selectedConnectionGroupIdsForAction() : [];
|
||||
const nextGroupIds = new Set(selectedGroupIds);
|
||||
if (nextGroupIds.has(activeNode.value.id)) nextGroupIds.delete(activeNode.value.id);
|
||||
else nextGroupIds.add(activeNode.value.id);
|
||||
const nodeIds = [...nextGroupIds];
|
||||
const activeNodeId = nextGroupIds.has(activeNode.value.id) ? activeNode.value.id : (nodeIds[0] ?? null);
|
||||
applyTreeNodeSelection(
|
||||
connectionStore,
|
||||
{
|
||||
nodeIds,
|
||||
activeNodeId,
|
||||
anchorNodeId: activeNodeId,
|
||||
},
|
||||
connectionIdsForSelection(),
|
||||
connectionGroupIdsForSelection(),
|
||||
);
|
||||
const groupConnectionIds = connectionIdsForActiveGroupSelection();
|
||||
if (groupConnectionIds.length === 0) return;
|
||||
|
||||
// 当前是分组多选(Ctrl/Shift 框选分组行)时,改为级联选中连接并重新开始
|
||||
const baseConnectionIds = isConnectionMultiSelectActive() ? selectedConnectionIdsForAction() : [];
|
||||
const next = new Set(baseConnectionIds);
|
||||
const allSelected = groupConnectionIds.every((id) => next.has(id));
|
||||
if (allSelected) {
|
||||
// 分组下连接已全部勾选:取消勾选这些连接,保留其他已勾选的连接
|
||||
for (const id of groupConnectionIds) next.delete(id);
|
||||
} else {
|
||||
// 分组下连接未全部勾选:级联勾选全部连接,并保留其他已勾选的连接
|
||||
for (const id of groupConnectionIds) next.add(id);
|
||||
// 自动展开该分组及含连接的子分组:折叠状态下选中的连接不可见,
|
||||
// 会被树的选择修剪逻辑清空,展开后选择才能保留并可被右键操作
|
||||
connectionStore.expandConnectionGroups(connectionBearingGroupIdsUnder(connectionStore.sidebarLayout, activeNode.value.id));
|
||||
}
|
||||
|
||||
const connectionIds = [...next];
|
||||
applyConnectionMultiSelection(connectionStore, {
|
||||
connectionIds,
|
||||
activeConnectionId: connectionIds[0] ?? null,
|
||||
anchorConnectionId: connectionIds[0] ?? null,
|
||||
active: connectionIds.length > 0,
|
||||
});
|
||||
rowRef.value?.focus({ preventScroll: true });
|
||||
}
|
||||
|
||||
@@ -1424,7 +1442,7 @@ function onKeydown(event: KeyboardEvent) {
|
||||
</div>
|
||||
<span v-if="node.type === 'connection' && node.connectionId && connectionStore.connectedIds.has(node.connectionId)" class="w-1.5 h-1.5 rounded-full bg-green-500 shrink-0" />
|
||||
<span v-if="databaseOpenVisual.showsIndicator" class="w-1.5 h-1.5 rounded-full bg-green-500 shrink-0" />
|
||||
<Badge v-if="isConnectionReadonly" variant="secondary" class="h-4 px-1.5 text-[10px] gap-0.5"><Lock class="w-2.5 h-2.5" />{{ t("connection.readOnlyBadge") }}</Badge>
|
||||
<Badge v-if="isConnectionReadonly" variant="secondary" class="h-4 px-1.5 text-[10px] gap-0.5"> <Lock class="w-2.5 h-2.5" />{{ t("connection.readOnlyBadge") }} </Badge>
|
||||
<ConnectionErrorIndicator v-if="node.type === 'connection'" :connection-id="node.connectionId" trigger-class="h-4 w-4" />
|
||||
<span v-if="formattedObjectStorage()" class="ml-auto shrink-0 text-right text-xs tabular-nums text-muted-foreground">{{ formattedObjectStorage() }}</span>
|
||||
<button
|
||||
@@ -1453,14 +1471,17 @@ function onKeydown(event: KeyboardEvent) {
|
||||
<button
|
||||
v-if="node.type === 'connection-group'"
|
||||
type="button"
|
||||
role="checkbox"
|
||||
data-sidebar-group-selection-toggle="true"
|
||||
class="ml-auto flex h-4 w-4 shrink-0 items-center justify-center rounded text-muted-foreground/55 opacity-0 transition-colors transition-opacity hover:bg-secondary/45 hover:text-foreground focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring group-hover/sidebar-row:opacity-100"
|
||||
:class="{ 'opacity-100': isConnectionGroupSelectionChecked() || isConnectionGroupMultiSelectActive() }"
|
||||
:aria-label="isConnectionGroupSelectionChecked() ? t('connectionGroup.deselectGroup') : t('connectionGroup.selectGroup')"
|
||||
:class="{ 'opacity-100': isConnectionMultiSelectActive() || isConnectionGroupMultiSelectActive() || connectionGroupSelectionState !== 'none' }"
|
||||
:aria-label="connectionGroupSelectionState === 'all' ? t('connectionGroup.deselectGroup') : t('connectionGroup.selectGroup')"
|
||||
:aria-checked="connectionGroupSelectionState === 'partial' ? 'mixed' : connectionGroupSelectionState === 'all'"
|
||||
@mousedown.stop
|
||||
@click="toggleConnectionGroupMultiSelection"
|
||||
>
|
||||
<Check v-if="isConnectionGroupSelectionChecked()" class="h-3 w-3 text-primary" />
|
||||
<Check v-if="connectionGroupSelectionState === 'all'" class="h-3 w-3 text-primary" />
|
||||
<Minus v-else-if="connectionGroupSelectionState === 'partial'" class="h-3 w-3 text-primary" />
|
||||
<Square v-else class="h-3 w-3 stroke-[1.7]" />
|
||||
</button>
|
||||
</div>
|
||||
@@ -1586,6 +1607,7 @@ function onKeydown(event: KeyboardEvent) {
|
||||
.tree-item-active {
|
||||
background-color: var(--tree-connection-active-bg, rgb(235 235 235)) !important;
|
||||
}
|
||||
|
||||
:root.dark .tree-item-active {
|
||||
background-color: var(--tree-connection-active-bg, rgb(36 36 36)) !important;
|
||||
}
|
||||
@@ -1594,6 +1616,7 @@ function onKeydown(event: KeyboardEvent) {
|
||||
.tree-item-active:focus {
|
||||
background-color: var(--tree-connection-active-focus-bg, rgb(211 227 245)) !important;
|
||||
}
|
||||
|
||||
:root.dark .tree-item-active:focus {
|
||||
background-color: var(--tree-connection-active-focus-bg, rgb(33 60 89)) !important;
|
||||
}
|
||||
@@ -1603,6 +1626,7 @@ function onKeydown(event: KeyboardEvent) {
|
||||
background-color: var(--tree-connection-active-bg, rgb(235 235 235)) !important;
|
||||
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--foreground) 14%, transparent);
|
||||
}
|
||||
|
||||
:root.dark .tree-item-active--selection-set:focus {
|
||||
background-color: var(--tree-connection-active-bg, rgb(36 36 36)) !important;
|
||||
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--foreground) 18%, transparent);
|
||||
|
||||
+191
-13
@@ -4,8 +4,9 @@ import { createApp, defineComponent, h, nextTick, type App } from "vue";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import i18n from "@/i18n";
|
||||
import TreeItem from "@/components/sidebar/TreeItem.vue";
|
||||
import { filterSidebarTree } from "@/lib/sidebar/sidebarSearchTree";
|
||||
import { filterSidebarTree, filterSidebarTreeToConnectedConnections } from "@/lib/sidebar/sidebarSearchTree";
|
||||
import { createSidebarTreeRuntime, sidebarTreeRuntimeKey, type SidebarTreeRuntimeHost } from "@/lib/sidebar/sidebarTreeRuntime";
|
||||
import { sidebarTreeContextKey } from "@/lib/sidebar/sidebarTreeContext";
|
||||
import type { SidebarLayout, TreeNode } from "@/types/database";
|
||||
|
||||
const connectionStore = {
|
||||
@@ -14,7 +15,8 @@ const connectionStore = {
|
||||
connectingIds: new Set<string>(),
|
||||
connectionErrors: {},
|
||||
connectionMultiSelectActive: false,
|
||||
connections: [],
|
||||
connections: [] as { id: string }[],
|
||||
expandConnectionGroups: vi.fn(),
|
||||
getConfig: () => ({ id: "connection-1", db_type: "sqlserver" }),
|
||||
getSidebarVisibleFilterSummary: () => null,
|
||||
clearConnectionError: vi.fn(),
|
||||
@@ -74,7 +76,7 @@ function runtimeHost(): SidebarTreeRuntimeHost {
|
||||
};
|
||||
}
|
||||
|
||||
async function mountTreeItem(node: TreeNode) {
|
||||
async function mountTreeItem(node: TreeNode, options: { projectedConnectionIds?: ReadonlySet<string> | null } = {}) {
|
||||
const container = document.createElement("div");
|
||||
document.body.append(container);
|
||||
const runtime = createSidebarTreeRuntime();
|
||||
@@ -88,6 +90,13 @@ async function mountTreeItem(node: TreeNode) {
|
||||
mountedApps.push(app);
|
||||
app.use(i18n);
|
||||
app.provide(sidebarTreeRuntimeKey, runtime);
|
||||
if (options.projectedConnectionIds !== undefined) {
|
||||
app.provide(sidebarTreeContextKey, {
|
||||
getVisibleNodes: () => [node],
|
||||
getVisibleNodeIndex: (id) => (id === node.id ? 0 : -1),
|
||||
getProjectedConnectionIds: () => options.projectedConnectionIds ?? null,
|
||||
});
|
||||
}
|
||||
app.mount(container);
|
||||
await nextTick();
|
||||
|
||||
@@ -96,6 +105,18 @@ async function mountTreeItem(node: TreeNode) {
|
||||
return { row, host };
|
||||
}
|
||||
|
||||
function connectionIdsInProjection(nodes: readonly TreeNode[]): Set<string> {
|
||||
const connectionIds = new Set<string>();
|
||||
const visit = (currentNodes: readonly TreeNode[]) => {
|
||||
for (const node of currentNodes) {
|
||||
if (node.type === "connection" && node.connectionId) connectionIds.add(node.connectionId);
|
||||
if (node.children?.length) visit(node.children);
|
||||
}
|
||||
};
|
||||
visit(nodes);
|
||||
return connectionIds;
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
for (const app of mountedApps.splice(0)) app.unmount();
|
||||
document.body.innerHTML = "";
|
||||
@@ -103,6 +124,10 @@ afterEach(() => {
|
||||
connectionStore.selectedTreeNodeIds = [];
|
||||
connectionStore.selectedTreeNodeIdsSet = new Set<string>();
|
||||
connectionStore.connectionMultiSelectActive = false;
|
||||
// 重置测试中修改的连接列表,避免残留数据影响后续用例的选择过滤
|
||||
connectionStore.connections = [];
|
||||
// 清空分组展开的调用记录,避免上一个用例的调用历史污染断言
|
||||
connectionStore.expandConnectionGroups.mockClear();
|
||||
connectionStore.sidebarLayout = { groups: [], order: [] };
|
||||
connectionStore.treeNodes = [];
|
||||
connectionStore.treeSelectionAnchorId = null;
|
||||
@@ -111,7 +136,43 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
describe("TreeItem load-more activation", () => {
|
||||
it("toggles a connection group through its row checkbox", async () => {
|
||||
it("cascades a connection group checkbox to its connections", async () => {
|
||||
const group: TreeNode = {
|
||||
id: "group-1",
|
||||
label: "Group 1",
|
||||
type: "connection-group",
|
||||
children: [],
|
||||
};
|
||||
connectionStore.treeNodes = [group];
|
||||
// 分组下挂一个连接,勾选框会级联选中该连接
|
||||
connectionStore.sidebarLayout = {
|
||||
groups: [{ id: group.id, name: group.label, collapsed: false }],
|
||||
order: [{ type: "group", id: group.id, children: [{ type: "connection", id: "connection-2" }] }],
|
||||
};
|
||||
connectionStore.connections = [{ id: "connection-2" }];
|
||||
const { row } = await mountTreeItem(group);
|
||||
const toggle = row.querySelector<HTMLButtonElement>('[data-sidebar-group-selection-toggle="true"]');
|
||||
expect(toggle).not.toBeNull();
|
||||
|
||||
toggle?.click();
|
||||
|
||||
// 勾选后级联选中分组下的连接,并自动展开该分组
|
||||
expect(connectionStore.selectedTreeNodeIds).toEqual(["connection-2"]);
|
||||
expect(connectionStore.selectedTreeNodeId).toBe("connection-2");
|
||||
expect(connectionStore.treeSelectionAnchorId).toBe("connection-2");
|
||||
expect(connectionStore.connectionMultiSelectActive).toBe(true);
|
||||
expect(connectionStore.expandConnectionGroups).toHaveBeenCalledWith([group.id]);
|
||||
|
||||
toggle?.click();
|
||||
|
||||
// 再次点击取消勾选分组下的全部连接
|
||||
expect(connectionStore.selectedTreeNodeIds).toEqual([]);
|
||||
expect(connectionStore.selectedTreeNodeId).toBeNull();
|
||||
expect(connectionStore.treeSelectionAnchorId).toBeNull();
|
||||
expect(connectionStore.connectionMultiSelectActive).toBe(false);
|
||||
});
|
||||
|
||||
it("ignores the checkbox of an empty connection group", async () => {
|
||||
const group: TreeNode = {
|
||||
id: "group-1",
|
||||
label: "Group 1",
|
||||
@@ -129,17 +190,134 @@ describe("TreeItem load-more activation", () => {
|
||||
|
||||
toggle?.click();
|
||||
|
||||
expect(connectionStore.selectedTreeNodeIds).toEqual([group.id]);
|
||||
expect(connectionStore.selectedTreeNodeId).toBe(group.id);
|
||||
expect(connectionStore.treeSelectionAnchorId).toBe(group.id);
|
||||
expect(connectionStore.connectionMultiSelectActive).toBe(true);
|
||||
|
||||
toggle?.click();
|
||||
|
||||
// 空分组没有可级联的连接,点击不产生任何选择变化
|
||||
expect(connectionStore.selectedTreeNodeIds).toEqual([]);
|
||||
expect(connectionStore.selectedTreeNodeId).toBeNull();
|
||||
expect(connectionStore.treeSelectionAnchorId).toBeNull();
|
||||
expect(connectionStore.connectionMultiSelectActive).toBe(false);
|
||||
expect(connectionStore.expandConnectionGroups).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("limits a searched group cascade to connections in the current projection", async () => {
|
||||
const firstConnection: TreeNode = {
|
||||
id: "connection-1",
|
||||
label: "Alpha",
|
||||
type: "connection",
|
||||
connectionId: "connection-1",
|
||||
};
|
||||
const secondConnection: TreeNode = {
|
||||
id: "connection-2",
|
||||
label: "Beta",
|
||||
type: "connection",
|
||||
connectionId: "connection-2",
|
||||
};
|
||||
const group: TreeNode = {
|
||||
id: "group-1",
|
||||
label: "Group 1",
|
||||
type: "connection-group",
|
||||
isExpanded: true,
|
||||
children: [firstConnection, secondConnection],
|
||||
};
|
||||
connectionStore.treeNodes = [group];
|
||||
connectionStore.sidebarLayout = {
|
||||
groups: [{ id: group.id, name: group.label, collapsed: false }],
|
||||
order: [
|
||||
{
|
||||
type: "group",
|
||||
id: group.id,
|
||||
children: [
|
||||
{ type: "connection", id: firstConnection.id },
|
||||
{ type: "connection", id: secondConnection.id },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
connectionStore.connections = [{ id: firstConnection.id }, { id: secondConnection.id }];
|
||||
const projection = connectionIdsInProjection(filterSidebarTree([group], "alpha", new Set()));
|
||||
const { row } = await mountTreeItem(group, { projectedConnectionIds: projection });
|
||||
|
||||
row.querySelector<HTMLButtonElement>('[data-sidebar-group-selection-toggle="true"]')?.click();
|
||||
|
||||
expect(connectionStore.selectedTreeNodeIds).toEqual([firstConnection.id]);
|
||||
expect(connectionStore.selectedTreeNodeIds).not.toContain(secondConnection.id);
|
||||
});
|
||||
|
||||
it("limits a connected-only group cascade to connected connections", async () => {
|
||||
const connected: TreeNode = {
|
||||
id: "connection-1",
|
||||
label: "Connected",
|
||||
type: "connection",
|
||||
connectionId: "connection-1",
|
||||
};
|
||||
const disconnected: TreeNode = {
|
||||
id: "connection-2",
|
||||
label: "Disconnected",
|
||||
type: "connection",
|
||||
connectionId: "connection-2",
|
||||
};
|
||||
const group: TreeNode = {
|
||||
id: "group-1",
|
||||
label: "Group 1",
|
||||
type: "connection-group",
|
||||
isExpanded: true,
|
||||
children: [connected, disconnected],
|
||||
};
|
||||
connectionStore.treeNodes = [group];
|
||||
connectionStore.sidebarLayout = {
|
||||
groups: [{ id: group.id, name: group.label, collapsed: false }],
|
||||
order: [
|
||||
{
|
||||
type: "group",
|
||||
id: group.id,
|
||||
children: [
|
||||
{ type: "connection", id: connected.id },
|
||||
{ type: "connection", id: disconnected.id },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
connectionStore.connections = [{ id: connected.id }, { id: disconnected.id }];
|
||||
const projection = connectionIdsInProjection(filterSidebarTreeToConnectedConnections([group], new Set([connected.id])));
|
||||
const { row } = await mountTreeItem(group, { projectedConnectionIds: projection });
|
||||
|
||||
row.querySelector<HTMLButtonElement>('[data-sidebar-group-selection-toggle="true"]')?.click();
|
||||
|
||||
expect(connectionStore.selectedTreeNodeIds).toEqual([connected.id]);
|
||||
expect(connectionStore.selectedTreeNodeIds).not.toContain(disconnected.id);
|
||||
});
|
||||
|
||||
it("exposes none, mixed, and all group selection states", async () => {
|
||||
const group: TreeNode = {
|
||||
id: "group-1",
|
||||
label: "Group 1",
|
||||
type: "connection-group",
|
||||
};
|
||||
connectionStore.sidebarLayout = {
|
||||
groups: [{ id: group.id, name: group.label, collapsed: false }],
|
||||
order: [
|
||||
{
|
||||
type: "group",
|
||||
id: group.id,
|
||||
children: [
|
||||
{ type: "connection", id: "connection-1" },
|
||||
{ type: "connection", id: "connection-2" },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const none = (await mountTreeItem(group)).row.querySelector<HTMLButtonElement>('[data-sidebar-group-selection-toggle="true"]');
|
||||
expect(none?.getAttribute("role")).toBe("checkbox");
|
||||
expect(none?.getAttribute("aria-checked")).toBe("false");
|
||||
expect(none?.getAttribute("aria-label")).toBe(i18n.global.t("connectionGroup.selectGroup"));
|
||||
|
||||
connectionStore.selectedTreeNodeIdsSet = new Set(["connection-1"]);
|
||||
const mixed = (await mountTreeItem(group)).row.querySelector<HTMLButtonElement>('[data-sidebar-group-selection-toggle="true"]');
|
||||
expect(mixed?.getAttribute("aria-checked")).toBe("mixed");
|
||||
expect(mixed?.getAttribute("aria-label")).toBe(i18n.global.t("connectionGroup.selectGroup"));
|
||||
|
||||
connectionStore.selectedTreeNodeIdsSet = new Set(["connection-1", "connection-2"]);
|
||||
const all = (await mountTreeItem(group)).row.querySelector<HTMLButtonElement>('[data-sidebar-group-selection-toggle="true"]');
|
||||
expect(all?.getAttribute("aria-checked")).toBe("true");
|
||||
expect(all?.getAttribute("aria-label")).toBe(i18n.global.t("connectionGroup.deselectGroup"));
|
||||
});
|
||||
|
||||
it("runs load-more on a single click in double-click activation mode", async () => {
|
||||
|
||||
Reference in new issue
Block a user