Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions code/core/src/cli/AddonVitestService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -601,5 +601,48 @@ describe('AddonVitestService', () => {
expect(result.reasons).toBeDefined();
expect(result.reasons!.length).toBe(2);
});

it('should validate mergeConfig with plain object literal', async () => {
vi.mocked(find.any)
.mockReturnValueOnce(undefined) // workspace
.mockReturnValueOnce('vitest.config.ts'); // config
vi.mocked(fs.readFile).mockResolvedValue(
'export default mergeConfig(viteConfig, { test: { name: "node" } })'
);
const result = await service.validateConfigFiles('.storybook');
expect(result.compatible).toBe(true);
});

it('should validate mergeConfig with defineConfig call', async () => {
vi.mocked(find.any)
.mockReturnValueOnce(undefined) // workspace
.mockReturnValueOnce('vitest.config.ts'); // config
vi.mocked(fs.readFile).mockResolvedValue(
'export default mergeConfig(viteConfig, defineConfig({ test: { name: "node" } }))'
);
const result = await service.validateConfigFiles('.storybook');
expect(result.compatible).toBe(true);
});

it('should validate mergeConfig with multiple plain objects', async () => {
vi.mocked(find.any)
.mockReturnValueOnce(undefined) // workspace
.mockReturnValueOnce('vitest.config.ts'); // config
vi.mocked(fs.readFile).mockResolvedValue(
'export default mergeConfig({ test: {} }, { plugins: [] })'
);
const result = await service.validateConfigFiles('.storybook');
expect(result.compatible).toBe(true);
});

it('should reject mergeConfig with invalid object (non-object argument)', async () => {
vi.mocked(find.any)
.mockReturnValueOnce(undefined) // workspace
.mockReturnValueOnce('vitest.config.ts'); // config
vi.mocked(fs.readFile).mockResolvedValue('export default mergeConfig(viteConfig, "string")');
const result = await service.validateConfigFiles('.storybook');
expect(result.compatible).toBe(false);
expect(result.reasons!.some((r) => r.includes('invalid Vitest config'))).toBe(true);
});
});
});
46 changes: 29 additions & 17 deletions code/core/src/cli/AddonVitestService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,9 +326,7 @@ export class AddonVitestService {
babel.traverse(parsedConfig, {
ExportDefaultDeclaration: (path: any) => {
if (this.isDefineConfigExpression(path.node.declaration)) {
isValidVitestConfig = this.isSafeToExtendWorkspace(
path.node.declaration as CallExpression
);
isValidVitestConfig = this.isSafeToExtendWorkspace(path.node.declaration);
} else if (this.isMergeConfigExpression(path.node.declaration)) {
// the config could be anywhere in the mergeConfig call, so we need to check each argument
const mergeCall = path.node.declaration as CallExpression;
Expand Down Expand Up @@ -372,20 +370,34 @@ export class AddonVitestService {
return babel.types.isCallExpression(path) && (path.callee as any)?.name === 'mergeConfig';
}

private isSafeToExtendWorkspace(node: CallExpression): boolean {
return (
babel.types.isCallExpression(node) &&
node.arguments.length > 0 &&
babel.types.isObjectExpression(node.arguments?.[0]) &&
node.arguments[0]?.properties.every(
(p: any) =>
p.key?.name !== 'test' ||
(babel.types.isObjectExpression(p.value) &&
p.value.properties.every(
({ key, value }: any) =>
key?.name !== 'workspace' || babel.types.isArrayExpression(value)
))
)
private isSafeToExtendWorkspace(node: babel.types.Node): boolean {
// Extract the object expression to validate
let objectToValidate: babel.types.ObjectExpression | null = null;

if (babel.types.isCallExpression(node)) {
// Handle function calls like defineConfig({...})
if (node.arguments.length > 0 && babel.types.isObjectExpression(node.arguments[0])) {
objectToValidate = node.arguments[0];
}
} else if (babel.types.isObjectExpression(node)) {
// Handle plain object literals like {...}
objectToValidate = node;
}

// If we couldn't extract a valid object, it's not safe
if (!objectToValidate) {
return false;
}

// Check that the object doesn't have problematic test.workspace properties
return objectToValidate.properties.every(
(p: any) =>
p.key?.name !== 'test' ||
(babel.types.isObjectExpression(p.value) &&
p.value.properties.every(
({ key, value }: any) =>
key?.name !== 'workspace' || babel.types.isArrayExpression(value)
))
);
}
}
Loading