mirror of
https://github.com/leporello-js/leporello-js
synced 2026-01-13 13:04:30 -08:00
5380 lines
133 KiB
JavaScript
5380 lines
133 KiB
JavaScript
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,
|
|
test_initial_state, test_initial_state_async,
|
|
test_deferred_calls_state,
|
|
print_debug_ct_node,
|
|
command_input_async,
|
|
patch_builtin,
|
|
original_setTimeout,
|
|
} 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} = COMMANDS.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.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.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} = COMMANDS.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', () => {
|
|
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: true,
|
|
module: {
|
|
'foo_var': 'foo_value'
|
|
},
|
|
}
|
|
})
|
|
|
|
const edited = `
|
|
// external
|
|
import {foo_var} from 'foo.js'
|
|
foo_var
|
|
`
|
|
|
|
const {state} = COMMANDS.input(
|
|
next,
|
|
edited,
|
|
edited.lastIndexOf('foo_var'),
|
|
)
|
|
|
|
// If cache was not used then effects will be `load_external_imports`
|
|
assert_equal(state.value_explorer.result.value, 'foo_value')
|
|
}),
|
|
|
|
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} = COMMANDS.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} = COMMANDS.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 = COMMANDS.input(s2, invalid, invalid.indexOf('return')).state
|
|
|
|
const edited = `
|
|
const x = foo => {
|
|
return foo*3
|
|
};
|
|
|
|
x(2);
|
|
`
|
|
|
|
const n = COMMANDS.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 = COMMANDS.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} = COMMANDS.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 = COMMANDS.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 = COMMANDS.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 = COMMANDS.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 = COMMANDS.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 input = COMMANDS.input(i, code, 0).state
|
|
|
|
// Make deferred call, calling fn from previous code
|
|
i.modules[''].fn(1)
|
|
|
|
const result = on_deferred_call(input)
|
|
|
|
// 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 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 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 command_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)
|
|
|
|
await i.eval_modules_state.promise.__original_then(result => {
|
|
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', () => {
|
|
// Patch Math.random to always return 1
|
|
patch_builtin('random', () => 1)
|
|
|
|
const initial = test_initial_state(`
|
|
const x = Math.random()
|
|
`)
|
|
|
|
// Now call to Math.random is cached, break it to ensure it was not called
|
|
// on next run
|
|
patch_builtin('random', () => { throw 'fail' })
|
|
|
|
const next = COMMANDS.input(initial, `const x = Math.random()*2`, 0).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
|
|
patch_builtin('random', () => 2)
|
|
const replay_failed = COMMANDS.input(
|
|
initial,
|
|
`const x = Math.random() + Math.random()`,
|
|
0
|
|
).state
|
|
|
|
// TODO must reuse first cached call?
|
|
assert_equal(replay_failed.value_explorer.result.value, 4)
|
|
|
|
// Remove patch
|
|
patch_builtin('random', null)
|
|
}),
|
|
|
|
|
|
test('record io trace discarded if args does not match', async () => {
|
|
// Patch fetch
|
|
patch_builtin('fetch', async () => 'first')
|
|
|
|
const initial = await test_initial_state_async(`
|
|
console.log(await fetch('url', {method: 'GET'}))
|
|
`)
|
|
assert_equal(initial.logs.logs[0].args[0], 'first')
|
|
|
|
// Patch fetch again
|
|
patch_builtin('fetch', async () => 'second')
|
|
|
|
const cache_discarded = await command_input_async(initial, `
|
|
console.log(await fetch('url', {method: 'POST'}))
|
|
`, 0)
|
|
assert_equal(cache_discarded.logs.logs[0].args[0], 'second')
|
|
|
|
// Remove patch
|
|
patch_builtin('fetch', null)
|
|
}),
|
|
|
|
test('record io fetch rejects', async () => {
|
|
// Patch fetch
|
|
patch_builtin('fetch', () => Promise.reject('fail'))
|
|
|
|
const initial = await test_initial_state_async(`
|
|
await fetch('url', {method: 'GET'})
|
|
`)
|
|
assert_equal(root_calltree_node(initial).error, 'fail')
|
|
|
|
// Patch fetch again
|
|
patch_builtin('fetch', () => async () => 'result')
|
|
|
|
const with_cache = await command_input_async(initial, `
|
|
await fetch('url', {method: 'GET'})
|
|
`, 0)
|
|
assert_equal(root_calltree_node(initial).error, 'fail')
|
|
|
|
// Remove patch
|
|
patch_builtin('fetch', null)
|
|
}),
|
|
|
|
test('record io preserve promise resolution order', async () => {
|
|
// Generate fetch function which calls get resolved in reverse order
|
|
const {fetch, resolve} = new Function(`
|
|
const calls = []
|
|
return {
|
|
fetch(...args) {
|
|
let resolver
|
|
const promise = new Promise(r => resolver = r)
|
|
calls.push({resolver, promise, args})
|
|
return promise
|
|
},
|
|
|
|
resolve() {
|
|
[...calls].reverse().forEach(call => call.resolver(...call.args))
|
|
},
|
|
}
|
|
`)()
|
|
|
|
// Patch fetch
|
|
patch_builtin('fetch', 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)
|
|
|
|
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
|
|
patch_builtin('fetch', () => {throw 'broken'})
|
|
|
|
const with_cache = await command_input_async(
|
|
initial,
|
|
code,
|
|
0
|
|
)
|
|
|
|
// 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])
|
|
|
|
// Remove patch
|
|
patch_builtin('fetch', null)
|
|
}),
|
|
|
|
test('record io setTimeout', async () => {
|
|
// Patch fetch to return result in 10ms
|
|
patch_builtin(
|
|
'fetch',
|
|
() => new Promise(resolve => original_setTimeout(resolve, 10))
|
|
)
|
|
|
|
const code = `
|
|
setTimeout(() => console.log('timeout'), 0)
|
|
await fetch().then(() => console.log('fetch'))
|
|
`
|
|
|
|
const i = await test_initial_state_async(code)
|
|
|
|
// 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
|
|
patch_builtin('fetch', async () => {throw 'break'})
|
|
|
|
const with_cache = await command_input_async(i, code, 0)
|
|
|
|
// Cache must preserve resolution order
|
|
assert_equal(with_cache.logs.logs.map(l => l.args[0]), ['timeout', 'fetch'])
|
|
|
|
patch_builtin('fetch', null)
|
|
}),
|
|
|
|
test('record io clear io trace', async () => {
|
|
const s1 = test_initial_state(`Math.random()`)
|
|
const rnd = s1.value_explorer.result.value
|
|
const s2 = COMMANDS.input(s1, `Math.random() + 1`, 0).state
|
|
assert_equal(s2.value_explorer.result.value, rnd + 1)
|
|
const cleared = COMMANDS.clear_io_trace(s2)
|
|
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 = COMMANDS.input(i, `await Promise.resolve()`, 0)
|
|
const next2 = COMMANDS.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 () => {
|
|
patch_builtin(
|
|
'fetch',
|
|
() => new Promise(resolve => original_setTimeout(resolve, 0))
|
|
)
|
|
|
|
const code = `
|
|
const p = fetch('')
|
|
Math.random()
|
|
await p
|
|
`
|
|
|
|
const i = await test_initial_state_async(code)
|
|
|
|
assert_equal(i.io_trace.length, 3)
|
|
|
|
const next_code = `await fetch('')`
|
|
|
|
const state = await command_input_async(i, next_code, 0)
|
|
assert_equal(state.io_trace.length, 2)
|
|
|
|
patch_builtin('fetch', null)
|
|
}),
|
|
|
|
test('record io logs recorded twice bug', () => {
|
|
const code = `Math.random()`
|
|
const i = test_initial_state(code)
|
|
const second = COMMANDS.input(
|
|
i,
|
|
`console.log(1); Math.random(); Math.random()`,
|
|
0
|
|
)
|
|
assert_equal(second.state.logs.logs.length, 1)
|
|
}),
|
|
|
|
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)
|
|
|
|
// 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)
|
|
|
|
}),
|
|
]
|