|
1 | 1 | import { assert, describe, test } from "@rezi-ui/testkit"; |
2 | 2 | import type { RuntimeBackend } from "../../backend.js"; |
3 | 3 | import type { ZrevEvent } from "../../events.js"; |
4 | | -import { defineWidget, ui } from "../../index.js"; |
| 4 | +import { type VNode, defineWidget, ui } from "../../index.js"; |
5 | 5 | import { DEFAULT_TERMINAL_CAPS } from "../../terminalCaps.js"; |
6 | 6 | import { defaultTheme } from "../../theme/defaultTheme.js"; |
7 | 7 | import { TOAST_HEIGHT, getToastActionFocusId } from "../../widgets/toast.js"; |
@@ -640,6 +640,76 @@ describe("WidgetRenderer integration battery", () => { |
640 | 640 | ]); |
641 | 641 | }); |
642 | 642 |
|
| 643 | + test("routing rebuild GC clears virtualList/table local state for removed ids", () => { |
| 644 | + const backend = createNoopBackend(); |
| 645 | + const renderer = new WidgetRenderer<void>({ |
| 646 | + backend, |
| 647 | + requestRender: () => {}, |
| 648 | + }); |
| 649 | + |
| 650 | + const vlistSelected: string[] = []; |
| 651 | + const tablePressed: string[] = []; |
| 652 | + |
| 653 | + const Virtual = () => |
| 654 | + ui.virtualList({ |
| 655 | + id: "v", |
| 656 | + items: ["a", "b"], |
| 657 | + itemHeight: 1, |
| 658 | + renderItem: (item) => ui.text(item), |
| 659 | + onSelect: (item, index) => vlistSelected.push(`${item}:${String(index)}`), |
| 660 | + }); |
| 661 | + |
| 662 | + const Table = () => |
| 663 | + ui.table({ |
| 664 | + id: "t", |
| 665 | + columns: [{ key: "id", header: "ID", flex: 1 }], |
| 666 | + data: [{ id: "r0" }, { id: "r1" }], |
| 667 | + getRowKey: (row) => row.id, |
| 668 | + onRowPress: (row, index) => tablePressed.push(`${row.id}:${String(index)}`), |
| 669 | + }); |
| 670 | + |
| 671 | + const submit = (vnode: VNode) => { |
| 672 | + const res = renderer.submitFrame( |
| 673 | + () => vnode, |
| 674 | + undefined, |
| 675 | + { cols: 40, rows: 10 }, |
| 676 | + defaultTheme, |
| 677 | + noRenderHooks(), |
| 678 | + ); |
| 679 | + assert.ok(res.ok); |
| 680 | + }; |
| 681 | + |
| 682 | + submit(ui.column({}, [Virtual(), Table()])); |
| 683 | + |
| 684 | + // VirtualList: select item index=1. |
| 685 | + renderer.routeEngineEvent(keyEvent(3 /* TAB */)); |
| 686 | + renderer.routeEngineEvent(keyEvent(21 /* DOWN */)); |
| 687 | + renderer.routeEngineEvent(keyEvent(2 /* ENTER */)); |
| 688 | + |
| 689 | + // Table: focus and press row index=1. |
| 690 | + renderer.routeEngineEvent(keyEvent(3 /* TAB */)); |
| 691 | + renderer.routeEngineEvent(keyEvent(21 /* DOWN */)); |
| 692 | + renderer.routeEngineEvent(keyEvent(2 /* ENTER */)); |
| 693 | + |
| 694 | + assert.deepEqual(vlistSelected, ["b:1"]); |
| 695 | + assert.deepEqual(tablePressed, ["r1:1"]); |
| 696 | + |
| 697 | + // Unmount both widgets and force a routing rebuild GC pass. |
| 698 | + submit(ui.text("gone")); |
| 699 | + |
| 700 | + // Remount with same ids; state should start from defaults. |
| 701 | + submit(ui.column({}, [Virtual(), Table()])); |
| 702 | + |
| 703 | + renderer.routeEngineEvent(keyEvent(3 /* TAB */)); |
| 704 | + renderer.routeEngineEvent(keyEvent(2 /* ENTER */)); |
| 705 | + |
| 706 | + renderer.routeEngineEvent(keyEvent(3 /* TAB */)); |
| 707 | + renderer.routeEngineEvent(keyEvent(2 /* ENTER */)); |
| 708 | + |
| 709 | + assert.deepEqual(vlistSelected, ["b:1", "a:0"]); |
| 710 | + assert.deepEqual(tablePressed, ["r1:1", "r0:0"]); |
| 711 | + }); |
| 712 | + |
643 | 713 | test("table routing moves focused row and activates on Enter", () => { |
644 | 714 | const backend = createNoopBackend(); |
645 | 715 | const renderer = new WidgetRenderer<void>({ |
|
0 commit comments