1package vt
2
3import (
4 "testing"
5
6 renderer "github.com/bnema/vev-vt/core"
7)
8
9func TestSnapshotCodecsPreserveEveryTerminalCellAcrossHistoryAndRecoveryTranscript(t *testing.T) {
10 indexed := renderer.Style{
11 Bold: true,
12 Italic: true,
13 Inverse: true,
14 Attrs: renderer.AttrDim | renderer.AttrUnderline | renderer.AttrBlink | renderer.AttrStrikethrough,
15 Foreground: 196,
16 Background: 17,
17 HasUnderlineColor: true,
18 UnderlineColor: 203,
19 }
20 rgb := renderer.Style{
21 Bold: true,
22 Italic: true,
23 Inverse: true,
24 Attrs: renderer.AttrDim | renderer.AttrUnderline | renderer.AttrBlink | renderer.AttrStrikethrough,
25 HasForegroundRGB: true,
26 ForegroundRGB: renderer.RGB{R: 1, G: 2, B: 3},
27 HasBackgroundRGB: true,
28 BackgroundRGB: renderer.RGB{R: 4, G: 5, B: 6},
29 HasUnderlineColorRGB: true,
30 UnderlineColorRGB: renderer.RGB{R: 7, G: 8, B: 9},
31 }
32 underlines := []renderer.UnderlineStyle{
33 renderer.UnderlineNone,
34 renderer.UnderlineSingle,
35 renderer.UnderlineDouble,
36 renderer.UnderlineCurly,
37 renderer.UnderlineDotted,
38 renderer.UnderlineDashed,
39 }
40 makeRow := func(r rune, style renderer.Style) []renderer.Cell {
41 style.UnderlineStyle = underlines[int(r)%len(underlines)]
42 return []renderer.Cell{
43 {Rune: r, Style: style},
44 {Rune: ' ', Style: renderer.DefaultStyle()},
45 {Rune: '界', Style: style},
46 {Continuation: true, Style: style},
47 }
48 }
49
50 tests := []struct {
51 name string
52 run func(*testing.T)
53 }{
54 {
55 name: "history view retains sealed chunks and mutable tail",
56 run: func(t *testing.T) {
57 history := NewHistory(HistoryConfig{MaxRows: 8, ChunkRows: 2})
58 // A through F exercise every underline style; G remains in the
59 // mutable tail so both history ownership paths are encoded.
60 want := [][]renderer.Cell{
61 makeRow('A', indexed), makeRow('B', rgb), makeRow('C', indexed),
62 makeRow('D', rgb), makeRow('E', indexed), makeRow('F', rgb), makeRow('G', indexed),
63 }
64 for _, row := range want {
65 requireHistoryAppend(t, history, row)
66 }
67 if got, wantChunks := history.View().ChunkCount(), 4; got != wantChunks {
68 t.Fatalf("chunk count = %d, want three sealed chunks plus copied tail %d", got, wantChunks)
69 }
70 encoded, err := MarshalHistory(history.View())
71 if err != nil {
72 t.Fatalf("MarshalHistory: %v", err)
73 }
74 got, err := UnmarshalHistory(encoded)
75 if err != nil {
76 t.Fatalf("UnmarshalHistory: %v", err)
77 }
78 assertHistoryRowsEqual(t, got, want)
79 },
80 },
81 {
82 name: "recovery transcript retains blanks wide heads continuations and styles",
83 run: func(t *testing.T) {
84 screen := NewScreen(4, 2)
85 want := [][]renderer.Cell{makeRow('D', rgb), makeRow('E', indexed)}
86 for y, row := range want {
87 copy(screen.Frame.Row(y), row)
88 screen.buffer.boundaries[y] = LineBound{End: len(row)}
89 }
90
91 encoded, err := screen.RecoveryTranscriptSnapshot().Marshal()
92 if err != nil {
93 t.Fatalf("marshal recovery transcript: %v", err)
94 }
95 got, err := UnmarshalHistory(encoded)
96 if err != nil {
97 t.Fatalf("unmarshal recovery transcript: %v", err)
98 }
99 assertHistoryRowsEqual(t, got, want)
100 },
101 },
102 }
103 for _, tt := range tests {
104 t.Run(tt.name, tt.run)
105 }
106}