Skip to content

Commit 61dda86

Browse files
authored
Merge pull request #124 from aurelia/feature/storybook-integration
feat(cli): adds storybook integration
2 parents 0f77577 + afd0916 commit 61dda86

File tree

12 files changed

+492
-1
lines changed

12 files changed

+492
-1
lines changed

__test__/questions.spec.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,41 @@ test('TailwindCSS question comes before unit testing question', t => {
5454
t.true(testingQuestionIndex >= 0, 'Unit testing question should exist');
5555
t.true(tailwindQuestionIndex < testingQuestionIndex, 'TailwindCSS question should come before unit testing question');
5656
});
57+
58+
test('Storybook question is properly included', t => {
59+
const storybookQuestion = questions.find(q =>
60+
q.message && q.message.includes('Storybook')
61+
);
62+
63+
t.truthy(storybookQuestion, 'Storybook question should exist');
64+
t.is(storybookQuestion.message, 'Do you want to add Storybook?');
65+
t.true(Array.isArray(storybookQuestion.choices), 'Storybook question should have choices');
66+
t.is(storybookQuestion.choices.length, 2, 'Storybook question should have 2 choices');
67+
68+
// Check "No" option
69+
const noChoice = storybookQuestion.choices[0];
70+
t.is(noChoice.title, 'No');
71+
t.is(noChoice.value, undefined);
72+
73+
// Check "Yes" option
74+
const yesChoice = storybookQuestion.choices[1];
75+
t.is(yesChoice.value, 'storybook');
76+
t.is(yesChoice.title, 'Yes');
77+
t.truthy(yesChoice.hint);
78+
t.true(yesChoice.hint.includes('Vite or Webpack'));
79+
t.truthy(yesChoice.if);
80+
t.is(yesChoice.if, '(app && (vite || webpack)) || (plugin && webpack)');
81+
});
82+
83+
test('Storybook question comes after e2e testing question', t => {
84+
const e2eQuestionIndex = questions.findIndex(q =>
85+
q.message && q.message.includes('e2e test')
86+
);
87+
const storybookQuestionIndex = questions.findIndex(q =>
88+
q.message && q.message.includes('Storybook')
89+
);
90+
91+
t.true(e2eQuestionIndex >= 0, 'E2E testing question should exist');
92+
t.true(storybookQuestionIndex >= 0, 'Storybook question should exist');
93+
t.true(storybookQuestionIndex > e2eQuestionIndex, 'Storybook question should come after e2e testing question');
94+
});

