Skip to content

Commit 02c190c

Browse files
Automigration: Enhance addon-essentials migration
1 parent 2dcf9dc commit 02c190c

8 files changed

+463
-258
lines changed

code/lib/cli-storybook/src/automigrate/fixes/addon-essentials-remove-docs.test.ts

+131-17
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,24 @@ vi.mock('storybook/internal/cli', () => ({
2828
getStorybookVersionSpecifier: vi.fn(),
2929
}));
3030

31+
vi.mock('storybook/internal/common', () => ({
32+
getAddonNames: vi.fn(),
33+
getProjectRoot: vi.fn().mockReturnValue('/fake/project/root'),
34+
commonGlobOptions: vi.fn().mockReturnValue({}),
35+
}));
36+
37+
vi.mock('prompts', () => ({
38+
default: vi.fn().mockResolvedValue({ glob: '**/*.{mjs,cjs,js,jsx,ts,tsx,mdx}' }),
39+
}));
40+
41+
vi.mock('globby', () => ({
42+
globby: vi.fn().mockResolvedValue(['/fake/project/root/src/stories/Button.stories.tsx']),
43+
}));
44+
45+
vi.mock('../helpers/transformImports', () => ({
46+
transformImportFiles: vi.fn().mockResolvedValue([]),
47+
}));
48+
3149
// Mock ConfigFile type
3250
interface MockConfigFile {
3351
getFieldValue: (path: string[]) => any;
@@ -47,7 +65,10 @@ const mockConfigs = new Map<string, MockConfigFile>();
4765
const readFileMock = vi.mocked(await import('node:fs/promises')).readFile;
4866

4967
const mockPackageManager = {
50-
retrievePackageJson: vi.fn(),
68+
retrievePackageJson: vi.fn().mockResolvedValue({
69+
dependencies: {},
70+
devDependencies: {},
71+
}),
5172
runPackageCommand: vi.fn(),
5273
} as unknown as JsPackageManager;
5374

@@ -70,6 +91,7 @@ interface AddonDocsOptions {
7091
hasEssentials: boolean;
7192
hasDocsDisabled: boolean;
7293
hasDocsAddon: boolean;
94+
additionalAddonsToRemove: string[];
7395
}
7496

7597
// Add type for migration object
@@ -92,7 +114,7 @@ describe('addon-essentials-remove-docs migration', () => {
92114
expect(result).toBeNull();
93115
});
94116

95-
it('returns null if essentials not found in config', async () => {
117+
it('returns null if essentials and no core addons found in config', async () => {
96118
const mainConfig = `
97119
export default {
98120
stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
@@ -112,13 +134,14 @@ describe('addon-essentials-remove-docs migration', () => {
112134
expect(result).toBeNull();
113135
});
114136

115-
it('detects essentials with docs disabled', async () => {
137+
it('detects essentials with docs disabled and core addons', async () => {
116138
const mockMain: MockConfigFile = {
117139
getFieldValue: vi.fn().mockReturnValue([
118140
{
119141
name: '@storybook/addon-essentials',
120142
options: { docs: false },
121143
},
144+
'@storybook/addon-actions',
122145
]),
123146
setFieldValue: vi.fn(),
124147
appendValueToArray: vi.fn(),
@@ -130,6 +153,23 @@ describe('addon-essentials-remove-docs migration', () => {
130153
};
131154

132155
mockConfigs.set('main.ts', mockMain);
156+
vi.mocked(await import('storybook/internal/common')).getAddonNames.mockReturnValue([
157+
'@storybook/addon-essentials',
158+
'@storybook/addon-actions',
159+
]);
160+
161+
const mockPackageJsonWithAddons = {
162+
dependencies: {
163+
'@storybook/addon-controls': '^7.0.0',
164+
},
165+
devDependencies: {
166+
'@storybook/addon-toolbars': '^7.0.0',
167+
},
168+
};
169+
170+
vi.mocked(mockPackageManager.retrievePackageJson).mockResolvedValueOnce(
171+
mockPackageJsonWithAddons
172+
);
133173

134174
const result = await typedAddonDocsEssentials.check({
135175
...baseCheckOptions,
@@ -141,19 +181,25 @@ describe('addon-essentials-remove-docs migration', () => {
141181
name: '@storybook/addon-essentials',
142182
options: { docs: false },
143183
},
184+
'@storybook/addon-actions',
144185
],
145186
} as StorybookConfigRaw,
146187
});
147188
expect(result).toEqual({
148189
hasEssentials: true,
149190
hasDocsDisabled: true,
150191
hasDocsAddon: false,
192+
additionalAddonsToRemove: [
193+
'@storybook/addon-actions',
194+
'@storybook/addon-controls',
195+
'@storybook/addon-toolbars',
196+
],
151197
});
152198
});
153199

154-
it('detects essentials with docs enabled', async () => {
200+
it('detects only core addons without essentials', async () => {
155201
const mockMain: MockConfigFile = {
156-
getFieldValue: vi.fn().mockReturnValue(['@storybook/addon-essentials']),
202+
getFieldValue: vi.fn().mockReturnValue(['@storybook/addon-actions']),
157203
setFieldValue: vi.fn(),
158204
appendValueToArray: vi.fn(),
159205
removeField: vi.fn(),
@@ -164,30 +210,46 @@ describe('addon-essentials-remove-docs migration', () => {
164210
};
165211

166212
mockConfigs.set('main.ts', mockMain);
213+
vi.mocked(await import('storybook/internal/common')).getAddonNames.mockReturnValue([
214+
'@storybook/addon-actions',
215+
]);
216+
217+
const mockPackageJsonWithViewport = {
218+
dependencies: {},
219+
devDependencies: {
220+
'@storybook/addon-viewport': '^7.0.0',
221+
},
222+
};
223+
224+
vi.mocked(mockPackageManager.retrievePackageJson).mockResolvedValueOnce(
225+
mockPackageJsonWithViewport
226+
);
167227

168228
const result = await typedAddonDocsEssentials.check({
169229
...baseCheckOptions,
170230
mainConfigPath: 'main.ts',
171231
mainConfig: {
172232
stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
173-
addons: ['@storybook/addon-essentials'],
233+
addons: ['@storybook/addon-actions'],
174234
} as StorybookConfigRaw,
175235
});
176236
expect(result).toEqual({
177-
hasEssentials: true,
237+
hasEssentials: false,
178238
hasDocsDisabled: false,
179239
hasDocsAddon: false,
240+
additionalAddonsToRemove: ['@storybook/addon-actions', '@storybook/addon-viewport'],
180241
});
181242
});
182243
});
183244

184245
describe('run phase', () => {
185-
it('removes essentials addon when docs is disabled', async () => {
246+
it('removes essentials addon and core addons when docs is disabled', async () => {
186247
await typedAddonDocsEssentials.run({
187248
result: {
188249
hasEssentials: true,
189250
hasDocsDisabled: true,
190251
hasDocsAddon: false,
252+
additionalAddonsToRemove: ['@storybook/addon-actions', '@storybook/addon-controls'],
191253
},
192254
packageManager: mockPackageManager,
193255
packageJson: mockPackageJson,
@@ -199,31 +261,81 @@ describe('addon-essentials-remove-docs migration', () => {
199261
'remove',
200262
'@storybook/addon-essentials',
201263
]);
202-
expect(mockPackageManager.runPackageCommand).toHaveBeenCalledTimes(1);
264+
expect(mockPackageManager.runPackageCommand).toHaveBeenCalledWith('storybook', [
265+
'remove',
266+
'@storybook/addon-actions',
267+
]);
268+
expect(mockPackageManager.runPackageCommand).toHaveBeenCalledWith('storybook', [
269+
'remove',
270+
'@storybook/addon-controls',
271+
]);
272+
expect(mockPackageManager.runPackageCommand).toHaveBeenCalledTimes(3);
203273
});
204274

205-
it('removes essentials addon and installs addon-docs when docs is enabled', async () => {
275+
it('removes core addons without essentials', async () => {
276+
const mockPackageManagerLocal = {
277+
retrievePackageJson: vi.fn(),
278+
runPackageCommand: vi.fn(),
279+
} as unknown as JsPackageManager;
280+
206281
await typedAddonDocsEssentials.run({
207282
result: {
208-
hasEssentials: true,
283+
hasEssentials: false,
209284
hasDocsDisabled: false,
210285
hasDocsAddon: false,
286+
additionalAddonsToRemove: ['@storybook/addon-actions', '@storybook/addon-controls'],
211287
},
212-
packageManager: mockPackageManager,
288+
packageManager: mockPackageManagerLocal,
213289
packageJson: mockPackageJson,
214290
mainConfigPath: 'main.ts',
215291
mainConfig: {} as StorybookConfigRaw,
216292
});
217293

218-
expect(mockPackageManager.runPackageCommand).toHaveBeenCalledWith('storybook', [
294+
expect(mockPackageManagerLocal.runPackageCommand).toHaveBeenCalledWith('storybook', [
219295
'remove',
220-
'@storybook/addon-essentials',
296+
'@storybook/addon-actions',
221297
]);
222-
expect(mockPackageManager.runPackageCommand).toHaveBeenCalledWith('storybook', [
298+
expect(mockPackageManagerLocal.runPackageCommand).toHaveBeenCalledWith('storybook', [
299+
'remove',
300+
'@storybook/addon-controls',
301+
]);
302+
expect(mockPackageManagerLocal.runPackageCommand).toHaveBeenCalledWith('storybook', [
223303
'add',
224304
'@storybook/addon-docs',
225305
]);
226-
expect(mockPackageManager.runPackageCommand).toHaveBeenCalledTimes(2);
306+
expect(mockPackageManagerLocal.runPackageCommand).toHaveBeenCalledTimes(3);
307+
});
308+
309+
it('handles import transformations', async () => {
310+
const { transformImportFiles } = await import('../helpers/transformImports');
311+
312+
await typedAddonDocsEssentials.run({
313+
result: {
314+
hasEssentials: false,
315+
hasDocsDisabled: false,
316+
hasDocsAddon: false,
317+
additionalAddonsToRemove: ['@storybook/addon-actions', '@storybook/addon-controls'],
318+
},
319+
packageManager: mockPackageManager,
320+
packageJson: mockPackageJson,
321+
mainConfigPath: 'main.ts',
322+
mainConfig: {} as StorybookConfigRaw,
323+
});
324+
325+
expect(transformImportFiles).toHaveBeenCalledWith(
326+
['/fake/project/root/src/stories/Button.stories.tsx'],
327+
{
328+
'@storybook/addon-actions': 'storybook/actions',
329+
'@storybook/addon-controls': 'storybook/internal/controls',
330+
'@storybook/addon-toolbars': 'storybook/internal/toolbars',
331+
'@storybook/addon-highlight': 'storybook/highlight',
332+
'@storybook/addon-measure': 'storybook/measure',
333+
'@storybook/addon-outline': 'storybook/outline',
334+
'@storybook/addon-backgrounds': 'storybook/backgrounds',
335+
'@storybook/addon-viewport': 'storybook/viewport',
336+
},
337+
undefined
338+
);
227339
});
228340

229341
it('does nothing in dry run mode', async () => {
@@ -232,6 +344,7 @@ describe('addon-essentials-remove-docs migration', () => {
232344
hasEssentials: true,
233345
hasDocsDisabled: false,
234346
hasDocsAddon: false,
347+
additionalAddonsToRemove: ['@storybook/addon-actions', '@storybook/addon-controls'],
235348
},
236349
packageManager: mockPackageManager,
237350
packageJson: mockPackageJson,
@@ -243,12 +356,13 @@ describe('addon-essentials-remove-docs migration', () => {
243356
expect(mockPackageManager.runPackageCommand).not.toHaveBeenCalled();
244357
});
245358

246-
it('handles missing essentials addon gracefully', async () => {
359+
it('handles missing essentials addon and no core addons gracefully', async () => {
247360
await typedAddonDocsEssentials.run({
248361
result: {
249362
hasEssentials: false,
250363
hasDocsDisabled: false,
251364
hasDocsAddon: false,
365+
additionalAddonsToRemove: [],
252366
},
253367
packageManager: mockPackageManager,
254368
packageJson: mockPackageJson,

0 commit comments

Comments
 (0)