diff --git a/dashboard/README.md b/dashboard/README.md index ef3fbb7d9..1b423fcb3 100644 --- a/dashboard/README.md +++ b/dashboard/README.md @@ -1,60 +1,193 @@ +# ๐Ÿš€ Komiser Dashboard + Komiser dashboard is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). -Full frontend stack: `Next.js`, `Typescript`, `Tailwind`, `Storybook`, `Jest` & `React Testing Library.` +**Full frontend stack:** + +- ๐Ÿ–ฅ [`Next.js`](https://nextjs.org/) +- ๐Ÿ“œ [`Typescript`](https://www.typescriptlang.org/) +- ๐ŸŽจ [`Tailwind`](https://tailwindcss.com/) +- ๐Ÿ“– [`Storybook`](https://storybook.js.org/) +- ๐Ÿงช [`Jest`](https://jestjs.io/) +- ๐Ÿ“š [`React Testing Library`](https://testing-library.com/docs/react-testing-library/intro) -## Getting Started +## ๐Ÿš€ Getting Started Follow the [Contribution Guide](https://github.com/tailwarden/komiser/blob/develop/CONTRIBUTING.md#contributing-to-komiser-dashboard-ui) first if you haven't done so already. Then come back here and follow the next steps: -1. Run the development server: +#### 1. Run the development server: -```bash -# From the Komiser root folder start the Komiser server, run: -go run *.go start --config /path/to/config.toml +From the Komiser root folder start the Komiser server by running: + +```shell +go run \*.go start --config /path/to/config.toml +``` + +In a different terminal tab navigate to the `/dashboard` folder: + +```shell +cd dashboard +``` + +and run: + +```shell +npm install -# In a different terminal tab in the dashboard folder, run: NEXT_PUBLIC_API_URL=http://localhost:3000 npm run dev +``` + +Alternatively, you can create an .env file with it, either manually or by running: -# Alternatively, you can create an .env file with it: -NEXT_PUBLIC_API_URL=http://localhost:3000 +```shell +echo "NEXT_PUBLIC_API_URL=http://localhost:3000" > .env ``` -2. Open [http://localhost:3002/](http://localhost:3002). If you see the dashboard, congrats! It's all up and running correctly. - image +and simply run: -> If you get an error page such as this, please refer to the logs and our [docs](https://docs.komiser.io/docs/introduction/getting-started). -> image +```shell +npm run dev +``` + +#### 2. Open [http://localhost:3002/](http://localhost:3002). If you see the dashboard, ๐ŸŽ‰ congrats! It's all up and running correctly. -## Components +โ— If you get an error page such as this, please refer to the logs and our [docs](https://docs.komiser.io/docs/introduction/getting-started). +Error Image + +## ๐Ÿงฉ Components Komiser components are documented under `/components` +> ๐Ÿ’ก **Hint:** +> We have the following import aliases defined in `tsconfig.json` +> +> ```json +> { +> "@components/": "/dashboard/components/", +> "@services/": "/dashboard/services/", +> "@environments/": "/dashboard/environments/", +> "@utils/": "/dashboard/utils/", +> "@styles/": "/dashboard/styles/" +> } +> ``` + You can find all the shared Components also inside [Storybook](https://storybook.komiser.io/). If you're implementing a new Story, please check for existing or new components with Storybook. We will require a story for new shared components like icons, inputs or similar. -Component convention: +**Component convention:** -- Component folder: component name in `kebab-case` -- Component file: component name in `UpperCamelCase.*` -- Component story: component name in `UpperCamelCase.stories.*` -- Component story mock (if needed): component name in `UpperCamelCase.mocks.*` -- Component unit test: component name in `UpperCamelCase.test.*` -- Check `Card` example for more details: +- ๐Ÿ“ Component folder: component name in `kebab-case` +- ๐Ÿ“„ Component file: component name in `UpperCamelCase.*` +- ๐Ÿ“– Component story: component name in `UpperCamelCase.stories.*` +- ๐ŸŽญ Component story mock (if needed): component name in `UpperCamelCase.mocks.*` +- ๐Ÿงช Component unit test: component name in `UpperCamelCase.test.*` +- ๐Ÿง Check `Card` example for more details: -image + Component Example -Additional instructions: +**Additional instructions:** + +- ๐Ÿ“– To view this component on Storybook, run: `npm run storybook`, then pick `Card` + + Storybook Image + +- ๐Ÿงช To run the unit tests, run: `npm run test:watch`, hit `p`, then `card` + + Unit Test Image + +## ๐Ÿงช Testing + +We use Jest & React Testing Library for our unit tests. + +- To run the unit tests, run: `npm run test` + +**Testing convention:** + +- โœ… All new Utils need to be tested. Existing ones when being changed +- โœ… All tests should be wrapped in a `describe` +- โœ… If it's a unit test for a function: `describe('[replace with function name]', () => { ... })` +- โœ… If it's a unit test for a util: `describe('[replace with util name] util', () => { ... })` +- โœ… If it's a unit test for a component: `describe('[replace with component name]', () => { ... })` +- โœ… A test should use 'it' for the test function: `it('should do something', () => { ... })` + +**Testing examples:** + +- Simple Jest unit test example (snippet from `/utils/formatNumber.test.ts`): + +```typescript +import formatNumber from './formatNumber'; + +describe('formatNumber util', () => { + it('should format number (over a thousand) in short notation', () => { + const result = formatNumber(12345); + expect(result).toBe('12K'); + }); + ... +}); +``` + +- Jest & Testing library example (snippet from `/components/card/Card.test.tsx`): + +```typescript +import { render, screen } from '@testing-library/react'; +import RefreshIcon from '../icons/RefreshIcon'; +import Card from './Card'; + +describe('Card', () => { + it('should render card component without crashing', () => { + render( + } + /> + ); + }); + + it('should display the value formatted', () => { + render( + } + /> + ); + const formattedNumber = screen.getByTestId('formattedNumber'); + expect(formattedNumber).toHaveTextContent('5K'); + }); + ... +}); +``` -- To view this component on Storybook locally, run: `npm run storybook`, then pick `Card` - image +If you're looking for an example with event firing and state updates, have a look at `components/select-checkbox/SelectCheckbox.test.tsx`: -- To run the unit tests, run: `npm run test:watch`, hit `p`, then `card` - image +```typescript +it('opens the dropdown when clicked', () => { + const { getByRole, getByText } = render( + {}} + /> + ); + + fireEvent.click(getByRole('button')); + + expect(getByText('Item 1')).toBeInTheDocument(); + expect(getByText('Item 2')).toBeInTheDocument(); + expect(getByText('Item 3')).toBeInTheDocument(); +}); +``` -## Adding to Storybook +## ๐ŸŽจ Adding to Storybook [**Storybook**](https://storybook.komiser.io/) is a tool for UI development. It makes development faster by isolating components. This allows you to work on one component at a time. If you create a new shared component or want to visualize variations of an existing one, follow these steps: +- To view this component on Storybook locally, run: `npm run storybook`, then pick an example (`Card`) or your new component story + + image + ### 1. **Create the Story**: In the same directory as your component, create a Storybook story: @@ -129,82 +262,18 @@ export default { --- -Remember: Storybook is not just a tool but also a way to document components. Ensure you provide meaningful names, descriptions, and use cases to help other developers understand the use and purpose of each component. - -## Testing - -We use Jest & React Testing Library for our unit tests. - -Testing convention: - -- All tests should be wrapped in a `describe` -- If it's a unit test for a function: `describe('functionName outputs', () => { ... })` -- If it's a unit test for a component: `describe('Component Name', () => { ... })` -- A test should use 'it' for the test function: `it('should do something', () => { ... })` - -Testing examples: - -- Simple Jest unit test example (snippet from `/utils/formatNumber.test.ts`): - -```typescript -import formatNumber from './formatNumber'; - -describe('formatNumber outputs', () => { - it('should format number (over a thousand) in short notation', () => { - const result = formatNumber(12345); - expect(result).toBe('12K'); - }); - - ... - -}); -``` - -- Jest & Testing library example (snippet from `/components/card/Card.test.tsx`): - -```typescript -import { render, screen } from '@testing-library/react'; -import RefreshIcon from '../icons/RefreshIcon'; -import Card from './Card'; - -describe('Card', () => { - it('should render card component without crashing', () => { - render( - } - /> - ); - }); - - it('should display the value formatted', () => { - render( - } - /> - ); - const formattedNumber = screen.getByTestId('formattedNumber'); - expect(formattedNumber).toHaveTextContent('5K'); - }); - - ... - -}); -``` +> Remember: Storybook is not just a tool but also a way to document components. Ensure you provide meaningful names, descriptions, and use cases to help other developers understand the use and purpose of each component. -## Contributing +## ๐Ÿค Contributing We welcome all contributors to join us on the mission of improving Komiser, especially when it comes to writing tests and adding documentation. Not sure where to start? -- Read the [contributor guidelines](https://docs.komiser.io/docs/introduction/community) -- [Join our Discord](https://discord.tailwarden.com/) and hang with us on #contributors channel. +- ๐Ÿ“– Read the [contributor guidelines](https://docs.komiser.io/docs/introduction/community) +- ๐Ÿ’ฌ [Join our Discord](https://discord.tailwarden.com/) and hang with us on #contributors channel. -## Learn More +## ๐Ÿ“š Learn More To learn more about our stack, take a look at the following resources: @@ -215,6 +284,6 @@ To learn more about our stack, take a look at the following resources: - [Jest documentation](https://jestjs.io/docs/getting-started) - [React testing library documentation](https://testing-library.com/docs/dom-testing-library/intro) -## Walkthrough video +## ๐ŸŽฅ Walkthrough video [![Watch the video](https://komiser-assets-cdn.s3.eu-central-1.amazonaws.com/images/dashboard-contrib-video-thumb.png)](https://www.youtube.com/watch?v=uwxj11-eRt8) diff --git a/dashboard/tsconfig.json b/dashboard/tsconfig.json index a5e48f892..15d7d31d3 100644 --- a/dashboard/tsconfig.json +++ b/dashboard/tsconfig.json @@ -21,7 +21,12 @@ "@environments/*": ["environments/*"], "@utils/*": ["utils/*"], "@styles/*": ["styles/*"] - } + }, + "plugins": [ + { + "name": "next" + } + ] }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], "exclude": ["node_modules"] diff --git a/dashboard/utils/formatNumber.test.ts b/dashboard/utils/formatNumber.test.ts index 33bd449f6..ff7add422 100644 --- a/dashboard/utils/formatNumber.test.ts +++ b/dashboard/utils/formatNumber.test.ts @@ -1,6 +1,6 @@ import formatNumber from './formatNumber'; -describe('formatNumber outputs', () => { +describe('formatNumber util', () => { it('should format number (over a thousand) in short notation', () => { const result = formatNumber(12345); expect(result).toBe('12K'); diff --git a/dashboard/utils/regex.test.ts b/dashboard/utils/regex.test.ts index c6b279f1a..8466d9f6a 100644 --- a/dashboard/utils/regex.test.ts +++ b/dashboard/utils/regex.test.ts @@ -1,6 +1,6 @@ import regex, { required } from './regex'; -describe('regex outputs', () => { +describe('regex util', () => { it('should return the required regex', () => { const result = required; expect(result).toStrictEqual(/./);