1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-03 17:08:35 -05:00
denoland-deno/cli/tests/integration/repl_tests.rs
David Sherret 9bc81b8ac7 fix(repl): improve package.json support (#18497)
1. Fixes a cosmetic issue in the repl where it would display lsp warning
messages.
2. Lazily loads dependencies from the package.json on use.
3. Supports using bare specifiers from package.json in the REPL.

Closes #17929
Closes #18494
2023-03-31 11:43:20 -06:00

923 lines
23 KiB
Rust

// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use test_util as util;
use test_util::assert_contains;
use test_util::assert_ends_with;
use test_util::assert_not_contains;
use util::TempDir;
use util::TestContextBuilder;
#[test]
fn pty_multiline() {
util::with_pty(&["repl"], |mut console| {
console.write_line("(\n1 + 2\n)");
console.expect("3");
console.write_line("{\nfoo: \"foo\"\n}");
console.expect("{ foo: \"foo\" }");
console.write_line("`\nfoo\n`");
console.expect("\"\\nfoo\\n\"");
console.write_line("`\n\\`\n`");
console.expect(r#""\n`\n""#);
console.write_line("'{'");
console.expect(r#""{""#);
console.write_line("'('");
console.expect(r#""(""#);
console.write_line("'['");
console.expect(r#""[""#);
console.write_line("/{/");
console.expect("/{/");
console.write_line("/\\(/");
console.expect("/\\(/");
console.write_line("/\\[/");
console.expect("/\\[/");
console.write_line("console.log(\"{test1} abc {test2} def {{test3}}\".match(/{([^{].+?)}/));");
console.expect("[ \"{test1}\", \"test1\" ]");
});
}
#[test]
fn pty_null() {
util::with_pty(&["repl"], |mut console| {
console.write_line("null");
console.expect("null");
});
}
#[test]
fn pty_unpaired_braces() {
for right_brace in &[")", "]", "}"] {
util::with_pty(&["repl"], |mut console| {
console.write_line(right_brace);
console.expect("parse error: Expression expected");
});
}
}
#[test]
fn pty_bad_input() {
util::with_pty(&["repl"], |mut console| {
console.write_line("'\\u{1f3b5}'[0]");
console.expect("Unterminated string literal");
});
}
#[test]
fn pty_syntax_error_input() {
util::with_pty(&["repl"], |mut console| {
console.write_line("('\\u')");
console.expect("Bad character escape sequence, expected 4 hex characters");
console.write_line("'");
console.expect("Unterminated string constant");
console.write_line("[{'a'}];");
console.expect("Expected a semicolon");
});
}
#[test]
fn pty_complete_symbol() {
util::with_pty(&["repl"], |mut console| {
console.write_line_raw("Symbol.it\t");
console.expect("Symbol(Symbol.iterator)");
});
}
#[test]
fn pty_complete_declarations() {
util::with_pty(&["repl"], |mut console| {
console.write_line("class MyClass {}");
console.expect("undefined");
console.write_line_raw("My\t");
console.expect("[Class: MyClass]");
console.write_line("let myVar = 2 + 3;");
console.expect("undefined");
console.write_line_raw("myV\t");
console.expect("5");
});
}
#[test]
fn pty_complete_primitives() {
util::with_pty(&["repl"], |mut console| {
console.write_line("let func = function test(){}");
console.expect("undefined");
console.write_line_raw("func.appl\t");
console.expect("func.apply");
console.write_line("let str = ''");
console.expect("undefined");
console.write_line_raw("str.leng\t");
console.expect("str.length");
console.write_line_raw("false.valueO\t");
console.expect("false.valueOf");
console.write_line_raw("5n.valueO\t");
console.expect("5n.valueOf");
console.write_line("let num = 5");
console.expect("undefined");
console.write_line_raw("num.toStrin\t");
console.expect("num.toString");
});
}
#[test]
fn pty_complete_expression() {
util::with_pty(&["repl"], |mut console| {
console.write_raw("Deno.\t\t");
console.expect("Display all");
console.write_raw("y");
console.expect_all(&["symlink", "args", "permissions", "exit"]);
});
}
#[test]
fn pty_complete_imports() {
util::with_pty(&["repl", "-A"], |mut console| {
// single quotes
console.write_line_raw("import './run/001_hel\t'");
console.expect("Hello World");
// double quotes
console.write_line_raw("import { output } from \"./run/045_out\t\"");
console.expect("\"./run/045_output.ts\"");
console.write_line_raw("output('testing output');");
console.expect("testing output");
});
// ensure when the directory changes that the suggestions come from the cwd
util::with_pty(&["repl", "-A"], |mut console| {
console.write_line("Deno.chdir('./subdir');");
console.expect("undefined");
console.write_line_raw("import '../run/001_hel\t'");
console.expect("Hello World");
});
}
#[test]
fn pty_complete_imports_no_panic_empty_specifier() {
// does not panic when tabbing when empty
util::with_pty(&["repl", "-A"], |mut console| {
if cfg!(windows) {
console.write_line_raw("import '\t'");
console.expect_any(&["not prefixed with", "https://deno.land"]);
} else {
console.write_raw("import '\t");
console.expect("import 'https://deno.land");
}
});
}
#[test]
fn pty_ignore_symbols() {
util::with_pty(&["repl"], |mut console| {
console.write_line_raw("Array.Symbol\t");
console.expect("undefined");
});
}
#[test]
fn pty_assign_global_this() {
util::with_pty(&["repl"], |mut console| {
console.write_line("globalThis = 40 + 2;");
console.expect("42");
});
}
#[test]
fn pty_assign_deno_keys_and_deno() {
util::with_pty(&["repl"], |mut console| {
console.write_line(
"Object.keys(Deno).forEach((key)=>{try{Deno[key] = undefined} catch {}})",
);
console.expect("undefined");
console.write_line("delete globalThis.Deno");
console.expect("true");
console.write_line("console.log('testing ' + 'this out');");
console.expect("testing this out");
console.expect("undefined");
});
}
#[test]
fn pty_internal_repl() {
util::with_pty(&["repl"], |mut console| {
console.write_line("'Length: ' + Object.keys(globalThis).filter(k => k.startsWith('__DENO_')).length;");
console.expect("Length: 0");
console.write_line_raw("__\t\t");
console.expect("> __");
let output = console.read_until("> __");
assert_contains!(output, "__defineGetter__");
// should not contain the internal repl variable
// in the `globalThis` or completions output
assert_not_contains!(output, "__DENO_");
});
}
#[test]
fn pty_emoji() {
// windows was having issues displaying this
util::with_pty(&["repl"], |mut console| {
console.write_line(r#"console.log('\u{1F995}');"#);
console.expect("🦕");
});
}
#[test]
fn console_log() {
util::with_pty(&["repl"], |mut console| {
console.write_line("console.log('hello');");
console.expect("hello");
console.write_line("'world'");
console.expect("\"world\"");
});
}
#[test]
fn object_literal() {
util::with_pty(&["repl"], |mut console| {
console.write_line("{}");
console.expect("{}");
console.write_line("{ foo: 'bar' }");
console.expect("{ foo: \"bar\" }");
});
}
#[test]
fn block_expression() {
util::with_pty(&["repl"], |mut console| {
console.write_line("{};");
console.expect("undefined");
console.write_line("{\"\"}");
console.expect("\"\"");
});
}
#[test]
fn await_resolve() {
util::with_pty(&["repl"], |mut console| {
console.write_line("await Promise.resolve('done')");
console.expect("\"done\"");
});
}
#[test]
fn await_timeout() {
util::with_pty(&["repl"], |mut console| {
console.write_line("await new Promise((r) => setTimeout(r, 0, 'done'))");
console.expect("\"done\"");
});
}
#[test]
fn let_redeclaration() {
util::with_pty(&["repl"], |mut console| {
console.write_line("let foo = 0;");
console.expect("undefined");
console.write_line("foo");
console.expect("0");
console.write_line("let foo = 1;");
console.expect("undefined");
console.write_line("foo");
console.expect("1");
});
}
#[test]
fn repl_cwd() {
util::with_pty(&["repl", "-A"], |mut console| {
console.write_line("Deno.cwd()");
console.expect("testdata");
});
}
#[test]
fn typescript() {
util::with_pty(&["repl"], |mut console| {
console.write_line("function add(a: number, b: number) { return a + b }");
console.expect("undefined");
console.write_line("const result: number = add(1, 2) as number;");
console.expect("undefined");
console.write_line("result");
console.expect("3");
});
}
#[test]
fn typescript_declarations() {
util::with_pty(&["repl"], |mut console| {
console.write_line("namespace Test { export enum Values { A, B, C } }");
console.expect("undefined");
console.write_line("Test.Values.A");
console.expect("0");
console.write_line("Test.Values.C");
console.expect("2");
console.write_line("interface MyInterface { prop: string; }");
console.expect("undefined");
console.write_line("type MyTypeAlias = string;");
console.expect("undefined");
});
}
#[test]
fn typescript_decorators() {
util::with_pty(&["repl"], |mut console| {
console
.write_line("function dec(target) { target.prototype.test = () => 2; }");
console.expect("undefined");
console.write_line("@dec class Test {}");
console.expect("[Class: Test]");
console.write_line("new Test().test()");
console.expect("2");
});
}
#[test]
fn eof() {
util::with_pty(&["repl"], |mut console| {
console.write_line("1 + 2");
console.expect("3");
});
}
#[test]
fn strict() {
util::with_pty(&["repl"], |mut console| {
console.write_line("let a = {};");
console.expect("undefined");
console.write_line("Object.preventExtensions(a)");
console.expect("{}");
console.write_line("a.c = 1;");
console.expect(
"Uncaught TypeError: Cannot add property c, object is not extensible",
);
});
}
#[test]
fn close_command() {
let (out, err) = util::run_and_collect_output(
true,
"repl",
Some(vec!["close()", "'ignored'"]),
None,
false,
);
assert_not_contains!(out, "ignored");
assert!(err.is_empty());
}
#[test]
fn function() {
util::with_pty(&["repl"], |mut console| {
console.write_line("Deno.writeFileSync");
console.expect("[Function: writeFileSync]");
});
}
#[test]
fn multiline() {
util::with_pty(&["repl"], |mut console| {
console.write_line("(\n1 + 2\n)");
console.expect("3");
});
}
#[test]
fn import() {
util::with_pty(&["repl", "-A"], |mut console| {
console.write_line("import('./subdir/auto_print_hello.ts')");
console.expect("hello!");
});
}
#[test]
fn import_declarations() {
util::with_pty(&["repl", "-A"], |mut console| {
console.write_line("import './subdir/auto_print_hello.ts'");
console.expect("hello!");
});
}
#[test]
fn exports_stripped() {
util::with_pty(&["repl"], |mut console| {
console.write_line("const test = 5 + 1; export default test;");
console.expect("6");
console.write_line("export class Test {}");
console.expect("undefined");
});
}
#[test]
fn call_eval_unterminated() {
util::with_pty(&["repl"], |mut console| {
console.write_line("eval('{')");
console.expect("Unexpected end of input");
});
}
#[test]
fn unpaired_braces() {
util::with_pty(&["repl"], |mut console| {
for right_brace in &[")", "]", "}"] {
console.write_line(right_brace);
console.expect("Expression expected");
}
});
}
#[test]
fn reference_error() {
util::with_pty(&["repl"], |mut console| {
console.write_line("not_a_variable");
console.expect("not_a_variable is not defined");
});
}
#[test]
fn syntax_error() {
util::with_pty(&["repl"], |mut console| {
console.write_line("syntax error");
console.expect("parse error: Expected ';', '}' or <eof>");
// ensure it keeps accepting input after
console.write_line("7 * 6");
console.expect("42");
});
}
#[test]
fn syntax_error_jsx() {
// JSX is not supported in the REPL
util::with_pty(&["repl"], |mut console| {
console.write_line("const element = <div />;");
console.expect("Expression expected");
});
}
#[test]
fn type_error() {
util::with_pty(&["repl"], |mut console| {
console.write_line("console()");
console.expect("console is not a function");
});
}
#[test]
fn variable() {
util::with_pty(&["repl"], |mut console| {
console.write_line("var a = 123 + 456;");
console.expect("undefined");
console.write_line("a");
console.expect("579");
});
}
#[test]
fn lexical_scoped_variable() {
util::with_pty(&["repl"], |mut console| {
console.write_line("let a = 123 + 456;");
console.expect("undefined");
console.write_line("a");
console.expect("579");
});
}
#[test]
fn missing_deno_dir() {
use std::fs::read_dir;
let temp_dir = TempDir::new();
let deno_dir_path = temp_dir.path().join("deno");
let (out, err) = util::run_and_collect_output(
true,
"repl",
Some(vec!["1"]),
Some(vec![
(
"DENO_DIR".to_owned(),
deno_dir_path.to_str().unwrap().to_owned(),
),
("NO_COLOR".to_owned(), "1".to_owned()),
]),
false,
);
assert!(read_dir(deno_dir_path).is_ok());
assert_ends_with!(out, "1\n");
assert!(err.is_empty());
}
#[test]
fn custom_history_path() {
use std::fs::read;
let temp_dir = TempDir::new();
let history_path = temp_dir.path().join("history.txt");
let (out, err) = util::run_and_collect_output(
true,
"repl",
Some(vec!["1"]),
Some(vec![
(
"DENO_REPL_HISTORY".to_owned(),
history_path.to_str().unwrap().to_owned(),
),
("NO_COLOR".to_owned(), "1".to_owned()),
]),
false,
);
assert!(read(&history_path).is_ok());
assert_ends_with!(out, "1\n");
assert!(err.is_empty());
}
#[test]
fn disable_history_file() {
let deno_dir = util::new_deno_dir();
let default_history_path = deno_dir.path().join("deno_history.txt");
let (out, err) = util::run_and_collect_output(
true,
"repl",
Some(vec!["1"]),
Some(vec![
(
"DENO_DIR".to_owned(),
deno_dir.path().to_str().unwrap().to_owned(),
),
("DENO_REPL_HISTORY".to_owned(), "".to_owned()),
("NO_COLOR".to_owned(), "1".to_owned()),
]),
false,
);
assert!(!default_history_path.try_exists().unwrap());
assert_ends_with!(out, "1\n");
assert!(err.is_empty());
}
#[test]
fn save_last_eval() {
util::with_pty(&["repl"], |mut console| {
console.write_line("1 + 2");
console.expect("3");
console.write_line("_ + 3");
console.expect("6");
});
}
#[test]
fn save_last_thrown() {
util::with_pty(&["repl"], |mut console| {
console.write_line("throw 1 + 2");
console.expect("Uncaught 3");
console.write_line("_error + 3");
console.expect("6");
});
}
#[test]
fn assign_underscore() {
util::with_pty(&["repl"], |mut console| {
console.write_line("_ = 1");
console.expect("Last evaluation result is no longer saved to _.");
console.write_line("2 + 3");
console.expect("5");
console.write_line("_");
console.expect("1");
});
}
#[test]
fn assign_underscore_error() {
util::with_pty(&["repl"], |mut console| {
console.write_line("_error = 1");
console.expect("Last thrown error is no longer saved to _error.");
console.write_line("throw 2");
console.expect("Uncaught 2");
console.write_line("_error");
console.expect("1");
});
}
#[test]
fn custom_inspect() {
util::with_pty(&["repl"], |mut console| {
console.write_line(
r#"const o = {
[Symbol.for("Deno.customInspect")]() {
throw new Error('Oops custom inspect error');
},
};"#,
);
console.expect("undefined");
console.write_line("o");
console.expect("Oops custom inspect error");
});
}
#[test]
fn eval_flag_valid_input() {
util::with_pty(&["repl", "--eval", "const t = 10;"], |mut console| {
console.write_line("t * 500");
console.expect("5000");
});
}
#[test]
fn eval_flag_parse_error() {
let (out, err) = util::run_and_collect_output_with_args(
true,
vec!["repl", "--eval", "const %"],
Some(vec!["250 * 10"]),
None,
false,
);
assert_contains!(
test_util::strip_ansi_codes(&out),
"Error in --eval flag: parse error: Unexpected token `%`."
);
assert_contains!(out, "2500"); // should not prevent input
assert!(err.is_empty());
}
#[test]
fn eval_flag_runtime_error() {
let (out, err) = util::run_and_collect_output_with_args(
true,
vec!["repl", "--eval", "throw new Error('Testing')"],
Some(vec!["250 * 10"]),
None,
false,
);
assert_contains!(out, "Error in --eval flag: Uncaught Error: Testing");
assert_contains!(out, "2500"); // should not prevent input
assert!(err.is_empty());
}
#[test]
fn eval_file_flag_valid_input() {
let (out, err) = util::run_and_collect_output_with_args(
true,
vec!["repl", "--eval-file=./run/001_hello.js"],
None,
None,
false,
);
assert_contains!(out, "Hello World");
assert!(err.is_empty());
}
#[test]
fn eval_file_flag_call_defined_function() {
let (out, err) = util::run_and_collect_output_with_args(
true,
vec!["repl", "--eval-file=./tsc/d.ts"],
Some(vec!["v4()"]),
None,
false,
);
assert_contains!(out, "hello");
assert!(err.is_empty());
}
#[test]
fn eval_file_flag_http_input() {
let (out, err) = util::run_and_collect_output_with_args(
true,
vec!["repl", "--eval-file=http://127.0.0.1:4545/tsc/d.ts"],
Some(vec!["v4()"]),
None,
true,
);
assert_contains!(out, "hello");
assert!(err.contains("Download"));
}
#[test]
fn eval_file_flag_multiple_files() {
let (out, err) = util::run_and_collect_output_with_args(
true,
vec!["repl", "--allow-read", "--eval-file=http://127.0.0.1:4545/repl/import_type.ts,./tsc/d.ts,http://127.0.0.1:4545/type_definitions/foo.js"],
Some(vec!["b.method1=v4", "b.method1()+foo.toUpperCase()"]),
None,
true,
);
assert_contains!(out, "helloFOO");
assert_contains!(err, "Download");
}
#[test]
fn pty_clear_function() {
util::with_pty(&["repl"], |mut console| {
console.write_line("console.log('h' + 'ello');");
console.expect_all(&["hello", "undefined"]);
console.write_line_raw("clear();");
if cfg!(windows) {
// expect a bunch of these in the output
console.expect_raw_in_current_output(
"\r\n\u{1b}[K\r\n\u{1b}[K\r\n\u{1b}[K\r\n\u{1b}[K\r\n\u{1b}[K",
);
} else {
console.expect_raw_in_current_output("[1;1H");
}
console.expect("undefined"); // advance past the "clear()"'s undefined
console.expect("> ");
console.write_line("const clear = 1234 + 2000;");
console.expect("undefined");
console.write_line("clear;");
console.expect("3234");
});
}
#[test]
fn pty_tab_handler() {
// If the last character is **not** whitespace, we show the completions
util::with_pty(&["repl"], |mut console| {
console.write_raw("a\t\t");
console.expect_all(&["addEventListener", "alert", "atob"]);
});
// If the last character is whitespace, we just insert a tab
util::with_pty(&["repl"], |mut console| {
console.write_line("const a = 5;");
console.expect("undefined");
console.write_raw("a; \t\ta + 2;\n"); // last character is whitespace
console.expect_any(&[
// windows
"a; a + 2;",
// unix
"a; \t\ta + 2;",
]);
});
}
#[test]
fn repl_report_error() {
util::with_pty(&["repl"], |mut console| {
console.write_line("console.log(1);");
console.expect_all(&["1", "undefined"]);
// TODO(nayeemrmn): The REPL should report event errors and rejections.
console.write_line(r#"reportError(new Error("foo"));"#);
console.expect("undefined");
console.write_line("console.log(2);");
console.expect("2");
});
}
#[test]
fn pty_aggregate_error() {
util::with_pty(&["repl"], |mut console| {
console.write_line("await Promise.any([])");
console.expect("AggregateError");
});
}
#[test]
fn repl_with_quiet_flag() {
let (out, err) = util::run_and_collect_output_with_args(
true,
vec!["repl", "--quiet"],
Some(vec!["await Promise.resolve('done')"]),
Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]),
false,
);
assert!(!out.contains("Deno"));
assert!(!out.contains("exit using ctrl+d, ctrl+c, or close()"));
assert_ends_with!(out, "\"done\"\n");
assert!(err.is_empty());
}
#[test]
fn npm_packages() {
let mut env_vars = util::env_vars_for_npm_tests();
env_vars.push(("NO_COLOR".to_owned(), "1".to_owned()));
let temp_dir = TempDir::new();
env_vars.push((
"DENO_DIR".to_string(),
temp_dir.path().to_string_lossy().to_string(),
));
{
let (out, err) = util::run_and_collect_output_with_args(
true,
vec!["repl", "--quiet", "--allow-read", "--allow-env"],
Some(vec![
r#"import chalk from "npm:chalk";"#,
"chalk.red('hel' + 'lo')",
]),
Some(env_vars.clone()),
true,
);
assert_contains!(out, "hello");
assert!(err.is_empty());
}
{
let (out, err) = util::run_and_collect_output_with_args(
true,
vec!["repl", "--quiet", "--allow-read", "--allow-env"],
Some(vec![
r#"const chalk = await import("npm:chalk");"#,
"chalk.default.red('hel' + 'lo')",
]),
Some(env_vars.clone()),
true,
);
assert_contains!(out, "hello");
assert!(err.is_empty());
}
{
let (out, err) = util::run_and_collect_output_with_args(
true,
vec!["repl", "--quiet", "--allow-read", "--allow-env"],
Some(vec![r#"export {} from "npm:chalk";"#]),
Some(env_vars.clone()),
true,
);
assert_contains!(out, "Module {");
assert_contains!(out, "Chalk: [Class: Chalk],");
assert!(err.is_empty());
}
{
let (out, err) = util::run_and_collect_output_with_args(
true,
vec!["repl", "--quiet", "--allow-read", "--allow-env"],
Some(vec![r#"import foo from "npm:asdfawe52345asdf""#]),
Some(env_vars.clone()),
true,
);
assert_contains!(
out,
"error: npm package 'asdfawe52345asdf' does not exist"
);
assert!(err.is_empty());
}
{
let (out, err) = util::run_and_collect_output_with_args(
true,
vec!["repl", "--quiet", "--allow-read", "--allow-env"],
Some(vec![
"import path from 'node:path';",
"path.isGlob('asdf') ? 'yes' : 'no'",
]),
Some(env_vars.clone()),
true,
);
assert_contains!(out, "no");
assert!(err.is_empty());
}
}
#[test]
fn pty_tab_indexable_props() {
util::with_pty(&["repl"], |mut console| {
console.write_line("const arr = [1, 2, 3]");
console.expect("undefined");
console.write_raw("arr.\t\t");
console.expect("> arr.");
let output = console.read_until("> arr.");
assert_contains!(output, "constructor");
assert_contains!(output, "sort");
assert_contains!(output, "at");
assert_not_contains!(output, "0", "1", "2");
});
}
#[test]
fn package_json_uncached_no_error() {
let test_context = TestContextBuilder::for_npm()
.use_temp_cwd()
.use_http_server()
.env("RUST_BACKTRACE", "1")
.build();
let temp_dir = test_context.temp_dir();
temp_dir.write(
"package.json",
r#"{
"dependencies": {
"@denotest/esm-basic": "1.0.0"
}
}
"#,
);
test_context.new_command().with_pty(|mut console| {
console.write_line("console.log(123 + 456);");
console.expect("579");
assert_not_contains!(
console.all_output(),
"Could not set npm package requirements",
);
// should support getting the package now though
console
.write_line("import { getValue, setValue } from '@denotest/esm-basic';");
console.expect("undefined");
console.write_line("setValue(12 + 30);");
console.expect("undefined");
console.write_line("getValue()");
console.expect("42")
});
}