|
|
|
@ -288,3 +288,152 @@ mod store {
|
|
|
|
assert_eq!(dir, Path::new("/tmp/project/.nbd/tickets"));
|
|
|
|
assert_eq!(dir, Path::new("/tmp/project/.nbd/tickets"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ── display module ────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Tests for [`crate::display`].
|
|
|
|
|
|
|
|
mod display {
|
|
|
|
|
|
|
|
use crate::display::{format_list, format_list_json, format_ticket, format_ticket_json};
|
|
|
|
|
|
|
|
use crate::ticket::{Status, Ticket, TicketType};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Build a fully-populated ticket for use in display tests.
|
|
|
|
|
|
|
|
fn sample_ticket() -> Ticket {
|
|
|
|
|
|
|
|
Ticket {
|
|
|
|
|
|
|
|
id: "a3f9c2".to_string(),
|
|
|
|
|
|
|
|
title: "Fix login bug".to_string(),
|
|
|
|
|
|
|
|
body: "Users cannot log in with email addresses containing +".to_string(),
|
|
|
|
|
|
|
|
priority: 8,
|
|
|
|
|
|
|
|
status: Status::InProgress,
|
|
|
|
|
|
|
|
dependencies: vec!["b7d41e".to_string(), "c9e823".to_string()],
|
|
|
|
|
|
|
|
ticket_type: TicketType::Bug,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// `format_ticket` includes all field values in its output.
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
|
|
fn format_ticket_contains_all_fields() {
|
|
|
|
|
|
|
|
let t = sample_ticket();
|
|
|
|
|
|
|
|
let output = format_ticket(&t);
|
|
|
|
|
|
|
|
assert!(output.contains("a3f9c2"), "should contain ID");
|
|
|
|
|
|
|
|
assert!(output.contains("Fix login bug"), "should contain title");
|
|
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
|
|
output.contains("Users cannot log in"),
|
|
|
|
|
|
|
|
"should contain body"
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
assert!(output.contains('8'), "should contain priority");
|
|
|
|
|
|
|
|
assert!(output.contains("in_progress"), "should contain status");
|
|
|
|
|
|
|
|
assert!(output.contains("bug"), "should contain ticket type");
|
|
|
|
|
|
|
|
assert!(output.contains("b7d41e"), "should contain first dependency");
|
|
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
|
|
output.contains("c9e823"),
|
|
|
|
|
|
|
|
"should contain second dependency"
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// `format_ticket` renders dependency IDs as a comma-separated list.
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
|
|
fn format_ticket_joins_dependencies() {
|
|
|
|
|
|
|
|
let t = sample_ticket();
|
|
|
|
|
|
|
|
let output = format_ticket(&t);
|
|
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
|
|
output.contains("b7d41e, c9e823"),
|
|
|
|
|
|
|
|
"dependencies should be comma-separated: {output}"
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// `format_ticket` renders an empty `Dependencies:` line when there are none.
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
|
|
fn format_ticket_empty_dependencies() {
|
|
|
|
|
|
|
|
let t = Ticket::new("aaaaaa".to_string(), "No deps".to_string());
|
|
|
|
|
|
|
|
let output = format_ticket(&t);
|
|
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
|
|
output.contains("Dependencies:"),
|
|
|
|
|
|
|
|
"Dependencies label should always appear"
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// `format_ticket_json` produces valid, parseable JSON containing key fields.
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
|
|
fn format_ticket_json_is_valid_json() {
|
|
|
|
|
|
|
|
let t = sample_ticket();
|
|
|
|
|
|
|
|
let output = format_ticket_json(&t);
|
|
|
|
|
|
|
|
let parsed: serde_json::Value =
|
|
|
|
|
|
|
|
serde_json::from_str(&output).expect("output should be valid JSON");
|
|
|
|
|
|
|
|
assert_eq!(parsed["id"], "a3f9c2");
|
|
|
|
|
|
|
|
assert_eq!(parsed["title"], "Fix login bug");
|
|
|
|
|
|
|
|
assert_eq!(parsed["priority"], 8);
|
|
|
|
|
|
|
|
assert_eq!(parsed["status"], "in_progress");
|
|
|
|
|
|
|
|
assert_eq!(parsed["ticket_type"], "bug");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// `format_list` includes a header row with all column names.
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
|
|
fn format_list_shows_header() {
|
|
|
|
|
|
|
|
let output = format_list(&[]);
|
|
|
|
|
|
|
|
assert!(output.contains("ID"), "header should contain ID");
|
|
|
|
|
|
|
|
assert!(output.contains("PRI"), "header should contain PRI");
|
|
|
|
|
|
|
|
assert!(output.contains("TYPE"), "header should contain TYPE");
|
|
|
|
|
|
|
|
assert!(output.contains("STATUS"), "header should contain STATUS");
|
|
|
|
|
|
|
|
assert!(output.contains("TITLE"), "header should contain TITLE");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// `format_list` includes every ticket's key fields.
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
|
|
fn format_list_contains_all_tickets() {
|
|
|
|
|
|
|
|
let mut t1 = Ticket::new("id0001".to_string(), "First ticket".to_string());
|
|
|
|
|
|
|
|
t1.priority = 9;
|
|
|
|
|
|
|
|
let mut t2 = Ticket::new("id0002".to_string(), "Second ticket".to_string());
|
|
|
|
|
|
|
|
t2.priority = 3;
|
|
|
|
|
|
|
|
let tickets = vec![t1, t2];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let output = format_list(&tickets);
|
|
|
|
|
|
|
|
assert!(output.contains("id0001"), "should contain first ID");
|
|
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
|
|
output.contains("First ticket"),
|
|
|
|
|
|
|
|
"should contain first title"
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
assert!(output.contains("id0002"), "should contain second ID");
|
|
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
|
|
output.contains("Second ticket"),
|
|
|
|
|
|
|
|
"should contain second title"
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// `format_list` renders the correct status and type strings.
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
|
|
fn format_list_renders_status_and_type_strings() {
|
|
|
|
|
|
|
|
let t = sample_ticket(); // status: in_progress, type: bug
|
|
|
|
|
|
|
|
let output = format_list(&[t]);
|
|
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
|
|
output.contains("in_progress"),
|
|
|
|
|
|
|
|
"should render status string"
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
assert!(output.contains("bug"), "should render type string");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// `format_list_json` produces a valid JSON array with one object per ticket.
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
|
|
fn format_list_json_is_valid_json_array() {
|
|
|
|
|
|
|
|
let tickets = vec![
|
|
|
|
|
|
|
|
Ticket::new("id0001".to_string(), "First".to_string()),
|
|
|
|
|
|
|
|
Ticket::new("id0002".to_string(), "Second".to_string()),
|
|
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
let output = format_list_json(&tickets);
|
|
|
|
|
|
|
|
let parsed: serde_json::Value =
|
|
|
|
|
|
|
|
serde_json::from_str(&output).expect("output should be valid JSON");
|
|
|
|
|
|
|
|
assert!(parsed.is_array(), "output should be a JSON array");
|
|
|
|
|
|
|
|
assert_eq!(parsed.as_array().unwrap().len(), 2);
|
|
|
|
|
|
|
|
assert_eq!(parsed[0]["id"], "id0001");
|
|
|
|
|
|
|
|
assert_eq!(parsed[1]["id"], "id0002");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// `format_list_json` returns an empty JSON array for an empty slice.
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
|
|
fn format_list_json_empty_slice() {
|
|
|
|
|
|
|
|
let output = format_list_json(&[]);
|
|
|
|
|
|
|
|
let parsed: serde_json::Value =
|
|
|
|
|
|
|
|
serde_json::from_str(&output).expect("output should be valid JSON");
|
|
|
|
|
|
|
|
assert!(parsed.is_array());
|
|
|
|
|
|
|
|
assert!(parsed.as_array().unwrap().is_empty());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|