mirror of
https://gitee.com/newgateway/vtj.git
synced 2026-06-24 04:03:38 +08:00
- 增加 dayjs 和 dateFormat 的核心API说明及使用示例 - 说明在组件中如何使用 @vtj/utils 导出的日期处理工具 - 调整后续章节编号,新增“日期处理”章节为第十三章 - 明确 @vtj/utils 中 dayjs 已默认配置中文 locale,简化使用说明 parser(fixer): 支持跳过动态绑定的 name 属性避免误替换 - 在处理 vant-icon name 属性时,跳过包含 :name 或 v-bind:name 的动态绑定情况 parser(vue): 增强 parseTemplate 返回context,支持上下文变量替换 - parseTemplate 函数返回值中新增 context 字段 - 在 compositionPatch 处理中,将 v-for 和 slot 上下文变量等替换为 this.context.xxx 访问形式 parser(tests): 补充 Composition API 模式下 v-for 和 slot 上下文变量转换测试 - 新增测试覆盖 vant-icon 动态 name 属性转换为 this.context.xxx - 测试 v-for 中上下文变量转换为 this.context.xxx - 测试 slot 作用域变量转换为 this.context.xxx parser(utils): 清理移除旧的复杂 replacer 函数及相关注释 - 删除无用代码,减小包体积,提高可维护性 test(debug): 新增调试用例打印 v-for 中 card.user 的节点转换结果
789 lines
21 KiB
TypeScript
789 lines
21 KiB
TypeScript
import { expect, test, describe } from 'vitest';
|
|
import { parseVue } from '../src';
|
|
import { project } from './sources/project';
|
|
|
|
const compositionSource = `
|
|
<template>
|
|
<div class="demo">
|
|
<h1>{{ title }}</h1>
|
|
<p>Count: {{ count }}</p>
|
|
<p>Double: {{ double }}</p>
|
|
<button @click="increment">+1</button>
|
|
<span>{{ greeting }}</span>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, reactive, computed, watch, onMounted, inject, provide } from 'vue';
|
|
import { useRouter, useRoute } from 'vue-router';
|
|
import { useMouse } from '@vueuse/core';
|
|
|
|
const __props = defineProps({
|
|
title: { type: String, required: true, default: 'Hello' }
|
|
});
|
|
|
|
const __emit = defineEmits(['change', 'update']);
|
|
|
|
const __router = useRouter();
|
|
const __route = useRoute();
|
|
const { x, y } = useMouse();
|
|
|
|
const count = ref(0);
|
|
const message = ref('hello');
|
|
|
|
const __state = reactive({
|
|
loading: false,
|
|
list: []
|
|
});
|
|
|
|
const form = reactive({
|
|
name: '',
|
|
age: 0
|
|
});
|
|
|
|
const double = computed(() => count.value * 2);
|
|
|
|
const greeting = computed(() => {
|
|
return __props.title + ' ' + message.value;
|
|
});
|
|
|
|
function increment() {
|
|
count.value++;
|
|
__emit('change', count.value);
|
|
}
|
|
|
|
async function fetchData() {
|
|
__state.loading = true;
|
|
const data = await fetch('/api/data');
|
|
__state.list = await data.json();
|
|
__state.loading = false;
|
|
}
|
|
|
|
const theme = inject('theme', 'light');
|
|
|
|
provide('count', count);
|
|
|
|
watch(
|
|
() => count.value,
|
|
(val) => {
|
|
console.log('count changed:', val);
|
|
__router.push('/result');
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
|
|
onMounted(() => {
|
|
fetchData();
|
|
console.log(__route.path);
|
|
});
|
|
|
|
defineExpose({ count, increment });
|
|
</script>
|
|
|
|
<style scoped>
|
|
.demo { padding: 20px; }
|
|
</style>
|
|
`;
|
|
|
|
describe('parseVue Composition mode', () => {
|
|
test('basic composition parsing', async () => {
|
|
const result = await parseVue({
|
|
project,
|
|
id: 'test-composition-1',
|
|
name: 'CompositionDemo',
|
|
source: compositionSource
|
|
});
|
|
|
|
expect(result).toBeDefined();
|
|
expect(result.apiMode).toBe('composition');
|
|
expect(result.name).toBe('CompositionDemo');
|
|
});
|
|
|
|
test('refs are correctly parsed', async () => {
|
|
const result = await parseVue({
|
|
project,
|
|
id: 'test-composition-2',
|
|
name: 'CompositionDemo',
|
|
source: compositionSource
|
|
});
|
|
|
|
expect(result.refs).toBeDefined();
|
|
expect(result.refs!['count']).toBeDefined();
|
|
expect(result.refs!['message']).toBeDefined();
|
|
});
|
|
|
|
test('state (reactive) is correctly parsed', async () => {
|
|
const result = await parseVue({
|
|
project,
|
|
id: 'test-composition-3',
|
|
name: 'CompositionDemo',
|
|
source: compositionSource
|
|
});
|
|
|
|
expect(result.state).toBeDefined();
|
|
expect(result.state!['loading']).toBeDefined();
|
|
expect(result.state!['list']).toBeDefined();
|
|
});
|
|
|
|
test('reactives are correctly parsed', async () => {
|
|
const result = await parseVue({
|
|
project,
|
|
id: 'test-composition-4',
|
|
name: 'CompositionDemo',
|
|
source: compositionSource
|
|
});
|
|
|
|
expect(result.reactives).toBeDefined();
|
|
expect(result.reactives!['form']).toBeDefined();
|
|
});
|
|
|
|
test('computed is correctly parsed', async () => {
|
|
const result = await parseVue({
|
|
project,
|
|
id: 'test-composition-5',
|
|
name: 'CompositionDemo',
|
|
source: compositionSource
|
|
});
|
|
|
|
expect(result.computed).toBeDefined();
|
|
expect(result.computed!['double']).toBeDefined();
|
|
expect(result.computed!['greeting']).toBeDefined();
|
|
});
|
|
|
|
test('methods are correctly parsed', async () => {
|
|
const result = await parseVue({
|
|
project,
|
|
id: 'test-composition-6',
|
|
name: 'CompositionDemo',
|
|
source: compositionSource
|
|
});
|
|
|
|
expect(result.methods).toBeDefined();
|
|
expect(result.methods!['increment']).toBeDefined();
|
|
expect(result.methods!['fetchData']).toBeDefined();
|
|
});
|
|
|
|
test('props are correctly parsed', async () => {
|
|
const result = await parseVue({
|
|
project,
|
|
id: 'test-composition-7',
|
|
name: 'CompositionDemo',
|
|
source: compositionSource
|
|
});
|
|
|
|
expect(result.props).toBeDefined();
|
|
expect(result.props!.length).toBeGreaterThan(0);
|
|
const titleProp = result.props!.find(
|
|
(p) => typeof p !== 'string' && p.name === 'title'
|
|
);
|
|
expect(titleProp).toBeDefined();
|
|
});
|
|
|
|
test('watch is correctly parsed', async () => {
|
|
const result = await parseVue({
|
|
project,
|
|
id: 'test-composition-8',
|
|
name: 'CompositionDemo',
|
|
source: compositionSource
|
|
});
|
|
|
|
expect(result.watch).toBeDefined();
|
|
expect(result.watch!.length).toBeGreaterThan(0);
|
|
expect(result.watch![0].immediate).toBe(true);
|
|
});
|
|
|
|
test('lifeCycles are correctly parsed', async () => {
|
|
const result = await parseVue({
|
|
project,
|
|
id: 'test-composition-9',
|
|
name: 'CompositionDemo',
|
|
source: compositionSource
|
|
});
|
|
|
|
expect(result.lifeCycles).toBeDefined();
|
|
expect(result.lifeCycles!['onMounted']).toBeDefined();
|
|
});
|
|
|
|
test('inject is correctly parsed', async () => {
|
|
const result = await parseVue({
|
|
project,
|
|
id: 'test-composition-10',
|
|
name: 'CompositionDemo',
|
|
source: compositionSource
|
|
});
|
|
|
|
expect(result.inject).toBeDefined();
|
|
expect(result.inject!.length).toBeGreaterThan(0);
|
|
expect(result.inject![0].name).toBe('theme');
|
|
});
|
|
|
|
test('provide is correctly parsed', async () => {
|
|
const result = await parseVue({
|
|
project,
|
|
id: 'test-composition-11',
|
|
name: 'CompositionDemo',
|
|
source: compositionSource
|
|
});
|
|
|
|
expect(result.provide).toBeDefined();
|
|
expect(result.provide!['count']).toBeDefined();
|
|
});
|
|
|
|
test('composables are correctly parsed (non-global)', async () => {
|
|
const result = await parseVue({
|
|
project,
|
|
id: 'test-composition-12',
|
|
name: 'CompositionDemo',
|
|
source: compositionSource
|
|
});
|
|
|
|
expect(result.composables).toBeDefined();
|
|
// useMouse should be collected as a composable
|
|
const mouse = result.composables!.find(
|
|
(c) =>
|
|
c.composable &&
|
|
typeof c.composable === 'object' &&
|
|
'value' in c.composable &&
|
|
c.composable.value.includes('useMouse')
|
|
);
|
|
expect(mouse).toBeDefined();
|
|
expect(mouse!.name).toBe('x'); // 第一个解构字段
|
|
expect(mouse!.destructure).toEqual(['x', 'y']);
|
|
// 验证 composable 表达式格式
|
|
if (mouse!.composable && typeof mouse!.composable === 'object') {
|
|
expect(mouse!.composable.value).toBe('this.$libs.VueUse.useMouse');
|
|
}
|
|
});
|
|
|
|
test('global composables (vue-router) are NOT in composables', async () => {
|
|
const result = await parseVue({
|
|
project,
|
|
id: 'test-composition-13',
|
|
name: 'CompositionDemo',
|
|
source: compositionSource
|
|
});
|
|
|
|
// useRouter/useRoute should NOT appear in composables
|
|
const routerComposable = result.composables!.find(
|
|
(c) =>
|
|
typeof c.composable === 'object' &&
|
|
(c.composable.value === 'useRouter' ||
|
|
c.composable.value === 'useRoute')
|
|
);
|
|
expect(routerComposable).toBeUndefined();
|
|
});
|
|
|
|
test('emits are correctly parsed', async () => {
|
|
const result = await parseVue({
|
|
project,
|
|
id: 'test-composition-14',
|
|
name: 'CompositionDemo',
|
|
source: compositionSource
|
|
});
|
|
|
|
expect(result.emits).toBeDefined();
|
|
expect(result.emits!.length).toBe(2);
|
|
expect(result.emits!.find((e: any) => e.name === 'change')).toBeDefined();
|
|
expect(result.emits!.find((e: any) => e.name === 'update')).toBeDefined();
|
|
});
|
|
|
|
test('expose is correctly parsed', async () => {
|
|
const result = await parseVue({
|
|
project,
|
|
id: 'test-composition-15',
|
|
name: 'CompositionDemo',
|
|
source: compositionSource
|
|
});
|
|
|
|
expect(result.expose).toBeDefined();
|
|
expect(result.expose).toContain('count');
|
|
expect(result.expose).toContain('increment');
|
|
});
|
|
|
|
test('compositionPatch transforms code correctly', async () => {
|
|
const result = await parseVue({
|
|
project,
|
|
id: 'test-composition-16',
|
|
name: 'CompositionDemo',
|
|
source: compositionSource
|
|
});
|
|
|
|
// Check that methods contain this.xxx pattern
|
|
const increment = result.methods!['increment'];
|
|
expect(increment).toBeDefined();
|
|
// count.value++ → this.count++ in DSL
|
|
expect(increment.value).toContain('this.count');
|
|
|
|
// Check that lifeCycles contain this.xxx pattern
|
|
const mounted = result.lifeCycles!['onMounted'];
|
|
expect(mounted).toBeDefined();
|
|
// fetchData() → this.fetchData()
|
|
expect(mounted.value).toContain('this.fetchData');
|
|
// route.path → this.$route.path
|
|
expect(mounted.value).toContain('this.$route');
|
|
});
|
|
|
|
test('composables destructure fields are transformed', async () => {
|
|
const result = await parseVue({
|
|
project,
|
|
id: 'test-composition-17',
|
|
name: 'CompositionDemo',
|
|
source: compositionSource
|
|
});
|
|
|
|
// useMouse destructure: x, y should be in composables
|
|
const mouse = result.composables!.find(
|
|
(c) => c.name === 'x' && c.destructure?.includes('y')
|
|
);
|
|
expect(mouse).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('parseVue Composition mode with i18n', () => {
|
|
const i18nSource = `
|
|
<template>
|
|
<div>
|
|
<p>{{ title }}</p>
|
|
<button @click="showMessage">Click</button>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, nextTick } from 'vue';
|
|
import { useI18n } from 'vue-i18n';
|
|
|
|
const __props = defineProps({ title: String });
|
|
|
|
const __i18n = useI18n();
|
|
const count = ref(0);
|
|
|
|
function showMessage() {
|
|
const msg = __i18n.t('hello');
|
|
__i18n.n(count.value);
|
|
nextTick(() => console.log(msg));
|
|
}
|
|
</script>
|
|
`;
|
|
|
|
test('i18n special forms are correctly transformed', async () => {
|
|
const result = await parseVue({
|
|
project,
|
|
id: 'test-i18n-1',
|
|
name: 'I18nDemo',
|
|
source: i18nSource
|
|
});
|
|
|
|
expect(result.apiMode).toBe('composition');
|
|
|
|
const showMessage = result.methods!['showMessage'];
|
|
expect(showMessage).toBeDefined();
|
|
// __i18n.t('hello') → this.$t('hello')
|
|
expect(showMessage.value).toContain('this.$t(');
|
|
// __i18n.n(count.value) → this.$n(this.count.value)
|
|
expect(showMessage.value).toContain('this.$n(');
|
|
// nextTick → this.$nextTick
|
|
expect(showMessage.value).toContain('this.$nextTick');
|
|
});
|
|
});
|
|
|
|
describe('parseVue Composition mode with element-plus globals', () => {
|
|
const elSource = `
|
|
<template>
|
|
<div>
|
|
<el-button @click="handleClick">Click</el-button>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
|
|
|
function handleClick() {
|
|
ElMessage.success('ok');
|
|
ElMessageBox.confirm('sure?');
|
|
}
|
|
</script>
|
|
`;
|
|
|
|
test('element-plus global APIs are correctly transformed', async () => {
|
|
const result = await parseVue({
|
|
project,
|
|
id: 'test-el-1',
|
|
name: 'ElDemo',
|
|
source: elSource
|
|
});
|
|
|
|
expect(result.apiMode).toBe('composition');
|
|
|
|
const handleClick = result.methods!['handleClick'];
|
|
expect(handleClick).toBeDefined();
|
|
// ElMessage.success('ok') → this.$message.success('ok')
|
|
expect(handleClick.value).toContain('this.$message');
|
|
// ElMessageBox.confirm('sure?') → this.$confirm('sure?')
|
|
expect(handleClick.value).toContain('this.$confirm');
|
|
});
|
|
});
|
|
|
|
describe('parseVue Composition mode props in template expressions', () => {
|
|
const propsSource = `
|
|
<template>
|
|
<span>{{ __props.title }}</span>
|
|
<span>{{ title }}</span>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
// @ts-nocheck
|
|
|
|
import { computed } from 'vue';
|
|
import { useProvider } from '@vtj/renderer';
|
|
const __props = defineProps({
|
|
title: {
|
|
type: [String],
|
|
required: false,
|
|
default: ''
|
|
}
|
|
});
|
|
|
|
const __provider = useProvider({ id: '1kwhcdeh', version: '1781199016753' });
|
|
|
|
</script>
|
|
<style lang="css" scoped>
|
|
|
|
</style>
|
|
`;
|
|
|
|
test('__props.title in template → this.title', async () => {
|
|
const result = await parseVue({
|
|
project,
|
|
id: 'test-props-template',
|
|
name: 'PropsDemo',
|
|
source: propsSource
|
|
});
|
|
|
|
expect(result.apiMode).toBe('composition');
|
|
expect(result.nodes).toBeDefined();
|
|
expect(result.nodes!.length).toBeGreaterThan(0);
|
|
|
|
// Find the span with __props.title expression
|
|
const spanNode = result.nodes!.find(
|
|
(n) => n.name === 'span' && typeof n.children === 'object'
|
|
);
|
|
expect(spanNode).toBeDefined();
|
|
|
|
const children = spanNode!.children as any;
|
|
// __props.title → this.title (NOT this.$this.title)
|
|
expect(children.value).toBe('this.title');
|
|
expect(children.value).not.toContain('$this');
|
|
});
|
|
|
|
test('bare prop name title in template → this.title', async () => {
|
|
const result = await parseVue({
|
|
project,
|
|
id: 'test-props-bare',
|
|
name: 'PropsBareDemo',
|
|
source: propsSource
|
|
});
|
|
|
|
// Find the span with bare title expression
|
|
const spanNodes = result.nodes!.filter(
|
|
(n) => n.name === 'span' && typeof n.children === 'object'
|
|
);
|
|
expect(spanNodes.length).toBe(2);
|
|
|
|
const secondSpan = spanNodes[1];
|
|
const children = secondSpan.children as any;
|
|
// bare title → this.title
|
|
expect(children.value).toBe('this.title');
|
|
});
|
|
});
|
|
|
|
describe('parseVue Composition mode with $t in template', () => {
|
|
const templateI18nSource = `
|
|
<template>
|
|
<div>
|
|
{{ $t('ABC') }}
|
|
</div>
|
|
</template>
|
|
<script lang="ts" setup>
|
|
// @ts-nocheck
|
|
|
|
import { ref, inject } from 'vue';
|
|
import { useProvider } from '@vtj/renderer';
|
|
|
|
const __provider = useProvider({ id: 'el0ka4ve', version: '1781414479968' });
|
|
|
|
const theme = inject('theme', 'light');
|
|
|
|
const value = ref('hello')
|
|
</script>
|
|
<style lang="css" scoped>
|
|
/* 组件样式内容 */
|
|
</style>
|
|
`;
|
|
|
|
test("$t('ABC') in template → this.$t('ABC')", async () => {
|
|
const result = await parseVue({
|
|
project,
|
|
id: 'test-template-i18n',
|
|
name: 'TemplateI18nDemo',
|
|
source: templateI18nSource
|
|
});
|
|
|
|
expect(result.apiMode).toBe('composition');
|
|
expect(result.nodes).toBeDefined();
|
|
expect(result.nodes!.length).toBeGreaterThan(0);
|
|
|
|
// Find the div node
|
|
const divNode = result.nodes![0];
|
|
expect(divNode.name).toBe('div');
|
|
|
|
// Check the children expression
|
|
const children = divNode.children as any;
|
|
expect(children.type).toBe('JSExpression');
|
|
// $t('ABC') → this.$t('ABC')
|
|
expect(children.value).toBe("this.$t('ABC')");
|
|
});
|
|
});
|
|
|
|
describe('parseVue Composition mode with destructured i18n', () => {
|
|
const destructureI18nSource = `
|
|
<template>
|
|
<div>
|
|
<p>{{ greeting }}</p>
|
|
<button @click="handleClick">Click</button>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref } from 'vue';
|
|
import { useI18n } from 'vue-i18n';
|
|
|
|
const { t, n, d } = useI18n();
|
|
const count = ref(0);
|
|
|
|
function handleClick() {
|
|
const msg = t('hello');
|
|
const formatted = n(count.value);
|
|
const dateStr = d(new Date(), 'short');
|
|
}
|
|
</script>
|
|
`;
|
|
|
|
test('destructured i18n fields are correctly mapped to $xxx APIs', async () => {
|
|
const result = await parseVue({
|
|
project,
|
|
id: 'test-destructure-i18n',
|
|
name: 'DestructuredI18nDemo',
|
|
source: destructureI18nSource
|
|
});
|
|
|
|
expect(result.apiMode).toBe('composition');
|
|
|
|
const handleClick = result.methods!['handleClick'];
|
|
expect(handleClick).toBeDefined();
|
|
// t('hello') → this.$t('hello')
|
|
expect(handleClick.value).toContain('this.$t(');
|
|
// n(count.value) → this.$n(this.count.value)
|
|
expect(handleClick.value).toContain('this.$n(');
|
|
// d(new Date(), 'short') → this.$d(new Date(), 'short')
|
|
expect(handleClick.value).toContain('this.$d(');
|
|
});
|
|
});
|
|
|
|
describe('parseVue Composition mode v-for with vant icon', () => {
|
|
const cardSource = `
|
|
<template>
|
|
<div v-for="(card, index) in cardList" :key="index">
|
|
<van-icon :name="card.user" size="24" color="#fff"></van-icon>
|
|
</div>
|
|
</template>
|
|
<script lang="ts" setup>
|
|
// @ts-nocheck
|
|
import { useProvider } from '@vtj/renderer';
|
|
import { ref } from 'vue';
|
|
import { Icon as VanIcon } from 'vant';
|
|
|
|
const __provider = useProvider({ id: '1l2qoifa', version: '1781539187917' });
|
|
|
|
const cardList = ref([{ icon: 'contact' }])
|
|
</script>
|
|
<style lang="css" scoped>
|
|
/* 组件样式内容 */
|
|
</style>
|
|
`;
|
|
|
|
test('v-for card.user on van-icon should become this.context.card.user', async () => {
|
|
const result = await parseVue({
|
|
project,
|
|
id: '1l2qoifa',
|
|
name: 'CardDemo',
|
|
source: cardSource
|
|
});
|
|
|
|
const divNode = result.nodes![0];
|
|
expect(divNode.name).toBe('div');
|
|
|
|
const iconNode = (divNode.children as any[])[0];
|
|
expect(iconNode.name).toBe('VanIcon');
|
|
|
|
// card.user 应该被正确解析为 this.context.card.user
|
|
expect(iconNode.props!.name.value).toBe('this.context.card.user');
|
|
});
|
|
});
|
|
|
|
describe('parseVue Composition mode with v-for context', () => {
|
|
const vForSource = `
|
|
<template>
|
|
<div>
|
|
<ElButton v-for="(item, index) in __state.list" type="primary">
|
|
{{ item.name }}
|
|
</ElButton>
|
|
</div>
|
|
</template>
|
|
<script lang="ts" setup>
|
|
// @ts-nocheck
|
|
import { useProvider } from '@vtj/renderer';
|
|
import { reactive } from 'vue';
|
|
import { ElButton } from 'element-plus';
|
|
|
|
const __provider = useProvider({ id: '1l2po11y', version: '1781537430199' });
|
|
|
|
const __state = reactive({
|
|
list: [{ name: 'a' }, { name: 'b' }, { name: 'c' }]
|
|
})
|
|
</script>
|
|
<style lang="css" scoped>
|
|
/* 组件样式内容 */
|
|
</style>
|
|
`;
|
|
|
|
test('v-for context variables are transformed to this.context.xxx', async () => {
|
|
const result = await parseVue({
|
|
project,
|
|
id: 'test-vfor-context',
|
|
name: 'VForContextDemo',
|
|
source: vForSource
|
|
});
|
|
|
|
expect(result.apiMode).toBe('composition');
|
|
expect(result.nodes).toBeDefined();
|
|
expect(result.nodes!.length).toBeGreaterThan(0);
|
|
|
|
// Find the div node
|
|
const divNode = result.nodes![0];
|
|
expect(divNode.name).toBe('div');
|
|
|
|
// Find the ElButton node
|
|
const buttonNode = (divNode.children as any[])[0];
|
|
expect(buttonNode.name).toBe('ElButton');
|
|
|
|
// v-for value should be this.state.list (__state.list → this.state.list)
|
|
const vForDirective = buttonNode.directives!.find(
|
|
(d: any) => d.name === 'vFor'
|
|
);
|
|
expect(vForDirective).toBeDefined();
|
|
expect(vForDirective.value.value).toBe('this.state.list');
|
|
|
|
// children should use this.context.item.name (item.name → this.context.item.name)
|
|
const children = buttonNode.children as any;
|
|
expect(children.value).toBe('this.context.item.name');
|
|
});
|
|
});
|
|
|
|
describe('parseVue Composition mode with slot context', () => {
|
|
const slotSource = `
|
|
<template>
|
|
<div>
|
|
<my-comp>
|
|
<template #default="{ row, idx }">
|
|
<span>{{ row.title }} - {{ idx }}</span>
|
|
</template>
|
|
</my-comp>
|
|
</div>
|
|
</template>
|
|
<script lang="ts" setup>
|
|
// @ts-nocheck
|
|
import { useProvider } from '@vtj/renderer';
|
|
import MyComp from './MyComp.vue';
|
|
|
|
const __provider = useProvider({ id: '1l2po11y', version: '1781537430199' });
|
|
</script>
|
|
`;
|
|
|
|
test('slot scope variables are transformed to this.context.xxx', async () => {
|
|
const result = await parseVue({
|
|
project,
|
|
id: 'test-slot-context',
|
|
name: 'SlotContextDemo',
|
|
source: slotSource
|
|
});
|
|
|
|
expect(result.apiMode).toBe('composition');
|
|
expect(result.nodes).toBeDefined();
|
|
expect(result.nodes!.length).toBeGreaterThan(0);
|
|
|
|
// Find the div node
|
|
const divNode = result.nodes![0];
|
|
expect(divNode.name).toBe('div');
|
|
|
|
// Find the MyComp node
|
|
const myCompNode = (divNode.children as any[])[0];
|
|
expect(myCompNode.name).toBe('MyComp');
|
|
|
|
// MyComp should have children (the slot content)
|
|
expect(Array.isArray(myCompNode.children)).toBe(true);
|
|
|
|
// Find the span node inside slot
|
|
const spanNode = (myCompNode.children as any[])[0];
|
|
expect(spanNode.name).toBe('span');
|
|
|
|
// The span children is a wrapper span containing the actual expressions
|
|
const wrapperSpan = (spanNode.children as any[])[0];
|
|
expect(wrapperSpan.name).toBe('span');
|
|
const exprSpans = wrapperSpan.children as any[];
|
|
expect(exprSpans.length).toBeGreaterThan(0);
|
|
|
|
// Check that row and idx are transformed to this.context.row / this.context.idx
|
|
const firstExpr = exprSpans[0].children as any;
|
|
expect(firstExpr.value).toBe('this.context.row.title');
|
|
|
|
const secondExpr = exprSpans[2].children as any;
|
|
expect(secondExpr.value).toBe('this.context.idx');
|
|
});
|
|
});
|
|
|
|
describe('parseVue Composition mode with destructured i18n mixed with non-destructured', () => {
|
|
const mixedSource = `
|
|
<template>
|
|
<div>
|
|
<button @click="handleClick">Click</button>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { useI18n } from 'vue-i18n';
|
|
|
|
const __i18n = useI18n();
|
|
|
|
function handleClick() {
|
|
__i18n.t('hello');
|
|
}
|
|
</script>
|
|
`;
|
|
|
|
test('non-destructured i18n still works alongside destructured support', async () => {
|
|
const result = await parseVue({
|
|
project,
|
|
id: 'test-mixed-i18n',
|
|
name: 'MixedI18nDemo',
|
|
source: mixedSource
|
|
});
|
|
|
|
expect(result.apiMode).toBe('composition');
|
|
|
|
const handleClick = result.methods!['handleClick'];
|
|
expect(handleClick).toBeDefined();
|
|
// __i18n.t('hello') → this.$t('hello') (member map, unchanged)
|
|
expect(handleClick.value).toContain('this.$t(');
|
|
});
|
|
});
|