mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
8b1a238c51
* Put the atlas sparkline behind the text it was covering The graphic is absolutely positioned and comes after the label, value and delta in the DOM, so with no stacking order of its own the browser painted it on top of them. On the dashboard's atlas row a status card is about 190px wide at 1440px, and the 80px sparkline covered more than half the delta line (#1787). Give it z-index 0 and lift the three text rows to 1. It is decoration, so it stops taking pointer events while we are here. * Let a sentence-length sheet subtitle wrap TileHeader truncates its subtitle to one line. That is right for a filename or a date, and wrong for the reorder hint: between the 40px icon tile and the 34px close button there is not enough width for a sentence, so it was cut off mid-word (#1814), and every translation is longer than the English one. Opt in with subWrap where the subtitle is prose; the file sheets stay single-line. * Translate the phone entry view MobileEntryView carried its own English label tables while the desktop view resolved the same values through i18n, so a German reader got "Amazing" and "Sunny" on a phone and the translated chips on a tablet (#1846). Which view appears is decided by two different breakpoints, which is why it looked arbitrary. The labels are keys now, the edit action, the photo counter and the pros/cons headings go through t(), and the date formats against the chosen locale instead of the browser default. No new keys: all of them already exist in all 23 files. The colour classes stay local. The desktop table carries raw hex values and no dark-mode variants, so sharing one table would change how this looks. * Name Google account linking in the Cloudflare bot section The section claimed the block was specific to ChatGPT, so someone hitting it with Gemini had no reason to read on (#1667). Google exchanges the code from its own servers, which Bot Fight Mode blocks the same way, and the symptom differs enough to be worth spelling out: the browser half succeeds and linking fails afterwards, with a 200 on /api/oauth/authorize and no /oauth/token at all. Also note that Bot Fight Mode ignores IP allowlists, which is the first thing people try.
124 lines
5.3 KiB
TypeScript
124 lines
5.3 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import MDaysSheet from '../../../../src/mobile/screens/trip/sheets/MDaysSheet'
|
|
import type { MTripShellApi, TripPlanner } from '../../../../src/mobile/screens/trip/MTripShell'
|
|
import type { Day } from '../../../../src/types'
|
|
import { buildPlanner, buildShell } from '../../../helpers/mobileTrip'
|
|
import { resetAllStores } from '../../../helpers/store'
|
|
import { fireEvent, render, screen } from '../../../helpers/render'
|
|
|
|
// FE-MOB-DAYSS-001 to FE-MOB-DAYSS-012
|
|
//
|
|
// The sheet reads its copy from the real TranslationProvider (useTranslation),
|
|
// not from planner.t — assertions therefore go against the English strings.
|
|
|
|
// Deliberately unsorted: the sheet orders by day_number.
|
|
const DAYS = [
|
|
{ id: 3, trip_id: 1, day_number: 3, date: '2026-05-03', title: null },
|
|
{ id: 1, trip_id: 1, day_number: 1, date: '2026-05-01', title: null },
|
|
{ id: 2, trip_id: 1, day_number: 2, date: '2026-05-02', title: 'Old Town' },
|
|
] as unknown as Day[]
|
|
|
|
function renderSheet(plannerOverrides: Partial<TripPlanner> = {}, shellOverrides: Partial<MTripShellApi> = {}) {
|
|
const planner = buildPlanner({ days: DAYS, ...plannerOverrides })
|
|
const shell = buildShell({ sheet: { id: 'days' }, ...shellOverrides })
|
|
render(<MDaysSheet planner={planner} shell={shell} />)
|
|
return { planner, shell }
|
|
}
|
|
|
|
describe('MDaysSheet', () => {
|
|
beforeEach(() => {
|
|
resetAllStores()
|
|
})
|
|
|
|
it('FE-MOB-DAYSS-001: stays closed while another sheet id is active', () => {
|
|
renderSheet({}, { sheet: { id: 'mehr' } })
|
|
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('FE-MOB-DAYSS-002: stays closed when no sheet is open at all', () => {
|
|
renderSheet({}, { sheet: null })
|
|
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('FE-MOB-DAYSS-003: renders the reorder header with its hint', () => {
|
|
renderSheet()
|
|
expect(screen.getByRole('dialog', { name: 'Reorder days' })).toBeInTheDocument()
|
|
expect(screen.getByText("A day's places, notes and bookings move with it.")).toBeInTheDocument()
|
|
})
|
|
|
|
it('FE-MOB-DAYSS-003b: the hint wraps instead of being cut off mid-sentence', () => {
|
|
// The shared header truncates its subtitle to one line, which is right for a
|
|
// filename but cut this sentence off in every language (#1814).
|
|
renderSheet()
|
|
const hint = screen.getByText("A day's places, notes and bookings move with it.")
|
|
expect(hint).not.toHaveClass('truncate')
|
|
})
|
|
|
|
it('FE-MOB-DAYSS-004: lists the days in day_number order and numbers the rows', () => {
|
|
renderSheet()
|
|
const positions = screen.getAllByText(/^[123]$/).map(el => el.textContent)
|
|
expect(positions).toEqual(['1', '2', '3'])
|
|
const labels = ['Fri, May 1', 'Old Town', 'Sun, May 3']
|
|
for (const label of labels) expect(screen.getByText(label)).toBeInTheDocument()
|
|
})
|
|
|
|
it('FE-MOB-DAYSS-005: falls back to "Day n" when neither title nor a usable date exists', () => {
|
|
const days = [
|
|
{ id: 7, trip_id: 1, day_number: 1, date: null, title: null },
|
|
{ id: 8, trip_id: 1, day_number: 2, date: 'not-a-date', title: '' },
|
|
] as unknown as Day[]
|
|
renderSheet({ days })
|
|
expect(screen.getByText('Day 1')).toBeInTheDocument()
|
|
expect(screen.getByText('Day 2')).toBeInTheDocument()
|
|
})
|
|
|
|
it('FE-MOB-DAYSS-006: numbers a day without day_number by its list position', () => {
|
|
const days = [{ id: 9, trip_id: 1, day_number: null, date: null, title: null }] as unknown as Day[]
|
|
renderSheet({ days })
|
|
expect(screen.getByText('Day 1')).toBeInTheDocument()
|
|
})
|
|
|
|
it('FE-MOB-DAYSS-007: moving a day up sends the whole reordered id list', () => {
|
|
const { planner } = renderSheet()
|
|
fireEvent.click(screen.getAllByRole('button', { name: 'Move up' })[1])
|
|
expect(planner.handleReorderDays).toHaveBeenCalledWith([2, 1, 3])
|
|
})
|
|
|
|
it('FE-MOB-DAYSS-008: moving a day down sends the whole reordered id list', () => {
|
|
const { planner } = renderSheet()
|
|
fireEvent.click(screen.getAllByRole('button', { name: 'Move down' })[0])
|
|
expect(planner.handleReorderDays).toHaveBeenCalledWith([2, 1, 3])
|
|
})
|
|
|
|
it('FE-MOB-DAYSS-009: disables the moves that would leave the list', () => {
|
|
renderSheet()
|
|
const up = screen.getAllByRole('button', { name: 'Move up' })
|
|
const down = screen.getAllByRole('button', { name: 'Move down' })
|
|
expect(up[0]).toBeDisabled()
|
|
expect(up[2]).toBeEnabled()
|
|
expect(down[2]).toBeDisabled()
|
|
expect(down[0]).toBeEnabled()
|
|
})
|
|
|
|
it('FE-MOB-DAYSS-010: appends a day', () => {
|
|
const { planner } = renderSheet()
|
|
fireEvent.click(screen.getByRole('button', { name: 'Add day' }))
|
|
expect(planner.handleAddDay).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('FE-MOB-DAYSS-011: hides every edit affordance for a read-only member', () => {
|
|
const { planner } = renderSheet({ can: vi.fn(() => false) })
|
|
expect(planner.can).toHaveBeenCalledWith('day_edit', planner.trip)
|
|
expect(screen.queryByRole('button', { name: 'Move up' })).not.toBeInTheDocument()
|
|
expect(screen.queryByRole('button', { name: 'Add day' })).not.toBeInTheDocument()
|
|
// The list itself stays readable.
|
|
expect(screen.getByText('Old Town')).toBeInTheDocument()
|
|
})
|
|
|
|
it('FE-MOB-DAYSS-012: the header close button closes the sheet', () => {
|
|
const { shell } = renderSheet()
|
|
fireEvent.click(screen.getByRole('button', { name: 'Close' }))
|
|
expect(shell.closeSheet).toHaveBeenCalledTimes(1)
|
|
})
|
|
})
|