import {find_leaf, ancestry, find_node} from '../src/ast_utils.js' import {print_debug_node} from '../src/parse_js.js' import {eval_frame} from '../src/eval.js' import {COMMANDS, with_version_number_of_log} from '../src/cmd.js' import {header} from '../src/value_explorer_utils.js' import { root_calltree_node, active_frame, pp_calltree, get_deferred_calls, current_cursor_position, get_execution_paths, } from '../src/calltree.js' import {color_file} from '../src/color.js' import { test, test_only, assert_equal, stringify, do_parse, assert_code_evals_to, assert_code_evals_to_async, assert_code_error, assert_code_error_async, assert_versioned_value, assert_value_explorer, assert_selection, parse_modules, run_code, test_initial_state, test_initial_state_async, test_deferred_calls_state, print_debug_ct_node, input, input_async, } from './utils.js' export const tests = [ test('reserved words', () => { const result = do_parse('let catch') assert_equal(result.ok, false) assert_equal(result.problems[0].index, 4) }), test('invalid token in the beginning', () => { const result = do_parse('# import') assert_equal(result, { ok: false, problems: [ { message: 'unexpected lexical token', index: 0 } ] }) }), test('invalid token in the middle', () => { const result = do_parse(': # import') assert_equal(result, { ok: false, problems: [ { message: 'unexpected lexical token', index: 2 } ] }) }), test('invalid token in the end', () => { const result = do_parse(': ^') assert_equal(result, { ok: false, problems: [ { message: 'unexpected lexical token', index: 2 } ] }) }), test('empty program', () => { const i = test_initial_state('') const frame = active_frame(i) assert_equal(frame.children, []) assert_equal(frame.result, {ok: true}) }), test('empty if branch', () => { const r = do_parse(` if(true) { } else { } `) assert_equal(r.ok, true) }), test('Must be finished by eof', () => { const result = do_parse('}') assert_equal(result.ok, false) }), test('Only semicolons', () => { const i = test_initial_state(';;;;') const frame = active_frame(i) assert_equal(frame.children, []) assert_equal(frame.result, {ok: true}) }), test('Comments', () => { assert_code_evals_to(` /*Qux */ // Foo 1 //Bar /* Baz */ `, 1 ) }), test('backtick_string', () => { assert_code_evals_to( 'const x = `b`; `a${x}a`', 'aba', ) }), // TODO // test('backtick_string let vars', () => { // assert_code_evals_to( // 'let x = `b`; `a${x}a`', // 'aba', // ) // }), test('Simple expression', () => { return assert_code_evals_to('1+1;', 2) }), test('Logical not', () => { return assert_code_evals_to('!false', true) }), test('function expr', () => { assert_code_evals_to( ` const x = function(){} x.name `, 'x' ) assert_code_evals_to( ` const x = function foo(){} x.name `, 'foo' ) assert_code_evals_to( ` (function foo(x) { return x*2 }).name `, 'foo' ) assert_code_evals_to( ` (function foo(x) { return x*2 })(1) `, 2 ) }), test('function declaration', () => { assert_code_evals_to( ` function x() {return 1} x() `, 1 ) }), test('More complex expression', () => { assert_code_evals_to( ` const plusone = x => x + 1; plusone(3); `, 4 ) }), test('closure', () => { const code = ` const x = 1 const y = () => x; y() ` const i = test_initial_state(code, code.indexOf('x;')) const frame = active_frame(i) assert_equal(frame.children[1].result.value, 1) }), // foo() call fails when tries to get closed variables, because // NOT_INITIALIZED is not initialized at the moment `foo` is called // TODO fix later // test('closure bug', () => { // test_initial_state(` // foo() // const NOT_INITIALIZED = 1 // function foo(){ // return NOT_INITIALIZED // } // `) // }), test('member access', () => { assert_code_evals_to( 'const foo = {bar: {baz: 2}};foo.bar.baz;', 2 ) }), test('optional chaining', () => { assert_code_evals_to(`null?.foo`, undefined) assert_code_evals_to(`{foo:1}?.foo`, 1) }), test('optional chaining computed', () => { assert_code_evals_to(`null?.['foo']`, undefined) assert_code_evals_to(`{foo: 1}?.['foo']`, 1) }), test('factorial', () => { assert_code_evals_to( ` const fac = x => x == 0 ? 1 : x * fac(x - 1); fac(10); `, 3628800 ) }), test('sort_1', () => { assert_code_evals_to( ` const sort = x => x.length == 0 ? [] : [ ...sort(x.slice(1).filter(y => y < x[0])), x[0], ...sort(x.slice(1).filter(y => x[0] <= y)), ]; sort([4, 7, 8, 9, 15, 1, 3, 2, 1]); `, [1, 1, 2, 3, 4, 7, 8, 9, 15] ) }), test('sort_2', () => { assert_code_evals_to( ` const sort = x => { return x.length == 0 ? [] : [ ...sort(x.slice(1).filter(y => y < x[0])), x[0], ...sort(x.slice(1).filter(y => x[0] <= y)), ] }; sort([4, 7, 8, 9, 15, 1, 3, 2, 1]); `, [1, 1, 2, 3, 4, 7, 8, 9, 15] ) }), test('chaining', () => { assert_code_evals_to( 'const foo = () => ({bar: 42}); foo().bar;', 42 ) }), test('logic ops', () => { assert_code_evals_to( ` const foo = false; const bar = true; const baz = false; const foo2 = false; foo || bar && baz || (foo2); `, false ) }), test('strict eq', () => { assert_code_evals_to( `null === undefined`, false ) }), test('ternary', () => { assert_code_evals_to(`true ? 1 : 2;`, 1) }), test('nested ternary', () => { assert_code_evals_to(`false ? 0 : true ? 1 : 2`, 1) }), test('complex expression', () => { assert_code_evals_to('(x => 2*x)(({foo: 1}).foo + 2 + 3)*10;', 120) }), test('function_call spread', () => { assert_code_evals_to( ` const test = (...args) => args[0] + args[1]; const data = [1,2]; const result = test(...data); result `, 3 ) }), test('destructuring array', () => { assert_code_evals_to( ` const [a,b=2,...c] = [1, undefined, 3,4]; [a,b,...c]; `, [1,2,3,4] ) }), test('destructuring object', () => { assert_code_evals_to( ` const {a, b: [b], ...c} = {a: 1, b: [2], c: 3, d: 4}; [a,b,c]; `, [1, 2, {c:3, d: 4}] ) }), test('destructuring function arguments', () => { assert_code_evals_to( ` const test = (first, ...others) => [first, others]; test(1,2,3); `, [1, [2,3]] ) }), /* test('let variable', () => { const code = ` let x, y = 2, unused, [z,q] = [3,4] x = 1 ` const i = test_initial_state(code, code.indexOf('x')) assert_equal(i.value_explorer.result.value, {y: 2, z: 3, q: 4}) }), */ test('let variable not initialized bug', () => { const code = ` let x x /*label*/ ` const i = test_initial_state(code, code.indexOf('x /*label')) assert_equal(i.value_explorer.result.ok, true) assert_equal(i.value_explorer.result.value === undefined, true) }), test('else if', () => { const code = ` let x if(false) { let x x = 0 } else if(true) { x = 1 } else { x = 2 }; x ` assert_code_evals_to( code, 1 ) }), test('if without else', () => { assert_code_evals_to( ` let x if(true) { x = 1 } if(false) { throw new Error() } x `, 1 ) }), test('out of order decl', () => { const i = test_initial_state( ` const y = () => x; const x = 1; y(); `) assert_equal(root_calltree_node(i).children[0].value, 1) }), test('nested closure', () => { assert_code_evals_to( ` const x = () => () => y const y = 1 x()() `, 1 ) }), test('Simple expression ASI', () => { return assert_code_evals_to('1+1', 2) }), test('Closing bracket ASI', () => { return assert_code_evals_to( ` let x if(true) { x = 1 } else { x = 2 }; x `, 1 ) }), test('parse assignment error', () => { const code = ` const x = [0] x[0] = 1, x?.[0] = 2 ` const parse_result = do_parse(code) assert_equal(parse_result.ok, false) }), test('parse assignment ok', () => { const code = ` const x = [0] x[0] = 1 ` const parse_result = do_parse(code) assert_equal(parse_result.ok, true) }), test('ASI_1', () => { const parse_result = do_parse(` 1 const y = 2; `) assert_equal(parse_result.ok, true) assert_equal( parse_result.node.children.map(c => c.type), ['number', 'const'] ) }), test('ASI_2', () => { const parse_result = do_parse(` 1 2 `) assert_equal(parse_result.ok, true) assert_equal( parse_result.node.children.map(c => c.type), ['number', 'number'] ) }), test('ASI_restricted', () => { assert_equal( do_parse(` return 1 `).ok, true ) assert_equal( do_parse(` throw 1 `).ok, false ) }), test('throw', () => { assert_code_error(` const x = () => { throw 1 }; x() `, 1 ) }), test('throw null', () => { assert_code_error(`throw null`, null) }), test('throw null from function', () => { const code = ` const throws = () => { throw null } throws() ` const s = test_initial_state(code) const moved = COMMANDS.move_cursor(s, code.indexOf('throws()')) assert_equal(moved.value_explorer.result.ok, false) assert_equal(moved.value_explorer.result.error, null) }), test('new', () => { assert_code_evals_to('new Error("test").message', 'test') }), test('new constructor expr', () => { assert_code_evals_to(` const x = {Error}; new (x.Error)('test').message `, 'test') }), test('new calls are recorded in calltree', () => { const code = ` const make_class = new Function("return class { constructor(x) { x() } }") const clazz = make_class() const x = () => 1 new clazz(x) ` const i = test_initial_state(code) const find_call = COMMANDS.move_cursor(i, code.indexOf('1')) assert_equal(root_calltree_node(find_call).children.length, 3) const x_call = root_calltree_node(find_call).children[2].children[0] assert_equal(x_call.fn.name, 'x') }), test('new calls step into', () => { const code = `new Set()` const i = test_initial_state(code) const into = COMMANDS.calltree.arrow_down(i) assert_equal(into.current_calltree_node.fn.name, 'Set') assert_equal(into.current_calltree_node.is_new, true) }), test('new call non-constructor', () => { assert_code_error( `const x = () => 1; new x()`, 'TypeError: fn is not a constructor' ) }), test('method chaining', () => { assert_code_evals_to( ` const x = [1,2,3,4]; x.slice(1).slice(1).slice(1); `, [4] ) }), test('error is not a function', () => { assert_code_error( ` const x = null x() `, 'TypeError: x is not a function' ) assert_code_error( ` const foo = () => ([{bar: {}}]) foo()[0].bar.baz() `, 'TypeError: foo(...)[0].bar.baz is not a function' ) }), test('native throws', () => { const s1 = test_initial_state( ` const throws = new Function('throw new Error("sorry")') throws() ` ) assert_equal( root_calltree_node(s1).error.message, "sorry" ) }), test('function name from object literal', () => { const code = ` const fns = {x: () => 1} fns.x() fns.x.name ` const i = test_initial_state(code) assert_equal(root_calltree_node(i).children[0].fn.name, 'x') assert_code_evals_to(code, 'x') }), test('function name from const decl', () => { const code = ` const x = () => 1 x() x.name ` const i = test_initial_state(code) assert_equal(root_calltree_node(i).children[0].fn.name, 'x') assert_code_evals_to( code, 'x', ) }), test('function name deduce', () => { const code = ` const make_fn = () => () => 1 const x = make_fn() x() x.name ` const i = test_initial_state(code) assert_equal(root_calltree_node(i).children[1].fn.name, 'x') assert_code_evals_to( code, 'x', ) }), test('function name dont deduce if already has name', () => { const code = ` const make_fn = () => { const y = () => 1 return y } const x = make_fn() x() x.name ` const i = test_initial_state(code) assert_equal(root_calltree_node(i).children[1].fn.name, 'y') assert_code_evals_to( code, 'y', ) }), /* TODO test('named function scope', () => { const code = 'const x = function y() { y }' const parse_result = do_parse(code) assert_equal(parse_result.ok, true) }), */ test('record call chain', () => { const code = ` const x = () => ({ y: () => 1, }) x().y() ` const s1 = test_initial_state(code) assert_equal(s1.current_calltree_node.children.length, 2) }), test('record native call chain', () => { const code = ` Object.entries({}).map(() => null) ` const s1 = test_initial_state(code) assert_equal(s1.current_calltree_node.children.length, 2) }), test('eval_frame logical short circuit', () => { assert_code_evals_to( `true || false`, true, ) }), test('eval_frame array_literal', () => { assert_code_evals_to( `[1,2,3,...[4,5]];`, [1,2,3,4,5] ) }), test('eval_frame object_literal', () => { assert_code_evals_to( `{foo: 1, ...{bar: 2}, ['baz']: 3};`, {foo:1, bar:2, baz: 3} ) }), test('eval_frame ternary', () => { assert_code_evals_to(`false ? 1 : 2`, 2) }), test('eval_frame unary', () => { assert_code_evals_to(`! false`, true) }), test('typeof', () => { assert_code_evals_to('typeof 1', 'number') }), test('eval_frame unary minus', () => { assert_code_evals_to(`-(1)`, -1) assert_code_evals_to(`-1`, -1) assert_code_evals_to(`-(-1)`, 1) }), test('eval_frame binary', () => { const i = test_initial_state(` 1 + 1 `) assert_equal(active_frame(i).children[0].result.value, 2) }), test('eval_frame instanceof', () => { assert_code_evals_to('1 instanceof Object', false) assert_code_evals_to('{} instanceof Object', true) }), test('eval_frame grouping', () => { const i = test_initial_state('(1+1)') assert_equal(active_frame(i).children[0].result.value, 2) }), test('eval_frame member_access', () => { const i = test_initial_state('{foo: "bar"}["foo"]') assert_equal(active_frame(i).children[0].result.value, 'bar') }), test('eval_frame member_access null', () => { const frame = active_frame(test_initial_state('null["foo"]')) const result = frame.children[0].result assert_equal(result.ok, false) assert_equal( result.error, new TypeError("Cannot read properties of null (reading 'foo')") ) }), test('eval_frame new', () => { const i = test_initial_state('new Error("foobar")') assert_equal(active_frame(i).children[0].result.value.message, 'foobar') }), test('eval_frame function_call', () => { const i = test_initial_state(` const x = () => 1; 2 * x(); `) assert_equal(active_frame(i).children[1].result.value, 2) }), test('eval_frame function_body_expr', () => { const code = ` const x = y => y; x(2); ` const i = test_initial_state(code, code.indexOf('y;')) const result = active_frame(i).children[1].result assert_equal(result.ok, true) assert_equal(result.value, 2) }), test('eval_frame function_body_do', () => { const code = ` const x = y => { return y; const z = 1; }; x(2); ` const i = test_initial_state(code, code.indexOf('return y')) const frame = active_frame(i) const ret = frame.children[1].children[0] const z_after_ret = frame.children[1].children[1] assert_equal(ret.result, {ok: true}) assert_equal(z_after_ret.result, null) }), test('eval_frame if', () => { const i = test_initial_state(` if(1) { const x = 1; } else { const x = 1; } `) const frame = active_frame(i) const _if = frame.children[0] assert_equal(_if.children[0].result.ok, true) assert_equal(_if.children[0].result.value, 1) assert_equal(_if.children[1].result, {ok: true}) assert_equal(_if.children[2].result, null) }), test('eval_frame if without else', () => { const i = test_initial_state(` if(1) { const x = 1; } `) const frame = active_frame(i) const _if = frame.children[0] assert_equal(_if.children.length, 2) assert_equal(_if.children[0].result.ok, true) assert_equal(_if.children[0].result.value, 1) assert_equal(_if.children[1].result, {ok: true}) }), test('eval_frame modules', () => { const i = test_initial_state({ '' : 'import {a} from "a"; export const b = a*2;', 'a' : 'export const a = 1;', }) const frame = active_frame(i) assert_equal(frame.children[1].result, {ok: true}) assert_equal( find_node(frame, n => n.string == 'b').result.value, 2 ) }), test('eval_frame error', () => { const code = ` const x = ({a}) => 0; x(null); ` const frame = active_frame( test_initial_state(code, code.indexOf('0')) ) assert_equal(frame.result, {ok: false}) }), test('eval_frame binary &&', () => { const frame = active_frame(test_initial_state(` const x = () => 1; const y = () => 2; false && x(); y(); `)) assert_equal(frame.children[3].result.value, 2) }), test('eval_frame binary ||', () => { const frame = active_frame(test_initial_state(` const x = () => 1; const y = () => 2; true || x(); y(); `)) assert_equal(frame.children[3].result.value, 2) }), test('eval_frame binary ??', () => { const frame = active_frame(test_initial_state(` const x = () => 1; const y = () => 2; 1 ?? x(); y(); `)) assert_equal(frame.children[3].result.value, 2) }), test('eval_frame null call', () => { const frame = active_frame(test_initial_state(`null()`)) assert_equal(frame.children[0].result.ok, false) }), test('eval_frame non-function call bug', () => { const frame = active_frame(test_initial_state(`Object.assign({}, {}); null()`)) assert_equal(frame.children[frame.children.length - 1].result.ok, false) }), test('eval_frame destructuring args', () => { const code = ` const x = (...a) => a; x(1,2,3); ` const i = test_initial_state(code, code.indexOf('a;')) const frame = active_frame(i) assert_equal(frame.children[0].children[0].children[0].result.value, [1,2,3]) }), test('eval_frame default arg', () => { const code = ` const x = 1 function y(z = x) { return z } y() ` const i = test_initial_state(code, code.indexOf('return z')) const frame = active_frame(i) assert_equal( // value for z in return statement find_node(frame.children[1], n => n.value == 'z').result.value, 1 ) // TODO not implemented //assert_equal( // // value for x in arguments // find_node(frame.children[0], n => n.value == 'x').result.value, // 1 //) }), test('eval_frame const lefthand', () => { const code = ` const x = 1 ` const initial = test_initial_state(code) const frame = active_frame(initial) const x = find_node(frame, n => n.string == 'x') assert_equal(x.result.value, 1) assert_equal(x.result.version_number, 0) }), test('bare return statement', () => { const code = ` function test() { return } test() /*call*/ ` assert_value_explorer( test_initial_state(code, code.indexOf('test() /*call*/')), undefined, ) assert_value_explorer( test_initial_state(code, code.indexOf('return')), undefined, ) }), test('array spread not iterable', () => { assert_code_error( `[...null]`, new Error('null is not iterable'), ) }), test('args spread not iterable', () => { assert_code_error( ` function x() {} x(...null) `, new Error('null is not iterable'), ) }), test('module not found', () => { const parsed = parse_modules( 'a', { 'a' : 'import {b} from "b"; import {c} from "c"', 'b' : 'for' } ) assert_equal(parsed.ok, false) assert_equal( parsed.problems.map(p => ({message: p.message, index: p.index, module: p.module})), [ { message: "failed lo load module c", index: 21, module: "a", }, { message: 'expected expression', index: 0, module: "b" } ] ) }), test('module parse cache', () => { const s = test_initial_state({ '' : `import {b} from 'b'`, 'b' : `import {c} from 'c'`, 'c' : `export const c = 1`, }) // Break file c. If parse result is cached then the file will not be parsed // and the code would not break const spoil_file = {...s, files: {...s.files, 'c': ',,,'}} // change module '' const {state: s2} = input(spoil_file, 'import {c} from "c"', 0) assert_equal(s2.parse_result.ok, true) }), test('modules', () => { const i = test_initial_state( { 'a' : 'export const a = 1;', 'b' : 'import {a} from "a"; export const b = a*2;', 'c1' : 'import {b} from "b"; import {a} from "a"; export const c1 = b*2;', 'c2' : 'import {b} from "b"; import {a} from "a"; export const c2 = b*2;', '' : 'import {c1} from "c1"; import {c2} from "c2"; export const result = c1 + c2;', } ) assert_equal(i.parse_result.sorted, ['a', 'b', 'c1', 'c2', '']) assert_equal(i.modules[''].result, 8) }), test('module loaded just once', () => { /* root -> intermediate1 -> leaf root -> intermediate2 -> leaf */ const i = test_initial_state( { '' : ` import {l1} from "intermediate1"; import {l2} from "intermediate2"; export const is_eq = l1 == l2; `, 'intermediate1' : 'import {leaf} from "leaf"; export const l1 = leaf;', 'intermediate2' : 'import {leaf} from "leaf"; export const l2 = leaf;', 'leaf' : 'export const leaf = {}', } ) // Check that the same symbol improted through different paths gives the // same result assert_equal(i.modules[''].is_eq, true) }), test('modules empty import', () => { const i = test_initial_state({ '': 'import {} from "a"', 'a': 'Object.assign(globalThis, {test_import: true})', }) assert_equal(i.active_calltree_node.ok, true) assert_equal(globalThis.app_window.test_import, true) }), test('modules bare import', () => { const i = test_initial_state({ '': 'import "a"', 'a': 'Object.assign(globalThis, {test_import: true})', }) assert_equal(i.active_calltree_node.ok, true) assert_equal(globalThis.app_window.test_import, true) }), test('bug parser pragma external', () => { const result = do_parse(` // external `) assert_equal(result.ok, true) }), test('module external', () => { const code = ` // external import {foo_var} from 'foo.js' console.log(foo_var) ` const s1 = test_initial_state(code) assert_equal(s1.loading_external_imports_state.external_imports, ['foo.js']) const state = COMMANDS.external_imports_loaded(s1, s1, { 'foo.js': { ok: true, module: { 'foo_var': 'foo_value' }, } }) assert_equal(state.logs.logs[0].args, ['foo_value']) assert_equal(state.loading_external_imports_state, null) }), test('module external input', () => { const initial_code = `` const initial = test_initial_state(initial_code) const edited = ` // external import {foo_var} from 'foo.js' console.log(foo_var) ` const index = edited.indexOf('foo_var') const {state, effects} = input( initial, edited, index ) // embed_value_explorer suspended until external imports resolved assert_equal(effects.length, 1) assert_equal(effects[0].type, 'write') assert_equal( state.loading_external_imports_state.external_imports, ['foo.js'], ) // TODO must have effect embed_value_explorer const next = COMMANDS.external_imports_loaded(state, state, { 'foo.js': { ok: true, module: { 'foo_var': 'foo_value' }, } }) assert_equal(next.loading_external_imports_state, null) assert_equal(next.logs.logs[0].args, ['foo_value']) }), test('module external load error', () => { const code = ` // external import {foo_var} from 'foo.js' console.log(foo_var) ` const initial = test_initial_state(code) const next = COMMANDS.external_imports_loaded(initial, initial, { 'foo.js': { ok: false, error: new Error('Failed to resolve module'), } }) assert_equal(next.parse_result.ok, false) assert_equal( next.parse_result.problems, [ { index: code.indexOf('import'), message: 'Failed to resolve module', module: '', } ] ) }), test('module external cache error bug', () => { const code = ` // external import {foo_var} from 'foo.js' console.log(foo_var) ` const initial = test_initial_state(code) // simulate module load error const next = COMMANDS.external_imports_loaded(initial, initial, { 'foo.js': { ok: false, error: new Error('Failed to resolve module'), } }) const edited = ` // external import {foo_var} from 'foo.js' // edit console.log(foo_var) ` // edit code const {state} = input( next, edited, edited.lastIndexOf('foo_var'), ) // Error must preserve after error assert_equal(next.parse_result.ok, false) assert_equal( next.parse_result.problems, [ { index: code.indexOf('import'), message: 'Failed to resolve module', module: '', } ] ) }), test('module external cache invalidation bug', () => { const code = ` // external import {foo_var} from 'foo.js' ` const initial = test_initial_state(code) // simulate module load error const next = COMMANDS.external_imports_loaded(initial, initial, { 'foo.js': { ok: false, error: new Error('Failed to resolve module'), } }) const edited = `` // edit code const {state, effects} = input( next, edited, 0, ) assert_equal(state.parse_result.ok, true) }), test('modules default export', () => { const modules = { '' : "import foo from 'foo'; foo", 'foo': `export default 1` } assert_code_evals_to(modules , 1) const i = test_initial_state(modules) const s = COMMANDS.goto_definition(i, modules[''].indexOf('foo')).state assert_equal(current_cursor_position(s), modules['foo'].indexOf('1')) assert_equal(s.current_module, 'foo') }), test('modules default import', () => { const code = ` // external import foo from 'foo.js' foo ` const initial = test_initial_state(code) const next = COMMANDS.external_imports_loaded(initial, initial, { 'foo.js': { ok: true, module: { 'default': 'foo_value' }, } }) assert_equal(active_frame(next).children.at(-1).result.value, 'foo_value') }), test('export value explorer', () => { const code = 'export const x = 1' const i = test_initial_state(code) assert_equal(i.value_explorer.result.value, 1) }), // Static analysis test('undeclared', () => { const undeclared_test = ` const foo = 1; const bar = baz => qux(foo, bar, baz, quux); const qux = 3; ` const result = do_parse(undeclared_test) assert_equal(result.problems.length, 1) assert_equal(result.problems[0].message, 'undeclared identifier: quux') }), test('name reuse', () => { assert_code_evals_to( ` const f = f => f; f(x => x + 1)(10); `, 11 ) }), test('assign to itself', () => { const code = ` const x = x; ` return assert_equal(do_parse(code).problems[0].message, 'undeclared identifier: x') }), test('function hoisting', () => { assert_code_evals_to(` function x() { return 1 } x() `, 1 ) assert_code_evals_to(` const y = x() function x() { return 1 } y `, 1 ) }), test('await only inside async fns', () => { const parse_result = do_parse('function x() { await 1 }') assert_equal(parse_result.ok, false) }), test('identifier has already been declared', () => { const code = ` const x = 1 const x = 2 ` const i = test_initial_state(code) assert_equal(i.parse_result.ok, false) assert_equal( i.parse_result.problems, [ { index: code.indexOf('x = 2'), length: 1, message: "Identifier 'x' has already been declared", module: '', } ] ) }), test('identifier has already been declared in fn arg', () => { const code = ` function foo(x) { const x = 1 } ` const i = test_initial_state(code) assert_equal(i.parse_result.ok, false) assert_equal( i.parse_result.problems, [ { index: code.indexOf('x = 1'), length: 1, message: "Identifier 'x' has already been declared", module: '', } ] ) }), test('identifier has been declared twice in args', () => { const code = ` function foo({x,x}) { } ` const i = test_initial_state(code) assert_equal(i.parse_result.ok, false) assert_equal( i.parse_result.problems, [ { index: code.indexOf('x}'), length: 1, message: "Identifier 'x' has already been declared", module: '', } ] ) }), test('identifier has already been declared fn decl', () => { const code = ` const x = 1 function x() { } ` const i = test_initial_state(code) assert_equal(i.parse_result.ok, false) assert_equal( i.parse_result.problems, [ { index: code.indexOf('function x()'), length: 1, message: "Identifier 'x' has already been declared", module: '', } ] ) }), test('identifier has already been declared export', () => { const code = ` export const x = 1 function x() { } ` const i = test_initial_state(code) assert_equal(i.parse_result.ok, false) assert_equal( i.parse_result.problems, [ { index: code.indexOf('function x()'), length: 1, message: "Identifier 'x' has already been declared", module: '', } ] ) }), test('identifier has already been declared import', () => { const code = { '': ` import {x} from 'x.js' function x() { } `, 'x.js': ` export const x = 1 ` } const i = test_initial_state(code) assert_equal(i.parse_result.ok, false) assert_equal( i.parse_result.problems, [ { index: code[''].indexOf('function x()'), length: 1, message: "Identifier 'x' has already been declared", module: '', } ] ) }), test('function decl', () => { const code = ` function fib(n) { if(n == 0 || n == 1) { return n } else { return fib(n - 1) + fib(n - 2) } } fib(6) ` const i = test_initial_state(code) const s = COMMANDS.calltree.arrow_right(COMMANDS.calltree.arrow_down( COMMANDS.calltree.arrow_right(COMMANDS.calltree.arrow_down(i)) )) const s2 = COMMANDS.calltree.arrow_down(s) assert_equal(s2.active_calltree_node.value, 5) }), /* TODO use before assignment test('no use before assignment', () => { const test = ` let x; x; ` return assert_equal(do_parse(test).problems[0].message, 'undeclared identifier: x') }), */ test('goto_definition', () => { const entry = ` import {x} from 'a' const y = x*x ` const a = `export const x = 2` const s = test_initial_state({ '' : entry, a, }) const y_result = COMMANDS.goto_definition(s, entry.indexOf('y')) assert_equal(y_result.effects, null) const x_result_1 = COMMANDS.goto_definition(s, entry.indexOf('x*x')) assert_equal(x_result_1.state.current_module, '') assert_equal(current_cursor_position(x_result_1.state), entry.indexOf('x')) const x_result_2 = COMMANDS.goto_definition(s, entry.indexOf('x')) assert_equal(x_result_2.state.current_module, 'a') assert_equal(current_cursor_position(x_result_2.state), a.indexOf('x = 2')) }), test('assignment', () => { const frame = assert_code_evals_to( ` let x; x = 1; x; `, 1 ) // assert let has result assert_equal(frame.children[0].result, {ok: true}) }), test('multiple assignments', () => { assert_code_evals_to( ` let x, y x = 1, y = 2 {x,y} `, {x: 1, y: 2} ) }), /* TODO assignments destructuring test('multiple assignments destructuring', () => { assert_code_evals_to( ` let x, y x = 1, {y} = {y: 2} {x,y} `, {x: 1, y: 2} ) }), */ test('assigments value explorer', () => { const code = ` let x x = 1 ` const i = test_initial_state(code, code.indexOf('x = 1')) assert_equal(i.value_explorer.result.value, 1) }), test('multiple assigments value explorer', () => { const code = ` let x, y x = 1, y = 2 ` const i = test_initial_state(code, code.indexOf('x = 1')) assert_equal(i.value_explorer.result.value, {x: 1, y: 2}) }), /* TODO test('assignments destructuring value explorer', () => { const code = ` let x, y x = 1, {y} = {y:2} ` const i = test_initial_state(code, code.indexOf('x = 1')) assert_equal(i.value_explorer.result.value, {x: 1, y: 2}) }), */ test('assigments error', () => { const code = ` let x, y x = 1, y = null.foo ` const i = test_initial_state(code, code.indexOf('x = 1')) assert_equal(i.value_explorer.result.ok, false) }), test('block scoping const', () => { assert_code_evals_to( ` const x = 0 if(true) { const x = 1 } x `, 0 ) }), test('block scoping', () => { assert_code_evals_to( ` const x = 10 let y if(true) { const x = 1 y = x } else { const x = 2 y = x } y `, 1 ) }), test('block scoping shadow', () => { assert_code_evals_to( ` let y y = 1 if(true) { let y y = 2 } y `, 1 ) }), test('block scoping shadow bug', () => { assert_code_evals_to( ` let y = 3 if(true) { let y y = 1 if(true) { let y y = 2 } y } y `, 3 ) }), test('step_into', () => { const code = ` const x = () => 1; const y = () => 1; if(1) { x(); } else { y(); } ` const initial = test_initial_state(code) const state = COMMANDS.step_into(initial, code.indexOf('x()')) const call_code = state.current_calltree_node.code assert_equal(call_code.index, code.indexOf('() =>')) assert_equal(current_cursor_position(state), code.indexOf('() =>')) assert_equal(state.value_explorer.index, code.indexOf('() =>')) }), test('step_into deepest', () => { const code = ` const x = () => () => 1; x(2)(3); ` const initial = test_initial_state(code) const next = COMMANDS.step_into(initial, code.indexOf('3')) const cn = next.current_calltree_node.code assert_equal(cn.index, code.indexOf('() => 1')) }), test('step_into expand_calltree_node', () => { const code = ` const x = () => 1 const y = () => x() y() ` const initial = test_initial_state(code) const next = COMMANDS.step_into(initial, code.indexOf('y()')) const cn = next.current_calltree_node.code assert_equal(cn.index, code.indexOf('() => x()')) }), test('step_into native bug', () => { const code = `Object()` const initial = test_initial_state(code) const {state, effects} = COMMANDS.step_into(initial, 0) assert_equal(initial == state, true) assert_equal(effects, { "type": "set_status", "args": [ "Cannot step into: function is either builtin or from external lib" ] }) }), test('coloring', () => { const code = ` const x = () => { throw new Error() } const y = x() ` const initial = test_initial_state(code) // only `throw new Error()` colored assert_equal( color_file(initial, ''), [ { index: code.indexOf('const x'), length: 'const x = '.length, result: { ok: true } }, { index: code.indexOf('x()'), length: 'x()'.length, result: { ok: false, is_error_origin: true } } ] ) const x_call = root_calltree_node(initial).children[0] const step_into = COMMANDS.calltree.select_and_toggle_expanded(initial, x_call.id) assert_equal( color_file(step_into, '').sort((a,b) => a.index - b.index), [ { index: code.indexOf('const x'), length: 'const x = '.length, result: { ok: true } }, { index: code.indexOf('() =>'), length: '()'.length, result: { ok: true } }, { index: code.indexOf('throw'), length: 'throw new Error()'.length, result: { ok: false, is_error_origin: true } }, { index: code.indexOf('x()'), length: "x()".length, result: { ok: false, is_error_origin: true } } ] ) }), test('coloring failed member access', () => { const code = '(null[1])'; const initial = test_initial_state(code) // Color only index access, not grouping braces assert_equal( color_file(initial, ''), [ { index: 1, length: 7, result: { ok: false, is_error_origin: true } } ], ) }), test('coloring if', () => { const code = ` const x = () => { if(false) {/*m1*/ if(true) { 1 } 2 } else { 3 } }/*end*/ x()` const initial = test_initial_state(code) const x_call = root_calltree_node(initial).children[0] const step_into = COMMANDS.calltree.select_and_toggle_expanded(initial, x_call.id) assert_equal( color_file(step_into, '').sort((c1, c2) => c1.index - c2.index), [ { index: code.indexOf('const x'), length: code.indexOf('() =>') - code.indexOf('const x'), result: { ok: true } }, { index: code.indexOf('() =>'), length: code.indexOf(' {/*m1*/') - code.indexOf('() =>') + 1, result: { ok: true } }, { index: code.indexOf(' else'), length: code.indexOf('/*end*/') - code.indexOf(' else'), result: { ok: true } }, { index: code.indexOf('/*end*/'), length: code.length - code.indexOf('/*end*/'), result: { ok: true } }, ] ) }), test('coloring failed toplevel', () => { const code = `throw new Error()` const initial = test_initial_state(code) assert_equal( color_file(initial, ''), [ { index: 0, length: code.length, result: { ok: false, is_error_origin: true } } ] ) }), test('coloring short circuit', () => { const code = `true || false` const initial = test_initial_state(code) assert_equal( color_file(initial, ''), [ { index: 0, length: "true".length, result: { ok: true } } ] ) }), test('coloring nested', () => { const code = // TODO reformat using .trim() `const x = () => { return () => { return 123 } } const y = x()` const initial = test_initial_state(code) const s = COMMANDS.move_cursor(initial, code.indexOf('return')) const coloring = color_file(s, '').sort((c1, c2) => c1.index - c2.index) // Checked by eye, test for regression assert_equal( coloring, [ { index: 0, length: 10, result: { ok: true } }, { index: 10, length: 18, result: { ok: true } }, { index: 56, length: 2, result: { ok: true } }, { index: 58, length: 14, result: { ok: true } } ] ) }), test('coloring function body after move inside', () => { const code = ` const x = () => { 1 } x() ` const i = test_initial_state(code) const moved = COMMANDS.move_cursor(i, code.indexOf('1')) const coloring = color_file(moved, '') const color_body = coloring.find(c => c.index == code.indexOf('(')) assert_equal(color_body.result.ok, true) }), test('coloring error with nested fns', () => { const code = `[1].map(_ => {throw new Error()}).map(x => x + 1)` const i = test_initial_state(code) const coloring = color_file(i, '') const result = {ok: false, is_error_origin: true} assert_equal( coloring, [ { index: 0, length: code.indexOf('_ =>'), result }, { index: code.indexOf(').map(x =>'), length: 1, result }, ] ) }), test('better parse errors', () => { const code = ` const x = z => { 1 2 } ` const r = do_parse(code) assert_equal(r.ok, false) const p = r.problems[0] assert_equal(p.index, code.indexOf('2')) }), test('better parse errors 2', () => { const code = ` if(true) { const x = 1 } else { , } ` const r = do_parse(code) assert_equal(r.ok, false) const p = r.problems[0] assert_equal(p.index, code.indexOf(',')) }), test('better parse errors 3', () => { const code = `[() => { , }] ` const r = do_parse(code) const p = r.problems[0] assert_equal(p.index, code.indexOf(',')) }), test('edit function', () => { const s = test_initial_state(` const x = foo => { return foo*2 }; x(2); `) const s2 = COMMANDS.calltree.select_and_toggle_expanded( s, root_calltree_node(s).children[0].id, ) // Make code invalid const invalid = ` const x = foo => { return }; x(2); ` const s3 = input(s2, invalid, invalid.indexOf('return')).state const edited = ` const x = foo => { return foo*3 }; x(2); ` const n = input(s3, edited, edited.indexOf('return')).state const res = find_leaf(active_frame(n), edited.indexOf('*')) assert_equal(res.result.value, 6) assert_equal( n.calltree_node_by_loc.get('').get(edited.indexOf('foo =>')) == null, false ) }), test('edit function 2', () => { const code = ` const x = () => { return 1 } [1,2,3].map(x) ` const s1 = test_initial_state(code) // Go into first call of `x` const s2 = COMMANDS.calltree.arrow_right(s1) const s3 = COMMANDS.calltree.arrow_right(s2) const s4 = COMMANDS.calltree.arrow_right(s3) assert_equal(s4.current_calltree_node.code.index, code.indexOf('() =>')) const edited = ` const x = () => { return 2 } [1,2,3].map(x) ` const e = input(s4, edited, edited.indexOf('2')).state const active = active_frame(e) assert_equal(active.index, edited.indexOf('() =>')) }), test('edit function modules bug', () => { const s1 = test_initial_state({ '' : ` import {x} from 'x.js' const fn = () => { } `, 'x.js': ` export const x = 1 ` }) const edited = ` import {x} from 'x.js' const fn = () => { 1 } ` const {state: s2} = input(s1, edited, edited.indexOf('1')) const s3 = COMMANDS.move_cursor(s2, edited.indexOf('import')) assert_equal(s3.value_explorer.result.value.x, 1) }), test('edit toplevel', () => { const code = ` const x = () => { return 1 } x() ` const s1 = test_initial_state(code) // Go into call of `x` const s2 = COMMANDS.calltree.arrow_right(s1) const s3 = COMMANDS.calltree.arrow_right(s2) assert_equal(s3.current_calltree_node.code.index, code.indexOf('() =>')) const edited = ` const y = 123 const x = () => { return 1 } x() ` const e = input(s3, edited, edited.indexOf('123')).state assert_equal(e.active_calltree_node.toplevel, true) }), test('edit module not_loaded', () => { const s1 = COMMANDS.change_current_module( test_initial_state({ '' : '', "x": 'export const x = 1', }), 'x' ) const e = input(s1, 'export const x = 2', 0).state assert_equal(e.current_calltree_node.module, '') assert_equal(e.active_calltree_node, null) }), test('edit function unreachable', () => { const code = ` const x = () => { return 1 } const y = () => { return 2 } x() ` const s1 = test_initial_state(code) // Go into call of `x` const s2 = COMMANDS.calltree.arrow_right(s1) const s3 = COMMANDS.calltree.arrow_right(s2) const edited = ` const x = () => { return 1 } const y = () => { return 3 } x() ` const moved = COMMANDS.move_cursor(s3, code.indexOf('2')) const e = input(moved, edited, edited.indexOf('3')).state assert_equal(e.active_calltree_node, null) assert_equal(e.current_calltree_node.toplevel, true) }), test('edit function step out', () => { const code = ` const x = () => { return 1 } x() ` const i = test_initial_state(code) const edited = input(i, code.replace('1', '100'), code.indexOf('1')).state const left = COMMANDS.calltree.arrow_left(edited) assert_equal(left.active_calltree_node.toplevel, true) }), test('expand_calltree_node', () => { // Test expecting MAX_DEPTH = 1 const s = test_initial_state(` const countdown = c => c == 0 ? 0 : 1 + countdown(c - 1); countdown(10) `) const first = root_calltree_node(s).children[0] assert_equal(first.value, 10) const s2 = COMMANDS.calltree.select_and_toggle_expanded(s, first.id) const first2 = root_calltree_node(s2).children[0] assert_equal(first2.children[0].value, 9) assert_equal(first2.code, first2.children[0].code) }), test('expand_calltree_node new', () => { const code = ` const make_class = new Function("return class { constructor(x) { x() } }") const clazz = make_class() const x = () => 1 new clazz(x) ` const s = test_initial_state(code) const new_call = root_calltree_node(s).children.at(-1) const expanded_new_call = COMMANDS.calltree.select_and_toggle_expanded(s, new_call.id) const x_call = root_calltree_node(expanded_new_call) .children.at(-1) .children[0] assert_equal(x_call.fn.name, 'x') }), test('expand_calltree_node native', () => { const s = test_initial_state(`[1,2,3].map(x => x + 1)`) const map = root_calltree_node(s).children[0] const s2 = COMMANDS.calltree.select_and_toggle_expanded(s, map.id) const map_expanded = root_calltree_node(s2).children[0] assert_equal(map_expanded.children.length, 3) }), test('value_explorer arguments', () => { const i = test_initial_state(` function foo(x, {y}) { } foo(1, {y: 2}) `) const expanded = COMMANDS.calltree.select_and_toggle_expanded(i, root_calltree_node(i).children[0].id) const args = expanded.value_explorer.result.value['*arguments*'] assert_equal(args, {value: {x: 1, y: 2}}) }), test('click native calltree node', () => { const s = test_initial_state(`Object.fromEntries([])`) const index = 0 // Where call starts const call = root_calltree_node(s).children[0] const state = COMMANDS.calltree.select_and_toggle_expanded(s, call.id) assert_equal(current_cursor_position(state), index) assert_equal( state.value_explorer, { index, result: { "ok": true, "is_calltree_node_explorer": true, "value": { "*arguments*": { value: [ [] ], }, "*return*": { value: {}, } } } } ) }), test('jump_calltree_location' , () => { const code = ` const x = foo => foo + 1; const y = arr => { return arr.map(x) } y([1,2,3]) ` const assert_loc = (s, substring) => { const state = COMMANDS.calltree.arrow_right(s) const index = code.indexOf(substring) assert_equal(current_cursor_position(state), index) assert_equal(active_frame(state) != null, true) return state } const s1 = test_initial_state(code) // Select call of `y()` const s2 = assert_loc(s1, 'y([') // Expand call of `y()` const s3 = assert_loc(s2, 'arr =>') // Select call of arr.map const s4 = assert_loc(s3, 'arr.map') // Expand call of arr.map // native call is not expandable const s5 = assert_loc(s4, 'arr.map') // Select call of x const s6 = assert_loc(s5, 'foo =>') }), test('jump_calltree select callsite', () => { const code = ` function x(y) {} x() ` const i = test_initial_state(code) const call_selected = COMMANDS.calltree.arrow_right(i) const node = call_selected.selection_state.node assert_equal(node.index, code.indexOf('x()')) assert_equal(node.length, 'x()'.length) }), // Test very specific case test('jump_calltree_location after error', () => { const code = ` const fail = () => { throw new Error('fail') } const good = () => {/*good*/} [good, fail].forEach(fn => fn()) ` const s = test_initial_state(code) const call_fn = root_calltree_node(s).children[0].children[0] const s2 = COMMANDS.calltree.select_and_toggle_expanded(s, call_fn.id) const good = s2.current_calltree_node.children[0] assert_equal(good.code.index, code.indexOf('() => {/*good')) }), test('jump_calltree select another call of the same fn', () => { const code = '[1,2].map(x => x*10)' const i = test_initial_state(code, code.indexOf('10')) assert_equal(i.value_explorer.result.value, 10) const second_iter = COMMANDS.calltree.arrow_down(i) const moved = COMMANDS.move_cursor(second_iter, code.indexOf('x*10')) assert_equal(moved.value_explorer.result.value, 20) }), test('unwind_stack', () => { const s = test_initial_state(` const y = () => 1 const deep_error = x => { if(x == 10) { throw 'deep_error' } else { y() deep_error(x + 1) } } deep_error(0) `) assert_equal(s.active_calltree_node.toplevel, true) assert_equal(s.current_calltree_node.id, s.active_calltree_node.id) const first = root_calltree_node(s).children[0] const depth = (node, i = 0) => { if(node.children == null) { return i } assert_equal(s.calltree_node_is_expanded[node.id], true) assert_equal(node.children.length, 2) return depth(node.children[1], i + 1) } assert_equal(depth(first), 10) assert_equal(first.ok, false) assert_equal(first.error, 'deep_error') }), /* Test when node where error occured has subcalls */ test('unwind_stack 2', () => { const code = ` const x = () => 1 const error = () => { x() null.y } error() ` const s = test_initial_state(code) assert_equal(s.current_calltree_node.toplevel, true) }), //TODO this test is fine standalone, but it breaks self-hosted test /* test('unwind_stack overflow', () => { const s = test_initial_state(` const overflow = x => overflow(x + 1); overflow(0) `) assert_equal( s.current_calltree_node.error.message, 'Maximum call stack size exceeded' ) assert_equal(s.current_calltree_node.toplevel, true) assert_equal(s.calltree_node_is_expanded[s.current_calltree_node.id], true) }), */ test('eval_selection', () => { const code = ` const x = () => () => 1 x() 2*2 false && 4 if(true) { } ` const s0 = test_initial_state(code) const s1 = COMMANDS.eval_selection(s0, code.indexOf('2'), true).state assert_equal(s1.value_explorer.result.value, 2) // Expand selection const s2 = COMMANDS.eval_selection(s1, code.indexOf('2'), true).state assert_equal(s2.value_explorer.result.value, 4) const s3 = COMMANDS.eval_selection(s2, code.indexOf('2'), true).state // Selection is not expanded beyond expression to statement assert_equal(s3.value_explorer.result.value, 4) assert_equal(s3.selection_state.node.index, code.indexOf('2')) assert_equal(s3.selection_state.node.length, 3) const s4 = COMMANDS.step_into(s0, code.indexOf('x()')) const s5 = COMMANDS.eval_selection(s4, code.indexOf('2')) assert_equal(s5.effects, {type: 'set_status', args: ['out of scope']}) const s6 = COMMANDS.eval_selection(s4, code.indexOf('1')) assert_equal( s6.effects, { type: 'set_status', args: ['code was not reached during program execution'] } ) const s7 = COMMANDS.eval_selection(s0, code.indexOf('4')) assert_equal( s7.effects, { type: 'set_status', args: ['expression was not reached during program execution'], } ) const s8 = COMMANDS.eval_selection(s0, code.indexOf('if')) assert_equal( s8.effects, { type: 'set_status', args: ['can only evaluate expression, not statement'], } ) }), test('eval_selection bug', () => { const code = `{foo: 1}` const i = test_initial_state(code) const index = code.indexOf('1') const moved = COMMANDS.move_cursor(i, index) const selection = COMMANDS.eval_selection(moved, index, true).state const selection2 = COMMANDS.eval_selection(selection, index, true).state const selection3 = COMMANDS.eval_selection(selection2, index, false).state assert_equal(selection3.selection_state.node.value, '1') }), test('find_call', () => { const code = ` const y = () => y2() const z = () => z2() const y2 = () => 1 const z2 = () => 2 const target = (x) => target2(x) const target2 = (x) => target3(x) const target3 = (x) => 3 const deep_call = x => { if(x == 10) { target(x) } else { y() deep_call(x + 1) z() } } deep_call(0) ` const s1 = test_initial_state(code) const s2 = COMMANDS.move_cursor(s1, code.indexOf('target2(x)')) assert_equal(s2.current_calltree_node.id, s2.active_calltree_node.id) assert_equal(s2.current_calltree_node.args, [10]) assert_equal(s2.current_calltree_node.code.index, code.indexOf('(x) => target2')) const root = root_calltree_node(s2) const first = root.children[0] assert_equal(first.ok, true) const find_target = (node, i = 0) => { if(node.children.length == 1) { return [i, node.children[0]] } assert_equal(s2.calltree_node_is_expanded[node.id], true) assert_equal(node.children.length, 3) assert_equal(node.code != null, true) return find_target(node.children[1], i + 1) } const [depth, target] = find_target(first) assert_equal(depth, 10) assert_equal(target.args, [10]) const target2 = target.children[0] }), test('find_call error', () => { const code = ` const unreachable = () => { 1 } const throws = () => { throw new Error('bad') } throws() ` const s1 = test_initial_state(code) const state = COMMANDS.move_cursor(s1, code.indexOf('1')) assert_equal(state.active_calltree_node, null) assert_equal(state.current_calltree_node.toplevel, true) assert_equal(state.value_explorer === null, true) }), test('find_call with native call', () => { const code = ` [1,2,3].map(x => x + 1) ` const s1 = test_initial_state(code) const s2 = COMMANDS.move_cursor(s1, code.indexOf('x + 1')) assert_equal(s2.current_calltree_node.code.index, code.indexOf('x =>')) }), test('find_call should find first call', () => { const code = ` const rec = i => i == 0 ? 0 : rec(i - 1) rec(10) ` const s1 = test_initial_state(code) const state = COMMANDS.move_cursor(s1, code.indexOf('i == 0')) assert_equal(state.current_calltree_node.args, [10]) }), test('select_return_value not expanded', () => { const code = ` const x = (a) => 1 x() ` const s1 = test_initial_state(code) const s2 = COMMANDS.calltree.arrow_right(s1) const {state: s3, effects} = COMMANDS.calltree.select_return_value(s2) assert_equal(s3.value_explorer.result.value, 1) assert_equal(s3.selection_state.node.index, code.indexOf('x()')) assert_equal(current_cursor_position(s3), code.indexOf('x()')) assert_equal(effects, {type: 'set_focus'}) }), test('select_return_value expanded', () => { const code = ` const x = (a) => 1 x() ` const s1 = test_initial_state(code) const s2_0 = COMMANDS.calltree.arrow_right(s1) // Expand const s2 = COMMANDS.calltree.arrow_right(s2_0) const {state: s3, effects} = COMMANDS.calltree.select_return_value(s2) assert_equal(s3.value_explorer.result.value, 1) assert_equal(s3.selection_state.node.index, code.indexOf('1')) assert_equal(current_cursor_position(s3), code.indexOf('1')) assert_equal(effects, {type: 'set_focus'}) }), test('select_return_value fn curly braces', () => { const code = ` const x = (a) => {return 1} x() ` const s1 = test_initial_state(code) const s2_0 = COMMANDS.calltree.arrow_right(s1) // Expand const s2 = COMMANDS.calltree.arrow_right(s2_0) const {state: s3, effects} = COMMANDS.calltree.select_return_value(s2) assert_equal(s3.value_explorer.result.value, 1) assert_equal(s3.selection_state.node.index, code.indexOf('1')) assert_equal(current_cursor_position(s3), code.indexOf('1')) assert_equal(effects, {type: 'set_focus'}) }), test('select_return_value fn curly braces no return', () => { const code = ` const x = (a) => { 1 } x() ` const s1 = test_initial_state(code) const s2_0 = COMMANDS.calltree.arrow_right(s1) // Expand const s2 = COMMANDS.calltree.arrow_right(s2_0) const {state: s3, effects} = COMMANDS.calltree.select_return_value(s2) assert_equal(s3.selection_state, null) assert_equal(current_cursor_position(s3), code.indexOf('{')) assert_equal(effects, {type: 'set_focus'}) }), test('select_return_value native', () => { const code = ` [1,2,3].map(() => 1) ` const s1 = test_initial_state(code) // Select map const s2 = COMMANDS.calltree.arrow_right(s1) const {state: s3, effects} = COMMANDS.calltree.select_return_value(s2) assert_equal(s3.value_explorer.result.value, [1, 1, 1]) }), test('select_return_value new call', () => { const code = `new String('1')` const s1 = test_initial_state(code) const s2 = COMMANDS.calltree.arrow_right(s1) const {state: s3, effects} = COMMANDS.calltree.select_return_value(s2) assert_equal(s3.value_explorer.result.value, '1') }), test('select_arguments not_expanded', () => { const code = ` const x = (a) => { 1 } x(1) ` const s1 = test_initial_state(code) // focus call const s2 = COMMANDS.calltree.arrow_right(s1) const s3 = COMMANDS.calltree.select_arguments(s2) assert_equal(s3.state.value_explorer.result.ok, true) assert_equal(s3.state.value_explorer.result.value, [1]) assert_equal(current_cursor_position(s3.state), code.indexOf('(1)')) assert_equal(s3.effects, {type: 'set_focus'}) }), test('select_arguments expanded', () => { const code = ` const x = (a) => { 1 } x(1) ` const s1 = test_initial_state(code) // focus call const s2_0 = COMMANDS.calltree.arrow_right(s1) // expand call const s2 = COMMANDS.calltree.arrow_right(s2_0) const s3 = COMMANDS.calltree.select_arguments(s2) assert_equal( s3.state.value_explorer.result, { ok: true, value: {a: 1}, version_number: 0, } ) assert_equal(current_cursor_position(s3.state), code.indexOf('(a)')) assert_equal(s3.effects, {type: 'set_focus'}) }), test('select_arguments new call', () => { const code = `new String("1")` const s1 = test_initial_state(code) const s2 = COMMANDS.calltree.arrow_right(s1) const s3 = COMMANDS.calltree.select_arguments(s2).state assert_equal(s3.value_explorer.result.ok, true) assert_equal(s3.value_explorer.result.value, ["1"]) }), test('select_error', () => { const code = ` const deep = x => { if(x == 10) { throw new Error() } else { deep(x + 1) } } deep(0) ` const i = test_initial_state(code, code.indexOf('deep(x + 1)')) const {state: found_err_state, effects} = COMMANDS.calltree.select_error(i) assert_equal(found_err_state.active_calltree_node.args, [10]) assert_equal(current_cursor_position(found_err_state), code.indexOf('throw')) }), test('select_error in native fn', () => { const code = ` function x() { Object.entries(null) } x() ` const i = test_initial_state(code) const {state: found_err_state} = COMMANDS.calltree.select_error(i) assert_equal(found_err_state.active_calltree_node.fn.name, 'x') assert_equal( current_cursor_position(found_err_state), code.indexOf('Object.entries') ) }), test('move_cursor arguments', () => { const code = ` const x = (a, b) => { } x(1, 2) ` const s1 = test_initial_state(code) // focus call const s2 = COMMANDS.calltree.arrow_right(s1) // expand call const s3 = COMMANDS.calltree.arrow_right(s2) const s4 = COMMANDS.move_cursor(s3, code.indexOf('a')) const selected = '(a, b)' assert_equal(s4.value_explorer, { index: code.indexOf(selected), length: selected.length, result: {ok: true, value: {a: 1, b: 2}, version_number: 0}, }) }), test('move_cursor concise fn', () => { const code = ` const x = y => y*2 x(2) ` const s1 = test_initial_state(code) const s2 = COMMANDS.move_cursor(s1, code.indexOf('2')) assert_equal(s2.value_explorer.index, code.indexOf('y*2')) assert_equal(s2.value_explorer.length, 3) assert_equal(s2.value_explorer.result.ok, true) assert_equal(s2.value_explorer.result.value, 4) }), test('move_cursor let', () => { const code = ` let x = 1 ` const s1 = test_initial_state(code) const s2 = COMMANDS.move_cursor(s1, code.indexOf('x')) const lettext = 'let x = 1' assert_equal(s2.value_explorer, { index: code.indexOf(lettext), length: lettext.length, result: {ok: true, value: 1, version_number: 0}, }) }), test('move_cursor destructuring default', () => { const code = `const [x = 1, y] = [undefined, 2]` const s = test_initial_state(code) assert_equal(s.value_explorer.result.value, {x: 1, y: 2}) }), test('move_cursor after type toplevel', () => { const code = `1` const s1 = test_initial_state(code) const s2 = COMMANDS.move_cursor(s1, code.indexOf('1') + 1) assert_equal(s2.value_explorer.result.value, 1) }), test('move_cursor after type fn', () => { const code = ` const x = () => { 1 } x() ` const s1 = test_initial_state(code) const s2 = COMMANDS.step_into(s1, code.indexOf('x()')) const s3 = COMMANDS.move_cursor(s2, code.indexOf('1') + 1) assert_equal(s3.value_explorer.result.value, 1) }), test('move_cursor between statements', () => { const code = ` 1 /*marker*/ 1 ` const s1 = test_initial_state(code) const s2 = COMMANDS.move_cursor(s1, code.indexOf('/') - 1) assert_equal(s2.value_explorer === null, true) }), test('move_cursor step_into fn', () => { const code = ` const x = () => { 1 } ` const s1 = test_initial_state(code) const s2 = COMMANDS.move_cursor(s1, code.indexOf('1')) assert_equal(s2.value_explorer === null, true) }), test('move_cursor brace', () => { const code = ` if(true) { 1 } ` const s1 = test_initial_state(code) const s2 = COMMANDS.move_cursor(s1, code.indexOf('{')) assert_equal(s2.value_explorer === null, true) }), test('move_cursor concise fn throws', () => { const code = ` const throws = () => { throw new Error('boom') } const x = () => 2 * (throws() + 1) x() ` const s1 = test_initial_state(code) const s2 = COMMANDS.move_cursor(s1, code.indexOf('throws()')) assert_equal(s2.value_explorer.result.error.message, 'boom') }), test('move_cursor error in fn args bug', () => { const code = ` function x() {} x(null.foo) ` const i = test_initial_state(code) const m = COMMANDS.move_cursor(i, code.indexOf('x(null')) assert_equal( m.value_explorer.result.error, new TypeError("Cannot read properties of null (reading 'foo')") ) }), test('frame follows cursor toplevel', () => { const code = ` const x = () => { 1 } x() ` const s1 = test_initial_state(code) const s2 = COMMANDS.move_cursor(s1, code.indexOf('const')) assert_equal(s2.current_calltree_node.toplevel, true) assert_equal(s2.active_calltree_node.toplevel, true) }), test('frame follows cursor fn', () => { const code = ` const x = () => { 1 2 } x() ` const s1 = test_initial_state(code) const s2 = COMMANDS.move_cursor(s1, code.indexOf('1')) assert_equal(s2.current_calltree_node.code.index, code.indexOf('() =>')) // Move within current node const s3 = COMMANDS.move_cursor(s2, code.indexOf('2')) assert_equal(s3.current_calltree_node.code.index, code.indexOf('() =>')) }), test('frame follows cursor return back to fn', () => { const code = ` const x = () => { 1 } x() ` const s1 = test_initial_state(code) const s2 = COMMANDS.move_cursor(s1, code.indexOf('1')) // Go back toplevel const s3 = COMMANDS.move_cursor(s2, code.indexOf('const')) assert_equal(s3.current_calltree_node.toplevel, true) // Go back to fn assert_equal(s3.rt_cxt == null, false) const s4 = COMMANDS.move_cursor( {...s3, // Set rt_cxt to null, ensure eval would not be called again rt_cxt: null }, code.indexOf('1') ) assert_equal(s4.current_calltree_node.code.index, code.indexOf('() =>')) }), // Tests for one specific bug test('frame follows cursor change fn', () => { const code = ` const x = () => { 1 } const y = () => {/*y*/ 2 z() } const z = () => { 3 } x() y() ` const s1 = test_initial_state(code) // goto x() const s2 = COMMANDS.move_cursor(s1, code.indexOf('1')) // goto y() const s3 = COMMANDS.move_cursor(s2, code.indexOf('2')) assert_equal(s3.active_calltree_node.code.index, code.indexOf('() => {/*y')) }), test('frame follows cursor deep nested fn', () => { const code = ` const y = () => { 1 } const x = i => i == 0 ? y() : x(i - 1) x(5) ` const s1 = test_initial_state(code) const s2 = COMMANDS.move_cursor(s1, code.indexOf('1')) assert_equal(s2.current_calltree_node.code.index, code.indexOf('() =>')) }), test('frame follows cursor intermediate fn', () => { const code = ` const y = () => { z() } const z = () => { 1 } const x = i => i == 0 ? y() : x(i - 1) x(5) ` const s1 = test_initial_state(code) const s2 = COMMANDS.move_cursor(s1, code.indexOf('1')) const s3 = COMMANDS.move_cursor(s2, code.indexOf('z()')) assert_equal(s3.current_calltree_node.code.index, code.indexOf('() =>')) // Check that node for `y` call was reused assert_equal( find_node(root_calltree_node(s2), n => n == s3.current_calltree_node) == null, false ) }), test('frame follows cursor unreachable fn', () => { const code = ` const x = () => { 1 2 } ` const s1 = test_initial_state(code) const s2 = COMMANDS.move_cursor(s1, code.indexOf('1')) assert_equal(s2.current_calltree_node.toplevel, true) assert_equal(s2.active_calltree_node, null) // Check that when we move cursor inside unreachable function, find_call // not called again assert_equal(s2.rt_cxt != null, true) const s3 = COMMANDS.move_cursor( // Set rt_cxt to null, ensure it would not be called again {...s2, rt_cxt: null}, code.indexOf('2') ) assert_equal(s3.active_calltree_node, null) }), test('frame follows cursor only find_call in entrypoint module', () => { const scratch = `import {x} from 'x'; x()` const x_code = `export const x = () => 1; x()` const s1 = test_initial_state({ '' : scratch, 'x' : x_code, }) const s2 = COMMANDS.move_cursor( {...s1, current_module: 'x'}, x_code.indexOf('1') ) assert_equal(root_calltree_node(s2).module, '') }), test('find branch initial', () => { const code = ` function x(cond) { if(cond) { return true } else { return false } } x(true) x(false) ` const i = test_initial_state(code, code.indexOf('return false')) assert_equal(i.value_explorer.result.value, false) }), test('find branch empty branch', () => { const code = ` function x(cond) { if(cond) { /* label */ } } x(false) x(true) ` const i = test_initial_state(code, code.indexOf('label')) assert_equal(i.active_calltree_node.args[0], true) }), test('find branch move_cursor', () => { const code = ` function x(cond) { if(cond) { return true } else { return false } } x(true) x(false) ` const i = test_initial_state(code) const moved = COMMANDS.move_cursor(i, code.indexOf('return false')) assert_equal(moved.value_explorer.result.value, false) assert_equal( i.colored_frames != moved.colored_frames, true ) }), test('find branch ternary', () => { const code = ` function x(cond) { return cond ? true : false } x(true) x(false) ` const i = test_initial_state(code, code.indexOf('false')) assert_equal(i.value_explorer.result.value, false) }), test('find branch move cursor within fn', () => { const code = ` function x(cond) { if(cond) { return true } else { return false } } x(true) x(false) ` const i = test_initial_state(code) const s1 = COMMANDS.move_cursor(i, code.indexOf('return false')) const s2 = COMMANDS.move_cursor(s1, code.indexOf('return true')) assert_equal(s2.value_explorer.result.value, true) assert_equal( s1.colored_frames != s2.colored_frames, true ) }), test('find branch fibonacci', () => { const code = ` function fib(n) { if(n == 0 || n == 1) { return n } else { return fib(n - 1) + fib(n - 2) } } fib(6) ` const i = test_initial_state(code) const moved = COMMANDS.move_cursor(i, code.indexOf('return n')) assert_equal(moved.value_explorer.result.value, 1) }), test('find branch after if with return', () => { const code = ` function x(cond) { if(cond) { return true } 1 } x(true) x(false) ` const i = test_initial_state(code, code.indexOf('1')) assert_equal(i.value_explorer.result.value, 1) }), test('find branch after if with return complex', () => { const code = ` function x(a, b) { if(a) { return true } if(a) { return true } if(b) { return true } else { if(false) { return null } 1 } } x(true) x(false, true) x(false, false) ` const i = test_initial_state(code, code.indexOf('1')) assert_equal(i.value_explorer.result.value, 1) assert_equal(i.active_calltree_node.args, [false, false]) }), test('find branch get_execution_paths', () => { const code = ` function x() { if(true) {/*1*/ } if(false) { } else {/*2*/ if(true) {/*3*/ true ? 4 : 5 } return null } // not executed if(true) { } // not executed true ? 6 : 7 } x() ` const i = test_initial_state(code, code.indexOf('if')) assert_equal( [...get_execution_paths(active_frame(i))].toSorted((a,b) => a - b), [ code.indexOf('if(true)') + 1, code.indexOf('/*1*/') - 1, code.indexOf('/*2*/') - 1, code.indexOf('if(true) {/*3*/') + 1, code.indexOf('/*3*/') - 1, code.indexOf('4'), ] ) }), test('find branch get_execution_paths consice body', () => { const code = ` const x = () => true ? 1 : 2 x() ` const i = test_initial_state(code, code.indexOf('true')) assert_equal( get_execution_paths(active_frame(i)), [code.indexOf('1')], ) }), test('find branch get_execution_paths nested fn', () => { const code = ` function x() { function y() { true ? 1 : 2 } } x() ` const i = test_initial_state(code, code.indexOf('{')) assert_equal( get_execution_paths(active_frame(i)), [], ) }), test('find branch jump_calltree_node', () => { const code = ` function test(x) { if(x > 0) { 'label' } } test(1) test(2) ` const i = test_initial_state(code, code.indexOf('label')) assert_equal(i.active_calltree_node.args[0], 1) // select second call const second = COMMANDS.calltree.select_and_toggle_expanded(i, root_calltree_node(i).children[1].id) assert_equal(second.active_calltree_node.args[0], 2) }), test('find branch preserve selected calltree node when moving inside fn', () => { const code = ` function x(cond) { if(cond) { true } else { false } 'finish' } x(true) x(false) ` const i = test_initial_state(code) const first_call_id = root_calltree_node(i).children[0].id // explicitly select first call const selected = COMMANDS.calltree.select_and_toggle_expanded(i, first_call_id) // implicitly select second call by moving cursor const moved = COMMANDS.move_cursor(selected, code.indexOf('false')) const finish = COMMANDS.move_cursor(moved, code.indexOf('finish')) assert_equal(finish.active_calltree_node.id, first_call_id) }), test('find branch select calltree node from logs', () => { const code = ` function f(x) { if(x > 1) { console.log(x) } else { console.log(x) } } f(5) f(10) ` const i = test_initial_state(code) const log_selected = COMMANDS.calltree.navigate_logs_position(i, 1) const moved = COMMANDS.move_cursor( log_selected, code.indexOf('console.log') ) assert_equal(moved.active_calltree_node.args, [10]) }), test('find branch deferred calls', () => { const code = ` export const foo = arg => { return arg } foo(1) ` const {state: i, on_deferred_call} = test_deferred_calls_state(code) // Make deferred call i.modules[''].foo(2) const state = on_deferred_call(i) const call = get_deferred_calls(state)[0] assert_equal(call.value, 2) // Expand call const expanded = COMMANDS.calltree.select_and_toggle_expanded(state, call.id) const moved = COMMANDS.move_cursor(expanded, code.indexOf('return arg')) assert_equal(moved.active_calltree_node.value, 2) }), test('stale id in frame function_call.result.calls bug', () => { const code = ` const x = () => {/*x*/ y() } const y = () => { 1 } x() ` // Eval toplevel frame, id of call (x) will be saved in frame const s1 = test_initial_state(code) // Expand call of x(), id will be changed (bug) const s2 = COMMANDS.move_cursor(s1, code.indexOf('y()')) // Step into from toplevel to call of x(), the stale id will be used const s3 = COMMANDS.move_cursor(s2, code.indexOf('x()')) const s4 = COMMANDS.step_into(s3, code.indexOf('x()')) assert_equal(s4.active_calltree_node.code.index, code.indexOf('() => {/*x')) }), test('get_initial_state toplevel not entrypoint', () => { const s = test_initial_state( { '' : `import {x} from 'x'; x()`, 'x' : `export const x = () => 1; x()`, }, undefined, { current_module: 'x', } ) assert_equal(s.current_calltree_node.toplevel, true) assert_equal(s.active_calltree_node, null) }), test('module not evaluated because of error in module it depends on', () => { const s = test_initial_state({ '' : `import {x} from 'x'`, 'x' : ` const has_child_calls = i => i == 0 ? 0 : has_child_calls(i - 1) has_child_calls(10) console.log('log') throw new Error('fail') `, }) assert_equal(root_calltree_node(s).module, 'x') // Must collect logs from failed module assert_equal(s.logs.logs.length, 1) const s2 = COMMANDS.move_cursor( COMMANDS.change_current_module(s, 'x'), s.files['x'].indexOf('throw') ) assert_equal(s2.value_explorer.index, s.files['x'].indexOf('throw')) const s3 = COMMANDS.calltree.arrow_right(s) assert_equal(s3.current_calltree_node.fn.name, 'has_child_calls') }), test('logs simple', () => { const code = `console.log(10)` const i = test_initial_state(code) assert_equal(i.logs.logs.length, 1) assert_equal(i.logs.logs[0].args, [10]) }), test('logs', () => { const code = ` const deep = x => { if(x == 10) { console.log(x) } else { deep(x + 1) } } deep(0) ` const i = test_initial_state(code) assert_equal(i.logs.logs.length, 1) assert_equal(i.logs.logs[0].args, [10]) const state = COMMANDS.calltree.navigate_logs_position(i, 0) assert_equal(state.logs.log_position, 0) assert_equal(state.value_explorer.result.value, [10]) assert_equal(current_cursor_position(state), code.indexOf('(x)')) }), test('deferred calls', () => { const code = ` export const fn = (x) => { fn2(x) } const fn2 = () => { console.log(1) } ` const {state: i, on_deferred_call} = test_deferred_calls_state(code) // Make deferred call i.modules[''].fn(10) const state = on_deferred_call(i) assert_equal(state.logs.logs.length, 1) const call = get_deferred_calls(state)[0] assert_equal(call.fn.name, 'fn') assert_equal(call.code.index, code.indexOf('(x) => {')) assert_equal(call.args, [10]) // Expand call const expanded = COMMANDS.calltree.select_and_toggle_expanded(state, call.id) assert_equal(get_deferred_calls(expanded)[0].children[0].fn.name, 'fn2') // Navigate logs const nav = COMMANDS.calltree.navigate_logs_position(expanded, 0) assert_equal(nav.current_calltree_node.is_log, true) const nav2 = COMMANDS.calltree.arrow_left(nav) assert_equal(nav2.current_calltree_node.fn.name, 'fn2') }), test('deferred calls calltree nav', () => { const code = ` const normal_call = (x) => { } normal_call(0) export const deferred_call = (x) => { } ` const {state: i, on_deferred_call} = test_deferred_calls_state(code) // When there are no deferred calls, and we press arrow down, nothing should // happen const no_deferred_down = COMMANDS.calltree.arrow_down( COMMANDS.calltree.arrow_down(i) ) assert_equal(no_deferred_down.current_calltree_node.fn.name, 'normal_call') const after_deferred_calls = [1, 2, 3].reduce( (s, a) => { // Make deferred calls i.modules[''].deferred_call(a) return on_deferred_call(s) }, i ) assert_equal( get_deferred_calls(after_deferred_calls).map(c => c.args[0]), [1,2,3] ) assert_equal(after_deferred_calls.current_calltree_node.toplevel, true) const down = COMMANDS.calltree.arrow_down(after_deferred_calls) const first_deferred_call_selected = COMMANDS.calltree.arrow_down( COMMANDS.calltree.arrow_down(after_deferred_calls) ) // After we press arrow down, first deferred call gets selected assert_equal( first_deferred_call_selected.current_calltree_node.args[0], 1, ) // One more arrow down, second deferred call gets selected assert_equal( COMMANDS.calltree.arrow_down(first_deferred_call_selected) .current_calltree_node .args[0], 2 ) // After we press arrow up when first deferred call selected, we select last // visible non deferred call assert_equal( COMMANDS.calltree.arrow_up(first_deferred_call_selected) .current_calltree_node .args[0], 0 ) // After we press arrow left when first deferred call selected, we stay on // this call assert_equal( COMMANDS.calltree.arrow_left(first_deferred_call_selected) .current_calltree_node .args[0], 1 ) }), test('deferred_calls find_call', () => { const code = ` export const fn = () => { fn2() } const fn2 = () => { console.log(1) } ` const {state: i, on_deferred_call} = test_deferred_calls_state(code) // Make deferred call i.modules[''].fn() const state = on_deferred_call(i) const moved = COMMANDS.move_cursor(state, code.indexOf('fn2')) 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), code.indexOf('fn2') ) assert_equal(move_back.active_calltree_node.fn.name, 'fn') }), test('deferred_calls find_call then deferred_call bug', () => { const code = ` export const fn = (x) => { /* label */ } ` const {state: i, on_deferred_call} = test_deferred_calls_state(code) // Make deferred call i.modules[''].fn(1) const state = on_deferred_call(i) // find call const moved = COMMANDS.move_cursor(state, code.indexOf('label')) // Make deferred call i.modules[''].fn(2) const result = on_deferred_call(moved) // there was a bug throwing error when added second deferred call assert_equal(get_deferred_calls(result).map(c => c.args), [[1], [2]]) }), test('deferred_calls discard on code rerun', () => { const code = ` export const fn = () => { /* label */ } ` const {state: i, on_deferred_call} = test_deferred_calls_state(code) const state = input(i, code, 0).state // Make deferred call, calling fn from previous code i.modules[''].fn(1) const result = on_deferred_call(state) // deferred calls must be null, because deferred calls from previous executions // must be discarded assert_equal(get_deferred_calls(result), null) }), test('deferred_calls several calls bug', () => { const code = ` export const fn = i => i == 0 ? 0 : fn(i - 1) ` const {state: i, on_deferred_call} = test_deferred_calls_state(code) // Make deferred call i.modules[''].fn(10) const state = on_deferred_call(i) const call = get_deferred_calls(state)[0] const expanded = COMMANDS.calltree.select_and_toggle_expanded(state, call.id) // Make deferred call again. There was a runtime error expanded.modules[''].fn(10) }), test('deferred_calls find call bug', () => { const code = ` export const fn = () => 1 ` const {state: i, on_deferred_call} = test_deferred_calls_state(code) const moved = COMMANDS.move_cursor(i, code.indexOf('1')) assert_equal(moved.active_calltree_node, null) // Make deferred call moved.modules[''].fn(10) const after_call = on_deferred_call(moved) const moved2 = COMMANDS.move_cursor(after_call, code.indexOf('1')) assert_equal(moved2.active_calltree_node.value, 1) }), test('async/await await non promise', async () => { await assert_code_evals_to_async( ` await 1 `, 1 ) }), test('async/await await Promise resolved immediately', async () => { await assert_code_evals_to_async( ` await new Promise(resolve => resolve(1)) `, 1 ) }), test('async/await return from async function', async () => { await assert_code_evals_to_async( ` const x = async () => 123 const y = async () => await x() await y() `, 123 ) }), test('async/await await resolved Promise', async () => { await assert_code_evals_to_async( ` await Promise.resolve(123) `, 123 ) }), test('async/await await Promise resolved with resolved Promise', async () => { await assert_code_evals_to_async( ` await Promise.resolve(Promise.resolve(123)) `, 123 ) }), test('async/await await Promise resolved with async', async () => { await assert_code_evals_to_async( ` const x = async () => 1 await Promise.resolve(x()) `, 1 ) }), test('async/await await Promise resolved with rejected Promise', async () => { await assert_code_error_async( ` await Promise.resolve(Promise.reject('boom')) `, 'boom', ) }), test('async/await await Promise returned from async function', async () => { await assert_code_evals_to_async( ` const x = async () => { return Promise.resolve(123) } await x() `, 123 ) }), test('async/await throw from async function', async () => { await assert_code_error_async( ` const x = async () => { throw 'boom' } await x() `, 'boom' ) }), test('async/await await rejected Promise', async () => { await assert_code_error_async( ` await Promise.reject('boom') `, 'boom' ) }), test('async/await await rejected Promise fn call', async () => { await assert_code_error_async( ` async function test() { await Promise.reject('boom') } await test() `, 'boom' ) }), test('async/await promise rejected with null', async () => { await assert_code_error_async( `await Promise.reject()`, undefined ) }), test('async/await await rejected Promise returned from async', async () => { await assert_code_error_async( ` const x = async () => Promise.reject('boom') await x() `, 'boom' ) }), test('async/await Promise.all', async () => { await assert_code_evals_to_async( ` const x = async i => i await Promise.all([x(0), x(1), x(2)]) `, [0,1,2] ) }), test('async/await calltree', async () => { const i = await test_initial_state_async(` const x = () => 1 const delay = async time => { await 1 x() } await delay(3) `) const root = root_calltree_node(i) assert_equal(root.children.length, 1) const call_delay = root.children[0] assert_equal(call_delay.fn.name, 'delay') assert_equal(call_delay.fn.name, 'delay') }), test('async/await Promise.all set child promises status ok', async () => { const i = await test_initial_state_async(` const async_fn = async () => 1 await Promise.all([1,2,3].map(async_fn)) `) const async_fn_call = root_calltree_node(i) .children[0] // map .children[0] // first call of async_fn assert_equal(async_fn_call.value.status.ok, true) assert_equal(async_fn_call.value.status.value, 1) }), test('async/await Promise.all set child promises status error', async () => { const i = await test_initial_state_async(` const async_fn = async () => { throw 1 } await Promise.all([1,2,3].map(async_fn)) `) const async_fn_call = root_calltree_node(i) .children[0] // map .children[0] // first call of async_fn assert_equal(async_fn_call.value.status.ok, false) assert_equal(async_fn_call.value.status.error, 1) }), test('async/await logs out of order', async () => { const i = await test_initial_state_async(` // Init promises p1 and p2 that are resolved in different order (p2 then // p1) const p2 = Promise.resolve(2) const p1 = p2.then(() => 1) const log = async p => { const v = await p console.log(v) } await Promise.all([log(p1), log(p2)]) `) const logs = i.logs.logs.map(l => l.args[0]) assert_equal(logs, [2, 1]) }), test('async/await logs out of order timeout', async () => { const i = await test_initial_state_async(` const delay = async time => { await new Promise(res => setTimeout(res, time*10)) console.log(time) } await Promise.all([delay(2), delay(1)]) `) const logs = i.logs.logs.map(l => l.args[0]) assert_equal(logs, [1, 2]) }), test('async/await external async fn', async () => { await assert_code_evals_to_async( ` const AsyncFunction = new Function('return (async () => {}).constructor')() const async_fn = new AsyncFunction('return 1') await async_fn() `, 1 ) }), test('async/await then bug', async () => { await assert_code_evals_to_async( ` const p2 = Promise.resolve(2) const p1 = p2.then(() => 1) const x = () => 1 await x() `, 1 ) }), test('async/await then non-function', async () => { await assert_code_evals_to_async( ` await Promise.resolve(1).then(2) `, 1 ) }), test('async/await Promise.then creates subcall', async () => { const i = await test_initial_state_async(` const x = () => 1 await Promise.resolve(1).then(x) `) const root = root_calltree_node(i) assert_equal(root.children.at(-1).fn.name, 'then') assert_equal(root.children.at(-1).children[0].fn.name, 'x') }), test('async/await Promise.catch creates subcall', async () => { const i = await test_initial_state_async(` const x = () => 1 await Promise.reject(1).catch(x) `) const root = root_calltree_node(i) assert_equal(root.children.at(-1).fn.name, 'catch') assert_equal(root.children.at(-1).children[0].fn.name, 'x') }), test('async/await native Promise.then creates subcall', async () => { const i = await test_initial_state_async(` const x = () => 1 const async_fn = async () => 1 await async_fn().then(x) `) const root = root_calltree_node(i) assert_equal(root.children.at(-1).children[0].fn.name, 'x') }), test('async/await await promise wrapped to some data structure', async () => { const i = await assert_code_evals_to_async( ` const async_fn = async () => 1 const x = () => { return {promise: async_fn()} } await x().promise `, 1 ) }), test('async/await await bug', async () => { const code = ` const x = () => {} const test = async () => { await 1 x() } await Promise.all([test(), test()]) ` const i = await test_initial_state_async(code, code.indexOf('await 1')) assert_value_explorer(i ,1) }), test('async/await edit', async () => { const code = ` const f = async () => { } await f() ` const i = await test_initial_state_async(code) const code2 = ` const f = async () => { 1 } await f() ` const next = await input_async(i, code2, code2.indexOf('1')) assert_equal(next.active_calltree_node.fn.name, 'f') assert_equal(next.value_explorer.result.value, 1) }), test('async/await move_cursor', async () => { const code = ` const f = async () => { 1 } await f() ` const i = await test_initial_state_async(code) const after_move = await COMMANDS.move_cursor(i, code.indexOf('1')) assert_equal(after_move.active_calltree_node.fn.name, 'f') }), test('async/await move_cursor deferred call', async () => { const code = ` export const fn = async () => { await fn2() } const fn2 = async () => { return 1 } ` const {state: i, on_deferred_call} = test_deferred_calls_state(code) // Make deferred call i.modules[''].fn() const state = on_deferred_call(i) const moved_state = COMMANDS.move_cursor(state, code.indexOf('1')) assert_equal(moved_state.active_calltree_node.fn.name, 'fn2') }), test('async/await async deferred call', async () => { const code = ` await new Object() export const fn = () => 1 ` const {state: i, on_deferred_call} = test_deferred_calls_state(code) const result = await i.eval_modules_state.promise const s = COMMANDS.eval_modules_finished( i, i, result, i.eval_modules_state.node, i.eval_modules_state.toplevel ) // Make deferred call s.modules[''].fn() const state = on_deferred_call(s) assert_equal(get_deferred_calls(state).length, 1) assert_equal(get_deferred_calls(state)[0].value, 1) }), test('async/await await argument bug', async () => { await assert_code_evals_to_async( ` Object.assign({}, await {foo: 1}) `, {foo: 1} ) }), test('async/await move_cursor before code evaluated', async () => { const i = test_initial_state(` await new Promise(resolve => null) `) const moved = COMMANDS.move_cursor(i, 0) // No assertion, must not throw }), test('record io', () => { let app_window_patches // Patch Math.random to always return 1 app_window_patches = {'Math.random': () => 1} const initial = test_initial_state(`const x = Math.random()`, undefined, { app_window_patches }) // Now call to Math.random is cached, break it to ensure it was not called // on next run app_window_patches = {'Math.random': () => { throw 'fail' }} const next = input(initial, `const x = Math.random()*2`, 0, {app_window_patches}).state assert_equal(next.value_explorer.result.value, 2) assert_equal(next.rt_cxt.io_trace_index, 1) // Patch Math.random to return 2. // TODO The first call to Math.random() is cached with value 1, and the // second shoud return 2 app_window_patches = {'Math.random': () => 2} const replay_failed = input( initial, `const x = Math.random() + Math.random()`, 0, {app_window_patches} ).state // TODO must reuse first cached call? assert_equal(replay_failed.value_explorer.result.value, 4) }), test('record io trace discarded if args does not match', async () => { // Patch fetch let app_window_patches app_window_patches = {fetch: async () => 'first'} const initial = await test_initial_state_async(` console.log(await fetch('url', {method: 'GET'})) `, undefined, {app_window_patches}) assert_equal(initial.logs.logs[0].args[0], 'first') // Patch fetch again app_window_patches = {fetch: async () => 'second'} const cache_discarded = await input_async(initial, ` console.log(await fetch('url', {method: 'POST'})) `, 0, {app_window_patches}) assert_equal(cache_discarded.logs.logs[0].args[0], 'second') }), test('record io fetch rejects', async () => { // Patch fetch let app_window_patches app_window_patches = {fetch: () => globalThis.app_window.Promise.reject('fail')} const initial = await test_initial_state_async(` await fetch('url', {method: 'GET'}) `, undefined, {app_window_patches}) assert_equal(root_calltree_node(initial).error, 'fail') // Patch fetch again app_window_patches = {fetch: () => async () => 'result'} const with_cache = await input_async(initial, ` await fetch('url', {method: 'GET'}) `, 0, {app_window_patches}) assert_equal(root_calltree_node(initial).error, 'fail') }), test('record io preserve promise resolution order', async () => { // Generate fetch function which calls get resolved in reverse order const calls = [] function fetch(...args) { let resolver const promise = new (globalThis.app_window.Promise)(r => {resolver = r}) calls.push({resolver, promise, args}) return promise } function resolve() { [...calls].reverse().forEach(call => call.resolver(...call.args)) } let app_window_patches // Patch fetch app_window_patches = {fetch} const code = ` await Promise.all( [1, 2, 3].map(async v => { const result = await fetch(v) console.log(result) }) ) ` const initial_promise = test_initial_state_async(code, undefined, {app_window_patches}) resolve() const initial = await initial_promise // calls to fetch are resolved in reverse order assert_equal(initial.logs.logs.map(l => l.args[0]), [3,2,1]) // Break fetch to ensure it is not get called anymore app_window_patches = {fetch: () => {throw 'broken'}} const with_cache = await input_async( initial, code, 0, {app_window_patches} ) // cached calls to fetch should be resolved in the same (reverse) order as // on the first run, so first call wins assert_equal(with_cache.logs.logs.map(l => l.args[0]), [3,2,1]) }), test('record io setTimeout', async () => { let app_window_patches // Patch fetch to return result in 10ms app_window_patches = { fetch: () => new (globalThis.app_window.Promise)(resolve => setTimeout(resolve, 10)) } const code = ` setTimeout(() => console.log('timeout'), 0) await fetch().then(() => console.log('fetch')) ` const i = await test_initial_state_async(code, undefined, {app_window_patches}) // First executed setTimeout, then fetch assert_equal(i.logs.logs.map(l => l.args[0]), ['timeout', 'fetch']) // Break fetch to ensure it would not be called app_window_patches = { fetch: async () => {throw 'break'} } const with_cache = await input_async(i, code, 0, {app_window_patches}) // Cache must preserve resolution order assert_equal(with_cache.logs.logs.map(l => l.args[0]), ['timeout', 'fetch']) }), test('record io clear io trace', async () => { const s1 = test_initial_state(`Math.random()`) const rnd = s1.value_explorer.result.value const s2 = input(s1, `Math.random() + 1`, 0).state assert_equal(s2.value_explorer.result.value, rnd + 1) const cleared = input(COMMANDS.clear_io_trace(s2), `Math.random() + 1`).state assert_equal( cleared.value_explorer.result.value == rnd + 1, false ) }), test('record io no io trace on deferred calls', async () => { const code = ` const x = Math.random export const fn = () => x() ` const {state: i, on_deferred_call} = test_deferred_calls_state(code) // Make deferred call i.modules[''].fn() const state = on_deferred_call(i) // Deferred calls should not be record in cache assert_equal(state.rt_cxt.io_trace.length, 0) }), test('record io discard prev execution', () => { // Populate cache const i = test_initial_state(`Math.random(0)`) const rnd = i.active_calltree_node.children[0].value // Run two versions of code in parallel const next = input(i, `await Promise.resolve()`, 0) const next2 = input(i, `Math.random(1)`, 0).state const next_rnd = i.active_calltree_node.children[0].value assert_equal(rnd, next_rnd) }), test('record io Date', () => { assert_equal( test_initial_state('new Date()').io_trace.length, 1 ) assert_equal( test_initial_state('new Date("2020-01-01")').io_trace, undefined, ) assert_equal( typeof(test_initial_state('Date()').io_trace[0].value), 'string', ) assert_equal( typeof(test_initial_state('new Date()').io_trace[0].value), 'object', ) }), test('record io hangs bug', async () => { let app_window_patches app_window_patches = { fetch: () => new (globalThis.app_window.Promise)(resolve => setTimeout(resolve, 0)) } const code = ` const p = fetch('') Math.random() await p ` const i = await test_initial_state_async(code, undefined, {app_window_patches}) assert_equal(i.io_trace.length, 3) const next_code = `await fetch('')` const state = await input_async(i, next_code, 0, {app_window_patches}) assert_equal(state.io_trace.length, 2) }), test('record io logs recorded twice bug', () => { const code = `Math.random()` const i = test_initial_state(code) const second = input( i, `console.log(1); Math.random(); Math.random()`, 0 ) assert_equal(second.state.logs.logs.length, 1) }), test('record io expand_calltree_node bug', () => { const code = ` function x(i) { return i == 0 ? Math.random() : x(i - 1) } x(2) x(2) ` const i = test_initial_state(code) const with_trace = run_code(i) const second_call = root_calltree_node(with_trace).children[1] assert_equal(second_call.fn.name, 'x') const expanded = COMMANDS.calltree.select_and_toggle_expanded(with_trace, second_call.id) const second_call_2 = root_calltree_node(expanded).children[1] assert_equal(second_call_2.ok, true) assert_equal(second_call.value, second_call_2.value) }), test('value_explorer Set', () => { assert_equal( header(new Set(['foo', 'bar'])), 'Set {0: "foo", 1: "bar"}' ) }), test('value_explorer Map', () => { assert_equal( header(new Map([['foo', 'bar'], ['baz', 'qux']])), 'Map {foo: "bar", baz: "qux"}' ) }), test('let_versions find_versioned_lets toplevel', () => { const result = do_parse(` let x x = 1 function foo() { x } `) assert_equal(result.node.has_versioned_let_vars, true) }), test('let_versions find_versioned_lets', () => { function assert_is_versioned_let(code, is_versioned) { const result = do_parse(code) const root = find_node(result.node, n => n.name == 'root' && n.type == 'function_expr' ) assert_equal(root.has_versioned_let_vars, is_versioned) const node = find_node(result.node, n => n.index == code.indexOf('x')) assert_equal(!(!node.is_versioned_let_var), is_versioned) } assert_is_versioned_let( ` function root() { let x x = 1 function foo() { x } } `, true ) // closed but constant assert_is_versioned_let( ` function root() { let x function foo() { x } } `, false ) // assigned but not closed assert_is_versioned_let( ` function root() { let x x = 1 } `, false ) // not closed, var has the same name assert_is_versioned_let( ` function root() { let x x = 1 function foo() { let x x } } `, false ) // not closed, var has the same name assert_is_versioned_let( ` function root() { let x x = 1 if(true) { let x function foo() { x } } } `, false ) }), test('let_versions assign to let variable', () => { const code = ` let result = 0 function unused() { result = 2 } result = 1 ` const i = test_initial_state(code, code.indexOf('result = 1')) assert_value_explorer(i, 1) }), test('let_versions', () => { const code = ` let x [1,2].forEach(y => { x /*x*/ x = y }) ` const x_pos = code.indexOf('x /*x*/') const i = test_initial_state(code, x_pos) const second_iter = COMMANDS.calltree.arrow_down(i) const select_x = COMMANDS.move_cursor(second_iter, x_pos) assert_equal(select_x.value_explorer.result.value, 1) }), test('let_versions close let var bug', () => { const code = ` let x x = 1 function y() { return {x} } y() /*y()*/ ` const i = test_initial_state(code, code.indexOf('y() /*y()*/')) assert_equal(i.value_explorer.result.value, {x: 1}) }), test('let_versions initial let value', () => { const code = ` let x function y() { x /*x*/ } y() ` const x_pos = code.indexOf('x /*x*/') const i = test_initial_state(code, x_pos) assert_equal(i.value_explorer.result.ok, true) assert_equal(i.value_explorer.result.value, undefined) }), test('let_versions save version bug', () => { const code = ` let x = 0 function set_x(value) { x = value } function get_x() { x /* result */ } get_x() set_x(10) x = 10 set_x(10) x = 10 ` const i = test_initial_state(code, code.indexOf('x /* result */')) assert_equal(i.value_explorer.result.value, 0) }), test('let_versions expand_calltree_node', () => { const code = ` let y function foo(x) { y /*y*/ bar(y) } function bar(arg) { } foo(0) y = 11 foo(0) y = 12 ` const i = test_initial_state(code) const second_foo_call = root_calltree_node(i).children[1] assert_equal(second_foo_call.has_more_children, true) const expanded = COMMANDS.calltree.select_and_toggle_expanded(i, second_foo_call.id) const bar_call = root_calltree_node(expanded).children[1].children[0] assert_equal(bar_call.fn.name, 'bar') assert_equal(bar_call.args, [11]) const moved = COMMANDS.move_cursor(expanded, code.indexOf('y /*y*/')) assert_equal(moved.value_explorer.result.value, 11) }), test('let_versions expand_calltree_node 2', () => { const code = ` let y function deep(x) { if(x < 10) { y /*y*/ y = x deep(x + 1) } } deep(0) y = 11 deep(0) y = 12 ` const i = test_initial_state(code) const second_deep_call = root_calltree_node(i).children[1] assert_equal(second_deep_call.has_more_children, true) const expanded = COMMANDS.calltree.select_and_toggle_expanded(i, second_deep_call.id) const moved = COMMANDS.move_cursor(expanded, code.indexOf('y /*y*/')) assert_equal(moved.value_explorer.result.value, 11) }), test('let_versions create multiversion within expand_calltree_node', () => { const code = ` function x() { let y function set(value) { y = value } set(1) y /*result*/ set(2) } x() x() ` const i = test_initial_state(code) const second_x_call = root_calltree_node(i).children[1] assert_equal(second_x_call.has_more_children, true) const expanded = COMMANDS.calltree.select_and_toggle_expanded(i, second_x_call.id) const moved = COMMANDS.move_cursor(expanded, code.indexOf('y /*result*/')) assert_equal(moved.value_explorer.result.value, 1) }), test('let_versions mutable closure', () => { const code = ` const holder = (function() { let value return { get: () => value, set: (v) => { value /*value*/ value = v } } })() Array.from({length: 10}).map((_, i) => { holder.set(i) }) holder.get() ` const i = test_initial_state(code, code.indexOf('holder.get')) assert_equal(i.value_explorer.result.value, 9) const map_expanded = COMMANDS.calltree.select_and_toggle_expanded( i, root_calltree_node(i).children[2].id ) const expanded = COMMANDS.calltree.select_and_toggle_expanded( map_expanded, root_calltree_node(map_expanded).children[2].children[5].id ) const set_call = COMMANDS.calltree.arrow_right( COMMANDS.calltree.arrow_right( expanded ) ) assert_equal( set_call.active_calltree_node.code.index, code.indexOf('(v) =>') ) const moved = COMMANDS.move_cursor(set_call, code.indexOf('value /*value*/')) assert_equal(moved.value_explorer.result.value, 4) }), test('let_versions forEach', () => { const code = ` let sum = 0 [1,2,3].forEach(v => { sum = sum + v }) sum /*first*/ [1,2,3].forEach(v => { sum = sum + v }) sum /*second*/ ` const i = test_initial_state(code, code.indexOf('sum /*first*/')) assert_equal(i.value_explorer.result.value, 6) const second = COMMANDS.move_cursor(i, code.indexOf('sum /*second*/')) assert_equal(second.value_explorer.result.value, 12) }), test('let_versions scope', () => { assert_code_evals_to(` let x = 1 let y = 1 function change_x() { x = 2 } function change_y() { y = 2 } function unused() { return {} } if(false) { } else { if((change_y() || true) ? true : null) { const a = [...[{...{ y: unused()[!(1 + (true ? {y: [change_x()]} : null))] }}]] } } {x,y} /*result*/ `, {x: 2, y: 2} ) }), test('let_versions expr', () => { assert_code_evals_to(` let x = 0 function inc() { x = x + 1 return 0 } x + inc() + x + inc() + x `, 3 ) }), test('let_versions update in assignment', () => { assert_code_evals_to(` let x function set(value) { x = 1 return 0 } x = set() x `, 0 ) }), test('let_versions update in assignment closed', () => { const code = ` function test() { let x function set(value) { x = 1 return 0 } x = set() return x } test() ` const i = test_initial_state(code, code.indexOf('return x')) assert_equal(i.value_explorer.result.value, 0) }), test('let_versions multiple vars with same name', () => { const code = ` let x function x_1() { x = 1 } if(true) { let x = 0 function x_2() { x = 2 } x /* result 0 */ x_1() x /* result 1 */ x_2() x /* result 2 */ } ` const i = test_initial_state(code, code.indexOf('x /* result 0 */')) const frame = active_frame(i) const result_0 = find_node(frame, n => n.index == code.indexOf('x /* result 0 */')).result assert_equal(result_0.value, 0) const result_1 = find_node(frame, n => n.index == code.indexOf('x /* result 1 */')).result assert_equal(result_1.value, 0) const result_2 = find_node(frame, n => n.index == code.indexOf('x /* result 2 */')).result assert_equal(result_2.value, 2) }), test('let_versions closed let vars bug', () => { const code = ` let x = 0 function inc() { x = x + 1 } function test() { inc() x /*x*/ } test() ` const i = test_initial_state(code, code.indexOf('x /*x*/')) assert_equal(i.value_explorer.result.value, 1) }), test('let_versions assign and read variable multiple times within call', () => { const code = ` let x; (() => { x = 1 console.log(x) x = 2 console.log(x) })() ` }), test('let_versions let assigned undefined bug', () => { const code = ` let x = 1 function set(value) { x = value } set(2) set(undefined) x /*x*/ ` const i = test_initial_state(code, code.indexOf('x /*x*/')) assert_equal(i.value_explorer.result.value, undefined) }), // TODO function args should have multiple versions same as let vars // test('let_versions function args closure', () => { // const code = ` // (function(x) { // function y() { // x /*x*/ // } // y() // x = 1 // y() // })(0) // ` // const i = test_initial_state(code) // const second_y_call = root_calltree_node(i).children[0].children[1] // const selected = COMMANDS.calltree.select_and_toggle_expanded(i, second_y_call.id) // const moved = COMMANDS.move_cursor(selected, code.indexOf('x /*x*/')) // assert_equal(moved.value_explorer.result.value, 1) // }), test('let_versions async/await', async () => { const code = ` let x function set(value) { x = value } await set(1) x /*x*/ ` const i = await test_initial_state_async(code, code.indexOf('x /*x*/')) assert_equal(i.value_explorer.result.value, 1) }), /* TODO this test fails. To fix it, we should record version_counter after await finished and save it in calltree_node */ //test('let_versions async/await 2', async () => { // const code = ` // let x // function set(value) { // x = value // Promise.resolve().then(() => { // x = 10 // }) // } // await set(1) // x /*x*/ // ` // const i = await test_initial_state_async(code, code.indexOf('x /*x*/')) // assert_equal(i.value_explorer.result.value, 10) //}), // Test that expand_calltree_node produces correct id for expanded nodes test('let_versions native call', () => { const code = ` function x() {} [1,2].map(x) [1,2].map(x) ` const i = test_initial_state(code) const second_map_call = i.calltree.children[0].children[1] assert_equal(second_map_call.has_more_children, true) const expanded = COMMANDS.calltree.select_and_toggle_expanded(i, second_map_call.id) const second_map_call_exp = expanded.calltree.children[0].children[1] assert_equal(second_map_call.id == second_map_call_exp.id, true) assert_equal(second_map_call_exp.children[0].id == second_map_call_exp.id + 1, true) }), test('let_versions expand_calltree_node twice', () => { const code = ` function test() { let x = 0 function test2() { function foo() { x /*x*/ } x = x + 1 foo() } test2() } test() test() ` const i = test_initial_state(code) const test_call = root_calltree_node(i).children[1] assert_equal(test_call.has_more_children , true) const expanded = COMMANDS.calltree.select_and_toggle_expanded(i, test_call.id) const test2_call = root_calltree_node(expanded).children[1].children[0] assert_equal(test2_call.has_more_children, true) const expanded2 = COMMANDS.calltree.select_and_toggle_expanded(expanded, test2_call.id) const foo_call = root_calltree_node(expanded2).children[1].children[0].children[0] const expanded3 = COMMANDS.calltree.select_and_toggle_expanded(expanded2, foo_call.id) const moved = COMMANDS.move_cursor(expanded3, code.indexOf('x /*x*/')) assert_equal(moved.value_explorer.result.value, 1) }), test('let_versions deferred calls', () => { const code = ` let x = 0 export const inc = () => { return do_inc() } const do_inc = () => { x = x + 1 return x } inc() ` const {state: i, on_deferred_call} = test_deferred_calls_state(code, code.indexOf('let x')) assert_value_explorer(i, 0) // Make deferred call i.modules[''].inc() const state = on_deferred_call(i) const call = get_deferred_calls(state)[0] assert_equal(call.has_more_children, true) assert_equal(call.value, 2) // Expand call // first arrow rights selects do_inc call, second steps into it const expanded = COMMANDS.calltree.arrow_right( COMMANDS.calltree.arrow_right( COMMANDS.calltree.select_and_toggle_expanded(state, call.id) ) ) // Move cursor const moved = COMMANDS.move_cursor(expanded, code.indexOf('return x')) assert_equal(moved.value_explorer.result.value, 2) }), test('let_versions deferred calls get value', () => { const code = ` let x = 0 function noop() { } function set(value) { x = value noop() } set(1) set(2) set(3) export const get = () => x ` const {state: i} = test_deferred_calls_state(code) const second_set_call = root_calltree_node(i).children[1] assert_equal(second_set_call.has_more_children, true) const exp = COMMANDS.calltree.select_and_toggle_expanded(i, second_set_call.id) assert_equal(exp.modules[''].get(), 3) }), test('let_versions multiple assignments', () => { const code = ` let x function foo () { x /*x foo*/ } x = 1 foo() x = 2 foo() /*foo 2*/ x = 3 x /*x*/ ` const i = test_initial_state(code, code.indexOf('x /*x*/')) assert_value_explorer(i, 3) const stepped = COMMANDS.step_into(i, code.indexOf('foo() /*foo 2*/')) const moved = COMMANDS.move_cursor(stepped, code.indexOf('x /*x foo*/')) assert_value_explorer(moved, 2) }), test('let_versions bug access before init', () => { const code = ` Object.assign({}) const x = {} x.y = 1 let result = 0 function() { result = 1 } ` const i = test_initial_state(code, code.indexOf('let result')) assert_value_explorer(i, 0) }), test('let_versions bug version counter', () => { const code = ` let i = 0 const x = {value: 1} function unused() { i = 1 } i = 2 x.value = 2 x /*result*/ ` const i = test_initial_state(code, code.indexOf('x /*result*/')) assert_value_explorer(i, {value: 2}) }), test('let_versions bug version counter 2', () => { const code = ` let i = 0 function unused() { i = 1 } i = 1 i /*result*/ i = 2 ` const i = test_initial_state(code, code.indexOf('i /*result*/')) assert_value_explorer(i, 1) }), test('let_versions bug version counter multiple assignments', () => { const code = ` let i = 0, j = 0 function unused() { i = 1 } i = 1, j = 1 i /*result*/ i = 2 ` const i = test_initial_state(code, code.indexOf('i /*result*/')) assert_value_explorer(i, 1) }), test('mutability array', () => { const code = ` const arr = [2,1] arr.at(1) arr.push(3) arr /*after push*/ arr.sort() arr /*after sort*/ arr[0] = 4 arr /*after set*/ ` const i = test_initial_state(code, code.indexOf('arr.at')) assert_value_explorer(i, 1) const s1 = COMMANDS.move_cursor(i, code.indexOf('arr /*after push*/')) assert_value_explorer(s1, [2,1,3]) const s2 = COMMANDS.move_cursor(i, code.indexOf('arr /*after sort*/')) assert_value_explorer(s2, [1,2,3]) const s3 = COMMANDS.move_cursor(i, code.indexOf('arr /*after set*/')) assert_value_explorer(s3, [4,2,3]) }), test('mutability array set length', () => { const code = ` const x = [1,2,3] x.length = 2 x /*x*/ x.length = 1 ` const i = test_initial_state(code, code.indexOf('x /*x*/')) assert_value_explorer(i, [1,2]) }), test('mutability array method name', () => { assert_code_evals_to(`[].sort.name`, 'sort') assert_code_evals_to(`[].forEach.name`, 'forEach') }), test('mutability array method returns itself', () => { const code = ` const x = [3,2,1] const y = x.sort() if(x != y) { throw new Error('not eq') } x.push(4) ` const i = test_initial_state(code, code.indexOf('const y')) assert_equal(root_calltree_node(i).ok, true) assert_value_explorer(i, [1,2,3]) }), test('mutability set', () => { const code = ` const s = new Set([1,2]) s.delete(2) if(s.size != 1) { throw new Error('size not eq') } s.add(3) s /*s*/ ` const i = test_initial_state(code, code.indexOf('const s')) assert_value_explorer(i, new Set([1,2])) const moved = COMMANDS.move_cursor(i, code.indexOf('s /*s*/')) assert_value_explorer(moved, new Set([1,3])) }), test('mutability set method name', () => { assert_code_evals_to(`new Set().delete.name`, 'delete') }), // This test is for browser environment where runtime is loaded from the main // (IDE) window, and user code is loaded from app window test('mutability instanceof', () => { assert_code_evals_to(`{} instanceof Object`, true) assert_code_evals_to(`new Object() instanceof Object`, true) assert_code_evals_to(`[] instanceof Array`, true) assert_code_evals_to(`new Array() instanceof Array`, true) assert_code_evals_to(`new Set() instanceof Set`, true) assert_code_evals_to(`new Map() instanceof Map`, true) }), test('mutability map', () => { const code = ` const s = new Map([['foo', 1], ['bar', 2]]) s.delete('foo') s.set('baz', 3) s /*s*/ ` const i = test_initial_state(code, code.indexOf('const s')) assert_value_explorer(i, {foo: 1, bar: 2}) const moved = COMMANDS.move_cursor(i, code.indexOf('s /*s*/')) assert_value_explorer(moved, {bar: 2, baz: 3}) }), test('mutability object', () => { const code = ` const s = {foo: 1, bar: 2} s.foo = 2 s.baz = 3 s /*s*/ ` const i = test_initial_state(code, code.indexOf('const s')) assert_value_explorer(i, {foo: 1, bar: 2}) const moved = COMMANDS.move_cursor(i, code.indexOf('s /*s*/')) assert_value_explorer(moved, {foo: 2, bar: 2, baz: 3}) }), test('mutability', () => { const code = ` const make_array = () => [3,2,1] const x = make_array() x.sort() ` const i = test_initial_state(code) const index = code.indexOf('x.sort()') const selected_x = COMMANDS.eval_selection(i, index, true).state assert_equal(selected_x.selection_state.node.length, 'x'.length) assert_selection(selected_x, [3, 2, 1]) const selected_sort = COMMANDS.eval_selection( COMMANDS.eval_selection(selected_x, index, true).state, index, true ).state assert_equal(selected_sort.selection_state.node.length, 'x.sort()'.length) assert_selection(selected_sort, [1,2,3]) }), test('mutability value_explorer bug', () => { const code = ` const x = [3,2,1] x.sort() x /*x*/ ` const i = test_initial_state(code, code.indexOf('x /*x*/')) assert_value_explorer( i, [1,2,3] ) }), test('mutability with_version_number', () => { const code = ` const make_array = () => [3,2,1] const x = make_array() x.sort() ` const i = test_initial_state(code, code.indexOf('const x')) assert_value_explorer(i, [3,2,1]) }), test('mutability member access version', () => { const code = ` const x = [0] x[0] /*x[0]*/ x[0] = 1 ` const i = test_initial_state(code, code.indexOf('x[0] /*x[0]*/')) assert_equal(i.value_explorer.result.value, 0) }), test('mutability assignment', () => { const code = ` const x = [0] x[0] = 1 ` const i = test_initial_state(code) const index = code.indexOf('x[0]') const evaled = COMMANDS.eval_selection( COMMANDS.eval_selection(i, index).state, index, ).state assert_equal(evaled.selection_state.node.length, 'x[0]'.length) assert_selection(evaled, 1) }), test('mutability assignment value explorer', () => { const code = ` const x = [0] x[0] = 1 ` const i = test_initial_state(code, code.indexOf('x[0]')) assert_value_explorer(i, 1) }), test('mutability multiple assignment value explorer', () => { const code = ` const x = [0] x[0] = 1, x[0] = 2 x /*x*/ ` const i = test_initial_state(code, code.indexOf('x[0]')) assert_equal(i.value_explorer, null) const moved = COMMANDS.move_cursor(i, code.indexOf('x /*x*/')) assert_value_explorer(moved, [2]) }), test('mutability assignment value explorer new value', () => { const code = ` const x = [0] x[0] = 1 x[0] /*x*/ ` const i = test_initial_state(code, code.indexOf('x[0] /*x*/')) assert_value_explorer(i, [1]) }), test('mutability eval_selection lefthand', () => { const code = ` const x = [0] x[0] = 1 ` const i = test_initial_state(code) const evaled = COMMANDS.eval_selection(i, code.indexOf('x[0]')).state assert_selection(evaled, [0]) // expand eval to x[0] const evaled2 = COMMANDS.eval_selection(evaled, code.indexOf('x[0]')).state assert_selection(evaled2, 1) }), test('mutability multiple assignments', () => { const code = ` const x = [0] x[0] = 1 x /*x*/ x[0] = 2 ` const i = test_initial_state(code, code.indexOf('x /*x*/')) assert_value_explorer(i, [1]) }), test('mutability value explorer', () => { const code = ` const x = [0] x[0] = 1 ` const i = test_initial_state(code, code.indexOf('x[0] = 1')) assert_value_explorer(i, 1) }), test('mutability calltree value explorer', () => { const i = test_initial_state(` const array = [3,2,1] function sort(array) { return array.sort() } sort(array) `) const selected = COMMANDS.calltree.select_and_toggle_expanded(i, root_calltree_node(i).children[0].id) const args = selected.value_explorer.result.value['*arguments*'] assert_versioned_value(i, args, {array: [3,2,1]}) const returned = selected.value_explorer.result.value['*return*'] assert_versioned_value(i, returned, [1,2,3]) }), test('mutability import mutable value', () => { const code = { '': ` import {array} from 'x.js' import {change_array} from 'x.js' change_array() array /*result*/ `, 'x.js': ` export const array = ['initial'] export const change_array = () => { array[0] = 'changed' } ` } const main = code[''] const i = test_initial_state(code, main.indexOf('import')) assert_value_explorer(i, {array: ['initial']}) const sel = COMMANDS.eval_selection(i, main.indexOf('array')).state assert_selection(sel, ['initial']) const moved = COMMANDS.move_cursor(sel, main.indexOf('array /*result*/')) assert_value_explorer(moved, ['changed']) }), test('mutability Object.assign', () => { const i = test_initial_state(`Object.assign({}, {foo: 1})`) assert_value_explorer(i, {foo: 1}) }), test('mutability wrap external arrays', () => { const code = ` const x = "foo bar".split(' ') x.push('baz') x /*x*/ ` const i = test_initial_state(code, code.indexOf('const x')) assert_value_explorer(i, ['foo', 'bar']) }), test('mutability logs', () => { const i = test_initial_state(` const x = [1] console.log(x) x.push(2) console.log(x) `) const log1 = i.logs.logs[0] with_version_number_of_log(i, log1, () => assert_equal( [[1]], log1.args, ) ) const log2 = i.logs.logs[1] with_version_number_of_log(i, log2, () => assert_equal( [[1,2]], log2.args, ) ) }), // copypasted from the same test for let_versions test('mutability expand_calltree_node', () => { const code = ` const y = [] function foo(x) { y /*y*/ bar(y) } function bar(arg) { } foo(0) y[0] = 11 foo(0) y[0] = 12 ` const i = test_initial_state(code) const second_foo_call = root_calltree_node(i).children[1] assert_equal(second_foo_call.has_more_children, true) const expanded = COMMANDS.calltree.select_and_toggle_expanded(i, second_foo_call.id) const bar_call = root_calltree_node(expanded).children[1].children[0] assert_equal(bar_call.fn.name, 'bar') const moved = COMMANDS.move_cursor(expanded, code.indexOf('y /*y*/')) assert_value_explorer(moved, [11]) }), // copypasted from the same test for let_versions test('mutability expand_calltree_node twice', () => { const code = ` function test() { let x = {value: 0} function test2() { function foo() { x /*x*/ } x.value = x.value + 1 foo() } test2() } test() test() ` const i = test_initial_state(code) const test_call = root_calltree_node(i).children[1] assert_equal(test_call.has_more_children , true) const expanded = COMMANDS.calltree.select_and_toggle_expanded(i, test_call.id) const test2_call = root_calltree_node(expanded).children[1].children[0] assert_equal(test2_call.has_more_children, true) const expanded2 = COMMANDS.calltree.select_and_toggle_expanded(expanded, test2_call.id) const foo_call = root_calltree_node(expanded2).children[1].children[0].children[0] const expanded3 = COMMANDS.calltree.select_and_toggle_expanded(expanded2, foo_call.id) const moved = COMMANDS.move_cursor(expanded3, code.indexOf('x /*x*/')) assert_equal(moved.value_explorer.result.value, {value: 1 }) }), test('mutability quicksort', () => { const code = ` const loop = new Function('action', 'while(true) { if(action()) { return } }') function partition(arr, begin, end) { const pivot = arr[begin] let i = begin - 1, j = end + 1 loop(() => { i = i + 1 loop(() => { if(arr[i] < pivot) { i = i + 1 } else { return true /* stop */ } }) j = j - 1 loop(() => { if(arr[j] > pivot) { j = j - 1 } else { return true // stop iteration } }) if(i >= j) { return true // stop iteration } const temp = arr[i] arr[i] = arr[j] arr[j] = temp }) return j } function qsort(arr, begin = 0, end = arr.length - 1) { if(begin >= 0 && end >= 0 && begin < end) { const p = partition(arr, begin, end) qsort(arr, begin, p) qsort(arr, p + 1, end) } } const arr = [ 2, 15, 13, 12, 3, 9, 14, 3, 18, 0 ] qsort(arr) arr /*result*/ ` const i = test_initial_state(code, code.indexOf('arr /*result*/')) const expected = [ 0, 2, 3, 3, 9, 12, 13, 14, 15, 18 ] assert_value_explorer(i, expected) }), test('leporello storage API', () => { const i = test_initial_state(` const value = leporello.storage.get('value') if(value == null) { leporello.storage.set('value', 1) } `) const with_storage = input(i, 'leporello.storage.get("value")', 0).state assert_value_explorer(with_storage, 1) const with_cleared_storage = run_code( COMMANDS.open_app_window(with_storage) ) assert_value_explorer(with_cleared_storage, undefined) }), ]