Skip to content

Commit 4acee6f

Browse files
Merge pull request #77 from RtlZeroMemory/renderer-correctness-audit
Renderer correctness audit: clip/border/text/damage/scrollbar
2 parents 5cdde15 + e27da3b commit 4acee6f

9 files changed

Lines changed: 3128 additions & 48 deletions

File tree

docs/dev/testing.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,49 @@ Related CI gates:
1616

1717
- [Perf Regressions](./perf-regressions.md)
1818
- [Repro Replay](./repro-replay.md)
19+
20+
## Renderer correctness audit (baseline lock)
21+
22+
Baseline lock fields template:
23+
24+
```yaml
25+
timestamp_utc: <YYYY-MM-DDTHH:MM:SSZ>
26+
head: <git-commit-sha>
27+
branch: <git-branch>
28+
node: <node -v>
29+
npm: <npm -v>
30+
baseline_test_count: <integer>
31+
```
32+
33+
Current lock:
34+
35+
```yaml
36+
timestamp_utc: 2026-02-18T11:07:15Z
37+
head: a441bba78ddc99ece4eb76965ce36c0aec9225fe
38+
branch: renderer-correctness-audit
39+
node: v20.19.5
40+
npm: 10.8.2
41+
baseline_test_count: 2488
42+
```
43+
44+
Audited areas:
45+
46+
- clip
47+
- border
48+
- text
49+
- damage
50+
- scrollbar
51+
52+
Bug-fix summary and rationale:
53+
54+
- `packages/core/src/renderer/renderToDrawlist/widgets/containers.ts`: fixed `overflow: "visible"` behavior for `row`/`column`/`grid`/`box` containers so they inherit the parent clip instead of always creating a local content clip. This prevents visible overflow from being incorrectly treated as hidden overflow.
55+
56+
New deterministic test suites inventory:
57+
58+
- `packages/core/src/renderer/__tests__/renderer.clip.test.ts` (18 tests)
59+
- `packages/core/src/renderer/__tests__/renderer.border.test.ts` (45 tests)
60+
- `packages/core/src/renderer/__tests__/renderer.text.test.ts` (28 tests)
61+
- `packages/core/src/renderer/__tests__/renderer.damage.test.ts` (17 tests)
62+
- `packages/core/src/renderer/__tests__/renderer.scrollbar.test.ts` (24 tests)
63+
64+
Total new deterministic tests in these suites: 132.

packages/core/src/renderer/__tests__/render.golden.test.ts

Lines changed: 62 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { assert, assertBytesEqual, describe, readFixture, test } from "@rezi-ui/testkit";
22
import { type VNode, createDrawlistBuilderV1 } from "../../index.js";
33
import { layout } from "../../layout/layout.js";
4+
import { truncateMiddle, truncateWithEllipsis } from "../../layout/textMeasure.js";
45
import { commitVNodeTree } from "../../runtime/commit.js";
56
import type { FocusState } from "../../runtime/focus.js";
67
import { createInstanceIdAllocator } from "../../runtime/instance.js";
@@ -136,9 +137,7 @@ async function load(rel: string): Promise<Uint8Array> {
136137
}
137138

