mod common;
use common::{Session, TempProject, DOWN, ENTER};
const SCAFFOLDED_SELENE: &str = r#"std = "roblox+testez"
exclude = ["Packages/**", "ServerPackages/**"]
[rules]
undefined_variable = "deny"
unused_variable = "warn"
shadowing = "warn"
global_usage = "warn"
incorrect_standard_library_use = "deny"
mixed_table = "warn"
multiple_statements = "allow"
roblox_incorrect_roact_usage = "deny"
"#;
const SELENE_PROMPTS: &[&str] = &[
"std",
"rules.undefined_variable",
"rules.unused_variable",
"rules.shadowing",
"rules.global_usage",
"rules.incorrect_standard_library_use",
"rules.mixed_table",
"rules.multiple_statements",
"rules.roblox_incorrect_roact_usage",
];
const STYLUA_PROMPTS: &[&str] = &[
"syntax",
"column_width",
"indent_type",
"indent_width",
"quote_style",
"call_parentheses",
"collapse_simple_statement",
"line_endings",
"sort_requires.enabled",
];
const VSCODE_STYLUA_PROMPTS: &[&str] = &["editor.formatOnSave", "stylua.searchParentDirectories"];
#[test]
fn agreeing_with_every_prompt_leaves_the_file_byte_identical() {
let project = TempProject::new("selene-noop");
project.write("selene.toml", SCAFFOLDED_SELENE);
let mut session = Session::start(project.path(), &["configure", "selene"]);
session.enter_through(SELENE_PROMPTS);
let outcome = session.finish();
assert_eq!(outcome.code, 0, "{}", outcome.text);
assert_eq!(project.read("selene.toml"), SCAFFOLDED_SELENE);
}
#[test]
fn changing_one_lint_rewrites_only_that_line() {
let project = TempProject::new("selene-change");
project.write("selene.toml", SCAFFOLDED_SELENE);
let mut session = Session::start(project.path(), &["configure", "selene"]);
session.enter_through(&SELENE_PROMPTS[..3]);
session.wait_for_prompt("rules.shadowing");
session.send(&format!("{DOWN}{ENTER}"));
session.enter_through(&SELENE_PROMPTS[4..]);
let outcome = session.finish();
assert_eq!(outcome.code, 0, "{}", outcome.text);
let after = project.read("selene.toml");
assert_eq!(after, SCAFFOLDED_SELENE.replace(r#"shadowing = "warn""#, r#"shadowing = "allow""#));
}
#[test]
fn an_unreadable_config_is_refused_before_a_single_question() {
let project = TempProject::new("selene-broken");
project.write("selene.toml", "std = \"roblox\n[rules]\n");
let outcome = Session::start(project.path(), &["configure", "selene"]).finish();
assert_eq!(outcome.code, 1, "{}", outcome.text);
outcome.assert_contains("could not parse");
outcome.assert_lacks("Value:");
}
#[test]
fn an_unknown_tool_is_refused_with_the_list_of_real_ones() {
let project = TempProject::new("unknown-tool");
let outcome = Session::start(project.path(), &["configure", "nosuchtool"]).finish();
assert_eq!(outcome.code, 1, "{}", outcome.text);
outcome.assert_contains("no configurable tool called `nosuchtool`");
for key in ["stylua", "selene", "luau-lsp", "stylua-vscode"] {
outcome.assert_contains(key);
}
}
#[test]
fn the_tool_picker_offers_every_tool_and_runs_the_chosen_one() {
let project = TempProject::new("tool-picker");
let mut session = Session::start(project.path(), &["configure"]);
session.wait_for("Which tool do you want to configure?");
for key in ["stylua - StyLua", "selene - Selene", "luau-lsp - ", "stylua-vscode - "] {
session.wait_for(key);
}
session.send(ENTER);
session.enter_through(STYLUA_PROMPTS);
let outcome = session.finish();
assert_eq!(outcome.code, 0, "{}", outcome.text);
let written = project.read("stylua.toml");
assert!(written.contains(r#"syntax = "Luau""#), "{written}");
assert!(written.contains("[sort_requires]"), "{written}");
}
#[test]
fn vscode_settings_are_merged_not_replaced() {
let project = TempProject::new("vscode-merge");
project.write(".vscode/settings.json", "{\n \"editor.tabSize\": 2\n}\n");
let mut session = Session::start(project.path(), &["configure", "stylua-vscode"]);
session.enter_through(VSCODE_STYLUA_PROMPTS);
let outcome = session.finish();
assert_eq!(outcome.code, 0, "{}", outcome.text);
let written = project.read(".vscode/settings.json");
assert!(written.contains(r#""editor.tabSize": 2"#), "unrelated setting lost:\n{written}");
assert!(written.contains(r#""editor.formatOnSave": true"#), "{written}");
assert!(written.contains(r#""stylua.searchParentDirectories": true"#), "{written}");
}
#[test]
fn a_second_run_proposes_what_you_chose_the_first_time() {
let project = TempProject::new("vscode-remember");
let mut first = Session::start(project.path(), &["configure", "stylua-vscode"]);
first.wait_for_prompt("editor.formatOnSave");
first.send(&format!("n{ENTER}"));
first.enter_through(&VSCODE_STYLUA_PROMPTS[1..]);
assert_eq!(first.finish().code, 0);
assert!(
project.read(".vscode/settings.json").contains(r#""editor.formatOnSave": false"#),
"first run should have turned it off"
);
let mut second = Session::start(project.path(), &["configure", "stylua-vscode"]);
second.wait_for_prompt("editor.formatOnSave");
second.wait_for("(y/N)");
second.enter_through(VSCODE_STYLUA_PROMPTS);
let outcome = second.finish();
assert_eq!(outcome.code, 0, "{}", outcome.text);
let written = project.read(".vscode/settings.json");
assert!(written.contains(r#""editor.formatOnSave": false"#), "reverted:\n{written}");
}