Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
101 changes: 101 additions & 0 deletions admin-ui/app/components/Accordion/__tests__/Accordion.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import React from 'react'
import { render, screen, fireEvent } from '@testing-library/react'
import Accordion from '@/components/Accordion'
import AppTestWrapper from 'Routes/Apps/Gluu/Tests/Components/AppTestWrapper'

const Wrapper = ({ children }: { children: React.ReactNode }) => (
<AppTestWrapper>{children}</AppTestWrapper>
)

type BuildProps = {
initialOpen?: boolean
open?: boolean
onToggle?: (isOpen: boolean) => void
}

const buildAccordion = ({ initialOpen, open, onToggle }: BuildProps = {}) => (
<Accordion initialOpen={initialOpen} open={open} onToggle={onToggle}>
<Accordion.Header>
Section Title
<Accordion.Indicator />
</Accordion.Header>
<Accordion.Body>Body Content</Accordion.Body>
</Accordion>
)

describe('Accordion', () => {
it('renders its header and body children', () => {
render(buildAccordion({ initialOpen: true }), { wrapper: Wrapper })

expect(screen.getByText('Section Title')).toBeInTheDocument()
expect(screen.getByText('Body Content')).toBeInTheDocument()
})

it('shows the closed indicator and hides the body when not initially open', () => {
render(buildAccordion(), { wrapper: Wrapper })

expect(screen.getByTestId('AddIcon')).toBeInTheDocument()
expect(screen.queryByTestId('RemoveIcon')).not.toBeInTheDocument()
expect(screen.getByText('Body Content')).not.toBeVisible()
})

it('shows the open indicator and reveals the body when initialOpen is true', () => {
render(buildAccordion({ initialOpen: true }), { wrapper: Wrapper })

expect(screen.getByTestId('RemoveIcon')).toBeInTheDocument()
expect(screen.queryByTestId('AddIcon')).not.toBeInTheDocument()
expect(screen.getByText('Body Content')).toBeVisible()
})

it('toggles open when the header is clicked (uncontrolled)', () => {
render(buildAccordion(), { wrapper: Wrapper })

expect(screen.getByTestId('AddIcon')).toBeInTheDocument()

fireEvent.click(screen.getByText('Section Title'))

expect(screen.getByTestId('RemoveIcon')).toBeInTheDocument()
expect(screen.getByText('Body Content')).toBeVisible()
})

it('toggles closed again on a second header click (uncontrolled)', () => {
render(buildAccordion({ initialOpen: true }), { wrapper: Wrapper })

fireEvent.click(screen.getByText('Section Title'))

// indicator flips back to the closed state
expect(screen.getByTestId('AddIcon')).toBeInTheDocument()
expect(screen.queryByTestId('RemoveIcon')).not.toBeInTheDocument()
})

it('throws when open is provided without onToggle', () => {
const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {})

expect(() => render(buildAccordion({ open: true }), { wrapper: Wrapper })).toThrow(
/props.open has to be used combined with props.onToggle/,
)

consoleError.mockRestore()
})

it('calls onToggle with the next open state when controlled, without changing on its own', () => {
const onToggle = jest.fn()
render(buildAccordion({ open: false, onToggle }), { wrapper: Wrapper })

expect(screen.getByTestId('AddIcon')).toBeInTheDocument()

fireEvent.click(screen.getByText('Section Title'))

expect(onToggle).toHaveBeenCalledTimes(1)
expect(onToggle).toHaveBeenCalledWith(true)
// parent owns state: still closed until the prop changes
expect(screen.getByTestId('AddIcon')).toBeInTheDocument()
})

it('renders the state dictated by the parent when controlled', () => {
render(buildAccordion({ open: true, onToggle: jest.fn() }), { wrapper: Wrapper })

expect(screen.getByTestId('RemoveIcon')).toBeInTheDocument()
expect(screen.getByText('Body Content')).toBeVisible()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React from 'react'
import { render, screen, fireEvent } from '@testing-library/react'
import { CustomInput } from '@/components/CustomInput/CustomInput'
import type { CustomInputProps } from '@/components/CustomInput/types'
import AppTestWrapper from 'Routes/Apps/Gluu/Tests/Components/AppTestWrapper'

const Wrapper = ({ children }: { children: React.ReactNode }) => (
<AppTestWrapper>{children}</AppTestWrapper>
)

const renderInput = (props: CustomInputProps) =>
render(<CustomInput {...props} />, { wrapper: Wrapper })

describe('CustomInput', () => {
it('renders a text input by default', () => {
renderInput({ name: 'firstName', type: 'text' })
const input = screen.getByTestId('firstName') as HTMLInputElement
expect(input.tagName).toBe('INPUT')
expect(input.type).toBe('text')
})

it('displays the provided value', () => {
renderInput({ name: 'firstName', type: 'text', value: 'Alice', onChange: jest.fn() })
const input = screen.getByTestId('firstName') as HTMLInputElement
expect(input.value).toBe('Alice')
})

it('fires onChange when typing in the input', () => {
const onChange = jest.fn()
renderInput({ name: 'firstName', type: 'text', onChange })
const input = screen.getByTestId('firstName') as HTMLInputElement
fireEvent.change(input, { target: { value: 'Bob' } })
expect(onChange).toHaveBeenCalledTimes(1)
})

it('forwards name, placeholder and disabled props to the input', () => {
renderInput({
name: 'email',
type: 'text',
disabled: true,
'data-testid': 'email',
})
const input = screen.getByTestId('email') as HTMLInputElement
expect(input.name).toBe('email')
expect(input.disabled).toBe(true)
})

it('renders a select element when type is select', () => {
renderInput({
name: 'country',
type: 'select',
children: (
<>
<option value="us">United States</option>
<option value="uk">United Kingdom</option>
</>
),
})
const select = screen.getByTestId('country') as HTMLSelectElement
expect(select.tagName).toBe('SELECT')
expect(screen.getByText('United States')).toBeInTheDocument()
expect(screen.getByText('United Kingdom')).toBeInTheDocument()
})

it('fires onChange when selecting an option', () => {
const onChange = jest.fn()
renderInput({
name: 'country',
type: 'select',
onChange,
children: (
<>
<option value="us">United States</option>
<option value="uk">United Kingdom</option>
</>
),
})
const select = screen.getByTestId('country') as HTMLSelectElement
fireEvent.change(select, { target: { value: 'uk' } })
expect(onChange).toHaveBeenCalledTimes(1)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { render, screen } from '@testing-library/react'
import { EmptyLayout } from '@/components/EmptyLayout/EmptyLayout'
import type { EmptyLayoutProps, SectionProps } from '@/components/EmptyLayout/types'
import AppTestWrapper from 'Routes/Apps/Gluu/Tests/Components/AppTestWrapper'

const renderLayout = (props: Omit<EmptyLayoutProps, 'pageConfig'>) =>
render(
<AppTestWrapper>
<EmptyLayout {...props} />
</AppTestWrapper>,
)

const renderSection = (props: SectionProps) =>
render(
<AppTestWrapper>
<EmptyLayout.Section {...props} />
</AppTestWrapper>,
)

describe('EmptyLayout', () => {
it('renders its children', () => {
renderLayout({ children: <span>hello child</span> })
expect(screen.getByText('hello child')).toBeInTheDocument()
})

it('always applies the fullscreen class on the root element', () => {
renderLayout({ children: <span data-testid="content">content</span> })
const root = screen.getByTestId('content').parentElement
expect(root).toHaveClass('fullscreen')
})

it('merges a custom className alongside fullscreen', () => {
renderLayout({ children: <span data-testid="content">content</span>, className: 'extra' })
const root = screen.getByTestId('content').parentElement
expect(root).toHaveClass('fullscreen')
expect(root).toHaveClass('extra')
})

it('exposes a Section sub-layout', () => {
expect(EmptyLayout.Section).toBeDefined()
})

describe('EmptyLayout.Section', () => {
it('renders children and the base section class', () => {
renderSection({ children: <span data-testid="sec">section child</span> })
expect(screen.getByText('section child')).toBeInTheDocument()
const section = screen.getByTestId('sec').parentElement
expect(section).toHaveClass('fullscreen__section')
expect(section).not.toHaveClass('fullscreen__section--center')
})

it('does not wrap children in a centering child when center is not set', () => {
renderSection({ children: <span data-testid="sec">section child</span> })
const section = screen.getByTestId('sec').parentElement
expect(section).toHaveClass('fullscreen__section')
})

it('applies the center modifier and wraps children with a max-width container when center is true', () => {
renderSection({ center: true, children: <span data-testid="sec">centered</span> })
const wrapper = screen.getByTestId('sec').parentElement
expect(wrapper).toHaveClass('fullscreen__section__child')
expect(wrapper).toHaveStyle({ maxWidth: '420px' })
const section = wrapper?.parentElement
expect(section).toHaveClass('fullscreen__section--center')
})

it('uses a string width verbatim as maxWidth', () => {
renderSection({ center: true, width: '50%', children: <span data-testid="sec">x</span> })
const wrapper = screen.getByTestId('sec').parentElement
expect(wrapper).toHaveStyle({ maxWidth: '50%' })
})

it('appends px to a numeric width', () => {
renderSection({ center: true, width: 600, children: <span data-testid="sec">x</span> })
const wrapper = screen.getByTestId('sec').parentElement
expect(wrapper).toHaveStyle({ maxWidth: '600px' })
})

it('merges a custom className onto the section', () => {
renderSection({ className: 'my-section', children: <span data-testid="sec">x</span> })
const section = screen.getByTestId('sec').parentElement
expect(section).toHaveClass('my-section')
expect(section).toHaveClass('fullscreen__section')
})
})
})
88 changes: 88 additions & 0 deletions admin-ui/app/components/GluuBadge/__tests__/GluuBadge.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React from 'react'
import { render, screen, fireEvent } from '@testing-library/react'
import GluuBadge from '@/components/GluuBadge/GluuBadge'
import type { GluuBadgeProps } from '@/components/GluuBadge/types'
import AppTestWrapper from 'Routes/Apps/Gluu/Tests/Components/AppTestWrapper'

const Wrapper = ({ children }: { children: React.ReactNode }) => (
<AppTestWrapper>{children}</AppTestWrapper>
)

const renderBadge = (props: Partial<GluuBadgeProps> = {}) =>
render(<GluuBadge {...props}>{props.children ?? 'Active'}</GluuBadge>, { wrapper: Wrapper })

describe('GluuBadge', () => {
it('renders its content', () => {
renderBadge({ children: 'Enabled' })
expect(screen.getByText('Enabled')).toBeInTheDocument()
})

it('renders distinct labels for distinct states', () => {
const { rerender } = renderBadge({ children: 'Active' })
expect(screen.getByText('Active')).toBeInTheDocument()

// wrapper is preserved by rerender; pass only the updated child element
rerender(<GluuBadge>Inactive</GluuBadge>)
expect(screen.getByText('Inactive')).toBeInTheDocument()
expect(screen.queryByText('Active')).not.toBeInTheDocument()
})

it('is non-interactive by default (no button role, default cursor)', () => {
renderBadge({ children: 'Static' })
const badge = screen.getByText('Static')
expect(badge).not.toHaveAttribute('role', 'button')
expect(badge).not.toHaveAttribute('tabindex')
expect(badge).toHaveStyle({ cursor: 'default' })
})

it('becomes interactive when onClick is provided', () => {
const onClick = jest.fn()
renderBadge({ children: 'Clickable', onClick })
const badge = screen.getByRole('button', { name: 'Clickable' })
expect(badge).toHaveAttribute('tabindex', '0')
expect(badge).toHaveStyle({ cursor: 'pointer' })
fireEvent.click(badge)
expect(onClick).toHaveBeenCalledTimes(1)
})

it('triggers onClick on Enter key when interactive', () => {
const onClick = jest.fn()
renderBadge({ children: 'Keyed', onClick })
fireEvent.keyDown(screen.getByRole('button'), { key: 'Enter' })
expect(onClick).toHaveBeenCalledTimes(1)
})

it('triggers onClick on Space key when interactive', () => {
const onClick = jest.fn()
renderBadge({ children: 'Spaced', onClick })
fireEvent.keyDown(screen.getByRole('button'), { key: ' ' })
expect(onClick).toHaveBeenCalledTimes(1)
})

it('ignores other keys when interactive', () => {
const onClick = jest.fn()
renderBadge({ children: 'Ignored', onClick })
fireEvent.keyDown(screen.getByRole('button'), { key: 'a' })
expect(onClick).not.toHaveBeenCalled()
})

it('renders outlined variant with transparent background and colored text', () => {
renderBadge({ children: 'Outlined', outlined: true, backgroundColor: 'rgb(255, 0, 0)' })
const badge = screen.getByText('Outlined')
// outlined => background transparent, text + border take the badge color
expect(badge.style.backgroundColor).toBe('transparent')
expect(badge).toHaveStyle({ color: 'rgb(255, 0, 0)' })
})

it('applies pill border radius when pill is set', () => {
renderBadge({ children: 'Pill', pill: true })
expect(screen.getByText('Pill')).toHaveStyle({ borderRadius: '50px' })
})

it('applies the title attribute and className', () => {
renderBadge({ children: 'Titled', title: 'A tooltip', className: 'my-badge' })
const badge = screen.getByText('Titled')
expect(badge).toHaveAttribute('title', 'A tooltip')
expect(badge).toHaveClass('my-badge')
})
})
Loading
Loading