Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP - storybook #2705

Draft
wants to merge 27 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0cf8923
stash
alishakawaguchi Sep 14, 2024
3795fff
working with tailwind
alishakawaguchi Sep 14, 2024
4be7c8d
add story book play tests
alishakawaguchi Sep 16, 2024
1c17bea
add storybook gh action
alishakawaguchi Sep 16, 2024
e332a40
clean up
alishakawaguchi Sep 16, 2024
b431d85
prettier
alishakawaguchi Sep 16, 2024
2102cff
fix storybook gh action directory
alishakawaguchi Sep 16, 2024
c869053
fix storybook gh action directory
alishakawaguchi Sep 16, 2024
7725a11
fix storybook gh action directory
alishakawaguchi Sep 16, 2024
201f79f
fix storybook gh action
alishakawaguchi Sep 16, 2024
d986427
fix storybook gh action
alishakawaguchi Sep 16, 2024
949dca9
fix storybook gh action
alishakawaguchi Sep 16, 2024
4e0bdd2
test storybook gh action
alishakawaguchi Sep 16, 2024
608c631
add storybook gh action
alishakawaguchi Sep 16, 2024
2946852
fix storybook gh action
alishakawaguchi Sep 16, 2024
e02b169
fix storybook gh action
alishakawaguchi Sep 16, 2024
ec9d7a7
add readme
alishakawaguchi Sep 16, 2024
6f69883
remove webpack config
alishakawaguchi Sep 16, 2024
290b95d
fix webpack
alishakawaguchi Sep 16, 2024
7de1483
gh action test
alishakawaguchi Sep 17, 2024
2b75fb1
gh action test
alishakawaguchi Sep 17, 2024
858ec09
gh action test
alishakawaguchi Sep 17, 2024
aa1924a
jest aliasing broken
alishakawaguchi Sep 17, 2024
11bad8a
closer
alishakawaguchi Sep 17, 2024
40e96f0
meh
alishakawaguchi Sep 19, 2024
c5d1cbe
Merge branch 'main' into alisha/storybook-2
alishakawaguchi Nov 13, 2024
cfdd2ac
remove tsconfig path alias
alishakawaguchi Nov 13, 2024
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
27 changes: 26 additions & 1 deletion .github/workflows/frontend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,32 @@ jobs:
env:
NODE_ENV: production

storybook:
name: Storybook Tests
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend/apps/web
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: lts/*
cache: npm
cache-dependency-path: ./frontend/package-lock.json
- run: npm install
- name: Install Playwright
run: npx playwright install --with-deps
- name: Build Storybook
run: npm run build-storybook --quiet
- name: Serve Storybook and run tests
run: |
npx concurrently -k -s first -n "SB,TEST" -c "magenta,blue" \
"npx http-server storybook-static --port 6006 --silent" \
"npx wait-on tcp:127.0.0.1:6006 && npm run test-storybook"
test:
name: Test
runs-on: ubuntu-latest
Expand All @@ -84,7 +110,6 @@ jobs:
node-version: lts/*
cache: npm
cache-dependency-path: ./frontend/package-lock.json

- run: npm ci
- name: Test
run: npm run test
15 changes: 15 additions & 0 deletions frontend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## Running Storybook Locally

To start Storybook and view the components locally:

Using npm:

```bash
cd apps/web && npm run storybook
```

To run all storybook tests. Make sure storybook is already running locally.

```bash
cd apps/web && npm run test-storybook
```
2 changes: 2 additions & 0 deletions frontend/apps/web/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,5 @@ charts/**/*.tgz
.env.development
.env.staging
.env.production

*storybook.log
31 changes: 31 additions & 0 deletions frontend/apps/web/.storybook/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { StorybookConfig } from '@storybook/nextjs';

import { dirname, join } from 'path';

/**
* This function is used to resolve the absolute path of a package.
* It is needed in projects that use Yarn PnP or are set up within a monorepo.
*/
function getAbsolutePath(value: string): any {
return dirname(require.resolve(join(value, 'package.json')));
}
const config: StorybookConfig = {
stories: [
// '../stories/**/*.mdx',
// '../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)',
'../components/**/*.stories.@(js|jsx|mjs|ts|tsx)',
],
addons: [
getAbsolutePath('@storybook/addon-onboarding'),
getAbsolutePath('@storybook/addon-links'),
getAbsolutePath('@storybook/addon-essentials'),
getAbsolutePath('@chromatic-com/storybook'),
getAbsolutePath('@storybook/addon-interactions'),
getAbsolutePath('@storybook/addon-themes'),
],
framework: {
name: getAbsolutePath('@storybook/nextjs'),
options: {},
},
};
export default config;
27 changes: 27 additions & 0 deletions frontend/apps/web/.storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { withThemeByClassName } from '@storybook/addon-themes';
import type { Preview } from '@storybook/react';