__test__/storybook.spec.js

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
const test = require('ava');
2+
const fs = require('fs');
3+
const path = require('path');
4+
5+
test('Storybook configuration files exist', t => {
6+
// Check main config
7+
const mainPath = path.join(__dirname, '..', 'common', 'storybook-main.ext__if_storybook');
8+
t.true(fs.existsSync(mainPath), 'Storybook main config should exist');
9+
10+
// Check preview config
11+
const previewPath = path.join(__dirname, '..', 'common', 'storybook-preview.ext__if_storybook');
12+
t.true(fs.existsSync(previewPath), 'Storybook preview config should exist');
13+
14+
// Check README
15+
const readmePath = path.join(__dirname, '..', 'storybook__if_storybook', 'README.md');
16+
t.true(fs.existsSync(readmePath), 'Storybook README should exist');
17+
});
18+
19+
test('Storybook configuration includes both Vite and Webpack setups', t => {
20+
const mainPath = path.join(__dirname, '..', 'common', 'storybook-main.ext__if_storybook');
21+
const content = fs.readFileSync(mainPath, 'utf8');
22+
23+
t.true(content.includes('@aurelia/storybook'), 'Should use Aurelia Storybook framework');
24+
t.true(content.includes('@storybook/builder-vite'), 'Should use Vite builder');
25+
t.true(content.includes('@storybook/builder-webpack5'), 'Should use Webpack5 builder');
26+
t.true(content.includes('@storybook/addon-links'), 'Should include links addon');
27+
t.true(content.includes('viteFinal'), 'Should have viteFinal configuration');
28+
t.true(content.includes('@aurelia/runtime-html'), 'Should exclude Aurelia runtime from optimization');
29+
t.true(content.includes('/* @if vite */'), 'Should have Vite conditional');
30+
t.true(content.includes('/* @if webpack */'), 'Should have Webpack conditional');
31+
});
32+
33+
test('Storybook Webpack configuration is included', t => {
34+
const mainPath = path.join(__dirname, '..', 'common', 'storybook-main.ext__if_storybook');
35+
const content = fs.readFileSync(mainPath, 'utf8');
36+
37+
t.true(content.includes('docs: {}'), 'Should have docs configuration');
38+
});
39+
40+
test('Storybook preview configuration is correct', t => {
41+
const previewPath = path.join(__dirname, '..', 'common', 'storybook-preview.ext__if_storybook');
42+
const content = fs.readFileSync(previewPath, 'utf8');
43+
44+
t.true(content.includes("export { render, renderToCanvas } from '@aurelia/storybook'"),
45+
'Should export render functions from Aurelia Storybook plugin');
46+
});
47+
48+
test('Storybook story files exist and are properly structured', t => {
49+
// Check app-min story file
50+
const appMinStoryPath = path.join(__dirname, '..', 'app-min', 'src', 'my-app.stories.ext__if_storybook');
51+
t.true(fs.existsSync(appMinStoryPath), 'App-min story file should exist');
52+
53+
const appMinContent = fs.readFileSync(appMinStoryPath, 'utf8');
54+
t.true(appMinContent.includes('/* @if vite */'), 'Should have Vite conditional');
55+
t.true(appMinContent.includes('/* @if webpack */'), 'Should have Webpack conditional');
56+
t.true(appMinContent.includes('import { MyApp }'), 'Should import MyApp component');
57+
58+
// Check app-with-router story file
59+
const routerStoryPath = path.join(__dirname, '..', 'app-with-router', 'src', 'welcome-page.stories.ext__if_storybook');
60+
t.true(fs.existsSync(routerStoryPath), 'Router app story file should exist');
61+
62+
// Check plugin story file
63+
const pluginStoryPath = path.join(__dirname, '..', 'plugin-min', 'src', 'hello-world.stories.ext__if_storybook');
64+
t.true(fs.existsSync(pluginStoryPath), 'Plugin story file should exist');
65+
});
66+
67+
test('Vite package.json includes correct Storybook dependencies', t => {
68+
const vitePackagePath = path.join(__dirname, '..', 'vite', 'package.json');
69+
const content = fs.readFileSync(vitePackagePath, 'utf8');
70+
71+
t.true(content.includes('"@aurelia/storybook": "^1.0.2"'), 'Should include Aurelia Storybook plugin v1.0.2');
72+
t.true(content.includes('"storybook": "^9.0.0"'), 'Should include Storybook 9');
73+
t.true(content.includes('"@storybook/builder-vite": "^9.0.0"'), 'Should include Vite builder');
74+
t.true(content.includes('"@storybook/addon-links": "^9.0.0"'), 'Should include links addon');
75+
t.true(content.includes('"@storybook/addon-actions": "^9.0.0"'), 'Should include actions addon');
76+
t.true(content.includes('"@storybook/test": "^9.0.0-alpha.2"'), 'Should include test utilities');
77+
78+
// Check scripts
79+
t.true(content.includes('"storybook": "storybook dev -p 6006"'), 'Should include storybook dev script');
80+
t.true(content.includes('"build-storybook": "storybook build"'), 'Should include build script');
81+
});
82+
83+
test('Webpack package.json includes correct Storybook dependencies', t => {
84+
const webpackPackagePath = path.join(__dirname, '..', 'webpack', 'package.json');
85+
const content = fs.readFileSync(webpackPackagePath, 'utf8');
86+
87+
t.true(content.includes('"@aurelia/storybook": "^1.0.2"'), 'Should include Aurelia Storybook plugin v1.0.2');
88+
t.true(content.includes('"storybook": "^9.0.0"'), 'Should include Storybook 9');
89+
t.true(content.includes('"@storybook/builder-webpack5": "^9.0.0"'), 'Should include Webpack5 builder');
90+
t.true(content.includes('"@storybook/addon-links": "^9.0.0"'), 'Should include links addon');
91+
92+
// Check scripts
93+
t.true(content.includes('"storybook": "storybook dev -p 6006"'), 'Should include storybook dev script');
94+
t.true(content.includes('"build-storybook": "storybook build"'), 'Should include build script');
95+
});
96+
97+
test('Storybook README contains helpful information', t => {
98+
const readmePath = path.join(__dirname, '..', 'storybook__if_storybook', 'README.md');
99+
const content = fs.readFileSync(readmePath, 'utf8');
100+
101+
t.true(content.includes('# Storybook Integration'), 'Should have main heading');
102+
t.true(content.includes('npm run storybook'), 'Should include dev command');
103+
t.true(content.includes('npm run build-storybook'), 'Should include build command');
104+
t.true(content.includes('http://localhost:6006'), 'Should mention default port');
105+
t.true(content.includes('.stories.ts'), 'Should explain story file naming');
106+
t.true(content.includes('Aurelia Storybook'), 'Should reference the plugin');
107+
});
108+
109+
test('After task includes Storybook setup logic', t => {
110+
const afterPath = path.join(__dirname, '..', 'after.js');
111+
const content = fs.readFileSync(afterPath, 'utf8');
112+
113+
t.true(content.includes("if (features.includes('storybook'))"), 'Should check for storybook feature');
114+
t.true(content.includes("fs.mkdirSync('.storybook')"), 'Should create .storybook directory');
115+
t.true(content.includes('storybook-main'), 'Should reference storybook-main file');
116+
t.true(content.includes('storybook-preview'), 'Should reference storybook-preview file');
117+
t.true(content.includes('.storybook/main'), 'Should move to .storybook/main');
118+
t.true(content.includes('.storybook/preview'), 'Should move to .storybook/preview');
119+
});

