Skip to content

Commit 6ac714f

Browse files
authored
Merge pull request #12 from rudyxu1102/feat/v-slot
feat: support v-slots
2 parents b171b96 + 1a1fd1f commit 6ac714f

File tree

13 files changed

+99
-21
lines changed

13 files changed

+99
-21
lines changed

examples/src/components/button/__tests__/Button2.spec.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { mount } from '@vue/test-utils';
22
import { describe, it, expect } from 'vitest';
3-
import { Button } from '../index';
3+
import Button from '../index';
44

55
describe('Button.tsx', () => {
6-
76
it('renders emits click', async () => {
87
const fn = vi.fn()
98
const wrapper = mount({
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import UiButton from './Button'
22

3-
export default UiButton
3+
// export default UiButton
44

5-
export { UiButton as Button }
5+
export { UiButton as default }

examples/src/components/input/Input.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export default defineComponent({
1010

1111
props: {
1212
...props,
13+
label: String
1314
},
1415

1516
slots: Object as SlotsType<{
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11

2-
import warpInput from './warpInput'
3-
export default warpInput
2+
import { withInstall } from './warpInput'
3+
import Input from './Input'
4+
export default withInstall(Input)

examples/src/components/input/input.spec.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,4 +133,10 @@ describe('Input.tsx', () => {
133133
(wrapper.vm as any).select();
134134
(wrapper.vm as any).clear();
135135
});
136+
137+
it('renders with custom class', () => {
138+
render(<Input label="custom-class" />)
139+
// expect(screen.getByRole('textbox')).toHaveClass('custom-class')
140+
expect(1).toBe(1)
141+
})
136142
});
Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1-
import Input from './Input'
2-
Input.aaa = 1
3-
export default Input
1+
export type WithInstall<T> = T & {
2+
install: (app: any) => void;
3+
};
4+
5+
export function withInstall<T>(options: T) {
6+
(options as Record<string, unknown>).install = (app: any) => {
7+
const { name } = options as unknown as { name: string };
8+
app.component(name, options);
9+
};
10+
11+
return options as WithInstall<T>;
12+
}
13+

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "vc-api-coverage",
3-
"version": "0.6.1",
3+
"version": "0.6.2",
44
"description": "Vue Component API Coverage Reporter",
55
"main": "lib/index.js",
66
"types": "lib/types/index.d.ts",

src/ApiReporter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export default class VcCoverageReporter implements Reporter {
128128
info.slots.total += comp.slots.length
129129
info.exposes.total += comp.exposes.length
130130
info.props.details = comp.props.map(p => ({ name: p, covered: unit.props.includes(p) }))
131-
info.slots.details = unit.slots.map(s => ({ name: s, covered: true }))
131+
info.slots.details = comp.slots.length > 0 ? comp.slots.map(s => ({ name: s, covered: unit.slots.includes(s) })) : unit.slots.map(s => ({ name: s, covered: true }))
132132
info.exposes.details = comp.exposes.map(e => ({ name: e, covered: unit.exposes.includes(e) }))
133133
info.props.covered = info.props.details.filter(d => d.covered).length
134134
info.slots.covered = info.slots.details.filter(d => d.covered).length

src/analyzer/ComponentAnalyzer.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { SourceFile, Node, Type, Expression, ObjectLiteralExpression, SyntaxKind } from "ts-morph";
2+
import { isComponentType } from "../common/utils";
23

34
class ComponentAnalyzer {
45
private sourceFile: SourceFile;
@@ -170,12 +171,6 @@ class ComponentAnalyzer {
170171
return item.getText().replace(/[\'\"\`]/g, '');
171172
}
172173

173-
isComponentFile(type: Type) {
174-
const constructSignatures = type.getConstructSignatures();
175-
if (constructSignatures.length === 0) return false;
176-
return true
177-
}
178-
179174
getExportedExpression() {
180175
let exportedExpression = this.getExportedExpressionFromDefault();
181176
if (exportedExpression) return exportedExpression;
@@ -202,7 +197,7 @@ class ComponentAnalyzer {
202197
}
203198
}
204199

205-
if (expression && this.isComponentFile(expression.getType())) {
200+
if (expression && isComponentType(expression.getType())) {
206201
return expression;
207202
}
208203
return null;
@@ -212,7 +207,7 @@ class ComponentAnalyzer {
212207
const namedExportSymbol = this.sourceFile.getExportSymbols().find(symbol => {
213208
const valueDeclaration = symbol.getValueDeclaration();
214209
if (!valueDeclaration) return false;
215-
return this.isComponentFile(valueDeclaration.getType());
210+
return isComponentType(valueDeclaration.getType());
216211
});
217212

218213
if (!namedExportSymbol) return null;

src/analyzer/UnitTestAnalyzer.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Project, SyntaxKind, Node, SourceFile, CallExpression, ObjectLiteralExpression, JsxSelfClosingElement, JsxElement, Identifier, Symbol } from 'ts-morph';
2+
import { isComponentFile, isComponentType } from '../common/utils';
23

34
interface TestUnit {
45
props?: string[];
@@ -89,12 +90,33 @@ class TestUnitAnalyzer {
8990
if (!declarationNode) return null;
9091
const declarationSourceFile = declarationNode.getSourceFile();
9192
const originalPath = declarationSourceFile.getFilePath();
93+
if (!isComponentFile(originalPath)) {
94+
return this.resolveTsPath(declarationNode);
95+
}
9296
return originalPath;
9397
} catch (error) {
9498
return null;
9599
}
96100
}
97101

102+
// 解析ts路径
103+
resolveTsPath(declarationNode: Node) {
104+
if (!Node.isExportAssignment(declarationNode)) return null;
105+
const exportedExpression = declarationNode.getExpression();
106+
if (Node.isCallExpression(exportedExpression)) {
107+
const args = exportedExpression.getArguments();
108+
for (const arg of args) {
109+
const argType = arg.getType();
110+
if (isComponentType(argType)) {
111+
// 获取文件路径
112+
const res = this.resolveComponentPath(arg as Identifier) as string;
113+
return res;
114+
}
115+
}
116+
}
117+
return null;
118+
}
119+
98120

99121
// 分析传统挂载mount/shallowMount方法调用
100122
private analyzeTraditionalMountCalls(testCall: CallExpression) {
@@ -545,6 +567,23 @@ class TestUnitAnalyzer {
545567
if (!component.emits.includes(propName)) {
546568
component.emits.push(propName);
547569
}
570+
} else if (propName === 'v-slots') {
571+
// Handle v-slots directive
572+
const initializer = attr.getInitializer();
573+
if (initializer && Node.isJsxExpression(initializer)) {
574+
const expression = initializer.getExpression();
575+
if (expression && Node.isObjectLiteralExpression(expression)) {
576+
component.slots = component.slots || [];
577+
expression.getProperties().forEach(prop => {
578+
if (Node.isPropertyAssignment(prop) || Node.isShorthandPropertyAssignment(prop)) {
579+
const slotName = prop.getName();
580+
if (slotName && !component.slots!.includes(slotName)) {
581+
component.slots!.push(slotName);
582+
}
583+
}
584+
});
585+
}
586+
}
548587
} else {
549588
let isVModel = false;
550589
// Handle v-model transformation for props

0 commit comments

Comments
 (0)