use std::path::PathBuf;
use super::FieldName;
#[derive(Debug, Default, Clone)]
pub struct EditUpdates {
pub name: Option<String>,
pub description: Option<String>,
pub refines: Option<Vec<String>>,
pub derives_from: Option<Vec<String>>,
pub satisfies: Option<Vec<String>>,
pub depends_on: Option<Vec<String>>,
pub specification: Option<String>,
pub platform: Option<String>,
}
impl EditUpdates {
pub fn has_updates(&self) -> bool {
self.name.is_some()
|| self.description.is_some()
|| self.refines.is_some()
|| self.derives_from.is_some()
|| self.satisfies.is_some()
|| self.depends_on.is_some()
|| self.specification.is_some()
|| self.platform.is_some()
}
}
#[derive(Debug, Clone)]
pub struct EditSummary {
pub item_id: String,
pub file_path: PathBuf,
pub changes: Vec<FieldChange>,
}
impl EditSummary {
pub fn has_changes(&self) -> bool {
self.changes.iter().any(|c| c.is_changed())
}
pub fn actual_changes(&self) -> Vec<&FieldChange> {
self.changes.iter().filter(|c| c.is_changed()).collect()
}
}
#[derive(Debug, Clone)]
pub struct FieldChange {
pub field: FieldName,
pub old_value: String,
pub new_value: String,
}
impl FieldChange {
pub fn new(
field: FieldName,
old_value: impl Into<String>,
new_value: impl Into<String>,
) -> Self {
Self {
field,
old_value: old_value.into(),
new_value: new_value.into(),
}
}
pub fn is_changed(&self) -> bool {
self.old_value != self.new_value
}
}
#[derive(Debug, Default, Clone)]
pub struct TraceabilityLinks {
pub refines: Vec<String>,
pub derives_from: Vec<String>,
pub satisfies: Vec<String>,
pub depends_on: Vec<String>,
pub justifies: Vec<String>,
}
impl TraceabilityLinks {
pub fn is_empty(&self) -> bool {
self.refines.is_empty()
&& self.derives_from.is_empty()
&& self.satisfies.is_empty()
&& self.depends_on.is_empty()
&& self.justifies.is_empty()
}
pub fn from_item(item: &super::Item) -> Self {
use super::RelationshipType;
let collect_ids = |rel_type: RelationshipType| -> Vec<String> {
item.relationship_ids(rel_type)
.map(|id| id.as_str().to_string())
.collect()
};
Self {
refines: collect_ids(RelationshipType::Refines),
derives_from: collect_ids(RelationshipType::DerivesFrom),
satisfies: collect_ids(RelationshipType::Satisfies),
depends_on: item
.attributes
.depends_on()
.iter()
.map(|id| id.as_str().to_string())
.collect(),
justifies: collect_ids(RelationshipType::Justifies),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_edit_updates_has_updates_empty() {
let updates = EditUpdates::default();
assert!(!updates.has_updates());
}
#[test]
fn test_edit_updates_has_updates_name() {
let updates = EditUpdates {
name: Some("New Name".to_string()),
..Default::default()
};
assert!(updates.has_updates());
}
#[test]
fn test_edit_updates_has_updates_traceability() {
let updates = EditUpdates {
derives_from: Some(vec!["SCEN-001".to_string()]),
..Default::default()
};
assert!(updates.has_updates());
}
#[test]
fn test_field_change_is_changed() {
let changed = FieldChange::new(FieldName::Name, "Old", "New");
assert!(changed.is_changed());
let unchanged = FieldChange::new(FieldName::Name, "Same", "Same");
assert!(!unchanged.is_changed());
}
#[test]
fn test_edit_summary_has_changes() {
let summary = EditSummary {
item_id: "SREQ-001".to_string(),
file_path: PathBuf::from("test.md"),
changes: vec![
FieldChange::new(FieldName::Name, "Old", "New"),
FieldChange::new(FieldName::Description, "Same", "Same"),
],
};
assert!(summary.has_changes());
assert_eq!(summary.actual_changes().len(), 1);
}
#[test]
fn test_edit_summary_no_changes() {
let summary = EditSummary {
item_id: "SREQ-001".to_string(),
file_path: PathBuf::from("test.md"),
changes: vec![FieldChange::new(FieldName::Name, "Same", "Same")],
};
assert!(!summary.has_changes());
assert_eq!(summary.actual_changes().len(), 0);
}
}