use super::{compile_sfc, helpers, normal_script};
use crate::types::{BindingType, ScriptCompileOptions, SfcCompileOptions, TemplateCompileOptions};
use crate::{parse_sfc, SfcParseOptions};
use std::fs;
use std::path::PathBuf;
use vize_carton::ToCompactString;
fn fixtures_path() -> PathBuf {
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
PathBuf::from(manifest_dir)
.parent()
.unwrap()
.parent()
.unwrap()
.join("tests")
.join("fixtures")
.join("sfc")
.join("imported_types")
}
#[test]
fn test_generate_scope_id() {
let id = helpers::generate_scope_id("src/App.vue");
assert_eq!(id.len(), 8);
}
#[test]
fn test_extract_component_name() {
assert_eq!(helpers::extract_component_name("src/App.vue"), "App");
assert_eq!(
helpers::extract_component_name("MyComponent.vue"),
"MyComponent"
);
}
#[test]
#[ignore = "TODO: fix v-model prop quoting"]
fn test_v_model_on_component_in_sfc() {
let source = r#"<script setup>
import { ref } from 'vue'
import MyComponent from './MyComponent.vue'
const msg = ref('')
</script>
<template>
<MyComponent v-model="msg" :language="'en'" />
</template>"#;
let descriptor = parse_sfc(source, SfcParseOptions::default()).expect("Failed to parse SFC");
let opts = SfcCompileOptions::default();
let result = compile_sfc(&descriptor, opts).expect("Failed to compile SFC");
assert!(
!result.code.contains("/* v-model */"),
"Should not contain v-model comment. Got:\n{}",
result.code
);
assert!(
result.code.contains("\"modelValue\":"),
"Should have modelValue prop. Got:\n{}",
result.code
);
assert!(
result.code.contains("\"onUpdate:modelValue\":"),
"Should have onUpdate:modelValue prop. Got:\n{}",
result.code
);
}
#[test]
#[ignore = "TODO: fix inline mode ref handling"]
fn test_bindings_passed_to_template() {
let source = r#"<script setup lang="ts">
import { ref } from 'vue';
import MonacoEditor from './MonacoEditor.vue';
const selectedPreset = ref('test');
const options = ref({ ssr: false });
function handleChange(val: string) { selectedPreset.value = val; }
</script>
<template>
<div>{{ selectedPreset }}</div>
<select :value="selectedPreset" @change="handleChange($event.target.value)">
<option value="a">A</option>
</select>
<input type="checkbox" v-model="options.ssr" />
<MonacoEditor />
</template>"#;
let descriptor = parse_sfc(source, SfcParseOptions::default()).expect("Failed to parse SFC");
let opts = SfcCompileOptions::default();
let result = compile_sfc(&descriptor, opts).expect("Failed to compile SFC");
eprintln!("=== COMPILED OUTPUT ===\n{}", result.code);
assert!(
result.code.contains("$setup.selectedPreset"),
"selectedPreset should have $setup prefix in non-inline mode with bindings. Got:\n{}",
result.code
);
assert!(
result.code.contains("$setup.handleChange"),
"handleChange should have $setup prefix in non-inline mode with bindings. Got:\n{}",
result.code
);
assert!(
result.code.contains("options"),
"options should be in __returned__. Got:\n{}",
result.code
);
assert!(
result.code.contains("$setup.options"),
"options.ssr should have $setup prefix. Got:\n{}",
result.code
);
assert!(
result.code.contains("MonacoEditor"),
"MonacoEditor should be in __returned__. Got:\n{}",
result.code
);
}
#[test]
#[ignore = "TODO: fix nested v-if prefix"]
fn test_nested_v_if_no_double_prefix() {
let source = r#"<script setup lang="ts">
import { ref } from 'vue';
import CodeHighlight from './CodeHighlight.vue';
const output = ref(null);
</script>
<template>
<div v-if="output">
<div v-if="output.preamble" class="preamble">
<CodeHighlight :code="output.preamble" />
</div>
</div>
</template>"#;
let descriptor = parse_sfc(source, SfcParseOptions::default()).expect("Failed to parse SFC");
let opts = SfcCompileOptions::default();
let result = compile_sfc(&descriptor, opts).expect("Failed to compile SFC");
eprintln!("=== NESTED V-IF OUTPUT ===\n{}", result.code);
assert!(
!result.code.contains("$setup.$setup"),
"Should NOT have double $setup prefix. Got:\n{}",
result.code
);
assert!(
result.code.contains("$setup.output"),
"Should have single $setup prefix for output. Got:\n{}",
result.code
);
assert!(
result.code.contains("CodeHighlight"),
"Should contain CodeHighlight. Got:\n{}",
result.code
);
}
#[test]
fn test_typescript_preserved_in_event_handler() {
let source = r#"<script setup lang="ts">
type PresetKey = 'a' | 'b'
function handlePresetChange(key: PresetKey) {}
</script>
<template>
<select @change="handlePresetChange(($event.target).value)">
<option value="a">A</option>
</select>
</template>"#;
let descriptor = parse_sfc(source, SfcParseOptions::default()).expect("Failed to parse SFC");
let opts = SfcCompileOptions {
script: ScriptCompileOptions {
is_ts: true,
..Default::default()
},
..Default::default()
};
let result = compile_sfc(&descriptor, opts).expect("Failed to compile SFC");
eprintln!("TypeScript SFC output:\n{}", result.code);
assert!(
result.code.contains("type PresetKey"),
"Should preserve type alias with lang='ts'. Got:\n{}",
result.code
);
assert!(
result.code.contains("key: PresetKey"),
"Should preserve function parameter type with lang='ts'. Got:\n{}",
result.code
);
assert!(
result.code.contains("handlePresetChange"),
"Should have event handler. Got:\n{}",
result.code
);
}
#[test]
fn test_multi_statement_event_handler() {
let source = r#"<script setup lang="ts">
const editDashboard = ref()
</script>
<template>
<div @click="
editDashboard = 'test';
console.log('done');
"></div>
</template>"#;
let descriptor = parse_sfc(source, SfcParseOptions::default()).expect("Failed to parse SFC");
let opts = SfcCompileOptions {
script: ScriptCompileOptions {
is_ts: true,
..Default::default()
},
..Default::default()
};
let result = compile_sfc(&descriptor, opts).expect("Failed to compile SFC");
eprintln!("Multi-statement event handler output:\n{}", result.code);
assert!(
result.code.contains("($event: any) => { "),
"Multi-statement handler should use block body ($event: any) => {{ ... }}. Got:\n{}",
result.code
);
assert!(
result.code.contains("editDashboard.value"),
"SetupRef assignment in event handler should add .value. Got:\n{}",
result.code
);
}
#[test]
fn test_typescript_function_types_preserved() {
let source = r#"<script setup lang="ts">
interface Item {
id: number;
name: string;
}
const getNumberOfItems = (
items: Item[]
): string => {
return items.length.toString();
};
const foo: string = "bar";
const count: number = 42;
function processData(data: Record<string, unknown>): void {
console.log(data);
}
</script>
<template>
<div>{{ foo }}</div>
</template>"#;
let descriptor = parse_sfc(source, SfcParseOptions::default()).expect("Failed to parse SFC");
let opts = SfcCompileOptions {
script: ScriptCompileOptions {
is_ts: true,
..Default::default()
},
..Default::default()
};
let result = compile_sfc(&descriptor, opts).expect("Failed to compile SFC");
eprintln!("TypeScript function types output:\n{}", result.code);
assert!(
result.code.contains("interface Item"),
"Should preserve interface with lang='ts'. Got:\n{}",
result.code
);
assert!(
result.code.contains(": Item[]"),
"Should preserve array type annotation with lang='ts'. Got:\n{}",
result.code
);
assert!(
result.code.contains("foo"),
"Should have variable foo. Got:\n{}",
result.code
);
}
#[test]
fn test_inline_template_keeps_patch_flags_for_ref_class_bindings() {
let source = r#"<script setup lang="ts">
import { ref } from 'vue';
const activeTab = ref<'a' | 'b'>('a');
</script>
<template>
<div class="tabs">
<button :class="['tab', { active: activeTab === 'a' }]" @click="activeTab = 'a'">A</button>
<button :class="['tab', { active: activeTab === 'b' }]" @click="activeTab = 'b'">B</button>
</div>
</template>"#;
let descriptor = parse_sfc(source, SfcParseOptions::default()).expect("Failed to parse SFC");
let result =
compile_sfc(&descriptor, SfcCompileOptions::default()).expect("Failed to compile SFC");
assert!(
result.code.contains("2 /* CLASS */"),
"Expected inline SFC output to preserve CLASS patch flags. Got:\n{}",
result.code
);
assert!(
result.code.contains("activeTab.value === 'a'"),
"Expected ref access to stay reactive in class binding. Got:\n{}",
result.code
);
}
#[test]
fn test_inline_component_dynamic_prop_keeps_props_patch_flag() {
let source = r#"<script setup lang="ts">
import { ref } from 'vue';
import CodeHighlight from './CodeHighlight.vue';
const currentCode = ref('dom');
</script>
<template>
<div class="wrapper">
<CodeHighlight :code="currentCode" language="javascript" />
</div>
</template>"#;
let descriptor = parse_sfc(source, SfcParseOptions::default()).expect("Failed to parse SFC");
let result =
compile_sfc(&descriptor, SfcCompileOptions::default()).expect("Failed to compile SFC");
assert!(
result.code.contains("_createVNode(CodeHighlight"),
"Expected inline component vnode output. Got:\n{}",
result.code
);
assert!(
result.code.contains("code: currentCode.value"),
"Expected inline component prop to stay reactive. Got:\n{}",
result.code
);
assert!(
result.code.contains("8 /* PROPS */"),
"Expected inline component output to preserve PROPS patch flag for dynamic prop. Got:\n{}",
result.code
);
assert!(
result.code.contains("[\"code\"]"),
"Expected inline component dynamic props list to include code. Got:\n{}",
result.code
);
}
#[test]
fn test_v_if_branch_component_dynamic_prop_keeps_props_patch_flag() {
let source = r#"<script setup lang="ts">
import { ref } from 'vue';
import CodeHighlight from './CodeHighlight.vue';
const show = ref(true);
const currentCode = ref('dom');
</script>
<template>
<div class="wrapper">
<CodeHighlight v-if="show" :code="currentCode" language="javascript" />
</div>
</template>"#;
let descriptor = parse_sfc(source, SfcParseOptions::default()).expect("Failed to parse SFC");
let result =
compile_sfc(&descriptor, SfcCompileOptions::default()).expect("Failed to compile SFC");
assert!(
result.code.contains("_createBlock(CodeHighlight"),
"Expected v-if branch component block output. Got:\n{}",
result.code
);
assert!(
result.code.contains("code: currentCode.value"),
"Expected v-if branch component prop to stay reactive. Got:\n{}",
result.code
);
assert!(
result.code.contains("8 /* PROPS */"),
"Expected v-if branch component output to preserve PROPS patch flag. Got:\n{}",
result.code
);
assert!(
result.code.contains("[\"code\"]"),
"Expected v-if branch component dynamic props list to include code. Got:\n{}",
result.code
);
}
#[test]
fn test_full_sfc_props_destructure() {
let input = r#"<script setup lang="ts">
import { computed } from 'vue'
const {
name,
count = 0,
} = defineProps<{
name: string
count?: number
}>()
const doubled = computed(() => count * 2)
</script>
<template>
<div class="card">
<h2>{{ name }}</h2>
<p>Count: {{ count }} (doubled: {{ doubled }})</p>
</div>
</template>"#;
let parse_opts = SfcParseOptions::default();
let descriptor = parse_sfc(input, parse_opts).unwrap();
let mut compile_opts = SfcCompileOptions::default();
compile_opts.script.id = Some("test.vue".to_compact_string());
let result = compile_sfc(&descriptor, compile_opts).unwrap();
eprintln!("=== Full SFC props destructure output ===\n{}", result.code);
assert!(
result.code.contains("__props.name") || result.code.contains("name"),
"Should have name access. Got:\n{}",
result.code
);
}
#[test]
fn test_let_var_unref() {
let input = r#"
<script setup>
const a = 1
let b = 2
var c = 3
</script>
<template>
<div>{{ a }} {{ b }} {{ c }}</div>
</template>
"#;
let parse_opts = SfcParseOptions::default();
let descriptor = parse_sfc(input, parse_opts).unwrap();
let mut compile_opts = SfcCompileOptions::default();
compile_opts.script.id = Some("test.vue".to_compact_string());
let result = compile_sfc(&descriptor, compile_opts).unwrap();
eprintln!("Let/var unref test output:\n{}", result.code);
if let Some(bindings) = &result.bindings {
eprintln!("Bindings:");
for (name, binding_type) in &bindings.bindings {
eprintln!(" {} => {:?}", name, binding_type);
}
assert!(
matches!(bindings.bindings.get("a"), Some(BindingType::LiteralConst)),
"a should be LiteralConst"
);
assert!(
matches!(bindings.bindings.get("b"), Some(BindingType::SetupLet)),
"b should be SetupLet"
);
assert!(
matches!(bindings.bindings.get("c"), Some(BindingType::SetupLet)),
"c should be SetupLet"
);
}
assert!(
result.code.contains("unref as _unref"),
"Should import _unref. Got:\n{}",
result.code
);
assert!(
result.code.contains("_unref(b)"),
"b should be wrapped with _unref. Got:\n{}",
result.code
);
assert!(
result.code.contains("_unref(c)"),
"c should be wrapped with _unref. Got:\n{}",
result.code
);
}
#[test]
fn test_extract_normal_script_content() {
let input = r#"import type { NuxtRoute } from "@typed-router";
import { useBreakpoint } from "./_utils";
import Button from "./Button.vue";
interface TabItem {
name: string;
label: string;
}
export default {
name: 'Tab'
}
"#;
let result = normal_script::extract_normal_script_content(input, true, true);
eprintln!("Extracted normal script content (preserve TS):\n{}", result);
assert!(
result.contains("import type { NuxtRoute }"),
"Should contain type import"
);
assert!(
result.contains("import { useBreakpoint }"),
"Should contain named import"
);
assert!(
result.contains("import Button"),
"Should contain default import"
);
assert!(
result.contains("interface TabItem"),
"Should contain interface"
);
assert!(
!result.contains("export default"),
"Should NOT contain export default"
);
}
#[test]
fn test_compile_both_script_blocks() {
let source = r#"<script lang="ts">
import type { RouteLocation } from "vue-router";
interface TabItem {
name: string;
label: string;
}
export type { TabItem };
</script>
<script setup lang="ts">
const { items } = defineProps<{
items: Array<TabItem>;
}>();
</script>
<template>
<div v-for="item in items" :key="item.name">
{{ item.label }}
</div>
</template>"#;
let descriptor = parse_sfc(source, SfcParseOptions::default()).expect("Failed to parse SFC");
eprintln!(
"Descriptor script: {:?}",
descriptor.script.as_ref().map(|s| &s.content)
);
eprintln!(
"Descriptor script_setup: {:?}",
descriptor.script_setup.as_ref().map(|s| &s.content)
);
let opts = SfcCompileOptions {
script: ScriptCompileOptions {
is_ts: true,
..Default::default()
},
template: TemplateCompileOptions {
is_ts: true,
..Default::default()
},
..Default::default()
};
let result = compile_sfc(&descriptor, opts).expect("Failed to compile SFC");
eprintln!("=== COMPILED OUTPUT ===\n{}", result.code);
assert!(
result.code.contains("RouteLocation") || result.code.contains("interface TabItem"),
"Should contain type definitions from normal script. Got:\n{}",
result.code
);
}
#[test]
fn test_define_model_basic() {
let source = r#"<script setup>
const model = defineModel()
</script>
<template>
<input v-model="model">
</template>"#;
let descriptor = parse_sfc(source, SfcParseOptions::default()).expect("Failed to parse SFC");
let opts = SfcCompileOptions::default();
let result = compile_sfc(&descriptor, opts).expect("Failed to compile SFC");
eprintln!("=== defineModel OUTPUT ===\n{}", result.code);
assert!(
result.code.contains("useModel as _useModel"),
"Should import useModel. Got:\n{}",
result.code
);
assert!(
result.code.contains("modelValue"),
"Should have modelValue prop. Got:\n{}",
result.code
);
assert!(
result.code.contains("update:modelValue"),
"Should have update:modelValue emit. Got:\n{}",
result.code
);
assert!(
result.code.contains("_useModel(__props"),
"Should use _useModel in setup. Got:\n{}",
result.code
);
}
#[test]
fn test_define_model_with_name() {
let source = r#"<script setup>
const title = defineModel('title')
</script>
<template>
<input v-model="title">
</template>"#;
let descriptor = parse_sfc(source, SfcParseOptions::default()).expect("Failed to parse SFC");
let opts = SfcCompileOptions::default();
let result = compile_sfc(&descriptor, opts).expect("Failed to compile SFC");
eprintln!("=== defineModel with name OUTPUT ===\n{}", result.code);
assert!(
result.code.contains("title:") || result.code.contains("\"title\""),
"Should have title prop. Got:\n{}",
result.code
);
assert!(
result.code.contains("update:title"),
"Should have update:title emit. Got:\n{}",
result.code
);
}
#[test]
fn test_non_script_setup_typescript_preserved() {
let source = r#"<script lang="ts">
interface Props {
name: string;
count?: number;
}
export default {
name: 'MyComponent',
props: {
name: String,
count: Number
} as Props,
setup(props: Props) {
const message: string = `Hello, ${props.name}!`;
return { message };
}
}
</script>
<template>
<div>{{ message }}</div>
</template>"#;
let descriptor = parse_sfc(source, SfcParseOptions::default()).expect("Failed to parse SFC");
let opts = SfcCompileOptions {
script: ScriptCompileOptions {
is_ts: true,
..Default::default()
},
..Default::default()
};
let result = compile_sfc(&descriptor, opts).expect("Failed to compile SFC");
eprintln!("=== Non-script-setup TS output ===\n{}", result.code);
assert!(
result.code.contains("interface Props") || result.code.contains(": Props"),
"Should preserve TypeScript with is_ts=true. Got:\n{}",
result.code
);
assert!(
result.code.contains("name: 'MyComponent'")
|| result.code.contains("name: \"MyComponent\""),
"Should have component name. Got:\n{}",
result.code
);
}
#[test]
fn test_non_script_setup_typescript_preserved_when_is_ts() {
let source = r#"<script lang="ts">
interface Props {
name: string;
}
export default {
props: {} as Props
}
</script>
<template>
<div></div>
</template>"#;
let descriptor = parse_sfc(source, SfcParseOptions::default()).expect("Failed to parse SFC");
let opts = SfcCompileOptions {
script: ScriptCompileOptions {
is_ts: true,
..Default::default()
},
template: TemplateCompileOptions {
is_ts: true,
..Default::default()
},
..Default::default()
};
let result = compile_sfc(&descriptor, opts).expect("Failed to compile SFC");
eprintln!(
"=== Non-script-setup TS preserved output ===\n{}",
result.code
);
assert!(
result.code.contains("interface Props") || result.code.contains("as Props"),
"Should preserve TypeScript when is_ts = true. Got:\n{}",
result.code
);
}
#[test]
fn test_define_props_imported_type_alias_is_exposed_to_template() {
let fixture_path = fixtures_path().join("ImportedSelectBase.vue");
let source = fs::read_to_string(&fixture_path).expect("fixture should load");
let descriptor = parse_sfc(&source, SfcParseOptions::default()).expect("Failed to parse SFC");
let mut opts = SfcCompileOptions::default();
opts.script.id = Some(fixture_path.to_string_lossy().as_ref().to_compact_string());
let result = compile_sfc(&descriptor, opts).expect("Failed to compile SFC");
assert!(
result.code.contains("disabled: { type: Boolean")
|| result.code.contains("disabled: { type: null"),
"Imported disabled prop should exist in runtime props. Got:\n{}",
result.code
);
assert!(
result.code.contains("size: {"),
"Imported size prop should exist in runtime props. Got:\n{}",
result.code
);
assert!(
!result.code.contains("_ctx.disabled"),
"Imported disabled prop should not fall back to _ctx. Got:\n{}",
result.code
);
assert!(
!result.code.contains("_ctx.size"),
"Imported size prop should not fall back to _ctx. Got:\n{}",
result.code
);
}
#[test]
fn test_define_props_interface_extends_imported_type_alias() {
let fixture_path = fixtures_path().join("ImportedSelectField.vue");
let source = fs::read_to_string(&fixture_path).expect("fixture should load");
let descriptor = parse_sfc(&source, SfcParseOptions::default()).expect("Failed to parse SFC");
let mut opts = SfcCompileOptions::default();
opts.script.id = Some(fixture_path.to_string_lossy().as_ref().to_compact_string());
let result = compile_sfc(&descriptor, opts).expect("Failed to compile SFC");
assert!(
result.code.contains("disabled: { type: Boolean")
|| result.code.contains("disabled: { type: null"),
"Extended imported disabled prop should exist in runtime props. Got:\n{}",
result.code
);
assert!(
result.code.contains("size: {"),
"Extended imported size prop should exist in runtime props. Got:\n{}",
result.code
);
assert!(
!result.code.contains("_ctx.disabled"),
"Extended imported disabled prop should not fall back to _ctx. Got:\n{}",
result.code
);
}
#[test]
fn test_template_only_sfc_vapor_output_mode() {
let source = r#"<template><div>{{ msg }}</div></template>"#;
let descriptor = parse_sfc(source, SfcParseOptions::default()).expect("Failed to parse SFC");
let opts = SfcCompileOptions {
vapor: true,
..Default::default()
};
let result = compile_sfc(&descriptor, opts).expect("Failed to compile SFC");
assert!(
result.code.contains("const t0 = _template"),
"Template-only Vapor output should keep template declarations. Got:\n{}",
result.code
);
assert!(
result.code.contains("__vapor: true"),
"Template-only Vapor output should mark the component as Vapor. Got:\n{}",
result.code
);
assert!(
result.code.contains("_sfc_main.render = render"),
"Template-only Vapor output should attach render to the component. Got:\n{}",
result.code
);
}
#[test]
fn test_script_setup_sfc_vapor_output_mode() {
let source = r#"<script setup lang="ts">
import { computed, ref } from 'vue'
const count = ref(1)
const doubled = computed(() => count.value * 2)
</script>
<template>
<div>{{ count }} {{ doubled }}</div>
</template>"#;
let descriptor = parse_sfc(source, SfcParseOptions::default()).expect("Failed to parse SFC");
let opts = SfcCompileOptions {
vapor: true,
script: ScriptCompileOptions {
is_ts: true,
..Default::default()
},
template: TemplateCompileOptions {
is_ts: true,
..Default::default()
},
..Default::default()
};
let result = compile_sfc(&descriptor, opts).expect("Failed to compile SFC");
assert!(
result.code.contains("_defineVaporComponent"),
"Script setup Vapor output should use defineVaporComponent. Got:\n{}",
result.code
);
assert!(
result.code.contains("const t0 = _template"),
"Script setup Vapor output should include template declarations. Got:\n{}",
result.code
);
assert!(
result.code.contains("_renderEffect"),
"Script setup Vapor output should retain Vapor render effects. Got:\n{}",
result.code
);
assert!(
result
.code
.contains("getCurrentInstance as _getCurrentInstance"),
"Script setup Vapor output should import current instance access for production-safe setupState wiring. Got:\n{}",
result.code
);
assert!(
result
.code
.contains("const __ctx = _proxyRefs(__returned__)"),
"Script setup Vapor output should build a proxyRefs render context. Got:\n{}",
result.code
);
assert!(
result
.code
.contains("const __vaporRender = render"),
"Script setup Vapor output should alias the template render to avoid local binding collisions. Got:\n{}",
result.code
);
assert!(
result
.code
.contains("return __vaporRender(__ctx, __props, __emit, __attrs, __slots)"),
"Script setup Vapor output should return a Vapor block directly from setup. Got:\n{}",
result.code
);
}
#[test]
fn test_script_setup_sfc_ssr_uses_server_renderer_output() {
let source = r#"<script setup lang="ts">
const msg = 'hello'
</script>
<template>
<div>{{ msg }}</div>
</template>"#;
let descriptor = parse_sfc(source, SfcParseOptions::default()).expect("Failed to parse SFC");
let opts = SfcCompileOptions {
vapor: true,
script: ScriptCompileOptions {
is_ts: true,
..Default::default()
},
template: TemplateCompileOptions {
is_ts: true,
ssr: true,
..Default::default()
},
..Default::default()
};
let result = compile_sfc(&descriptor, opts).expect("Failed to compile SFC");
assert!(
result.code.contains("_defineComponent"),
"SSR output should fall back to the VDOM compiler. Got:\n{}",
result.code
);
assert!(
!result.code.contains("_defineVaporComponent"),
"SSR output should not keep Vapor component wrappers. Got:\n{}",
result.code
);
assert!(
!result.code.contains("__vapor"),
"SSR output should not mark the component as Vapor. Got:\n{}",
result.code
);
assert!(
result.code.contains("function ssrRender"),
"SSR output should keep the compiled ssrRender function. Got:\n{}",
result.code
);
assert!(
result.code.contains("_ssrInterpolate"),
"SSR output should use the server renderer helpers. Got:\n{}",
result.code
);
assert!(
result
.code
.contains("_push(`<div>${_ssrInterpolate($setup.msg)}</div>`)"),
"SSR output should generate HTML pushes instead of VDOM returns. Got:\n{}",
result.code
);
assert!(
result.code.contains("ssrRender,"),
"SSR output should attach ssrRender to the component options. Got:\n{}",
result.code
);
assert!(
!result.code.contains("render,"),
"SSR output should not attach a client render option. Got:\n{}",
result.code
);
}
#[test]
fn test_script_setup_sfc_ssr_uses_setup_bindings_for_components_and_slots() {
let source = r##"<script setup lang="ts">
import { NuxtLayout, NuxtPage } from "#components"
</script>
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>"##;
let descriptor = parse_sfc(source, SfcParseOptions::default()).expect("Failed to parse SFC");
let opts = SfcCompileOptions {
script: ScriptCompileOptions {
is_ts: true,
..Default::default()
},
template: TemplateCompileOptions {
is_ts: true,
ssr: true,
..Default::default()
},
..Default::default()
};
let result = compile_sfc(&descriptor, opts).expect("Failed to compile SFC");
assert!(
result
.code
.contains("_ssrRenderComponent($setup.NuxtLayout, null, {"),
"SSR output should use setup bindings for imported components. Got:\n{}",
result.code
);
assert!(
result
.code
.contains("default: _withCtx((_, _push, _parent, _scopeId) => {"),
"SSR output should emit SSR-aware slot functions. Got:\n{}",
result.code
);
assert!(
result
.code
.contains("_ssrRenderComponent($setup.NuxtPage, null, null, _parent))"),
"SSR slot content should render children through server-renderer helpers. Got:\n{}",
result.code
);
}
#[test]
fn test_normal_script_sfc_ssr_attaches_ssr_render() {
let source = r#"<script lang="ts">
export default {
name: 'HelloSsr'
}
</script>
<template>
<div>Hello</div>
</template>"#;
let descriptor = parse_sfc(source, SfcParseOptions::default()).expect("Failed to parse SFC");
let opts = SfcCompileOptions {
script: ScriptCompileOptions {
is_ts: true,
..Default::default()
},
template: TemplateCompileOptions {
is_ts: true,
ssr: true,
..Default::default()
},
..Default::default()
};
let result = compile_sfc(&descriptor, opts).expect("Failed to compile SFC");
assert!(
result.code.contains("_sfc_main.ssrRender = ssrRender"),
"Normal script SSR output should attach ssrRender. Got:\n{}",
result.code
);
assert!(
!result.code.contains("_sfc_main.render = render"),
"Normal script SSR output should not attach the client render function. Got:\n{}",
result.code
);
}
#[test]
fn test_template_only_sfc_ssr_exports_default_component() {
let source = r#"<template>
<div>Hello</div>
</template>"#;
let descriptor = parse_sfc(source, SfcParseOptions::default()).expect("Failed to parse SFC");
let opts = SfcCompileOptions {
template: TemplateCompileOptions {
ssr: true,
..Default::default()
},
..Default::default()
};
let result = compile_sfc(&descriptor, opts).expect("Failed to compile SFC");
assert!(
result.code.contains("function ssrRender"),
"Template-only SSR output should keep the ssrRender function. Got:\n{}",
result.code
);
assert!(
result.code.contains("_sfc_main.ssrRender = ssrRender"),
"Template-only SSR output should export a default component with ssrRender. Got:\n{}",
result.code
);
}
#[test]
fn test_script_setup_sfc_vapor_output_avoids_local_render_collision() {
let source = r#"<script setup lang="ts">
function render() {
return 'local'
}
</script>
<template>
<div>Hello</div>
</template>"#;
let descriptor = parse_sfc(source, SfcParseOptions::default()).expect("Failed to parse SFC");
let opts = SfcCompileOptions {
vapor: true,
script: ScriptCompileOptions {
is_ts: true,
..Default::default()
},
template: TemplateCompileOptions {
is_ts: true,
..Default::default()
},
..Default::default()
};
let result = compile_sfc(&descriptor, opts).expect("Failed to compile SFC");
assert!(
result.code.contains("const __vaporRender = render"),
"Vapor output should create a module-scope render alias. Got:\n{}",
result.code
);
assert!(
result.code.contains("render: __vaporRender"),
"Vapor component options should use the alias to keep template render stable. Got:\n{}",
result.code
);
assert!(
result
.code
.contains("return __vaporRender(__ctx, __props, __emit, __attrs, __slots)"),
"Vapor setup should call the aliased template render instead of a local binding. Got:\n{}",
result.code
);
}
#[test]
fn test_script_setup_sfc_vapor_output_keeps_render_block_statements() {
let source = r#"<script setup lang="ts">
import { ref } from 'vue'
const count = ref(1)
</script>
<template>
<div>{{ count }}</div>
</template>"#;
let descriptor = parse_sfc(source, SfcParseOptions::default()).expect("Failed to parse SFC");
let opts = SfcCompileOptions {
vapor: true,
script: ScriptCompileOptions {
is_ts: true,
..Default::default()
},
template: TemplateCompileOptions {
is_ts: true,
..Default::default()
},
..Default::default()
};
let result = compile_sfc(&descriptor, opts).expect("Failed to compile SFC");
assert!(
result.code.contains("const n0 = t0()"),
"Script setup Vapor output should keep render block statements. Got:\n{}",
result.code
);
assert!(
result.code.contains("return n0"),
"Script setup Vapor output should return the Vapor root node. Got:\n{}",
result.code
);
}
#[test]
fn test_script_setup_sfc_vapor_uses_ctx_bindings_for_imported_components() {
let source = r#"<script setup lang="ts">
import FooPanel from './FooPanel.vue'
</script>
<template>
<FooPanel />
</template>"#;
let descriptor = parse_sfc(source, SfcParseOptions::default()).expect("Failed to parse SFC");
let opts = SfcCompileOptions {
vapor: true,
script: ScriptCompileOptions {
is_ts: true,
..Default::default()
},
template: TemplateCompileOptions {
is_ts: true,
..Default::default()
},
..Default::default()
};
let result = compile_sfc(&descriptor, opts).expect("Failed to compile SFC");
assert!(
result
.code
.contains("const _component_FooPanel = _ctx.FooPanel"),
"Imported script setup components should be read from _ctx in Vapor mode. Got:\n{}",
result.code
);
assert!(
!result.code.contains("_resolveComponent(\"FooPanel\")"),
"Imported script setup components should not go through resolveComponent. Got:\n{}",
result.code
);
}
#[test]
fn test_normal_script_sfc_vapor_output_mode() {
let source = r#"<script>
export default {
name: 'NormalVapor'
}
</script>
<template>
<div>Hello</div>
</template>"#;
let descriptor = parse_sfc(source, SfcParseOptions::default()).expect("Failed to parse SFC");
let opts = SfcCompileOptions {
vapor: true,
..Default::default()
};
let result = compile_sfc(&descriptor, opts).expect("Failed to compile SFC");
assert!(
result.code.contains("const t0 = _template"),
"Normal script Vapor output should keep template declarations. Got:\n{}",
result.code
);
assert!(
result.code.contains("_sfc_main.__vapor = true"),
"Normal script Vapor output should mark the component as Vapor. Got:\n{}",
result.code
);
assert!(
result.code.contains("export default _sfc_main"),
"Normal script Vapor output should continue exporting _sfc_main. Got:\n{}",
result.code
);
}