Skip to content

Commit

Permalink
chore: setup jest for unit test hooks and component from joi (janhq#3540
Browse files Browse the repository at this point in the history
)

* chore: setup jest for unit test hooks and component from joi

* chore: update gitignore

* chore: exclude jest setup file from tsconfig
  • Loading branch information
urmauur authored Sep 5, 2024
1 parent edf5c77 commit 1ffb7f2
Show file tree
Hide file tree
Showing 16 changed files with 626 additions and 15 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ electron/themes
electron/playwright-report
server/pre-install
package-lock.json

coverage
*.log
core/lib/**

Expand Down
8 changes: 8 additions & 0 deletions joi/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src'],
testMatch: ['**/*.test.*'],
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
testEnvironment: 'jsdom',
}
Empty file added joi/jest.setup.js
Empty file.
16 changes: 13 additions & 3 deletions joi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"bugs": "https://github.com/codecentrum/piksel/issues",
"scripts": {
"dev": "rollup -c -w",
"build": "rimraf ./dist && rollup -c"
"build": "rimraf ./dist && rollup -c",
"test": "jest"
},
"peerDependencies": {
"class-variance-authority": "^0.7.0",
Expand All @@ -38,13 +39,22 @@
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7",
"tailwind-merge": "^2.2.0",
"@types/jest": "^29.5.12",
"autoprefixer": "10.4.16",
"tailwindcss": "^3.4.1"
"jest": "^29.7.0",
"tailwind-merge": "^2.2.0",
"tailwindcss": "^3.4.1",
"ts-jest": "^29.2.5"
},
"devDependencies": {
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-terser": "^0.4.4",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.5.0",
"@testing-library/react": "^16.0.1",
"@types/jest": "^29.5.12",
"jest-environment-jsdom": "^29.7.0",
"jest-transform-css": "^6.0.1",
"prettier": "^3.0.3",
"prettier-plugin-tailwindcss": "^0.5.6",
"rollup": "^4.12.0",
Expand Down
64 changes: 64 additions & 0 deletions joi/src/core/Accordion/Accordion.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React from 'react'
import '@testing-library/jest-dom'
import { render, screen, fireEvent } from '@testing-library/react'
import { Accordion, AccordionItem } from './index'

// Mock the SCSS import
jest.mock('./styles.scss', () => ({}))

describe('Accordion', () => {
it('renders accordion with items', () => {
render(
<Accordion defaultValue={['item1']}>
<AccordionItem value="item1" title="Item 1">
Content 1
</AccordionItem>
<AccordionItem value="item2" title="Item 2">
Content 2
</AccordionItem>
</Accordion>
)

expect(screen.getByText('Item 1')).toBeInTheDocument()
expect(screen.getByText('Item 2')).toBeInTheDocument()
})

it('expands and collapses accordion items', () => {
render(
<Accordion defaultValue={[]}>
<AccordionItem value="item1" title="Item 1">
Content 1
</AccordionItem>
</Accordion>
)

const trigger = screen.getByText('Item 1')

// Initially, content should not be visible
expect(screen.queryByText('Content 1')).not.toBeInTheDocument()

// Click to expand
fireEvent.click(trigger)
expect(screen.getByText('Content 1')).toBeInTheDocument()

// Click to collapse
fireEvent.click(trigger)
expect(screen.queryByText('Content 1')).not.toBeInTheDocument()
})

it('respects defaultValue prop', () => {
render(
<Accordion defaultValue={['item2']}>
<AccordionItem value="item1" title="Item 1">
Content 1
</AccordionItem>
<AccordionItem value="item2" title="Item 2">
Content 2
</AccordionItem>
</Accordion>
)

expect(screen.queryByText('Content 1')).not.toBeInTheDocument()
expect(screen.getByText('Content 2')).toBeInTheDocument()
})
})
83 changes: 83 additions & 0 deletions joi/src/core/Badge/Badge.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React from 'react'
import { render, screen } from '@testing-library/react'
import '@testing-library/jest-dom'
import { Badge, badgeConfig } from './index'

// Mock the styles
jest.mock('./styles.scss', () => ({}))

describe('@joi/core/Badge', () => {
it('renders with default props', () => {
render(<Badge>Test Badge</Badge>)
const badge = screen.getByText('Test Badge')
expect(badge).toBeInTheDocument()
expect(badge).toHaveClass('badge')
expect(badge).toHaveClass('badge--primary')
expect(badge).toHaveClass('badge--medium')
expect(badge).toHaveClass('badge--solid')
})

it('applies custom className', () => {
render(<Badge className="custom-class">Test Badge</Badge>)
const badge = screen.getByText('Test Badge')
expect(badge).toHaveClass('custom-class')
})

it('renders with different themes', () => {
const themes = Object.keys(badgeConfig.variants.theme)
themes.forEach((theme) => {
render(<Badge theme={theme as any}>Test Badge {theme}</Badge>)
const badge = screen.getByText(`Test Badge ${theme}`)
expect(badge).toHaveClass(`badge--${theme}`)
})
})

it('renders with different variants', () => {
const variants = Object.keys(badgeConfig.variants.variant)
variants.forEach((variant) => {
render(<Badge variant={variant as any}>Test Badge {variant}</Badge>)
const badge = screen.getByText(`Test Badge ${variant}`)
expect(badge).toHaveClass(`badge--${variant}`)
})
})

it('renders with different sizes', () => {
const sizes = Object.keys(badgeConfig.variants.size)
sizes.forEach((size) => {
render(<Badge size={size as any}>Test Badge {size}</Badge>)
const badge = screen.getByText(`Test Badge ${size}`)
expect(badge).toHaveClass(`badge--${size}`)
})
})

it('fails when a new theme is added without updating the test', () => {
const expectedThemes = [
'primary',
'secondary',
'warning',
'success',
'info',
'destructive',
]
const actualThemes = Object.keys(badgeConfig.variants.theme)
expect(actualThemes).toEqual(expectedThemes)
})

it('fails when a new variant is added without updating the test', () => {
const expectedVariant = ['solid', 'soft', 'outline']
const actualVariants = Object.keys(badgeConfig.variants.variant)
expect(actualVariants).toEqual(expectedVariant)
})

it('fails when a new size is added without updating the test', () => {
const expectedSizes = ['small', 'medium', 'large']
const actualSizes = Object.keys(badgeConfig.variants.size)
expect(actualSizes).toEqual(expectedSizes)
})

it('fails when a new variant CVA is added without updating the test', () => {
const expectedVariantsCVA = ['theme', 'variant', 'size']
const actualVariant = Object.keys(badgeConfig.variants)
expect(actualVariant).toEqual(expectedVariantsCVA)
})
})
12 changes: 7 additions & 5 deletions joi/src/core/Badge/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { twMerge } from 'tailwind-merge'

import './styles.scss'

const badgeVariants = cva('badge', {
export const badgeConfig = {
variants: {
theme: {
primary: 'badge--primary',
Expand All @@ -28,11 +28,13 @@ const badgeVariants = cva('badge', {
},
},
defaultVariants: {
theme: 'primary',
size: 'medium',
variant: 'solid',
theme: 'primary' as const,
size: 'medium' as const,
variant: 'solid' as const,
},
})
}

const badgeVariants = cva('badge', badgeConfig)

export interface BadgeProps
extends HTMLAttributes<HTMLDivElement>,
Expand Down
68 changes: 68 additions & 0 deletions joi/src/core/Button/Button.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React from 'react'
import { render, screen } from '@testing-library/react'
import '@testing-library/jest-dom'
import { Button, buttonConfig } from './index'

// Mock the styles
jest.mock('./styles.scss', () => ({}))

describe('Button', () => {
it('renders with default props', () => {
render(<Button>Click me</Button>)
const button = screen.getByRole('button', { name: /click me/i })
expect(button).toBeInTheDocument()
expect(button).toHaveClass('btn btn--primary btn--medium btn--solid')
})

it('renders as a child component when asChild is true', () => {
render(
<Button asChild>
<a href="/">Link Button</a>
</Button>
)
const link = screen.getByRole('link', { name: /link button/i })
expect(link).toBeInTheDocument()
expect(link).toHaveClass('btn btn--primary btn--medium btn--solid')
})

it.each(Object.keys(buttonConfig.variants.theme))(
'renders with theme %s',
(theme) => {
render(<Button theme={theme as any}>Theme Button</Button>)
const button = screen.getByRole('button', { name: /theme button/i })
expect(button).toHaveClass(`btn btn--${theme}`)
}
)

it.each(Object.keys(buttonConfig.variants.variant))(
'renders with variant %s',
(variant) => {
render(<Button variant={variant as any}>Variant Button</Button>)
const button = screen.getByRole('button', { name: /variant button/i })
expect(button).toHaveClass(`btn btn--${variant}`)
}
)

it.each(Object.keys(buttonConfig.variants.size))(
'renders with size %s',
(size) => {
render(<Button size={size as any}>Size Button</Button>)
const button = screen.getByRole('button', { name: /size button/i })
expect(button).toHaveClass(`btn btn--${size}`)
}
)

it('renders with block prop', () => {
render(<Button block>Block Button</Button>)
const button = screen.getByRole('button', { name: /block button/i })
expect(button).toHaveClass('btn btn--block')
})

it('merges custom className with generated classes', () => {
render(<Button className="custom-class">Custom Class Button</Button>)
const button = screen.getByRole('button', { name: /custom class button/i })
expect(button).toHaveClass(
'btn btn--primary btn--medium btn--solid custom-class'
)
})
})
13 changes: 7 additions & 6 deletions joi/src/core/Button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { twMerge } from 'tailwind-merge'

import './styles.scss'

const buttonVariants = cva('btn', {
export const buttonConfig = {
variants: {
theme: {
primary: 'btn--primary',
Expand All @@ -30,12 +30,13 @@ const buttonVariants = cva('btn', {
},
},
defaultVariants: {
theme: 'primary',
size: 'medium',
variant: 'solid',
block: false,
theme: 'primary' as const,
size: 'medium' as const,
variant: 'solid' as const,
block: false as const,
},
})
}
const buttonVariants = cva('btn', buttonConfig)

export interface ButtonProps
extends ButtonHTMLAttributes<HTMLButtonElement>,
Expand Down
55 changes: 55 additions & 0 deletions joi/src/hooks/useClickOutside/useClickOutside.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from 'react'
import { render, fireEvent, act } from '@testing-library/react'
import { useClickOutside } from './index'

// Mock component to test the hook
const TestComponent: React.FC<{ onClickOutside: () => void }> = ({
onClickOutside,
}) => {
const ref = useClickOutside(onClickOutside)
return <div ref={ref as React.RefObject<HTMLDivElement>}>Test</div>
}

describe('@joi/hooks/useClickOutside', () => {
it('should call handler when clicking outside', () => {
const handleClickOutside = jest.fn()
const { container } = render(
<TestComponent onClickOutside={handleClickOutside} />
)

act(() => {
fireEvent.mouseDown(document.body)
})

expect(handleClickOutside).toHaveBeenCalledTimes(1)
})

it('should not call handler when clicking inside', () => {
const handleClickOutside = jest.fn()
const { getByText } = render(
<TestComponent onClickOutside={handleClickOutside} />
)

act(() => {
fireEvent.mouseDown(getByText('Test'))
})

expect(handleClickOutside).not.toHaveBeenCalled()
})

it('should work with custom events', () => {
const handleClickOutside = jest.fn()
const TestComponentWithCustomEvent: React.FC = () => {
const ref = useClickOutside(handleClickOutside, ['click'])
return <div ref={ref as React.RefObject<HTMLDivElement>}>Test</div>
}

render(<TestComponentWithCustomEvent />)

act(() => {
fireEvent.click(document.body)
})

expect(handleClickOutside).toHaveBeenCalledTimes(1)
})
})
Loading

0 comments on commit 1ffb7f2

Please sign in to comment.