#[cfg(test)]
mod compile_script_tests {
use crate::compile_script::compile_script;
use crate::compile_script::function_mode::compile_script_setup;
use crate::compile_script::props::{
extract_prop_types_from_type, extract_with_defaults_defaults, is_valid_identifier,
};
use crate::compile_script::typescript::transform_typescript_to_js;
use crate::types::SfcDescriptor;
use vize_carton::ToCompactString;
#[test]
fn test_compile_empty_script() {
let descriptor = SfcDescriptor::default();
let result =
compile_script(&descriptor, &Default::default(), "Test", false, false).unwrap();
assert!(result.code.contains("__sfc__"));
}
#[test]
fn test_is_valid_identifier() {
assert!(is_valid_identifier("foo"));
assert!(is_valid_identifier("_bar"));
assert!(is_valid_identifier("$baz"));
assert!(is_valid_identifier("foo123"));
assert!(!is_valid_identifier("123foo"));
assert!(!is_valid_identifier(""));
assert!(!is_valid_identifier("foo-bar"));
}
#[test]
fn test_extract_with_defaults_defaults() {
let input = r#"withDefaults(defineProps<{ msg?: string }>(), { msg: "hello" })"#;
let defaults = extract_with_defaults_defaults(input);
eprintln!("Defaults: {:?}", defaults);
assert_eq!(defaults.get("msg"), Some(&r#""hello""#.to_compact_string()));
let input2 = r#"withDefaults(defineProps<{ msg?: string, count?: number }>(), { msg: "hello", count: 42 })"#;
let defaults2 = extract_with_defaults_defaults(input2);
assert_eq!(
defaults2.get("msg"),
Some(&r#""hello""#.to_compact_string())
);
assert_eq!(defaults2.get("count"), Some(&"42".to_compact_string()));
let input3 = r#"withDefaults(
defineProps<{
checked: boolean;
label?: string;
color?: "primary" | "secondary";
}>(),
{
label: undefined,
color: "primary",
},
)"#;
let defaults3 = extract_with_defaults_defaults(input3);
eprintln!("Defaults3: {:?}", defaults3);
assert_eq!(
defaults3.get("label"),
Some(&"undefined".to_compact_string())
);
assert_eq!(
defaults3.get("color"),
Some(&r#""primary""#.to_compact_string())
);
let input4 = r#"withDefaults(defineProps<{ description?: string }>(), { description: 'a fast, modern browser for the **npm registry**' })"#;
let defaults4 = extract_with_defaults_defaults(input4);
assert_eq!(
defaults4.get("description"),
Some(&"'a fast, modern browser for the **npm registry**'".to_compact_string())
);
}
#[test]
fn test_compile_script_setup_with_define_props() {
let content = r#"
import { ref } from 'vue'
const props = defineProps(['msg'])
const count = ref(0)
"#;
let result = compile_script_setup(content, "Test", false, false, None).unwrap();
println!("Compiled output:\n{}", result.code);
assert!(
result.code.contains("const __sfc__ ="),
"Should have __sfc__"
);
assert!(
result.code.contains("__name:") && result.code.contains("Test"),
"Should have __name. Got:\n{}",
result.code
);
assert!(
result.code.contains("props:") && result.code.contains("msg"),
"Should have props definition. Got:\n{}",
result.code
);
assert!(
result
.code
.contains("setup(__props, { expose: __expose, emit: __emit })"),
"Should have proper setup signature"
);
assert!(
result.code.contains("const __returned__ =")
|| result.code.contains("__returned__ = {"),
"Should have __returned__"
);
}
#[test]
fn test_type_only_imports_not_in_bindings() {
let content = r#"
import type { AnalysisResult } from './wasm'
import type { Diagnostic } from './MonacoEditor.vue'
import { ref } from 'vue'
const analysisResult = ref<AnalysisResult | null>(null)
"#;
let result = compile_script_setup(content, "Test", false, true, None).unwrap();
let bindings = result.bindings.expect("bindings should be present");
assert!(!bindings.bindings.contains_key("AnalysisResult"));
assert!(!bindings.bindings.contains_key("Diagnostic"));
assert!(bindings.bindings.contains_key("analysisResult"));
}
#[test]
fn test_compile_script_setup_with_define_emits() {
let content = r#"
const emit = defineEmits(['click', 'update'])
"#;
let result = compile_script_setup(content, "Test", false, false, None).unwrap();
println!("Full output:\n{}", result.code);
assert!(
result.code.contains("emits:"),
"Should contain emits definition"
);
assert!(
result.code.contains("const emit = __emit"),
"Should bind emit to __emit"
);
assert!(
result.code.contains("emit"),
"emit should be accessible in template"
);
assert!(
!result.code.contains("defineEmits"),
"defineEmits should be removed from setup"
);
}
#[test]
fn test_compile_script_setup_with_define_emits_usage() {
let content = r#"
import { ref } from 'vue'
const emit = defineEmits(['click', 'update'])
const count = ref(0)
function onClick() {
emit('click', count.value)
}
"#;
let result = compile_script_setup(content, "Test", false, false, None).unwrap();
println!("Compiled output:\n{}", result.code);
assert!(
!result.code.contains("defineEmits"),
"defineEmits call should be removed from setup"
);
assert!(
result.code.contains("const emit = __emit"),
"Should bind emit to __emit"
);
assert!(
result.code.contains("function onClick()"),
"onClick should be in setup"
);
assert!(
result.code.contains("emits:")
&& result.code.contains("click")
&& result.code.contains("update"),
"Should have emits definition. Got:\n{}",
result.code
);
}
#[test]
fn test_compile_script_setup_without_macros() {
let content = r#"
import { ref } from 'vue'
const msg = ref('hello')
"#;
let result = compile_script_setup(content, "Test", false, false, None).unwrap();
assert!(result.code.contains("setup(__props"), "Should have setup");
assert!(
!result.code.contains(" props:"),
"Should not contain props"
);
assert!(!result.code.contains("emits:"), "Should not contain emits");
}
#[test]
fn test_compile_script_setup_with_props_destructure() {
let content = r#"
import { computed } from 'vue'
const { count } = defineProps({ count: Number })
const double = computed(() => count * 2)
"#;
let result = compile_script_setup(content, "Test", false, false, None).unwrap();
println!("Compiled output:\n{}", result.code);
assert!(
result.code.contains("__props.count"),
"Should transform destructured prop to __props.count"
);
assert!(
result.code.contains("computed(() => __props.count * 2)"),
"Should have transformed computed expression"
);
assert!(
!result.code.contains("__returned__ = { computed, count,"),
"Destructured props should not be in __returned__"
);
assert!(
result.code.contains("computed") && result.code.contains("double"),
"Should have computed and double in __returned__"
);
}
#[test]
fn test_compiler_macros_not_in_returned() {
let content = r#"
import { defineProps, ref } from 'vue'
const props = defineProps(['msg'])
const count = ref(0)
"#;
let result = compile_script_setup(content, "Test", false, false, None).unwrap();
println!("Compiled output:\n{}", result.code);
let code = &result.code;
let returned_start = code.find("__returned__").expect("Should have __returned__");
let returned_block = &code[returned_start..];
let block_end = returned_block.find(';').unwrap_or(returned_block.len());
let returned_content = &returned_block[..block_end];
println!("__returned__ block: {}", returned_content);
assert!(
!returned_content.contains("defineProps"),
"Compiler macros should not be in __returned__"
);
assert!(
returned_content.contains("ref"),
"Regular imports should be in __returned__"
);
}
#[test]
fn test_props_destructure_with_defaults() {
let content = r#"
import { computed, watch } from 'vue'
const {
name,
count = 0,
disabled = false,
items = () => []
} = defineProps<{
name: string
count?: number
disabled?: boolean
items?: string[]
}>()
const doubled = computed(() => count * 2)
const itemCount = computed(() => items.length)
"#;
let mut ctx = crate::script::ScriptCompileContext::new(content);
ctx.analyze();
println!("=== Context Analysis ===");
println!("props_destructure: {:?}", ctx.macros.props_destructure);
println!("bindings: {:?}", ctx.bindings.bindings);
let result = compile_script_setup(content, "Test", false, false, None).unwrap();
println!("\n=== Compiled output ===\n{}", result.code);
assert!(
!result.code.contains("const {"),
"Should not contain destructure statement"
);
assert!(
!result.code.contains("} = defineProps"),
"Should not contain defineProps assignment"
);
assert!(
result.code.contains("props:"),
"Should have props definition"
);
assert!(
result.code.contains("__props.count"),
"Should transform count to __props.count"
);
assert!(
result.code.contains("__props.items"),
"Should transform items to __props.items"
);
assert!(
result.code.contains("computed(() => __props.count * 2)"),
"Should transform count in computed. Got:\n{}",
result.code
);
}
#[test]
fn test_extract_prop_types() {
let type_args = r#"{
name: string
count?: number
disabled?: boolean
items?: string[]
}"#;
let props = extract_prop_types_from_type(type_args);
let find = |name: &str| props.iter().find(|(n, _)| n == name).map(|(_, v)| v);
assert!(find("name").is_some(), "Should extract name");
assert!(find("count").is_some(), "Should extract count");
assert!(find("disabled").is_some(), "Should extract disabled");
assert!(find("items").is_some(), "Should extract items");
assert_eq!(find("name").unwrap().js_type, "String");
assert_eq!(find("count").unwrap().js_type, "Number");
assert_eq!(find("disabled").unwrap().js_type, "Boolean");
assert_eq!(find("items").unwrap().js_type, "Array");
assert!(!find("name").unwrap().optional);
assert!(find("count").unwrap().optional);
assert!(find("disabled").unwrap().optional);
assert!(find("items").unwrap().optional);
}
#[test]
fn test_compile_script_setup_with_multiline_define_emits() {
let content = r#"
const emit = defineEmits<{
(e: 'click', payload: MouseEvent): void
(e: 'update', value: string): void
}>()
function handleClick(e: MouseEvent) {
emit('click', e)
}
"#;
let result = compile_script_setup(content, "Test", false, false, None).unwrap();
println!("Multi-line defineEmits output:\n{}", result.code);
assert!(
!result.code.contains("defineEmits"),
"defineEmits should be removed from setup"
);
assert!(
result.code.contains("const emit = __emit"),
"Should bind emit to __emit"
);
assert!(
result.code.contains("function handleClick"),
"handleClick should be in setup"
);
assert!(
result.code.contains("emits:"),
"Should have emits definition"
);
}
#[test]
fn test_compile_script_setup_with_typed_define_emits_single_line() {
let content = r#"
const emit = defineEmits<(e: 'click') => void>()
"#;
let result = compile_script_setup(content, "Test", false, false, None).unwrap();
println!("Typed defineEmits output:\n{}", result.code);
assert!(
!result.code.contains("defineEmits"),
"defineEmits should be removed from setup"
);
assert!(
result.code.contains("const emit = __emit"),
"Should bind emit to __emit"
);
}
#[test]
fn test_compile_script_setup_with_define_expose() {
let content = r#"
import { ref } from 'vue'
const count = ref(0)
const reset = () => count.value = 0
defineExpose({ count, reset })
"#;
let result = compile_script_setup(content, "Test", false, false, None).unwrap();
println!("defineExpose output:\n{}", result.code);
assert!(
result.code.contains("__expose({"),
"Should have __expose call with arguments"
);
assert!(
result.code.contains("count"),
"__expose should include count"
);
assert!(
result.code.contains("reset"),
"__expose should include reset"
);
assert!(
!result.code.contains("defineExpose"),
"defineExpose should be removed from setup"
);
}
#[test]
fn test_compile_script_setup_without_define_expose() {
let content = r#"
import { ref } from 'vue'
const count = ref(0)
"#;
let result = compile_script_setup(content, "Test", false, false, None).unwrap();
println!("Output without defineExpose:\n{}", result.code);
assert!(
result.code.contains("__expose()"),
"Should have __expose() call even without defineExpose. Got:\n{}",
result.code
);
}
#[test]
fn test_compile_script_setup_with_empty_define_expose() {
let content = r#"
import { ref } from 'vue'
const count = ref(0)
defineExpose()
"#;
let result = compile_script_setup(content, "Test", false, false, None).unwrap();
println!("Output with empty defineExpose:\n{}", result.code);
assert!(
result.code.contains("__expose()"),
"Should have __expose() call for empty defineExpose. Got:\n{}",
result.code
);
assert!(
!result.code.contains("defineExpose"),
"defineExpose should be removed from setup"
);
}
#[test]
fn test_transform_typescript_to_js_strips_types() {
let ts_code = r#"const getNumber = (x: number): string => {
return x.toString();
}
const foo: string = "bar";"#;
let result = transform_typescript_to_js(ts_code);
eprintln!("TypeScript transform result:\n{}", result);
assert!(
!result.contains(": number"),
"Should strip parameter type annotation. Got:\n{}",
result
);
assert!(
!result.contains(": string"),
"Should strip return type and variable type annotations. Got:\n{}",
result
);
}
#[test]
fn test_compile_script_setup_strips_typescript() {
let content = r#"
const getNumberOfTeachers = (
items: Item[]
): string => {
return items.length.toString();
};
"#;
let result = compile_script_setup(content, "Test", false, false, None).unwrap();
eprintln!("Compiled TypeScript output:\n{}", result.code);
assert!(
!result.code.contains(": Item[]"),
"Should strip parameter type annotation. Got:\n{}",
result.code
);
assert!(
!result.code.contains("): string"),
"Should strip return type annotation. Got:\n{}",
result.code
);
}
#[test]
fn test_compile_script_setup_preserves_typescript_when_is_ts() {
let content = r#"
const count: number = 1;
const items: Array<string> = [];
"#;
let result = compile_script_setup(content, "Test", false, true, None).unwrap();
assert!(
result.code.contains(": number") || result.code.contains("Array<string>"),
"Expected TypeScript annotations to be preserved. Got:\n{}",
result.code
);
}
#[test]
fn test_props_destructure_type_based_defaults() {
let content = r#"
const { color = "primary" } = defineProps<{
color?: "primary" | "secondary";
}>();
"#;
let result = compile_script_setup(content, "Test", false, false, None).unwrap();
assert!(
result.code.contains("_mergeDefaults(")
&& result.code.contains("color")
&& result.code.contains(": {}"),
"Expected mergeDefaults with runtime props. Got:\n{}",
result.code
);
}
#[test]
fn test_duplicate_imports_filtered() {
let content = r#"
import { ref } from 'vue'
import { ref } from 'vue'
const count = ref(0)
"#;
let result = compile_script_setup(content, "Test", false, false, None).unwrap();
let import_ref_lines = result
.code
.lines()
.filter(|line| {
line.contains("import {") && line.contains("ref") && line.contains("vue")
})
.count();
assert_eq!(
import_ref_lines, 1,
"Expected duplicate ref import to be filtered. Got:\n{}",
result.code
);
}
#[test]
fn test_async_setup_detection() {
let content = r#"
const response = await fetch('/api/data')
const data = await response.json()
"#;
let result = compile_script_setup(content, "Test", false, false, None).unwrap();
assert!(
result.code.contains("async setup("),
"Expected async setup when top-level await is present. Got:\n{}",
result.code
);
}
#[test]
fn test_await_in_string_literal_does_not_trigger_async() {
let content = r#"
const msg = "await should not trigger async"
"#;
let result = compile_script_setup(content, "Test", false, false, None).unwrap();
assert!(
!result.code.contains("async setup("),
"Did not expect async setup for await in string literal. Got:\n{}",
result.code
);
}
#[test]
fn test_type_comparison_not_stripped() {
let content = r#"
const props = defineProps(['type'])
const isButton = props.type === 'button'
"#;
let result = compile_script_setup(content, "Test", false, false, None).unwrap();
assert!(
result.code.contains("type === 'button'")
|| result.code.contains("type === \"button\""),
"Expected type comparison to remain. Got:\n{}",
result.code
);
}
#[test]
fn test_generic_function_call_stripped() {
let content = r#"
const store = useStore<RootState>()
"#;
let result = compile_script_setup(content, "Test", false, false, None).unwrap();
assert!(
!result.code.contains("<RootState>"),
"Expected generic type arguments to be stripped. Got:\n{}",
result.code
);
}
}