fix(tui): guard nullish args in truncateToWidth wrapper
renderContentPreview crashed on the Windows prebuilt with "Failed to convert napi value Null into rust type `u8`" (and the related `bool` variant) because the wrapper forwarded `null` to napi-rs 3's `Option<Ellipsis>` / `Option<bool>` parameters; the Windows binding rejects null where Linux/macOS tolerates it. `maxWidth` was also passed through unchecked, so a stray null/undefined upstream blew up the required `u32` everywhere. Pass concrete defaults that mirror the Rust `unwrap_or`s and clamp `maxWidth` to a sane non-negative integer. Fixes #848
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import type { Ellipsis, ExtractSegmentsResult, SliceResult } from "@oh-my-pi/pi-natives";
|
||||
import {
|
||||
Ellipsis,
|
||||
type ExtractSegmentsResult,
|
||||
extractSegments as nativeExtractSegments,
|
||||
sliceWithWidth as nativeSliceWithWidth,
|
||||
truncateToWidth as nativeTruncateToWidth,
|
||||
wrapTextWithAnsi as nativeWrapTextWithAnsi,
|
||||
type SliceResult,
|
||||
} from "@oh-my-pi/pi-natives";
|
||||
import { getDefaultTabWidth, getIndentation } from "@oh-my-pi/pi-utils";
|
||||
|
||||
@@ -21,7 +23,12 @@ export function truncateToWidth(
|
||||
ellipsisKind?: Ellipsis | null,
|
||||
pad?: boolean | null,
|
||||
): string {
|
||||
return nativeTruncateToWidth(text, maxWidth, ellipsisKind ?? null, pad ?? null, getDefaultTabWidth());
|
||||
// Guard nullish napi inputs: napi-rs 3 on the Windows prebuilt rejects
|
||||
// `null` for `Option<u8>` (Ellipsis) / `Option<bool>` (pad) (issue #848),
|
||||
// and `maxWidth` is a required `u32` that throws on `null`/`undefined`
|
||||
// everywhere. Pass concrete defaults that mirror the Rust `unwrap_or`s.
|
||||
const safeWidth = Number.isFinite(maxWidth) ? Math.max(0, Math.trunc(maxWidth)) : 0;
|
||||
return nativeTruncateToWidth(text, safeWidth, ellipsisKind ?? Ellipsis.Unicode, pad ?? false, getDefaultTabWidth());
|
||||
}
|
||||
|
||||
export function wrapTextWithAnsi(text: string, width: number): string[] {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import { Ellipsis, truncateToWidth } from "../src/utils";
|
||||
|
||||
// Regression test for https://github.com/can1357/oh-my-pi/issues/848
|
||||
//
|
||||
// On Windows, rendering a saved write tool record crashed with:
|
||||
// Error: Failed to convert napi value Null into rust type `u8`
|
||||
// at truncateToWidth (unknown)
|
||||
// at renderContentPreview (...)
|
||||
// and the related variant "Failed to convert napi value into rust type `bool`"
|
||||
// at the same call site.
|
||||
//
|
||||
// Root cause: the `truncateToWidth` JS wrapper coerced optional ellipsis/pad
|
||||
// arguments to literal `null` (`?? null`) before forwarding to the napi
|
||||
// binding. napi-rs 3 accepts that on Linux/macOS but rejects it on the
|
||||
// Windows prebuilt: the null hits the underlying `u8` (Ellipsis enum) /
|
||||
// `bool` (pad) conversions instead of the `Option<T>` short-circuit. The
|
||||
// wrapper must pass concrete defaults (matching the Rust `unwrap_or`s) so it
|
||||
// never depends on the platform's null-handling for `Option<T>`. Likewise,
|
||||
// `maxWidth` must not silently forward `null`/`undefined` into the required
|
||||
// `u32` parameter — the wrapper must clamp it to a sane non-negative integer.
|
||||
describe("issue #848: truncateToWidth wrapper rejects nullish napi inputs", () => {
|
||||
it("returns a string when ellipsisKind / pad are null", () => {
|
||||
const result = truncateToWidth("hello world", 80, null, null);
|
||||
expect(typeof result).toBe("string");
|
||||
expect(result).toBe("hello world");
|
||||
});
|
||||
|
||||
it("returns a string when ellipsisKind / pad are undefined", () => {
|
||||
const result = truncateToWidth("hello world", 80, undefined, undefined);
|
||||
expect(typeof result).toBe("string");
|
||||
expect(result).toBe("hello world");
|
||||
});
|
||||
|
||||
it("does not throw a napi conversion error when maxWidth is null", () => {
|
||||
// Matches the resumed-render code path on Windows where a width derived
|
||||
// from terminal state can briefly be null/undefined. The wrapper must
|
||||
// degrade to an empty truncation rather than blowing up the renderer.
|
||||
const result = truncateToWidth("hello world", null as unknown as number, Ellipsis.Omit, null);
|
||||
expect(typeof result).toBe("string");
|
||||
});
|
||||
|
||||
it("does not throw a napi conversion error when maxWidth is undefined", () => {
|
||||
const result = truncateToWidth("hello world", undefined as unknown as number, Ellipsis.Omit, null);
|
||||
expect(typeof result).toBe("string");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user