Skip to content
This repository was archived by the owner on Feb 17, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .agent/workflows/tdd.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,3 @@ description: Test-Driven Development workflow with high safety and auditable res
- **Git Note**: Attach a summary of the task using `git notes add -m "<summary>" <hash>`.
- **Note Content**: Task name, changes, files modified, and "why".

## 6. Documentation Updates
- Update `PLAN.md` or track plans with the commit SHA and status `[x]`.
36 changes: 36 additions & 0 deletions csln/src/style/locale.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,3 +269,39 @@ pub enum LocalizedTermNameMisc {

WorkingPaper,
}
#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_locale_deserialization() {
let json = r#"
{
"locale": "en-US",
"dates": {
"months": {
"long": ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
"short": ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
},
"seasons": ["Spring", "Summer", "Autumn", "Winter"]
},
"roles": {},
"terms": {
"and": "and",
"anonymous": {
"long": "anonymous",
"short": "anon"
},
"circa": {
"long": "circa",
"short": "c."
}
}
}
"#;
let locale: Locale = serde_json::from_str(json).unwrap();
assert_eq!(locale.locale, "en-US");
assert_eq!(locale.dates.months.long[0], "January");
assert_eq!(locale.terms.and.as_ref().unwrap(), "and");
}
}
28 changes: 28 additions & 0 deletions csln/src/style/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,32 @@ mod tests {
assert!(style.bibliography.is_none());
assert!(style.citation.is_none());
}

#[test]
fn test_style_deserialization_complex() {
let json = r#"
{
"info": {
"title": "Complex Style",
"id": "http://example.com/styles/complex"
},
"bibliography": {
"template": [
{
"contributor": "author",
"form": "long"
},
{
"date": "issued",
"form": "year"
}
]
}
}
"#;
let style: Style = serde_json::from_str(json).unwrap();
assert_eq!(style.info.title.as_ref().unwrap(), "Complex Style");
let bib = style.bibliography.unwrap();
assert_eq!(bib.template.len(), 2);
}
}
41 changes: 41 additions & 0 deletions docs/TEST_PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Test Coverage Extension Plan

This document outlines a strategy to significantly increase test coverage across the `csln` workspace, focusing on the core processing logic and data extraction.

## 1. Processor Component Logic (`processor/src/values.rs`)

The extraction of values from bibliographic references into template components is currently the most complex and least tested area.

### Priorities:
- [ ] **Field Extraction**: Implement unit tests for all `Variables` and `Numbers` variants across all `InputReference` types (Monograph, SerialComponent, etc.).
- [ ] **Contributor Formatting**: Expand tests for `role_to_string` and `TemplateContributor::values` to cover all forms (Long, Short, Verb) and role substitution logic.
- [ ] **Date Processing**: Add comprehensive tests for `TemplateDate` including EDTF parsing edge cases and disambiguation suffixes (`int_to_letter`).

## 2. Rendering & Formatting (`processor/src/render.rs`)

Ensure the final string output matches expected citation standards.

### Priorities:
- [ ] **Display Trait**: Add tests for `ProcTemplateComponent`'s `Display` implementation, specifically verifying prefix/suffix handling and `WrapPunctuation` (Parentheses, Brackets).
- [ ] **Collection Rendering**: Test `refs_to_string` with multiple references to ensure proper separators and final punctuation.

## 3. Top-Level Processor Features (`processor/src/processor.rs`)

Verify the orchestration of styles, bibliographies, and citations.

### Priorities:
- [ ] **Sorting**: Test `sort_references` with complex `SortKey` arrays (e.g., Author -> Year -> Title).
- [ ] **Disambiguation**: Verify hint calculation and how it affects output (e.g., adding 'a', 'b' to years).
- [ ] **Error Handling**: Test `process_citations` with missing references or invalid style configurations.

## 4. Model Integrity (`csln/src/`)