import '../app/globals.css';

export const decorators = [
withThemeByClassName({
themes: {
light: 'light',
dark: 'dark',
},
defaultTheme: 'light',
}),
];

const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
};

export default preview;
47 changes: 47 additions & 0 deletions frontend/apps/web/components/PasswordComponent.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type { Meta, StoryObj } from '@storybook/react';
import { expect, userEvent, within } from '@storybook/test';

import { PasswordInput } from './PasswordComponent';

const meta: Meta<typeof PasswordInput> = {
title: 'Components/PasswordInput',
component: PasswordInput,
};

export default meta;

type Story = StoryObj<typeof PasswordInput>;

export const Default: Story = {
args: {},
};

export const HasPassword: Story = {
args: {
value: 'password',
},
};

export const Disabled: Story = {
args: {
disabled: true,
value: 'password',
},
};

export const FullExample: Story = {
args: {},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.type(
canvas.getByLabelText('password', { selector: 'input' }),
'topsecret'
);
await expect(canvas.getByDisplayValue('topsecret')).toBeInTheDocument();

await userEvent.click(canvas.getByRole('button'));
await expect(canvas.getByRole('textbox')).toBeInTheDocument();
await userEvent.click(canvas.getByRole('button'));
await expect(canvas.queryByRole('textbox')).not.toBeInTheDocument();
},
};
29 changes: 29 additions & 0 deletions frontend/apps/web/components/PasswordComponent.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* @jest-environment jsdom
*/

import '@testing-library/jest-dom';
import { fireEvent, screen } from '@testing-library/react';

import { composeStories } from '@storybook/nextjs';

import * as stories from './PasswordComponent.stories'; // 👈 Our stories imported here.

