Skip to content

Commit a07d5e8

Browse files
feat(qwik-nx): storybook generator (#110)
* feat(qwik-nx): storybook generator * refactor: cleanup storybook config * test(qwik-nx): tests for storybook
1 parent 9456a9d commit a07d5e8

27 files changed

+693
-15
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import {
2+
checkFilesExist,
3+
ensureNxProject,
4+
runNxCommandAsync,
5+
uniq,
6+
} from '@nrwl/nx-plugin/testing';
7+
8+
import {
9+
runCommandUntil,
10+
promisifiedTreeKill,
11+
killPort,
12+
killPorts,
13+
} from '@qwikifiers/e2e/utils';
14+
15+
const STORYBOOK_PORT = 4400;
16+
17+
describe('qwikNxVite plugin e2e', () => {
18+
beforeAll(async () => {
19+
await killPorts(STORYBOOK_PORT);
20+
ensureNxProject('qwik-nx', 'dist/packages/qwik-nx');
21+
}, 10000);
22+
23+
afterAll(async () => {
24+
// `nx reset` kills the daemon, and performs
25+
// some work which can help clean up e2e leftovers
26+
await runNxCommandAsync('reset');
27+
});
28+
29+
describe('should be able to import components from libraries', () => {
30+
const appProject = uniq('qwik-nx');
31+
const libProject = uniq('qwik-nx');
32+
const secondLibProject = uniq('qwik-nx');
33+
beforeAll(async () => {
34+
await runNxCommandAsync(
35+
`generate qwik-nx:app ${appProject} --e2eTestRunner=none --no-interactive`
36+
);
37+
await runNxCommandAsync(
38+
`generate qwik-nx:library ${libProject} --no-interactive`
39+
);
40+
await runNxCommandAsync(
41+
`generate qwik-nx:storybook-configuration ${appProject} --no-interactive`
42+
);
43+
await runNxCommandAsync(
44+
`generate qwik-nx:storybook-configuration ${libProject} --no-interactive`
45+
);
46+
}, 200000);
47+
48+
describe('Applying storybook for existing application', () => {
49+
checkStorybookIsBuiltAndServed(appProject, 'apps', false);
50+
});
51+
describe('Applying storybook for existing library', () => {
52+
checkStorybookIsBuiltAndServed(libProject, 'libs', false);
53+
});
54+
55+
describe('Generating a new library with storybook configuration', () => {
56+
beforeAll(async () => {
57+
await runNxCommandAsync(
58+
`generate qwik-nx:library ${secondLibProject} --storybookConfiguration=true --no-interactive`
59+
);
60+
}, 200000);
61+
checkStorybookIsBuiltAndServed(secondLibProject, 'libs', true);
62+
});
63+
});
64+
});
65+
66+
function checkStorybookIsBuiltAndServed(
67+
projectName: string,
68+
type: 'apps' | 'libs',
69+
hasTsStories: boolean
70+
) {
71+
it(`should be able to build storybook for the "${projectName}"`, async () => {
72+
const result = await runNxCommandAsync(`build-storybook ${projectName}`);
73+
expect(result.stdout).toContain(
74+
`Successfully ran target build-storybook for project ${projectName}`
75+
);
76+
expect(() =>
77+
checkFilesExist(`dist/storybook/${projectName}/index.html`)
78+
).not.toThrow();
79+
}, 200000);
80+
81+
it(`should serve storybook for the "${projectName}"`, async () => {
82+
let resultOutput: string | undefined;
83+
const p = await runCommandUntil(
84+
`run ${projectName}:storybook`,
85+
(output) => {
86+
if (
87+
output.includes('Local:') &&
88+
output.includes(`:${STORYBOOK_PORT}`)
89+
) {
90+
resultOutput = output;
91+
return true;
92+
}
93+
return false;
94+
}
95+
);
96+
97+
// it is expected that projects won't have stories by default and storybook should recognize it.
98+
expect(resultOutput).toContain(
99+
`No story files found for the specified pattern: ${type}/${projectName}/**/*.stories.mdx`
100+
);
101+
if (!hasTsStories) {
102+
expect(resultOutput).toContain(
103+
`No story files found for the specified pattern: ${type}/${projectName}/**/*.stories.@(js|jsx|ts|tsx)`
104+
);
105+
}
106+
try {
107+
await promisifiedTreeKill(p.pid!, 'SIGKILL');
108+
await killPort(STORYBOOK_PORT);
109+
} catch {
110+
// ignore
111+
}
112+
}, 200000);
113+
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"@nrwl/js": "15.8.2",
2727
"@nrwl/linter": "15.8.2",
2828
"@nrwl/nx-plugin": "15.8.2",
29+
"@nrwl/storybook": "15.8.2",
2930
"@nrwl/vite": "15.8.2",
3031
"@nrwl/workspace": "15.8.2",
3132
"@nxkit/playwright": "^2.1.2",

packages/qwik-nx/generators.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@
6464
"factory": "./src/generators/remote/generator",
6565
"schema": "./src/generators/remote/schema.json",
6666
"description": "Generate a remote Qwik application for the micro frontend setup"
67+
},
68+
"storybook-configuration": {
69+
"factory": "./src/generators/storybook-configuration/generator",
70+
"schema": "./src/generators/storybook-configuration/schema.json",
71+
"description": "Adds Storybook configuration to a project."
6772
}
6873
}
6974
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Canvas, Story } from '@storybook/addon-docs';
2+
import { <%- className %> } from './<%- fileName %>';
3+
4+
# <%- className %> Component
5+
6+
## Purpose
7+
8+
{/* Why the component is needed */}
9+
10+
## Example
11+
12+
{/* Common copy/paste example that people can throw into their templates and ts */}
13+
14+
~~~tsx
15+
<<%- className %> param="value" />
16+
~~~
17+
18+
## Use case examples
19+
20+
{/* Examples based on use cases */}
21+
22+
### Primary
23+
24+
<Canvas>
25+
<Story id="<%- className.toLowerCase() %>--primary" />
26+
</Canvas>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { Meta } from 'storybook-framework-qwik';
2+
import { <%- className %> } from './<%- fileName %>';
3+
import doc from './<%- fileName %>.doc.mdx';
4+
5+
export default {
6+
title: '<%- className %>',
7+
tags: ['autodocs'],
8+
parameters: {
9+
docs: {
10+
page: doc,
11+
},
12+
},
13+
argTypes: {
14+
// put component params here
15+
},
16+
render(args) {
17+
return <<%- className %> {...args}/>;
18+
},
19+
} as Meta;
20+
21+
export const Primary = {};