### Priorities:
- [ ] **Style Validation**: Add tests for `Style` deserialization from YAML/JSON to ensure complex templates are parsed correctly.
- [ ] **Locale Handling**: Verify locale merging and term lookup safety.

## Execution Strategy

1. **Phase 1**: Add unit tests in the same files as the logic (using `#[cfg(test)]`) for fast feedback.
2. **Phase 2**: Create a `tests/` directory in the `processor` crate for high-level integration tests using real styles and bibliographies.
3. **Phase 3**: Integrate coverage reporting into CI to maintain a >80% threshold for new PRs.
67 changes: 60 additions & 7 deletions processor/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,13 +410,66 @@ mod tests {
}

#[test]
fn make_group_key_defaults() {
// Test default grouping (should be empty or based on default config)
fn test_sort_references() {
use csln::style::options::{Processing, ProcessingCustom, Sort, SortSpec};

let mut style = Style::default();
let config = ProcessingCustom {
sort: Some(Sort {
template: vec![SortSpec { key: SortKey::Author, ascending: true }],
..Default::default()
}),
..Default::default()
};
style.options = Some(Config {
processing: Some(Processing::Custom(config)),
..Default::default()
});

let ref_a = mock_reference("a", "Zzz", "2020");
let ref_b = mock_reference("b", "Aaa", "2021");

let processor = Processor { style, ..Default::default() };

let sorted = processor.sort_references(vec![&ref_a, &ref_b]);
assert_eq!(sorted[0].id().unwrap(), "b");
assert_eq!(sorted[1].id().unwrap(), "a");
}

#[test]
fn test_calculate_proc_hints() {
// Two refs with same author and year should trigger disambiguation
let ref_a = mock_reference("a", "Smith", "2020");
let ref_b = mock_reference("b", "Smith", "2020");

let mut bib = Bibliography::new();
bib.insert("a".to_string(), ref_a);
bib.insert("b".to_string(), ref_b);

let processor = Processor::new(
Style::default(),
bib,
Citations::default(),
Locale::default(),
);
let hints = processor.get_proc_hints();

let hint_a = hints.get("a").unwrap();
let hint_b = hints.get("b").unwrap();

assert!(hint_a.disamb_condition);
assert!(hint_b.disamb_condition);
assert_ne!(hint_a.group_index, hint_b.group_index);
}

#[test]
fn test_error_handling() {
let processor = Processor::default();
let reference = mock_reference("ref1", "Smith", "2020");
let key = processor.make_group_key(&reference);
// Default group key produces "First Last:Year" format or similar depending on implementation
// The failure shows "Given Smith:2020"
assert_eq!(key, "Given Smith:2020");
let result = processor.get_reference("nonexistent");
assert!(result.is_err());
match result {
Err(ProcessorError::ReferenceNotFound(id)) => assert_eq!(id, "nonexistent"),
_ => panic!("Expected ReferenceNotFound error"),
}
}
}
36 changes: 36 additions & 0 deletions processor/src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,39 @@ fn render_proc_template_component() {
);
assert_eq!(proc_template_component.to_string(), "(doi: 10/1234 ||)".to_string());
}

#[test]
fn test_refs_to_string() {
use crate::types::{ProcTemplateComponent, ProcValues};
use csln::style::template::{TemplateComponent, TemplateSimpleString, Variables};

let comp1 = TemplateComponent::SimpleString(TemplateSimpleString {
variable: Variables::Doi,
rendering: None,
});
let proc1 = ProcTemplateComponent::new(
comp1,
ProcValues {
value: "10.1".to_string(),
prefix: None,
suffix: None,
},
);

let comp2 = TemplateComponent::SimpleString(TemplateSimpleString {
variable: Variables::Isbn,
rendering: None,
});
let proc2 = ProcTemplateComponent::new(
comp2,
ProcValues {
value: "1234".to_string(),
prefix: None,
suffix: None,
},
);

let template = vec![proc1, proc2];
let output = refs_to_string(vec![template]);
assert_eq!(output, "10.1. 1234.");
}
Loading