mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-31 15:45:23 +00:00
fix(sidebar): remeasure visible rows after density changes (#1489)
* fix(sidebar): remeasure visible rows after density changes * test(credentials): avoid flaky Ed25519 key generation
This commit is contained in:
1 parent
107cf55d8c
commit
5f3372f9f6
2 files changed
+135
No files matched your search
@@ -861,6 +861,12 @@ export function SidebarTree({
|
|||||||
// and made the list visibly jump apart on every pointer move.
|
// and made the list visibly jump apart on every pointer move.
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
virtualizer.measure();
|
virtualizer.measure();
|
||||||
|
// Rebuild the cleared size cache before measuring the mounted rows again.
|
||||||
|
virtualizer.getTotalSize();
|
||||||
|
// Unchanged rows will not trigger ResizeObserver after their cache is cleared.
|
||||||
|
parentRef.current
|
||||||
|
?.querySelectorAll<HTMLElement>("[data-index]")
|
||||||
|
.forEach((element) => virtualizer.measureElement(element));
|
||||||
}, [virtualizer, density, trayTrigger, showTags, showResourceBars]);
|
}, [virtualizer, density, trayTrigger, showTags, showResourceBars]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
import { act, render } from "@testing-library/react";
|
||||||
|
import { afterEach, beforeEach, expect, it, vi } from "vitest";
|
||||||
|
import type { Host, HostFolder } from "@/types/ui-types";
|
||||||
|
import { SidebarTree } from "@/sidebar/tree/SidebarTree";
|
||||||
|
|
||||||
|
vi.mock("@/main-axios", () => ({}));
|
||||||
|
vi.mock("@/api/plugins-api", () => ({}));
|
||||||
|
vi.mock("@/settings/HostPluginSections", () => ({
|
||||||
|
usePluginHostSections: () => [],
|
||||||
|
}));
|
||||||
|
vi.mock("@/contexts/UiPreferencesContext", () => ({
|
||||||
|
useAreaPreferences: () => ({ showResourceBars: false, rowActions: "hover" }),
|
||||||
|
}));
|
||||||
|
vi.mock("@/sidebar/FolderMetadataDialog", () => ({
|
||||||
|
FolderMetadataDialog: () => null,
|
||||||
|
}));
|
||||||
|
vi.mock("@/sidebar/HostShareModal", () => ({ HostShareModal: () => null }));
|
||||||
|
vi.mock("@/sidebar/tree/FolderItem/FolderItem", () => ({
|
||||||
|
FolderItem: () => <div>Folder</div>,
|
||||||
|
folderHostCount: () => 1,
|
||||||
|
}));
|
||||||
|
vi.mock("@/sidebar/tree/HostItem/HostItem", () => ({
|
||||||
|
HostItem: () => <div>Host</div>,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const observers: Array<{
|
||||||
|
callback: ResizeObserverCallback;
|
||||||
|
nodes: Set<Element>;
|
||||||
|
}> = [];
|
||||||
|
let hostHeight = 55;
|
||||||
|
function rect(element: Element): DOMRect {
|
||||||
|
const index = element.getAttribute("data-index");
|
||||||
|
const height = index === "0" ? 40 : index === "1" ? hostHeight : 600;
|
||||||
|
return {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 300,
|
||||||
|
height,
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 300,
|
||||||
|
bottom: height,
|
||||||
|
toJSON() {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function resize(rows: Element[]) {
|
||||||
|
act(() => {
|
||||||
|
for (const observer of observers) {
|
||||||
|
const entries = rows
|
||||||
|
.filter((row) => observer.nodes.has(row))
|
||||||
|
.map(
|
||||||
|
(target) =>
|
||||||
|
({
|
||||||
|
target,
|
||||||
|
borderBoxSize: [
|
||||||
|
{ blockSize: rect(target).height, inlineSize: 300 },
|
||||||
|
],
|
||||||
|
}) as unknown as ResizeObserverEntry,
|
||||||
|
);
|
||||||
|
if (entries.length) observer.callback(entries, {} as ResizeObserver);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
localStorage.setItem("hostOpenFolders", JSON.stringify(["group"]));
|
||||||
|
hostHeight = 55;
|
||||||
|
observers.length = 0;
|
||||||
|
vi.stubGlobal(
|
||||||
|
"ResizeObserver",
|
||||||
|
class {
|
||||||
|
nodes = new Set<Element>();
|
||||||
|
constructor(callback: ResizeObserverCallback) {
|
||||||
|
observers.push({ callback, nodes: this.nodes });
|
||||||
|
}
|
||||||
|
observe(node: Element) {
|
||||||
|
this.nodes.add(node);
|
||||||
|
}
|
||||||
|
unobserve(node: Element) {
|
||||||
|
this.nodes.delete(node);
|
||||||
|
}
|
||||||
|
disconnect() {
|
||||||
|
this.nodes.clear();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
vi.spyOn(HTMLElement.prototype, "getBoundingClientRect").mockImplementation(
|
||||||
|
function () {
|
||||||
|
return rect(this);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
vi.spyOn(HTMLElement.prototype, "offsetHeight", "get").mockReturnValue(600);
|
||||||
|
vi.spyOn(HTMLElement.prototype, "offsetWidth", "get").mockReturnValue(300);
|
||||||
|
});
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
vi.unstubAllGlobals();
|
||||||
|
localStorage.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps an unchanged folder's measured height after changing density", () => {
|
||||||
|
const host = {
|
||||||
|
id: "1",
|
||||||
|
name: "Host",
|
||||||
|
ip: "example.com",
|
||||||
|
username: "test",
|
||||||
|
} as Host;
|
||||||
|
const children = [
|
||||||
|
{ name: "group", path: "group", children: [host] },
|
||||||
|
] as HostFolder[];
|
||||||
|
const props = {
|
||||||
|
children,
|
||||||
|
onOpenTab: vi.fn(),
|
||||||
|
onEditHost: vi.fn(),
|
||||||
|
selectionMode: false,
|
||||||
|
onToggleSelectionMode: vi.fn(),
|
||||||
|
};
|
||||||
|
const view = render(<SidebarTree {...props} density="comfortable" />);
|
||||||
|
const rows = () =>
|
||||||
|
Array.from(view.container.querySelectorAll("[data-index]"));
|
||||||
|
expect(rows()).toHaveLength(2);
|
||||||
|
resize(rows());
|
||||||
|
expect((rows()[1] as HTMLElement).style.transform).toBe("translateY(40px)");
|
||||||
|
hostHeight = 30;
|
||||||
|
view.rerender(<SidebarTree {...props} density="compact" />);
|
||||||
|
// Folder height is unchanged, so the browser only reports the host resize.
|
||||||
|
resize([rows()[1]]);
|
||||||
|
expect((rows()[1] as HTMLElement).style.transform).toBe("translateY(40px)");
|
||||||
|
});
|
||||||
Reference in new issue
Block a user