ZIm/crates/debugger_ui/src/tests/variable_list.rs
Piotr Osiewicz 0d2f70ee8c
Another batch of lint fixes (#36521)
- **Enable a bunch of extra lints**
- **First batch of fixes**
- **More fixes**

Release Notes:

- N/A
2025-08-23 10:39:23 -04:00

2342 lines
70 KiB
Rust

use std::sync::{
Arc,
atomic::{AtomicBool, Ordering},
};
use crate::{
DebugPanel,
persistence::DebuggerPaneItem,
session::running::variable_list::{
AddWatch, CollapseSelectedEntry, ExpandSelectedEntry, RemoveWatch,
},
tests::{active_debug_session_panel, init_test, init_test_workspace, start_debug_session},
};
use collections::HashMap;
use dap::{
Scope, StackFrame, Variable,
requests::{Evaluate, Initialize, Launch, Scopes, StackTrace, Variables},
};
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
use menu::{SelectFirst, SelectNext, SelectPrevious};
use project::{FakeFs, Project};
use serde_json::json;
use ui::SharedString;
use unindent::Unindent as _;
use util::path;
/// This only tests fetching one scope and 2 variables for a single stackframe
#[gpui::test]
async fn test_basic_fetch_initial_scope_and_variables(
executor: BackgroundExecutor,
cx: &mut TestAppContext,
) {
init_test(cx);
let fs = FakeFs::new(executor.clone());
let test_file_content = r#"
const variable1 = "Value 1";
const variable2 = "Value 2";
"#
.unindent();
fs.insert_tree(
path!("/project"),
json!({
"src": {
"test.js": test_file_content,
}
}),
)
.await;
let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
let workspace = init_test_workspace(&project, cx).await;
workspace
.update(cx, |workspace, window, cx| {
workspace.focus_panel::<DebugPanel>(window, cx);
})
.unwrap();
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
client.on_request::<dap::requests::Threads, _>(move |_, _| {
Ok(dap::ThreadsResponse {
threads: vec![dap::Thread {
id: 1,
name: "Thread 1".into(),
}],
})
});
let stack_frames = vec![StackFrame {
id: 1,
name: "Stack Frame 1".into(),
source: Some(dap::Source {
name: Some("test.js".into()),
path: Some(path!("/project/src/test.js").into()),
source_reference: None,
presentation_hint: None,
origin: None,
sources: None,
adapter_data: None,
checksums: None,
}),
line: 1,
column: 1,
end_line: None,
end_column: None,
can_restart: None,
instruction_pointer_reference: None,
module_id: None,
presentation_hint: None,
}];
client.on_request::<StackTrace, _>({
let stack_frames = Arc::new(stack_frames.clone());
move |_, args| {
assert_eq!(1, args.thread_id);
Ok(dap::StackTraceResponse {
stack_frames: (*stack_frames).clone(),
total_frames: None,
})
}
});
let scopes = vec![Scope {
name: "Scope 1".into(),
presentation_hint: None,
variables_reference: 2,
named_variables: None,
indexed_variables: None,
expensive: false,
source: None,
line: None,
column: None,
end_line: None,
end_column: None,
}];
client.on_request::<Scopes, _>({
let scopes = Arc::new(scopes.clone());
move |_, args| {
assert_eq!(1, args.frame_id);
Ok(dap::ScopesResponse {
scopes: (*scopes).clone(),
})
}
});
let variables = vec![
Variable {
name: "variable1".into(),
value: "value 1".into(),
type_: None,
presentation_hint: None,
evaluate_name: None,
variables_reference: 0,
named_variables: None,
indexed_variables: None,
memory_reference: None,
declaration_location_reference: None,
value_location_reference: None,
},
Variable {
name: "variable2".into(),
value: "value 2".into(),
type_: None,
presentation_hint: None,
evaluate_name: None,
variables_reference: 0,
named_variables: None,
indexed_variables: None,
memory_reference: None,
declaration_location_reference: None,
value_location_reference: None,
},
];
client.on_request::<Variables, _>({
let variables = Arc::new(variables.clone());
move |_, args| {
assert_eq!(2, args.variables_reference);
Ok(dap::VariablesResponse {
variables: (*variables).clone(),
})
}
});
client
.fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
reason: dap::StoppedEventReason::Pause,
description: None,
thread_id: Some(1),
preserve_focus_hint: None,
text: None,
all_threads_stopped: None,
hit_breakpoint_ids: None,
}))
.await;
cx.run_until_parked();
let running_state =
active_debug_session_panel(workspace, cx).update_in(cx, |item, window, cx| {
cx.focus_self(window);
item.running_state().clone()
});
cx.run_until_parked();
running_state.update(cx, |running_state, cx| {
let (stack_frame_list, stack_frame_id) =
running_state.stack_frame_list().update(cx, |list, _| {
(
list.flatten_entries(true, true),
list.opened_stack_frame_id(),
)
});
assert_eq!(stack_frames, stack_frame_list);
assert_eq!(Some(1), stack_frame_id);
running_state
.variable_list()
.update(cx, |variable_list, _| {
assert_eq!(scopes, variable_list.scopes());
assert_eq!(
vec![variables[0].clone(), variables[1].clone(),],
variable_list.variables()
);
variable_list.assert_visual_entries(vec![
"v Scope 1",
" > variable1",
" > variable2",
]);
});
});
}
/// This tests fetching multiple scopes and variables for them with a single stackframe
#[gpui::test]
async fn test_fetch_variables_for_multiple_scopes(
executor: BackgroundExecutor,
cx: &mut TestAppContext,
) {
init_test(cx);
let fs = FakeFs::new(executor.clone());
let test_file_content = r#"
const variable1 = {
nested1: "Nested 1",
nested2: "Nested 2",
};
const variable2 = "Value 2";
const variable3 = "Value 3";
"#
.unindent();
fs.insert_tree(
path!("/project"),
json!({
"src": {
"test.js": test_file_content,
}
}),
)
.await;
let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
let workspace = init_test_workspace(&project, cx).await;
workspace
.update(cx, |workspace, window, cx| {
workspace.focus_panel::<DebugPanel>(window, cx);
})
.unwrap();
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
client.on_request::<dap::requests::Threads, _>(move |_, _| {
Ok(dap::ThreadsResponse {
threads: vec![dap::Thread {
id: 1,
name: "Thread 1".into(),
}],
})
});
client.on_request::<Initialize, _>(move |_, _| {
Ok(dap::Capabilities {
supports_step_back: Some(false),
..Default::default()
})
});
client.on_request::<Launch, _>(move |_, _| Ok(()));
let stack_frames = vec![StackFrame {
id: 1,
name: "Stack Frame 1".into(),
source: Some(dap::Source {
name: Some("test.js".into()),
path: Some(path!("/project/src/test.js").into()),
source_reference: None,
presentation_hint: None,
origin: None,
sources: None,
adapter_data: None,
checksums: None,
}),
line: 1,
column: 1,
end_line: None,
end_column: None,
can_restart: None,
instruction_pointer_reference: None,
module_id: None,
presentation_hint: None,
}];
client.on_request::<StackTrace, _>({
let stack_frames = Arc::new(stack_frames.clone());
move |_, args| {
assert_eq!(1, args.thread_id);
Ok(dap::StackTraceResponse {
stack_frames: (*stack_frames).clone(),
total_frames: None,
})
}
});
let scopes = vec![
Scope {
name: "Scope 1".into(),
presentation_hint: Some(dap::ScopePresentationHint::Locals),
variables_reference: 2,
named_variables: None,
indexed_variables: None,
expensive: false,
source: None,
line: None,
column: None,
end_line: None,
end_column: None,
},
Scope {
name: "Scope 2".into(),
presentation_hint: None,
variables_reference: 3,
named_variables: None,
indexed_variables: None,
expensive: false,
source: None,
line: None,
column: None,
end_line: None,
end_column: None,
},
];
client.on_request::<Scopes, _>({
let scopes = Arc::new(scopes.clone());
move |_, args| {
assert_eq!(1, args.frame_id);
Ok(dap::ScopesResponse {
scopes: (*scopes).clone(),
})
}
});
let mut variables = HashMap::default();
variables.insert(
2,
vec![
Variable {
name: "variable1".into(),
value: "{nested1: \"Nested 1\", nested2: \"Nested 2\"}".into(),
type_: None,
presentation_hint: None,
evaluate_name: None,
variables_reference: 0,
named_variables: None,
indexed_variables: None,
memory_reference: None,
declaration_location_reference: None,
value_location_reference: None,
},
Variable {
name: "variable2".into(),
value: "Value 2".into(),
type_: None,
presentation_hint: None,
evaluate_name: None,
variables_reference: 0,
named_variables: None,
indexed_variables: None,
memory_reference: None,
declaration_location_reference: None,
value_location_reference: None,
},
],
);
variables.insert(
3,
vec![Variable {
name: "variable3".into(),
value: "Value 3".into(),
type_: None,
presentation_hint: None,
evaluate_name: None,
variables_reference: 0,
named_variables: None,
indexed_variables: None,
memory_reference: None,
declaration_location_reference: None,
value_location_reference: None,
}],
);
client.on_request::<Variables, _>({
let variables = Arc::new(variables.clone());
move |_, args| {
Ok(dap::VariablesResponse {
variables: variables.get(&args.variables_reference).unwrap().clone(),
})
}
});
client
.fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
reason: dap::StoppedEventReason::Pause,
description: None,
thread_id: Some(1),
preserve_focus_hint: None,
text: None,
all_threads_stopped: None,
hit_breakpoint_ids: None,
}))
.await;
cx.run_until_parked();
let running_state =
active_debug_session_panel(workspace, cx).update_in(cx, |item, window, cx| {
cx.focus_self(window);
item.running_state().clone()
});
cx.run_until_parked();
running_state.update(cx, |running_state, cx| {
let (stack_frame_list, stack_frame_id) =
running_state.stack_frame_list().update(cx, |list, _| {
(
list.flatten_entries(true, true),
list.opened_stack_frame_id(),
)
});
assert_eq!(Some(1), stack_frame_id);
assert_eq!(stack_frames, stack_frame_list);
running_state
.variable_list()
.update(cx, |variable_list, _| {
assert_eq!(2, variable_list.scopes().len());
assert_eq!(scopes, variable_list.scopes());
let variables_by_scope = variable_list.variables_per_scope();
// scope 1
assert_eq!(
vec![
variables.get(&2).unwrap()[0].clone(),
variables.get(&2).unwrap()[1].clone(),
],
variables_by_scope[0].1
);
// scope 2
let empty_vec: Vec<dap::Variable> = vec![];
assert_eq!(empty_vec, variables_by_scope[1].1);
variable_list.assert_visual_entries(vec![
"v Scope 1",
" > variable1",
" > variable2",
"> Scope 2",
]);
});
});
}
// tests that toggling a variable will fetch its children and shows it
#[gpui::test]
async fn test_keyboard_navigation(executor: BackgroundExecutor, cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(executor.clone());
let test_file_content = r#"
const variable1 = {
nested1: "Nested 1",
nested2: "Nested 2",
};
const variable2 = "Value 2";
const variable3 = "Value 3";
"#
.unindent();
fs.insert_tree(
path!("/project"),
json!({
"src": {
"test.js": test_file_content,
}
}),
)
.await;
let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
let workspace = init_test_workspace(&project, cx).await;
workspace
.update(cx, |workspace, window, cx| {
workspace.focus_panel::<DebugPanel>(window, cx);
})
.unwrap();
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
client.on_request::<dap::requests::Threads, _>(move |_, _| {
Ok(dap::ThreadsResponse {
threads: vec![dap::Thread {
id: 1,
name: "Thread 1".into(),
}],
})
});
client.on_request::<Initialize, _>(move |_, _| {
Ok(dap::Capabilities {
supports_step_back: Some(false),
..Default::default()
})
});
client.on_request::<Launch, _>(move |_, _| Ok(()));
let stack_frames = vec![StackFrame {
id: 1,
name: "Stack Frame 1".into(),
source: Some(dap::Source {
name: Some("test.js".into()),
path: Some(path!("/project/src/test.js").into()),
source_reference: None,
presentation_hint: None,
origin: None,
sources: None,
adapter_data: None,
checksums: None,
}),
line: 1,
column: 1,
end_line: None,
end_column: None,
can_restart: None,
instruction_pointer_reference: None,
module_id: None,
presentation_hint: None,
}];
client.on_request::<StackTrace, _>({
let stack_frames = Arc::new(stack_frames.clone());
move |_, args| {
assert_eq!(1, args.thread_id);
Ok(dap::StackTraceResponse {
stack_frames: (*stack_frames).clone(),
total_frames: None,
})
}
});
let scopes = vec![
Scope {
name: "Scope 1".into(),
presentation_hint: Some(dap::ScopePresentationHint::Locals),
variables_reference: 2,
named_variables: None,
indexed_variables: None,
expensive: false,
source: None,
line: None,
column: None,
end_line: None,
end_column: None,
},
Scope {
name: "Scope 2".into(),
presentation_hint: None,
variables_reference: 4,
named_variables: None,
indexed_variables: None,
expensive: false,
source: None,
line: None,
column: None,
end_line: None,
end_column: None,
},
];
client.on_request::<Scopes, _>({
let scopes = Arc::new(scopes.clone());
move |_, args| {
assert_eq!(1, args.frame_id);
Ok(dap::ScopesResponse {
scopes: (*scopes).clone(),
})
}
});
let scope1_variables = vec![
Variable {
name: "variable1".into(),
value: "{nested1: \"Nested 1\", nested2: \"Nested 2\"}".into(),
type_: None,
presentation_hint: None,
evaluate_name: None,
variables_reference: 3,
named_variables: None,
indexed_variables: None,
memory_reference: None,
declaration_location_reference: None,
value_location_reference: None,
},
Variable {
name: "variable2".into(),
value: "Value 2".into(),
type_: None,
presentation_hint: None,
evaluate_name: None,
variables_reference: 0,
named_variables: None,
indexed_variables: None,
memory_reference: None,
declaration_location_reference: None,
value_location_reference: None,
},
];
let nested_variables = vec![
Variable {
name: "nested1".into(),
value: "Nested 1".into(),
type_: None,
presentation_hint: None,
evaluate_name: None,
variables_reference: 0,
named_variables: None,
indexed_variables: None,
memory_reference: None,
declaration_location_reference: None,
value_location_reference: None,
},
Variable {
name: "nested2".into(),
value: "Nested 2".into(),
type_: None,
presentation_hint: None,
evaluate_name: None,
variables_reference: 0,
named_variables: None,
indexed_variables: None,
memory_reference: None,
declaration_location_reference: None,
value_location_reference: None,
},
];
let scope2_variables = vec![Variable {
name: "variable3".into(),
value: "Value 3".into(),
type_: None,
presentation_hint: None,
evaluate_name: None,
variables_reference: 0,
named_variables: None,
indexed_variables: None,
memory_reference: None,
declaration_location_reference: None,
value_location_reference: None,
}];
client.on_request::<Variables, _>({
let scope1_variables = Arc::new(scope1_variables.clone());
let nested_variables = Arc::new(nested_variables.clone());
let scope2_variables = Arc::new(scope2_variables.clone());
move |_, args| match args.variables_reference {
4 => Ok(dap::VariablesResponse {
variables: (*scope2_variables).clone(),
}),
3 => Ok(dap::VariablesResponse {
variables: (*nested_variables).clone(),
}),
2 => Ok(dap::VariablesResponse {
variables: (*scope1_variables).clone(),
}),
id => unreachable!("unexpected variables reference {id}"),
}
});
client
.fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
reason: dap::StoppedEventReason::Pause,
description: None,
thread_id: Some(1),
preserve_focus_hint: None,
text: None,
all_threads_stopped: None,
hit_breakpoint_ids: None,
}))
.await;
cx.run_until_parked();
let running_state =
active_debug_session_panel(workspace, cx).update_in(cx, |item, window, cx| {
cx.focus_self(window);
let running = item.running_state().clone();
let variable_list = running.update(cx, |state, cx| {
// have to do this because the variable list pane should be shown/active
// for testing keyboard navigation
state.activate_item(DebuggerPaneItem::Variables, window, cx);
state.variable_list().clone()
});
variable_list.update(cx, |_, cx| cx.focus_self(window));
running
});
cx.dispatch_action(SelectFirst);
cx.dispatch_action(SelectFirst);
cx.run_until_parked();
running_state.update(cx, |running_state, cx| {
running_state
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec![
"v Scope 1 <=== selected",
" > variable1",
" > variable2",
"> Scope 2",
]);
});
});
cx.dispatch_action(SelectNext);
cx.run_until_parked();
running_state.update(cx, |running_state, cx| {
running_state
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec![
"v Scope 1",
" > variable1 <=== selected",
" > variable2",
"> Scope 2",
]);
});
});
// expand the nested variables of variable 1
cx.dispatch_action(ExpandSelectedEntry);
cx.run_until_parked();
running_state.update(cx, |running_state, cx| {
running_state
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec![
"v Scope 1",
" v variable1 <=== selected",
" > nested1",
" > nested2",
" > variable2",
"> Scope 2",
]);
});
});
// select the first nested variable of variable 1
cx.dispatch_action(SelectNext);
cx.run_until_parked();
running_state.update(cx, |debug_panel_item, cx| {
debug_panel_item
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec![
"v Scope 1",
" v variable1",
" > nested1 <=== selected",
" > nested2",
" > variable2",
"> Scope 2",
]);
});
});
// select the second nested variable of variable 1
cx.dispatch_action(SelectNext);
cx.run_until_parked();
running_state.update(cx, |debug_panel_item, cx| {
debug_panel_item
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec![
"v Scope 1",
" v variable1",
" > nested1",
" > nested2 <=== selected",
" > variable2",
"> Scope 2",
]);
});
});
// select variable 2 of scope 1
cx.dispatch_action(SelectNext);
cx.run_until_parked();
running_state.update(cx, |debug_panel_item, cx| {
debug_panel_item
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec![
"v Scope 1",
" v variable1",
" > nested1",
" > nested2",
" > variable2 <=== selected",
"> Scope 2",
]);
});
});
// select scope 2
cx.dispatch_action(SelectNext);
cx.run_until_parked();
running_state.update(cx, |debug_panel_item, cx| {
debug_panel_item
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec![
"v Scope 1",
" v variable1",
" > nested1",
" > nested2",
" > variable2",
"> Scope 2 <=== selected",
]);
});
});
// expand the nested variables of scope 2
cx.dispatch_action(ExpandSelectedEntry);
cx.run_until_parked();
running_state.update(cx, |debug_panel_item, cx| {
debug_panel_item
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec![
"v Scope 1",
" v variable1",
" > nested1",
" > nested2",
" > variable2",
"v Scope 2 <=== selected",
" > variable3",
]);
});
});
// select variable 3 of scope 2
cx.dispatch_action(SelectNext);
cx.run_until_parked();
running_state.update(cx, |debug_panel_item, cx| {
debug_panel_item
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec![
"v Scope 1",
" v variable1",
" > nested1",
" > nested2",
" > variable2",
"v Scope 2",
" > variable3 <=== selected",
]);
});
});
// select scope 2
cx.dispatch_action(SelectPrevious);
cx.run_until_parked();
running_state.update(cx, |debug_panel_item, cx| {
debug_panel_item
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec![
"v Scope 1",
" v variable1",
" > nested1",
" > nested2",
" > variable2",
"v Scope 2 <=== selected",
" > variable3",
]);
});
});
// collapse variables of scope 2
cx.dispatch_action(CollapseSelectedEntry);
cx.run_until_parked();
running_state.update(cx, |debug_panel_item, cx| {
debug_panel_item
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec![
"v Scope 1",
" v variable1",
" > nested1",
" > nested2",
" > variable2",
"> Scope 2 <=== selected",
]);
});
});
// select variable 2 of scope 1
cx.dispatch_action(SelectPrevious);
cx.run_until_parked();
running_state.update(cx, |debug_panel_item, cx| {
debug_panel_item
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec![
"v Scope 1",
" v variable1",
" > nested1",
" > nested2",
" > variable2 <=== selected",
"> Scope 2",
]);
});
});
// select nested2 of variable 1
cx.dispatch_action(SelectPrevious);
cx.run_until_parked();
running_state.update(cx, |debug_panel_item, cx| {
debug_panel_item
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec![
"v Scope 1",
" v variable1",
" > nested1",
" > nested2 <=== selected",
" > variable2",
"> Scope 2",
]);
});
});
// select nested1 of variable 1
cx.dispatch_action(SelectPrevious);
cx.run_until_parked();
running_state.update(cx, |debug_panel_item, cx| {
debug_panel_item
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec![
"v Scope 1",
" v variable1",
" > nested1 <=== selected",
" > nested2",
" > variable2",
"> Scope 2",
]);
});
});
// select variable 1 of scope 1
cx.dispatch_action(SelectPrevious);
cx.run_until_parked();
running_state.update(cx, |debug_panel_item, cx| {
debug_panel_item
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec![
"v Scope 1",
" v variable1 <=== selected",
" > nested1",
" > nested2",
" > variable2",
"> Scope 2",
]);
});
});
// collapse variables of variable 1
cx.dispatch_action(CollapseSelectedEntry);
cx.run_until_parked();
running_state.update(cx, |debug_panel_item, cx| {
debug_panel_item
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec![
"v Scope 1",
" > variable1 <=== selected",
" > variable2",
"> Scope 2",
]);
});
});
// select scope 1
cx.dispatch_action(SelectPrevious);
cx.run_until_parked();
running_state.update(cx, |running_state, cx| {
running_state
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec![
"v Scope 1 <=== selected",
" > variable1",
" > variable2",
"> Scope 2",
]);
});
});
// collapse variables of scope 1
cx.dispatch_action(CollapseSelectedEntry);
cx.run_until_parked();
running_state.update(cx, |debug_panel_item, cx| {
debug_panel_item
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec!["> Scope 1 <=== selected", "> Scope 2"]);
});
});
// select scope 2 backwards
cx.dispatch_action(SelectPrevious);
cx.run_until_parked();
running_state.update(cx, |debug_panel_item, cx| {
debug_panel_item
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec!["> Scope 1", "> Scope 2 <=== selected"]);
});
});
// select scope 1 backwards
cx.dispatch_action(SelectNext);
cx.run_until_parked();
running_state.update(cx, |debug_panel_item, cx| {
debug_panel_item
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec!["> Scope 1 <=== selected", "> Scope 2"]);
});
});
// test stepping through nested with ExpandSelectedEntry/CollapseSelectedEntry actions
cx.dispatch_action(ExpandSelectedEntry);
cx.run_until_parked();
running_state.update(cx, |debug_panel_item, cx| {
debug_panel_item
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec![
"v Scope 1 <=== selected",
" > variable1",
" > variable2",
"> Scope 2",
]);
});
});
cx.dispatch_action(ExpandSelectedEntry);
cx.run_until_parked();
running_state.update(cx, |debug_panel_item, cx| {
debug_panel_item
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec![
"v Scope 1",
" > variable1 <=== selected",
" > variable2",
"> Scope 2",
]);
});
});
cx.dispatch_action(ExpandSelectedEntry);
cx.run_until_parked();
running_state.update(cx, |debug_panel_item, cx| {
debug_panel_item
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec![
"v Scope 1",
" v variable1 <=== selected",
" > nested1",
" > nested2",
" > variable2",
"> Scope 2",
]);
});
});
cx.dispatch_action(ExpandSelectedEntry);
cx.run_until_parked();
running_state.update(cx, |debug_panel_item, cx| {
debug_panel_item
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec![
"v Scope 1",
" v variable1",
" > nested1 <=== selected",
" > nested2",
" > variable2",
"> Scope 2",
]);
});
});
cx.dispatch_action(ExpandSelectedEntry);
cx.run_until_parked();
running_state.update(cx, |debug_panel_item, cx| {
debug_panel_item
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec![
"v Scope 1",
" v variable1",
" > nested1",
" > nested2 <=== selected",
" > variable2",
"> Scope 2",
]);
});
});
cx.dispatch_action(ExpandSelectedEntry);
cx.run_until_parked();
running_state.update(cx, |debug_panel_item, cx| {
debug_panel_item
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec![
"v Scope 1",
" v variable1",
" > nested1",
" > nested2",
" > variable2 <=== selected",
"> Scope 2",
]);
});
});
cx.dispatch_action(CollapseSelectedEntry);
cx.run_until_parked();
running_state.update(cx, |debug_panel_item, cx| {
debug_panel_item
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec![
"v Scope 1",
" v variable1",
" > nested1",
" > nested2 <=== selected",
" > variable2",
"> Scope 2",
]);
});
});
cx.dispatch_action(CollapseSelectedEntry);
cx.run_until_parked();
running_state.update(cx, |debug_panel_item, cx| {
debug_panel_item
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec![
"v Scope 1",
" v variable1",
" > nested1 <=== selected",
" > nested2",
" > variable2",
"> Scope 2",
]);
});
});
cx.dispatch_action(CollapseSelectedEntry);
cx.run_until_parked();
running_state.update(cx, |debug_panel_item, cx| {
debug_panel_item
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec![
"v Scope 1",
" v variable1 <=== selected",
" > nested1",
" > nested2",
" > variable2",
"> Scope 2",
]);
});
});
cx.dispatch_action(CollapseSelectedEntry);
cx.run_until_parked();
running_state.update(cx, |debug_panel_item, cx| {
debug_panel_item
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec![
"v Scope 1",
" > variable1 <=== selected",
" > variable2",
"> Scope 2",
]);
});
});
cx.dispatch_action(CollapseSelectedEntry);
cx.run_until_parked();
running_state.update(cx, |debug_panel_item, cx| {
debug_panel_item
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec![
"v Scope 1 <=== selected",
" > variable1",
" > variable2",
"> Scope 2",
]);
});
});
cx.dispatch_action(CollapseSelectedEntry);
cx.run_until_parked();
running_state.update(cx, |debug_panel_item, cx| {
debug_panel_item
.variable_list()
.update(cx, |variable_list, _| {
variable_list.assert_visual_entries(vec!["> Scope 1 <=== selected", "> Scope 2"]);
});
});
}
#[gpui::test]
async fn test_variable_list_only_sends_requests_when_rendering(
executor: BackgroundExecutor,
cx: &mut TestAppContext,
) {
init_test(cx);
let fs = FakeFs::new(executor.clone());
let test_file_content = r#"
import { SOME_VALUE } './module.js';
console.log(SOME_VALUE);
"#
.unindent();
let module_file_content = r#"
export SOME_VALUE = 'some value';
"#
.unindent();
fs.insert_tree(
path!("/project"),
json!({
"src": {
"test.js": test_file_content,
"module.js": module_file_content,
}
}),
)
.await;
let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
let workspace = init_test_workspace(&project, cx).await;
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
client.on_request::<dap::requests::Threads, _>(move |_, _| {
Ok(dap::ThreadsResponse {
threads: vec![dap::Thread {
id: 1,
name: "Thread 1".into(),
}],
})
});
client.on_request::<Initialize, _>(move |_, _| {
Ok(dap::Capabilities {
supports_step_back: Some(false),
..Default::default()
})
});
client.on_request::<Launch, _>(move |_, _| Ok(()));
let stack_frames = vec![
StackFrame {
id: 1,
name: "Stack Frame 1".into(),
source: Some(dap::Source {
name: Some("test.js".into()),
path: Some(path!("/project/src/test.js").into()),
source_reference: None,
presentation_hint: None,
origin: None,
sources: None,
adapter_data: None,
checksums: None,
}),
line: 3,
column: 1,
end_line: None,
end_column: None,
can_restart: None,
instruction_pointer_reference: None,
module_id: None,
presentation_hint: None,
},
StackFrame {
id: 2,
name: "Stack Frame 2".into(),
source: Some(dap::Source {
name: Some("module.js".into()),
path: Some(path!("/project/src/module.js").into()),
source_reference: None,
presentation_hint: None,
origin: None,
sources: None,
adapter_data: None,
checksums: None,
}),
line: 1,
column: 1,
end_line: None,
end_column: None,
can_restart: None,
instruction_pointer_reference: None,
module_id: None,
presentation_hint: None,
},
];
client.on_request::<StackTrace, _>({
let stack_frames = Arc::new(stack_frames.clone());
move |_, args| {
assert_eq!(1, args.thread_id);
Ok(dap::StackTraceResponse {
stack_frames: (*stack_frames).clone(),
total_frames: None,
})
}
});
let frame_1_scopes = vec![Scope {
name: "Frame 1 Scope 1".into(),
presentation_hint: None,
variables_reference: 2,
named_variables: None,
indexed_variables: None,
expensive: false,
source: None,
line: None,
column: None,
end_line: None,
end_column: None,
}];
let made_scopes_request = Arc::new(AtomicBool::new(false));
client.on_request::<Scopes, _>({
let frame_1_scopes = Arc::new(frame_1_scopes.clone());
let made_scopes_request = made_scopes_request.clone();
move |_, args| {
assert_eq!(1, args.frame_id);
assert!(
!made_scopes_request.load(Ordering::SeqCst),
"We should be caching the scope request"
);
made_scopes_request.store(true, Ordering::SeqCst);
Ok(dap::ScopesResponse {
scopes: (*frame_1_scopes).clone(),
})
}
});
let frame_1_variables = vec![
Variable {
name: "variable1".into(),
value: "value 1".into(),
type_: None,
presentation_hint: None,
evaluate_name: None,
variables_reference: 0,
named_variables: None,
indexed_variables: None,
memory_reference: None,
declaration_location_reference: None,
value_location_reference: None,
},
Variable {
name: "variable2".into(),
value: "value 2".into(),
type_: None,
presentation_hint: None,
evaluate_name: None,
variables_reference: 0,
named_variables: None,
indexed_variables: None,
memory_reference: None,
declaration_location_reference: None,
value_location_reference: None,
},
];
client.on_request::<Variables, _>({
let frame_1_variables = Arc::new(frame_1_variables.clone());
move |_, args| {
assert_eq!(2, args.variables_reference);
Ok(dap::VariablesResponse {
variables: (*frame_1_variables).clone(),
})
}
});
cx.run_until_parked();
let running_state = active_debug_session_panel(workspace, cx)
.update_in(cx, |item, _, _| item.running_state().clone());
client
.fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
reason: dap::StoppedEventReason::Pause,
description: None,
thread_id: Some(1),
preserve_focus_hint: None,
text: None,
all_threads_stopped: None,
hit_breakpoint_ids: None,
}))
.await;
cx.run_until_parked();
running_state.update(cx, |running_state, cx| {
let (stack_frame_list, stack_frame_id) =
running_state.stack_frame_list().update(cx, |list, _| {
(
list.flatten_entries(true, true),
list.opened_stack_frame_id(),
)
});
assert_eq!(Some(1), stack_frame_id);
assert_eq!(stack_frames, stack_frame_list);
let variable_list = running_state.variable_list().read(cx);
assert_eq!(frame_1_variables, variable_list.variables());
assert!(made_scopes_request.load(Ordering::SeqCst));
});
}
#[gpui::test]
async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame(
executor: BackgroundExecutor,
cx: &mut TestAppContext,
) {
init_test(cx);
let fs = FakeFs::new(executor.clone());
let test_file_content = r#"
import { SOME_VALUE } './module.js';
console.log(SOME_VALUE);
"#
.unindent();
let module_file_content = r#"
export SOME_VALUE = 'some value';
"#
.unindent();
fs.insert_tree(
path!("/project"),
json!({
"src": {
"test.js": test_file_content,
"module.js": module_file_content,
}
}),
)
.await;
let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
let workspace = init_test_workspace(&project, cx).await;
workspace
.update(cx, |workspace, window, cx| {
workspace.focus_panel::<DebugPanel>(window, cx);
})
.unwrap();
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
client.on_request::<dap::requests::Threads, _>(move |_, _| {
Ok(dap::ThreadsResponse {
threads: vec![dap::Thread {
id: 1,
name: "Thread 1".into(),
}],
})
});
client.on_request::<Initialize, _>(move |_, _| {
Ok(dap::Capabilities {
supports_step_back: Some(false),
..Default::default()
})
});
client.on_request::<Launch, _>(move |_, _| Ok(()));
let stack_frames = vec![
StackFrame {
id: 1,
name: "Stack Frame 1".into(),
source: Some(dap::Source {
name: Some("test.js".into()),
path: Some(path!("/project/src/test.js").into()),
source_reference: None,
presentation_hint: None,
origin: None,
sources: None,
adapter_data: None,
checksums: None,
}),
line: 3,
column: 1,
end_line: None,
end_column: None,
can_restart: None,
instruction_pointer_reference: None,
module_id: None,
presentation_hint: None,
},
StackFrame {
id: 2,
name: "Stack Frame 2".into(),
source: Some(dap::Source {
name: Some("module.js".into()),
path: Some(path!("/project/src/module.js").into()),
source_reference: None,
presentation_hint: None,
origin: None,
sources: None,
adapter_data: None,
checksums: None,
}),
line: 1,
column: 1,
end_line: None,
end_column: None,
can_restart: None,
instruction_pointer_reference: None,
module_id: None,
presentation_hint: None,
},
];
client.on_request::<StackTrace, _>({
let stack_frames = Arc::new(stack_frames.clone());
move |_, args| {
assert_eq!(1, args.thread_id);
Ok(dap::StackTraceResponse {
stack_frames: (*stack_frames).clone(),
total_frames: None,
})
}
});
let frame_1_scopes = vec![Scope {
name: "Frame 1 Scope 1".into(),
presentation_hint: None,
variables_reference: 2,
named_variables: None,
indexed_variables: None,
expensive: false,
source: None,
line: None,
column: None,
end_line: None,
end_column: None,
}];
// add handlers for fetching the second stack frame's scopes and variables
// after the user clicked the stack frame
let frame_2_scopes = vec![Scope {
name: "Frame 2 Scope 1".into(),
presentation_hint: None,
variables_reference: 3,
named_variables: None,
indexed_variables: None,
expensive: false,
source: None,
line: None,
column: None,
end_line: None,
end_column: None,
}];
let called_second_stack_frame = Arc::new(AtomicBool::new(false));
let called_first_stack_frame = Arc::new(AtomicBool::new(false));
client.on_request::<Scopes, _>({
let frame_1_scopes = Arc::new(frame_1_scopes.clone());
let frame_2_scopes = Arc::new(frame_2_scopes.clone());
let called_first_stack_frame = called_first_stack_frame.clone();
let called_second_stack_frame = called_second_stack_frame.clone();
move |_, args| match args.frame_id {
1 => {
called_first_stack_frame.store(true, Ordering::SeqCst);
Ok(dap::ScopesResponse {
scopes: (*frame_1_scopes).clone(),
})
}
2 => {
called_second_stack_frame.store(true, Ordering::SeqCst);
Ok(dap::ScopesResponse {
scopes: (*frame_2_scopes).clone(),
})
}
_ => panic!("Made a scopes request with an invalid frame id"),
}
});
let frame_1_variables = vec![
Variable {
name: "variable1".into(),
value: "value 1".into(),
type_: None,
presentation_hint: None,
evaluate_name: None,
variables_reference: 0,
named_variables: None,
indexed_variables: None,
memory_reference: None,
declaration_location_reference: None,
value_location_reference: None,
},
Variable {
name: "variable2".into(),
value: "value 2".into(),
type_: None,
presentation_hint: None,
evaluate_name: None,
variables_reference: 0,
named_variables: None,
indexed_variables: None,
memory_reference: None,
declaration_location_reference: None,
value_location_reference: None,
},
];
let frame_2_variables = vec![
Variable {
name: "variable3".into(),
value: "old value 1".into(),
type_: None,
presentation_hint: None,
evaluate_name: None,
variables_reference: 0,
named_variables: None,
indexed_variables: None,
memory_reference: None,
declaration_location_reference: None,
value_location_reference: None,
},
Variable {
name: "variable4".into(),
value: "old value 2".into(),
type_: None,
presentation_hint: None,
evaluate_name: None,
variables_reference: 0,
named_variables: None,
indexed_variables: None,
memory_reference: None,
declaration_location_reference: None,
value_location_reference: None,
},
];
client.on_request::<Variables, _>({
let frame_1_variables = Arc::new(frame_1_variables.clone());
move |_, args| {
assert_eq!(2, args.variables_reference);
Ok(dap::VariablesResponse {
variables: (*frame_1_variables).clone(),
})
}
});
client
.fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
reason: dap::StoppedEventReason::Pause,
description: None,
thread_id: Some(1),
preserve_focus_hint: None,
text: None,
all_threads_stopped: None,
hit_breakpoint_ids: None,
}))
.await;
cx.run_until_parked();
let running_state =
active_debug_session_panel(workspace, cx).update_in(cx, |item, window, cx| {
cx.focus_self(window);
item.running_state().clone()
});
running_state.update(cx, |running_state, cx| {
let (stack_frame_list, stack_frame_id) =
running_state.stack_frame_list().update(cx, |list, _| {
(
list.flatten_entries(true, true),
list.opened_stack_frame_id(),
)
});
let variable_list = running_state.variable_list().read(cx);
let variables = variable_list.variables();
assert_eq!(Some(1), stack_frame_id);
assert_eq!(
running_state
.stack_frame_list()
.read(cx)
.opened_stack_frame_id(),
Some(1)
);
assert!(
called_first_stack_frame.load(std::sync::atomic::Ordering::SeqCst),
"Request scopes shouldn't be called before it's needed"
);
assert!(
!called_second_stack_frame.load(std::sync::atomic::Ordering::SeqCst),
"Request scopes shouldn't be called before it's needed"
);
assert_eq!(stack_frames, stack_frame_list);
assert_eq!(frame_1_variables, variables);
});
client.on_request::<Variables, _>({
let frame_2_variables = Arc::new(frame_2_variables.clone());
move |_, args| {
assert_eq!(3, args.variables_reference);
Ok(dap::VariablesResponse {
variables: (*frame_2_variables).clone(),
})
}
});
running_state
.update_in(cx, |running_state, window, cx| {
running_state
.stack_frame_list()
.update(cx, |stack_frame_list, cx| {
stack_frame_list.go_to_stack_frame(stack_frames[1].id, window, cx)
})
})
.await
.unwrap();
cx.run_until_parked();
running_state.update(cx, |running_state, cx| {
let (stack_frame_list, stack_frame_id) =
running_state.stack_frame_list().update(cx, |list, _| {
(
list.flatten_entries(true, true),
list.opened_stack_frame_id(),
)
});
let variable_list = running_state.variable_list().read(cx);
let variables = variable_list.variables();
assert_eq!(Some(2), stack_frame_id);
assert!(
called_second_stack_frame.load(std::sync::atomic::Ordering::SeqCst),
"Request scopes shouldn't be called before it's needed"
);
assert_eq!(stack_frames, stack_frame_list);
assert_eq!(variables, frame_2_variables,);
});
}
#[gpui::test]
async fn test_add_and_remove_watcher(executor: BackgroundExecutor, cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(executor.clone());
let test_file_content = r#"
const variable1 = "Value 1";
const variable2 = "Value 2";
"#
.unindent();
fs.insert_tree(
path!("/project"),
json!({
"src": {
"test.js": test_file_content,
}
}),
)
.await;
let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
let workspace = init_test_workspace(&project, cx).await;
workspace
.update(cx, |workspace, window, cx| {
workspace.focus_panel::<DebugPanel>(window, cx);
})
.unwrap();
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
client.on_request::<dap::requests::Threads, _>(move |_, _| {
Ok(dap::ThreadsResponse {
threads: vec![dap::Thread {
id: 1,
name: "Thread 1".into(),
}],
})
});
let stack_frames = vec![StackFrame {
id: 1,
name: "Stack Frame 1".into(),
source: Some(dap::Source {
name: Some("test.js".into()),
path: Some(path!("/project/src/test.js").into()),
source_reference: None,
presentation_hint: None,
origin: None,
sources: None,
adapter_data: None,
checksums: None,
}),
line: 1,
column: 1,
end_line: None,
end_column: None,
can_restart: None,
instruction_pointer_reference: None,
module_id: None,
presentation_hint: None,
}];
client.on_request::<StackTrace, _>({
let stack_frames = Arc::new(stack_frames.clone());
move |_, args| {
assert_eq!(1, args.thread_id);
Ok(dap::StackTraceResponse {
stack_frames: (*stack_frames).clone(),
total_frames: None,
})
}
});
let scopes = vec![Scope {
name: "Scope 1".into(),
presentation_hint: None,
variables_reference: 2,
named_variables: None,
indexed_variables: None,
expensive: false,
source: None,
line: None,
column: None,
end_line: None,
end_column: None,
}];
client.on_request::<Scopes, _>({
let scopes = Arc::new(scopes.clone());
move |_, args| {
assert_eq!(1, args.frame_id);
Ok(dap::ScopesResponse {
scopes: (*scopes).clone(),
})
}
});
let variables = vec![
Variable {
name: "variable1".into(),
value: "value 1".into(),
type_: None,
presentation_hint: None,
evaluate_name: None,
variables_reference: 0,
named_variables: None,
indexed_variables: None,
memory_reference: None,
declaration_location_reference: None,
value_location_reference: None,
},
Variable {
name: "variable2".into(),
value: "value 2".into(),
type_: None,
presentation_hint: None,
evaluate_name: None,
variables_reference: 0,
named_variables: None,
indexed_variables: None,
memory_reference: None,
declaration_location_reference: None,
value_location_reference: None,
},
];
client.on_request::<Variables, _>({
let variables = Arc::new(variables.clone());
move |_, args| {
assert_eq!(2, args.variables_reference);
Ok(dap::VariablesResponse {
variables: (*variables).clone(),
})
}
});
client.on_request::<Evaluate, _>({
move |_, args| {
assert_eq!("variable1", args.expression);
Ok(dap::EvaluateResponse {
result: "value1".to_owned(),
type_: None,
presentation_hint: None,
variables_reference: 2,
named_variables: None,
indexed_variables: None,
memory_reference: None,
value_location_reference: None,
})
}
});
client
.fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
reason: dap::StoppedEventReason::Pause,
description: None,
thread_id: Some(1),
preserve_focus_hint: None,
text: None,
all_threads_stopped: None,
hit_breakpoint_ids: None,
}))
.await;
cx.run_until_parked();
let running_state =
active_debug_session_panel(workspace, cx).update_in(cx, |item, window, cx| {
cx.focus_self(window);
let running = item.running_state().clone();
let variable_list = running.update(cx, |state, cx| {
// have to do this because the variable list pane should be shown/active
// for testing the variable list
state.activate_item(DebuggerPaneItem::Variables, window, cx);
state.variable_list().clone()
});
variable_list.update(cx, |_, cx| cx.focus_self(window));
running
});
cx.run_until_parked();
// select variable 1 from first scope
running_state.update(cx, |running_state, cx| {
running_state.variable_list().update(cx, |_, cx| {
cx.dispatch_action(&SelectFirst);
cx.dispatch_action(&SelectNext);
});
});
cx.run_until_parked();
running_state.update(cx, |running_state, cx| {
running_state.variable_list().update(cx, |_, cx| {
cx.dispatch_action(&AddWatch);
});
});
cx.run_until_parked();
// assert watcher for variable1 was added
running_state.update(cx, |running_state, cx| {
running_state.variable_list().update(cx, |list, _| {
list.assert_visual_entries(vec![
"> variable1",
"v Scope 1",
" > variable1 <=== selected",
" > variable2",
]);
});
});
session.update(cx, |session, _| {
let watcher = session
.watchers()
.get(&SharedString::from("variable1"))
.unwrap();
assert_eq!("value1", watcher.value.to_string());
assert_eq!("variable1", watcher.expression.to_string());
assert_eq!(2, watcher.variables_reference);
});
// select added watcher for variable1
running_state.update(cx, |running_state, cx| {
running_state.variable_list().update(cx, |_, cx| {
cx.dispatch_action(&SelectFirst);
});
});
cx.run_until_parked();
running_state.update(cx, |running_state, cx| {
running_state.variable_list().update(cx, |_, cx| {
cx.dispatch_action(&RemoveWatch);
});
});
cx.run_until_parked();
// assert watcher for variable1 was removed
running_state.update(cx, |running_state, cx| {
running_state.variable_list().update(cx, |list, _| {
list.assert_visual_entries(vec!["v Scope 1", " > variable1", " > variable2"]);
});
});
}
#[gpui::test]
async fn test_refresh_watchers(executor: BackgroundExecutor, cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(executor.clone());
let test_file_content = r#"
const variable1 = "Value 1";
const variable2 = "Value 2";
"#
.unindent();
fs.insert_tree(
path!("/project"),
json!({
"src": {
"test.js": test_file_content,
}
}),
)
.await;
let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
let workspace = init_test_workspace(&project, cx).await;
workspace
.update(cx, |workspace, window, cx| {
workspace.focus_panel::<DebugPanel>(window, cx);
})
.unwrap();
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
client.on_request::<dap::requests::Threads, _>(move |_, _| {
Ok(dap::ThreadsResponse {
threads: vec![dap::Thread {
id: 1,
name: "Thread 1".into(),
}],
})
});
let stack_frames = vec![StackFrame {
id: 1,
name: "Stack Frame 1".into(),
source: Some(dap::Source {
name: Some("test.js".into()),
path: Some(path!("/project/src/test.js").into()),
source_reference: None,
presentation_hint: None,
origin: None,
sources: None,
adapter_data: None,
checksums: None,
}),
line: 1,
column: 1,
end_line: None,
end_column: None,
can_restart: None,
instruction_pointer_reference: None,
module_id: None,
presentation_hint: None,
}];
client.on_request::<StackTrace, _>({
let stack_frames = Arc::new(stack_frames.clone());
move |_, args| {
assert_eq!(1, args.thread_id);
Ok(dap::StackTraceResponse {
stack_frames: (*stack_frames).clone(),
total_frames: None,
})
}
});
let scopes = vec![Scope {
name: "Scope 1".into(),
presentation_hint: None,
variables_reference: 2,
named_variables: None,
indexed_variables: None,
expensive: false,
source: None,
line: None,
column: None,
end_line: None,
end_column: None,
}];
client.on_request::<Scopes, _>({
let scopes = Arc::new(scopes.clone());
move |_, args| {
assert_eq!(1, args.frame_id);
Ok(dap::ScopesResponse {
scopes: (*scopes).clone(),
})
}
});
let variables = vec![
Variable {
name: "variable1".into(),
value: "value 1".into(),
type_: None,
presentation_hint: None,
evaluate_name: None,
variables_reference: 0,
named_variables: None,
indexed_variables: None,
memory_reference: None,
declaration_location_reference: None,
value_location_reference: None,
},
Variable {
name: "variable2".into(),
value: "value 2".into(),
type_: None,
presentation_hint: None,
evaluate_name: None,
variables_reference: 0,
named_variables: None,
indexed_variables: None,
memory_reference: None,
declaration_location_reference: None,
value_location_reference: None,
},
];
client.on_request::<Variables, _>({
let variables = Arc::new(variables.clone());
move |_, args| {
assert_eq!(2, args.variables_reference);
Ok(dap::VariablesResponse {
variables: (*variables).clone(),
})
}
});
client.on_request::<Evaluate, _>({
move |_, args| {
assert_eq!("variable1", args.expression);
Ok(dap::EvaluateResponse {
result: "value1".to_owned(),
type_: None,
presentation_hint: None,
variables_reference: 2,
named_variables: None,
indexed_variables: None,
memory_reference: None,
value_location_reference: None,
})
}
});
client
.fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
reason: dap::StoppedEventReason::Pause,
description: None,
thread_id: Some(1),
preserve_focus_hint: None,
text: None,
all_threads_stopped: None,
hit_breakpoint_ids: None,
}))
.await;
cx.run_until_parked();
let running_state =
active_debug_session_panel(workspace, cx).update_in(cx, |item, window, cx| {
cx.focus_self(window);
let running = item.running_state().clone();
let variable_list = running.update(cx, |state, cx| {
// have to do this because the variable list pane should be shown/active
// for testing the variable list
state.activate_item(DebuggerPaneItem::Variables, window, cx);
state.variable_list().clone()
});
variable_list.update(cx, |_, cx| cx.focus_self(window));
running
});
cx.run_until_parked();
// select variable 1 from first scope
running_state.update(cx, |running_state, cx| {
running_state.variable_list().update(cx, |_, cx| {
cx.dispatch_action(&SelectFirst);
cx.dispatch_action(&SelectNext);
});
});
cx.run_until_parked();
running_state.update(cx, |running_state, cx| {
running_state.variable_list().update(cx, |_, cx| {
cx.dispatch_action(&AddWatch);
});
});
cx.run_until_parked();
session.update(cx, |session, _| {
let watcher = session
.watchers()
.get(&SharedString::from("variable1"))
.unwrap();
assert_eq!("value1", watcher.value.to_string());
assert_eq!("variable1", watcher.expression.to_string());
assert_eq!(2, watcher.variables_reference);
});
client.on_request::<Evaluate, _>({
move |_, args| {
assert_eq!("variable1", args.expression);
Ok(dap::EvaluateResponse {
result: "value updated".to_owned(),
type_: None,
presentation_hint: None,
variables_reference: 3,
named_variables: None,
indexed_variables: None,
memory_reference: None,
value_location_reference: None,
})
}
});
client
.fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
reason: dap::StoppedEventReason::Pause,
description: None,
thread_id: Some(1),
preserve_focus_hint: None,
text: None,
all_threads_stopped: None,
hit_breakpoint_ids: None,
}))
.await;
cx.run_until_parked();
session.update(cx, |session, _| {
let watcher = session
.watchers()
.get(&SharedString::from("variable1"))
.unwrap();
assert_eq!("value updated", watcher.value.to_string());
assert_eq!("variable1", watcher.expression.to_string());
assert_eq!(3, watcher.variables_reference);
});
}