packages/qwik-nx/src/generators/component/generator.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
Tree,
1010
} from '@nrwl/devkit';
1111
import { addStyledModuleDependencies } from '../../utils/add-styled-dependencies';
12+
import { ensureMdxTypeInTsConfig } from '../../utils/ensure-file-utils';
1213
import { ComponentGeneratorSchema } from './schema';
1314

1415
interface NormalizedSchema extends ComponentGeneratorSchema {
@@ -95,6 +96,15 @@ function createComponentFiles(tree: Tree, options: NormalizedSchema) {
9596
templateOptions
9697
);
9798
}
99+
if (options.generateStories) {
100+
generateFiles(
101+
tree,
102+
joinPathFragments(__dirname, 'files/storybook'),
103+
componentDir,
104+
templateOptions
105+
);
106+
ensureMdxTypeInTsConfig(tree, options.project);
107+
}
98108
}
99109

100110
export async function componentGenerator(

packages/qwik-nx/src/generators/component/schema.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ export interface ComponentGeneratorSchema {
55
style?: 'none' | 'css' | 'scss' | 'styl' | 'less';
66
skipTests?: boolean;
77
flat?: boolean;
8+
generateStories?: boolean;
89
}

packages/qwik-nx/src/generators/component/schema.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@
6868
"type": "boolean",
6969
"description": "Create component at the source root rather than its own directory.",
7070
"default": false
71+
},
72+
"generateStories": {
73+
"description": "Create Storybook stories for the component",
74+
"type": "boolean"
7175
}
7276
},
7377
"required": ["name", "project"]

packages/qwik-nx/src/generators/e2e-project/generator.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ describe('e2e project', () => {
2020
.spyOn(getInstalledNxVersionModule, 'getInstalledNxVersion')
2121
.mockReturnValue('15.6.0');
2222

23-
beforeEach(() => {
23+
beforeEach(async () => {
2424
appTree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
25-
appGenerator(appTree, {
25+
await appGenerator(appTree, {
2626
name: 'myapp',
2727
e2eTestRunner: 'none',
2828
});

packages/qwik-nx/src/generators/integrations/cloudflare-pages-integration/generator.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ describe('cloudflare-pages-integration generator', () => {
1818
project: projectName,
1919
};
2020

21-
beforeEach(() => {
21+
beforeEach(async () => {
2222
appTree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
2323

24-
applicationGenerator(appTree, {
24+
await applicationGenerator(appTree, {
2525
name: projectName,
2626
e2eTestRunner: 'none',
2727
linter: Linter.None,

0 commit comments

Comments
 (0)