diff --git a/src/calltree.js b/src/calltree.js index 159597c..305ff87 100644 --- a/src/calltree.js +++ b/src/calltree.js @@ -606,7 +606,7 @@ export const find_call = (state, index) => { if(ct_node_id != null) { const ct_node = find_node( - root_calltree_node(state), + state.calltree, n => n.id == ct_node_id ) if(ct_node == null) { @@ -630,7 +630,12 @@ export const find_call = (state, index) => { } const loc = {index: node.index, module: state.current_module} - const {calltree, call} = state.calltree_actions.find_call( + const { + calltree, + call, + is_found_async_call, + async_call_index + } = state.calltree_actions.find_call( loc, get_async_calls(state) ) @@ -645,20 +650,41 @@ export const find_call = (state, index) => { ) } - const merged = make_calltree( - merge_calltrees(root_calltree_node(state), calltree), - get_async_calls(state), - ) + let next_calltree, active_calltree_node - const active_calltree_node = find_same_node( - root_calltree_node({calltree: merged}), - calltree, - call.id - ) + if(is_found_async_call) { + const async_calls = get_async_calls(state) + const prev_call = async_calls[async_call_index] + const merged = merge_calltrees(prev_call, calltree) + const next_async_calls = async_calls.map((c, i) => + i == async_call_index + ? merged + : c + ) + next_calltree = make_calltree( + root_calltree_node(state), + next_async_calls, + ) + active_calltree_node = find_same_node( + merged, + calltree, + call.id + ) + } else { + next_calltree = make_calltree( + merge_calltrees(root_calltree_node(state), calltree), + get_async_calls(state), + ) + active_calltree_node = find_same_node( + root_calltree_node({calltree: next_calltree}), + calltree, + call.id + ) + } return add_frame( expand_path( - {...state, calltree: merged}, + {...state, calltree: next_calltree}, active_calltree_node ), active_calltree_node, diff --git a/src/eval.js b/src/eval.js index e918a58..44889a9 100644 --- a/src/eval.js +++ b/src/eval.js @@ -312,19 +312,40 @@ export const eval_modules = ( const find_call = (location, async_calls) => { searched_location = location - const {modules, calltree} = run() + let is_found_async_call = false + let i + + let {calltree} = run() + + is_recording_async_calls = false if(found_call == null && async_calls != null) { - for(let c of async_calls) { - c.fn.apply(c.context, c.args) + for(i = 0; i < async_calls.length; i++) { + const c = async_calls[i] + try { + c.fn.apply(c.context, c.args) + } catch(e) { + // do nothing. Exception was caught and recorded inside 'trace' + } if(found_call != null) { + is_found_async_call = true + calltree = children[0] + children = null break } } } + + is_recording_async_calls = true + searched_location = null const call = found_call found_call = null - return {modules, calltree, call} + return { + is_found_async_call, + async_call_index: i, + calltree, + call + } } const trace = (fn, name, argscount, __location, get_closure) => { @@ -563,8 +584,15 @@ export const eval_modules = ( return assign_code(parse_result.modules, expanded) }, find_call: (loc, async_calls) => { - const {modules, calltree, call} = actions.find_call(loc, async_calls) + const { + is_found_async_call, + async_call_index, + calltree, + call + } = actions.find_call(loc, async_calls) return { + is_found_async_call, + async_call_index, calltree: assign_code(parse_result.modules, calltree), // TODO: `call` does not have `code` property here. Currently it is // worked around by callers. Refactor diff --git a/test/test.js b/test/test.js index 5d05b72..9fa1f52 100644 --- a/test/test.js +++ b/test/test.js @@ -2313,16 +2313,13 @@ const y = x()` test('async calls', () => { const code = ` - const fn = () => { + export const fn = () => { fn2() } const fn2 = () => { console.log(1) } - - // Use Function constructor to exec impure code for testing - new Function('fn', 'globalThis.__run_async_call = fn')(fn) ` const {get_async_call, on_async_call} = (new Function(` @@ -2338,7 +2335,10 @@ const y = x()` `))() const i = test_initial_state(code, { on_async_call }) - globalThis.__run_async_call(10) + + // Make async call + i.modules[''].fn(10) + const call = get_async_call() assert_equal(call.fn.name, 'fn') assert_equal(call.code.index, code.indexOf('() => {')) @@ -2360,20 +2360,15 @@ const y = x()` assert_equal(nav2.state.current_calltree_node.fn.name, 'fn2') }), - // TODO - /* test('async calls calltree nav', () => { const code = ` - const fn = () => { - fn2() - } - - const fn2 = () => { - console.log(1) + const normal_call = () => { } - // Use Function constructor to exec impure code for testing - new Function('fn', 'globalThis.__run_async_call = fn')(fn) + normal_call(0) + + export const async_call = () => { + } ` const {get_async_call, on_async_call} = (new Function(` @@ -2389,41 +2384,76 @@ const y = x()` `))() const i = test_initial_state(code, { on_async_call }) - globalThis.__run_async_call(10) - const call = get_async_call() - assert_equal(call.fn.name, 'fn') - assert_equal(call.code.index, code.indexOf('() => {')) - assert_equal(call.args, [10]) - const state = COMMANDS.on_async_call(i, call) - assert_equal(get_async_calls(state), [call]) - assert_equal(state.logs.logs.length, 1) + const after_async_calls = [1, 2, 3].reduce( + (s, a) => { + // Make async calls + i.modules[''].async_call(a) + const call = get_async_call() + return COMMANDS.on_async_call(s, call) + }, + i + ) - // Expand call - const {state: expanded} = COMMANDS.calltree.click(state, call.id) - assert_equal(get_async_calls(expanded)[0].children[0].fn.name, 'fn2') + assert_equal( + get_async_calls(after_async_calls).map(c => c.args[0]), + [1,2,3] + ) + + assert_equal(after_async_calls.current_calltree_node.toplevel, true) + + const down = COMMANDS.calltree.arrow_down(after_async_calls).state + + const first_async_call_selected = COMMANDS.calltree.arrow_down( + COMMANDS.calltree.arrow_down(after_async_calls).state + ).state + + // After we press arrow down, first async call gets selected + assert_equal( + first_async_call_selected.current_calltree_node.args[0], + 1, + ) + + // One more arrow down, second async call gets selected + assert_equal( + COMMANDS.calltree.arrow_down(first_async_call_selected) + .state + .current_calltree_node + .args[0], + 2 + ) + + // After we press arrow up when first async call selected, we select last + // visible non async call + assert_equal( + COMMANDS.calltree.arrow_up(first_async_call_selected) + .state + .current_calltree_node + .args[0], + 0 + ) + + // After we press arrow left when first async call selected, we stay on + // this call + assert_equal( + COMMANDS.calltree.arrow_left(first_async_call_selected) + .current_calltree_node + .args[0], + 1 + ) - // Navigate logs - const nav = COMMANDS.calltree.navigate_logs_position(expanded, 0) - assert_equal(nav.state.current_calltree_node.is_log, true) - const nav2 = COMMANDS.calltree.arrow_left(nav.state) - assert_equal(nav2.state.current_calltree_node.fn.name, 'fn2') }), - */ - test_only('async_calls find_call', () => { + test('async_calls find_call', () => { const code = ` - const fn = () => { + export const fn = () => { fn2() } const fn2 = () => { console.log(1) } - - // Use Function constructor to exec impure code for testing - new Function('fn', 'globalThis.__run_async_call = fn')(fn) ` const {get_async_call, on_async_call} = (new Function(` @@ -2439,12 +2469,58 @@ const y = x()` `))() const i = test_initial_state(code, { on_async_call }) - globalThis.__run_async_call(10) + + // Make async call + i.modules[''].fn() + const call = get_async_call() const state = COMMANDS.on_async_call(i, call) const {state: moved} = COMMANDS.move_cursor(state, code.indexOf('fn2')) - console.log('active_calltree_node', moved.active_calltree_node) + assert_equal(moved.active_calltree_node.fn.name, 'fn') + + // Move cursor to toplevel and back, find cached (calltree_node_by_loc) call + const move_back = COMMANDS.move_cursor( + COMMANDS.move_cursor(moved, 0).state, + code.indexOf('fn2') + ).state + + assert_equal(move_back.active_calltree_node.fn.name, 'fn') }), + test('async_calls find_call then async_call bug', () => { + const code = ` + export const fn = () => { /* label */ } + ` + + const {get_async_call, on_async_call} = (new Function(` + let call + return { + get_async_call() { + return call + }, + on_async_call(_call) { + call = _call + } + } + `))() + + const i = test_initial_state(code, { on_async_call }) + + // Make async call + i.modules[''].fn(1) + + const state = COMMANDS.on_async_call(i, get_async_call()) + + // find call + const {state: moved} = COMMANDS.move_cursor(state, code.indexOf('label')) + + // Make async call + i.modules[''].fn(2) + + const result = COMMANDS.on_async_call(moved, get_async_call()) + + // there was a bug throwing error when added second async call + assert_equal(get_async_calls(result).map(c => c.args), [[1], [2]]) + }), ]