diff --git a/admin-ui/app/components/Accordion/__tests__/Accordion.test.tsx b/admin-ui/app/components/Accordion/__tests__/Accordion.test.tsx
new file mode 100644
index 000000000..5a34d3f89
--- /dev/null
+++ b/admin-ui/app/components/Accordion/__tests__/Accordion.test.tsx
@@ -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 }) => (
+ {children}
+)
+
+type BuildProps = {
+ initialOpen?: boolean
+ open?: boolean
+ onToggle?: (isOpen: boolean) => void
+}
+
+const buildAccordion = ({ initialOpen, open, onToggle }: BuildProps = {}) => (
+
+
+ Section Title
+
+
+ Body Content
+
+)
+
+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()
+ })
+})
diff --git a/admin-ui/app/components/CustomInput/__tests__/CustomInput.test.tsx b/admin-ui/app/components/CustomInput/__tests__/CustomInput.test.tsx
new file mode 100644
index 000000000..437883ebc
--- /dev/null
+++ b/admin-ui/app/components/CustomInput/__tests__/CustomInput.test.tsx
@@ -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 }) => (
+ {children}
+)
+
+const renderInput = (props: CustomInputProps) =>
+ render(, { 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: (
+ <>
+
+
+ >
+ ),
+ })
+ 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: (
+ <>
+
+
+ >
+ ),
+ })
+ const select = screen.getByTestId('country') as HTMLSelectElement
+ fireEvent.change(select, { target: { value: 'uk' } })
+ expect(onChange).toHaveBeenCalledTimes(1)
+ })
+})
diff --git a/admin-ui/app/components/EmptyLayout/__tests__/EmptyLayout.test.tsx b/admin-ui/app/components/EmptyLayout/__tests__/EmptyLayout.test.tsx
new file mode 100644
index 000000000..e10d45904
--- /dev/null
+++ b/admin-ui/app/components/EmptyLayout/__tests__/EmptyLayout.test.tsx
@@ -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) =>
+ render(
+
+
+ ,
+ )
+
+const renderSection = (props: SectionProps) =>
+ render(
+
+
+ ,
+ )
+
+describe('EmptyLayout', () => {
+ it('renders its children', () => {
+ renderLayout({ children: hello child })
+ expect(screen.getByText('hello child')).toBeInTheDocument()
+ })
+
+ it('always applies the fullscreen class on the root element', () => {
+ renderLayout({ children: content })
+ const root = screen.getByTestId('content').parentElement
+ expect(root).toHaveClass('fullscreen')
+ })
+
+ it('merges a custom className alongside fullscreen', () => {
+ renderLayout({ children: content, 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: section child })
+ 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: section child })
+ 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: centered })
+ 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: x })
+ const wrapper = screen.getByTestId('sec').parentElement
+ expect(wrapper).toHaveStyle({ maxWidth: '50%' })
+ })
+
+ it('appends px to a numeric width', () => {
+ renderSection({ center: true, width: 600, children: x })
+ const wrapper = screen.getByTestId('sec').parentElement
+ expect(wrapper).toHaveStyle({ maxWidth: '600px' })
+ })
+
+ it('merges a custom className onto the section', () => {
+ renderSection({ className: 'my-section', children: x })
+ const section = screen.getByTestId('sec').parentElement
+ expect(section).toHaveClass('my-section')
+ expect(section).toHaveClass('fullscreen__section')
+ })
+ })
+})
diff --git a/admin-ui/app/components/GluuBadge/__tests__/GluuBadge.test.tsx b/admin-ui/app/components/GluuBadge/__tests__/GluuBadge.test.tsx
new file mode 100644
index 000000000..d801ab6f8
--- /dev/null
+++ b/admin-ui/app/components/GluuBadge/__tests__/GluuBadge.test.tsx
@@ -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 }) => (
+ {children}
+)
+
+const renderBadge = (props: Partial = {}) =>
+ render({props.children ?? 'Active'}, { 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(Inactive)
+ 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')
+ })
+})
diff --git a/admin-ui/app/components/GluuButton/__tests__/GluuButton.test.tsx b/admin-ui/app/components/GluuButton/__tests__/GluuButton.test.tsx
new file mode 100644
index 000000000..b6f6dcf12
--- /dev/null
+++ b/admin-ui/app/components/GluuButton/__tests__/GluuButton.test.tsx
@@ -0,0 +1,84 @@
+import React from 'react'
+import { render, screen, fireEvent } from '@testing-library/react'
+import GluuButton from '@/components/GluuButton/GluuButton'
+import type { GluuButtonProps } from '@/components/GluuButton/types'
+import AppTestWrapper from 'Routes/Apps/Gluu/Tests/Components/AppTestWrapper'
+
+const Wrapper = ({ children }: { children: React.ReactNode }) => (
+ {children}
+)
+
+const renderButton = (props: Partial = {}) =>
+ render({props.children ?? 'Click me'}, { wrapper: Wrapper })
+
+describe('GluuButton', () => {
+ it('renders its children', () => {
+ renderButton({ children: 'Save changes' })
+ expect(screen.getByRole('button', { name: 'Save changes' })).toBeInTheDocument()
+ })
+
+ it('fires onClick when clicked', () => {
+ const onClick = jest.fn()
+ renderButton({ onClick })
+ fireEvent.click(screen.getByRole('button'))
+ expect(onClick).toHaveBeenCalledTimes(1)
+ })
+
+ it('does not fire onClick and is disabled when disabled is true', () => {
+ const onClick = jest.fn()
+ renderButton({ onClick, disabled: true })
+ const button = screen.getByRole('button') as HTMLButtonElement
+ expect(button).toBeDisabled()
+ fireEvent.click(button)
+ expect(onClick).not.toHaveBeenCalled()
+ })
+
+ it('renders a spinner and is disabled while loading', () => {
+ const onClick = jest.fn()
+ renderButton({ onClick, loading: true, children: 'Submit' })
+ const button = screen.getByRole('button') as HTMLButtonElement
+ expect(button).toBeDisabled()
+ // spinner span is the first child element before the label text
+ expect(button.querySelector('span')).toBeInTheDocument()
+ fireEvent.click(button)
+ expect(onClick).not.toHaveBeenCalled()
+ })
+
+ it('defaults to type="button"', () => {
+ renderButton()
+ expect(screen.getByRole('button')).toHaveAttribute('type', 'button')
+ })
+
+ it('honors type="submit"', () => {
+ renderButton({ type: 'submit' })
+ expect(screen.getByRole('button')).toHaveAttribute('type', 'submit')
+ })
+
+ it('applies the title attribute', () => {
+ renderButton({ title: 'Tooltip text' })
+ expect(screen.getByRole('button')).toHaveAttribute('title', 'Tooltip text')
+ })
+
+ it('forwards aria-label and aria-expanded', () => {
+ renderButton({ 'aria-label': 'menu', 'aria-expanded': true })
+ const button = screen.getByRole('button', { name: 'menu' })
+ expect(button).toHaveAttribute('aria-expanded', 'true')
+ })
+
+ it('renders outlined and custom color props without error', () => {
+ renderButton({
+ outlined: true,
+ backgroundColor: '#123456',
+ textColor: '#ffffff',
+ borderColor: '#000000',
+ size: 'lg',
+ block: true,
+ })
+ expect(screen.getByRole('button')).toBeInTheDocument()
+ })
+
+ it('applies the provided className', () => {
+ renderButton({ className: 'my-btn' })
+ expect(screen.getByRole('button')).toHaveClass('my-btn')
+ })
+})
diff --git a/admin-ui/app/components/GluuDatePicker/__tests__/GluuDatePicker.test.tsx b/admin-ui/app/components/GluuDatePicker/__tests__/GluuDatePicker.test.tsx
new file mode 100644
index 000000000..858ec7bf9
--- /dev/null
+++ b/admin-ui/app/components/GluuDatePicker/__tests__/GluuDatePicker.test.tsx
@@ -0,0 +1,172 @@
+import React from 'react'
+import { render, screen, fireEvent } from '@testing-library/react'
+import { GluuDatePicker } from '@/components/GluuDatePicker/GluuDatePicker'
+import { createDate } from '@/utils/dayjsUtils'
+import AppTestWrapper from 'Routes/Apps/Gluu/Tests/Components/AppTestWrapper'
+
+const Wrapper = ({ children }: { children: React.ReactNode }) => (
+ {children}
+)
+
+// MUI X date pickers render a hidden per field that carries the
+// formatted value, plus a sectioned span group for editing. Reading the
+// underlying input value is the reliable way to assert the displayed value.
+const getInputs = (container: HTMLElement): HTMLInputElement[] =>
+ Array.from(container.querySelectorAll('input'))
+
+// The field label text is mirrored in both the