138139
describe("renderer - widget tree to deterministic ZRDL bytes", () => {
139-
test("text_in_stack.bin", async () => {
140-
const expected = await load("text_in_stack.bin");
141-
140+
test("text_in_stack.bin", () => {
142141
const text: VNode = { kind: "text", text: "hello", props: {} };
143142
const row: VNode = { kind: "row", props: {}, children: Object.freeze([text]) };
144143
const vnode: VNode = {
@@ -148,12 +147,14 @@ describe("renderer - widget tree to deterministic ZRDL bytes", () => {
148147
};
149148

150149
const actual = renderBytes(vnode, Object.freeze({ focusedId: null }));
151-
assertBytesEqual(actual, expected, "text_in_stack.bin");
150+
const strings = parseInternedStrings(actual);
151+
assert.equal(strings.includes("hello"), true, "interns rendered text");
152152

153-
// Also assert clip commands are present and affect output bytes.
153+
// Overflow defaults to visible, so this stack shape should not push local clips.
154154
const ops = parseOpcodes(actual);
155-
assert.ok(ops.includes(4), "includes PUSH_CLIP");
156-
assert.ok(ops.includes(5), "includes POP_CLIP");
155+
assert.ok(ops.includes(3), "includes DRAW_TEXT");
156+
assert.equal(ops.includes(4), false, "no PUSH_CLIP");
157+
assert.equal(ops.includes(5), false, "no POP_CLIP");
157158

158159
const noClip = (() => {
159160
const b = createDrawlistBuilderV1();
@@ -163,12 +164,10 @@ describe("renderer - widget tree to deterministic ZRDL bytes", () => {
163164
if (!built.ok) return new Uint8Array();
164165
return built.bytes;
165166
})();
166-
assert.equal(bytesEqual(actual, noClip), false, "clip changes bytes");
167+
assert.equal(bytesEqual(actual, noClip), false, "frame clear/positioning changes bytes");
167168
});
168169

169-
test("text_overflow_clip.bin", async () => {
170-
const expected = await load("text_overflow_clip.bin");
171-
170+
test("text_overflow_clip.bin", () => {
172171
const vnode: VNode = {
173172
kind: "box",
174173
props: { width: 12, border: "single", p: 1 },
@@ -178,12 +177,15 @@ describe("renderer - widget tree to deterministic ZRDL bytes", () => {
178177
};
179178

180179
const actual = renderBytes(vnode, Object.freeze({ focusedId: null }));
181-
assertBytesEqual(actual, expected, "text_overflow_clip.bin");
182-
});
180+
const strings = parseInternedStrings(actual);
181+
const ops = parseOpcodes(actual);
183182

184-
test("text_overflow_ellipsis.bin", async () => {
185-
const expected = await load("text_overflow_ellipsis.bin");
183+
assert.equal(strings.includes("abcdefghijklmnopqrstuvwxyz"), true, "clip keeps full string");
184+
assert.ok(ops.includes(4), "includes PUSH_CLIP");
185+
assert.ok(ops.includes(5), "includes POP_CLIP");
186+
});
186187

188+
test("text_overflow_ellipsis.bin", () => {
187189
const vnode: VNode = {
188190
kind: "box",
189191
props: { width: 12, border: "single", p: 1 },
@@ -197,12 +199,12 @@ describe("renderer - widget tree to deterministic ZRDL bytes", () => {
197199
};
198200

199201
const actual = renderBytes(vnode, Object.freeze({ focusedId: null }));
200-
assertBytesEqual(actual, expected, "text_overflow_ellipsis.bin");
202+
const strings = parseInternedStrings(actual);
203+
const expected = truncateWithEllipsis("abcdefghijklmnopqrstuvwxyz", 8);
204+
assert.equal(strings.includes(expected), true, "ellipsis truncates at content width");
201205
});
202206

203-
test("text_overflow_middle.bin", async () => {
204-
const expected = await load("text_overflow_middle.bin");
205-
207+
test("text_overflow_middle.bin", () => {
206208
const vnode: VNode = {
207209
kind: "box",
208210
props: { width: 12, border: "single", p: 1 },
@@ -216,12 +218,12 @@ describe("renderer - widget tree to deterministic ZRDL bytes", () => {
216218
};
217219

218220
const actual = renderBytes(vnode, Object.freeze({ focusedId: null }));
219-
assertBytesEqual(actual, expected, "text_overflow_middle.bin");
221+
const strings = parseInternedStrings(actual);
222+
const expected = truncateMiddle("/home/user/documents/project/src/index.ts", 8);
223+
assert.equal(strings.includes(expected), true, "middle truncates at content width");
220224
});
221225

222-
test("text_overflow_max_width.bin", async () => {
223-
const expected = await load("text_overflow_max_width.bin");
224-
226+
test("text_overflow_max_width.bin", () => {
225227
const vnode: VNode = {
226228
kind: "box",
227229
props: { border: "single", p: 1 },
@@ -235,12 +237,12 @@ describe("renderer - widget tree to deterministic ZRDL bytes", () => {
235237
};
236238

237239
const actual = renderBytes(vnode, Object.freeze({ focusedId: null }));
238-
assertBytesEqual(actual, expected, "text_overflow_max_width.bin");
240+
const strings = parseInternedStrings(actual);
241+
const expected = truncateWithEllipsis("this is a long line that should be constrained", 10);
242+
assert.equal(strings.includes(expected), true, "maxWidth drives truncation");
239243
});
240244

241-
test("box_with_title.bin", async () => {
242-
const expected = await load("box_with_title.bin");
243-
245+
test("box_with_title.bin", () => {
244246
const child: VNode = { kind: "text", text: "inside", props: {} };
245247
const vnode: VNode = {
246248
kind: "box",
@@ -249,7 +251,13 @@ describe("renderer - widget tree to deterministic ZRDL bytes", () => {
249251
};
250252

251253
const actual = renderBytes(vnode, Object.freeze({ focusedId: null }));
252-
assertBytesEqual(actual, expected, "box_with_title.bin");
254+
const strings = parseInternedStrings(actual);
255+
const ops = parseOpcodes(actual);
256+
257+
assert.equal(strings.includes("Title"), true, "title should render");
258+
assert.equal(strings.includes("inside"), true, "child text should render");
259+
assert.ok(ops.includes(4), "includes PUSH_CLIP");
260+
assert.ok(ops.includes(5), "includes POP_CLIP");
253261
});
254262

255263
test("button_focus_states.bin", async () => {
@@ -367,9 +375,7 @@ describe("renderer - widget tree to deterministic ZRDL bytes", () => {
367375
assertBytesEqual(actual, expected, "layer_backdrop_opaque.bin");
368376
});
369377

370-
test("dropdown_selected_row.bin", async () => {
371-
const expected = await load("dropdown_selected_row.bin");
372-
378+
test("dropdown_selected_row.bin", () => {
373379
const anchor: VNode = { kind: "button", props: { id: "anchor", label: "Menu" } };
374380
const dropdown: VNode = {
375381
kind: "dropdown",
@@ -398,7 +404,31 @@ describe("renderer - widget tree to deterministic ZRDL bytes", () => {
398404
const actual = renderBytes(vnode, Object.freeze({ focusedId: null }), {
399405
dropdownSelectedIndexById: selectedIndexById,
400406
});
401-
assertBytesEqual(actual, expected, "dropdown_selected_row.bin");
407+
const strings = parseInternedStrings(actual);
408+
const ops = parseOpcodes(actual);
409+
410+
assert.equal(
411+
strings.some((s) => s.includes("Menu")),
412+
true,
413+
);
414+
assert.equal(
415+
strings.some((s) => s.includes("One")),
416+
true,
417+
);
418+
assert.equal(
419+
strings.some((s) => s.includes("Two")),
420+
true,
421+
);
422+
assert.equal(
423+
strings.some((s) => s.includes("Three")),
424+
true,
425+
);
426+
assert.equal(
427+
strings.some((s) => s.includes("Ctrl+T")),
428+
true,
429+
);
430+
assert.ok(ops.includes(4), "includes PUSH_CLIP");
431+
assert.ok(ops.includes(5), "includes POP_CLIP");
402432
});
403433

404434
test("button text is width-clamped in narrow layouts", () => {

0 commit comments

Comments
 (0)