From 5c32cae14f39a71e467bcd9d36a9d5a8a54375c0 Mon Sep 17 00:00:00 2001 From: Xavi Abad <77491413+xabg2@users.noreply.github.com> Date: Thu, 26 Feb 2026 11:53:42 +0100 Subject: [PATCH 1/3] feat: email list --- src/components/mail/tray/MessageCheap.tsx | 48 ++++++ .../mail/tray/MessageCheapSkeleton.tsx | 15 ++ src/components/mail/tray/TrayList.tsx | 85 ++++++++++ .../components/mail/tray/Tray.stories.tsx | 157 ++++++++++++++++++ .../tray/message/MessageCheap.stories.tsx | 101 +++++++++++ .../message/MessageCheapSkeleton.stories.tsx | 33 ++++ 6 files changed, 439 insertions(+) create mode 100644 src/components/mail/tray/MessageCheap.tsx create mode 100644 src/components/mail/tray/MessageCheapSkeleton.tsx create mode 100644 src/components/mail/tray/TrayList.tsx create mode 100644 src/stories/components/mail/tray/Tray.stories.tsx create mode 100644 src/stories/components/mail/tray/message/MessageCheap.stories.tsx create mode 100644 src/stories/components/mail/tray/message/MessageCheapSkeleton.stories.tsx diff --git a/src/components/mail/tray/MessageCheap.tsx b/src/components/mail/tray/MessageCheap.tsx new file mode 100644 index 0000000..61357c8 --- /dev/null +++ b/src/components/mail/tray/MessageCheap.tsx @@ -0,0 +1,48 @@ +import { Avatar } from '@/components/avatar'; + +interface MessageCheapProps { + email: { + id: string; + from: { + name: string; + avatar: string; + }; + subject: string; + createdAt: string; + body: string; + read: boolean; + }; + active?: boolean; + selected?: boolean; + onClick: (id: string) => void; +} + +export const MessageCheap = ({ email, active, selected, onClick }: MessageCheapProps) => { + const isHighlighted = active || selected; + + return ( + + ); +}; diff --git a/src/components/mail/tray/MessageCheapSkeleton.tsx b/src/components/mail/tray/MessageCheapSkeleton.tsx new file mode 100644 index 0000000..17cfad6 --- /dev/null +++ b/src/components/mail/tray/MessageCheapSkeleton.tsx @@ -0,0 +1,15 @@ +export const MessageCheapSkeleton = () => ( +
+
+
+
+
+
+
+
+
+
+
+
+
+); diff --git a/src/components/mail/tray/TrayList.tsx b/src/components/mail/tray/TrayList.tsx new file mode 100644 index 0000000..101a923 --- /dev/null +++ b/src/components/mail/tray/TrayList.tsx @@ -0,0 +1,85 @@ +import { InfiniteScroll } from '@/components/infiniteScroll'; +import { MessageCheapSkeleton } from './MessageCheapSkeleton'; +import { MessageCheap } from './MessageCheap'; + +interface TrayListProps { + mails: { + id: string; + from: { + name: string; + avatar: string; + }; + subject: string; + createdAt: string; + body: string; + read: boolean; + }[]; + selectedEmails?: string[]; + loading: boolean; + checked: boolean; + activeEmail: string; + hasMoreItems?: boolean; + onMailSelected: (id: string) => void; + onLoadMore?: () => void; +} + +export const TrayList = ({ + mails, + selectedEmails = [], + loading, + checked, + activeEmail, + hasMoreItems = false, + onMailSelected, + onLoadMore, +}: TrayListProps) => { + const loader = ( +
+ {new Array(3).fill(0).map((_, index) => ( + + ))} +
+ ); + + return ( +
+
+ {loading ? ( + <> + {new Array(8).fill(0).map((_, index) => ( +
+ +
+ ))} + + ) : ( +
+ {mails.length === 0 ? ( +
+

No emails

+
+ ) : ( + {})} + hasMoreItems={hasMoreItems} + loader={loader} + scrollableTarget="tray-scroll-container" + > + {mails.map((email) => ( +
+ +
+ ))} +
+ )} +
+ )} +
+
+ ); +}; diff --git a/src/stories/components/mail/tray/Tray.stories.tsx b/src/stories/components/mail/tray/Tray.stories.tsx new file mode 100644 index 0000000..59baa09 --- /dev/null +++ b/src/stories/components/mail/tray/Tray.stories.tsx @@ -0,0 +1,157 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { fn } from 'storybook/test'; +import { TrayList } from '../../../../components/mail/tray/TrayList'; + +const mockMails = [ + { + id: '1', + from: { name: 'John Doe', avatar: '' }, + subject: 'Meeting tomorrow', + createdAt: '10:30 AM', + body: 'Hi, just a reminder about our meeting tomorrow at 3pm.', + read: false, + }, + { + id: '2', + from: { name: 'Jane Smith', avatar: '' }, + subject: 'Project update', + createdAt: '9:15 AM', + body: 'The project is progressing well. Here are the latest updates...', + read: true, + }, + { + id: '3', + from: { name: 'Mike Johnson', avatar: '' }, + subject: 'Invoice #1234', + createdAt: 'Yesterday', + body: 'Please find attached the invoice for last month services.', + read: true, + }, + { + id: '4', + from: { name: 'Sarah Wilson', avatar: '' }, + subject: 'Welcome to the team!', + createdAt: 'Yesterday', + body: 'We are excited to have you on board. Let me know if you need anything.', + read: false, + }, + { + id: '5', + from: { name: 'Tech Support', avatar: '' }, + subject: 'Your ticket has been resolved', + createdAt: 'Feb 20', + body: 'Your support ticket #5678 has been resolved. Please let us know if you have any questions.', + read: true, + }, +]; + +const meta: Meta = { + title: 'Mail/Tray List', + component: TrayList, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + loading: { + control: 'boolean', + description: 'Shows skeleton loaders when true', + }, + checked: { + control: 'boolean', + description: 'Select all checkbox state', + }, + activeEmail: { + control: 'text', + description: 'ID of the currently active email', + }, + hasMoreItems: { + control: 'boolean', + description: 'Whether there are more items to load', + }, + }, + args: { + onMailSelected: fn(), + onLoadMore: fn(), + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + mails: mockMails, + selectedEmails: [], + loading: false, + checked: false, + activeEmail: '', + hasMoreItems: false, + }, +}; + +export const WithActiveEmail: Story = { + args: { + mails: mockMails, + selectedEmails: [], + loading: false, + checked: false, + activeEmail: '2', + hasMoreItems: false, + }, +}; + +export const WithSelectedEmails: Story = { + args: { + mails: mockMails, + selectedEmails: ['1', '3', '5'], + loading: false, + checked: false, + activeEmail: '', + hasMoreItems: false, + }, +}; + +export const AllSelected: Story = { + args: { + mails: mockMails, + selectedEmails: ['1', '3', '5'], + loading: false, + checked: true, + activeEmail: '', + hasMoreItems: false, + }, +}; + +export const WithInfiniteScroll: Story = { + args: { + mails: mockMails, + selectedEmails: [], + loading: false, + checked: false, + activeEmail: '', + hasMoreItems: true, + }, +}; + +export const Loading: Story = { + args: { + mails: [], + selectedEmails: [], + loading: true, + checked: false, + activeEmail: '', + hasMoreItems: false, + }, +}; + +export const Empty: Story = { + args: { + mails: [], + selectedEmails: [], + loading: false, + checked: false, + activeEmail: '', + hasMoreItems: false, + }, +}; diff --git a/src/stories/components/mail/tray/message/MessageCheap.stories.tsx b/src/stories/components/mail/tray/message/MessageCheap.stories.tsx new file mode 100644 index 0000000..61c7d4c --- /dev/null +++ b/src/stories/components/mail/tray/message/MessageCheap.stories.tsx @@ -0,0 +1,101 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { fn } from 'storybook/test'; +import { MessageCheap } from '../../../../../components/mail/tray/MessageCheap'; + +const mockEmail = { + id: '1', + from: { name: 'John Doe', avatar: '' }, + subject: 'Meeting tomorrow', + createdAt: '10:30 AM', + body: 'Hi, just a reminder about our meeting tomorrow at 3pm.', + read: false, +}; + +const meta: Meta = { + title: 'Mail/MessageCheap', + component: MessageCheap, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + active: { + control: 'boolean', + description: 'Whether the message is currently selected/active', + }, + }, + args: { + onClick: fn(), + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + email: mockEmail, + active: false, + }, +}; + +export const Active: Story = { + args: { + email: mockEmail, + active: true, + }, +}; + +export const Read: Story = { + args: { + email: { + ...mockEmail, + read: true, + }, + active: false, + }, +}; + +export const Unread: Story = { + args: { + email: { + ...mockEmail, + read: false, + }, + active: false, + }, +}; + +export const LongContent: Story = { + args: { + email: { + id: '2', + from: { name: 'Alexander Maximilian Richardson III', avatar: '' }, + subject: 'Important: Project deadline extension request for Q4 deliverables', + createdAt: 'Yesterday', + body: 'This is a very long email body that should be truncated when it exceeds the available width of the container.', + read: false, + }, + active: false, + }, +}; + +export const WithAvatar: Story = { + args: { + email: { + ...mockEmail, + from: { + name: 'Jane Smith', + avatar: 'https://i.pravatar.cc/150?img=5', + }, + }, + active: false, + }, +}; diff --git a/src/stories/components/mail/tray/message/MessageCheapSkeleton.stories.tsx b/src/stories/components/mail/tray/message/MessageCheapSkeleton.stories.tsx new file mode 100644 index 0000000..11c71dc --- /dev/null +++ b/src/stories/components/mail/tray/message/MessageCheapSkeleton.stories.tsx @@ -0,0 +1,33 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { MessageCheapSkeleton } from '../../../../../components/mail/tray/MessageCheapSkeleton'; + +const meta: Meta = { + title: 'Mail/MessageCheapSkeleton', + component: MessageCheapSkeleton, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + +export const Multiple: Story = { + render: () => ( +
+ {new Array(5).fill(0).map((_, index) => ( + + ))} +
+ ), +}; From 3f34f6fa17cdba94e35c819961926ffa1db8ee29 Mon Sep 17 00:00:00 2001 From: Xavi Abad <77491413+xabg2@users.noreply.github.com> Date: Fri, 27 Feb 2026 15:09:37 +0100 Subject: [PATCH 2/3] feat: add empty state component --- .../mail/tray/MessageCheapSkeleton.tsx | 6 +++- src/components/mail/tray/TrayList.tsx | 35 ++++++++++++++++--- .../components/mail/tray/Tray.stories.tsx | 1 + 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/components/mail/tray/MessageCheapSkeleton.tsx b/src/components/mail/tray/MessageCheapSkeleton.tsx index 17cfad6..e6779ff 100644 --- a/src/components/mail/tray/MessageCheapSkeleton.tsx +++ b/src/components/mail/tray/MessageCheapSkeleton.tsx @@ -1,13 +1,17 @@ export const MessageCheapSkeleton = () => (
-
+ {/* Avatar */} +
+ {/* Name and date */}
+ {/* Subject */}
+ {/* Body */}
diff --git a/src/components/mail/tray/TrayList.tsx b/src/components/mail/tray/TrayList.tsx index 101a923..13f42aa 100644 --- a/src/components/mail/tray/TrayList.tsx +++ b/src/components/mail/tray/TrayList.tsx @@ -1,6 +1,7 @@ import { InfiniteScroll } from '@/components/infiniteScroll'; import { MessageCheapSkeleton } from './MessageCheapSkeleton'; import { MessageCheap } from './MessageCheap'; +import { ReactNode } from 'react'; interface TrayListProps { mails: { @@ -19,10 +20,35 @@ interface TrayListProps { checked: boolean; activeEmail: string; hasMoreItems?: boolean; + emptyState?: ReactNode; onMailSelected: (id: string) => void; onLoadMore?: () => void; } +/** + * + * @param {TrayListProps} TrayListProps - Props for the TrayList component + * @prop {Array} TrayListProps.mails - An array of email objects + * + * @prop {string[]} TrayListProps.selectedEmails - An array of selected email IDs + * + * @prop {boolean} TrayListProps.loading - A boolean indicating loading state + * + * @prop {boolean} TrayListProps.checked - A boolean indicating whether all emails are checked + * + * @prop {string} TrayListProps.activeEmail - The ID of the currently active email + * + * @prop {boolean} TrayListProps.hasMoreItems - A boolean indicating whether there are more items to load + * + * @prop {ReactNode} TrayListProps.emptyState - A JSX element to display when there are no emails + * + * @prop {(id: string) => void} TrayListProps.onMailSelected - A function to handle email selection + * + * @prop {() => void} TrayListProps.onLoadMore - A function to load more emails + * + * @returns {JSX.Element} The rendered TrayList component + */ + export const TrayList = ({ mails, selectedEmails = [], @@ -30,6 +56,7 @@ export const TrayList = ({ checked, activeEmail, hasMoreItems = false, + emptyState, onMailSelected, onLoadMore, }: TrayListProps) => { @@ -53,11 +80,9 @@ export const TrayList = ({ ))} ) : ( -
+ <> {mails.length === 0 ? ( -
-

No emails

-
+ <>{emptyState} ) : ( {})} @@ -77,7 +102,7 @@ export const TrayList = ({ ))} )} -
+ )}
diff --git a/src/stories/components/mail/tray/Tray.stories.tsx b/src/stories/components/mail/tray/Tray.stories.tsx index 59baa09..0e66a35 100644 --- a/src/stories/components/mail/tray/Tray.stories.tsx +++ b/src/stories/components/mail/tray/Tray.stories.tsx @@ -153,5 +153,6 @@ export const Empty: Story = { checked: false, activeEmail: '', hasMoreItems: false, + emptyState:
Empty state
, }, }; From c644292a7977235bf72c111e975c1c8fa778dac6 Mon Sep 17 00:00:00 2001 From: Xavi Abad <77491413+xabg2@users.noreply.github.com> Date: Fri, 27 Feb 2026 17:55:18 +0100 Subject: [PATCH 3/3] test: add coverage to components --- .../mail/tray/__test__/MessageCheap.test.tsx | 94 ++++++ .../__test__/MessageCheapSkeleton.test.tsx | 26 ++ .../mail/tray/__test__/TrayList.test.tsx | 185 +++++++++++ .../__snapshots__/MessageCheap.test.tsx.snap | 166 ++++++++++ .../MessageCheapSkeleton.test.tsx.snap | 126 ++++++++ .../__snapshots__/TrayList.test.tsx.snap | 300 ++++++++++++++++++ 6 files changed, 897 insertions(+) create mode 100644 src/components/mail/tray/__test__/MessageCheap.test.tsx create mode 100644 src/components/mail/tray/__test__/MessageCheapSkeleton.test.tsx create mode 100644 src/components/mail/tray/__test__/TrayList.test.tsx create mode 100644 src/components/mail/tray/__test__/__snapshots__/MessageCheap.test.tsx.snap create mode 100644 src/components/mail/tray/__test__/__snapshots__/MessageCheapSkeleton.test.tsx.snap create mode 100644 src/components/mail/tray/__test__/__snapshots__/TrayList.test.tsx.snap diff --git a/src/components/mail/tray/__test__/MessageCheap.test.tsx b/src/components/mail/tray/__test__/MessageCheap.test.tsx new file mode 100644 index 0000000..adc886e --- /dev/null +++ b/src/components/mail/tray/__test__/MessageCheap.test.tsx @@ -0,0 +1,94 @@ +import React from 'react'; +import { render, fireEvent, screen } from '@testing-library/react'; +import { describe, it, expect, vi, afterEach } from 'vitest'; +import { MessageCheap } from '../MessageCheap'; + +const mockEmail = { + id: '1', + from: { + name: 'John Doe', + avatar: 'https://example.com/avatar.jpg', + }, + subject: 'Test Subject', + createdAt: '2024-01-15', + body: 'This is a test email body', + read: false, +}; + +const mockOnClick = vi.fn(); + +const renderMessageCheap = (props = {}) => + render(); + +describe('MessageCheap', () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should match snapshot', () => { + const messageCheap = renderMessageCheap(); + expect(messageCheap).toMatchSnapshot(); + }); + + it('should render email details correctly', () => { + renderMessageCheap(); + + expect(screen.getByText('John Doe')).toBeInTheDocument(); + expect(screen.getByText('Test Subject')).toBeInTheDocument(); + expect(screen.getByText('This is a test email body')).toBeInTheDocument(); + expect(screen.getByText('2024-01-15')).toBeInTheDocument(); + }); + + it('should call onClick with email id when clicked', () => { + renderMessageCheap(); + + const button = screen.getByRole('button'); + fireEvent.click(button); + + expect(mockOnClick).toHaveBeenCalledTimes(1); + expect(mockOnClick).toHaveBeenCalledWith('1'); + }); + + it('should show unread indicator for unread emails', () => { + const { container } = renderMessageCheap(); + + const unreadIndicator = container.querySelector('.bg-primary'); + expect(unreadIndicator).toBeInTheDocument(); + }); + + it('should not show unread indicator for read emails', () => { + const readEmail = { ...mockEmail, read: true }; + const { container } = render(); + + const unreadIndicator = container.querySelector('.bg-primary.h-2.w-2'); + expect(unreadIndicator).not.toBeInTheDocument(); + }); + + it('should apply highlighted styles when active', () => { + const { container } = renderMessageCheap({ active: true }); + + const button = container.querySelector('button'); + expect(button?.className).toContain('bg-primary/10'); + }); + + it('should apply highlighted styles when selected', () => { + const { container } = renderMessageCheap({ selected: true }); + + const button = container.querySelector('button'); + expect(button?.className).toContain('bg-primary/10'); + }); + + it('should not apply highlighted styles when neither active nor selected', () => { + const { container } = renderMessageCheap({ active: false, selected: false }); + + const button = container.querySelector('button'); + expect(button?.className).not.toContain('bg-primary/10'); + }); + + it('should render Avatar component with correct props', () => { + renderMessageCheap(); + + const avatarContainer = screen.getByText('John Doe').closest('.flex.flex-row'); + expect(avatarContainer).toBeInTheDocument(); + }); +}); diff --git a/src/components/mail/tray/__test__/MessageCheapSkeleton.test.tsx b/src/components/mail/tray/__test__/MessageCheapSkeleton.test.tsx new file mode 100644 index 0000000..f6d6c08 --- /dev/null +++ b/src/components/mail/tray/__test__/MessageCheapSkeleton.test.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import { describe, it, expect } from 'vitest'; +import { MessageCheapSkeleton } from '../MessageCheapSkeleton'; + +describe('MessageCheapSkeleton', () => { + it('should match snapshot', () => { + const skeleton = render(); + expect(skeleton).toMatchSnapshot(); + }); + + it('should render skeleton structure', () => { + const { container } = render(); + + const skeletonElements = container.querySelectorAll('.animate-pulse'); + expect(skeletonElements.length).toBeGreaterThan(0); + }); + + it('should have proper border styling', () => { + const { container } = render(); + + const wrapper = container.firstChild as HTMLElement; + expect(wrapper.className).toContain('border-b'); + expect(wrapper.className).toContain('border-gray-5'); + }); +}); diff --git a/src/components/mail/tray/__test__/TrayList.test.tsx b/src/components/mail/tray/__test__/TrayList.test.tsx new file mode 100644 index 0000000..17c2b11 --- /dev/null +++ b/src/components/mail/tray/__test__/TrayList.test.tsx @@ -0,0 +1,185 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { describe, it, expect, vi, afterEach, beforeAll, afterAll } from 'vitest'; +import { TrayList } from '../TrayList'; + +const mockMails = [ + { + id: '1', + from: { + name: 'John Doe', + avatar: 'https://example.com/avatar1.jpg', + }, + subject: 'Test Subject 1', + createdAt: '2024-01-15', + body: 'This is test email 1', + read: false, + }, + { + id: '2', + from: { + name: 'Jane Smith', + avatar: 'https://example.com/avatar2.jpg', + }, + subject: 'Test Subject 2', + createdAt: '2024-01-16', + body: 'This is test email 2', + read: true, + }, +]; + +const mockOnMailSelected = vi.fn(); +const mockOnLoadMore = vi.fn(); + +const renderTrayList = (props = {}) => + render( + , + ); + +describe('TrayList', () => { + beforeAll(() => { + (globalThis as any).IntersectionObserver = class { + observe() { + return null; + } + unobserve() { + return null; + } + disconnect() { + return null; + } + }; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + afterAll(() => { + delete (globalThis as any).IntersectionObserver; + }); + + it('should match snapshot', () => { + const trayList = renderTrayList(); + expect(trayList).toMatchSnapshot(); + }); + + it('should render list of emails', () => { + renderTrayList(); + + expect(screen.getByText('John Doe')).toBeInTheDocument(); + expect(screen.getByText('Test Subject 1')).toBeInTheDocument(); + expect(screen.getByText('Jane Smith')).toBeInTheDocument(); + expect(screen.getByText('Test Subject 2')).toBeInTheDocument(); + }); + + it('should show loading skeletons when loading is true', () => { + const { container } = renderTrayList({ loading: true }); + + const skeletons = container.querySelectorAll('.animate-pulse'); + expect(skeletons.length).toBeGreaterThan(0); + }); + + it('should not show emails when loading', () => { + renderTrayList({ loading: true }); + + expect(screen.queryByText('John Doe')).not.toBeInTheDocument(); + expect(screen.queryByText('Jane Smith')).not.toBeInTheDocument(); + }); + + it('should show empty state when no emails and not loading', () => { + const emptyState =
No emails found
; + renderTrayList({ mails: [], loading: false, emptyState }); + + expect(screen.getByText('No emails found')).toBeInTheDocument(); + }); + + it('should not show empty state when there are emails', () => { + const emptyState =
No emails found
; + renderTrayList({ emptyState }); + + expect(screen.queryByText('No emails found')).not.toBeInTheDocument(); + }); + + it('should call onMailSelected when an email is clicked', () => { + renderTrayList(); + + const emailButtons = screen.getAllByRole('button'); + fireEvent.click(emailButtons[0]); + + expect(mockOnMailSelected).toHaveBeenCalledTimes(1); + expect(mockOnMailSelected).toHaveBeenCalledWith('1'); + }); + + it('should highlight active email', () => { + const { container } = renderTrayList({ activeEmail: '1' }); + + const buttons = container.querySelectorAll('button'); + const activeButton = buttons[0]; + expect(activeButton.className).toContain('bg-primary/10'); + }); + + it('should highlight selected emails', () => { + const { container } = renderTrayList({ selectedEmails: ['2'] }); + + const buttons = container.querySelectorAll('button'); + const selectedButton = buttons[1]; + expect(selectedButton.className).toContain('bg-primary/10'); + }); + + it('should highlight all emails when checked is true', () => { + const { container } = renderTrayList({ checked: true }); + + const buttons = container.querySelectorAll('button'); + buttons.forEach((button) => { + expect(button.className).toContain('bg-primary/10'); + }); + }); + + it('should render with correct container dimensions', () => { + const { container } = renderTrayList(); + + const mainContainer = container.firstChild as HTMLElement; + expect(mainContainer.className).toContain('w-[400px]'); + expect(mainContainer.className).toContain('min-w-[200px]'); + expect(mainContainer.className).toContain('max-w-[400px]'); + expect(mainContainer.className).toContain('h-screen'); + }); + + it('should have scrollable container with correct id', () => { + const { container } = renderTrayList(); + + const scrollContainer = container.querySelector('#tray-scroll-container'); + expect(scrollContainer).toBeInTheDocument(); + expect(scrollContainer?.className).toContain('overflow-y-auto'); + }); + + it('should pass hasMoreItems to InfiniteScroll', () => { + renderTrayList({ hasMoreItems: true, onLoadMore: mockOnLoadMore }); + + expect(screen.getByText('John Doe')).toBeInTheDocument(); + }); + + it('should default selectedEmails to empty array', () => { + const { container } = renderTrayList({ selectedEmails: undefined }); + + const buttons = container.querySelectorAll('button'); + buttons.forEach((button) => { + expect(button.className).not.toContain('bg-primary/10'); + }); + }); + + it('should default hasMoreItems to false', () => { + renderTrayList({ hasMoreItems: undefined }); + + expect(screen.getByText('John Doe')).toBeInTheDocument(); + }); +}); diff --git a/src/components/mail/tray/__test__/__snapshots__/MessageCheap.test.tsx.snap b/src/components/mail/tray/__test__/__snapshots__/MessageCheap.test.tsx.snap new file mode 100644 index 0000000..6e329bd --- /dev/null +++ b/src/components/mail/tray/__test__/__snapshots__/MessageCheap.test.tsx.snap @@ -0,0 +1,166 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`MessageCheap > should match snapshot 1`] = ` +{ + "asFragment": [Function], + "baseElement": +
+ +
+ , + "container":
+ +
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; diff --git a/src/components/mail/tray/__test__/__snapshots__/MessageCheapSkeleton.test.tsx.snap b/src/components/mail/tray/__test__/__snapshots__/MessageCheapSkeleton.test.tsx.snap new file mode 100644 index 0000000..94b16c6 --- /dev/null +++ b/src/components/mail/tray/__test__/__snapshots__/MessageCheapSkeleton.test.tsx.snap @@ -0,0 +1,126 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`MessageCheapSkeleton > should match snapshot 1`] = ` +{ + "asFragment": [Function], + "baseElement": +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ , + "container":
+
+
+
+
+
+
+
+
+
+
+
+
+
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; diff --git a/src/components/mail/tray/__test__/__snapshots__/TrayList.test.tsx.snap b/src/components/mail/tray/__test__/__snapshots__/TrayList.test.tsx.snap new file mode 100644 index 0000000..eaccb7e --- /dev/null +++ b/src/components/mail/tray/__test__/__snapshots__/TrayList.test.tsx.snap @@ -0,0 +1,300 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`TrayList > should match snapshot 1`] = ` +{ + "asFragment": [Function], + "baseElement": +
+
+
+
+
+ +
+
+ +
+
+
+
+
+ , + "container":
+
+
+
+
+ +
+
+ +
+
+
+
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`;