diff --git a/package-lock.json b/package-lock.json
index 3a4e2c1..a358015 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -22,7 +22,7 @@
"devDependencies": {
"@commitlint/cli": "^21.2.2",
"@commitlint/config-conventional": "^21.2.0",
- "@termix-ssh/plugin-sdk": "^1.0.25",
+ "@termix-ssh/plugin-sdk": "^1.1.0",
"@testing-library/dom": "^10.4.2",
"@testing-library/jest-dom": "^7.0.1",
"@testing-library/react": "^16.3.3",
@@ -46,7 +46,7 @@
"node": ">=22.12.0"
},
"peerDependencies": {
- "@termix-ssh/plugin-sdk": "^1.0.25",
+ "@termix-ssh/plugin-sdk": "^1.1.0",
"drizzle-orm": "^0.45.2",
"express": "^5.2.1",
"multer": "^2.3.0",
@@ -2291,9 +2291,9 @@
}
},
"node_modules/@termix-ssh/plugin-sdk": {
- "version": "1.0.25",
- "resolved": "https://registry.npmjs.org/@termix-ssh/plugin-sdk/-/plugin-sdk-1.0.25.tgz",
- "integrity": "sha512-rJlbB/Wsas+0zRQabq4JXErU1dP8S0iYUi0J1LjHeecAirZr0Ffv4QsaagAuHOPXj3XSeYTJQPX0cAkwTR+NkA==",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@termix-ssh/plugin-sdk/-/plugin-sdk-1.1.1.tgz",
+ "integrity": "sha512-/bSnsIxvQBmu4qAtPLZH3gjaVSfGggTRRCI227RC/Ay/9UvwIDWMJhbCJeVCGWyG0SIb43fvI6USFi8NxCOPcA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
diff --git a/src/frontend/local-terminal/LocalTerminal.tsx b/src/frontend/local-terminal/LocalTerminal.tsx
index 4a32e0b..fedb192 100644
--- a/src/frontend/local-terminal/LocalTerminal.tsx
+++ b/src/frontend/local-terminal/LocalTerminal.tsx
@@ -341,8 +341,8 @@ export function LocalTerminal({
)}
-
-
+
+
({
+ ref: { current: null as HTMLElement | null },
+}));
+vi.mock("react-xtermjs", () => ({
+ useXTerm: () => ({ instance: null, ref: state.ref }),
+}));
+vi.mock("@termix-ssh/plugin-sdk/frontend", () => ({
+ useTranslation: () => ({ t: (key: string) => key }),
+ usePluginApi: () => null,
+}));
+vi.mock("@termix-ssh/plugin-sdk/ui", () => ({
+ useAppTheme: () => ({ theme: "dark" }),
+ ComponentSlot: () => null,
+ Select2: () => null,
+}));
+vi.mock("../../../src/frontend/terminal-api", () => ({
+ getDefaultAppearance: () => new Promise(() => {}),
+}));
+vi.mock("../../../src/frontend/terminal-tools/TerminalContextMenu", () => ({
+ TerminalContextMenu: () => null,
+}));
+
+import { LocalTerminal } from "../../../src/frontend/local-terminal/LocalTerminal";
+
+afterEach(() => {
+ vi.restoreAllMocks();
+ cleanup();
+});
+
+describe("local terminal fitting inside its padded pane", () => {
+ it.each([300, 301, 400, 600, 601, 800])(
+ "keeps every fitted row inside a %dpx pane",
+ (height) => {
+ const { container } = render(
+ ,
+ );
+ const style = document.createElement("style");
+ style.textContent = "* { box-sizing: border-box } .p-2 { padding: 8px }";
+ container.append(style);
+ const mount = state.ref.current!;
+ const wrapper = mount.parentElement!;
+ const element = document.createElement("div");
+ element.style.padding = "0px";
+ mount.append(element);
+ const computed = window.getComputedStyle.bind(window);
+ const inset = (node: Element) => {
+ const css = computed(node);
+ return (
+ (parseFloat(css.paddingTop) || 0) +
+ (parseFloat(css.paddingBottom) || 0)
+ );
+ };
+ const mountHeight = height - inset(wrapper);
+ // jsdom has no layout engine. Supply the mount's border-box dimensions;
+ // keep production markup, padding and FitAddon measurement logic real.
+ vi.spyOn(window, "getComputedStyle").mockImplementation((node) => {
+ const css = computed(node);
+ if (node === mount) {
+ return new Proxy(css, {
+ get(target, property) {
+ if (property === "getPropertyValue")
+ return (name: string) =>
+ name === "height"
+ ? `${mountHeight}px`
+ : name === "width"
+ ? "900px"
+ : target.getPropertyValue(name);
+ return Reflect.get(target, property);
+ },
+ });
+ }
+ return css;
+ });
+ const terminal = {
+ element,
+ options: { scrollback: 1000 },
+ _core: {
+ _renderService: {
+ dimensions: { css: { cell: { width: 8, height: 16 } } },
+ },
+ },
+ };
+ const fit = new FitAddon();
+ fit.activate(terminal as unknown as Terminal);
+ const dimensions = fit.proposeDimensions()!;
+ expect(dimensions.rows * 16 + inset(mount)).toBeLessThanOrEqual(
+ mountHeight,
+ );
+ expect(inset(wrapper) + inset(mount)).toBe(16);
+ expect(dimensions.rows).toBe(Math.floor((height - 16) / 16));
+ },
+ );
+});