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)' + }) +})