mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-20 22:16:26 +00:00
152 lines
6.3 KiB
TypeScript
152 lines
6.3 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest'
|
|
import type { ReservationTraveler } from '@trek/shared'
|
|
import {
|
|
CountPill, Field, SectionHeader, StatusDot, TabScroller, TravelerAvatars, TravelerFilterRow,
|
|
} from '../../../../src/mobile/screens/trip/tabs/tabChrome'
|
|
import { fireEvent, render, screen } from '../../../helpers/render'
|
|
|
|
// FE-MOB-TABCHR-001 to FE-MOB-TABCHR-014
|
|
|
|
function traveler(overrides: Partial<ReservationTraveler>): ReservationTraveler {
|
|
return {
|
|
user_id: 1, username: 'Ada', avatar: null, avatar_url: null, is_guest: 0,
|
|
...overrides,
|
|
} as unknown as ReservationTraveler
|
|
}
|
|
|
|
describe('tabChrome', () => {
|
|
it('FE-MOB-TABCHR-001: TabScroller puts its children in the scroll body with dock + top clearance', () => {
|
|
const { container } = render(<TabScroller><p>panel body</p></TabScroller>)
|
|
const body = screen.getByText('panel body').parentElement as HTMLElement
|
|
expect(body).toHaveClass('overflow-y-auto')
|
|
expect(body.className).toContain('--bottom-nav-h')
|
|
expect(body.className).toContain('--m-safe-top')
|
|
expect(container.firstElementChild).toHaveClass('h-full')
|
|
})
|
|
|
|
it('FE-MOB-TABCHR-002: CountPill renders its count', () => {
|
|
render(<CountPill>7</CountPill>)
|
|
expect(screen.getByText('7')).toBeInTheDocument()
|
|
})
|
|
|
|
it('FE-MOB-TABCHR-003: SectionHeader exposes the open state and its count pill', () => {
|
|
render(<SectionHeader label="Confirmed" count={3} open onToggle={vi.fn()} />)
|
|
const header = screen.getByRole('button', { name: /Confirmed/ })
|
|
expect(header).toHaveAttribute('aria-expanded', 'true')
|
|
expect(screen.getByText('3')).toBeInTheDocument()
|
|
// Open → chevron points up.
|
|
expect(header.querySelector('.lucide-chevron-up')).not.toBeNull()
|
|
expect(header.querySelector('.lucide-chevron-down')).toBeNull()
|
|
})
|
|
|
|
it('FE-MOB-TABCHR-004: SectionHeader flips the chevron and drops the pill when collapsed without a count', () => {
|
|
render(<SectionHeader label="Pending" open={false} onToggle={vi.fn()} />)
|
|
const header = screen.getByRole('button', { name: 'Pending' })
|
|
expect(header).toHaveAttribute('aria-expanded', 'false')
|
|
expect(header.querySelector('.lucide-chevron-down')).not.toBeNull()
|
|
expect(header.textContent).toBe('Pending')
|
|
})
|
|
|
|
it('FE-MOB-TABCHR-005: SectionHeader reports taps', () => {
|
|
const onToggle = vi.fn()
|
|
render(<SectionHeader label="Pending" open={false} onToggle={onToggle} />)
|
|
fireEvent.click(screen.getByRole('button', { name: 'Pending' }))
|
|
expect(onToggle).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('FE-MOB-TABCHR-006: Field pairs its label with the value box and takes the caller width', () => {
|
|
const { container } = render(<Field label="Departure" className="flex-1">08:15</Field>)
|
|
expect(screen.getByText('Departure')).toBeInTheDocument()
|
|
const value = screen.getByText('08:15')
|
|
expect(value).not.toHaveClass('tabular-nums')
|
|
expect(container.firstElementChild).toHaveClass('flex-1')
|
|
})
|
|
|
|
it('FE-MOB-TABCHR-007: Field aligns numbers when asked to', () => {
|
|
render(<Field label="Price" tabular>12,50 €</Field>)
|
|
expect(screen.getByText('12,50 €')).toHaveClass('tabular-nums')
|
|
})
|
|
|
|
it('FE-MOB-TABCHR-008: StatusDot paints the passed token', () => {
|
|
const { container } = render(<StatusDot color="var(--m-st-confirmed)" />)
|
|
expect(container.firstElementChild).toHaveStyle({ background: 'var(--m-st-confirmed)' })
|
|
})
|
|
|
|
it('FE-MOB-TABCHR-009: TravelerAvatars renders nothing when nobody is assigned', () => {
|
|
const { container } = render(<TravelerAvatars travelers={[]} label="Travelers" />)
|
|
expect(container).toBeEmptyDOMElement()
|
|
})
|
|
|
|
it('FE-MOB-TABCHR-010: TravelerAvatars shows the label and one chip per traveler', () => {
|
|
render(
|
|
<TravelerAvatars
|
|
travelers={[traveler({ user_id: 1, username: 'Ada' }), traveler({ user_id: 2, username: 'Bob' })]}
|
|
label="Travelers"
|
|
/>,
|
|
)
|
|
expect(screen.getByText('Travelers')).toBeInTheDocument()
|
|
expect(screen.getByText('Ada')).toBeInTheDocument()
|
|
expect(screen.getByText('Bob')).toBeInTheDocument()
|
|
// No avatar → initial only.
|
|
expect(screen.getByText('A')).toBeInTheDocument()
|
|
})
|
|
|
|
it('FE-MOB-TABCHR-011: TravelerAvatars prefers avatar_url and falls back to the raw avatar path', () => {
|
|
const { container } = render(
|
|
<TravelerAvatars
|
|
travelers={[
|
|
traveler({ user_id: 1, username: 'Ada', avatar_url: '/uploads/avatars/ada.png' }),
|
|
traveler({ user_id: 2, username: 'Bob', avatar: 'bob.jpg' }),
|
|
traveler({ user_id: 3, username: 'Cid', avatar: 'https://idp.example/cid.png' }),
|
|
]}
|
|
label="Travelers"
|
|
/>,
|
|
)
|
|
const srcs = [...container.querySelectorAll('img')].map(i => i.getAttribute('src'))
|
|
expect(srcs).toEqual([
|
|
'/uploads/avatars/ada.png',
|
|
'/uploads/avatars/bob.jpg',
|
|
'https://idp.example/cid.png',
|
|
])
|
|
})
|
|
|
|
it('FE-MOB-TABCHR-012: TravelerAvatars marks accountless guests', () => {
|
|
render(<TravelerAvatars travelers={[traveler({ user_id: 9, username: 'Zoe', is_guest: 1 })]} label="Travelers" />)
|
|
expect(screen.getByText('Guest')).toBeInTheDocument()
|
|
})
|
|
|
|
it('FE-MOB-TABCHR-013: TravelerFilterRow toggles a member and dims the inactive ones', () => {
|
|
const onToggle = vi.fn()
|
|
render(
|
|
<TravelerFilterRow
|
|
members={[{ id: 1, username: 'Ada' }, { id: 2, username: 'Bob', avatar_url: '/uploads/avatars/bob.png' }]}
|
|
active={new Set([1])}
|
|
onToggle={onToggle}
|
|
label="Filter by traveler"
|
|
/>,
|
|
)
|
|
expect(screen.getByLabelText('Filter by traveler')).toBeInTheDocument()
|
|
const ada = screen.getByTitle('Ada')
|
|
const bob = screen.getByTitle('Bob')
|
|
expect(ada.className).toContain('opacity-100')
|
|
expect(bob.className).toContain('opacity-40')
|
|
expect(bob.querySelector('img')).toHaveAttribute('src', '/uploads/avatars/bob.png')
|
|
|
|
fireEvent.click(bob)
|
|
expect(onToggle).toHaveBeenCalledWith(2)
|
|
})
|
|
|
|
it('FE-MOB-TABCHR-014: TravelerFilterRow keeps everyone at full opacity while no filter is set', () => {
|
|
render(
|
|
<TravelerFilterRow
|
|
members={[{ id: 1, username: 'Ada' }, { id: 2, username: 'Bob' }]}
|
|
active={new Set()}
|
|
onToggle={vi.fn()}
|
|
label="Filter by traveler"
|
|
/>,
|
|
)
|
|
expect(screen.getByTitle('Ada').className).toContain('opacity-100')
|
|
expect(screen.getByTitle('Bob').className).toContain('opacity-100')
|
|
})
|
|
})
|