From f953331f912c44b143711c8b3220d2136ee98d07 Mon Sep 17 00:00:00 2001
From: Saumya Talwani <68903741+saumyatalwani@users.noreply.github.com>
Date: Thu, 12 Feb 2026 07:21:18 +0530
Subject: [PATCH] test: add unit tests for some base components (#32201)
---
.../base/answer-icon/index.spec.tsx | 34 ++
.../components/base/copy-icon/index.spec.tsx | 54 +++
.../base/corner-label/index.spec.tsx | 16 +
.../base/drawer-plus/index.spec.tsx | 447 ++++++++++++++++++
.../components/base/dropdown/index.spec.tsx | 225 +++++++++
web/app/components/base/effect/index.spec.tsx | 9 +
.../base/encrypted-bottom/index.spec.tsx | 15 +
.../components/base/file-icon/index.spec.tsx | 28 ++
.../base/node-status/index.spec.tsx | 61 +++
.../base/notion-icon/index.spec.tsx | 49 ++
.../base/premium-badge/index.spec.tsx | 46 ++
11 files changed, 984 insertions(+)
create mode 100644 web/app/components/base/answer-icon/index.spec.tsx
create mode 100644 web/app/components/base/copy-icon/index.spec.tsx
create mode 100644 web/app/components/base/corner-label/index.spec.tsx
create mode 100644 web/app/components/base/drawer-plus/index.spec.tsx
create mode 100644 web/app/components/base/dropdown/index.spec.tsx
create mode 100644 web/app/components/base/effect/index.spec.tsx
create mode 100644 web/app/components/base/encrypted-bottom/index.spec.tsx
create mode 100644 web/app/components/base/file-icon/index.spec.tsx
create mode 100644 web/app/components/base/node-status/index.spec.tsx
create mode 100644 web/app/components/base/notion-icon/index.spec.tsx
create mode 100644 web/app/components/base/premium-badge/index.spec.tsx
diff --git a/web/app/components/base/answer-icon/index.spec.tsx b/web/app/components/base/answer-icon/index.spec.tsx
new file mode 100644
index 0000000000..72573fca5b
--- /dev/null
+++ b/web/app/components/base/answer-icon/index.spec.tsx
@@ -0,0 +1,34 @@
+import { render, screen } from '@testing-library/react'
+import AnswerIcon from '.'
+
+describe('AnswerIcon', () => {
+ it('renders default emoji when no icon or image is provided', () => {
+ const { container } = render()
+ const emojiElement = container.querySelector('em-emoji')
+ expect(emojiElement).toBeInTheDocument()
+ expect(emojiElement).toHaveAttribute('id', '🤖')
+ })
+
+ it('renders with custom emoji when icon is provided', () => {
+ const { container } = render()
+ const emojiElement = container.querySelector('em-emoji')
+ expect(emojiElement).toBeInTheDocument()
+ expect(emojiElement).toHaveAttribute('id', 'smile')
+ })
+ it('renders image when iconType is image and imageUrl is provided', () => {
+ render()
+ const imgElement = screen.getByAltText('answer icon')
+ expect(imgElement).toBeInTheDocument()
+ expect(imgElement).toHaveAttribute('src', 'test-image.jpg')
+ })
+
+ it('applies custom background color', () => {
+ const { container } = render()
+ expect(container.firstChild).toHaveStyle('background: #FF5500')
+ })
+
+ it('uses default background color when no background is provided for non-image icons', () => {
+ const { container } = render()
+ expect(container.firstChild).toHaveStyle('background: #D5F5F6')
+ })
+})
diff --git a/web/app/components/base/copy-icon/index.spec.tsx b/web/app/components/base/copy-icon/index.spec.tsx
new file mode 100644
index 0000000000..b4cf192174
--- /dev/null
+++ b/web/app/components/base/copy-icon/index.spec.tsx
@@ -0,0 +1,54 @@
+import { fireEvent, render } from '@testing-library/react'
+import CopyIcon from '.'
+
+const copy = vi.fn()
+const reset = vi.fn()
+let copied = false
+
+vi.mock('foxact/use-clipboard', () => ({
+ useClipboard: () => ({
+ copy,
+ reset,
+ copied,
+ }),
+}))
+
+describe('copy icon component', () => {
+ beforeEach(() => {
+ vi.resetAllMocks()
+ copied = false
+ })
+
+ it('renders normally', () => {
+ const { container } = render()
+ expect(container.querySelector('svg')).not.toBeNull()
+ })
+
+ it('shows copy icon initially', () => {
+ const { container } = render()
+ const icon = container.querySelector('[data-icon="Copy"]')
+ expect(icon).toBeInTheDocument()
+ })
+
+ it('shows copy check icon when copied', () => {
+ copied = true
+ const { container } = render()
+ const icon = container.querySelector('[data-icon="CopyCheck"]')
+ expect(icon).toBeInTheDocument()
+ })
+
+ it('handles copy when clicked', () => {
+ const { container } = render()
+ const icon = container.querySelector('[data-icon="Copy"]')
+ fireEvent.click(icon as Element)
+ expect(copy).toBeCalledTimes(1)
+ })
+
+ it('resets on mouse leave', () => {
+ const { container } = render()
+ const icon = container.querySelector('[data-icon="Copy"]')
+ const div = icon?.parentElement as HTMLElement
+ fireEvent.mouseLeave(div)
+ expect(reset).toBeCalledTimes(1)
+ })
+})
diff --git a/web/app/components/base/corner-label/index.spec.tsx b/web/app/components/base/corner-label/index.spec.tsx
new file mode 100644
index 0000000000..479eaeff0d
--- /dev/null
+++ b/web/app/components/base/corner-label/index.spec.tsx
@@ -0,0 +1,16 @@
+import { render, screen } from '@testing-library/react'
+import CornerLabel from '.'
+
+describe('CornerLabel', () => {
+ it('renders the label correctly', () => {
+ render()
+ expect(screen.getByText('Test Label')).toBeInTheDocument()
+ })
+
+ it('applies custom class names', () => {
+ const { container } = render()
+ expect(container.querySelector('.custom-class')).toBeInTheDocument()
+ expect(container.querySelector('.custom-label-class')).toBeInTheDocument()
+ expect(screen.getByText('Test Label')).toBeInTheDocument()
+ })
+})
diff --git a/web/app/components/base/drawer-plus/index.spec.tsx b/web/app/components/base/drawer-plus/index.spec.tsx
new file mode 100644
index 0000000000..e2d5c88df8
--- /dev/null
+++ b/web/app/components/base/drawer-plus/index.spec.tsx
@@ -0,0 +1,447 @@
+import { fireEvent, render, screen } from '@testing-library/react'
+import * as React from 'react'
+import DrawerPlus from '.'
+
+vi.mock('@/hooks/use-breakpoints', () => ({
+ default: () => 'desktop',
+ MediaType: { mobile: 'mobile', desktop: 'desktop', tablet: 'tablet' },
+}))
+
+describe('DrawerPlus', () => {
+ beforeEach(() => {
+ vi.clearAllMocks()
+ })
+
+ describe('Rendering', () => {
+ it('should not render when isShow is false', () => {
+ render(
+ {}}
+ title="Test Drawer"
+ body={Content
}
+ />,
+ )
+
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
+ })
+
+ it('should render when isShow is true', () => {
+ const bodyContent = Body Content
+ render(
+ {}}
+ title="Test Drawer"
+ body={bodyContent}
+ />,
+ )
+
+ expect(screen.getByRole('dialog')).toBeInTheDocument()
+ expect(screen.getByText('Test Drawer')).toBeInTheDocument()
+ expect(screen.getByText('Body Content')).toBeInTheDocument()
+ })
+
+ it('should render footer when provided', () => {
+ const footerContent = Footer Content
+ render(
+ {}}
+ title="Test Drawer"
+ body={Body
}
+ foot={footerContent}
+ />,
+ )
+
+ expect(screen.getByText('Footer Content')).toBeInTheDocument()
+ })
+
+ it('should render JSX element as title', () => {
+ const titleElement = Custom Title
+ render(
+ {}}
+ title={titleElement}
+ body={Body
}
+ />,
+ )
+
+ expect(screen.getByTestId('custom-title')).toBeInTheDocument()
+ })
+
+ it('should render titleDescription when provided', () => {
+ render(
+ {}}
+ title="Test Drawer"
+ titleDescription="Description text"
+ body={Body
}
+ />,
+ )
+
+ expect(screen.getByText('Description text')).toBeInTheDocument()
+ })
+
+ it('should not render titleDescription when not provided', () => {
+ render(
+ {}}
+ title="Test Drawer"
+ body={Body
}
+ />,
+ )
+
+ expect(screen.queryByText(/Description/)).not.toBeInTheDocument()
+ })
+
+ it('should render JSX element as titleDescription', () => {
+ const descElement = Custom Description
+ render(
+ {}}
+ title="Test"
+ titleDescription={descElement}
+ body={Body
}
+ />,
+ )
+
+ expect(screen.getByTestId('custom-desc')).toBeInTheDocument()
+ })
+ })
+
+ describe('Props - Display Options', () => {
+ it('should apply default maxWidthClassName', () => {
+ render(
+ {}}
+ title="Test"
+ body={Body
}
+ />,
+ )
+ const innerPanel = screen.getByText('Test').closest('.bg-components-panel-bg')
+ const outerPanel = innerPanel?.parentElement
+ expect(outerPanel?.className).toContain('!max-w-[640px]')
+ })
+
+ it('should apply custom maxWidthClassName', () => {
+ render(
+ {}}
+ title="Test"
+ body={Body
}
+ maxWidthClassName="!max-w-[800px]"
+ />,
+ )
+
+ const innerPanel = screen.getByText('Test').closest('.bg-components-panel-bg')
+ const outerPanel = innerPanel?.parentElement
+ expect(outerPanel?.className).toContain('!max-w-[800px]')
+ })
+
+ it('should apply custom panelClassName', () => {
+ render(
+ {}}
+ title="Test"
+ body={Body
}
+ panelClassName="custom-panel"
+ />,
+ )
+
+ const innerPanel = screen.getByText('Test').closest('.bg-components-panel-bg')
+ const outerPanel = innerPanel?.parentElement
+ expect(outerPanel?.className).toContain('custom-panel')
+ })
+
+ it('should apply custom dialogClassName', () => {
+ render(
+ {}}
+ title="Test"
+ body={Body
}
+ dialogClassName="custom-dialog"
+ />,
+ )
+
+ const dialog = screen.getByRole('dialog')
+ expect(dialog.className).toContain('custom-dialog')
+ })
+
+ it('should apply custom contentClassName', () => {
+ render(
+ {}}
+ title="Test"
+ body={Body
}
+ contentClassName="custom-content"
+ />,
+ )
+ const title = screen.getByText('Test')
+ const header = title.closest('.shrink-0.border-b.border-divider-subtle')
+ const content = header?.parentElement
+ expect(content?.className).toContain('custom-content')
+ })
+
+ it('should apply custom headerClassName', () => {
+ render(
+ {}}
+ title="Test"
+ body={Body
}
+ headerClassName="custom-header"
+ />,
+ )
+
+ const title = screen.getByText('Test')
+ const header = title.closest('.shrink-0.border-b.border-divider-subtle')
+ expect(header?.className).toContain('custom-header')
+ })
+
+ it('should apply custom height', () => {
+ render(
+ {}}
+ title="Test"
+ body={Body
}
+ height="500px"
+ />,
+ )
+
+ const title = screen.getByText('Test')
+ const header = title.closest('.shrink-0.border-b.border-divider-subtle')
+ const content = header?.parentElement
+ expect(content?.getAttribute('style')).toContain('height: 500px')
+ })
+
+ it('should use default height', () => {
+ render(
+ {}}
+ title="Test"
+ body={Body
}
+ />,
+ )
+
+ const title = screen.getByText('Test')
+ const header = title.closest('.shrink-0.border-b.border-divider-subtle')
+ const content = header?.parentElement
+ expect(content?.getAttribute('style')).toContain('calc(100vh - 72px)')
+ })
+ })
+
+ describe('Event Handlers', () => {
+ it('should call onHide when close button is clicked', () => {
+ const handleHide = vi.fn()
+ render(
+ Body}
+ />,
+ )
+
+ const title = screen.getByText('Test')
+ const headerRight = title.nextElementSibling // .flex items-center
+ const closeDiv = headerRight?.querySelector('.cursor-pointer') as HTMLElement
+
+ fireEvent.click(closeDiv)
+ expect(handleHide).toHaveBeenCalledTimes(1)
+ })
+ })
+
+ describe('Complex Content', () => {
+ it('should render complex JSX elements in body', () => {
+ const complexBody = (
+
+
Header
+
Paragraph
+
+
+ )
+
+ render(
+ {}}
+ title="Test"
+ body={complexBody}
+ />,
+ )
+
+ expect(screen.getByText('Header')).toBeInTheDocument()
+ expect(screen.getByText('Paragraph')).toBeInTheDocument()
+ expect(screen.getByRole('button', { name: 'Action Button' })).toBeInTheDocument()
+ })
+
+ it('should render complex footer', () => {
+ const complexFooter = (
+
+
+
+
+ )
+
+ render(
+ {}}
+ title="Test"
+ body={Body
}
+ foot={complexFooter}
+ />,
+ )
+
+ expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument()
+ expect(screen.getByRole('button', { name: 'Save' })).toBeInTheDocument()
+ })
+ })
+
+ describe('Edge Cases', () => {
+ it('should handle empty title', () => {
+ render(
+ {}}
+ title=""
+ body={Body
}
+ />,
+ )
+
+ expect(screen.getByRole('dialog')).toBeInTheDocument()
+ })
+
+ it('should handle undefined titleDescription', () => {
+ render(
+ {}}
+ title="Test"
+ titleDescription={undefined}
+ body={Body
}
+ />,
+ )
+
+ expect(screen.getByRole('dialog')).toBeInTheDocument()
+ })
+
+ it('should handle rapid isShow toggle', () => {
+ const { rerender } = render(
+ {}}
+ title="Test"
+ body={Body
}
+ />,
+ )
+
+ expect(screen.getByRole('dialog')).toBeInTheDocument()
+
+ rerender(
+ {}}
+ title="Test"
+ body={Body
}
+ />,
+ )
+
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
+
+ rerender(
+ {}}
+ title="Test"
+ body={Body
}
+ />,
+ )
+
+ expect(screen.getByRole('dialog')).toBeInTheDocument()
+ })
+
+ it('should handle special characters in title', () => {
+ const specialTitle = 'Test <> & " \' | Drawer'
+ render(
+ {}}
+ title={specialTitle}
+ body={Body
}
+ />,
+ )
+
+ expect(screen.getByText(specialTitle)).toBeInTheDocument()
+ })
+
+ it('should handle empty body content', () => {
+ render(
+ {}}
+ title="Test"
+ body={}
+ />,
+ )
+
+ expect(screen.getByRole('dialog')).toBeInTheDocument()
+ })
+
+ it('should apply both custom maxWidth and panel classNames', () => {
+ render(
+ {}}
+ title="Test"
+ body={Body
}
+ maxWidthClassName="!max-w-[500px]"
+ panelClassName="custom-style"
+ />,
+ )
+
+ const innerPanel = screen.getByText('Test').closest('.bg-components-panel-bg')
+ const outerPanel = innerPanel?.parentElement
+ expect(outerPanel?.className).toContain('!max-w-[500px]')
+ expect(outerPanel?.className).toContain('custom-style')
+ })
+ })
+
+ describe('Memoization', () => {
+ it('should be memoized and not re-render on parent changes', () => {
+ const { rerender } = render(
+ {}}
+ title="Test"
+ body={Body
}
+ />,
+ )
+
+ const dialog = screen.getByRole('dialog')
+
+ rerender(
+ {}}
+ title="Test"
+ body={Body
}
+ />,
+ )
+
+ expect(dialog).toBeInTheDocument()
+ })
+ })
+})
diff --git a/web/app/components/base/dropdown/index.spec.tsx b/web/app/components/base/dropdown/index.spec.tsx
new file mode 100644
index 0000000000..7d61b332d4
--- /dev/null
+++ b/web/app/components/base/dropdown/index.spec.tsx
@@ -0,0 +1,225 @@
+import { act, cleanup, fireEvent, render, screen } from '@testing-library/react'
+import Dropdown from './index'
+
+describe('Dropdown Component', () => {
+ const mockItems = [
+ { value: 'option1', text: 'Option 1' },
+ { value: 'option2', text: 'Option 2' },
+ ]
+ const mockSecondItems = [
+ { value: 'option3', text: 'Option 3' },
+ ]
+ const onSelect = vi.fn()
+
+ afterEach(() => {
+ cleanup()
+ vi.clearAllMocks()
+ })
+
+ it('renders default trigger properly', () => {
+ const { container } = render(
+ ,
+ )
+ const trigger = container.querySelector('button')
+ expect(trigger).toBeInTheDocument()
+ })
+
+ it('renders custom trigger when provided', () => {
+ render(
+ }
+ />,
+ )
+ const trigger = screen.getByTestId('custom-trigger')
+ expect(trigger).toBeInTheDocument()
+ expect(trigger).toHaveTextContent('Closed')
+ })
+
+ it('opens dropdown menu on trigger click and shows items', async () => {
+ render(
+ ,
+ )
+ const trigger = screen.getByRole('button')
+
+ await act(async () => {
+ fireEvent.click(trigger)
+ })
+
+ // Dropdown items are rendered in a portal (document.body)
+ expect(screen.getByText('Option 1')).toBeInTheDocument()
+ expect(screen.getByText('Option 2')).toBeInTheDocument()
+ })
+
+ it('calls onSelect and closes dropdown when an item is clicked', async () => {
+ render(
+ ,
+ )
+ const trigger = screen.getByRole('button')
+
+ await act(async () => {
+ fireEvent.click(trigger)
+ })
+
+ const option1 = screen.getByText('Option 1')
+ await act(async () => {
+ fireEvent.click(option1)
+ })
+
+ expect(onSelect).toHaveBeenCalledWith(mockItems[0])
+ expect(screen.queryByText('Option 1')).not.toBeInTheDocument()
+ })
+
+ it('calls onSelect and closes dropdown when a second item is clicked', async () => {
+ render(
+ ,
+ )
+
+ await act(async () => {
+ fireEvent.click(screen.getByRole('button'))
+ })
+
+ const option3 = screen.getByText('Option 3')
+ await act(async () => {
+ fireEvent.click(option3)
+ })
+ expect(onSelect).toHaveBeenCalledWith(mockSecondItems[0])
+ expect(screen.queryByText('Option 3')).not.toBeInTheDocument()
+ })
+
+ it('renders second items and divider when provided', async () => {
+ render(
+ ,
+ )
+ const trigger = screen.getByRole('button')
+
+ await act(async () => {
+ fireEvent.click(trigger)
+ })
+
+ expect(screen.getByText('Option 1')).toBeInTheDocument()
+ expect(screen.getByText('Option 3')).toBeInTheDocument()
+
+ // Check for divider (h-px bg-divider-regular)
+ const divider = document.body.querySelector('.bg-divider-regular.h-px')
+ expect(divider).toBeInTheDocument()
+ })
+
+ it('applies custom classNames', async () => {
+ const popupClass = 'custom-popup'
+ const itemClass = 'custom-item'
+ const secondItemClass = 'custom-second-item'
+
+ render(
+ ,
+ )
+
+ await act(async () => {
+ fireEvent.click(screen.getByRole('button'))
+ })
+
+ const popup = document.body.querySelector(`.${popupClass}`)
+ expect(popup).toBeInTheDocument()
+
+ const items = screen.getAllByText('Option 1')
+ expect(items[0]).toHaveClass(itemClass)
+
+ const secondItems = screen.getAllByText('Option 3')
+ expect(secondItems[0]).toHaveClass(secondItemClass)
+ })
+
+ it('applies open class to trigger when menu is open', async () => {
+ render()
+ const trigger = screen.getByRole('button')
+ await act(async () => {
+ fireEvent.click(trigger)
+ })
+ expect(trigger).toHaveClass('bg-divider-regular')
+ })
+
+ it('handles JSX elements as item text', async () => {
+ const itemsWithJSX = [
+ { value: 'jsx', text: JSX Content },
+ ]
+ render(
+ ,
+ )
+
+ await act(async () => {
+ fireEvent.click(screen.getByRole('button'))
+ })
+
+ expect(screen.getByTestId('jsx-item')).toBeInTheDocument()
+ expect(screen.getByText('JSX Content')).toBeInTheDocument()
+ })
+
+ it('does not render items section if items list is empty', async () => {
+ render(
+ ,
+ )
+
+ await act(async () => {
+ fireEvent.click(screen.getByRole('button'))
+ })
+
+ const p1Divs = document.body.querySelectorAll('.p-1')
+ expect(p1Divs.length).toBe(1)
+ expect(screen.queryByText('Option 1')).not.toBeInTheDocument()
+ expect(screen.getByText('Option 3')).toBeInTheDocument()
+ })
+
+ it('does not render divider if only one section is provided', async () => {
+ const { rerender } = render(
+ ,
+ )
+ await act(async () => {
+ fireEvent.click(screen.getByRole('button'))
+ })
+ expect(document.body.querySelector('.bg-divider-regular.h-px')).not.toBeInTheDocument()
+
+ await act(async () => {
+ rerender(
+ ,
+ )
+ })
+ expect(document.body.querySelector('.bg-divider-regular.h-px')).not.toBeInTheDocument()
+ })
+
+ it('renders nothing if both item lists are empty', async () => {
+ render()
+ await act(async () => {
+ fireEvent.click(screen.getByRole('button'))
+ })
+ const popup = document.body.querySelector('.bg-components-panel-bg')
+ expect(popup?.children.length).toBe(0)
+ })
+
+ it('passes triggerProps to ActionButton and applies custom className', () => {
+ render(
+ ,
+ )
+ const trigger = screen.getByLabelText('dropdown-trigger')
+ expect(trigger).toBeDisabled()
+ expect(trigger).toHaveClass('custom-trigger-class')
+ })
+})
diff --git a/web/app/components/base/effect/index.spec.tsx b/web/app/components/base/effect/index.spec.tsx
new file mode 100644
index 0000000000..38410f6987
--- /dev/null
+++ b/web/app/components/base/effect/index.spec.tsx
@@ -0,0 +1,9 @@
+import { render } from '@testing-library/react'
+import Effect from '.'
+
+describe('Effect', () => {
+ it('applies custom class names', () => {
+ const { container } = render()
+ expect(container.firstChild).toHaveClass('custom-class')
+ })
+})
diff --git a/web/app/components/base/encrypted-bottom/index.spec.tsx b/web/app/components/base/encrypted-bottom/index.spec.tsx
new file mode 100644
index 0000000000..aeeb546fe9
--- /dev/null
+++ b/web/app/components/base/encrypted-bottom/index.spec.tsx
@@ -0,0 +1,15 @@
+import { render, screen } from '@testing-library/react'
+import { EncryptedBottom } from '.'
+
+describe('EncryptedBottom', () => {
+ it('applies custom class names', () => {
+ const { container } = render()
+ expect(container.firstChild).toHaveClass('custom-class')
+ })
+
+ it('passes keys', async () => {
+ render()
+ expect(await screen.findByText(/provider.encrypted.front/i)).toBeInTheDocument()
+ expect(await screen.findByText(/provider.encrypted.back/i)).toBeInTheDocument()
+ })
+})
diff --git a/web/app/components/base/file-icon/index.spec.tsx b/web/app/components/base/file-icon/index.spec.tsx
new file mode 100644
index 0000000000..526a889f34
--- /dev/null
+++ b/web/app/components/base/file-icon/index.spec.tsx
@@ -0,0 +1,28 @@
+import { render } from '@testing-library/react'
+import FileIcon from '.'
+
+describe('File icon component', () => {
+ const testCases = [
+ { type: 'csv', icon: 'Csv' },
+ { type: 'doc', icon: 'Doc' },
+ { type: 'docx', icon: 'Docx' },
+ { type: 'htm', icon: 'Html' },
+ { type: 'html', icon: 'Html' },
+ { type: 'md', icon: 'Md' },
+ { type: 'mdx', icon: 'Md' },
+ { type: 'markdown', icon: 'Md' },
+ { type: 'pdf', icon: 'Pdf' },
+ { type: 'xls', icon: 'Xlsx' },
+ { type: 'xlsx', icon: 'Xlsx' },
+ { type: 'notion', icon: 'Notion' },
+ { type: 'something-else', icon: 'Unknown' },
+ { type: 'txt', icon: 'Txt' },
+ { type: 'json', icon: 'Json' },
+ ]
+
+ it.each(testCases)('renders $icon icon for type $type', ({ type, icon }) => {
+ const { container } = render()
+ const iconElement = container.querySelector(`[data-icon="${icon}"]`)
+ expect(iconElement).toBeInTheDocument()
+ })
+})
diff --git a/web/app/components/base/node-status/index.spec.tsx b/web/app/components/base/node-status/index.spec.tsx
new file mode 100644
index 0000000000..566a537653
--- /dev/null
+++ b/web/app/components/base/node-status/index.spec.tsx
@@ -0,0 +1,61 @@
+import { render, screen } from '@testing-library/react'
+import NodeStatus, { NodeStatusEnum } from '.'
+
+describe('NodeStatus', () => {
+ it('renders with default status (warning) and default message', () => {
+ const { container } = render()
+
+ expect(screen.getByText('Warning')).toBeInTheDocument()
+ // Default warning class
+ expect(container.firstChild).toHaveClass('bg-state-warning-hover')
+ expect(container.firstChild).toHaveClass('text-text-warning')
+ })
+
+ it('renders with error status and default message', () => {
+ const { container } = render()
+
+ expect(screen.getByText('Error')).toBeInTheDocument()
+ expect(container.firstChild).toHaveClass('bg-state-destructive-hover')
+ expect(container.firstChild).toHaveClass('text-text-destructive')
+ })
+
+ it('renders with custom message', () => {
+ render()
+ expect(screen.getByText('Custom Message')).toBeInTheDocument()
+ })
+
+ it('renders children correctly', () => {
+ render(
+
+ Child Element
+ ,
+ )
+ expect(screen.getByTestId('child')).toBeInTheDocument()
+ expect(screen.getByText('Child Element')).toBeInTheDocument()
+ })
+
+ it('applies custom className', () => {
+ const { container } = render()
+ expect(container.firstChild).toHaveClass('custom-test-class')
+ })
+
+ it('applies styleCss correctly', () => {
+ const { container } = render()
+ expect(container.firstChild).toHaveStyle({ color: 'rgb(255, 0, 0)' })
+ })
+
+ it('applies iconClassName to the icon', () => {
+ const { container } = render()
+ // The icon is the first child of the div
+ const icon = container.querySelector('.custom-icon-class')
+ expect(icon).toBeInTheDocument()
+ expect(icon).toHaveClass('h-3.5')
+ expect(icon).toHaveClass('w-3.5')
+ })
+
+ it('passes additional HTML attributes to the container', () => {
+ render()
+ const container = screen.getByTestId('node-status-container')
+ expect(container).toHaveAttribute('id', 'my-id')
+ })
+})
diff --git a/web/app/components/base/notion-icon/index.spec.tsx b/web/app/components/base/notion-icon/index.spec.tsx
new file mode 100644
index 0000000000..582beab054
--- /dev/null
+++ b/web/app/components/base/notion-icon/index.spec.tsx
@@ -0,0 +1,49 @@
+import { render, screen } from '@testing-library/react'
+import NotionIcon from '.'
+
+describe('Notion Icon', () => {
+ it('applies custom class names', () => {
+ const { container } = render()
+ expect(container.firstChild).toHaveClass('custom-class')
+ })
+
+ it('renders image on http url', () => {
+ render()
+ expect(screen.getByAltText('workspace icon')).toHaveAttribute('src', 'http://example.com/image.png')
+ })
+
+ it('renders image on https url', () => {
+ render()
+ expect(screen.getByAltText('workspace icon')).toHaveAttribute('src', 'https://example.com/image.png')
+ })
+
+ it('renders div on non-http url', () => {
+ render()
+ expect(screen.getByText('example.com/image.png')).toBeInTheDocument()
+ })
+
+ it('renders name when no url is provided', () => {
+ render()
+ expect(screen.getByText('T')).toBeInTheDocument()
+ })
+
+ it('renders image on type url for page', () => {
+ render()
+ expect(screen.getByAltText('page icon')).toHaveAttribute('src', 'https://example.com/image.png')
+ })
+
+ it('renders blank image on type url if no url is passed for page', () => {
+ render()
+ expect(screen.getByAltText('page icon')).not.toHaveAttribute('src')
+ })
+
+ it('renders emoji on type emoji for page', () => {
+ render()
+ expect(screen.getByText('🚀')).toBeInTheDocument()
+ })
+
+ it('renders icon on url for page', () => {
+ const { container } = render()
+ expect(container.querySelector('svg')).not.toBeNull()
+ })
+})
diff --git a/web/app/components/base/premium-badge/index.spec.tsx b/web/app/components/base/premium-badge/index.spec.tsx
new file mode 100644
index 0000000000..a589aef828
--- /dev/null
+++ b/web/app/components/base/premium-badge/index.spec.tsx
@@ -0,0 +1,46 @@
+import { render, screen } from '@testing-library/react'
+import PremiumBadge from './index'
+
+describe('PremiumBadge', () => {
+ it('renders with default props', () => {
+ render(Premium)
+ const badge = screen.getByText('Premium')
+ expect(badge).toBeInTheDocument()
+ expect(badge).toHaveClass('premium-badge-m')
+ expect(badge).toHaveClass('premium-badge-blue')
+ })
+
+ it('renders with custom size and color', () => {
+ render(
+
+ Premium
+ ,
+ )
+ const badge = screen.getByText('Premium')
+ expect(badge).toBeInTheDocument()
+ expect(badge).toHaveClass('premium-badge-s')
+ expect(badge).toHaveClass('premium-badge-indigo')
+ })
+
+ it('applies allowHover class when allowHover is true', () => {
+ render(
+
+ Premium
+ ,
+ )
+ const badge = screen.getByText('Premium')
+ expect(badge).toBeInTheDocument()
+ expect(badge).toHaveClass('allowHover')
+ })
+
+ it('applies custom styles', () => {
+ render(
+
+ Premium
+ ,
+ )
+ const badge = screen.getByText('Premium')
+ expect(badge).toBeInTheDocument()
+ expect(badge).toHaveStyle('background-color: rgb(255, 0, 0)') // Note: React converts 'red' to 'rgb(255, 0, 0)'
+ })
+})