@@ -3080,6 +3080,156 @@ describe('Directory Exclusion', () => {
30803080 } ) ;
30813081} ) ;
30823082
3083+ describe ( 'Vue Extraction' , ( ) => {
3084+ it ( 'should detect Vue files' , ( ) => {
3085+ expect ( detectLanguage ( 'App.vue' ) ) . toBe ( 'vue' ) ;
3086+ expect ( detectLanguage ( 'components/Button.vue' ) ) . toBe ( 'vue' ) ;
3087+ expect ( isLanguageSupported ( 'vue' ) ) . toBe ( true ) ;
3088+ } ) ;
3089+
3090+ it ( 'should extract component node from a Vue SFC' , ( ) => {
3091+ const code = `<template>
3092+ <div>{{ message }}</div>
3093+ </template>
3094+
3095+ <script>
3096+ export default {
3097+ data() {
3098+ return { message: 'Hello' };
3099+ }
3100+ }
3101+ </script>
3102+ ` ;
3103+ const result = extractFromSource ( 'HelloWorld.vue' , code ) ;
3104+
3105+ const componentNode = result . nodes . find ( ( n ) => n . kind === 'component' ) ;
3106+ expect ( componentNode ) . toBeDefined ( ) ;
3107+ expect ( componentNode ?. name ) . toBe ( 'HelloWorld' ) ;
3108+ expect ( componentNode ?. language ) . toBe ( 'vue' ) ;
3109+ expect ( componentNode ?. isExported ) . toBe ( true ) ;
3110+ } ) ;
3111+
3112+ it ( 'should extract functions from <script> block' , ( ) => {
3113+ const code = `<template>
3114+ <button @click="handleClick">Click</button>
3115+ </template>
3116+
3117+ <script>
3118+ function handleClick() {
3119+ console.log('clicked');
3120+ }
3121+
3122+ const count = 0;
3123+ </script>
3124+ ` ;
3125+ const result = extractFromSource ( 'Button.vue' , code ) ;
3126+
3127+ const componentNode = result . nodes . find ( ( n ) => n . kind === 'component' ) ;
3128+ expect ( componentNode ) . toBeDefined ( ) ;
3129+ expect ( componentNode ?. name ) . toBe ( 'Button' ) ;
3130+
3131+ const funcNode = result . nodes . find ( ( n ) => n . kind === 'function' && n . name === 'handleClick' ) ;
3132+ expect ( funcNode ) . toBeDefined ( ) ;
3133+ expect ( funcNode ?. language ) . toBe ( 'vue' ) ;
3134+ } ) ;
3135+
3136+ it ( 'should extract from <script setup lang="ts"> block' , ( ) => {
3137+ const code = `<template>
3138+ <div>{{ count }}</div>
3139+ </template>
3140+
3141+ <script setup lang="ts">
3142+ import { ref } from 'vue';
3143+
3144+ const count = ref(0);
3145+
3146+ function increment(): void {
3147+ count.value++;
3148+ }
3149+ </script>
3150+ ` ;
3151+ const result = extractFromSource ( 'Counter.vue' , code ) ;
3152+
3153+ const componentNode = result . nodes . find ( ( n ) => n . kind === 'component' ) ;
3154+ expect ( componentNode ) . toBeDefined ( ) ;
3155+ expect ( componentNode ?. name ) . toBe ( 'Counter' ) ;
3156+
3157+ const funcNode = result . nodes . find ( ( n ) => n . kind === 'function' && n . name === 'increment' ) ;
3158+ expect ( funcNode ) . toBeDefined ( ) ;
3159+ expect ( funcNode ?. language ) . toBe ( 'vue' ) ;
3160+
3161+ // All nodes should be marked as vue language
3162+ for ( const node of result . nodes ) {
3163+ expect ( node . language ) . toBe ( 'vue' ) ;
3164+ }
3165+ } ) ;
3166+
3167+ it ( 'should extract from both <script> and <script setup> blocks' , ( ) => {
3168+ const code = `<template>
3169+ <div>{{ msg }}</div>
3170+ </template>
3171+
3172+ <script>
3173+ export default {
3174+ name: 'DualScript'
3175+ }
3176+ </script>
3177+
3178+ <script setup>
3179+ const msg = 'hello';
3180+
3181+ function greet() {
3182+ return msg;
3183+ }
3184+ </script>
3185+ ` ;
3186+ const result = extractFromSource ( 'DualScript.vue' , code ) ;
3187+
3188+ const componentNode = result . nodes . find ( ( n ) => n . kind === 'component' ) ;
3189+ expect ( componentNode ) . toBeDefined ( ) ;
3190+
3191+ const greetFunc = result . nodes . find ( ( n ) => n . kind === 'function' && n . name === 'greet' ) ;
3192+ expect ( greetFunc ) . toBeDefined ( ) ;
3193+ } ) ;
3194+
3195+ it ( 'should create component node for template-only Vue file' , ( ) => {
3196+ const code = `<template>
3197+ <div>Static content</div>
3198+ </template>
3199+ ` ;
3200+ const result = extractFromSource ( 'Static.vue' , code ) ;
3201+
3202+ const componentNode = result . nodes . find ( ( n ) => n . kind === 'component' ) ;
3203+ expect ( componentNode ) . toBeDefined ( ) ;
3204+ expect ( componentNode ?. name ) . toBe ( 'Static' ) ;
3205+ expect ( componentNode ?. language ) . toBe ( 'vue' ) ;
3206+
3207+ // Only the component node should exist (no script nodes)
3208+ expect ( result . nodes . length ) . toBe ( 1 ) ;
3209+ } ) ;
3210+
3211+ it ( 'should create containment edges from component to script nodes' , ( ) => {
3212+ const code = `<template>
3213+ <div>{{ value }}</div>
3214+ </template>
3215+
3216+ <script setup lang="ts">
3217+ const value = 42;
3218+ </script>
3219+ ` ;
3220+ const result = extractFromSource ( 'Contained.vue' , code ) ;
3221+
3222+ const componentNode = result . nodes . find ( ( n ) => n . kind === 'component' ) ;
3223+ expect ( componentNode ) . toBeDefined ( ) ;
3224+
3225+ // Should have containment edges from component to child nodes
3226+ const containEdges = result . edges . filter (
3227+ ( e ) => e . source === componentNode ! . id && e . kind === 'contains'
3228+ ) ;
3229+ expect ( containEdges . length ) . toBeGreaterThan ( 0 ) ;
3230+ } ) ;
3231+ } ) ;
3232+
30833233describe ( 'Instantiates + Decorates edge extraction' , ( ) => {
30843234 it ( 'emits an instantiates ref for `new Foo()`' , ( ) => {
30853235 const code = `
0 commit comments