const story = composeStories(stories);
test('Checks if the form is valid', async () => {
// Renders the composed story
await story.Default.run();

const passwordInput = screen.getByLabelText('password', {
selector: 'input',
});

fireEvent.change(passwordInput, {
target: { value: 'topsecret' },
});

fireEvent.click(screen.getByRole('button'));
expect(screen.getByRole('textbox')).toBeInTheDocument();
fireEvent.click(screen.getByRole('button'));
expect(screen.queryByRole('textbox')).not.toBeInTheDocument();
});
9 changes: 4 additions & 5 deletions frontend/apps/web/components/PasswordComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,12 @@ import { Input, InputProps } from './ui/input';
export const PasswordInput = React.forwardRef<HTMLInputElement, InputProps>(
({ className, ...props }, ref) => {
const [showPassword, setShowPassword] = useState<boolean>(false);
const disabled =
props.value === '' || props.value === undefined || props.disabled;

return (
<div className="relative">
<Input
type={showPassword ? 'text' : 'password'}
aria-label="password"
aria-labelledby="password"
className={cn('hide-password-toggle pr-10', className)}
ref={ref}
{...props}
Expand All @@ -26,9 +25,9 @@ export const PasswordInput = React.forwardRef<HTMLInputElement, InputProps>(
size="sm"
className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
onClick={() => setShowPassword((prev) => !prev)}
disabled={disabled}
disabled={props.disabled}
>
{showPassword && !disabled ? (
{showPassword && !props.disabled ? (
<EyeOpenIcon className="h-4 w-4" aria-hidden="true" />
) : (
<EyeNoneIcon className="h-4 w-4" aria-hidden="true" />
Expand Down
60 changes: 60 additions & 0 deletions frontend/apps/web/components/headers/PageHeader.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { LightningBoltIcon } from '@radix-ui/react-icons';
import { action } from '@storybook/addon-actions';
import { Meta, StoryObj } from '@storybook/react';
import { expect, userEvent, within } from '@storybook/test';
import ButtonText from '../ButtonText';
import { Badge } from '../ui/badge';
import { Button } from '../ui/button';
import PageHeader from './PageHeader';

const meta: Meta<typeof PageHeader> = {
title: 'Components/PageHeader',
component: PageHeader,
};

export default meta;

type Story = StoryObj<typeof PageHeader>;

export const Default: Story = {
args: {
header: 'Default Header',
},
};

export const FullExample: Story = {
args: {
header: 'PageHeader',
leftBadgeValue: 'Important',
extraHeading: (
<div className="md:flex grid grid-cols-2 md:flex-row gap-4">
<Button onClick={action('button-click')}>
<ButtonText leftIcon={<LightningBoltIcon />} text="Button" />
</Button>
</div>
),
leftIcon: <span>📘</span>,
progressSteps: (
<div className="flex gap-2">
<Badge variant="secondary">Step 1</Badge>
<Badge variant="secondary">Step 2</Badge>
<Badge variant="default">Step 3</Badge>
</div>
),
pageHeaderContainerClassName: 'bg-gray-100 p-4',
subHeadings: [
<h3 key="1" className="text-muted-foreground text-sm">
This is a subheading with more details
</h3>,
],
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await expect(
canvas.getByRole('heading', { name: 'PageHeader', level: 1 })
).toBeInTheDocument();
await userEvent.click(canvas.getByRole('button', { name: 'Button' }));
await expect(canvas.getByRole('heading', { level: 3 })).toBeInTheDocument();
await expect(canvas.getByText('Important')).toBeInTheDocument();
},
};
44 changes: 42 additions & 2 deletions frontend/apps/web/jest.config.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,52 @@
import type { Config } from 'jest';
import nextJest from 'next/jest.js';

import { getPackageAliases } from '@storybook/nextjs/export-mocks';
// import { pathsToModuleNameMapper } from 'ts-jest';
// import { compilerOptions } from './tsconfig.json';

import path from 'path';

const createJestConfig = nextJest({
// Provide the path to your Next.js app to load next.config.js and .env files in your test environment
dir: './',
});

// console.log(
// pathsToModuleNameMapper(compilerOptions.paths, {
// prefix: '<rootDir>/',
// })
// );
// console.log(__dirname);

const config: Config = {
preset: 'ts-jest',
testEnvironment: 'node',
// coverageProvider: 'v8',
testEnvironment: 'jsdom',
verbose: true,
moduleNameMapper: {
...getPackageAliases(),
// ...pathsToModuleNameMapper(compilerOptions.paths, {
// prefix: '<rootDir>/',
// }),
'^@/(.*)$': '<rootDir>/$1',
'^@neosync/sdk$': '<rootDir>/../../packages/sdk/$2',
},
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
moduleDirectories: ['node_modules'],
roots: ['<rootDir>', path.join(__dirname, '..', '..', 'packages', 'sdk')],
moduleFileExtensions: [
'js',
'mjs',
'cjs',
'jsx',
'ts',
'tsx',
'json',
'node',
],
modulePathIgnorePatterns: ['<rootDir>/.next/'],
testPathIgnorePatterns: ['<rootDir>/.next/'],
};

export default config;
export default createJestConfig(config);
1 change: 1 addition & 0 deletions frontend/apps/web/jest.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import '@testing-library/jest-dom';
12 changes: 12 additions & 0 deletions frontend/apps/web/libs/util.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/**
* @jest-environment node
*/

import {
AwsS3ConnectionConfig,
Connection,
Expand Down Expand Up @@ -67,6 +71,11 @@ describe('splitConnections', () => {
config: { case: 'mysqlConfig', value: {} as MysqlConnectionConfig },
} as ConnectionConfig,
});
const mssql = new Connection({
connectionConfig: {
config: { case: 'mssqlConfig', value: {} as MssqlConnectionConfig },
} as ConnectionConfig,
});
const s3 = new Connection({
connectionConfig: {
config: { case: 'awsS3Config', value: {} as AwsS3ConnectionConfig },
Expand Down Expand Up @@ -130,6 +139,7 @@ describe('splitConnections', () => {
const connections: Connection[] = [
postgres,
mysql,
mssql,
s3,
openai,
mongodb,
Expand All @@ -140,6 +150,7 @@ describe('splitConnections', () => {
const expected = {
postgres: [postgres],
mysql: [mysql],
mssql: [mssql],
s3: [s3],
openai: [openai],
mongodb: [mongodb],
Expand All @@ -154,6 +165,7 @@ describe('splitConnections', () => {
const expected = {
postgres: [],
mysql: [],
mssql: [],
s3: [],
openai: [],
mongodb: [],
Expand Down
Loading
Loading