use super::parse_sfc;
use std::borrow::Cow;
#[test]
fn test_parse_empty_sfc() {
let result = parse_sfc("", Default::default()).unwrap();
assert!(result.template.is_none());
assert!(result.script.is_none());
assert!(result.styles.is_empty());
}
#[test]
fn test_parse_template_only() {
let source = "<template><div>Hello</div></template>";
let result = parse_sfc(source, Default::default()).unwrap();
assert!(result.template.is_some());
let template = result.template.unwrap();
assert_eq!(template.content, "<div>Hello</div>");
}
#[test]
fn test_parse_with_lang_attr() {
let source = r#"<script lang="ts">const x: number = 1</script>"#;
let result = parse_sfc(source, Default::default()).unwrap();
assert!(result.script.is_some());
let script = result.script.unwrap();
assert_eq!(script.lang.as_deref(), Some("ts"));
}
#[test]
fn test_parse_multiple_styles() {
let source = r#"
<style>.a {}</style>
<style scoped>.b {}</style>
<style lang="scss">.c {}</style>
"#;
let result = parse_sfc(source, Default::default()).unwrap();
assert_eq!(result.styles.len(), 3);
assert!(!result.styles[0].scoped);
assert!(result.styles[1].scoped);
assert_eq!(result.styles[2].lang.as_deref(), Some("scss"));
}
#[test]
fn test_parse_custom_block() {
let source = r#"
<template><div></div></template>
<i18n>{"en": {"hello": "Hello"}}</i18n>
"#;
let result = parse_sfc(source, Default::default()).unwrap();
assert_eq!(result.custom_blocks.len(), 1);
assert_eq!(result.custom_blocks[0].block_type, "i18n");
}
#[test]
fn test_parse_script_setup() {
let source = r#"
<script setup lang="ts">
import { ref } from 'vue'
const count = ref(0)
</script>
"#;
let result = parse_sfc(source, Default::default()).unwrap();
assert!(result.script_setup.is_some());
let script = result.script_setup.unwrap();
assert!(script.setup);
assert_eq!(script.lang.as_deref(), Some("ts"));
}
#[test]
fn test_zero_copy_content() {
let source = "<template><div>Hello World</div></template>";
let result = parse_sfc(source, Default::default()).unwrap();
let template = result.template.unwrap();
match &template.content {
Cow::Borrowed(s) => {
let ptr = s.as_ptr();
let source_ptr = source.as_ptr();
assert!(ptr >= source_ptr && ptr < unsafe { source_ptr.add(source.len()) });
}
Cow::Owned(_) => panic!("Expected Cow::Borrowed, got Cow::Owned"),
}
}
#[test]
fn test_closing_template_tag_with_whitespace() {
let source = r#"<script setup>
const x = 1
</script>
<template
><div>Hello</div></template
>"#;
let result = parse_sfc(source, Default::default()).unwrap();
assert!(result.template.is_some());
let template = result.template.unwrap();
assert_eq!(template.content, "<div>Hello</div>");
}
#[test]
fn test_closing_template_tag_with_newline() {
let source = r#"<template>
<div>Content</div>
</template
>
<style>
.foo {}
</style>"#;
let result = parse_sfc(source, Default::default()).unwrap();
assert!(result.template.is_some());
let template = result.template.unwrap();
insta::assert_debug_snapshot!(template);
assert_eq!(result.styles.len(), 1);
}
#[test]
fn test_nested_template_in_string_literal() {
let source = r#"<script setup lang="ts">
const code = `<template>
<div>Nested</div>
</template>`
</script>
<template>
<div>{{ code }}</div>
</template>"#;
let result = parse_sfc(source, Default::default()).unwrap();
assert!(result.script_setup.is_some());
assert!(result.template.is_some());
let template = result.template.unwrap();
insta::assert_debug_snapshot!(template);
}
#[test]
fn test_template_with_v_slot_syntax() {
let source = r#"<template>
<MyComponent>
<template #header>Header</template>
<template v-slot:footer>Footer</template>
</MyComponent>
</template>"#;
let result = parse_sfc(source, Default::default()).unwrap();
assert!(result.template.is_some());
let template = result.template.unwrap();
insta::assert_debug_snapshot!(template);
}
#[test]
fn test_multiline_closing_tag_complex() {
let source = r#"<script setup>
const x = `</template>` // embedded in string
</script>
<template
><div class="container">
<template v-if="show">
Content
</template
><template v-else>
Other
</template>
</div></template
>
<style scoped>
.container {}
</style>"#;
let result = parse_sfc(source, Default::default()).unwrap();
assert!(result.script_setup.is_some());
assert!(result.template.is_some());
assert_eq!(result.styles.len(), 1);
assert!(result.styles[0].scoped);
let template = result.template.unwrap();
insta::assert_debug_snapshot!(template);
}
#[test]
fn test_script_with_embedded_closing_tag_in_template_literal() {
let source = r#"<script setup lang="ts">
const code = `<script setup>
console.log('hello')
</script>`
const x = 1
</script>
<template>
<div>{{ code }}</div>
</template>"#;
let result = parse_sfc(source, Default::default()).unwrap();
assert!(result.script_setup.is_some());
let script = result.script_setup.unwrap();
insta::assert_debug_snapshot!(script);
}
#[test]
fn test_script_with_embedded_closing_tag_in_single_quote() {
let source = r#"<script setup>
const tag = '</script>'
const y = 2
</script>"#;
let result = parse_sfc(source, Default::default()).unwrap();
assert!(result.script_setup.is_some());
let script = result.script_setup.unwrap();
insta::assert_debug_snapshot!(script);
}
#[test]
fn test_script_with_embedded_closing_tag_in_double_quote() {
let source = r#"<script setup>
const tag = "</script>"
const z = 3
</script>"#;
let result = parse_sfc(source, Default::default()).unwrap();
assert!(result.script_setup.is_some());
let script = result.script_setup.unwrap();
insta::assert_debug_snapshot!(script);
}
#[test]
fn test_script_with_embedded_closing_tag_in_comment() {
let source = r#"<script setup>
// This is a comment: </script>
const a = 1
/* Multi-line comment
</script>
*/
const b = 2
</script>"#;
let result = parse_sfc(source, Default::default()).unwrap();
assert!(result.script_setup.is_some());
let script = result.script_setup.unwrap();
insta::assert_debug_snapshot!(script);
}
#[test]
fn test_script_with_template_literal_expression() {
let source = r#"<script setup>
const name = 'world'
const code = `Hello ${name}! </script> ${1 + 2}`
const c = 3
</script>"#;
let result = parse_sfc(source, Default::default()).unwrap();
assert!(result.script_setup.is_some());
let script = result.script_setup.unwrap();
insta::assert_debug_snapshot!(script);
}
#[test]
fn test_script_with_escaped_quotes() {
let source = r#"<script setup>
const str1 = "He said \"</script>\""
const str2 = 'It\'s </script> here'
const str3 = `Template \` </script> \``
const d = 4
</script>"#;
let result = parse_sfc(source, Default::default()).unwrap();
assert!(result.script_setup.is_some());
let script = result.script_setup.unwrap();
insta::assert_debug_snapshot!(script);
}
#[test]
fn test_script_with_regex_containing_quotes() {
let source = r#"<script setup>
const tokenizer = {
root: [
[/<script[^>]*>/, "tag"],
[/"[^"]*"/, "string"],
[/'[^']*'/, "string"],
[/`[^`]*`/, "string"],
]
}
const e = 5
</script>
<template>
<div>Test</div>
</template>"#;
let result = parse_sfc(source, Default::default()).unwrap();
assert!(result.script_setup.is_some());
let script = result.script_setup.unwrap();
insta::assert_debug_snapshot!(script);
assert!(result.template.is_some());
let template = result.template.unwrap();
insta::assert_debug_snapshot!(template);
}
#[test]
fn test_script_with_regex_character_class_containing_comment_start() {
let source = r#"<script>
const regexp = /[/*]/g;
</script>"#;
let result = parse_sfc(source, Default::default()).unwrap();
assert!(result.script.is_some());
assert_eq!(
result.script.unwrap().content.trim(),
"const regexp = /[/*]/g;"
);
}
#[test]
fn test_script_with_division_operator() {
let source = r#"<script setup>
const x = 10 / 2
const y = "test"
const z = x / y
</script>"#;
let result = parse_sfc(source, Default::default()).unwrap();
assert!(result.script_setup.is_some());
let script = result.script_setup.unwrap();
insta::assert_debug_snapshot!(script);
}
#[test]
fn test_script_with_tagged_template_literal() {
let source = r#"<script setup>
const tag = html`<span style="color: red">Hello</span>`
const result = css`
.container {
color: blue;
}
`
const x = 1
</script>"#;
let result = parse_sfc(source, Default::default()).unwrap();
assert!(result.script_setup.is_some());
let script = result.script_setup.unwrap();
insta::assert_debug_snapshot!(script);
}
#[test]
fn test_script_with_keyword_template_literal() {
let source = r#"<script setup>
function render() {
const x = 'test'
return `<span>${x}</span>`
}
function throwError() {
throw `Error: </script>`
}
const y = 2
</script>"#;
let result = parse_sfc(source, Default::default()).unwrap();
assert!(result.script_setup.is_some());
let script = result.script_setup.unwrap();
insta::assert_debug_snapshot!(script);
}
#[test]
fn test_script_with_method_call_template_literal() {
let source = r#"<script setup>
const result = getTemplate()`<div>${content}</div>`
const arr = items.map((x) => `<li>${x}</li>`)
const z = 3
</script>"#;
let result = parse_sfc(source, Default::default()).unwrap();
assert!(result.script_setup.is_some());
let script = result.script_setup.unwrap();
insta::assert_debug_snapshot!(script);
}
#[test]
fn test_tags_in_code_comments() {
let source = r#" <!-- <script> </script> -->
<!-- <script>
console.log("HI")
</script> -->
<script setup>
const x = 1
</script>
<template><div>{{ x }}</div></template>"#;
let result = parse_sfc(source, Default::default()).unwrap();
assert!(result.script.is_none());
assert!(result.template.is_some());
assert!(result.script_setup.is_some());
insta::assert_debug_snapshot!(result.script_setup.unwrap());
}
#[test]
fn test_malformed_template_reports_error() {
let source = "<template><div></div>";
let err = parse_sfc(source, Default::default()).unwrap_err();
assert_eq!(err.code.as_deref(), Some("MALFORMED_BLOCK"));
assert!(err.message.contains("<template>"));
let loc = err.loc.as_ref().unwrap();
assert_eq!(loc.tag_start, 0);
assert_eq!(loc.start_line, 1);
assert_eq!(loc.start_column, 1);
insta::assert_debug_snapshot!(err);
}
#[test]
fn test_malformed_script_opening_tag_reports_error() {
let source = r#"<script lang="ts""#;
let err = parse_sfc(source, Default::default()).unwrap_err();
assert_eq!(err.code.as_deref(), Some("MALFORMED_BLOCK"));
assert!(err.message.contains("opening tag is incomplete"));
}
#[test]
fn test_malformed_script_closing_tag_reports_error() {
let source = r#"<script lang="ts"></script"#;
let err = parse_sfc(source, Default::default()).unwrap_err();
assert_eq!(err.code.as_deref(), Some("MALFORMED_BLOCK"));
assert!(err.message.contains("closing tag is missing"));
}
#[test]
fn test_malformed_custom_block_reports_error() {
let source = r#"<i18n>{"hello":"world"}"#;
let err = parse_sfc(source, Default::default()).unwrap_err();
assert_eq!(err.code.as_deref(), Some("MALFORMED_BLOCK"));
assert!(err.message.contains("<i18n>"));
}
#[test]
fn test_closing_block_tags_with_whitespace() {
let source = r#"<script>console.log(1)</script >
<style>.red { color: red; }</style
>
<i18n>{"hello":"world"}</i18n >"#;
let result = parse_sfc(source, Default::default()).unwrap();
let script = result.script.unwrap();
assert!(script.content.contains("console.log(1)"));
assert_eq!(result.styles.len(), 1);
assert!(result.styles[0].content.contains(".red"));
assert_eq!(result.custom_blocks.len(), 1);
assert_eq!(result.custom_blocks[0].block_type, "i18n");
}