#[test]
fn test_emit_public_api() {
let config = Config::default();
let ir = ShellIR::Let {
name: "test".to_string(),
value: ShellValue::String("value".to_string()),
effects: EffectSet::pure(),
};
let result = emit(&ir).unwrap();
assert!(result.contains("test='value'"));
assert!(!result.contains("readonly"));
}
#[test]
fn test_different_shell_dialects() {
let mut config = Config::default();
let ir = ShellIR::Noop;
config.target = crate::models::ShellDialect::Posix;
let result = emit(&ir).unwrap();
assert!(result.contains("#!/bin/sh"));
config.target = crate::models::ShellDialect::Bash;
let result = emit(&ir).unwrap();
assert!(result.contains("#!/bin/sh"));
}
#[test]
fn test_indentation_consistency() {
let config = Config::default();
let emitter = PosixEmitter::new();
let ir = ShellIR::If {
test: ShellValue::Bool(true),
then_branch: Box::new(ShellIR::If {
test: ShellValue::Bool(false),
then_branch: Box::new(ShellIR::Let {
name: "nested".to_string(),
value: ShellValue::String("deep".to_string()),
effects: EffectSet::pure(),
}),
else_branch: None,
}),
else_branch: None,
};
let result = emitter.emit(&ir).unwrap();
let lines: Vec<&str> = result.lines().collect();
let main_function_start = lines
.iter()
.position(|&line| line.contains("main() {"))
.unwrap();
for line in &lines[main_function_start + 1..] {
if line.trim().is_empty() || line.starts_with('#') || line.starts_with('}') {
continue;
}
if line.contains("trap") || line.contains("main \"$@\"") {
break;
}
assert!(
line.starts_with(" ") || line.starts_with(" "),
"Line not properly indented: '{line}'"
);
}
}
#[test]
fn test_env_emits_dollar_brace_syntax() {
use crate::models::Config;
let ir = crate::ir::shell_ir::ShellIR::Let {
name: "home".to_string(),
value: ShellValue::EnvVar {
name: "HOME".to_string(),
default: None,
},
effects: crate::ir::effects::EffectSet::pure(),
};
let config = Config::default();
let output = super::emit(&ir).unwrap();
assert!(
output.contains("\"${HOME}\""),
"env() should emit ${{VAR}} with quotes, got: {}",
output
);
assert!(
output.contains("home=\"${HOME}\""),
"Should assign quoted env var to variable, got: {}",
output
);
}
#[test]
fn test_env_var_or_emits_with_default() {
use crate::models::Config;
let ir = crate::ir::shell_ir::ShellIR::Let {
name: "prefix".to_string(),
value: ShellValue::EnvVar {
name: "PREFIX".to_string(),
default: Some("/usr/local".to_string()),
},
effects: crate::ir::effects::EffectSet::pure(),
};
let config = Config::default();
let output = super::emit(&ir).unwrap();
assert!(
output.contains("\"${PREFIX:-/usr/local}\""),
"env_var_or() should emit ${{VAR:-default}} with quotes, got: {}",
output
);
assert!(
output.contains("prefix=\"${PREFIX:-/usr/local}\""),
"Should assign quoted env var with default, got: {}",
output
);
}
#[test]
fn test_env_var_quoted_for_safety() {
use crate::models::Config;
let ir = crate::ir::shell_ir::ShellIR::Sequence(vec![
crate::ir::shell_ir::ShellIR::Let {
name: "user".to_string(),
value: ShellValue::EnvVar {
name: "USER".to_string(),
default: None,
},
effects: crate::ir::effects::EffectSet::pure(),
},
crate::ir::shell_ir::ShellIR::Let {
name: "home".to_string(),
value: ShellValue::EnvVar {
name: "HOME".to_string(),
default: Some("/tmp".to_string()),
},
effects: crate::ir::effects::EffectSet::pure(),
},
]);
let config = Config::default();
let output = super::emit(&ir).unwrap();
assert!(
!output.contains("=$USER") && !output.contains("= $USER"),
"Env vars must be quoted, found unquoted $USER: {}",
output
);
assert!(
!output.contains("=$HOME:"),
"Env vars with defaults must be quoted, found unquoted $HOME:-...: {}",
output
);
assert!(
output.contains("\"${USER}\"") || output.contains("\"$USER\""),
"Should have quoted $USER: {}",
output
);
assert!(
output.contains("\"${HOME:-/tmp}\"") || output.contains("\"$HOME:-/tmp\""),
"Should have quoted $HOME:-/tmp: {}",
output
);
}
#[test]
fn test_env_complex_default_value() {
use crate::models::Config;
let ir = crate::ir::shell_ir::ShellIR::Let {
name: "message".to_string(),
value: ShellValue::EnvVar {
name: "MESSAGE".to_string(),
default: Some("hello world".to_string()), },
effects: crate::ir::effects::EffectSet::pure(),
};
let config = Config::default();
let output = super::emit(&ir).unwrap();
assert!(
output.contains("${MESSAGE:-hello world}")
|| output.contains("${MESSAGE:-\"hello world\"}"),
"Should handle default with spaces, got: {}",
output
);
}
#[test]
fn test_arg_emits_positional_syntax() {
use crate::models::Config;
let ir = crate::ir::shell_ir::ShellIR::Let {
name: "first".to_string(),
value: ShellValue::Arg { position: Some(1) },
effects: crate::ir::effects::EffectSet::pure(),
};
let config = Config::default();
let output = super::emit(&ir).unwrap();
assert!(
output.contains("\"$1\""),
"arg(1) should emit $1 with quotes, got: {}",
output
);
assert!(
output.contains("first=\"$1\""),
"Should assign quoted positional arg to variable, got: {}",
output
);
}