diff options
Diffstat (limited to 'test')
| -rw-r--r-- | test/output.zig | 26 | ||||
| -rw-r--r-- | test/panes.zig | 126 | ||||
| -rw-r--r-- | test/pdf.zig | 18 |
3 files changed, 162 insertions, 8 deletions
diff --git a/test/output.zig b/test/output.zig index fd4252e1..42b1ce7c 100644 --- a/test/output.zig +++ b/test/output.zig @@ -11,6 +11,24 @@ const platform = pardes.platform; const font_picker = pardes.font_picker; const fonts = if (font_picker) pardes.fonts else struct {}; +// Sorting tests compare original locations and source bytes independently of +// alignment. Dedicated Locations tests verify the rendered column widths. +fn expectResultRows(expected: []const u8, file: *const panes.File.State) !void { + if (file.location_rows.len == 0) return std.testing.expectEqualStrings(expected, file.content); + try std.testing.expectEqual(std.mem.endsWith(u8, expected, "\n"), std.mem.endsWith(u8, file.content, "\n")); + var source = std.mem.splitScalar(u8, expected, '\n'); + var shown = std.mem.splitScalar(u8, file.content, '\n'); + for (file.location_rows) |row| { + const want = source.next() orelse return error.MissingExpectedResult; + const actual = shown.next() orelse return error.MissingRenderedResult; + const target = panes.Output.location(want); + try std.testing.expectEqualStrings(want[0..target.end], actual[0..row.location_end]); + try std.testing.expectEqualStrings(want[@min(want.len, target.end + 1)..], actual[row.code_start..]); + } + try std.testing.expectEqualStrings("", source.next() orelse ""); + try std.testing.expect(source.next() == null); +} + test "search tint survives wrapping and theme changes while selections remain authoritative" { const text = "nested/path/a.txt:1:5-7 let abc = 1;\n"; for ([_]u16{ 30, 80 }) |width| { @@ -63,7 +81,7 @@ test "location results sort filenames and numeric positions while remapping thei const source = try p.setTestFile("notes\n"); try panes.Output.fillResults(p, 0, "/tmp", origin, "", try p.gpa.dupe(u8, before), 2); const results = p.panes[source.search_pane.?].?; - try std.testing.expectEqualStrings(after, results.file.?.content); + try expectResultRows(after, &results.file.?); try std.testing.expectEqual(@as(usize, 3), source.search_row.?); } } @@ -106,7 +124,7 @@ test "reference Look selects the next sorted row from the captured request and k p.lspResponse(request, response); const rid = source.search_pane orelse return error.MissingResults; const results = p.panes[rid].?; - try std.testing.expectEqualStrings(sorted, results.file.?.content); + try expectResultRows(sorted, &results.file.?); try std.testing.expectEqual(@as(usize, 0), p.active); try std.testing.expectEqual(@as(i32, 3), source.cur_row); try std.testing.expect(source.vsel.active); @@ -159,7 +177,7 @@ test "location sorting leaves mixed prose alone and preserves newline shape" { defer p.deinit(); const source = try p.setTestFile("notes\n"); try panes.Output.fillResults(p, 0, "/tmp", .search, "", try p.gpa.dupe(u8, case.before), 1); - try std.testing.expectEqualStrings(case.after, p.panes[source.search_pane.?].?.file.?.content); + try expectResultRows(case.after, &p.panes[source.search_pane.?].?.file.?); try std.testing.expectEqual(case.anchor, source.search_row.?); } } @@ -218,7 +236,7 @@ test "reordered identical results preserve the selected row and warm line index" result.select = true; const selected = result.vsel; try panes.Output.fillResults(p, 0, "/tmp", .{ .query = .references }, "new request", try p.gpa.dupe(u8, "a.zig:10:1 second\na.zig:2:1 first\n"), 0); - try std.testing.expectEqualStrings(sorted, file.content); + try expectResultRows(sorted, file); try std.testing.expectEqual(content, file.content.ptr); try std.testing.expectEqual(index.ptr, file.line_starts.ptr); try std.testing.expectEqual(revision, file.revision); diff --git a/test/panes.zig b/test/panes.zig index 073a9668..97dc3927 100644 --- a/test/panes.zig +++ b/test/panes.zig @@ -1597,6 +1597,132 @@ const OutputTests = struct { const fromWord = panes.Output.fromWord; const fillResults = panes.Output.fillResults; + test "Locations align Unicode paths by display width and preserve source indentation" { + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true }); + defer p.deinit(); + _ = try p.setTestFile("notes\n"); + try fillResults(p, 0, "/", .search, "alignment", try p.gpa.dupe(u8, "/界.zig:2:1 first\n/longer-name.zig:12:3 \tsecond\n"), null); + const result = p.panes[p.panes[0].?.search_pane.?].?; + const file = &result.file.?; + try std.testing.expectEqual(@as(usize, 2), file.location_rows.len); + var column: ?usize = null; + for (file.location_rows, 0..) |row, i| { + const line = modal.lineSlice(file.content, i); + try std.testing.expectEqual(.match, row.kind); + const current = panes.File.displayWidth(line[0..row.code_start]); + if (column) |previous| try std.testing.expectEqual(previous, current); + column = current; + const expected = if (std.mem.eql(u8, row.path, "/界.zig")) " first" else "\tsecond"; + try std.testing.expectEqualStrings(expected, line[row.code_start..]); + } + } + + test "Locations context uses unsaved sources and navigation visits only matches" { + if (!syntax.enabled) return error.SkipZigTest; + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 100, .rows = 30 }); + defer p.deinit(); + const source = "const Box = struct {\n" ++ + " pub fn run() void {\n" ++ + " // unsaved before\n" ++ + " first();\n" ++ + " // shared context\n" ++ + " second();\n" ++ + " // unsaved after\n" ++ + " }\n};\n"; + const pane = try TreeContextTests.setZigFile(p, source); + p.locations_config.context = 1; + p.locations_config.tscontext = true; + p.locations_config.tslocations = false; + const path = pane.file.?.path; + const input = try std.fmt.allocPrint(p.gpa, "{s}:6:9 second();\n{s}:4:9 first();\n", .{ path, path }); + // Original first result is the later source match: sorting and inserted + // context must both preserve its anchor. + try fillResults(p, 0, "/", .search, "context", input, 0); + const result = p.panes[pane.search_pane.?].?; + const file = &result.file.?; + const expected_lines = [_]usize{ 1, 2, 3, 4, 5, 6, 7 }; + try std.testing.expectEqual(expected_lines.len, file.location_rows.len); + var matches: [2]usize = undefined; + var count: usize = 0; + for (file.location_rows, expected_lines, 0..) |row, expected_line, i| { + try std.testing.expectEqual(expected_line, row.at.line); + try std.testing.expectEqualStrings(path, row.path); + const line = modal.lineSlice(file.content, i); + if (row.kind == .match) { + matches[count] = i; + count += 1; + } else { + try std.testing.expect(std.mem.startsWith(u8, line, "| ")); + try std.testing.expect(!panes.Output.isResultRow(file, i)); + try std.testing.expectEqualStrings(modal.lineSlice(source, expected_line - 1), line[row.code_start..]); + } + if (expected_line <= 2) try std.testing.expectEqual(@as(usize, 0), row.location_end); + } + try std.testing.expectEqual(@as(usize, 2), count); + try std.testing.expectEqual(matches[1], pane.search_row.?); + try std.testing.expect(pardes.test_api.searchStep(p, 0, -1)); + try std.testing.expectEqual(matches[0], pane.search_row.?); + try std.testing.expectEqual(@as(i32, 3), pane.cur_row); + try std.testing.expect(pardes.test_api.searchStep(p, 0, 1)); + try std.testing.expectEqual(matches[1], pane.search_row.?); + try std.testing.expectEqual(@as(i32, 5), pane.cur_row); + try std.testing.expect(pardes.test_api.searchStep(p, 0, 1)); + try std.testing.expectEqual(matches[1], pane.search_row.?); + + pane.search_row = null; + try std.testing.expect(pardes.test_api.searchStep(p, 0, 1)); + try std.testing.expectEqual(matches[0], pane.search_row.?); + var frame = std.heap.ArenaAllocator.init(p.gpa); + defer frame.deinit(); + _ = try p.render(frame.allocator()); + const declaration = file.location_rows[0]; + const keyword = declaration.code_start + std.mem.indexOf(u8, modal.lineSlice(source, 0), "struct").?; + const styles = file.highlights[keyword..][0.."struct".len]; + var colored = false; + for (styles) |style| colored = colored or style != 0; + try std.testing.expect(colored); + + try p.dumpState(); + const restored = try Pardes.initFromDump(p.gpa, .{ .tty_only = true }, p.dump_out.?); + defer restored.deinit(); + const restored_source = restored.panes[0].?; + const restored_id = pane.search_pane.?; + const restored_result = restored.panes[restored_id].?; + const restored_file = &restored_result.file.?; + try std.testing.expectEqualStrings(file.content, restored_file.content); + try std.testing.expectEqual(file.location_rows.len, restored_file.location_rows.len); + for (file.location_rows, restored_file.location_rows) |before, after| { + try std.testing.expectEqual(before.kind, after.kind); + try std.testing.expectEqualStrings(before.path, after.path); + try std.testing.expectEqual(before.at, after.at); + try std.testing.expectEqual(before.location_end, after.location_end); + try std.testing.expectEqual(before.code_start, after.code_start); + try std.testing.expectEqualSlices(u8, before.colors, after.colors); + } + _ = try restored.render(frame.allocator()); + try std.testing.expectEqualSlices(u8, styles, restored_file.highlights[keyword..][0.."struct".len]); + // Search links are transient UI state; the restored output metadata + // still governs stepping when that output is attached to a source. + restored_source.search_pane = restored_id; + restored_source.search_row = null; + try std.testing.expect(pardes.test_api.searchStep(restored, 0, 1)); + try std.testing.expectEqual(matches[0], restored_source.search_row.?); + try std.testing.expect(pardes.test_api.searchStep(restored, 0, 1)); + try std.testing.expectEqual(matches[1], restored_source.search_row.?); + try std.testing.expect(pardes.test_api.searchStep(restored, 0, -1)); + try std.testing.expectEqual(matches[0], restored_source.search_row.?); + + // Once an edit invalidates source metadata, the explicit marker still + // prevents context declarations from becoming search destinations. + panes.File.setContent(p, file, try p.gpa.dupe(u8, file.content)); + try std.testing.expectEqual(@as(usize, 0), file.location_rows.len); + pane.search_row = null; + try std.testing.expect(pardes.test_api.searchStep(p, 0, 1)); + try std.testing.expectEqual(matches[0], pane.search_row.?); + try std.testing.expect(pardes.test_api.searchStep(p, 0, 1)); + try std.testing.expectEqual(matches[1], pane.search_row.?); + } + test "replacing generated Help clears stale selection before executing a new row" { const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 120, .rows = 40 }); defer p.deinit(); diff --git a/test/pdf.zig b/test/pdf.zig index 4c0daf34..a835c6ea 100644 --- a/test/pdf.zig +++ b/test/pdf.zig @@ -1408,12 +1408,22 @@ test "PDF n/N addresses and reveals distinct MuPDF hits on one page" { defer page_hits.deinit(gpa); try std.testing.expect(page_hits.hit_count >= 3); + p.locations_config.context = 2; + p.locations_config.tscontext = true; try p.runSearch(0, "Pardes", .text, .top); const results_id = pane.search_pane orelse return error.MissingPdfSearchResults; - const rows = p.panes[results_id].?.file.?.content; - try std.testing.expect(std.mem.indexOf(u8, rows, "design.pdf:1:1 Pardes\n") != null); - try std.testing.expect(std.mem.indexOf(u8, rows, "design.pdf:1:2 Pardes\n") != null); - try std.testing.expect(std.mem.indexOf(u8, rows, "design.pdf:1:3 Pardes\n") != null); + const results = p.panes[results_id].?.file.?; + var found: [3]bool = @splat(false); + for (results.location_rows, 0..) |row, index| { + // PDF locations address page/hit pairs, not binary file lines. + try std.testing.expectEqual(.match, row.kind); + if (!std.mem.endsWith(u8, row.path, "design.pdf") or row.at.line != 1) continue; + if (row.at.col < 1 or row.at.col > found.len) continue; + const line = pardes.modal.lineSlice(results.content, index); + try std.testing.expectEqualStrings("Pardes", line[row.code_start..]); + found[row.at.col - 1] = true; + } + for (found) |present| try std.testing.expect(present); // Exaggerate the cell aspect only to make the three fixture hits occupy // distinct fit-width crops. The search/reveal math must use the same |
