92380971da
Carry exact visible widths from Text and Box render results into their owners, and through Editor wrap/layout/render state. Stamp derived widths and render caches with a monotonic Hangul width-config epoch. Retain sidecar entries only by WeakMap owner identity, with immutable publication-time line/width proof, and conservatively remeasure escape-leading padded rows. Before: static_redraw 1633.11 us/op editor_edits 1249.71 us/op After hardened repair: static_redraw 1321.51 us/op (1.236x) editor_edits 1150.32 us/op (1.086x) Win: Exact byte/grid/scrollback hashes match across all five phases; every phase CV is below 20%, with no B/C regression. Memory: retained heap 251,393,541 -> 251,500,114 bytes (+0.042%). Op: carry exact widths through render ownership and editor layout Restores: repeated visible-width measurement in core TUI hot paths
87 lines
3.0 KiB
TypeScript
87 lines
3.0 KiB
TypeScript
/**
|
|
* Carried-width contract: components may publish the exact `visibleWidth` of
|
|
* each line of a render result, keyed by the result array itself. Consumers
|
|
* (Box) must only ever observe widths that (a) match a direct measurement and
|
|
* (b) were computed under the current width configuration — a Hangul
|
|
* Compatibility Jamo width change must invalidate every published width.
|
|
*/
|
|
import { afterEach, describe, expect, it } from "bun:test";
|
|
import {
|
|
getPublishedLineWidths,
|
|
getWidthConfigEpoch,
|
|
publishLineWidths,
|
|
resetHangulCompatibilityJamoWidthForTests,
|
|
setHangulCompatibilityJamoWidth,
|
|
visibleWidth,
|
|
} from "@oh-my-pi/pi-tui/utils";
|
|
|
|
afterEach(() => {
|
|
resetHangulCompatibilityJamoWidthForTests();
|
|
});
|
|
|
|
describe("line-width sidecar", () => {
|
|
it("returns published widths for the same array reference only", () => {
|
|
const lines = ["abc", "漢字"];
|
|
publishLineWidths(lines, [3, 4]);
|
|
expect(getPublishedLineWidths(lines)).toEqual([3, 4]);
|
|
// A value-equal but distinct array has no published widths.
|
|
expect(getPublishedLineWidths(["abc", "漢字"])).toBeUndefined();
|
|
});
|
|
|
|
it("rejects a publication whose widths do not match the line count", () => {
|
|
expect(() => publishLineWidths(["one", "two"], [3])).toThrow(RangeError);
|
|
});
|
|
|
|
it("keeps publication proof immutable from publishers and consumers", () => {
|
|
const lines = ["hi"];
|
|
const widths = [2];
|
|
publishLineWidths(lines, widths);
|
|
|
|
widths[0] = 99;
|
|
const published = getPublishedLineWidths(lines);
|
|
expect(published).toEqual([2]);
|
|
expect(Object.isFrozen(published)).toBe(true);
|
|
expect(Reflect.set(published ?? [], "0", 7)).toBe(false);
|
|
expect(getPublishedLineWidths(lines)).toEqual([2]);
|
|
});
|
|
|
|
it("drops published widths after same-array content or length mutation", () => {
|
|
const lines = ["hi"];
|
|
publishLineWidths(lines, [2]);
|
|
|
|
lines[0] = "hello";
|
|
expect(getPublishedLineWidths(lines)).toBeUndefined();
|
|
|
|
publishLineWidths(lines, [5]);
|
|
lines.push("!");
|
|
expect(getPublishedLineWidths(lines)).toBeUndefined();
|
|
});
|
|
|
|
it("drops published widths when the Hangul jamo width setting changes", () => {
|
|
const jamo = "\u3131\u314F";
|
|
const lines = [jamo];
|
|
setHangulCompatibilityJamoWidth(1);
|
|
publishLineWidths(lines, [visibleWidth(jamo)]);
|
|
expect(getPublishedLineWidths(lines)).toEqual([visibleWidth(jamo)]);
|
|
|
|
// Widths measured under the old setting must not survive the switch:
|
|
// the same string now measures differently.
|
|
setHangulCompatibilityJamoWidth(2);
|
|
expect(getPublishedLineWidths(lines)).toBeUndefined();
|
|
|
|
// Republishing under the new setting is visible again and exact.
|
|
publishLineWidths(lines, [visibleWidth(jamo)]);
|
|
expect(getPublishedLineWidths(lines)).toEqual([4]);
|
|
});
|
|
|
|
it("bumps the width-config epoch only on an effective setting change", () => {
|
|
const before = getWidthConfigEpoch();
|
|
setHangulCompatibilityJamoWidth(1);
|
|
const afterFirst = getWidthConfigEpoch();
|
|
expect(afterFirst).toBeGreaterThan(before);
|
|
// No-op set: same value, no invalidation.
|
|
setHangulCompatibilityJamoWidth(1);
|
|
expect(getWidthConfigEpoch()).toBe(afterFirst);
|
|
});
|
|
});
|