after.js

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Use "after" task to ask user to install deps.
22

33
const {execSync} = require('child_process');
4+
const fs = require('fs');
5+
const path = require('path');
46

57
function isAvailable(bin) {
68
try {
@@ -21,7 +23,7 @@ module.exports = async function({
2123
const c = ansiColors;
2224
let depsInstalled = false;
2325
let packageManager = undefined;
24-
26+
2527
if (!unattended) {
2628
const choices = [
2729
{title: 'No'},
@@ -58,6 +60,45 @@ module.exports = async function({
5860
_log(c.inverse(` npx makes aurelia new-project-name${here ? ' --here' : ''} -s ${notDefaultFeatures.length ? (notDefaultFeatures.join(',') + ' ') : ''}`));
5961
}
6062

63+
// Setup Storybook directory and files
64+
if (features.includes('storybook')) {
65+
try {
66+
// Navigate to project directory if we're not in it already
67+
const projectDir = here ? '.' : properties.name;
68+
const originalCwd = process.cwd();
69+
70+
if (!here && fs.existsSync(projectDir)) {
71+
process.chdir(projectDir);
72+
}
73+
74+
// Create .storybook directory
75+
if (!fs.existsSync('.storybook')) {
76+
fs.mkdirSync('.storybook');
77+
}
78+
79+
// Move and rename storybook configuration files
80+
const extension = features.includes('typescript') ? '.ts' : '.js';
81+
82+
const mainFile = `storybook-main${extension}`;
83+
const previewFile = `storybook-preview${extension}`;
84+
85+
if (fs.existsSync(mainFile)) {
86+
fs.renameSync(mainFile, `.storybook/main${extension}`);
87+
}
88+
89+
if (fs.existsSync(previewFile)) {
90+
fs.renameSync(previewFile, `.storybook/preview${extension}`);
91+
}
92+
93+
// Return to original directory
94+
if (!here && originalCwd !== process.cwd()) {
95+
process.chdir(originalCwd);
96+
}
97+
} catch (error) {
98+
_log(c.yellow(`Warning: Could not setup .storybook directory: ${error.message}`));
99+
}
100+
}
101+
61102
_log(`\n${c.underline.bold('Get Started')}`);
62103
if (!here) _log('cd ' + properties.name);
63104

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/* @if vite */
2+
import { MyApp } from './my-app';
3+
//import { action } from '@storybook/addon-actions';
4+
//import { userEvent, within } from '@storybook/test';
5+
6+
const meta = {
7+
title: 'Example/MyApp',
8+
component: MyApp,
9+
render: () => ({
10+
template: `<my-app message.bind="message"></my-app>`,
11+
}),
12+
argTypes: {
13+
message: { control: 'text' }
14+
}
15+
};
16+
17+
export default meta;
18+
19+
export const Default = {
20+
args: {
21+
message: 'Hello from Storybook!'
22+
}
23+
};
24+
25+
export const CustomMessage = {
26+
args: {
27+
message: 'This is a custom message for testing'
28+
}
29+
};
30+
31+
export const WelcomeMessage = {
32+
args: {
33+
message: 'Welcome to your Aurelia 2 + Storybook setup!'
34+
}
35+
};
36+
37+
export const NoArgs = {
38+
render: () => ({
39+
template: `<my-app></my-app>`
40+
})
41+
};
42+
/* @endif */
43+
/* @if webpack */
44+
import { MyApp } from './my-app';
45+
46+
export default {
47+
title: 'MyApp',
48+
component: MyApp,
49+
};
50+
51+
export const Default = () => ({
52+
Component: MyApp,
53+
template: '<my-app></my-app>',
54+
props: {}
55+
});
56+
/* @endif */
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/* @if vite */
2+
import { WelcomePage } from './welcome-page';
3+
4+
const meta = {
5+
title: 'Pages/WelcomePage',
6+
component: WelcomePage,
7+
render: () => ({
8+
template: `<welcome-page message.bind="message"></welcome-page>`,
9+
}),
10+
argTypes: {
11+
message: { control: 'text' }
12+
}
13+
};
14+
15+
export default meta;
16+
17+
export const Default = {
18+
args: {
19+
message: 'Welcome to Aurelia 2!'
20+
}
21+
};
22+
23+
export const CustomWelcome = {
24+
args: {
25+
message: 'Welcome to your amazing Aurelia app!'
26+
}
27+
};
28+
29+
export const StorybookWelcome = {
30+
args: {
31+
message: 'Welcome to Storybook + Aurelia 2!'
32+
}
33+
};
34+
35+
export const LongMessage = {
36+
args: {
37+
message: 'Welcome to this comprehensive demonstration of Aurelia 2 components in Storybook!'
38+
}
39+
};
40+
/* @endif */
41+
/* @if webpack */
42+
import { WelcomePage } from './welcome-page';
43+
44+
export default {
45+
title: 'WelcomePage',
46+
component: WelcomePage,
47+
};
48+
49+
export const Default = () => ({
50+
Component: WelcomePage,
51+
template: '<welcome-page></welcome-page>',
52+
props: {}
53+
});
54+
/* @endif */
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/* @if vite */
2+
import type { StorybookConfig } from 'storybook/internal/types';
3+
import { mergeConfig, type InlineConfig } from 'vite';
4+
5+
const config: StorybookConfig & { viteFinal?: (config: InlineConfig, options: { configType: string }) => InlineConfig | Promise<InlineConfig> } = {
6+
stories: ['../src/**/*.stories.@(ts|tsx|js|jsx|mdx)'],
7+
addons: [
8+
'@storybook/addon-links'
9+
],
10+
framework: {
11+
name: '@aurelia/storybook',
12+
options: {},
13+
},
14+
core: {
15+
builder: '@storybook/builder-vite',
16+
},
17+
viteFinal: async (viteConfig) => {
18+
viteConfig.optimizeDeps = viteConfig.optimizeDeps || {};
19+
viteConfig.optimizeDeps.exclude = viteConfig.optimizeDeps.exclude || [];
20+
if (!viteConfig.optimizeDeps.exclude.includes('@aurelia/runtime-html')) {
21+
viteConfig.optimizeDeps.exclude.push('@aurelia/runtime-html');
22+
}
23+
return mergeConfig(viteConfig, {
24+
// ...any additional Vite configuration
25+
});
26+
},
27+
};
28+
29+
export default config;
30+
/* @endif */
31+
/* @if webpack */
32+
const config = {
33+
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
34+
addons: [
35+
"@storybook/addon-links"
36+
],
37+
framework: {
38+
name: '@aurelia/storybook',
39+
options: {},
40+
},
41+
core: {
42+
builder: '@storybook/builder-webpack5',
43+
},
44+
docs: {},
45+
};
46+
47+
export default config;
48+
/* @endif */
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Import the render function from the Aurelia Storybook plugin
2+
export { render, renderToCanvas } from '@aurelia/storybook';

0 commit comments

Comments
 (0)