summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-15 13:21:29 -0300
committerGabriel Schneider <[email protected]>2026-09-15 17:24:42 -0300
commit2bdde8c5ec1049ea0f575c6f9165ed4cef00ea50 (patch)
tree0d7879e387223bd19d78a295bee0ba7ab3a62e00 /test
parent8ec8924cd577f71953f8e420af1cd60da92f3300 (diff)
downloadpardes-2bdde8c5ec1049ea0f575c6f9165ed4cef00ea50.tar.gz
pardes-2bdde8c5ec1049ea0f575c6f9165ed4cef00ea50.zip
Align location results and configure source context
Diffstat (limited to 'test')
-rw-r--r--test/output.zig26
-rw-r--r--test/panes.zig126
-rw-r--r--test/pdf.zig18
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