diff --git a/src/Query.zig b/src/Query.zig index 2ccc06b..5be12a6 100644 --- a/src/Query.zig +++ b/src/Query.zig @@ -37,6 +37,17 @@ pub fn getQueryString(self: *const Query) [:0]const u8 { return std.mem.span(c.notmuch_query_get_query_string(self.query)); } +test getQueryString { + const fixture = @import("testing.zig"); + + var test_db = try fixture.corpusDatabase(); + defer test_db.deinit(); + + const query = try test_db.database.queryCreate("tag:inbox"); + defer query.deinit(); + try std.testing.expectEqualStrings("tag:inbox", query.getQueryString()); +} + /// Return the notmuch database of this query. See `Database.queryCreate`. pub fn getDatabase(self: *const Query) ?Database { return .{ @@ -44,6 +55,20 @@ pub fn getDatabase(self: *const Query) ?Database { }; } +test getDatabase { + const fixture = @import("testing.zig"); + + var test_db = try fixture.corpusDatabase(); + defer test_db.deinit(); + + const query = try test_db.database.queryCreate("tag:inbox"); + defer query.deinit(); + + // The query's owning database is the one it was created from. + const db = query.getDatabase() orelse return error.TestUnexpectedResult; + try std.testing.expectEqual(test_db.database.database, db.database); +} + /// Specify whether to omit excluded results or simply flag them. By default, /// this is set to `true`. /// @@ -70,16 +95,85 @@ pub fn setOmitExcluded(self: *const Query, omit_excluded: Exclude) void { c.notmuch_query_set_omit_excluded(self.query, @intFromEnum(omit_excluded)); } +test setOmitExcluded { + const fixture = @import("testing.zig"); + + var test_db = try fixture.corpusDatabase(); + defer test_db.deinit(); + const db = test_db.database; + + const spam = try db.findMessage("three@example.org") orelse return error.TestUnexpectedResult; + defer spam.deinit(); + try spam.addTag("spam"); + + const query = try db.queryCreate("*"); + defer query.deinit(); + try query.addTagExclude("spam"); + + // With .false, the exclusion is disabled entirely. + query.setOmitExcluded(.false); + { + var messages = try query.searchMessages(); + defer messages.deinit(); + var count: usize = 0; + while (try messages.next()) |message| : (count += 1) message.deinit(); + try std.testing.expectEqual(fixture.corpus.len, count); + } + + // With .flag, excluded messages are returned but marked with the + // excluded message flag. + query.setOmitExcluded(.flag); + { + var messages = try query.searchMessages(); + defer messages.deinit(); + var excluded_count: usize = 0; + while (try messages.next()) |message| { + defer message.deinit(); + if (try message.getFlag(.excluded)) excluded_count += 1; + } + try std.testing.expectEqual(@as(usize, 1), excluded_count); + } +} + /// Specify the sorting desired for this query. pub fn setSort(self: *const Query, sort: Sort) void { c.notmuch_query_set_sort(self.query, @intFromEnum(sort)); } +test setSort { + const fixture = @import("testing.zig"); + + var test_db = try fixture.corpusDatabase(); + defer test_db.deinit(); + + const query = try test_db.database.queryCreate("*"); + defer query.deinit(); + + // Every sort order round-trips through getSort. See `searchMessages` + // for the effect on search results. + for ([_]Sort{ .oldest_first, .newest_first, .message_id, .unsorted }) |sort| { + query.setSort(sort); + try std.testing.expectEqual(sort, query.getSort()); + } +} + /// Return the sort specified for this query. pub fn getSort(self: *const Query) Sort { return @enumFromInt(c.notmuch_query_get_sort(self.query)); } +test getSort { + const fixture = @import("testing.zig"); + + var test_db = try fixture.corpusDatabase(); + defer test_db.deinit(); + + // The default sort order is newest first. + const query = try test_db.database.queryCreate("*"); + defer query.deinit(); + try std.testing.expectEqual(Sort.newest_first, query.getSort()); +} + pub const AddTagExcludeError = error{ /// The tag is explicitly present in the query, so not excluded. Ignored, @@ -160,6 +254,28 @@ pub fn searchThreads(self: *const Query) Error!ThreadsIterator { }; } +test searchThreads { + const fixture = @import("testing.zig"); + + var test_db = try fixture.corpusDatabase(); + defer test_db.deinit(); + + const query = try test_db.database.queryCreate("*"); + defer query.deinit(); + query.setSort(.oldest_first); + + // The corpus holds a two-message thread plus two standalone messages. + var threads = try query.searchThreads(); + defer threads.deinit(); + const first = try threads.next() orelse return error.TestUnexpectedResult; + defer first.deinit(); + try std.testing.expectEqualStrings("Lunch plans", first.getSubject()); + + var remaining: usize = 0; + while (try threads.next()) |thread| : (remaining += 1) thread.deinit(); + try std.testing.expectEqual(@as(usize, 2), remaining); +} + /// Execute a query for messages, returning a MessagesIterator object which can /// be used to iterate over the results. The returned messages object is owned /// by the query and as such, will only be valid until `Query.deinit`. @@ -285,6 +401,19 @@ pub fn countThreads(self: *const Query) CountThreadsError!u32 { }; } +test countThreads { + const fixture = @import("testing.zig"); + + var test_db = try fixture.corpusDatabase(); + defer test_db.deinit(); + + // Four messages, but the lunch pair shares a thread. + const query = try test_db.database.queryCreate("*"); + defer query.deinit(); + try std.testing.expectEqual(@as(u32, 4), try query.countMessages()); + try std.testing.expectEqual(@as(u32, 3), try query.countThreads()); +} + /// Deinitialize the `Query` along with any associated resources. /// /// This will in turn deinitialize any `ThreadsIterator` and `MessagesIterator` diff --git a/src/tests.zig b/src/tests.zig index ecaef68..f245b06 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -101,19 +101,6 @@ test "Database.reopen revives a closed database" { try std.testing.expectEqualStrings("one@example.org", message.getMessageID() orelse ""); } -test "Query string, sort, and database accessors" { - var test_db = try fixture.corpusDatabase(); - defer test_db.deinit(); - - const query = try test_db.database.queryCreate("tag:inbox"); - defer query.deinit(); - - try std.testing.expectEqualStrings("tag:inbox", query.getQueryString()); - query.setSort(.oldest_first); - try std.testing.expectEqual(notmuch.Query.Sort.oldest_first, query.getSort()); - try std.testing.expect(query.getDatabase() != null); -} - test "Query tag exclusion error paths" { var test_db = try fixture.corpusDatabase(); defer test_db.deinit();