@ -963,3 +963,172 @@ fn list_filter_type_and_status_wildcard_includes_done_bugs() {
"both bug tickets should appear when status=* is set"
) ;
}
// ── nbd next tests ────────────────────────────────────────────────────────────
/// `nbd next --json` returns the highest-priority ready ticket.
///
/// Setup: A (pri 5, no deps), B (pri 8, dep A → blocked), C (pri 7, no deps).
/// Expected: C is the highest-priority ready ticket (B is blocked by A).
#[ test ]
fn next_returns_highest_priority_ready ( ) {
let env = TestEnv ::new ( ) ;
let a = env . create ( & [ "--title" , "Ticket A" , "--priority" , "5" ] ) ;
env . run ( & [
"create" ,
"--title" ,
"Ticket B" ,
"--priority" ,
"8" ,
"--deps" ,
& a ,
"--json" ,
] ) ;
env . create ( & [ "--title" , "Ticket C" , "--priority" , "7" ] ) ;
let output = env . run ( & [ "next" , "--json" ] ) ;
assert! ( output . status . success ( ) ) ;
let stdout = String ::from_utf8 ( output . stdout ) . unwrap ( ) ;
let parsed : serde_json ::Value =
serde_json ::from_str ( & stdout ) . expect ( "next --json should be valid JSON" ) ;
assert! ( parsed . get ( "next" ) . is_some ( ) , "should have 'next' key" ) ;
let ticket = & parsed [ "next" ] ;
assert! ( ! ticket . is_null ( ) , "next should not be null" ) ;
assert_eq! (
ticket [ "title" ] , "Ticket C" ,
"C has the highest priority among ready tickets"
) ;
}
/// After marking the dependency done, a previously blocked ticket becomes next.
#[ test ]
fn next_updates_after_dep_done ( ) {
let env = TestEnv ::new ( ) ;
let a = env . create ( & [ "--title" , "Dep A" , "--priority" , "5" ] ) ;
env . run ( & [
"create" ,
"--title" ,
"Blocked B" ,
"--priority" ,
"8" ,
"--deps" ,
& a ,
"--json" ,
] ) ;
// Before A is done: A is next (priority 5, the only ready ticket).
let before = env . run ( & [ "next" , "--json" ] ) ;
let before_str = String ::from_utf8 ( before . stdout ) . unwrap ( ) ;
let before_parsed : serde_json ::Value = serde_json ::from_str ( & before_str ) . unwrap ( ) ;
assert_eq! ( before_parsed [ "next" ] [ "title" ] , "Dep A" ) ;
// Mark A done.
env . run ( & [ "update" , & a , "--status" , "done" ] ) ;
// Now B (priority 8) is next.
let after = env . run ( & [ "next" , "--json" ] ) ;
let after_str = String ::from_utf8 ( after . stdout ) . unwrap ( ) ;
let after_parsed : serde_json ::Value = serde_json ::from_str ( & after_str ) . unwrap ( ) ;
assert_eq! ( after_parsed [ "next" ] [ "title" ] , "Blocked B" ) ;
}
/// When all tickets are done, `nbd next --json` returns `{"next": null}`.
#[ test ]
fn next_null_when_all_done ( ) {
let env = TestEnv ::new ( ) ;
let id = env . create ( & [ "--title" , "Only ticket" ] ) ;
env . run ( & [ "update" , & id , "--status" , "done" ] ) ;
let output = env . run ( & [ "next" , "--json" ] ) ;
assert! ( output . status . success ( ) ) ;
let stdout = String ::from_utf8 ( output . stdout ) . unwrap ( ) ;
let parsed : serde_json ::Value = serde_json ::from_str ( & stdout ) . unwrap ( ) ;
assert! (
parsed [ "next" ] . is_null ( ) ,
"next should be null when all are done"
) ;
}
/// `nbd next` without `--json` prints "No ready tickets." and exits 0 when nothing is ready.
#[ test ]
fn next_no_json_prints_message_when_empty ( ) {
let env = TestEnv ::new ( ) ;
let id = env . create ( & [ "--title" , "Finished" ] ) ;
env . run ( & [ "update" , & id , "--status" , "done" ] ) ;
let output = env . run ( & [ "next" ] ) ;
assert! (
output . status . success ( ) ,
"should exit 0 even with no ready tickets"
) ;
let stdout = String ::from_utf8 ( output . stdout ) . unwrap ( ) ;
assert! (
stdout . contains ( "No ready tickets." ) ,
"should print 'No ready tickets.', got: {stdout}"
) ;
}
/// `nbd next --filter type=bug --json` returns the highest-priority ready bug,
/// even when a higher-priority non-bug ticket exists.
#[ test ]
fn next_filter_by_type ( ) {
let env = TestEnv ::new ( ) ;
// Task with priority 9 (highest overall).
env . create ( & [ "--title" , "High task" , "--priority" , "9" , "--type" , "task" ] ) ;
// Bug with priority 8.
env . create ( & [ "--title" , "High bug" , "--priority" , "8" , "--type" , "bug" ] ) ;
let output = env . run ( & [ "next" , "--filter" , "type=bug" , "--json" ] ) ;
assert! ( output . status . success ( ) ) ;
let stdout = String ::from_utf8 ( output . stdout ) . unwrap ( ) ;
let parsed : serde_json ::Value = serde_json ::from_str ( & stdout ) . unwrap ( ) ;
assert_eq! (
parsed [ "next" ] [ "ticket_type" ] , "bug" ,
"filter should restrict to bug tickets"
) ;
assert_eq! ( parsed [ "next" ] [ "title" ] , "High bug" ) ;
}
/// `nbd next --json` output object always contains an `"id"` field.
#[ test ]
fn next_json_includes_id_field ( ) {
let env = TestEnv ::new ( ) ;
env . create ( & [ "--title" , "Has ID" ] ) ;
let output = env . run ( & [ "next" , "--json" ] ) ;
assert! ( output . status . success ( ) ) ;
let stdout = String ::from_utf8 ( output . stdout ) . unwrap ( ) ;
let parsed : serde_json ::Value = serde_json ::from_str ( & stdout ) . unwrap ( ) ;
let ticket = & parsed [ "next" ] ;
assert! ( ! ticket . is_null ( ) , "should find a ready ticket" ) ;
assert! (
ticket . get ( "id" ) . is_some ( ) ,
"ticket object should contain 'id' field"
) ;
let id = ticket [ "id" ] . as_str ( ) . unwrap ( ) ;
assert_eq! ( id . len ( ) , 6 , "id should be 6 characters" ) ;
}
/// `nbd next --filter priority=99 --json` returns `{"next": null}` when no
/// ticket has the requested priority.
#[ test ]
fn next_filter_no_match_returns_null ( ) {
let env = TestEnv ::new ( ) ;
env . create ( & [ "--title" , "Normal ticket" , "--priority" , "5" ] ) ;
let output = env . run ( & [ "next" , "--filter" , "priority=99" , "--json" ] ) ;
assert! ( output . status . success ( ) ) ;
let stdout = String ::from_utf8 ( output . stdout ) . unwrap ( ) ;
let parsed : serde_json ::Value = serde_json ::from_str ( & stdout ) . unwrap ( ) ;
assert! (
parsed [ "next" ] . is_null ( ) ,
"no ticket has priority 99 so next should be null"
) ;
}