mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 06:05:24 +00:00
fix(sidebar): keep compact host addresses visible (#1524)
This commit is contained in:
1 parent
64b9b18fe6
commit
718717554d
4 files changed
+117
-16
No files matched your search
@@ -2,8 +2,6 @@ import fs from "node:fs";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { readManifest } from "../lib/plugin-dir.mjs";
|
import { readManifest } from "../lib/plugin-dir.mjs";
|
||||||
import { LEGACY_TABLE_OWNERS as LEGACY_TABLES } from "../../dist/db.js";
|
|
||||||
import { collectOwnedIndexes, findUnownedTableWrites } from "../../dist/ddl.js";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The manifest rules live in src/manifest.ts, which the server uses too, so
|
* The manifest rules live in src/manifest.ts, which the server uses too, so
|
||||||
@@ -37,7 +35,9 @@ export async function validate({ cwd }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
problems.push(...validateMigrations(cwd, raw.id ?? path.basename(cwd)));
|
problems.push(
|
||||||
|
...(await validateMigrations(cwd, raw.id ?? path.basename(cwd))),
|
||||||
|
);
|
||||||
problems.push(...validateNativeDependencies(cwd, raw));
|
problems.push(...validateNativeDependencies(cwd, raw));
|
||||||
problems.push(...validatePackage(cwd, raw));
|
problems.push(...validatePackage(cwd, raw));
|
||||||
|
|
||||||
@@ -115,7 +115,12 @@ const DIALECTS = ["sqlite", "postgres", "mysql"];
|
|||||||
* never ran this command. Here it is a build-time error with a file name
|
* never ran this command. Here it is a build-time error with a file name
|
||||||
* attached, rather than a plugin that fails to activate later.
|
* attached, rather than a plugin that fails to activate later.
|
||||||
*/
|
*/
|
||||||
function validateMigrations(cwd, pluginId) {
|
async function validateMigrations(cwd, pluginId) {
|
||||||
|
const { LEGACY_TABLE_OWNERS: LEGACY_TABLES } =
|
||||||
|
await import("../../dist/db.js");
|
||||||
|
const { collectOwnedIndexes, findUnownedTableWrites } =
|
||||||
|
await import("../../dist/ddl.js");
|
||||||
|
|
||||||
const problems = [];
|
const problems = [];
|
||||||
const legacy = new Set(
|
const legacy = new Set(
|
||||||
Object.entries(LEGACY_TABLES)
|
Object.entries(LEGACY_TABLES)
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
|
import fs from "node:fs";
|
||||||
|
import os from "node:os";
|
||||||
|
import path from "node:path";
|
||||||
|
import { spawnSync } from "node:child_process";
|
||||||
|
|
||||||
|
const roots: string[] = [];
|
||||||
|
afterEach(() => {
|
||||||
|
for (const root of roots.splice(0))
|
||||||
|
fs.rmSync(root, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
function fixture() {
|
||||||
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "termix-cli-bootstrap-"));
|
||||||
|
roots.push(root);
|
||||||
|
const repo = path.resolve(import.meta.dirname, "..");
|
||||||
|
fs.cpSync(
|
||||||
|
path.join(repo, "packages/plugin-sdk/cli"),
|
||||||
|
path.join(root, "packages/plugin-sdk/cli"),
|
||||||
|
{ recursive: true },
|
||||||
|
);
|
||||||
|
fs.symlinkSync(
|
||||||
|
path.join(repo, "node_modules"),
|
||||||
|
path.join(root, "node_modules"),
|
||||||
|
"dir",
|
||||||
|
);
|
||||||
|
fs.mkdirSync(path.join(root, "scripts"));
|
||||||
|
fs.copyFileSync(
|
||||||
|
path.join(repo, "scripts/apply-plugin-patches.cjs"),
|
||||||
|
path.join(root, "scripts/apply-plugin-patches.cjs"),
|
||||||
|
);
|
||||||
|
const plugin = path.join(root, "plugins/fixture");
|
||||||
|
fs.mkdirSync(path.join(plugin, "patches"), { recursive: true });
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(plugin, "manifest.json"),
|
||||||
|
JSON.stringify({ id: "fixture" }),
|
||||||
|
);
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(plugin, "package.json"),
|
||||||
|
JSON.stringify({ name: "fixture" }),
|
||||||
|
);
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(plugin, "patches/check.cjs"),
|
||||||
|
'require("node:fs").writeFileSync("patched", "ok");',
|
||||||
|
);
|
||||||
|
const run = (...args: string[]) =>
|
||||||
|
spawnSync(process.execPath, args, { cwd: plugin, encoding: "utf8" });
|
||||||
|
return {
|
||||||
|
root,
|
||||||
|
plugin,
|
||||||
|
run,
|
||||||
|
cli: path.join(root, "packages/plugin-sdk/cli/index.mjs"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("plugin CLI before the SDK is built", () => {
|
||||||
|
it("runs the postinstall plugin patches without SDK dist files", () => {
|
||||||
|
const { root, plugin, run } = fixture();
|
||||||
|
expect(fs.existsSync(path.join(root, "packages/plugin-sdk/dist"))).toBe(
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
const result = run(path.join(root, "scripts/apply-plugin-patches.cjs"));
|
||||||
|
expect(result.status, result.stderr).toBe(0);
|
||||||
|
expect(fs.readFileSync(path.join(plugin, "patched"), "utf8")).toBe("ok");
|
||||||
|
});
|
||||||
|
it("prints help without SDK dist files", () => {
|
||||||
|
const { cli, run } = fixture();
|
||||||
|
const result = run(cli, "--help");
|
||||||
|
expect(result.status, result.stderr).toBe(0);
|
||||||
|
expect(result.stdout).toContain("Usage: termix-plugin");
|
||||||
|
});
|
||||||
|
it("still requires a built SDK for validation with an actionable error", () => {
|
||||||
|
const { cli, run } = fixture();
|
||||||
|
const result = run(cli, "validate");
|
||||||
|
expect(result.status).toBe(1);
|
||||||
|
expect(result.stderr).toContain("Run: npm run build:sdk");
|
||||||
|
expect(result.stderr).not.toContain("ERR_MODULE_NOT_FOUND");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1167,18 +1167,11 @@ export function HostItem({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
)}
|
)}
|
||||||
{isCompact &&
|
{isCompact && (
|
||||||
!selectionMode &&
|
<span
|
||||||
!shouldUseClickTray &&
|
className="text-[11px] text-muted-foreground/70 truncate leading-none ml-auto max-w-[50%] shrink-0"
|
||||||
!actionsOnly && (
|
title={host.ip}
|
||||||
<span
|
>
|
||||||
className={`text-[11px] text-muted-foreground/70 truncate leading-none ml-auto shrink-0 ${hoverTrayOpen ? "hidden" : ""}`}
|
|
||||||
>
|
|
||||||
{host.ip}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{isCompact && selectionMode && (
|
|
||||||
<span className="text-[11px] text-muted-foreground/70 truncate leading-none ml-auto shrink-0">
|
|
||||||
{host.ip}
|
{host.ip}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -187,6 +187,30 @@ describe("HostItem density parity", () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
it.each(["hover", "click", "always", "actionsOnly"] as const)(
|
||||||
|
"keeps the compact host address visible with %s actions",
|
||||||
|
(trayTrigger) => {
|
||||||
|
const props = {
|
||||||
|
host: baseHost,
|
||||||
|
onOpenTab: noop,
|
||||||
|
onDelete: noop,
|
||||||
|
onDuplicate: noop,
|
||||||
|
density: "compact" as const,
|
||||||
|
trayTrigger,
|
||||||
|
};
|
||||||
|
const { rerender } = render(<HostItem {...props} />);
|
||||||
|
expect(screen.getByText(baseHost.ip).classList.contains("hidden")).toBe(
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
rerender(<HostItem {...props} isHovered />);
|
||||||
|
expect(screen.getByText(baseHost.ip).classList.contains("hidden")).toBe(
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
rerender(<HostItem {...props} isTrayOpen selectionMode />);
|
||||||
|
expect(screen.getAllByText(baseHost.ip)).toHaveLength(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
it("shows tags in both densities when showTags is true", () => {
|
it("shows tags in both densities when showTags is true", () => {
|
||||||
renderHostItem("comfortable");
|
renderHostItem("comfortable");
|
||||||
expect(screen.getByText("prod")).toBeTruthy();
|
expect(screen.getByText("prod")).toBeTruthy();
|
||||||
|
|||||||
Reference in new issue
Block a user