package e2e_test import ( "fmt" "strings" "testing" "pad/internal/editor" ) // lineFile builds a file of nLines lines, each exactly 512 bytes: // "LINE%06d:" (11) + 500 'x' (500) + "\n" (1). Line i therefore starts at // byte i*512, so with the default 64 KiB chunk size, lines 128, 256, 384, ... // start exactly on chunk boundaries. The file ends with '\n', so the line // index has nLines+1 entries (a trailing empty line). func lineFile(nLines int) string { var sb strings.Builder for i := 0; i < nLines; i++ { fmt.Fprintf(&sb, "LINE%06d:", i) sb.WriteString(strings.Repeat("x", 500)) sb.WriteByte('\n') } return sb.String() } const ( chunkBytes = 64 * 1024 lineBytes = 512 ) // TestRealFile_ChunkBoundary_InsertAtBoundary inserts at a line that starts // exactly on a chunk boundary and verifies the buffer and disk agree. func TestRealFile_ChunkBoundary_InsertAtBoundary(t *testing.T) { content := lineFile(500) // 256 KiB, 4 chunks h, path := realFileHarness(t, "boundary.txt", content) defer h.Cleanup() pos := 128 * lineBytes // start of LINE000128 == byte 65536 == chunk edge if pos != chunkBytes { t.Fatalf("test premise broken: %d != %d", pos, chunkBytes) } original, _ := h.FullContent() if err := h.WithState(func(st *editor.State) { st.Editor.CursorPosition = pos editor.HandleInsert("INSERTED-LINE\n") }); err != nil { t.Fatalf("insert: %v", err) } want := original[:pos] + "INSERTED-LINE\n" + original[pos:] got, _ := h.FullContent() if got != want { t.Fatalf("buffer mismatch after boundary insert (len %d vs %d)", len(got), len(want)) } // 500 lines + trailing empty line + 1 new line = 502 index entries. checkLineIndex(t, h, want, 502) if err := h.Flush(); err != nil { t.Fatalf("flush: %v", err) } if disk := readDisk(t, path); disk != want { t.Fatalf("disk mismatch after boundary insert (len %d vs %d)", len(disk), len(want)) } } // TestRealFile_ChunkBoundary_DeleteAcrossBoundary deletes a range that spans // a chunk boundary (via selection) and verifies consistency. func TestRealFile_ChunkBoundary_DeleteAcrossBoundary(t *testing.T) { content := lineFile(500) h, path := realFileHarness(t, "boundary.txt", content) defer h.Cleanup() pos := 256 * lineBytes // second chunk edge (byte 131072) lo, hi := pos-10, pos+10 original, _ := h.FullContent() if err := h.WithState(func(st *editor.State) { editor.SetSelection(lo, hi) editor.HandleBackspace() }); err != nil { t.Fatalf("delete: %v", err) } want := original[:lo] + original[hi:] got, _ := h.FullContent() if got != want { t.Fatalf("buffer mismatch after cross-boundary delete (len %d vs %d)", len(got), len(want)) } // The range includes the '\n' ending line 255 (byte pos-1), so lines 255 // and 256 merge into one: 501 entries -> 500. checkLineIndex(t, h, want, 500) if err := h.Flush(); err != nil { t.Fatalf("flush: %v", err) } if disk := readDisk(t, path); disk != want { t.Fatalf("disk mismatch after cross-boundary delete") } } // TestRealFile_ChunkBoundary_SustainedTypingAtBoundary inserts a large string // at a chunk edge, forcing chunk splits (per-edit copy bound) and line-index // shifts, then verifies the result byte-for-byte. func TestRealFile_ChunkBoundary_SustainedTypingAtBoundary(t *testing.T) { content := lineFile(500) h, path := realFileHarness(t, "boundary.txt", content) defer h.Cleanup() pos := 384 * lineBytes // third chunk edge original, _ := h.FullContent() // 3000 chars with a single trailing newline: inserts one new line at the // top of LINE000384 and pushes everything after it down. pasted := strings.Repeat("p", 2990) + "PASTE-END\n" // len 3000 if len(pasted) != 3000 { t.Fatalf("pasted len = %d", len(pasted)) } if err := h.WithState(func(st *editor.State) { st.Editor.CursorPosition = pos editor.HandleInsert(pasted) }); err != nil { t.Fatalf("paste: %v", err) } want := original[:pos] + pasted + original[pos:] got, _ := h.FullContent() if got != want { t.Fatalf("buffer mismatch after sustained typing at boundary (len %d vs %d)", len(got), len(want)) } // 501 original entries (incl. trailing empty line) + 1 new line. checkLineIndex(t, h, want, 502) if err := h.Flush(); err != nil { t.Fatalf("flush: %v", err) } if disk := readDisk(t, path); disk != want { t.Fatalf("disk mismatch after sustained typing at boundary") } } // TestRealFile_ChunkBoundary_UTF8SplitAcrossBoundary verifies a file whose // multi-byte rune straddles a chunk boundary loads intact and edits cleanly // around that rune. func TestRealFile_ChunkBoundary_UTF8SplitAcrossBoundary(t *testing.T) { // "é" is 2 bytes at offset 65535..65537, split by the 65536 chunk edge. content := strings.Repeat("A", chunkBytes-1) + "é" + strings.Repeat("B", 100) h, path := realFileHarness(t, "utf8.txt", content) defer h.Cleanup() got, _ := h.FullContent() if got != content { t.Fatalf("loaded buffer != original (rune split across chunks corrupted? len %d vs %d)", len(got), len(content)) } // Delete the é (2 bytes) from after it. if err := h.WithState(func(st *editor.State) { st.Editor.CursorPosition = chunkBytes - 1 + 2 editor.HandleBackspace() }); err != nil { t.Fatalf("backspace: %v", err) } want := strings.Repeat("A", chunkBytes-1) + strings.Repeat("B", 100) got, _ = h.FullContent() if got != want { t.Fatalf("buffer = %d bytes, want %d (é not removed cleanly)", len(got), len(want)) } // Insert a fresh é before the B run: exercises insert at the same spot. if err := h.WithState(func(st *editor.State) { st.Editor.CursorPosition = chunkBytes - 1 editor.HandleInsert("é") }); err != nil { t.Fatalf("insert: %v", err) } got, _ = h.FullContent() if got != content { t.Fatalf("buffer != original after re-insert of é") } if err := h.Flush(); err != nil { t.Fatalf("flush: %v", err) } if disk := readDisk(t, path); disk != content { t.Fatalf("disk != original after é round-trip") } } // TestRealFile_ChunkBoundary_EditsAtBothEnds performs inserts at byte 0 and at // EOF on a multi-chunk file, the two extreme positions relative to chunks. func TestRealFile_ChunkBoundary_EditsAtBothEnds(t *testing.T) { content := lineFile(500) h, path := realFileHarness(t, "boundary.txt", content) defer h.Cleanup() original, _ := h.FullContent() // Prepend at byte 0. if err := h.WithState(func(st *editor.State) { st.Editor.CursorPosition = 0 editor.HandleInsert("TOP\n") }); err != nil { t.Fatalf("prepend: %v", err) } // Append at EOF. if err := h.WithState(func(st *editor.State) { st.Editor.CursorPosition = int(st.Editor.ChunkedBuffer.FileLen()) editor.HandleInsert("BOTTOM\n") }); err != nil { t.Fatalf("append: %v", err) } want := "TOP\n" + original + "BOTTOM\n" got, _ := h.FullContent() if got != want { t.Fatalf("buffer mismatch after end edits (len %d vs %d)", len(got), len(want)) } // 501 original entries + 2 (prepend + append). checkLineIndex(t, h, want, 503) if err := h.Flush(); err != nil { t.Fatalf("flush: %v", err) } if disk := readDisk(t, path); disk != want { t.Fatalf("disk mismatch after end edits") } }