use super::*;
fn compile_sfc(source: &str) -> VerterCompileResult {
let alloc = Allocator::new();
let options = CodegenOptions {
filename: Some("App.vue".to_string()),
..Default::default()
};
let verter_opts = VerterCompileOptions {
force_js: true,
..Default::default()
};
compile(source, &options, &verter_opts, &alloc)
}
fn assert_has_error(result: &VerterCompileResult, code_str: &str) {
assert!(
result.errors.iter().any(|e| e.code == code_str),
"Expected error code '{}' but got: {:?}",
code_str,
result.errors.iter().map(|e| &e.code).collect::<Vec<_>>()
);
}
fn assert_error_count(result: &VerterCompileResult, code_str: &str, n: usize) {
let count = result.errors.iter().filter(|e| e.code == code_str).count();
assert_eq!(
count,
n,
"Expected {} errors with code '{}', got {}. All errors: {:?}",
n,
code_str,
count,
result.errors.iter().map(|e| &e.code).collect::<Vec<_>>()
);
}
fn assert_no_error(result: &VerterCompileResult, code_str: &str) {
assert!(
!result.errors.iter().any(|e| e.code == code_str),
"Expected no error code '{}' but found one. All errors: {:?}",
code_str,
result.errors.iter().map(|e| &e.code).collect::<Vec<_>>()
);
}
fn assert_no_errors(result: &VerterCompileResult) {
assert!(
result.errors.is_empty(),
"Expected no errors but got: {:?}",
result
.errors
.iter()
.map(|e| format!("{}: {}", e.code, e.message))
.collect::<Vec<_>>()
);
}
fn assert_error_span_contains(
result: &VerterCompileResult,
source: &str,
code_str: &str,
substr: &str,
) {
let diag = result
.errors
.iter()
.find(|e| e.code == code_str)
.unwrap_or_else(|| panic!("No error with code '{}'", code_str));
let span = diag
.span
.unwrap_or_else(|| panic!("Error '{}' has no span", code_str));
let spanned = &source[span.start as usize..span.end as usize];
assert!(
spanned.contains(substr),
"Error '{}' span covers '{}', expected it to contain '{}'",
code_str,
spanned,
substr
);
}
fn assert_error_severity(result: &VerterCompileResult, code_str: &str) {
let diag = result
.errors
.iter()
.find(|e| e.code == code_str)
.unwrap_or_else(|| panic!("No error with code '{}'", code_str));
assert_eq!(
diag.severity,
CompileDiagnosticSeverity::Error,
"Expected Error severity for '{}', got {:?}",
code_str,
diag.severity
);
}
fn assert_warning_severity(result: &VerterCompileResult, code_str: &str) {
let diag = result
.errors
.iter()
.find(|e| e.code == code_str)
.unwrap_or_else(|| panic!("No error with code '{}'", code_str));
assert_eq!(
diag.severity,
CompileDiagnosticSeverity::Warning,
"Expected Warning severity for '{}', got {:?}",
code_str,
diag.severity
);
}
#[test]
fn error_duplicate_attribute() {
let src = r#"<template><div class="a" class="b">hello</div></template>"#;
let result = compile_sfc(src);
assert_has_error(&result, "DuplicateAttribute");
assert_error_severity(&result, "DuplicateAttribute");
}
#[test]
fn no_error_duplicate_attribute_on_valid() {
let src = r#"<template><div class="a" id="b">hello</div></template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "DuplicateAttribute");
}
#[test]
fn no_error_duplicate_attribute_style_and_v_bind_style() {
let src = r#"<template><div style="position: relative" :style="{ height: '100px' }">hello</div></template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "DuplicateAttribute");
}
#[test]
fn no_error_duplicate_attribute_class_and_v_bind_class() {
let src = r#"<template><div class="foo" :class="{ bar: true }">hello</div></template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "DuplicateAttribute");
}
#[test]
fn no_error_duplicate_attribute_nexus_virtualizer_pattern() {
let src = r#"<script setup lang="ts" generic="T extends Record<string, any>">
import { ref } from 'vue'
const stickyTop = ref(0)
const groupHeight = ref(40)
const freeze = false
</script>
<template>
<div
ref="containerElm"
style="position: absolute; position: relative; width: 100%"
:style="{
height: '100px',
}"
>
<template v-if="true">
<div
ref="stickyGroupElm"
key="sticky-group"
:sticky-top
:group-height
data-sticky
>
content
</div>
<div
v-for="i in 5"
:key="i"
:group-height
:index="i"
>
<template v-if="true" #item="args">
<slot v-bind="args" :loop-index="i - 1">
{{ i }}
</slot>
</template>
<template v-if="true" #group="args">
<slot name="group" v-bind="args" :loop-index="i - 1">
{{ i }}
</slot>
</template>
</div>
</template>
</div>
</template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "DuplicateAttribute");
}
#[test]
fn error_end_tag_with_attributes() {
let src = r#"<template><div></div foo="bar"></template>"#;
let result = compile_sfc(src);
assert_has_error(&result, "EndTagWithAttributes");
assert_warning_severity(&result, "EndTagWithAttributes");
}
#[test]
fn no_error_end_tag_with_attributes_on_valid() {
let src = r#"<template><div></div></template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "EndTagWithAttributes");
}
#[test]
fn error_eof_before_tag_name() {
let src = "<template><";
let result = compile_sfc(src);
assert_has_error(&result, "EofBeforeTagName");
assert_error_severity(&result, "EofBeforeTagName");
}
#[test]
fn error_eof_in_tag() {
let src = "<template><div attr";
let result = compile_sfc(src);
assert_has_error(&result, "EofInTag");
assert_error_severity(&result, "EofInTag");
}
#[test]
fn no_error_eof_in_tag_on_valid() {
let src = r#"<template><div attr="val">hello</div></template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "EofInTag");
}
#[test]
fn error_missing_attribute_value() {
let src = r#"<template><div class=>hello</div></template>"#;
let result = compile_sfc(src);
assert_has_error(&result, "MissingAttributeValue");
assert_error_severity(&result, "MissingAttributeValue");
}
#[test]
fn no_error_missing_attribute_value_on_valid() {
let src = r#"<template><div class="foo">hello</div></template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "MissingAttributeValue");
}
#[test]
fn error_missing_end_tag_name() {
let src = "<template><div></></template>";
let result = compile_sfc(src);
assert_has_error(&result, "MissingEndTagName");
assert_error_severity(&result, "MissingEndTagName");
}
#[test]
fn no_error_missing_end_tag_name_on_valid() {
let src = "<template><div></div></template>";
let result = compile_sfc(src);
assert_no_error(&result, "MissingEndTagName");
}
#[test]
fn error_missing_whitespace_between_attributes() {
let src = r#"<template><div class="a"id="b">hello</div></template>"#;
let result = compile_sfc(src);
assert_has_error(&result, "MissingWhitespaceBetweenAttributes");
assert_warning_severity(&result, "MissingWhitespaceBetweenAttributes");
}
#[test]
fn no_error_missing_whitespace_between_attributes_on_valid() {
let src = r#"<template><div class="a" id="b">hello</div></template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "MissingWhitespaceBetweenAttributes");
}
#[test]
fn error_x_invalid_end_tag() {
let src = "<template><div></span></div></template>";
let result = compile_sfc(src);
assert_has_error(&result, "XInvalidEndTag");
assert_error_severity(&result, "XInvalidEndTag");
}
#[test]
fn no_error_x_invalid_end_tag_on_valid() {
let src = "<template><div></div></template>";
let result = compile_sfc(src);
assert_no_error(&result, "XInvalidEndTag");
}
#[test]
fn error_x_missing_end_tag() {
let src = "<template><div>";
let result = compile_sfc(src);
assert_has_error(&result, "XMissingEndTag");
assert_error_severity(&result, "XMissingEndTag");
}
#[test]
fn no_error_x_missing_end_tag_on_valid() {
let src = "<template><div>hello</div></template>";
let result = compile_sfc(src);
assert_no_error(&result, "XMissingEndTag");
}
#[test]
fn error_x_missing_interpolation_end() {
let src = "<template>{{ foo</template>";
let result = compile_sfc(src);
assert_has_error(&result, "XMissingInterpolationEnd");
assert_error_severity(&result, "XMissingInterpolationEnd");
}
#[test]
fn no_error_x_missing_interpolation_end_on_valid() {
let src = "<template>{{ foo }}</template>";
let result = compile_sfc(src);
assert_no_error(&result, "XMissingInterpolationEnd");
}
#[test]
fn error_x_missing_directive_name() {
let src = r#"<template><div v-="">hello</div></template>"#;
let result = compile_sfc(src);
assert_has_error(&result, "XMissingDirectiveName");
assert_error_severity(&result, "XMissingDirectiveName");
}
#[test]
fn no_error_x_missing_directive_name_on_valid() {
let src = r#"<template><div v-show="true">hello</div></template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "XMissingDirectiveName");
}
#[test]
fn error_x_missing_dynamic_directive_argument_end() {
let src = r#"<template><div v-bind:[foo="bar">hello</div></template>"#;
let result = compile_sfc(src);
assert_has_error(&result, "XMissingDynamicDirectiveArgumentEnd");
assert_error_severity(&result, "XMissingDynamicDirectiveArgumentEnd");
}
#[test]
fn no_error_x_missing_dynamic_directive_argument_end_on_valid() {
let src = r#"<template><div v-bind:[foo]="bar">hello</div></template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "XMissingDynamicDirectiveArgumentEnd");
}
#[test]
fn error_x_v_if_no_expression() {
let src = r#"<template><div v-if>hello</div></template>"#;
let result = compile_sfc(src);
assert_has_error(&result, "XVIfNoExpression");
assert_error_severity(&result, "XVIfNoExpression");
}
#[test]
fn no_error_x_v_if_no_expression_on_valid() {
let src = r#"<template><div v-if="show">hello</div></template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "XVIfNoExpression");
}
#[test]
fn error_x_v_if_no_expression_else_if() {
let src = r#"<template><div v-if="a">a</div><div v-else-if>b</div></template>"#;
let result = compile_sfc(src);
assert_has_error(&result, "XVIfNoExpression");
}
#[test]
fn error_x_v_else_no_adjacent_if() {
let src = "<template><div v-else>hello</div></template>";
let result = compile_sfc(src);
assert_has_error(&result, "XVElseNoAdjacentIf");
assert_error_severity(&result, "XVElseNoAdjacentIf");
}
#[test]
fn no_error_x_v_else_no_adjacent_if_on_valid() {
let src = r#"<template><div v-if="show">a</div><div v-else>b</div></template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "XVElseNoAdjacentIf");
}
#[test]
fn error_x_v_else_no_adjacent_if_with_text_between() {
let src = r#"<template><div v-if="show">a</div>text<div v-else>b</div></template>"#;
let result = compile_sfc(src);
assert_has_error(&result, "XVElseNoAdjacentIf");
}
#[test]
fn error_x_v_for_no_expression() {
let src = r#"<template><div v-for>hello</div></template>"#;
let result = compile_sfc(src);
assert_has_error(&result, "XVForNoExpression");
assert_error_severity(&result, "XVForNoExpression");
}
#[test]
fn no_error_x_v_for_no_expression_on_valid() {
let src = r#"<template><div v-for="item in items" :key="item">{{ item }}</div></template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "XVForNoExpression");
}
#[test]
fn error_x_v_for_malformed_expression() {
let src = r#"<template><div v-for="x">hello</div></template>"#;
let result = compile_sfc(src);
assert_has_error(&result, "XVForMalformedExpression");
assert_error_severity(&result, "XVForMalformedExpression");
}
#[test]
fn no_error_x_v_for_malformed_expression_on_valid() {
let src = r#"<template><div v-for="item in items">{{ item }}</div></template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "XVForMalformedExpression");
}
#[test]
fn no_error_x_v_bind_same_name_shorthand() {
let src = r#"<template><div v-bind:class>hello</div></template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "XVBindNoExpression");
}
#[test]
fn no_error_x_v_bind_shorthand_colon() {
let src = r#"<template><div :class>hello</div></template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "XVBindNoExpression");
}
#[test]
fn no_error_x_v_bind_no_expression_on_valid() {
let src = r#"<template><div v-bind:class="cls">hello</div></template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "XVBindNoExpression");
}
#[test]
fn no_error_x_v_bind_spread() {
let src = r#"<template><div v-bind="obj">hello</div></template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "XVBindNoExpression");
}
#[test]
fn no_error_x_v_on_bare_event() {
let src = r#"<template><div v-on:click>hello</div></template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "XVOnNoExpression");
}
#[test]
fn no_error_x_v_on_bare_event_shorthand() {
let src = r#"<template><div @click>hello</div></template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "XVOnNoExpression");
}
#[test]
fn no_error_x_v_on_no_expression_on_valid() {
let src = r#"<template><div v-on:click="handler">hello</div></template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "XVOnNoExpression");
}
#[test]
fn no_error_x_v_on_modifier_only() {
let src = r#"<template><div @click.prevent>hello</div></template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "XVOnNoExpression");
}
#[test]
fn no_error_x_v_on_spread() {
let src = r#"<template><div v-on="handlers">hello</div></template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "XVOnNoExpression");
}
#[test]
fn error_x_v_slot_misplaced() {
let src = r#"<template><div v-slot>hello</div></template>"#;
let result = compile_sfc(src);
assert_has_error(&result, "XVSlotMisplaced");
assert_error_severity(&result, "XVSlotMisplaced");
}
#[test]
fn no_error_x_v_slot_misplaced_on_component() {
let src = r#"<template><MyComp v-slot="{ item }">{{ item }}</MyComp></template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "XVSlotMisplaced");
}
#[test]
fn no_error_x_v_slot_misplaced_on_template() {
let src = r#"<template><MyComp><template #default="{ item }">{{ item }}</template></MyComp></template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "XVSlotMisplaced");
}
#[test]
fn error_x_v_slot_duplicate_slot_names() {
let src = r#"<template><MyComp><template #default>a</template><template #default>b</template></MyComp></template>"#;
let result = compile_sfc(src);
assert_has_error(&result, "XVSlotDuplicateSlotNames");
assert_error_severity(&result, "XVSlotDuplicateSlotNames");
}
#[test]
fn no_error_x_v_slot_duplicate_slot_names_on_valid() {
let src = r#"<template><MyComp><template #header>h</template><template #footer>f</template></MyComp></template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "XVSlotDuplicateSlotNames");
}
#[test]
fn error_x_v_model_no_expression() {
let src = r#"<template><input v-model></template>"#;
let result = compile_sfc(src);
assert_has_error(&result, "XVModelNoExpression");
assert_error_severity(&result, "XVModelNoExpression");
}
#[test]
fn no_error_x_v_model_no_expression_on_valid() {
let src = r#"<template><input v-model="val"></template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "XVModelNoExpression");
}
#[test]
fn error_x_v_model_malformed_expression() {
let src = r#"<template><input v-model="a + b"></template>"#;
let result = compile_sfc(src);
assert_has_error(&result, "XVModelMalformedExpression");
assert_error_severity(&result, "XVModelMalformedExpression");
}
#[test]
fn no_error_x_v_model_malformed_expression_on_valid() {
let src = r#"<template><input v-model="obj.prop"></template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "XVModelMalformedExpression");
}
#[test]
fn no_error_x_v_model_malformed_expression_on_ident() {
let src = r#"<template><input v-model="val"></template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "XVModelMalformedExpression");
}
#[test]
fn error_x_duplicate_directive_v_if() {
let src = r#"<template><div v-if="a" v-if="b">hello</div></template>"#;
let result = compile_sfc(src);
assert_has_error(&result, "XDuplicateDirective");
assert_warning_severity(&result, "XDuplicateDirective");
}
#[test]
fn no_error_x_duplicate_directive_on_valid() {
let src = r#"<template><div v-if="a">hello</div></template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "XDuplicateDirective");
}
#[test]
fn error_duplicate_script_setup() {
let src = "<script setup>const a = 1</script><script setup>const b = 2</script>";
let result = compile_sfc(src);
assert_has_error(&result, "DuplicateScriptSetup");
assert_error_severity(&result, "DuplicateScriptSetup");
}
#[test]
fn no_error_duplicate_script_setup_on_valid() {
let src = "<script setup>const a = 1</script>";
let result = compile_sfc(src);
assert_no_error(&result, "DuplicateScriptSetup");
}
#[test]
fn error_duplicate_script() {
let src = "<script>export default {}</script><script>export default {}</script>";
let result = compile_sfc(src);
assert_has_error(&result, "DuplicateScript");
assert_error_severity(&result, "DuplicateScript");
}
#[test]
fn no_error_duplicate_script_on_valid() {
let src = "<script>export default {}</script><script setup>const a = 1</script>";
let result = compile_sfc(src);
assert_no_error(&result, "DuplicateScript");
assert_no_error(&result, "DuplicateScriptSetup");
}
#[test]
fn error_x_invalid_expression() {
let src = r#"<template>{{ if(true){} }}</template>"#;
let result = compile_sfc(src);
assert_has_error(&result, "XInvalidExpression");
assert_warning_severity(&result, "XInvalidExpression");
}
#[test]
fn no_error_x_invalid_expression_on_valid() {
let src = r#"<template>{{ count + 1 }}</template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "XInvalidExpression");
}
#[test]
fn error_x_css_parse_error() {
let src = "<template><div></div></template><style scoped>div { color: }</style>";
let result = compile_sfc(src);
if result.errors.iter().any(|e| e.code == "XCssParseError") {
assert_error_severity(&result, "XCssParseError");
}
}
#[test]
fn no_errors_basic_template() {
let src = "<template><div>hello</div></template>";
let result = compile_sfc(src);
assert_no_errors(&result);
}
#[test]
fn no_errors_full_sfc() {
let src = r#"<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>
<style scoped>
button { color: red; }
</style>"#;
let result = compile_sfc(src);
assert_no_errors(&result);
}
#[test]
fn no_errors_v_if_chain() {
let src = r#"<template>
<div v-if="a">a</div>
<div v-else-if="b">b</div>
<div v-else>c</div>
</template>"#;
let result = compile_sfc(src);
assert_no_errors(&result);
}
#[test]
fn no_errors_v_for_with_key() {
let src = r#"<template>
<div v-for="item in items" :key="item.id">{{ item.name }}</div>
</template>"#;
let result = compile_sfc(src);
assert_no_errors(&result);
}
#[test]
fn no_errors_v_model() {
let src = r#"<template><input v-model="text"></template>"#;
let result = compile_sfc(src);
assert_no_errors(&result);
}
#[test]
fn no_errors_v_bind_spread() {
let src = r#"<template><div v-bind="attrs">hello</div></template>"#;
let result = compile_sfc(src);
assert_no_errors(&result);
}
#[test]
fn no_errors_void_elements() {
let src = r#"<template><div><br><hr><img src="test.png"><input type="text"></div></template>"#;
let result = compile_sfc(src);
assert_no_errors(&result);
}
#[test]
fn no_errors_components_with_slots() {
let src = r#"<template>
<MyComp>
<template #header>Header</template>
<template #default>Content</template>
<template #footer>Footer</template>
</MyComp>
</template>"#;
let result = compile_sfc(src);
assert_no_errors(&result);
}
#[test]
fn no_errors_v_for_with_of() {
let src = r#"<template><div v-for="item of items" :key="item">{{ item }}</div></template>"#;
let result = compile_sfc(src);
assert_no_errors(&result);
}
#[test]
fn no_errors_v_for_destructured() {
let src =
r#"<template><div v-for="(item, index) in items" :key="index">{{ item }}</div></template>"#;
let result = compile_sfc(src);
assert_no_errors(&result);
}
#[test]
fn error_x_v_if_same_key() {
let src = r#"<template><div v-if="a" :key="1">a</div><div v-else :key="1">b</div></template>"#;
let result = compile_sfc(src);
assert_has_error(&result, "XVIfSameKey");
}
#[test]
fn no_error_x_v_if_same_key_on_valid() {
let src = r#"<template><div v-if="a" :key="1">a</div><div v-else :key="2">b</div></template>"#;
let result = compile_sfc(src);
assert_no_error(&result, "XVIfSameKey");
}