b8e8ca5a
test: cover redraw erasure and protected terminal state
a73x 2026-09-05 05:20
Commit message
src/engine/engine.zig
| Old | New | ||
|---|---|---|---|
| @@ -1805,3 +1805,89 @@ test "binary output bounded deterministic flood keeps every encoded viewport rep | |||
| 1805 | } | 1805 | } |
| 1806 | } | 1806 | } |
| 1807 | } | 1807 | } |
| 1808 | |||
| 1809 | test "erase and shorter redraw replace styled wide tails through live deltas" { | ||
| 1810 | const alloc = std.testing.allocator; | ||
| 1811 | var e = try Engine.init(alloc, .{ .cols = 36, .rows = 6 }); | ||
| 1812 | defer e.deinit(); | ||
| 1813 | // Plural, off-origin rows with style and wide spacers: clearing only the | ||
| 1814 | // text, keeping an old tail, or missing a changed empty row all show up. | ||
| 1815 | e.feed("\x1b[2;5H\x1b[1;31;44mLONG 漢字 WIDE TAIL\x1b[5;8HSECOND 漢字 TAIL\x1b[0m"); | ||
| 1816 | var tracker: delta.DeltaTracker = .{}; | ||
| 1817 | defer tracker.deinit(alloc); | ||
| 1818 | try tracker.rebuild(alloc, e, 6, 36); | ||
| 1819 | const g = try Grid.init(alloc, 36, 6); | ||
| 1820 | defer g.deinit(); | ||
| 1821 | var replica = @import("term").replica.Replica.init(alloc, g); | ||
| 1822 | const snapshot = try delta.buildSnapshot(alloc, e, .{ .seq = tracker.seq, .history_rows = 0, .cols = 36, .rows = 6, .epoch = 93 }); | ||
| 1823 | defer alloc.free(snapshot); | ||
| 1824 | _ = try replica.apply(.snapshot, snapshot); | ||
| 1825 | try std.testing.expect(g.row(1).cells[4].style.flags != 0); | ||
| 1826 | try std.testing.expectEqual(proto.Wide.wide, g.row(4).cells[14].wide); | ||
| 1827 | |||
| 1828 | const actions = [_][]const u8{ | ||
| 1829 | "\x1b[2;5Hshort\x1b[K\x1b[5;8Htiny\x1b[K", | ||
| 1830 | "\x1b[H\x1b[2J\x1b[3J", | ||
| 1831 | }; | ||
| 1832 | for (actions, 0..) |action, step| { | ||
| 1833 | const since = replica.last_seq; | ||
| 1834 | e.feed(action); | ||
| 1835 | try std.testing.expectEqual(delta.DeltaTracker.Update.advanced, try tracker.update(alloc, e)); | ||
| 1836 | const payload = try tracker.buildDeltaSince(alloc, e, since); | ||
| 1837 | defer alloc.free(payload); | ||
| 1838 | try std.testing.expectEqual(@import("term").replica.Replica.Applied.painted, try replica.apply(.delta, payload)); | ||
| 1839 | const dump = try g.dumpPlain(alloc); | ||
| 1840 | defer alloc.free(dump); | ||
| 1841 | if (step == 0) { | ||
| 1842 | try std.testing.expectEqualStrings("\n short\n\n\n tiny", dump); | ||
| 1843 | } else try std.testing.expectEqualStrings("", dump); | ||
| 1844 | for (g.lines, 0..) |row, y| { | ||
| 1845 | for (row.cells, 0..) |cell, x| { | ||
| 1846 | const retained_text = step == 0 and ((y == 1 and x >= 4 and x < 9) or (y == 4 and x >= 7 and x < 11)); | ||
| 1847 | try std.testing.expect(cell.style.isDefault()); | ||
| 1848 | try std.testing.expectEqual(proto.Wide.narrow, cell.wide); | ||
| 1849 | if (!retained_text) try std.testing.expect(cell.text_len == 0 or std.mem.eql(u8, row.textOf(cell), " ")); | ||
| 1850 | } | ||
| 1851 | } | ||
| 1852 | } | ||
| 1853 | } | ||
| 1854 | |||
| 1855 | test "erase preserves ISO protected text until terminal reset and deltas mirror that state" { | ||
| 1856 | const alloc = std.testing.allocator; | ||
| 1857 | var e = try Engine.init(alloc, .{ .cols = 36, .rows = 6 }); | ||
| 1858 | defer e.deinit(); | ||
| 1859 | var tracker: delta.DeltaTracker = .{}; | ||
| 1860 | defer tracker.deinit(alloc); | ||
| 1861 | try tracker.rebuild(alloc, e, 6, 36); | ||
| 1862 | const g = try Grid.init(alloc, 36, 6); | ||
| 1863 | defer g.deinit(); | ||
| 1864 | var replica = @import("term").replica.Replica.init(alloc, g); | ||
| 1865 | const snapshot = try delta.buildSnapshot(alloc, e, .{ .seq = tracker.seq, .history_rows = 0, .cols = 36, .rows = 6, .epoch = 93 }); | ||
| 1866 | defer alloc.free(snapshot); | ||
| 1867 | _ = try replica.apply(.snapshot, snapshot); | ||
| 1868 | |||
| 1869 | // Binary output can contain SPA (ESC V). EPA (ESC W) stops protecting | ||
| 1870 | // new characters but does not remove protection from the existing cells. | ||
| 1871 | // Their survival after ED/EL is authoritative terminal state, not stale | ||
| 1872 | // replica or framebuffer content. RIS resets that state and clears them. | ||
| 1873 | const actions = [_]struct { bytes: []const u8, expected: []const u8 }{ | ||
| 1874 | .{ .bytes = "\x1b[2;5H\x1bVPROTECTED-FIRST\x1bW\x1b[5;8H\x1bVPROTECTED-SECOND\x1bW", .expected = "\n PROTECTED-FIRST\n\n\n PROTECTED-SECOND" }, | ||
| 1875 | .{ .bytes = "\x1b[H\x1b[2J\x1b[3J", .expected = "\n PROTECTED-FIRST\n\n\n PROTECTED-SECOND" }, | ||
| 1876 | .{ .bytes = "\x1b[2;5Hnew\x1b[K\x1b[5;8Hx\x1b[K", .expected = "\n newTECTED-FIRST\n\n\n xROTECTED-SECOND" }, | ||
| 1877 | .{ .bytes = "\x1bc", .expected = "" }, | ||
| 1878 | }; | ||
| 1879 | for (actions) |action| { | ||
| 1880 | const since = replica.last_seq; | ||
| 1881 | e.feed(action.bytes); | ||
| 1882 | try std.testing.expectEqual(delta.DeltaTracker.Update.advanced, try tracker.update(alloc, e)); | ||
| 1883 | const payload = try tracker.buildDeltaSince(alloc, e, since); | ||
| 1884 | defer alloc.free(payload); | ||
| 1885 | try std.testing.expectEqual(@import("term").replica.Replica.Applied.painted, try replica.apply(.delta, payload)); | ||
| 1886 | const dump = try g.dumpPlain(alloc); | ||
| 1887 | defer alloc.free(dump); | ||
| 1888 | try std.testing.expectEqualStrings(action.expected, dump); | ||
| 1889 | } | ||
| 1890 | for (g.lines) |row| { | ||
| 1891 | for (row.cells) |cell| try std.testing.expect(cell.isBlank()); | ||
| 1892 | } | ||
| 1893 | } | ||