1use std::{
2 cmp::Ordering,
3 collections::HashSet,
4 fmt::{Debug, Display},
5 path::Path,
6};
7
8use anyhow::Result;
9use colored::Colorize;
10
11use crate::{config::Config, package::Package, update_type::UpdateType, workspace::Workspace};
12
13#[derive(Debug)]
19pub enum Project {
20 Workspace(Box<dyn Workspace>),
22 Package(Box<dyn Package>),
24}
25
26impl Project {
27 #[must_use]
28 pub fn name(&self) -> Option<&str> {
29 match self {
30 Self::Workspace(workspace) => workspace.name(),
31 Self::Package(package) => package.name(),
32 }
33 }
34
35 #[must_use]
36 pub fn version(&self) -> Option<&str> {
37 match self {
38 Self::Workspace(workspace) => workspace.version(),
39 Self::Package(package) => package.version(),
40 }
41 }
42 #[must_use]
43 pub fn path(&self) -> &Path {
44 match self {
45 Self::Workspace(workspace) => workspace.path(),
46 Self::Package(package) => package.path(),
47 }
48 }
49
50 #[must_use]
51 pub fn relative_path(&self) -> &Path {
52 match self {
53 Self::Workspace(workspace) => workspace.relative_path(),
54 Self::Package(package) => package.relative_path(),
55 }
56 }
57
58 pub async fn update_version(&mut self, update_type: UpdateType) -> Result<()> {
61 match self {
62 Self::Workspace(workspace) => workspace.update_version(update_type).await?,
63 Self::Package(package) => package.update_version(update_type).await?,
64 }
65 Ok(())
66 }
67
68 pub fn check_changed(&mut self, path: &Path) -> Result<()> {
71 match self {
72 Self::Workspace(workspace) => workspace.check_changed(path)?,
73 Self::Package(package) => package.check_changed(path)?,
74 }
75 Ok(())
76 }
77
78 #[must_use]
79 pub fn is_changed(&self) -> bool {
80 match self {
81 Self::Workspace(workspace) => workspace.is_changed(),
82 Self::Package(package) => package.is_changed(),
83 }
84 }
85
86 #[must_use]
87 pub fn dependencies(&self) -> &HashSet<String> {
88 match self {
89 Self::Workspace(workspace) => workspace.dependencies(),
90 Self::Package(package) => package.dependencies(),
91 }
92 }
93
94 pub fn add_dependency(&mut self, dependency: &str) {
95 match self {
96 Self::Workspace(workspace) => workspace.add_dependency(dependency),
97 Self::Package(package) => package.add_dependency(dependency),
98 }
99 }
100
101 pub fn set_name(&mut self, name: String) {
102 match self {
103 Self::Workspace(workspace) => workspace.set_name(name),
104 Self::Package(package) => package.set_name(name),
105 }
106 }
107
108 #[must_use]
109 pub fn language(&self) -> crate::Language {
110 match self {
111 Self::Workspace(workspace) => workspace.language(),
112 Self::Package(package) => package.language(),
113 }
114 }
115
116 pub async fn publish(&self, config: &Config) -> Result<()> {
119 match self {
120 Self::Workspace(workspace) => workspace.publish(config).await,
121 Self::Package(package) => package.publish(config).await,
122 }
123 }
124}
125
126impl PartialEq for Project {
127 fn eq(&self, other: &Self) -> bool {
128 self.cmp(other) == Ordering::Equal
129 }
130}
131
132impl Eq for Project {}
133
134impl PartialOrd for Project {
135 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
136 Some(self.cmp(other))
137 }
138}
139
140impl Ord for Project {
141 fn cmp(&self, other: &Self) -> Ordering {
142 match (self, other) {
143 (Self::Workspace(_), Self::Package(_)) => Ordering::Less,
144 (Self::Package(_), Self::Workspace(_)) => Ordering::Greater,
145 (Self::Workspace(w1), Self::Workspace(w2)) => {
146 let lang_ord = w1.language().cmp(&w2.language());
147 if lang_ord != Ordering::Equal {
148 return lang_ord;
149 }
150
151 let name1 = w1.name();
152 let name2 = w2.name();
153
154 match (name1, name2) {
155 (Some(n1), Some(n2)) => n1.cmp(n2),
156 (Some(_), None) => Ordering::Less,
157 (None, Some(_)) => Ordering::Greater,
158 (None, None) => {
159 let v1 = w1.version().unwrap_or("");
160 let v2 = w2.version().unwrap_or("");
161 v1.cmp(v2)
162 }
163 }
164 }
165 (Self::Package(p1), Self::Package(p2)) => {
166 let lang_ord = p1.language().cmp(&p2.language());
167 if lang_ord != Ordering::Equal {
168 return lang_ord;
169 }
170 match (p1.name(), p2.name()) {
171 (Some(n1), Some(n2)) => n1.cmp(n2),
172 (Some(_), None) => Ordering::Less,
173 (None, Some(_)) => Ordering::Greater,
174 (None, None) => Ordering::Equal,
175 }
176 }
177 }
178 }
179}
180
181impl Display for Project {
182 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183 match self {
184 Self::Workspace(workspace) => {
185 write!(
186 f,
187 "{} {} {} {} {}",
188 format!("[Workspace - {}]", workspace.language())
189 .bright_blue()
190 .bold(),
191 workspace.name().unwrap_or("noname").bright_white().bold(),
192 format!(
193 "({})",
194 workspace
195 .version()
196 .map_or("unknown".to_string(), |v| format!("v{v}")),
197 )
198 .bright_green(),
199 "-".bright_cyan(),
200 workspace
201 .relative_path()
202 .display()
203 .to_string()
204 .bright_black()
205 )
206 }
207 Self::Package(package) => {
208 write!(
209 f,
210 "{} {} {} {} {}",
211 format!("[{}]", package.language()).bright_blue().bold(),
212 package.name().unwrap_or("noname").bright_white().bold(),
213 format!(
214 "({})",
215 package
216 .version()
217 .map_or("unknown".to_string(), |v| format!("v{v}"))
218 )
219 .bright_green(),
220 "-".bright_cyan(),
221 package.relative_path().display().to_string().bright_black()
222 )
223 }
224 }
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231 use crate::Language;
232 use async_trait::async_trait;
233 use std::path::PathBuf;
234
235 #[derive(Debug)]
236 struct MockWorkspace {
237 name: Option<String>,
238 version: Option<String>,
239 path: PathBuf,
240 relative_path: PathBuf,
241 language: Language,
242 dependencies: HashSet<String>,
243 changed: bool,
244 }
245
246 impl MockWorkspace {
247 fn new(name: Option<&str>, version: Option<&str>, language: Language) -> Self {
248 Self {
249 name: name.map(String::from),
250 version: version.map(String::from),
251 path: PathBuf::from("/test/package.json"),
252 relative_path: PathBuf::from("package.json"),
253 language,
254 dependencies: HashSet::new(),
255 changed: false,
256 }
257 }
258 }
259
260 #[async_trait]
261 impl Workspace for MockWorkspace {
262 fn name(&self) -> Option<&str> {
263 self.name.as_deref()
264 }
265 fn path(&self) -> &Path {
266 &self.path
267 }
268 fn relative_path(&self) -> &Path {
269 &self.relative_path
270 }
271 fn version(&self) -> Option<&str> {
272 self.version.as_deref()
273 }
274 async fn update_version(&mut self, _update_type: UpdateType) -> Result<()> {
275 Ok(())
276 }
277 fn language(&self) -> Language {
278 self.language
279 }
280 fn dependencies(&self) -> &HashSet<String> {
281 &self.dependencies
282 }
283 fn add_dependency(&mut self, dependency: &str) {
284 self.dependencies.insert(dependency.to_string());
285 }
286 fn is_changed(&self) -> bool {
287 self.changed
288 }
289 fn set_changed(&mut self, changed: bool) {
290 self.changed = changed;
291 }
292 fn default_publish_command(&self) -> String {
293 "echo publish".to_string()
294 }
295 }
296
297 #[derive(Debug)]
298 struct MockPackage {
299 name: Option<String>,
300 version: Option<String>,
301 path: PathBuf,
302 relative_path: PathBuf,
303 language: Language,
304 dependencies: HashSet<String>,
305 changed: bool,
306 }
307
308 impl MockPackage {
309 fn new(name: Option<&str>, version: Option<&str>, language: Language) -> Self {
310 Self {
311 name: name.map(String::from),
312 version: version.map(String::from),
313 path: PathBuf::from("/test/Cargo.toml"),
314 relative_path: PathBuf::from("Cargo.toml"),
315 language,
316 dependencies: HashSet::new(),
317 changed: false,
318 }
319 }
320 }
321
322 #[async_trait]
323 impl Package for MockPackage {
324 fn name(&self) -> Option<&str> {
325 self.name.as_deref()
326 }
327 fn path(&self) -> &Path {
328 &self.path
329 }
330 fn relative_path(&self) -> &Path {
331 &self.relative_path
332 }
333 fn version(&self) -> Option<&str> {
334 self.version.as_deref()
335 }
336 async fn update_version(&mut self, _update_type: UpdateType) -> Result<()> {
337 Ok(())
338 }
339 fn language(&self) -> Language {
340 self.language
341 }
342 fn dependencies(&self) -> &HashSet<String> {
343 &self.dependencies
344 }
345 fn add_dependency(&mut self, dependency: &str) {
346 self.dependencies.insert(dependency.to_string());
347 }
348 fn is_changed(&self) -> bool {
349 self.changed
350 }
351 fn set_changed(&mut self, changed: bool) {
352 self.changed = changed;
353 }
354 fn default_publish_command(&self) -> String {
355 "echo publish".to_string()
356 }
357 }
358
359 #[test]
360 fn test_project_workspace_name() {
361 let workspace = MockWorkspace::new(Some("test-ws"), Some("1.0.0"), Language::Node);
362 let project = Project::Workspace(Box::new(workspace));
363 assert_eq!(project.name(), Some("test-ws"));
364 }
365
366 #[test]
367 fn test_project_package_name() {
368 let package = MockPackage::new(Some("test-pkg"), Some("1.0.0"), Language::Rust);
369 let project = Project::Package(Box::new(package));
370 assert_eq!(project.name(), Some("test-pkg"));
371 }
372
373 #[test]
374 fn test_project_workspace_version() {
375 let workspace = MockWorkspace::new(Some("test"), Some("2.0.0"), Language::Node);
376 let project = Project::Workspace(Box::new(workspace));
377 assert_eq!(project.version(), Some("2.0.0"));
378 }
379
380 #[test]
381 fn test_project_package_version() {
382 let package = MockPackage::new(Some("test"), Some("3.0.0"), Language::Rust);
383 let project = Project::Package(Box::new(package));
384 assert_eq!(project.version(), Some("3.0.0"));
385 }
386
387 #[test]
388 fn test_project_workspace_path() {
389 let workspace = MockWorkspace::new(Some("test"), Some("1.0.0"), Language::Node);
390 let project = Project::Workspace(Box::new(workspace));
391 assert_eq!(project.path(), Path::new("/test/package.json"));
392 }
393
394 #[test]
395 fn test_project_package_path() {
396 let package = MockPackage::new(Some("test"), Some("1.0.0"), Language::Rust);
397 let project = Project::Package(Box::new(package));
398 assert_eq!(project.path(), Path::new("/test/Cargo.toml"));
399 }
400
401 #[test]
402 fn test_project_workspace_relative_path() {
403 let workspace = MockWorkspace::new(Some("test"), Some("1.0.0"), Language::Node);
404 let project = Project::Workspace(Box::new(workspace));
405 assert_eq!(project.relative_path(), Path::new("package.json"));
406 }
407
408 #[test]
409 fn test_project_package_relative_path() {
410 let package = MockPackage::new(Some("test"), Some("1.0.0"), Language::Rust);
411 let project = Project::Package(Box::new(package));
412 assert_eq!(project.relative_path(), Path::new("Cargo.toml"));
413 }
414
415 #[tokio::test]
416 async fn test_project_workspace_update_version() {
417 let workspace = MockWorkspace::new(Some("test"), Some("1.0.0"), Language::Node);
418 let mut project = Project::Workspace(Box::new(workspace));
419 let result = project.update_version(UpdateType::Minor).await;
420 assert!(result.is_ok());
421 }
422
423 #[tokio::test]
424 async fn test_project_package_update_version() {
425 let package = MockPackage::new(Some("test"), Some("1.0.0"), Language::Rust);
426 let mut project = Project::Package(Box::new(package));
427 let result = project.update_version(UpdateType::Patch).await;
428 assert!(result.is_ok());
429 }
430
431 #[test]
432 fn test_project_workspace_check_changed() {
433 let workspace = MockWorkspace::new(Some("test"), Some("1.0.0"), Language::Node);
434 let mut project = Project::Workspace(Box::new(workspace));
435 let result = project.check_changed(Path::new("/test/src/index.js"));
436 assert!(result.is_ok());
437 }
438
439 #[test]
440 fn test_project_package_check_changed() {
441 let package = MockPackage::new(Some("test"), Some("1.0.0"), Language::Rust);
442 let mut project = Project::Package(Box::new(package));
443 let result = project.check_changed(Path::new("/test/src/main.rs"));
444 assert!(result.is_ok());
445 }
446
447 #[test]
448 fn test_project_workspace_is_changed() {
449 let mut workspace = MockWorkspace::new(Some("test"), Some("1.0.0"), Language::Node);
450 workspace.changed = true;
451 let project = Project::Workspace(Box::new(workspace));
452 assert!(project.is_changed());
453 }
454
455 #[test]
456 fn test_project_package_is_changed() {
457 let mut package = MockPackage::new(Some("test"), Some("1.0.0"), Language::Rust);
458 package.changed = true;
459 let project = Project::Package(Box::new(package));
460 assert!(project.is_changed());
461 }
462
463 #[test]
464 fn test_project_workspace_dependencies() {
465 let mut workspace = MockWorkspace::new(Some("test"), Some("1.0.0"), Language::Node);
466 workspace.dependencies.insert("dep1".to_string());
467 let project = Project::Workspace(Box::new(workspace));
468 assert!(project.dependencies().contains("dep1"));
469 }
470
471 #[test]
472 fn test_project_package_dependencies() {
473 let mut package = MockPackage::new(Some("test"), Some("1.0.0"), Language::Rust);
474 package.dependencies.insert("dep2".to_string());
475 let project = Project::Package(Box::new(package));
476 assert!(project.dependencies().contains("dep2"));
477 }
478
479 #[test]
480 fn test_project_workspace_add_dependency() {
481 let workspace = MockWorkspace::new(Some("test"), Some("1.0.0"), Language::Node);
482 let mut project = Project::Workspace(Box::new(workspace));
483 project.add_dependency("new-dep");
484 assert!(project.dependencies().contains("new-dep"));
485 }
486
487 #[test]
488 fn test_project_package_add_dependency() {
489 let package = MockPackage::new(Some("test"), Some("1.0.0"), Language::Rust);
490 let mut project = Project::Package(Box::new(package));
491 project.add_dependency("new-dep");
492 assert!(project.dependencies().contains("new-dep"));
493 }
494
495 #[test]
496 fn test_project_workspace_language() {
497 let workspace = MockWorkspace::new(Some("test"), Some("1.0.0"), Language::Python);
498 let project = Project::Workspace(Box::new(workspace));
499 assert!(matches!(project.language(), Language::Python));
500 }
501
502 #[test]
503 fn test_project_package_language() {
504 let package = MockPackage::new(Some("test"), Some("1.0.0"), Language::Dart);
505 let project = Project::Package(Box::new(package));
506 assert!(matches!(project.language(), Language::Dart));
507 }
508
509 #[tokio::test]
510 async fn test_project_workspace_publish() {
511 let temp_dir = std::env::temp_dir();
512 let mut workspace = MockWorkspace::new(Some("test"), Some("1.0.0"), Language::Node);
513 workspace.path = temp_dir.join("package.json");
514 let project = Project::Workspace(Box::new(workspace));
515 let config = Config::default();
516 let result = project.publish(&config).await;
517 assert!(result.is_ok());
518 }
519
520 #[tokio::test]
521 async fn test_project_package_publish() {
522 let temp_dir = std::env::temp_dir();
523 let mut package = MockPackage::new(Some("test"), Some("1.0.0"), Language::Rust);
524 package.path = temp_dir.join("Cargo.toml");
525 let project = Project::Package(Box::new(package));
526 let config = Config::default();
527 let result = project.publish(&config).await;
528 assert!(result.is_ok());
529 }
530
531 #[test]
532 fn test_project_eq_same_workspace() {
533 let w1 = MockWorkspace::new(Some("test"), Some("1.0.0"), Language::Node);
534 let w2 = MockWorkspace::new(Some("test"), Some("1.0.0"), Language::Node);
535 let p1 = Project::Workspace(Box::new(w1));
536 let p2 = Project::Workspace(Box::new(w2));
537 assert_eq!(p1, p2);
538 }
539
540 #[test]
541 fn test_project_partial_ord() {
542 let w1 = MockWorkspace::new(Some("a"), Some("1.0.0"), Language::Node);
543 let w2 = MockWorkspace::new(Some("b"), Some("1.0.0"), Language::Node);
544 let p1 = Project::Workspace(Box::new(w1));
545 let p2 = Project::Workspace(Box::new(w2));
546 assert!(p1.partial_cmp(&p2).is_some());
547 }
548
549 #[test]
550 fn test_project_ord_workspace_before_package() {
551 let workspace = MockWorkspace::new(Some("test"), Some("1.0.0"), Language::Node);
552 let package = MockPackage::new(Some("test"), Some("1.0.0"), Language::Rust);
553 let p1 = Project::Workspace(Box::new(workspace));
554 let p2 = Project::Package(Box::new(package));
555 assert!(p1 < p2);
556 }
557
558 #[test]
559 fn test_project_ord_package_after_workspace() {
560 let workspace = MockWorkspace::new(Some("test"), Some("1.0.0"), Language::Node);
561 let package = MockPackage::new(Some("test"), Some("1.0.0"), Language::Rust);
562 let p1 = Project::Package(Box::new(package));
563 let p2 = Project::Workspace(Box::new(workspace));
564 assert!(p1 > p2);
565 }
566
567 #[test]
568 fn test_project_ord_workspaces_by_language() {
569 let w1 = MockWorkspace::new(Some("test"), Some("1.0.0"), Language::Node);
570 let w2 = MockWorkspace::new(Some("test"), Some("1.0.0"), Language::Python);
571 let p1 = Project::Workspace(Box::new(w1));
572 let p2 = Project::Workspace(Box::new(w2));
573 assert_ne!(p1.cmp(&p2), Ordering::Equal);
574 }
575
576 #[test]
577 fn test_project_ord_workspaces_by_name() {
578 let w1 = MockWorkspace::new(Some("aaa"), Some("1.0.0"), Language::Node);
579 let w2 = MockWorkspace::new(Some("bbb"), Some("1.0.0"), Language::Node);
580 let p1 = Project::Workspace(Box::new(w1));
581 let p2 = Project::Workspace(Box::new(w2));
582 assert!(p1 < p2);
583 }
584
585 #[test]
586 fn test_project_ord_workspaces_name_some_vs_none() {
587 let w1 = MockWorkspace::new(Some("test"), Some("1.0.0"), Language::Node);
588 let w2 = MockWorkspace::new(None, Some("1.0.0"), Language::Node);
589 let p1 = Project::Workspace(Box::new(w1));
590 let p2 = Project::Workspace(Box::new(w2));
591 assert!(p1 < p2);
592 }
593
594 #[test]
595 fn test_project_ord_workspaces_name_none_vs_some() {
596 let w1 = MockWorkspace::new(None, Some("1.0.0"), Language::Node);
597 let w2 = MockWorkspace::new(Some("test"), Some("1.0.0"), Language::Node);
598 let p1 = Project::Workspace(Box::new(w1));
599 let p2 = Project::Workspace(Box::new(w2));
600 assert!(p1 > p2);
601 }
602
603 #[test]
604 fn test_project_ord_workspaces_both_none_names() {
605 let w1 = MockWorkspace::new(None, Some("1.0.0"), Language::Node);
606 let w2 = MockWorkspace::new(None, Some("2.0.0"), Language::Node);
607 let p1 = Project::Workspace(Box::new(w1));
608 let p2 = Project::Workspace(Box::new(w2));
609 assert!(p1 < p2);
610 }
611
612 #[test]
613 fn test_project_ord_packages_by_language() {
614 let pkg1 = MockPackage::new(Some("test"), Some("1.0.0"), Language::Node);
615 let pkg2 = MockPackage::new(Some("test"), Some("1.0.0"), Language::Rust);
616 let p1 = Project::Package(Box::new(pkg1));
617 let p2 = Project::Package(Box::new(pkg2));
618 assert_ne!(p1.cmp(&p2), Ordering::Equal);
619 }
620
621 #[test]
622 fn test_project_ord_packages_by_name() {
623 let pkg1 = MockPackage::new(Some("aaa"), Some("1.0.0"), Language::Rust);
624 let pkg2 = MockPackage::new(Some("bbb"), Some("1.0.0"), Language::Rust);
625 let p1 = Project::Package(Box::new(pkg1));
626 let p2 = Project::Package(Box::new(pkg2));
627 assert!(p1 < p2);
628 }
629
630 #[test]
631 fn test_project_ord_packages_name_some_vs_none() {
632 let pkg1 = MockPackage::new(Some("test"), Some("1.0.0"), Language::Rust);
633 let pkg2 = MockPackage::new(None, Some("1.0.0"), Language::Rust);
634 let p1 = Project::Package(Box::new(pkg1));
635 let p2 = Project::Package(Box::new(pkg2));
636 assert!(p1 < p2);
637 }
638
639 #[test]
640 fn test_project_ord_packages_name_none_vs_some() {
641 let pkg1 = MockPackage::new(None, Some("1.0.0"), Language::Rust);
642 let pkg2 = MockPackage::new(Some("test"), Some("1.0.0"), Language::Rust);
643 let p1 = Project::Package(Box::new(pkg1));
644 let p2 = Project::Package(Box::new(pkg2));
645 assert!(p1 > p2);
646 }
647
648 #[test]
649 fn test_project_ord_packages_both_none_names() {
650 let pkg1 = MockPackage::new(None, Some("1.0.0"), Language::Rust);
651 let pkg2 = MockPackage::new(None, Some("1.0.0"), Language::Rust);
652 let p1 = Project::Package(Box::new(pkg1));
653 let p2 = Project::Package(Box::new(pkg2));
654 assert_eq!(p1.cmp(&p2), Ordering::Equal);
655 }
656
657 #[test]
658 fn test_project_display_workspace() {
659 let workspace = MockWorkspace::new(Some("my-workspace"), Some("1.0.0"), Language::Node);
660 let project = Project::Workspace(Box::new(workspace));
661 let display = format!("{}", project);
662 assert!(display.contains("Workspace"));
663 assert!(display.contains("my-workspace"));
664 assert!(display.contains("v1.0.0"));
665 }
666
667 #[test]
668 fn test_project_display_workspace_no_name() {
669 let workspace = MockWorkspace::new(None, Some("1.0.0"), Language::Node);
670 let project = Project::Workspace(Box::new(workspace));
671 let display = format!("{}", project);
672 assert!(display.contains("noname"));
673 }
674
675 #[test]
676 fn test_project_display_workspace_no_version() {
677 let workspace = MockWorkspace::new(Some("test"), None, Language::Node);
678 let project = Project::Workspace(Box::new(workspace));
679 let display = format!("{}", project);
680 assert!(display.contains("unknown"));
681 }
682
683 #[test]
684 fn test_project_display_package() {
685 let package = MockPackage::new(Some("my-package"), Some("2.0.0"), Language::Rust);
686 let project = Project::Package(Box::new(package));
687 let display = format!("{}", project);
688 assert!(display.contains("my-package"));
689 assert!(display.contains("v2.0.0"));
690 }
691
692 #[test]
693 fn test_project_display_package_no_name() {
694 let package = MockPackage::new(None, Some("1.0.0"), Language::Rust);
695 let project = Project::Package(Box::new(package));
696 let display = format!("{}", project);
697 assert!(display.contains("noname"));
698 }
699
700 #[test]
701 fn test_project_display_package_no_version() {
702 let package = MockPackage::new(Some("test"), None, Language::Rust);
703 let project = Project::Package(Box::new(package));
704 let display = format!("{}", project);
705 assert!(display.contains("unknown"));
706 }
707
708 #[test]
709 fn test_project_sort_stability() {
710 let make_projects = || {
711 vec![
712 Project::Package(Box::new(MockPackage::new(
713 Some("charlie"),
714 Some("1.0.0"),
715 Language::Rust,
716 ))),
717 Project::Workspace(Box::new(MockWorkspace::new(
718 Some("alpha"),
719 Some("2.0.0"),
720 Language::Node,
721 ))),
722 Project::Package(Box::new(MockPackage::new(
723 Some("bravo"),
724 Some("0.1.0"),
725 Language::Node,
726 ))),
727 Project::Workspace(Box::new(MockWorkspace::new(
728 Some("delta"),
729 Some("3.0.0"),
730 Language::Python,
731 ))),
732 Project::Package(Box::new(MockPackage::new(
733 Some("echo"),
734 Some("1.0.0"),
735 Language::Dart,
736 ))),
737 ]
738 };
739
740 let mut first = make_projects();
741 first.sort();
742 let first_order: Vec<Option<&str>> = first.iter().map(|p| p.name()).collect();
743
744 let mut second = make_projects();
745 second.sort();
746 let second_order: Vec<Option<&str>> = second.iter().map(|p| p.name()).collect();
747
748 let mut third = make_projects();
749 third.sort();
750 let third_order: Vec<Option<&str>> = third.iter().map(|p| p.name()).collect();
751
752 assert_eq!(first_order, second_order);
753 assert_eq!(second_order, third_order);
754 }
755
756 #[test]
757 fn test_project_sort_mixed() {
758 let mut projects = [
759 Project::Package(Box::new(MockPackage::new(
760 Some("pkg-a"),
761 Some("1.0.0"),
762 Language::Node,
763 ))),
764 Project::Workspace(Box::new(MockWorkspace::new(
765 Some("ws-b"),
766 Some("1.0.0"),
767 Language::Node,
768 ))),
769 Project::Package(Box::new(MockPackage::new(
770 Some("pkg-c"),
771 Some("1.0.0"),
772 Language::Rust,
773 ))),
774 Project::Workspace(Box::new(MockWorkspace::new(
775 Some("ws-d"),
776 Some("1.0.0"),
777 Language::Rust,
778 ))),
779 ];
780 projects.sort();
781
782 let workspace_count = projects
784 .iter()
785 .take_while(|p| matches!(p, Project::Workspace(_)))
786 .count();
787 assert_eq!(workspace_count, 2);
788
789 let package_count = projects
790 .iter()
791 .skip(workspace_count)
792 .filter(|p| matches!(p, Project::Package(_)))
793 .count();
794 assert_eq!(package_count, 2);
795 }
796
797 #[test]
798 fn test_project_set_name_workspace() {
799 let workspace = MockWorkspace::new(Some("test"), Some("1.0.0"), Language::Node);
800 let mut project = Project::Workspace(Box::new(workspace));
801 project.set_name("new-name".to_string());
802 assert_eq!(project.name(), Some("test"));
804 }
805
806 #[test]
807 fn test_project_set_name_package() {
808 let package = MockPackage::new(Some("test"), Some("1.0.0"), Language::Rust);
809 let mut project = Project::Package(Box::new(package));
810 project.set_name("new-name".to_string());
811 assert_eq!(project.name(), Some("test"));
813 }
814
815 #[test]
816 fn test_project_cmp_is_consistent_with_eq() {
817 let w1 = MockWorkspace::new(Some("same"), Some("1.0.0"), Language::Node);
819 let w2 = MockWorkspace::new(Some("same"), Some("1.0.0"), Language::Node);
820 let p1 = Project::Workspace(Box::new(w1));
821 let p2 = Project::Workspace(Box::new(w2));
822 assert_eq!(p1, p2);
823 assert_eq!(p1.cmp(&p2), Ordering::Equal);
824
825 let pkg1 = MockPackage::new(Some("same"), Some("1.0.0"), Language::Rust);
827 let pkg2 = MockPackage::new(Some("same"), Some("1.0.0"), Language::Rust);
828 let pp1 = Project::Package(Box::new(pkg1));
829 let pp2 = Project::Package(Box::new(pkg2));
830 assert_eq!(pp1, pp2);
831 assert_eq!(pp1.cmp(&pp2), Ordering::Equal);
832 }
833}