feat: normalize review bundle into review document
This commit is contained in:
Generated
+122
@@ -11,6 +11,15 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "1.0.0"
|
||||
@@ -98,6 +107,12 @@ version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
@@ -155,6 +170,20 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.6.0"
|
||||
@@ -210,6 +239,12 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "difflib"
|
||||
version = "0.4.0"
|
||||
@@ -371,6 +406,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_cmd",
|
||||
"chrono",
|
||||
"clap",
|
||||
"mockito",
|
||||
"reqwest",
|
||||
@@ -527,6 +563,30 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "2.2.0"
|
||||
@@ -775,6 +835,15 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
@@ -1693,12 +1762,65 @@ dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
|
||||
@@ -6,6 +6,7 @@ edition = "2024"
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
@@ -6,6 +6,7 @@ pub mod cli;
|
||||
pub mod error;
|
||||
pub mod gitea;
|
||||
pub mod model;
|
||||
pub mod normalize;
|
||||
pub mod output;
|
||||
pub mod render;
|
||||
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
|
||||
use crate::gitea::dto::{ChangedFileDto, CommitDto, PullBundleDto, ReviewCommentDto, ReviewDto};
|
||||
use crate::model::{
|
||||
CommentItem, CommentThread, CommitItem, DiffStat, FileStat, PrMeta, PrReviewDocument,
|
||||
ReviewItem,
|
||||
};
|
||||
|
||||
pub fn normalize_bundle(repo: &str, bundle: PullBundleDto) -> PrReviewDocument {
|
||||
let PullBundleDto {
|
||||
pull,
|
||||
reviews,
|
||||
comments,
|
||||
commits,
|
||||
files,
|
||||
} = bundle;
|
||||
|
||||
let mut threads = normalize_threads(comments);
|
||||
threads.sort_by(|a, b| {
|
||||
comment_item_sort_key(&a.root_comment).cmp(&comment_item_sort_key(&b.root_comment))
|
||||
});
|
||||
|
||||
let commits = normalize_commits(commits);
|
||||
let reviews = normalize_reviews(reviews);
|
||||
let diff_stat = normalize_diff_stat(&pull, files);
|
||||
|
||||
PrReviewDocument {
|
||||
meta: PrMeta {
|
||||
repo: repo.to_string(),
|
||||
pr_index: pull.number,
|
||||
title: pull.title,
|
||||
state: pull.state,
|
||||
author: pull.user.login,
|
||||
base_branch: pull.base.ref_name,
|
||||
head_branch: pull.head.ref_name,
|
||||
created_at: pull.created_at,
|
||||
updated_at: pull.updated_at,
|
||||
merged_at: pull.merged_at,
|
||||
},
|
||||
commits,
|
||||
diff_stat,
|
||||
reviews,
|
||||
threads,
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_threads(comments: Vec<ReviewCommentDto>) -> Vec<CommentThread> {
|
||||
let mut grouped: BTreeMap<String, Vec<ReviewCommentDto>> = BTreeMap::new();
|
||||
|
||||
for comment in comments {
|
||||
let key = thread_key(&comment);
|
||||
grouped.entry(key).or_default().push(comment);
|
||||
}
|
||||
|
||||
grouped
|
||||
.into_iter()
|
||||
.map(|(thread_id, mut group)| {
|
||||
group.sort_by(|a, b| comment_sort_key(a).cmp(&comment_sort_key(b)));
|
||||
|
||||
let root = group.remove(0);
|
||||
let file_path = root
|
||||
.path
|
||||
.clone()
|
||||
.or_else(|| group.iter().find_map(|comment| comment.path.clone()));
|
||||
let line = root
|
||||
.line
|
||||
.or_else(|| group.iter().find_map(|comment| comment.line));
|
||||
|
||||
CommentThread {
|
||||
thread_id,
|
||||
file_path,
|
||||
line,
|
||||
root_comment: to_comment_item(root),
|
||||
replies: group.into_iter().map(to_comment_item).collect(),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn thread_key(comment: &ReviewCommentDto) -> String {
|
||||
match comment.pull_request_review_id {
|
||||
Some(review_id) => format!(
|
||||
"review-{review_id}-{path}-{line}-{position}-{commit}",
|
||||
path = comment.path.as_deref().unwrap_or(""),
|
||||
line = comment
|
||||
.line
|
||||
.map(|value| value.to_string())
|
||||
.unwrap_or_default(),
|
||||
position = comment
|
||||
.original_position
|
||||
.or(comment.position)
|
||||
.map(|value| value.to_string())
|
||||
.unwrap_or_default(),
|
||||
commit = comment
|
||||
.original_commit_id
|
||||
.as_deref()
|
||||
.or(comment.commit_id.as_deref())
|
||||
.unwrap_or("")
|
||||
),
|
||||
None => format!("comment-{}", comment.id),
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_commits(commits: Vec<CommitDto>) -> Vec<CommitItem> {
|
||||
let mut commits = commits;
|
||||
commits.sort_by(|a, b| {
|
||||
commit_sort_key(a)
|
||||
.cmp(&commit_sort_key(b))
|
||||
.then_with(|| a.sha.cmp(&b.sha))
|
||||
});
|
||||
|
||||
commits.into_iter().map(to_commit_item).collect()
|
||||
}
|
||||
|
||||
fn normalize_reviews(reviews: Vec<ReviewDto>) -> Vec<ReviewItem> {
|
||||
let mut reviews = reviews;
|
||||
reviews.sort_by(|a, b| {
|
||||
review_sort_key(a)
|
||||
.cmp(&review_sort_key(b))
|
||||
.then_with(|| a.id.cmp(&b.id))
|
||||
});
|
||||
|
||||
reviews
|
||||
.into_iter()
|
||||
.map(|review| ReviewItem {
|
||||
id: review.id,
|
||||
state: review.state,
|
||||
reviewer: review.user.login,
|
||||
submitted_at: review.submitted_at,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn normalize_diff_stat(pull: &crate::gitea::dto::PullDto, files: Vec<ChangedFileDto>) -> DiffStat {
|
||||
let file_stats: Vec<FileStat> = files
|
||||
.into_iter()
|
||||
.map(|file| FileStat {
|
||||
path: file.filename,
|
||||
additions: file.additions,
|
||||
deletions: file.deletions,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let files_changed = if file_stats.is_empty() {
|
||||
pull.changed_files
|
||||
.and_then(|value| usize::try_from(value).ok())
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
file_stats.len()
|
||||
};
|
||||
|
||||
let additions: i64 = if file_stats.is_empty() {
|
||||
pull.additions.unwrap_or_default()
|
||||
} else {
|
||||
file_stats.iter().map(|file| file.additions).sum()
|
||||
};
|
||||
let deletions: i64 = if file_stats.is_empty() {
|
||||
pull.deletions.unwrap_or_default()
|
||||
} else {
|
||||
file_stats.iter().map(|file| file.deletions).sum()
|
||||
};
|
||||
|
||||
DiffStat {
|
||||
files_changed,
|
||||
additions,
|
||||
deletions,
|
||||
files: file_stats,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_commit_item(commit: CommitDto) -> CommitItem {
|
||||
let title = commit
|
||||
.commit
|
||||
.message
|
||||
.lines()
|
||||
.next()
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
let short_sha = commit.sha.chars().take(7).collect::<String>();
|
||||
let author = commit
|
||||
.commit
|
||||
.author
|
||||
.as_ref()
|
||||
.map(|author| author.name.clone())
|
||||
.unwrap_or_default();
|
||||
let date = commit
|
||||
.commit
|
||||
.author
|
||||
.as_ref()
|
||||
.map(|author| author.date.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
CommitItem {
|
||||
sha: commit.sha,
|
||||
short_sha,
|
||||
title,
|
||||
author,
|
||||
date,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_comment_item(comment: ReviewCommentDto) -> CommentItem {
|
||||
CommentItem {
|
||||
id: comment.id,
|
||||
user: comment.user.login,
|
||||
created_at: comment.created_at,
|
||||
body: comment.body,
|
||||
}
|
||||
}
|
||||
|
||||
fn review_sort_key(review: &ReviewDto) -> (i64, i64) {
|
||||
(timestamp_millis(review.submitted_at.as_deref()), review.id)
|
||||
}
|
||||
|
||||
fn commit_sort_key(commit: &CommitDto) -> (i64, String) {
|
||||
(
|
||||
timestamp_millis(Some(
|
||||
commit
|
||||
.commit
|
||||
.author
|
||||
.as_ref()
|
||||
.map(|author| author.date.as_str())
|
||||
.unwrap_or_default(),
|
||||
)),
|
||||
commit.sha.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
fn comment_sort_key(comment: &ReviewCommentDto) -> (i64, i64) {
|
||||
(
|
||||
timestamp_millis(Some(comment.created_at.as_str())),
|
||||
comment.id,
|
||||
)
|
||||
}
|
||||
|
||||
fn comment_item_sort_key(comment: &CommentItem) -> (i64, i64) {
|
||||
(
|
||||
timestamp_millis(Some(comment.created_at.as_str())),
|
||||
comment.id,
|
||||
)
|
||||
}
|
||||
|
||||
fn timestamp_millis(input: Option<&str>) -> i64 {
|
||||
input
|
||||
.and_then(|value| DateTime::parse_from_rfc3339(value).ok())
|
||||
.map(|dt: DateTime<FixedOffset>| dt.timestamp_millis())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
use gitea_pr_review::gitea::dto::*;
|
||||
use gitea_pr_review::normalize::normalize_bundle;
|
||||
|
||||
#[test]
|
||||
fn normalize_groups_replies_and_sorts_threads_by_time() {
|
||||
let bundle = PullBundleDto {
|
||||
pull: PullDto {
|
||||
number: 42,
|
||||
title: "Fix parser".into(),
|
||||
state: "open".into(),
|
||||
body: Some("desc".into()),
|
||||
user: UserDto {
|
||||
login: "alice".into(),
|
||||
},
|
||||
base: PullBranchDto {
|
||||
ref_name: "main".into(),
|
||||
},
|
||||
head: PullBranchDto {
|
||||
ref_name: "feature/x".into(),
|
||||
},
|
||||
created_at: "2026-04-08T10:00:00Z".into(),
|
||||
updated_at: "2026-04-08T11:00:00Z".into(),
|
||||
merged_at: None,
|
||||
additions: Some(99),
|
||||
deletions: Some(33),
|
||||
changed_files: Some(7),
|
||||
},
|
||||
reviews: vec![
|
||||
ReviewDto {
|
||||
id: 8,
|
||||
state: "APPROVED".into(),
|
||||
user: UserDto {
|
||||
login: "carol".into(),
|
||||
},
|
||||
submitted_at: Some("2026-04-08T13:00:00Z".into()),
|
||||
},
|
||||
ReviewDto {
|
||||
id: 7,
|
||||
state: "COMMENT".into(),
|
||||
user: UserDto {
|
||||
login: "bob".into(),
|
||||
},
|
||||
submitted_at: Some("2026-04-08T12:00:00Z".into()),
|
||||
},
|
||||
],
|
||||
comments: vec![
|
||||
ReviewCommentDto {
|
||||
id: 2,
|
||||
body: "reply".into(),
|
||||
created_at: "2026-04-08T12:02:00Z".into(),
|
||||
updated_at: Some("2026-04-08T12:03:00Z".into()),
|
||||
user: UserDto {
|
||||
login: "bob".into(),
|
||||
},
|
||||
path: Some("src/main.rs".into()),
|
||||
line: Some(10),
|
||||
pull_request_review_id: Some(7),
|
||||
original_position: Some(10),
|
||||
position: Some(10),
|
||||
commit_id: Some("abc123".into()),
|
||||
original_commit_id: Some("abc123".into()),
|
||||
diff_hunk: Some("@@ -1 +1 @@".into()),
|
||||
},
|
||||
ReviewCommentDto {
|
||||
id: 1,
|
||||
body: "root".into(),
|
||||
created_at: "2026-04-08T12:01:00Z".into(),
|
||||
updated_at: Some("2026-04-08T12:02:00Z".into()),
|
||||
user: UserDto {
|
||||
login: "bob".into(),
|
||||
},
|
||||
path: Some("src/main.rs".into()),
|
||||
line: Some(10),
|
||||
pull_request_review_id: Some(7),
|
||||
original_position: Some(10),
|
||||
position: Some(10),
|
||||
commit_id: Some("abc123".into()),
|
||||
original_commit_id: Some("abc123".into()),
|
||||
diff_hunk: Some("@@ -1 +1 @@".into()),
|
||||
},
|
||||
ReviewCommentDto {
|
||||
id: 3,
|
||||
body: "other thread".into(),
|
||||
created_at: "2026-04-08T11:59:00Z".into(),
|
||||
updated_at: None,
|
||||
user: UserDto {
|
||||
login: "dave".into(),
|
||||
},
|
||||
path: Some("src/lib.rs".into()),
|
||||
line: Some(22),
|
||||
pull_request_review_id: Some(8),
|
||||
original_position: Some(22),
|
||||
position: Some(22),
|
||||
commit_id: Some("def456".into()),
|
||||
original_commit_id: Some("def456".into()),
|
||||
diff_hunk: Some("@@ -2 +2 @@".into()),
|
||||
},
|
||||
],
|
||||
commits: vec![
|
||||
CommitDto {
|
||||
sha: "bbbbbbbccccccc".into(),
|
||||
commit: RepoCommitDto {
|
||||
message: "fix: later commit\n\nbody".into(),
|
||||
author: Some(CommitUserDto {
|
||||
name: "Bob".into(),
|
||||
date: "2026-04-08T12:30:00Z".into(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
CommitDto {
|
||||
sha: "aaaaaaabbbbbbb".into(),
|
||||
commit: RepoCommitDto {
|
||||
message: "feat: earlier commit".into(),
|
||||
author: Some(CommitUserDto {
|
||||
name: "Alice".into(),
|
||||
date: "2026-04-08T11:30:00Z".into(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
],
|
||||
files: vec![
|
||||
ChangedFileDto {
|
||||
filename: "src/main.rs".into(),
|
||||
additions: 5,
|
||||
deletions: 1,
|
||||
changes: Some(6),
|
||||
status: Some("modified".into()),
|
||||
previous_filename: None,
|
||||
},
|
||||
ChangedFileDto {
|
||||
filename: "src/lib.rs".into(),
|
||||
additions: 3,
|
||||
deletions: 2,
|
||||
changes: Some(5),
|
||||
status: Some("modified".into()),
|
||||
previous_filename: None,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
let doc = normalize_bundle("org/repo", bundle);
|
||||
|
||||
assert_eq!(doc.meta.repo, "org/repo");
|
||||
assert_eq!(doc.meta.pr_index, 42);
|
||||
assert_eq!(doc.meta.base_branch, "main");
|
||||
assert_eq!(doc.meta.head_branch, "feature/x");
|
||||
|
||||
assert_eq!(doc.commits.len(), 2);
|
||||
assert_eq!(doc.commits[0].title, "feat: earlier commit");
|
||||
assert_eq!(doc.commits[0].author, "Alice");
|
||||
assert_eq!(doc.commits[1].title, "fix: later commit");
|
||||
|
||||
assert_eq!(doc.reviews.len(), 2);
|
||||
assert_eq!(doc.reviews[0].id, 7);
|
||||
assert_eq!(doc.reviews[1].id, 8);
|
||||
|
||||
assert_eq!(doc.diff_stat.files_changed, 2);
|
||||
assert_eq!(doc.diff_stat.additions, 8);
|
||||
assert_eq!(doc.diff_stat.deletions, 3);
|
||||
assert_eq!(doc.diff_stat.files[0].path, "src/main.rs");
|
||||
assert_eq!(doc.diff_stat.files[1].path, "src/lib.rs");
|
||||
|
||||
assert_eq!(doc.threads.len(), 2);
|
||||
assert_eq!(doc.threads[0].root_comment.id, 3);
|
||||
assert_eq!(doc.threads[0].file_path.as_deref(), Some("src/lib.rs"));
|
||||
assert_eq!(doc.threads[0].line, Some(22));
|
||||
assert_eq!(doc.threads[1].root_comment.id, 1);
|
||||
assert_eq!(doc.threads[1].replies.len(), 1);
|
||||
assert_eq!(doc.threads[1].replies[0].id, 2);
|
||||
assert_eq!(doc.threads[1].file_path.as_deref(), Some("src/main.rs"));
|
||||
assert_eq!(doc.threads[1].line, Some(10));
|
||||
}
|
||||
Reference in New Issue
Block a user