mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 13:21:46 +00:00
fix tests for sidebar/settings refactor + weather archive fallback
- DayPlanSidebar: add aria-label to undo button, replace title with aria-label
so tests can still locate buttons by accessible name after tooltip refactor
- tests: switch getByTitle("Add Note") to getByLabelText
- tests: find undo button via aria-label (new expand/collapse button also uses
width:30, breaking the old style-based lookup)
- PlacesSidebar tests: loosen "All" button regex to account for count badge
- DisplaySettingsTab tests: use getByRole for Auto button (two "Auto" spans
coexist for mobile/desktop); handle multiple English matches in lang test
- weatherService tests: past-date case now expects an archive fetch instead
of an immediate no_forecast error
This commit is contained in:
@@ -187,7 +187,7 @@ describe('DayPlanSidebar', () => {
|
|||||||
const assignments = { '10': [assignment] }
|
const assignments = { '10': [assignment] }
|
||||||
render(<DayPlanSidebar {...makeDefaultProps({ days: [day], places: [place], assignments })} />)
|
render(<DayPlanSidebar {...makeDefaultProps({ days: [day], places: [place], assignments })} />)
|
||||||
// The chevron button immediately follows the "Add Note" button (which has a title attribute)
|
// The chevron button immediately follows the "Add Note" button (which has a title attribute)
|
||||||
const addNoteBtn = screen.getByTitle('Add Note')
|
const addNoteBtn = screen.getByLabelText('Add Note')
|
||||||
const chevron = addNoteBtn.nextElementSibling as HTMLButtonElement
|
const chevron = addNoteBtn.nextElementSibling as HTMLButtonElement
|
||||||
expect(chevron).toBeTruthy()
|
expect(chevron).toBeTruthy()
|
||||||
await user.click(chevron)
|
await user.click(chevron)
|
||||||
@@ -201,7 +201,7 @@ describe('DayPlanSidebar', () => {
|
|||||||
const assignment = buildAssignment({ id: 99, day_id: 10, order_index: 0, place })
|
const assignment = buildAssignment({ id: 99, day_id: 10, order_index: 0, place })
|
||||||
const assignments = { '10': [assignment] }
|
const assignments = { '10': [assignment] }
|
||||||
render(<DayPlanSidebar {...makeDefaultProps({ days: [day], places: [place], assignments })} />)
|
render(<DayPlanSidebar {...makeDefaultProps({ days: [day], places: [place], assignments })} />)
|
||||||
const getChevron = () => screen.getByTitle('Add Note').nextElementSibling as HTMLButtonElement
|
const getChevron = () => screen.getByLabelText('Add Note').nextElementSibling as HTMLButtonElement
|
||||||
await user.click(getChevron()) // collapse
|
await user.click(getChevron()) // collapse
|
||||||
expect(screen.queryByText('Eiffel Tower')).not.toBeInTheDocument()
|
expect(screen.queryByText('Eiffel Tower')).not.toBeInTheDocument()
|
||||||
await user.click(getChevron()) // re-expand
|
await user.click(getChevron()) // re-expand
|
||||||
@@ -362,28 +362,14 @@ describe('DayPlanSidebar', () => {
|
|||||||
const user = userEvent.setup()
|
const user = userEvent.setup()
|
||||||
const onUndo = vi.fn()
|
const onUndo = vi.fn()
|
||||||
render(<DayPlanSidebar {...makeDefaultProps({ canUndo: true, lastActionLabel: 'Removed place', onUndo })} />)
|
render(<DayPlanSidebar {...makeDefaultProps({ canUndo: true, lastActionLabel: 'Removed place', onUndo })} />)
|
||||||
// Find the undo button — it has width 30, height 30 and is not disabled
|
const undoBtn = screen.getByLabelText('Undo')
|
||||||
const buttons = screen.getAllByRole('button')
|
await user.click(undoBtn)
|
||||||
// The undo button is the one with the Undo2 icon and is not disabled
|
expect(onUndo).toHaveBeenCalled()
|
||||||
const undoBtn = buttons.find(btn => {
|
|
||||||
const style = btn.getAttribute('style') || ''
|
|
||||||
return style.includes('width: 30px') || style.includes('width:30px') || (style.includes('30') && !btn.disabled)
|
|
||||||
})
|
|
||||||
if (undoBtn) {
|
|
||||||
await user.click(undoBtn)
|
|
||||||
expect(onUndo).toHaveBeenCalled()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('FE-PLANNER-DAYPLAN-024: undo button not present when onUndo not provided', () => {
|
it('FE-PLANNER-DAYPLAN-024: undo button not present when onUndo not provided', () => {
|
||||||
render(<DayPlanSidebar {...makeDefaultProps({ canUndo: false })} />)
|
render(<DayPlanSidebar {...makeDefaultProps({ canUndo: false })} />)
|
||||||
// When onUndo is not provided, the undo section is not rendered at all
|
expect(screen.queryByLabelText('Undo')).toBeNull()
|
||||||
const buttons = screen.getAllByRole('button')
|
|
||||||
const undoBtn = buttons.find(btn => {
|
|
||||||
const style = btn.getAttribute('style') || ''
|
|
||||||
return style.includes('width: 30px')
|
|
||||||
})
|
|
||||||
expect(undoBtn).toBeUndefined()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// ── PDF export ──────────────────────────────────────────────────────────
|
// ── PDF export ──────────────────────────────────────────────────────────
|
||||||
@@ -931,7 +917,7 @@ describe('DayPlanSidebar', () => {
|
|||||||
const user = userEvent.setup()
|
const user = userEvent.setup()
|
||||||
const day = buildDay({ id: 10, date: '2025-06-01', title: 'Day 1' })
|
const day = buildDay({ id: 10, date: '2025-06-01', title: 'Day 1' })
|
||||||
render(<DayPlanSidebar {...makeDefaultProps({ days: [day] })} />)
|
render(<DayPlanSidebar {...makeDefaultProps({ days: [day] })} />)
|
||||||
const addNoteBtn = screen.getByTitle('Add Note')
|
const addNoteBtn = screen.getByLabelText('Add Note')
|
||||||
await user.click(addNoteBtn)
|
await user.click(addNoteBtn)
|
||||||
expect(mockDayNotesState.openAddNote).toHaveBeenCalled()
|
expect(mockDayNotesState.openAddNote).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1074,6 +1074,7 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar({
|
|||||||
<button
|
<button
|
||||||
onClick={onUndo}
|
onClick={onUndo}
|
||||||
disabled={!canUndo}
|
disabled={!canUndo}
|
||||||
|
aria-label={t('undo.button')}
|
||||||
onMouseEnter={() => setUndoHover(true)}
|
onMouseEnter={() => setUndoHover(true)}
|
||||||
onMouseLeave={() => setUndoHover(false)}
|
onMouseLeave={() => setUndoHover(false)}
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ describe('Filter tabs', () => {
|
|||||||
const assignments = { '1': [buildAssignment({ place: planned, day_id: 1 })] };
|
const assignments = { '1': [buildAssignment({ place: planned, day_id: 1 })] };
|
||||||
render(<PlacesSidebar {...defaultProps} places={[planned, unplanned]} assignments={assignments} />);
|
render(<PlacesSidebar {...defaultProps} places={[planned, unplanned]} assignments={assignments} />);
|
||||||
await user.click(screen.getByRole('button', { name: /Unplanned/i }));
|
await user.click(screen.getByRole('button', { name: /Unplanned/i }));
|
||||||
await user.click(screen.getByRole('button', { name: /^All$/i }));
|
await user.click(screen.getByRole('button', { name: /^All/i }));
|
||||||
expect(screen.getByText('Planned Place')).toBeInTheDocument();
|
expect(screen.getByText('Planned Place')).toBeInTheDocument();
|
||||||
expect(screen.getByText('Unplanned Place')).toBeInTheDocument();
|
expect(screen.getByText('Unplanned Place')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ describe('DisplaySettingsTab', () => {
|
|||||||
|
|
||||||
it('FE-COMP-DISPLAY-005: shows Auto mode button', () => {
|
it('FE-COMP-DISPLAY-005: shows Auto mode button', () => {
|
||||||
render(<DisplaySettingsTab />);
|
render(<DisplaySettingsTab />);
|
||||||
expect(screen.getByText('Auto')).toBeInTheDocument();
|
expect(screen.getByRole('button', { name: /Auto/i })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('FE-COMP-DISPLAY-006: shows Language section', () => {
|
it('FE-COMP-DISPLAY-006: shows Language section', () => {
|
||||||
@@ -95,16 +95,16 @@ describe('DisplaySettingsTab', () => {
|
|||||||
const updateSetting = vi.fn().mockResolvedValue(undefined);
|
const updateSetting = vi.fn().mockResolvedValue(undefined);
|
||||||
seedStore(useSettingsStore, { settings: buildSettings({ dark_mode: 'light' }), updateSetting });
|
seedStore(useSettingsStore, { settings: buildSettings({ dark_mode: 'light' }), updateSetting });
|
||||||
render(<DisplaySettingsTab />);
|
render(<DisplaySettingsTab />);
|
||||||
await user.click(screen.getByText('Auto'));
|
await user.click(screen.getByRole('button', { name: /Auto/i }));
|
||||||
expect(updateSetting).toHaveBeenCalledWith('dark_mode', 'auto');
|
expect(updateSetting).toHaveBeenCalledWith('dark_mode', 'auto');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('FE-COMP-DISPLAY-014: active color mode button has border with var(--text-primary)', () => {
|
it('FE-COMP-DISPLAY-014: active color mode button has border with var(--text-primary)', () => {
|
||||||
seedStore(useSettingsStore, { settings: buildSettings({ dark_mode: 'dark' }) });
|
seedStore(useSettingsStore, { settings: buildSettings({ dark_mode: 'dark' }) });
|
||||||
render(<DisplaySettingsTab />);
|
render(<DisplaySettingsTab />);
|
||||||
const darkBtn = screen.getByText('Dark').closest('button')!;
|
const darkBtn = screen.getByRole('button', { name: /^Dark$/i });
|
||||||
const lightBtn = screen.getByText('Light').closest('button')!;
|
const lightBtn = screen.getByRole('button', { name: /^Light$/i });
|
||||||
const autoBtn = screen.getByText('Auto').closest('button')!;
|
const autoBtn = screen.getByRole('button', { name: /Auto/i });
|
||||||
expect(darkBtn.style.border).toContain('var(--text-primary)');
|
expect(darkBtn.style.border).toContain('var(--text-primary)');
|
||||||
expect(lightBtn.style.border).toContain('var(--border-primary)');
|
expect(lightBtn.style.border).toContain('var(--border-primary)');
|
||||||
expect(autoBtn.style.border).toContain('var(--border-primary)');
|
expect(autoBtn.style.border).toContain('var(--border-primary)');
|
||||||
@@ -122,8 +122,11 @@ describe('DisplaySettingsTab', () => {
|
|||||||
it('FE-COMP-DISPLAY-016: active language button is visually highlighted', () => {
|
it('FE-COMP-DISPLAY-016: active language button is visually highlighted', () => {
|
||||||
seedStore(useSettingsStore, { settings: buildSettings({ language: 'en' }) });
|
seedStore(useSettingsStore, { settings: buildSettings({ language: 'en' }) });
|
||||||
render(<DisplaySettingsTab />);
|
render(<DisplaySettingsTab />);
|
||||||
const englishBtn = screen.getByText('English').closest('button')!;
|
// Multiple elements contain "English" (desktop grid button + mobile dropdown trigger).
|
||||||
expect(englishBtn.style.border).toContain('var(--text-primary)');
|
// The desktop grid button is the one with the active border style.
|
||||||
|
const englishMatches = screen.getAllByText('English').map(el => el.closest('button')!).filter(Boolean);
|
||||||
|
const activeBtn = englishMatches.find(btn => (btn.style.border || '').includes('var(--text-primary)'));
|
||||||
|
expect(activeBtn).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('FE-COMP-DISPLAY-017: shows Temperature section label', () => {
|
it('FE-COMP-DISPLAY-017: shows Temperature section label', () => {
|
||||||
|
|||||||
@@ -282,13 +282,36 @@ describe('getWeather', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('with date — past date (diffDays < -1)', () => {
|
describe('with date — past date (diffDays < -1)', () => {
|
||||||
it('returns no_forecast error immediately without fetching', async () => {
|
it('returns forecast-type WeatherResult from the archive API', async () => {
|
||||||
const date = dateOffset(-5); // 5 days in the past
|
const date = dateOffset(-5); // 5 days in the past
|
||||||
|
const archiveBody = {
|
||||||
|
daily: {
|
||||||
|
time: [date],
|
||||||
|
temperature_2m_max: [18],
|
||||||
|
temperature_2m_min: [10],
|
||||||
|
weathercode: [2],
|
||||||
|
precipitation_sum: [0],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
vi.mocked(fetch).mockResolvedValueOnce(mockResponse(archiveBody));
|
||||||
|
|
||||||
const result = await getWeather('14.00', '24.00', date, 'en');
|
const result = await getWeather('14.00', '24.00', date, 'en');
|
||||||
|
|
||||||
|
expect(result.type).toBe('forecast');
|
||||||
|
expect(result.temp).toBe(14);
|
||||||
|
expect(result.temp_max).toBe(18);
|
||||||
|
expect(result.temp_min).toBe(10);
|
||||||
|
expect(fetch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(vi.mocked(fetch).mock.calls[0][0]).toContain('archive-api.open-meteo.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns no_forecast error when archive has no data for the date', async () => {
|
||||||
|
const date = dateOffset(-5);
|
||||||
|
vi.mocked(fetch).mockResolvedValueOnce(mockResponse({ daily: { time: [], temperature_2m_max: [], temperature_2m_min: [], weathercode: [] } }));
|
||||||
|
|
||||||
|
const result = await getWeather('14.01', '24.01', date, 'en');
|
||||||
|
|
||||||
expect(result.error).toBe('no_forecast');
|
expect(result.error).toBe('no_forecast');
|
||||||
expect(fetch).not.toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user