1#![allow(dead_code)]
2mod block;
12mod dnd;
13mod event;
14mod tab;
15mod window;
16mod workspace;
17
18pub use block::*;
20#[allow(unused_imports)]
21pub use event::*;
22pub use tab::*;
23pub use window::*;
24pub use workspace::*;
25
26use uuid::Uuid;
27
28use super::storage::store::Store;
29use super::storage::StoreError;
30use super::obj::*;
31
32pub const LAYOUT_ACTION_INSERT: &str = "insert";
35pub const LAYOUT_ACTION_INSERT_AT_INDEX: &str = "insertatindex";
36pub const LAYOUT_ACTION_REMOVE: &str = "remove";
37pub const LAYOUT_ACTION_CLEAR_TREE: &str = "cleartree";
38pub const LAYOUT_ACTION_REPLACE: &str = "replace";
39pub const LAYOUT_ACTION_SPLIT_HORIZONTAL: &str = "splithorizontal";
40pub const LAYOUT_ACTION_SPLIT_VERTICAL: &str = "splitvertical";
41
42pub fn ensure_initial_data(store: &Store) -> Result<bool, StoreError> {
48 let clients = store.get_all::<Client>()?;
49
50 if !clients.is_empty() {
51 let client = &clients[0];
53 if client.tempoid.is_empty() {
54 let mut client = client.clone();
55 client.tempoid = Uuid::new_v4().to_string();
56 store.update(&mut client)?;
57 }
58 for window_id in &client.windowids {
60 window::check_and_fix_window(store, window_id)?;
61 }
62 return Ok(false);
63 }
64
65 let first_launch = true;
67
68 let mut client = Client {
71 oid: Uuid::new_v4().to_string(),
72 windowids: vec![],
73 tempoid: String::new(),
74 meta: MetaMapType::new(),
75 ..Default::default()
76 };
77
78 store.insert(&mut client)?;
79
80 client.tempoid = Uuid::new_v4().to_string();
82 store.update(&mut client)?;
83
84 let ws = create_workspace(store, "Starter workspace")?;
86
87 let mut win = create_window(store, &ws.oid)?;
89
90 if let Ok(name) = std::env::var("AGENTMUX_WINDOW_NAME") {
97 let name = name.trim();
98 if !name.is_empty() {
99 win.meta.insert(
100 "window:displayname".to_string(),
101 serde_json::json!(name.chars().take(64).collect::<String>()),
102 );
103 store.update(&mut win)?;
104 }
105 }
106
107 client.windowids.push(win.oid.clone());
109 store.update(&mut client)?;
110
111 let tab = create_tab_with_opts(store, &ws.oid, "", true)?;
113
114 seed_default_layout(store, &tab.oid)?;
118
119 Ok(first_launch)
120}
121
122pub(crate) fn seed_default_layout(store: &Store, tab_id: &str) -> Result<(), StoreError> {
154 let tab = store.must_get::<Tab>(tab_id)?;
155
156 let mut agent_meta = MetaMapType::new();
157 agent_meta.insert("view".to_string(), serde_json::json!("agent"));
158 let agent_block = create_block(store, &tab.oid, agent_meta)?;
159
160 let mut swarm_meta = MetaMapType::new();
161 swarm_meta.insert("view".to_string(), serde_json::json!("swarm"));
162 let swarm_block = create_block(store, &tab.oid, swarm_meta)?;
163
164 let mut sysinfo_meta = MetaMapType::new();
165 sysinfo_meta.insert("view".to_string(), serde_json::json!("sysinfo"));
166 let sysinfo_block = create_block(store, &tab.oid, sysinfo_meta)?;
167
168 write_default_three_pane_layout(store, tab_id, &agent_block.oid, &swarm_block.oid, &sysinfo_block.oid)
169}
170
171pub(crate) fn write_default_three_pane_layout(
186 store: &Store,
187 tab_id: &str,
188 agent_block_id: &str,
189 swarm_block_id: &str,
190 sysinfo_block_id: &str,
191) -> Result<(), StoreError> {
192 let tab = store.must_get::<Tab>(tab_id)?;
193 let (rootnode, focused_node_id, leaforder) =
194 default_three_pane_tree(agent_block_id, swarm_block_id, sysinfo_block_id);
195 let mut layout = store.must_get::<LayoutState>(&tab.layoutstate)?;
196 layout.rootnode = Some(rootnode);
197 layout.focusednodeid = focused_node_id;
198 layout.leaforder = Some(leaforder);
199 layout.pendingbackendactions = None;
200 store.update(&mut layout)?;
201 Ok(())
202}
203
204pub(crate) fn default_three_pane_tree(
216 agent_block_id: &str,
217 swarm_block_id: &str,
218 sysinfo_block_id: &str,
219) -> (LayoutNode, String, Vec<LeafOrderEntry>) {
220 let agent_node_id = Uuid::new_v4().to_string();
223 let swarm_node_id = Uuid::new_v4().to_string();
224 let sysinfo_node_id = Uuid::new_v4().to_string();
225 let right_col_id = Uuid::new_v4().to_string();
226 let root_id = Uuid::new_v4().to_string();
227
228 let rootnode = LayoutNode {
230 id: root_id,
231 flex_direction: FlexDirection::Row,
232 size: 10.0,
233 data: None,
234 children: vec![
235 LayoutNode {
236 id: agent_node_id.clone(),
237 flex_direction: FlexDirection::Column,
238 size: 5.0,
239 children: Vec::new(),
240 data: Some(LayoutNodeData {
241 block_id: agent_block_id.to_string(),
242 ..Default::default()
243 }),
244 ..Default::default()
245 },
246 LayoutNode {
247 id: right_col_id,
248 flex_direction: FlexDirection::Column,
249 size: 5.0,
250 children: vec![
251 LayoutNode {
254 id: sysinfo_node_id.clone(),
255 flex_direction: FlexDirection::Row,
256 size: 2.0,
257 children: Vec::new(),
258 data: Some(LayoutNodeData {
259 block_id: sysinfo_block_id.to_string(),
260 ..Default::default()
261 }),
262 ..Default::default()
263 },
264 LayoutNode {
265 id: swarm_node_id.clone(),
266 flex_direction: FlexDirection::Row,
267 size: 8.0,
268 children: Vec::new(),
269 data: Some(LayoutNodeData {
270 block_id: swarm_block_id.to_string(),
271 ..Default::default()
272 }),
273 ..Default::default()
274 },
275 ],
276 data: None,
277 ..Default::default()
278 },
279 ],
280 ..Default::default()
281 };
282
283 let leaforder = vec![
285 LeafOrderEntry { nodeid: agent_node_id.clone(), blockid: agent_block_id.to_string() },
286 LeafOrderEntry { nodeid: sysinfo_node_id, blockid: sysinfo_block_id.to_string() },
287 LeafOrderEntry { nodeid: swarm_node_id, blockid: swarm_block_id.to_string() },
288 ];
289 (rootnode, agent_node_id, leaforder)
290}
291
292pub(crate) fn single_leaf_tree(block_id: &str) -> (LayoutNode, String, Vec<LeafOrderEntry>) {
299 let node_id = Uuid::new_v4().to_string();
300 let rootnode = LayoutNode {
301 id: node_id.clone(),
302 flex_direction: FlexDirection::Column,
303 size: 10.0,
304 children: Vec::new(),
305 data: Some(LayoutNodeData {
306 block_id: block_id.to_string(),
307 ..Default::default()
308 }),
309 ..Default::default()
310 };
311 let leaforder = vec![LeafOrderEntry { nodeid: node_id.clone(), blockid: block_id.to_string() }];
312 (rootnode, node_id, leaforder)
313}
314
315pub fn get_client(store: &Store) -> Result<Client, StoreError> {
317 let clients = store.get_all::<Client>()?;
318 clients.into_iter().next().ok_or(StoreError::NotFound)
319}
320
321#[cfg(test)]
326mod tests {
327 use super::*;
328 use super::dnd::*;
329
330 fn make_store() -> Store {
331 Store::open_in_memory().unwrap()
332 }
333
334 #[test]
335 fn test_ensure_initial_data_first_launch() {
336 let store = make_store();
337 let first = ensure_initial_data(&store).unwrap();
338 assert!(first);
339
340 let client = get_client(&store).unwrap();
342 assert_eq!(client.windowids.len(), 1);
343 assert!(!client.tempoid.is_empty());
344
345 let windows = store.get_all::<Window>().unwrap();
346 assert_eq!(windows.len(), 1);
347 assert_eq!(windows[0].pos.x, 0);
349 assert_eq!(windows[0].pos.y, 0);
350 assert_eq!(windows[0].winsize.width, 0);
351 assert_eq!(windows[0].winsize.height, 0);
352
353 let workspaces = store.get_all::<Workspace>().unwrap();
354 assert_eq!(workspaces.len(), 1);
355 assert_eq!(workspaces[0].name, "Starter workspace");
356 assert_eq!(workspaces[0].pinnedtabids.len(), 1);
358 assert_eq!(workspaces[0].tabids.len(), 0);
359
360 let tabs = store.get_all::<Tab>().unwrap();
361 assert_eq!(tabs.len(), 1);
362 assert_eq!(tabs[0].name, "Tab 1");
366 }
367
368 #[test]
369 fn test_ensure_initial_data_idempotent() {
370 let store = make_store();
371 let first = ensure_initial_data(&store).unwrap();
372 assert!(first);
373
374 let second = ensure_initial_data(&store).unwrap();
375 assert!(!second);
376
377 assert_eq!(store.count::<Client>().unwrap(), 1);
379 }
380
381 #[test]
382 fn test_create_and_delete_workspace() {
383 let store = make_store();
384 let ws = create_workspace(&store, "Test WS").unwrap();
385 assert_eq!(ws.name, "Test WS");
386
387 let t1 = create_tab(&store, &ws.oid).unwrap();
389 let t2 = create_tab(&store, &ws.oid).unwrap();
390
391 let ws = get_workspace(&store, &ws.oid).unwrap();
392 assert_eq!(ws.tabids.len(), 2);
393
394 let t1_oid = t1.oid.clone();
396 let t2_oid = t2.oid.clone();
397 delete_workspace(&store, &ws.oid).unwrap();
398 assert!(store.get::<Workspace>(&ws.oid).unwrap().is_none());
399 assert!(store.get::<Tab>(&t1_oid).unwrap().is_none());
400 assert!(store.get::<Tab>(&t2_oid).unwrap().is_none());
401 }
402
403 #[test]
404 fn test_create_and_delete_tab() {
405 let store = make_store();
406 let ws = create_workspace(&store, "WS").unwrap();
407 let tab1 = create_tab(&store, &ws.oid).unwrap();
408 let tab2 = create_tab(&store, &ws.oid).unwrap();
409
410 let ws = get_workspace(&store, &ws.oid).unwrap();
411 assert_eq!(ws.tabids.len(), 2);
412 assert_eq!(ws.activetabid, tab1.oid);
413
414 delete_tab(&store, &ws.oid, &tab1.oid).unwrap();
416 let ws = get_workspace(&store, &ws.oid).unwrap();
417 assert_eq!(ws.tabids.len(), 1);
418 assert_eq!(ws.activetabid, tab2.oid);
419 }
420
421 #[test]
422 fn test_set_active_tab() {
423 let store = make_store();
424 let ws = create_workspace(&store, "WS").unwrap();
425 let _tab1 = create_tab(&store, &ws.oid).unwrap();
426 let tab2 = create_tab(&store, &ws.oid).unwrap();
427
428 set_active_tab(&store, &ws.oid, &tab2.oid).unwrap();
429 let ws = get_workspace(&store, &ws.oid).unwrap();
430 assert_eq!(ws.activetabid, tab2.oid);
431
432 let result = set_active_tab(&store, &ws.oid, "nonexistent");
434 assert!(result.is_err());
435 }
436
437 #[test]
438 fn test_create_and_delete_block() {
439 let store = make_store();
440 let ws = create_workspace(&store, "WS").unwrap();
441 let tab = create_tab(&store, &ws.oid).unwrap();
442
443 let mut meta = MetaMapType::new();
444 meta.insert("view".to_string(), serde_json::json!("term"));
445 let block = create_block(&store, &tab.oid, meta).unwrap();
446
447 let tab = store.must_get::<Tab>(&tab.oid).unwrap();
448 assert_eq!(tab.blockids.len(), 1);
449 assert_eq!(tab.blockids[0], block.oid);
450
451 let loaded = store.must_get::<Block>(&block.oid).unwrap();
452 assert_eq!(loaded.parentoref, format!("tab:{}", tab.oid));
453 assert_eq!(loaded.meta.get("view").unwrap(), "term");
454
455 delete_block(&store, &tab.oid, &block.oid).unwrap();
456 assert!(store.get::<Block>(&block.oid).unwrap().is_none());
457 let tab = store.must_get::<Tab>(&tab.oid).unwrap();
458 assert!(tab.blockids.is_empty());
459 }
460
461 #[test]
462 fn seed_default_layout_creates_three_pane_layout() {
463 let store = make_store();
468 let ws = create_workspace(&store, "WS").unwrap();
469 let tab = create_tab(&store, &ws.oid).unwrap();
470 assert!(tab.blockids.is_empty(), "fresh tab starts blank");
471 let layout = store.must_get::<LayoutState>(&tab.layoutstate).unwrap();
472 assert!(layout.rootnode.is_none(), "fresh tab has no layout");
473
474 seed_default_layout(&store, &tab.oid).unwrap();
475
476 let tab = store.must_get::<Tab>(&tab.oid).unwrap();
479 assert_eq!(tab.blockids.len(), 3, "agent + swarm + sysinfo");
480 let views: Vec<String> = tab
481 .blockids
482 .iter()
483 .map(|bid| {
484 store
485 .must_get::<Block>(bid)
486 .unwrap()
487 .meta
488 .get("view")
489 .and_then(|v| v.as_str())
490 .unwrap_or_default()
491 .to_string()
492 })
493 .collect();
494 assert!(views.contains(&"agent".to_string()));
495 assert!(views.contains(&"swarm".to_string()));
496 assert!(views.contains(&"sysinfo".to_string()));
497 for view in ["connectors", "memory"] {
498 assert!(!views.contains(&view.to_string()), "{view} must not be seeded");
499 }
500
501 let layout = store.must_get::<LayoutState>(&tab.layoutstate).unwrap();
503 assert!(layout.rootnode.is_some(), "rootnode must be populated, not blank");
504 assert_eq!(layout.leaforder.as_ref().unwrap().len(), 3);
505 }
506
507 #[test]
512 fn default_three_pane_tree_puts_sysinfo_above_swarm() {
513 let (root, focused, leaforder) = default_three_pane_tree("b-agent", "b-swarm", "b-sysinfo");
514
515 assert_eq!(root.children.len(), 2, "agent | right-column");
516 let agent = &root.children[0];
517 assert_eq!(agent.data.as_ref().unwrap().block_id, "b-agent");
518 assert_eq!(focused, agent.id, "agent pane starts focused");
519
520 let right = &root.children[1];
521 assert_eq!(right.flex_direction, FlexDirection::Column);
522 assert_eq!(right.children.len(), 2, "sysinfo + swarm only");
523 assert_eq!(right.children[0].data.as_ref().unwrap().block_id, "b-sysinfo", "sysinfo on top");
524 assert_eq!(right.children[1].data.as_ref().unwrap().block_id, "b-swarm", "swarm below");
525 assert_eq!(right.children[0].size, 2.0);
526 assert_eq!(right.children[1].size, 8.0);
527
528 let order: Vec<&str> = leaforder.iter().map(|e| e.blockid.as_str()).collect();
529 assert_eq!(order, vec!["b-agent", "b-sysinfo", "b-swarm"]);
530 }
531
532 #[test]
533 fn single_leaf_tree_wraps_exactly_one_block_with_no_split() {
534 let (root, focused, leaforder) = single_leaf_tree("b-widget");
535
536 assert!(root.children.is_empty(), "no split — the root node IS the leaf");
537 assert_eq!(root.data.as_ref().unwrap().block_id, "b-widget");
538 assert_eq!(focused, root.id, "the sole pane starts focused");
539 assert_eq!(leaforder.len(), 1);
540 assert_eq!(leaforder[0].blockid, "b-widget");
541 assert_eq!(leaforder[0].nodeid, root.id);
542 }
543
544 #[test]
545 fn test_create_and_close_window() {
546 let store = make_store();
547 ensure_initial_data(&store).unwrap();
548
549 let client = get_client(&store).unwrap();
550 let initial_count = client.windowids.len();
551
552 let ws = create_workspace(&store, "WS2").unwrap();
553 let window = create_window(&store, &ws.oid).unwrap();
554
555 let mut client = get_client(&store).unwrap();
557 client.windowids.push(window.oid.clone());
558 store.update(&mut client).unwrap();
559
560 close_window(&store, &window.oid).unwrap();
561 let client = get_client(&store).unwrap();
562 assert_eq!(client.windowids.len(), initial_count);
563 assert!(store.get::<Window>(&window.oid).unwrap().is_none());
564 }
565
566 #[test]
567 fn test_focus_window() {
568 let store = make_store();
569 ensure_initial_data(&store).unwrap();
570
571 let ws = create_workspace(&store, "WS2").unwrap();
572 let w2 = create_window(&store, &ws.oid).unwrap();
573 let mut client = get_client(&store).unwrap();
574 client.windowids.push(w2.oid.clone());
575 store.update(&mut client).unwrap();
576
577 focus_window(&store, &w2.oid).unwrap();
579 let client = get_client(&store).unwrap();
580 assert_eq!(client.windowids[0], w2.oid);
581 }
582
583 #[test]
584 fn test_switch_workspace() {
585 let store = make_store();
586 ensure_initial_data(&store).unwrap();
587
588 let client = get_client(&store).unwrap();
589 let window_id = &client.windowids[0];
590 let window = store.must_get::<Window>(window_id).unwrap();
591 let old_ws = window.workspaceid.clone();
592
593 let new_ws = create_workspace(&store, "New WS").unwrap();
594 switch_workspace(&store, window_id, &new_ws.oid).unwrap();
595
596 let window = store.must_get::<Window>(window_id).unwrap();
597 assert_eq!(window.workspaceid, new_ws.oid);
598 assert_ne!(window.workspaceid, old_ws);
599 }
600
601 #[test]
602 fn test_resolve_block_id_prefix() {
603 let store = make_store();
604 let ws = create_workspace(&store, "WS").unwrap();
605 let tab = create_tab(&store, &ws.oid).unwrap();
606
607 let meta = MetaMapType::new();
608 let block = create_block(&store, &tab.oid, meta).unwrap();
609 let prefix = &block.oid[..8];
610
611 let resolved = resolve_block_id_from_prefix(&store, &tab.oid, prefix).unwrap();
612 assert_eq!(resolved, block.oid);
613
614 let result = resolve_block_id_from_prefix(&store, &tab.oid, "00000000");
616 assert!(result.is_err());
617 }
618
619 #[test]
620 fn test_list_workspaces() {
621 let store = make_store();
622 create_workspace(&store, "WS1").unwrap();
623 create_workspace(&store, "WS2").unwrap();
624 create_workspace(&store, "WS3").unwrap();
625
626 let all = list_workspaces(&store).unwrap();
627 assert_eq!(all.len(), 3);
628 }
629
630 #[test]
631 fn test_check_and_fix_window_missing_workspace() {
632 let store = make_store();
633 let mut window = Window {
635 oid: Uuid::new_v4().to_string(),
636 workspaceid: "nonexistent".to_string(),
637 pos: Point { x: 0, y: 0 },
638 winsize: WinSize {
639 width: 800,
640 height: 600,
641 },
642 meta: MetaMapType::new(),
643 ..Default::default()
644 };
645 store.insert(&mut window).unwrap();
646
647 window::check_and_fix_window(&store, &window.oid).unwrap();
648
649 let fixed = store.must_get::<Window>(&window.oid).unwrap();
651 assert_ne!(fixed.workspaceid, "nonexistent");
652 let ws = store.must_get::<Workspace>(&fixed.workspaceid).unwrap();
653 assert_eq!(ws.tabids.len(), 1); }
655
656 #[test]
657 fn test_move_block_to_tab() {
658 let store = make_store();
659 let ws = create_workspace(&store, "WS").unwrap();
660 let tab1 = create_tab(&store, &ws.oid).unwrap();
661 let tab2 = create_tab(&store, &ws.oid).unwrap();
662
663 let meta = MetaMapType::new();
664 let block = create_block(&store, &tab1.oid, meta).unwrap();
665
666 let t1 = store.must_get::<Tab>(&tab1.oid).unwrap();
668 assert_eq!(t1.blockids.len(), 1);
669 assert_eq!(t1.blockids[0], block.oid);
670
671 move_block_to_tab(&store, &block.oid, &tab1.oid, &tab2.oid, &ws.oid, false).unwrap();
673
674 let t1 = store.must_get::<Tab>(&tab1.oid).unwrap();
676 let t2 = store.must_get::<Tab>(&tab2.oid).unwrap();
677 assert!(t1.blockids.is_empty());
678 assert_eq!(t2.blockids.len(), 1);
679 assert_eq!(t2.blockids[0], block.oid);
680
681 let b = store.must_get::<Block>(&block.oid).unwrap();
683 assert_eq!(b.parentoref, format!("tab:{}", tab2.oid));
684 }
685
686 #[test]
687 fn test_move_block_to_tab_auto_close() {
688 let store = make_store();
689 let ws = create_workspace(&store, "WS").unwrap();
690 let tab1 = create_tab(&store, &ws.oid).unwrap();
691 let tab2 = create_tab(&store, &ws.oid).unwrap();
692
693 let block = create_block(&store, &tab1.oid, MetaMapType::new()).unwrap();
694
695 move_block_to_tab(&store, &block.oid, &tab1.oid, &tab2.oid, &ws.oid, true).unwrap();
697
698 assert!(store.get::<Tab>(&tab1.oid).unwrap().is_none());
700
701 let ws = store.must_get::<Workspace>(&ws.oid).unwrap();
703 assert_eq!(ws.tabids.len(), 1);
704 assert_eq!(ws.tabids[0], tab2.oid);
705 }
706
707 #[test]
708 fn test_move_block_same_tab_noop() {
709 let store = make_store();
710 let ws = create_workspace(&store, "WS").unwrap();
711 let tab = create_tab(&store, &ws.oid).unwrap();
712 let block = create_block(&store, &tab.oid, MetaMapType::new()).unwrap();
713
714 move_block_to_tab(&store, &block.oid, &tab.oid, &tab.oid, &ws.oid, false).unwrap();
716
717 let t = store.must_get::<Tab>(&tab.oid).unwrap();
718 assert_eq!(t.blockids.len(), 1);
719 }
720
721 #[test]
722 fn test_promote_block_to_tab() {
723 let store = make_store();
724 let ws = create_workspace(&store, "WS").unwrap();
725 let tab = create_tab(&store, &ws.oid).unwrap();
726 let block = create_block(&store, &tab.oid, MetaMapType::new()).unwrap();
727
728 let new_tab = promote_block_to_tab(&store, &block.oid, &tab.oid, &ws.oid, false).unwrap();
730
731 let old_tab = store.must_get::<Tab>(&tab.oid).unwrap();
733 assert!(old_tab.blockids.is_empty());
734
735 let nt = store.must_get::<Tab>(&new_tab.oid).unwrap();
737 assert_eq!(nt.blockids.len(), 1);
738 assert_eq!(nt.blockids[0], block.oid);
739
740 let ws = store.must_get::<Workspace>(&ws.oid).unwrap();
742 assert_eq!(ws.tabids.len(), 2);
743
744 assert_eq!(ws.activetabid, new_tab.oid);
746 }
747
748 #[test]
749 fn test_reorder_tab() {
750 let store = make_store();
751 let ws = create_workspace(&store, "WS").unwrap();
752 let tab1 = create_tab(&store, &ws.oid).unwrap();
753 let tab2 = create_tab(&store, &ws.oid).unwrap();
754 let tab3 = create_tab(&store, &ws.oid).unwrap();
755
756 let ws_data = store.must_get::<Workspace>(&ws.oid).unwrap();
758 assert_eq!(ws_data.tabids, vec![tab1.oid.clone(), tab2.oid.clone(), tab3.oid.clone()]);
759
760 reorder_tab(&store, &ws.oid, &tab3.oid, 0).unwrap();
762
763 let ws_data = store.must_get::<Workspace>(&ws.oid).unwrap();
764 assert_eq!(ws_data.tabids, vec![tab3.oid.clone(), tab1.oid.clone(), tab2.oid.clone()]);
765
766 reorder_tab(&store, &ws.oid, &tab1.oid, 99).unwrap();
768
769 let ws_data = store.must_get::<Workspace>(&ws.oid).unwrap();
770 assert_eq!(ws_data.tabids, vec![tab3.oid.clone(), tab2.oid.clone(), tab1.oid.clone()]);
771 }
772
773 #[test]
774 fn test_move_tab_to_workspace() {
775 let store = make_store();
776 let ws1 = create_workspace(&store, "WS1").unwrap();
777 let ws2 = create_workspace(&store, "WS2").unwrap();
778 let tab1 = create_tab(&store, &ws1.oid).unwrap();
779 let tab2 = create_tab(&store, &ws1.oid).unwrap();
780 let tab3 = create_tab(&store, &ws2.oid).unwrap();
781
782 set_active_tab(&store, &ws1.oid, &tab1.oid).unwrap();
784
785 move_tab_to_workspace(&store, &tab2.oid, &ws1.oid, &ws2.oid, None).unwrap();
787
788 let ws1_data = store.must_get::<Workspace>(&ws1.oid).unwrap();
789 let ws2_data = store.must_get::<Workspace>(&ws2.oid).unwrap();
790
791 assert_eq!(ws1_data.tabids, vec![tab1.oid.clone()]);
792 assert!(ws2_data.tabids.contains(&tab2.oid));
793 assert!(ws2_data.tabids.contains(&tab3.oid));
794 assert_eq!(ws2_data.activetabid, tab2.oid);
796 }
797
798 #[test]
799 fn test_move_tab_to_workspace_last_tab_blocked() {
800 let store = make_store();
801 let ws1 = create_workspace(&store, "WS1").unwrap();
802 let ws2 = create_workspace(&store, "WS2").unwrap();
803 let tab1 = create_tab(&store, &ws1.oid).unwrap();
804 let _tab2 = create_tab(&store, &ws2.oid).unwrap();
805
806 let result = move_tab_to_workspace(&store, &tab1.oid, &ws1.oid, &ws2.oid, None);
808 assert!(result.is_err());
809 }
810
811 #[test]
812 fn test_tear_off_tab() {
813 let store = make_store();
814 let ws = create_workspace(&store, "WS").unwrap();
815 let tab1 = create_tab(&store, &ws.oid).unwrap();
816 let tab2 = create_tab(&store, &ws.oid).unwrap();
817 set_active_tab(&store, &ws.oid, &tab1.oid).unwrap();
818
819 let new_ws = tear_off_tab(&store, &tab2.oid, &ws.oid).unwrap();
821
822 let ws_data = store.must_get::<Workspace>(&ws.oid).unwrap();
824 assert_eq!(ws_data.tabids, vec![tab1.oid.clone()]);
825
826 assert_eq!(new_ws.tabids, vec![tab2.oid.clone()]);
828 assert_eq!(new_ws.activetabid, tab2.oid);
829 }
830
831 #[test]
832 fn test_tear_off_last_tab_blocked() {
833 let store = make_store();
834 let ws = create_workspace(&store, "WS").unwrap();
835 let tab1 = create_tab(&store, &ws.oid).unwrap();
836
837 let result = tear_off_tab(&store, &tab1.oid, &ws.oid);
839 assert!(result.is_err());
840 }
841}