diff --git a/client/src/components/Admin/AddonManager.test.tsx b/client/src/components/Admin/AddonManager.test.tsx
index cfffacead..14afcc882 100644
--- a/client/src/components/Admin/AddonManager.test.tsx
+++ b/client/src/components/Admin/AddonManager.test.tsx
@@ -98,7 +98,7 @@ describe('AddonManager', () => {
it('FE-ADMIN-ADDON-002: empty state when addons list is empty', async () => {
render();
- await screen.findByText('No addons available');
+ expect(await screen.findByText('No addons available')).toBeInTheDocument();
});
it('FE-ADMIN-ADDON-003: trip addons section renders with correct section header', async () => {
diff --git a/client/src/components/Admin/AdminMcpTokensPanel.test.tsx b/client/src/components/Admin/AdminMcpTokensPanel.test.tsx
index 8abcd44da..edd016b1c 100644
--- a/client/src/components/Admin/AdminMcpTokensPanel.test.tsx
+++ b/client/src/components/Admin/AdminMcpTokensPanel.test.tsx
@@ -49,7 +49,7 @@ describe('AdminMcpTokensPanel', () => {
it('FE-ADMIN-MCP-002: empty state rendered when no tokens', async () => {
render();
- await screen.findByText('No MCP tokens have been created yet');
+ expect(await screen.findByText('No MCP tokens have been created yet')).toBeInTheDocument();
});
it('FE-ADMIN-MCP-003: token list renders correctly', async () => {
@@ -195,7 +195,7 @@ describe('AdminMcpTokensPanel', () => {
)
);
render(<>>);
- await screen.findByText('Failed to load tokens');
+ expect(await screen.findByText('Failed to load tokens')).toBeInTheDocument();
});
it('FE-ADMIN-MCP-011: OAuth sessions loading spinner shown on mount', async () => {
@@ -216,7 +216,7 @@ describe('AdminMcpTokensPanel', () => {
)
);
render();
- await screen.findByText('No active OAuth sessions');
+ expect(await screen.findByText('No active OAuth sessions')).toBeInTheDocument();
});
it('FE-ADMIN-MCP-013: OAuth sessions list renders with scopes', async () => {
@@ -318,6 +318,6 @@ describe('AdminMcpTokensPanel', () => {
const deleteBtns = screen.getAllByRole('button', { name: 'Delete' });
const confirmBtn = deleteBtns.find(b => !b.title);
await user.click(confirmBtn ?? deleteBtns[deleteBtns.length - 1]);
- await screen.findByText('Failed to revoke session');
+ expect(await screen.findByText('Failed to revoke session')).toBeInTheDocument();
});
});
diff --git a/client/src/components/Admin/CategoryManager.test.tsx b/client/src/components/Admin/CategoryManager.test.tsx
index d3e5173cb..945ea698d 100644
--- a/client/src/components/Admin/CategoryManager.test.tsx
+++ b/client/src/components/Admin/CategoryManager.test.tsx
@@ -27,17 +27,17 @@ describe('CategoryManager', () => {
it('FE-COMP-CAT-002: shows Categories title', async () => {
render();
- await screen.findByText('Categories');
+ expect(await screen.findByText('Categories')).toBeInTheDocument();
});
it('FE-COMP-CAT-003: shows empty state when no categories', async () => {
render();
- await screen.findByText('No categories yet');
+ expect(await screen.findByText('No categories yet')).toBeInTheDocument();
});
it('FE-COMP-CAT-004: shows New Category button', async () => {
render();
- await screen.findByText('New Category');
+ expect(await screen.findByText('New Category')).toBeInTheDocument();
});
it('FE-COMP-CAT-005: clicking New Category shows form', async () => {
@@ -129,7 +129,7 @@ describe('CategoryManager', () => {
it('FE-COMP-CAT-010: shows subtitle text', async () => {
render();
- await screen.findByText('Manage categories for places');
+ expect(await screen.findByText('Manage categories for places')).toBeInTheDocument();
});
it('FE-COMP-CAT-011: category count is shown', async () => {
diff --git a/client/src/components/Admin/DevNotificationsPanel.test.tsx b/client/src/components/Admin/DevNotificationsPanel.test.tsx
index 35932e52e..2499bf595 100644
--- a/client/src/components/Admin/DevNotificationsPanel.test.tsx
+++ b/client/src/components/Admin/DevNotificationsPanel.test.tsx
@@ -87,7 +87,7 @@ describe('DevNotificationsPanel', () => {
render(<>>);
await screen.findByText('Type Testing');
await user.click(screen.getByText('Simple → Me').closest('button')!);
- await screen.findByText('Sent: simple-me');
+ expect(await screen.findByText('Sent: simple-me')).toBeInTheDocument();
});
it('FE-ADMIN-DEVNOTIF-007: all buttons disabled while a send is in-flight', async () => {
@@ -120,7 +120,7 @@ describe('DevNotificationsPanel', () => {
render(<>>);
await screen.findByText('Type Testing');
await user.click(screen.getByText('Simple → Me').closest('button')!);
- await screen.findByText('No channel configured');
+ expect(await screen.findByText('No channel configured')).toBeInTheDocument();
});
it('FE-ADMIN-DEVNOTIF-008b: a failure without an error field falls back to the generic text', async () => {
@@ -133,7 +133,7 @@ describe('DevNotificationsPanel', () => {
render(<>>);
await screen.findByText('Type Testing');
await user.click(screen.getByText('Simple → Me').closest('button')!);
- await screen.findByText('Failed');
+ expect(await screen.findByText('Failed')).toBeInTheDocument();
});
it('FE-ADMIN-DEVNOTIF-009: changing trip selector updates payload targetId', async () => {
diff --git a/client/src/components/Admin/GitHubPanel.test.tsx b/client/src/components/Admin/GitHubPanel.test.tsx
index 57b6d1315..b369d200f 100644
--- a/client/src/components/Admin/GitHubPanel.test.tsx
+++ b/client/src/components/Admin/GitHubPanel.test.tsx
@@ -231,7 +231,7 @@ describe('GitHubPanel', () => {
render();
await screen.findByText('v1.7.0');
await user.click(screen.getByText('Show details'));
- await screen.findByText('This is a plain paragraph without any markdown syntax.');
+ expect(await screen.findByText('This is a plain paragraph without any markdown syntax.')).toBeInTheDocument();
});
it('FE-ADMIN-GH-014: markdown link with safe href renders as anchor', async () => {
diff --git a/client/src/components/Admin/PackingTemplateManager.test.tsx b/client/src/components/Admin/PackingTemplateManager.test.tsx
index 8abb51cde..293d60801 100644
--- a/client/src/components/Admin/PackingTemplateManager.test.tsx
+++ b/client/src/components/Admin/PackingTemplateManager.test.tsx
@@ -250,7 +250,7 @@ describe('PackingTemplateManager', () => {
await user.click(screen.getByText('Add category'));
const catInput = screen.getByPlaceholderText('Category name (e.g. Clothing)');
await user.type(catInput, 'Electronics{Enter}');
- await screen.findByText('Electronics');
+ expect(await screen.findByText('Electronics')).toBeInTheDocument();
});
it('FE-ADMIN-PKG-012: adding an item to a category', async () => {
@@ -280,7 +280,7 @@ describe('PackingTemplateManager', () => {
await user.type(itemInput, 'Sandals');
// Submit via Enter key (the input's onKeyDown handler triggers handleAddItem)
await user.type(itemInput, '{Enter}');
- await screen.findByText('Sandals');
+ expect(await screen.findByText('Sandals')).toBeInTheDocument();
});
it('FE-ADMIN-PKG-013: renaming a category inline updates its name', async () => {
@@ -312,7 +312,7 @@ describe('PackingTemplateManager', () => {
const catInput = screen.getByDisplayValue('Clothing');
await user.clear(catInput);
await user.type(catInput, 'Shoes{Enter}');
- await screen.findByText('Shoes');
+ expect(await screen.findByText('Shoes')).toBeInTheDocument();
});
it('FE-ADMIN-PKG-014: deleting a category removes it and its items', async () => {
@@ -379,7 +379,7 @@ describe('PackingTemplateManager', () => {
const input = screen.getByDisplayValue('T-shirt');
await user.clear(input);
await user.type(input, 'Tank Top{Enter}');
- await screen.findByText('Tank Top');
+ expect(await screen.findByText('Tank Top')).toBeInTheDocument();
});
it('FE-ADMIN-PKG-016: deleting an item removes it from the list', async () => {
diff --git a/client/src/components/Admin/PermissionsPanel.test.tsx b/client/src/components/Admin/PermissionsPanel.test.tsx
index fb7323ecf..d53ab7464 100644
--- a/client/src/components/Admin/PermissionsPanel.test.tsx
+++ b/client/src/components/Admin/PermissionsPanel.test.tsx
@@ -269,6 +269,6 @@ describe('PermissionsPanel', () => {
),
);
renderPanel();
- await screen.findByText('Error');
+ expect(await screen.findByText('Error')).toBeInTheDocument();
});
});
diff --git a/client/src/components/Budget/BudgetPanel.test.tsx b/client/src/components/Budget/BudgetPanel.test.tsx
index fa8e496d9..7844e766d 100644
--- a/client/src/components/Budget/BudgetPanel.test.tsx
+++ b/client/src/components/Budget/BudgetPanel.test.tsx
@@ -32,7 +32,7 @@ describe('BudgetPanel', () => {
http.get('/api/trips/1/budget', () => HttpResponse.json({ items: [] }))
);
render();
- await screen.findByText('No budget created yet');
+ expect(await screen.findByText('No budget created yet')).toBeInTheDocument();
});
it('FE-COMP-BUDGET-002: shows empty state text body', async () => {
@@ -40,7 +40,7 @@ describe('BudgetPanel', () => {
http.get('/api/trips/1/budget', () => HttpResponse.json({ items: [] }))
);
render();
- await screen.findByText(/Create categories and entries/i);
+ expect(await screen.findByText(/Create categories and entries/i)).toBeInTheDocument();
});
it('FE-COMP-BUDGET-003: shows category input in empty state when user can edit', async () => {
@@ -48,7 +48,7 @@ describe('BudgetPanel', () => {
http.get('/api/trips/1/budget', () => HttpResponse.json({ items: [] }))
);
render();
- await screen.findByPlaceholderText('Enter category name...');
+ expect(await screen.findByPlaceholderText('Enter category name...')).toBeInTheDocument();
});
it('FE-COMP-BUDGET-004: renders budget items from store after load', async () => {
@@ -57,7 +57,7 @@ describe('BudgetPanel', () => {
http.get('/api/trips/1/budget', () => HttpResponse.json({ items: [item] }))
);
render();
- await screen.findByText('Hotel Paris');
+ expect(await screen.findByText('Hotel Paris')).toBeInTheDocument();
});
it('FE-COMP-BUDGET-005: renders category section header', async () => {
@@ -87,7 +87,7 @@ describe('BudgetPanel', () => {
http.get('/api/trips/1/budget', () => HttpResponse.json({ items: [item] }))
);
render();
- await screen.findByText('Budget');
+ expect(await screen.findByText('Budget')).toBeInTheDocument();
});
it('FE-COMP-BUDGET-008: shows CSV export button', async () => {
@@ -96,7 +96,7 @@ describe('BudgetPanel', () => {
http.get('/api/trips/1/budget', () => HttpResponse.json({ items: [item] }))
);
render();
- await screen.findByText('CSV');
+ expect(await screen.findByText('CSV')).toBeInTheDocument();
});
it('FE-COMP-BUDGET-009: add item row visible in table', async () => {
@@ -105,7 +105,7 @@ describe('BudgetPanel', () => {
http.get('/api/trips/1/budget', () => HttpResponse.json({ items: [item] }))
);
render();
- await screen.findByPlaceholderText('New Entry');
+ expect(await screen.findByPlaceholderText('New Entry')).toBeInTheDocument();
});
it('FE-COMP-BUDGET-010: adding new item via form calls POST and shows item', async () => {
@@ -124,7 +124,7 @@ describe('BudgetPanel', () => {
await user.type(nameInput, 'Restaurant Dinner');
const addBtn = screen.getByTitle('Add Reservation');
await user.click(addBtn);
- await screen.findByText('Restaurant Dinner');
+ expect(await screen.findByText('Restaurant Dinner')).toBeInTheDocument();
});
it('FE-COMP-BUDGET-011: delete button present for items when user can edit', async () => {
@@ -161,7 +161,7 @@ describe('BudgetPanel', () => {
);
render();
await screen.findByText('Hotel A');
- await screen.findByText('Hotel B');
+ expect(await screen.findByText('Hotel B')).toBeInTheDocument();
});
it('FE-COMP-BUDGET-014: items from different categories render separate sections', async () => {
@@ -183,7 +183,7 @@ describe('BudgetPanel', () => {
);
render();
// Component renders even in empty state
- await screen.findByText('No budget created yet');
+ expect(await screen.findByText('No budget created yet')).toBeInTheDocument();
});
it('FE-COMP-BUDGET-016: trip currency EUR is shown in header for item rows', async () => {
@@ -193,7 +193,7 @@ describe('BudgetPanel', () => {
http.get('/api/trips/1/budget', () => HttpResponse.json({ items: [item] }))
);
render();
- await screen.findByText('Misc');
+ expect(await screen.findByText('Misc')).toBeInTheDocument();
// Row exists - EUR formatting would appear in values
});
@@ -233,7 +233,7 @@ describe('BudgetPanel', () => {
render();
const nameInput = await screen.findByPlaceholderText('New Entry');
await user.type(nameInput, 'Pizza{Enter}');
- await screen.findByText('Pizza');
+ expect(await screen.findByText('Pizza')).toBeInTheDocument();
});
it('FE-COMP-BUDGET-020: component renders without crashing with empty tripMembers', async () => {
@@ -241,7 +241,7 @@ describe('BudgetPanel', () => {
http.get('/api/trips/1/budget', () => HttpResponse.json({ items: [] }))
);
render();
- await screen.findByText('No budget created yet');
+ expect(await screen.findByText('No budget created yet')).toBeInTheDocument();
});
it('FE-COMP-BUDGET-021: inline edit name cell — clicking a name cell makes it editable', async () => {
@@ -342,7 +342,7 @@ describe('BudgetPanel', () => {
http.get('/api/trips/1/budget', () => HttpResponse.json({ items: [] }))
);
render();
- await screen.findByPlaceholderText('Enter category name...');
+ expect(await screen.findByPlaceholderText('Enter category name...')).toBeInTheDocument();
});
it('FE-COMP-BUDGET-028: creating a new category via input calls POST and adds a section', async () => {
@@ -356,7 +356,7 @@ describe('BudgetPanel', () => {
render();
const input = await screen.findByPlaceholderText('Enter category name...');
await user.type(input, 'Souvenirs{Enter}');
- await screen.findByText('Souvenirs');
+ expect(await screen.findByText('Souvenirs')).toBeInTheDocument();
});
it('FE-COMP-BUDGET-029: settlement section renders flows with usernames', async () => {
@@ -387,7 +387,7 @@ describe('BudgetPanel', () => {
await user.click(settlementBtn);
// alice and bob should appear in balances section
await screen.findByText('alice');
- await screen.findByText('bob');
+ expect(await screen.findByText('bob')).toBeInTheDocument();
});
it('FE-COMP-BUDGET-030: per-person summary renders usernames', async () => {
@@ -408,7 +408,7 @@ describe('BudgetPanel', () => {
];
render();
await screen.findByText('Shared Dinner');
- await screen.findByText('testuser');
+ expect(await screen.findByText('testuser')).toBeInTheDocument();
});
it('FE-COMP-BUDGET-032: grand total row shows sum across all categories', async () => {
@@ -451,7 +451,7 @@ describe('BudgetPanel', () => {
render();
await screen.findByText('Train');
// expense_date is rendered as plain text in read-only mode
- await screen.findByText('2025-06-15');
+ expect(await screen.findByText('2025-06-15')).toBeInTheDocument();
});
it('FE-COMP-BUDGET-035: settlement section with avatar renders user avatar image', async () => {
diff --git a/client/src/components/Collab/CollabChat.test.tsx b/client/src/components/Collab/CollabChat.test.tsx
index 34f6339e6..2f818b142 100644
--- a/client/src/components/Collab/CollabChat.test.tsx
+++ b/client/src/components/Collab/CollabChat.test.tsx
@@ -53,7 +53,7 @@ describe('CollabChat', () => {
it('FE-COMP-CHAT-002: shows empty state when no messages', async () => {
render();
- await screen.findByText('Start the conversation');
+ expect(await screen.findByText('Start the conversation')).toBeInTheDocument();
});
it('FE-COMP-CHAT-003: shows message input placeholder', async () => {
@@ -85,7 +85,7 @@ describe('CollabChat', () => {
)
);
render();
- await screen.findByText('Hello world!');
+ expect(await screen.findByText('Hello world!')).toBeInTheDocument();
});
it('FE-COMP-CHAT-006: typing in input updates text field', async () => {
@@ -599,7 +599,7 @@ describe('CollabChat', () => {
await screen.findByText('New 100');
const loadMoreBtn = screen.getByRole('button', { name: /load/i });
await user.click(loadMoreBtn);
- await screen.findByText('Older message');
+ expect(await screen.findByText('Older message')).toBeInTheDocument();
});
it('FE-COMP-CHAT-033: clicking delete on own message marks it as deleted', async () => {
diff --git a/client/src/components/Collab/CollabNotes.test.tsx b/client/src/components/Collab/CollabNotes.test.tsx
index dcd9f9bbd..b18854590 100644
--- a/client/src/components/Collab/CollabNotes.test.tsx
+++ b/client/src/components/Collab/CollabNotes.test.tsx
@@ -47,7 +47,7 @@ describe('CollabNotes', () => {
it('FE-COMP-NOTES-002: shows empty state when no notes', async () => {
render();
- await screen.findByText('No notes yet');
+ expect(await screen.findByText('No notes yet')).toBeInTheDocument();
});
it('FE-COMP-NOTES-003: shows New Note button', async () => {
@@ -70,7 +70,7 @@ describe('CollabNotes', () => {
)
);
render();
- await screen.findByText('Packing Tips');
+ expect(await screen.findByText('Packing Tips')).toBeInTheDocument();
});
it('FE-COMP-NOTES-005: clicking New Note opens modal', async () => {
@@ -79,7 +79,7 @@ describe('CollabNotes', () => {
await screen.findByText('No notes yet');
await user.click(screen.getByText('New Note'));
// Modal opens with a title input — placeholder is "Note title" (no ellipsis)
- await screen.findByPlaceholderText('Note title');
+ expect(await screen.findByPlaceholderText('Note title')).toBeInTheDocument();
});
it('FE-COMP-NOTES-006: note title is shown in the grid', async () => {
@@ -96,7 +96,7 @@ describe('CollabNotes', () => {
)
);
render();
- await screen.findByText('My Checklist');
+ expect(await screen.findByText('My Checklist')).toBeInTheDocument();
});
it('FE-COMP-NOTES-007: multiple notes all render', async () => {
@@ -117,8 +117,11 @@ describe('CollabNotes', () => {
it('FE-COMP-NOTES-008: Notes title heading is shown', async () => {
render();
+ // The loading panel renders its own copy of the heading and swaps the whole
+ // node out once the fetch settles, so wait for the loaded state first.
+ await screen.findByText('No notes yet');
// collab.notes.title = "Notes"
- await screen.findByText('Notes');
+ expect(screen.getByText('Notes')).toBeInTheDocument();
});
it('FE-COMP-NOTES-009: create note calls POST API', async () => {
@@ -236,7 +239,7 @@ describe('CollabNotes', () => {
render();
await screen.findByText('Editable Note');
await user.click(screen.getByTitle('Edit'));
- await screen.findByDisplayValue('Editable Note');
+ expect(await screen.findByDisplayValue('Editable Note')).toBeInTheDocument();
});
it('FE-COMP-NOTES-016: category filter hides notes from other categories', async () => {
@@ -280,7 +283,7 @@ describe('CollabNotes', () => {
},
});
});
- await screen.findByText('Live Note');
+ expect(await screen.findByText('Live Note')).toBeInTheDocument();
});
it('FE-COMP-NOTES-018: WebSocket collab:note:deleted event removes note', async () => {
@@ -403,7 +406,7 @@ describe('CollabNotes', () => {
await screen.findByText('No notes yet');
await user.click(screen.getByTitle('Manage Categories'));
// The modal header renders "Category Settings" or similar
- await screen.findByText('Manage Categories', { selector: 'h3' });
+ expect(await screen.findByText('Manage Categories', { selector: 'h3' })).toBeInTheDocument();
});
it('FE-COMP-NOTES-025: CategorySettingsModal shows no categories message when empty', async () => {
@@ -411,7 +414,7 @@ describe('CollabNotes', () => {
render();
await screen.findByText('No notes yet');
await user.click(screen.getByTitle('Manage Categories'));
- await screen.findByText('No categories yet');
+ expect(await screen.findByText('No categories yet')).toBeInTheDocument();
});
it('FE-COMP-NOTES-026: CategorySettingsModal add new category', async () => {
@@ -426,7 +429,7 @@ describe('CollabNotes', () => {
const addBtn = newCatInput.nextElementSibling as HTMLElement;
await user.click(addBtn);
// "Transport" category appears in the modal
- await screen.findByText('Transport');
+ expect(await screen.findByText('Transport')).toBeInTheDocument();
});
it('FE-COMP-NOTES-027: CategorySettingsModal close button dismisses it', async () => {
@@ -674,7 +677,7 @@ describe('CollabNotes', () => {
// Click the PDF badge to open FilePreviewPortal
await user.click(screen.getByText('PDF'));
// FilePreviewPortal renders the file name in the header
- await screen.findByText('document.pdf');
+ expect(await screen.findByText('document.pdf')).toBeInTheDocument();
});
it('FE-COMP-NOTES-037: note with website shows website thumbnail component', async () => {
@@ -1348,7 +1351,7 @@ describe('CollabNotes details', () => {
it('FE-W5CNT-001: a corrupt category cache in localStorage is ignored', async () => {
localStorage.setItem('collab-cats-1', '{not json');
render();
- await screen.findByText('No notes yet');
+ expect(await screen.findByText('No notes yet')).toBeInTheDocument();
});
it('FE-W5CNT-002: a category without a stored colour falls back to the first palette entry', async () => {
@@ -1369,13 +1372,13 @@ describe('CollabNotes details', () => {
it('FE-W5CNT-004: notes served as a bare array are rendered', async () => {
serveNotes([buildNote({ title: 'Array note' })]);
render();
- await screen.findByText('Array note');
+ expect(await screen.findByText('Array note')).toBeInTheDocument();
});
it('FE-W5CNT-005: an empty payload yields an empty list', async () => {
serveNotes(null);
render();
- await screen.findByText('No notes yet');
+ expect(await screen.findByText('No notes yet')).toBeInTheDocument();
});
it('FE-W5CNT-006: a failing load falls back to the empty state', async () => {
@@ -1383,7 +1386,7 @@ describe('CollabNotes details', () => {
http.get('/api/trips/1/collab/notes', () => new HttpResponse(null, { status: 500 })),
);
render();
- await screen.findByText('No notes yet');
+ expect(await screen.findByText('No notes yet')).toBeInTheDocument();
});
it('FE-W5CNT-007: a WebSocket create for a note already in the list does not duplicate it', async () => {
@@ -1434,7 +1437,7 @@ describe('CollabNotes details', () => {
await user.click(screen.getByText('New Note'));
await user.type(await screen.findByPlaceholderText('Note title'), 'Fresh note');
await user.click(screen.getByRole('button', { name: 'Create' }));
- await screen.findByText('Fresh note');
+ expect(await screen.findByText('Fresh note')).toBeInTheDocument();
});
it('FE-W5CNT-011: an empty create response leaves the list untouched', async () => {
diff --git a/client/src/components/Collab/CollabPolls.test.tsx b/client/src/components/Collab/CollabPolls.test.tsx
index 5840333aa..2981db8df 100644
--- a/client/src/components/Collab/CollabPolls.test.tsx
+++ b/client/src/components/Collab/CollabPolls.test.tsx
@@ -55,7 +55,7 @@ beforeEach(() => {
describe('CollabPolls', () => {
it('FE-COMP-POLLS-001: renders empty state when no polls exist', async () => {
render();
- await screen.findByText(/no polls yet|collab\.polls\.empty/i);
+ expect(await screen.findByText(/no polls yet|collab\.polls\.empty/i)).toBeInTheDocument();
});
it('FE-COMP-POLLS-002: shows loading spinner initially', async () => {
@@ -79,7 +79,7 @@ describe('CollabPolls', () => {
),
);
render();
- await screen.findByText('Best destination?');
+ expect(await screen.findByText('Best destination?')).toBeInTheDocument();
});
it('FE-COMP-POLLS-004: renders poll options', async () => {
@@ -108,7 +108,7 @@ describe('CollabPolls', () => {
await screen.findByText(/no polls yet|collab\.polls\.empty/i);
await user.click(screen.getByRole('button', { name: /new/i }));
// Modal has a question placeholder input
- await screen.findByPlaceholderText(/what should we do/i);
+ expect(await screen.findByPlaceholderText(/what should we do/i)).toBeInTheDocument();
});
it('FE-COMP-POLLS-007: create modal requires question and at least 2 options to enable submit', async () => {
@@ -153,7 +153,7 @@ describe('CollabPolls', () => {
await user.type(optionInputs[1], 'Japanese');
await user.click(screen.getByRole('button', { name: /create|collab\.polls\.create/i }));
- await screen.findByText('Where to eat?');
+ expect(await screen.findByText('Where to eat?')).toBeInTheDocument();
});
it('FE-COMP-POLLS-009: voting on an option calls POST vote API', async () => {
@@ -188,7 +188,7 @@ describe('CollabPolls', () => {
),
);
render();
- await screen.findByText(/closed/i);
+ expect(await screen.findByText(/closed/i)).toBeInTheDocument();
});
it('FE-COMP-POLLS-011: closed poll options are disabled (cannot vote)', async () => {
@@ -236,7 +236,7 @@ describe('CollabPolls', () => {
const listener = (addListener as ReturnType).mock.calls[0][0];
listener({ tripId: 1, type: 'collab:poll:created', poll: buildPoll({ id: 77, question: 'Live poll?' }) });
- await screen.findByText('Live poll?');
+ expect(await screen.findByText('Live poll?')).toBeInTheDocument();
});
it('FE-COMP-POLLS-014: WebSocket collab:poll:deleted event removes poll', async () => {
@@ -337,7 +337,7 @@ describe('CollabPolls details', () => {
it('FE-W5CPL-005: a poll served as a bare array is rendered', async () => {
servePolls([buildPoll({ question: 'Array shaped?' })]);
render();
- await screen.findByText('Array shaped?');
+ expect(await screen.findByText('Array shaped?')).toBeInTheDocument();
});
it('FE-W5CPL-006: options without a voters array count as zero votes', async () => {
@@ -484,13 +484,13 @@ describe('CollabPolls details', () => {
http.get('/api/trips/1/collab/polls', () => new HttpResponse(null, { status: 500 })),
);
render();
- await screen.findByText(/no polls yet|collab\.polls\.empty/i);
+ expect(await screen.findByText(/no polls yet|collab\.polls\.empty/i)).toBeInTheDocument();
});
it('FE-W5CPL-028: a payload without a polls key yields an empty list', async () => {
servePolls({});
render();
- await screen.findByText(/no polls yet|collab\.polls\.empty/i);
+ expect(await screen.findByText(/no polls yet|collab\.polls\.empty/i)).toBeInTheDocument();
});
it('FE-W5CPL-015: a failing close shows an error and leaves the poll open', async () => {
diff --git a/client/src/components/Files/FileManager.test.tsx b/client/src/components/Files/FileManager.test.tsx
index 478caf79f..0ff151f32 100644
--- a/client/src/components/Files/FileManager.test.tsx
+++ b/client/src/components/Files/FileManager.test.tsx
@@ -176,7 +176,7 @@ describe('FileManager', () => {
await user.click(trashBtn);
// Trashed file should appear
- await screen.findByText('old.pdf');
+ expect(await screen.findByText('old.pdf')).toBeInTheDocument();
});
it('FE-COMP-FILEMANAGER-007: restore button calls filesApi.restore', async () => {
@@ -407,7 +407,7 @@ describe('FileManager', () => {
const assignBtn = screen.getByTitle(/assign/i);
await user.click(assignBtn);
- await screen.findByText('Eiffel Tower');
+ expect(await screen.findByText('Eiffel Tower')).toBeInTheDocument();
});
it('FE-COMP-FILEMANAGER-021: file description is shown when present', () => {
@@ -445,7 +445,7 @@ describe('FileManager', () => {
const assignBtn = screen.getByTitle(/assign/i);
await user.click(assignBtn);
- await screen.findByText('Hotel Paris');
+ expect(await screen.findByText('Hotel Paris')).toBeInTheDocument();
});
it('FE-COMP-FILEMANAGER-024: clicking a place in assign modal calls filesApi.update', async () => {
@@ -492,7 +492,7 @@ describe('FileManager', () => {
await user.click(screen.getByTitle(/assign/i));
await screen.findByText('Notre Dame');
- await screen.findByText('Airbnb');
+ expect(await screen.findByText('Airbnb')).toBeInTheDocument();
});
it('FE-COMP-FILEMANAGER-027: paste event uploads file when user can upload', async () => {
@@ -543,7 +543,7 @@ describe('FileManager', () => {
const user = userEvent.setup();
await user.click(screen.getByTitle(/assign/i));
- await screen.findByText('Arc de Triomphe');
+ expect(await screen.findByText('Arc de Triomphe')).toBeInTheDocument();
});
it('FE-COMP-FILEMANAGER-030: file with linked place shows source badge', async () => {
@@ -554,7 +554,7 @@ describe('FileManager', () => {
render();
// Source badge text includes place name
- await screen.findByText(/Colosseum/);
+ expect(await screen.findByText(/Colosseum/)).toBeInTheDocument();
});
it('FE-COMP-FILEMANAGER-031: unlink place from assign modal calls filesApi.update', async () => {
diff --git a/client/src/components/Layout/InAppNotificationBell.test.tsx b/client/src/components/Layout/InAppNotificationBell.test.tsx
index 45ccea8bc..b9fe381d7 100644
--- a/client/src/components/Layout/InAppNotificationBell.test.tsx
+++ b/client/src/components/Layout/InAppNotificationBell.test.tsx
@@ -62,7 +62,7 @@ describe('InAppNotificationBell', () => {
const bell = screen.getAllByRole('button')[0];
await user.click(bell);
// Panel shows "Notifications" title
- await screen.findByText('Notifications');
+ expect(await screen.findByText('Notifications')).toBeInTheDocument();
});
it('FE-COMP-BELL-004: notification panel shows empty state when no notifications', async () => {
@@ -76,7 +76,7 @@ describe('InAppNotificationBell', () => {
render();
const bell = screen.getAllByRole('button')[0];
await user.click(bell);
- await screen.findByText('No notifications');
+ expect(await screen.findByText('No notifications')).toBeInTheDocument();
});
it('FE-COMP-BELL-005: shows unread badge count when there are unread notifications', async () => {
@@ -98,7 +98,7 @@ describe('InAppNotificationBell', () => {
render();
const bell = screen.getAllByRole('button')[0];
await user.click(bell);
- await screen.findByTitle('Mark all read');
+ expect(await screen.findByTitle('Mark all read')).toBeInTheDocument();
});
it('FE-COMP-BELL-008: panel shows empty description when no notifications', async () => {
@@ -111,7 +111,7 @@ describe('InAppNotificationBell', () => {
const user = userEvent.setup();
render();
await user.click(screen.getAllByRole('button')[0]);
- await screen.findByText("You're all caught up!");
+ expect(await screen.findByText("You're all caught up!")).toBeInTheDocument();
});
it('FE-COMP-BELL-009: bell is accessible as a button', () => {
diff --git a/client/src/components/Packing/PackingListPanel.test.tsx b/client/src/components/Packing/PackingListPanel.test.tsx
index a86cb9d28..63be3f0b3 100644
--- a/client/src/components/Packing/PackingListPanel.test.tsx
+++ b/client/src/components/Packing/PackingListPanel.test.tsx
@@ -171,7 +171,7 @@ describe('PackingListPanel', () => {
const user = userEvent.setup();
render();
await user.click(screen.getByText('Add list'));
- await screen.findByPlaceholderText('List name (e.g. Clothing)');
+ expect(await screen.findByPlaceholderText('List name (e.g. Clothing)')).toBeInTheDocument();
});
it('FE-COMP-PACKING-016: delete item button exists and triggers API call', async () => {
@@ -392,7 +392,7 @@ describe('PackingListPanel', () => {
render();
// "Apply template" button appears when templates are available
- await screen.findByText('Apply template');
+ expect(await screen.findByText('Apply template')).toBeInTheDocument();
});
it('FE-COMP-PACKING-031: "Uncheck All" bulk action calls PUT to uncheck checked items', async () => {
diff --git a/client/src/components/Planner/DayDetailPanel.test.tsx b/client/src/components/Planner/DayDetailPanel.test.tsx
index f8ee30de8..564ec67cc 100644
--- a/client/src/components/Planner/DayDetailPanel.test.tsx
+++ b/client/src/components/Planner/DayDetailPanel.test.tsx
@@ -158,7 +158,7 @@ describe('DayDetailPanel', () => {
),
);
render();
- await screen.findByText(/22°C/);
+ expect(await screen.findByText(/22°C/)).toBeInTheDocument();
});
it('FE-PLANNER-DAYDETAIL-011: weather in Fahrenheit when setting is fahrenheit', async () => {
@@ -171,7 +171,7 @@ describe('DayDetailPanel', () => {
),
);
render();
- await screen.findByText(/32°F/);
+ expect(await screen.findByText(/32°F/)).toBeInTheDocument();
});
it('FE-PLANNER-DAYDETAIL-012: no weather shows "No weather data" message', async () => {
@@ -179,7 +179,7 @@ describe('DayDetailPanel', () => {
http.get('/api/weather/detailed', () => HttpResponse.json({ error: true })),
);
render();
- await screen.findByText(/No weather/i);
+ expect(await screen.findByText(/No weather/i)).toBeInTheDocument();
});
// ── Reservations ─────────────────────────────────────────────────────────────
@@ -197,7 +197,7 @@ describe('DayDetailPanel', () => {
assignments={{ '1': [{ id: 50, place, place_id: place.id, day_id: 1, order_index: 0, notes: null }] }}
reservations={[reservation]}
/>);
- await screen.findByText('Museum Tour Ticket');
+ expect(await screen.findByText('Museum Tour Ticket')).toBeInTheDocument();
});
it('FE-PLANNER-DAYDETAIL-014: reservations from OTHER days are not shown', async () => {
@@ -266,7 +266,7 @@ describe('DayDetailPanel', () => {
),
);
render();
- await screen.findByText('Grand Hotel');
+ expect(await screen.findByText('Grand Hotel')).toBeInTheDocument();
});
it('FE-PLANNER-DAYDETAIL-018: check-in time shown for check-in day', async () => {
@@ -305,7 +305,7 @@ describe('DayDetailPanel', () => {
day={checkOutDay}
days={[day, checkOutDay]}
/>);
- await screen.findByText('11:00');
+ expect(await screen.findByText('11:00')).toBeInTheDocument();
});
it('FE-PLANNER-DAYDETAIL-020: confirmation code shown', async () => {
@@ -320,7 +320,7 @@ describe('DayDetailPanel', () => {
),
);
render();
- await screen.findByText('HOTEL99');
+ expect(await screen.findByText('HOTEL99')).toBeInTheDocument();
});
it('FE-PLANNER-DAYDETAIL-021: accommodation edit/remove buttons shown when canEditDays=true', async () => {
@@ -396,7 +396,7 @@ describe('DayDetailPanel', () => {
it('FE-PLANNER-DAYDETAIL-023: "Add accommodation" button visible when canEditDays=true and no accommodation', async () => {
seedStore(useAuthStore, { user: buildAdmin(), isAuthenticated: true });
render();
- await screen.findByText(/Add accommodation/i);
+ expect(await screen.findByText(/Add accommodation/i)).toBeInTheDocument();
});
it('FE-PLANNER-DAYDETAIL-024: clicking add accommodation opens hotel picker', async () => {
@@ -466,7 +466,7 @@ describe('DayDetailPanel', () => {
await screen.findByText('5.2 mm');
await screen.findByText('30 km/h');
await screen.findByText('06:30');
- await screen.findByText('20:15');
+ expect(await screen.findByText('20:15')).toBeInTheDocument();
});
it('FE-PLANNER-DAYDETAIL-027: weather chips show Fahrenheit wind speed', async () => {
@@ -487,7 +487,7 @@ describe('DayDetailPanel', () => {
);
render();
// 50 km/h * 0.621371 ≈ 31 mph
- await screen.findByText('31 mph');
+ expect(await screen.findByText('31 mph')).toBeInTheDocument();
});
// ── Hotel picker interactions ─────────────────────────────────────────────────
@@ -516,7 +516,7 @@ describe('DayDetailPanel', () => {
await userEvent.click(addButton);
await screen.findByText('Hotel du Nord');
await screen.findByText('Hotel du Sud');
- await screen.findByText('102 Quai de Jemmapes');
+ expect(await screen.findByText('102 Quai de Jemmapes')).toBeInTheDocument();
});
it('FE-PLANNER-DAYDETAIL-030: selecting a place in hotel picker enables save button', async () => {
@@ -691,7 +691,7 @@ describe('DayDetailPanel', () => {
),
);
render();
- await screen.findByText(/Ø/);
+ expect(await screen.findByText(/Ø/)).toBeInTheDocument();
});
it('FE-PLANNER-DAYDETAIL-038: hotel picker with category filter renders category buttons', async () => {
@@ -722,7 +722,7 @@ describe('DayDetailPanel', () => {
render();
await screen.findByText('Existing Hotel');
// "Add accommodation" dashed button should also appear for adding more
- await screen.findByText(/Add accommodation/i);
+ expect(await screen.findByText(/Add accommodation/i)).toBeInTheDocument();
});
it('FE-PLANNER-DAYDETAIL-041: save new accommodation calls API and updates list', async () => {
@@ -838,7 +838,7 @@ describe('DayDetailPanel', () => {
);
render();
// Should show "No weather" after error (catch sets weather to null)
- await screen.findByText(/No weather/i);
+ expect(await screen.findByText(/No weather/i)).toBeInTheDocument();
});
it('FE-PLANNER-DAYDETAIL-046: save edited accommodation calls update API', async () => {
@@ -1251,7 +1251,7 @@ describe('DayDetailPanel', () => {
// Intermediate day (id=1, position 9): old filter: 1>=17 → false. New: 9 in [0,15] → visible.
render();
- await screen.findByText('Full Trip Hotel');
+ expect(await screen.findByText('Full Trip Hotel')).toBeInTheDocument();
});
it('FE-PLANNER-DAYDETAIL-040: 12h time format renders reservation time with AM/PM', async () => {
diff --git a/client/src/components/Planner/PlaceFormModal.test.tsx b/client/src/components/Planner/PlaceFormModal.test.tsx
index 063614aca..3d5d45265 100644
--- a/client/src/components/Planner/PlaceFormModal.test.tsx
+++ b/client/src/components/Planner/PlaceFormModal.test.tsx
@@ -183,7 +183,7 @@ describe('PlaceFormModal', () => {
const searchBtn = within(searchRow).getByRole('button');
await user.click(searchBtn);
- await screen.findByText('Eiffel Tower');
+ expect(await screen.findByText('Eiffel Tower')).toBeInTheDocument();
});
it('FE-PLANNER-PLACEFORM-019: pressing Enter in search input triggers search', async () => {
@@ -201,7 +201,7 @@ describe('PlaceFormModal', () => {
await user.type(searchInput, 'Eiffel Tower');
await user.keyboard('{Enter}');
- await screen.findByText('Eiffel Tower');
+ expect(await screen.findByText('Eiffel Tower')).toBeInTheDocument();
});
it('FE-PLANNER-PLACEFORM-020: clicking a maps result fills the form', async () => {
diff --git a/client/src/components/Planner/PlaceInspector.test.tsx b/client/src/components/Planner/PlaceInspector.test.tsx
index 75fb0a8eb..6c464cad0 100644
--- a/client/src/components/Planner/PlaceInspector.test.tsx
+++ b/client/src/components/Planner/PlaceInspector.test.tsx
@@ -335,7 +335,7 @@ describe('PlaceInspector', () => {
} as any);
const p = buildPlace({ id: 201, google_place_id: 'ChIJ002' });
render();
- await screen.findByText(/4\.5/);
+ expect(await screen.findByText(/4\.5/)).toBeInTheDocument();
});
it('FE-PLANNER-INSPECTOR-025: opening hours shown when available', async () => {
@@ -361,7 +361,7 @@ describe('PlaceInspector', () => {
} as any);
const p = buildPlace({ id: 203, google_place_id: 'ChIJ004' });
render();
- await screen.findByText(/open/i);
+ expect(await screen.findByText(/open/i)).toBeInTheDocument();
});
it('FE-PLANNER-INSPECTOR-027: mapsApi.details NOT called when place has no google_place_id or osm_id', async () => {
@@ -394,7 +394,7 @@ describe('PlaceInspector', () => {
// Click the expand button (file count label button)
if (filesBtn) {
await user.click(filesBtn);
- await screen.findByText('photo.jpg');
+ expect(await screen.findByText('photo.jpg')).toBeInTheDocument();
} else {
// Try clicking the last non-footer button
const toggleButtons = allButtons.filter(btn => !btn.closest('footer'));
diff --git a/client/src/components/Settings/AccountTab.test.tsx b/client/src/components/Settings/AccountTab.test.tsx
index 54a0abe2c..11bd84931 100644
--- a/client/src/components/Settings/AccountTab.test.tsx
+++ b/client/src/components/Settings/AccountTab.test.tsx
@@ -74,7 +74,7 @@ describe('AccountTab', () => {
render(<>>);
await user.click(screen.getByText('Update password'));
// Validation fires: first checks currentPassword — "Current password is required"
- await screen.findByText(/Current password is required/i);
+ expect(await screen.findByText(/Current password is required/i)).toBeInTheDocument();
});
it('FE-COMP-ACCOUNT-011: password mismatch shows error', async () => {
@@ -86,7 +86,7 @@ describe('AccountTab', () => {
await user.type(passwordInputs[1], 'NewPassword1!');
await user.type(passwordInputs[2], 'DifferentPass1!');
await user.click(screen.getByText('Update password'));
- await screen.findByText('Passwords do not match');
+ expect(await screen.findByText('Passwords do not match')).toBeInTheDocument();
});
it('FE-COMP-ACCOUNT-012: valid password change calls API', async () => {
@@ -140,7 +140,7 @@ describe('AccountTab – Profile', () => {
seedStore(useAuthStore, { updateProfile: vi.fn().mockResolvedValue(undefined) });
render(<>>);
await user.click(screen.getByRole('button', { name: /save profile/i }));
- await screen.findByText('Profile saved');
+ expect(await screen.findByText('Profile saved')).toBeInTheDocument();
});
it('FE-COMP-ACCOUNT-016: failed save shows error toast with error message', async () => {
@@ -148,7 +148,7 @@ describe('AccountTab – Profile', () => {
seedStore(useAuthStore, { updateProfile: vi.fn().mockRejectedValue(new Error('Server error')) });
render(<>>);
await user.click(screen.getByRole('button', { name: /save profile/i }));
- await screen.findByText('Server error');
+ expect(await screen.findByText('Server error')).toBeInTheDocument();
});
it('FE-COMP-ACCOUNT-017: Save button shows spinner while saving', async () => {
@@ -171,7 +171,7 @@ describe('AccountTab – Password change', () => {
await user.type(passwordInputs[1], 'short');
await user.type(passwordInputs[2], 'short');
await user.click(screen.getByText('Update password'));
- await screen.findByText(/at least 8 characters/i);
+ expect(await screen.findByText(/at least 8 characters/i)).toBeInTheDocument();
});
it('FE-COMP-ACCOUNT-019: password change clears fields on success', async () => {
@@ -205,7 +205,7 @@ describe('AccountTab – Password change', () => {
await user.type(passwordInputs[1], 'NewPassword1!');
await user.type(passwordInputs[2], 'NewPassword1!');
await user.click(screen.getByText('Update password'));
- await screen.findByText('Wrong password');
+ expect(await screen.findByText('Wrong password')).toBeInTheDocument();
});
it('FE-COMP-ACCOUNT-021: password section hidden in OIDC-only mode', async () => {
@@ -376,7 +376,7 @@ describe('AccountTab – MFA', () => {
const codeInput = screen.getByPlaceholderText('6-digit code');
await user.type(codeInput, '123456');
await user.click(screen.getByRole('button', { name: 'Disable 2FA' }));
- await screen.findByText('Two-factor authentication disabled');
+ expect(await screen.findByText('Two-factor authentication disabled')).toBeInTheDocument();
});
it('FE-COMP-ACCOUNT-035: policy warning shown when MFA is required by policy', () => {
@@ -577,7 +577,7 @@ describe('AccountTab – Backup codes', () => {
await user.click(await screen.findByRole('button', { name: 'Copy codes' }));
- await screen.findByText('Error');
+ expect(await screen.findByText('Error')).toBeInTheDocument();
});
it('FE-COMP-ACCOUNT-052: Download TXT builds a blob, names the file and revokes the URL', async () => {
@@ -682,7 +682,7 @@ describe('AccountTab – Avatar upload', () => {
const input = document.querySelector('input[type="file"]') as HTMLInputElement;
await user.upload(input, new File(['avatar'], 'me.png', { type: 'image/png' }));
- await screen.findByText('Upload failed');
+ expect(await screen.findByText('Upload failed')).toBeInTheDocument();
});
it('FE-COMP-ACCOUNT-058: a change event without a file uploads nothing', () => {
@@ -723,7 +723,7 @@ describe('AccountTab – Avatar upload', () => {
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
await user.click(fileInput.parentElement!.querySelectorAll('button')[1]);
- await screen.findByText('Upload failed');
+ expect(await screen.findByText('Upload failed')).toBeInTheDocument();
});
});
diff --git a/client/src/components/Settings/AirTrailConnectionSection.test.tsx b/client/src/components/Settings/AirTrailConnectionSection.test.tsx
index 753f7cf77..301d3ccbf 100644
--- a/client/src/components/Settings/AirTrailConnectionSection.test.tsx
+++ b/client/src/components/Settings/AirTrailConnectionSection.test.tsx
@@ -177,7 +177,7 @@ describe('AirTrailConnectionSection', () => {
await screen.findByDisplayValue('https://air.example.com');
await user.click(screen.getByRole('button', { name: /^Save$/ }));
- await screen.findByText('Not connected');
+ expect(await screen.findByText('Not connected')).toBeInTheDocument();
});
it('FE-COMP-AIRTRAIL-010: a rejected save surfaces the server error message', async () => {
@@ -193,7 +193,7 @@ describe('AirTrailConnectionSection', () => {
await screen.findByDisplayValue('https://air.example.com');
await user.click(screen.getByRole('button', { name: /^Save$/ }));
- await screen.findByText('Instance unreachable');
+ expect(await screen.findByText('Instance unreachable')).toBeInTheDocument();
});
it('FE-COMP-AIRTRAIL-011: a save failure without a server message falls back to the generic error', async () => {
@@ -207,7 +207,7 @@ describe('AirTrailConnectionSection', () => {
await screen.findByDisplayValue('https://air.example.com');
await user.click(screen.getByRole('button', { name: /^Save$/ }));
- await screen.findByText('Could not save the connection');
+ expect(await screen.findByText('Could not save the connection')).toBeInTheDocument();
});
it('FE-COMP-AIRTRAIL-012: Save is locked while the request is in flight', async () => {
@@ -265,7 +265,7 @@ describe('AirTrailConnectionSection', () => {
await screen.findByDisplayValue('https://air.example.com');
await user.click(screen.getByRole('button', { name: /Test connection/ }));
- await screen.findByText('Connected — 0 flight(s) found');
+ expect(await screen.findByText('Connected — 0 flight(s) found')).toBeInTheDocument();
});
it('FE-COMP-AIRTRAIL-015: a refused test shows the returned error and keeps the badge off', async () => {
diff --git a/client/src/components/Settings/AppearanceSettingsTab.test.tsx b/client/src/components/Settings/AppearanceSettingsTab.test.tsx
index 61ddaa6de..3317a818a 100644
--- a/client/src/components/Settings/AppearanceSettingsTab.test.tsx
+++ b/client/src/components/Settings/AppearanceSettingsTab.test.tsx
@@ -120,7 +120,7 @@ describe('AppearanceSettingsTab – custom accent and sliders', () => {
await user.click(screen.getByText('Teal'));
- await screen.findByText('Appearance rejected', undefined, PERSIST);
+ expect(await screen.findByText('Appearance rejected', undefined, PERSIST)).toBeInTheDocument();
});
it('FE-COMP-APPEARANCE-011: a failing colour-mode change surfaces the error toast', async () => {
@@ -130,7 +130,7 @@ describe('AppearanceSettingsTab – custom accent and sliders', () => {
await user.click(screen.getByText('Dark'));
- await screen.findByText('Mode rejected');
+ expect(await screen.findByText('Mode rejected')).toBeInTheDocument();
});
it('FE-COMP-APPEARANCE-012: picking Custom reveals the accent picker', async () => {
diff --git a/client/src/components/Settings/DisplaySettingsTab.test.tsx b/client/src/components/Settings/DisplaySettingsTab.test.tsx
index 66d565e87..54f22de99 100644
--- a/client/src/components/Settings/DisplaySettingsTab.test.tsx
+++ b/client/src/components/Settings/DisplaySettingsTab.test.tsx
@@ -165,7 +165,7 @@ describe('DisplaySettingsTab', () => {
seedStore(useSettingsStore, { settings: buildSettings({ temperature_unit: 'celsius' }), updateSetting });
render(<>>);
await user.click(screen.getByText('°F Fahrenheit'));
- await screen.findByText('Server error');
+ expect(await screen.findByText('Server error')).toBeInTheDocument();
});
it('FE-COMP-DISPLAY-027: temperature unit local state updates optimistically before API resolves', async () => {
@@ -214,7 +214,7 @@ describe('DisplaySettingsTab – Display currency', () => {
await user.click(screen.getByRole('button', { name: /USD/ }));
await user.click(await screen.findByText('Trip currency'));
- await screen.findByText('Currency locked');
+ expect(await screen.findByText('Currency locked')).toBeInTheDocument();
});
});
@@ -261,7 +261,7 @@ describe('DisplaySettingsTab – Compact language picker', () => {
await user.click(within(wrap).getByRole('button'));
await user.click(within(wrap).getByText('Deutsch').closest('button')!);
- await screen.findByText('Language locked');
+ expect(await screen.findByText('Language locked')).toBeInTheDocument();
});
it('FE-COMP-DISPLAY-048: a rejected pick from the desktop grid surfaces the error', async () => {
@@ -271,7 +271,7 @@ describe('DisplaySettingsTab – Compact language picker', () => {
await user.click(screen.getByText('Deutsch'));
- await screen.findByText('Language locked');
+ expect(await screen.findByText('Language locked')).toBeInTheDocument();
});
it('FE-COMP-DISPLAY-038: a mousedown outside the picker closes it', async () => {
@@ -320,7 +320,7 @@ describe('DisplaySettingsTab – Map and privacy toggles', () => {
await user.click(within(optionBlock(/booking route labels/i)).getByText(/^On$/));
- await screen.findByText('Labels locked');
+ expect(await screen.findByText('Labels locked')).toBeInTheDocument();
});
it('FE-COMP-DISPLAY-042: a rejected always-show-routes change surfaces the error', async () => {
@@ -330,7 +330,7 @@ describe('DisplaySettingsTab – Map and privacy toggles', () => {
await user.click(within(optionBlock(/always show booking routes/i)).getByText(/^On$/));
- await screen.findByText('Routes locked');
+ expect(await screen.findByText('Routes locked')).toBeInTheDocument();
});
it('FE-COMP-DISPLAY-043: the POI pill defaults to On and can be turned off', async () => {
@@ -353,7 +353,7 @@ describe('DisplaySettingsTab – Map and privacy toggles', () => {
await user.click(within(optionBlock(/explore places on the map/i)).getByText(/^Off$/));
- await screen.findByText('POI locked');
+ expect(await screen.findByText('POI locked')).toBeInTheDocument();
});
it('FE-COMP-DISPLAY-045: turning blur booking codes on persists true', async () => {
@@ -441,6 +441,6 @@ describe('DisplaySettingsTab – startup destination', () => {
await user.click(within(optionBlock(/^Start page$/)).getByText('Active trip'));
- await screen.findByText('Start locked');
+ expect(await screen.findByText('Start locked')).toBeInTheDocument();
});
});
diff --git a/client/src/components/Settings/IntegrationsTab.test.tsx b/client/src/components/Settings/IntegrationsTab.test.tsx
index 47ec0e49d..b60726f98 100644
--- a/client/src/components/Settings/IntegrationsTab.test.tsx
+++ b/client/src/components/Settings/IntegrationsTab.test.tsx
@@ -60,7 +60,7 @@ describe('IntegrationsTab', () => {
it('FE-COMP-INTEGRATIONS-003: MCP section is visible when mcp addon is enabled', async () => {
enableMcp();
render();
- await screen.findByText('MCP Configuration');
+ expect(await screen.findByText('MCP Configuration')).toBeInTheDocument();
});
it('FE-COMP-INTEGRATIONS-004: MCP endpoint URL is displayed', async () => {
@@ -92,7 +92,7 @@ describe('IntegrationsTab', () => {
render();
await screen.findByText('MCP Configuration');
await user.click(screen.getByRole('button', { name: /API Tokens/i }));
- await screen.findByText('No tokens yet. Create one to connect MCP clients.');
+ expect(await screen.findByText('No tokens yet. Create one to connect MCP clients.')).toBeInTheDocument();
});
it('FE-COMP-INTEGRATIONS-007: token list renders when tokens exist', async () => {
@@ -112,7 +112,7 @@ describe('IntegrationsTab', () => {
await screen.findByText('MCP Configuration');
await user.click(screen.getByRole('button', { name: /API Tokens/i }));
await screen.findByText('My Token');
- await screen.findByText('Other Token');
+ expect(await screen.findByText('Other Token')).toBeInTheDocument();
});
it('FE-COMP-INTEGRATIONS-008: clicking "Create New Token" button opens the modal', async () => {
@@ -123,7 +123,7 @@ describe('IntegrationsTab', () => {
await user.click(screen.getByRole('button', { name: /API Tokens/i }));
const createBtn = screen.getByRole('button', { name: /Create New Token/i });
await user.click(createBtn);
- await screen.findByText('Create API Token');
+ expect(await screen.findByText('Create API Token')).toBeInTheDocument();
});
it('FE-COMP-INTEGRATIONS-009: Create button in modal is disabled when name is empty', async () => {
@@ -591,7 +591,7 @@ describe('IntegrationsTab', () => {
// Confirm — button text is 'Rotate'
const rotateBtns = screen.getAllByRole('button', { name: /^Rotate$/i });
await user.click(rotateBtns[rotateBtns.length - 1]);
- await screen.findByText(/new-rotated-secret/);
+ expect(await screen.findByText(/new-rotated-secret/)).toBeInTheDocument();
});
it('FE-COMP-INTEGRATIONS-030: revoke OAuth session removes it from list', async () => {
@@ -649,7 +649,7 @@ describe('IntegrationsTab', () => {
await user.type(screen.getByPlaceholderText(/Claude Web, My MCP App/i), 'Fail Client');
await user.type(screen.getByPlaceholderText(/https:\/\/your-app/i), 'http://localhost');
await user.click(screen.getByRole('button', { name: /Register Client/i }));
- await screen.findByText(/Failed to register/i);
+ expect(await screen.findByText(/Failed to register/i)).toBeInTheDocument();
});
});
@@ -893,7 +893,7 @@ describe('IntegrationsTab – failure toasts', () => {
await user.type(screen.getByPlaceholderText(/Claude Desktop/i), 'Doomed');
await user.click(screen.getByRole('button', { name: /^Create Token$/i }));
- await screen.findByText('Failed to create token');
+ expect(await screen.findByText('Failed to create token')).toBeInTheDocument();
});
it('FE-COMP-INTEGRATIONS-045: a failing token deletion toasts and keeps the token', async () => {
@@ -934,7 +934,7 @@ describe('IntegrationsTab – failure toasts', () => {
await user.click(screen.getByTitle('Rotate Secret'));
const rotateBtns = await screen.findAllByRole('button', { name: /^Rotate$/i });
await user.click(rotateBtns[rotateBtns.length - 1]);
- await screen.findByText('Failed to rotate client secret');
+ expect(await screen.findByText('Failed to rotate client secret')).toBeInTheDocument();
});
it('FE-COMP-INTEGRATIONS-047: a failing session revoke toasts and keeps the session', async () => {
diff --git a/client/src/components/Settings/MapSettingsTab.test.tsx b/client/src/components/Settings/MapSettingsTab.test.tsx
index 706a45b13..867465fbf 100644
--- a/client/src/components/Settings/MapSettingsTab.test.tsx
+++ b/client/src/components/Settings/MapSettingsTab.test.tsx
@@ -134,7 +134,7 @@ describe('MapSettingsTab', () => {
});
render(<>>);
await user.click(screen.getByText('Save Map'));
- await screen.findByText('Save failed');
+ expect(await screen.findByText('Save failed')).toBeInTheDocument();
});
it('FE-COMP-MAP-016: preset dropdown is rendered', () => {
diff --git a/client/src/components/Settings/NotificationsTab.test.tsx b/client/src/components/Settings/NotificationsTab.test.tsx
index b234b318a..78720dc35 100644
--- a/client/src/components/Settings/NotificationsTab.test.tsx
+++ b/client/src/components/Settings/NotificationsTab.test.tsx
@@ -1007,7 +1007,7 @@ describe('NotificationsTab — ntfy credentials', () => {
await screen.findByDisplayValue('alerts');
await user.click(screen.getByRole('button', { name: 'Save' }));
- await screen.findByText('Error');
+ expect(await screen.findByText('Error')).toBeInTheDocument();
});
it('FE-COMP-NOTIFICATIONS-019: Clear wipes the stored token and hides the button', async () => {
@@ -1073,7 +1073,7 @@ describe('NotificationsTab — ntfy credentials', () => {
await screen.findByDisplayValue('alerts');
await user.click(screen.getByRole('button', { name: 'Test' }));
- await screen.findByText('Topic not found');
+ expect(await screen.findByText('Topic not found')).toBeInTheDocument();
});
it('FE-COMP-NOTIFICATIONS-023: a network error during the test falls back to the generic message', async () => {
@@ -1087,7 +1087,7 @@ describe('NotificationsTab — ntfy credentials', () => {
await screen.findByDisplayValue('alerts');
await user.click(screen.getByRole('button', { name: 'Test' }));
- await screen.findByText('Test ntfy notification failed');
+ expect(await screen.findByText('Test ntfy notification failed')).toBeInTheDocument();
});
it('FE-COMP-NOTIFICATIONS-024: without a topic the test button stays disabled', async () => {
@@ -1141,7 +1141,7 @@ describe('NotificationsTab — webhook and channel-test failures', () => {
await screen.findByRole('textbox');
await user.click(screen.getByRole('button', { name: 'Save' }));
- await screen.findByText('Error');
+ expect(await screen.findByText('Error')).toBeInTheDocument();
});
it('FE-COMP-NOTIFICATIONS-027: a network error during the webhook test falls back to the generic message', async () => {
@@ -1154,7 +1154,7 @@ describe('NotificationsTab — webhook and channel-test failures', () => {
await screen.findByRole('textbox');
await user.click(screen.getByRole('button', { name: 'Test' }));
- await screen.findByText('Test webhook failed');
+ expect(await screen.findByText('Test webhook failed')).toBeInTheDocument();
});
it('FE-COMP-NOTIFICATIONS-028: a refused plugin channel test reports the returned error', async () => {
@@ -1169,7 +1169,7 @@ describe('NotificationsTab — webhook and channel-test failures', () => {
await user.click(await screen.findByRole('button', { name: /send test/i }));
- await screen.findByText('Gotify rejected the token');
+ expect(await screen.findByText('Gotify rejected the token')).toBeInTheDocument();
});
it('FE-COMP-NOTIFICATIONS-029: a plugin channel test that errors out falls back to the generic message', async () => {
@@ -1182,6 +1182,6 @@ describe('NotificationsTab — webhook and channel-test failures', () => {
await user.click(await screen.findByRole('button', { name: /send test/i }));
- await screen.findByText('Test failed.');
+ expect(await screen.findByText('Test failed.')).toBeInTheDocument();
});
});
diff --git a/client/src/components/Settings/PasskeysSection.test.tsx b/client/src/components/Settings/PasskeysSection.test.tsx
index 634153438..a78f7e093 100644
--- a/client/src/components/Settings/PasskeysSection.test.tsx
+++ b/client/src/components/Settings/PasskeysSection.test.tsx
@@ -344,7 +344,7 @@ describe('PasskeysSection', () => {
await user.type(screen.getByDisplayValue('MacBook'), '-x');
await user.click(screen.getByRole('button', { name: 'Save' }));
- await screen.findByText('Name taken');
+ expect(await screen.findByText('Name taken')).toBeInTheDocument();
});
it('FE-COMP-PASSKEYS-020: deleting asks for the password, calls the API and can be cancelled', async () => {
diff --git a/client/src/components/Settings/PhotoProvidersSection.test.tsx b/client/src/components/Settings/PhotoProvidersSection.test.tsx
index 67b32030f..2f4df8114 100644
--- a/client/src/components/Settings/PhotoProvidersSection.test.tsx
+++ b/client/src/components/Settings/PhotoProvidersSection.test.tsx
@@ -81,7 +81,7 @@ describe('PhotoProvidersSection', () => {
it('FE-COMP-PHOTOPROVIDERS-003: renders a section card for each active provider', async () => {
seedMemoriesEnabled();
render();
- await screen.findByText('Immich');
+ expect(await screen.findByText('Immich')).toBeInTheDocument();
});
it('FE-COMP-PHOTOPROVIDERS-004: renders field inputs for each provider field', async () => {
@@ -95,7 +95,7 @@ describe('PhotoProvidersSection', () => {
it('FE-COMP-PHOTOPROVIDERS-005: non-secret field is prefilled with value from settings API', async () => {
seedMemoriesEnabled();
render();
- await screen.findByDisplayValue('https://photos.example.com');
+ expect(await screen.findByDisplayValue('https://photos.example.com')).toBeInTheDocument();
});
it('FE-COMP-PHOTOPROVIDERS-006: secret field is NOT prefilled (blank value)', async () => {
@@ -240,7 +240,9 @@ describe('PhotoProvidersSection', () => {
await screen.findByText('Immich');
const testBtn = screen.getByRole('button', { name: /test connection/i });
await user.click(testBtn);
- await screen.findByText(/connected/i);
+ // Exact text on purpose: the badge always renders, and a loose /connected/i
+ // also matches the "Not connected" it starts out as.
+ expect(await screen.findByText('Connected')).toBeInTheDocument();
});
it('FE-COMP-PHOTOPROVIDERS-015: failed test shows error toast', async () => {
@@ -258,7 +260,7 @@ describe('PhotoProvidersSection', () => {
await screen.findByText('Immich');
const testBtn = screen.getByRole('button', { name: /test connection/i });
await user.click(testBtn);
- await screen.findByText(/Auth failed/i);
+ expect(await screen.findByText(/Auth failed/i)).toBeInTheDocument();
});
it('FE-COMP-PHOTOPROVIDERS-016: Test button is disabled while test is in progress', async () => {
@@ -327,7 +329,7 @@ describe('PhotoProvidersSection', () => {
seedMemoriesEnabled([fakeProvider, secondProvider]);
render();
await screen.findByText('Immich');
- await screen.findByText('Piwigo');
+ expect(await screen.findByText('Piwigo')).toBeInTheDocument();
});
});
@@ -433,7 +435,7 @@ describe('PhotoProvidersSection – checkbox fields and failures', () => {
await screen.findByText('Immich');
await user.click(screen.getByRole('button', { name: /test connection/i }));
- await screen.findByText('Could not connect to Immich');
+ expect(await screen.findByText('Could not connect to Immich')).toBeInTheDocument();
});
it('FE-COMP-PHOTOPROVIDERS-024: a provider without a test POST route probes over GET instead', async () => {
diff --git a/client/src/components/Todo/TodoListPanel.test.tsx b/client/src/components/Todo/TodoListPanel.test.tsx
index 592d56d6e..5940024e8 100644
--- a/client/src/components/Todo/TodoListPanel.test.tsx
+++ b/client/src/components/Todo/TodoListPanel.test.tsx
@@ -42,7 +42,7 @@ describe('TodoListPanel', () => {
it('FE-COMP-TODO-002: raising addItemSignal opens the new task form', async () => {
const { rerender } = render();
rerender();
- await screen.findByText('Create task');
+ expect(await screen.findByText('Create task')).toBeInTheDocument();
});
it('FE-COMP-TODO-003: sidebar filter buttons are rendered', () => {
@@ -125,7 +125,7 @@ describe('TodoListPanel', () => {
it('FE-COMP-TODO-011: raising addItemSignal opens detail form with Create task button', async () => {
const { rerender } = render();
rerender();
- await screen.findByText('Create task');
+ expect(await screen.findByText('Create task')).toBeInTheDocument();
});
it('FE-COMP-TODO-012: toggling item calls toggleTodoItem action', async () => {
@@ -158,7 +158,7 @@ describe('TodoListPanel', () => {
render();
await user.click(screen.getByText('Click Me'));
// Detail pane should open showing the task title
- await screen.findByText('Task');
+ expect(await screen.findByText('Task')).toBeInTheDocument();
});
it('FE-COMP-TODO-014: category filter appears in sidebar for items with categories', () => {
diff --git a/client/src/components/Trips/TripFormModal.test.tsx b/client/src/components/Trips/TripFormModal.test.tsx
index 75982b6cd..b144809fb 100644
--- a/client/src/components/Trips/TripFormModal.test.tsx
+++ b/client/src/components/Trips/TripFormModal.test.tsx
@@ -112,7 +112,7 @@ describe('TripFormModal', () => {
await user.click(submitBtn.closest('button') || submitBtn);
}
// Error: "Title is required"
- await screen.findByText('Title is required');
+ expect(await screen.findByText('Title is required')).toBeInTheDocument();
});
it('FE-COMP-TRIPFORM-010: typing title and submitting calls onSave', async () => {
@@ -229,7 +229,7 @@ describe('TripFormModal', () => {
)
);
render();
- await screen.findByText('Travel buddies');
+ expect(await screen.findByText('Travel buddies')).toBeInTheDocument();
});
it('FE-COMP-TRIPFORM-024: selecting a member adds a chip', async () => {
@@ -305,7 +305,7 @@ describe('TripFormModal', () => {
const submitBtns = screen.getAllByText('Create New Trip');
const submitBtn = submitBtns.find(el => el.closest('button'))!;
await user.click(submitBtn.closest('button')!);
- await screen.findByText('Server error');
+ expect(await screen.findByText('Server error')).toBeInTheDocument();
});
it('FE-COMP-TRIPFORM-028: loading spinner shown while submitting', async () => {
@@ -720,7 +720,7 @@ describe('TripFormModal', () => {
await user.type(screen.getByPlaceholderText('Search destination photos'), 'nowhere');
await user.click(screen.getByRole('button', { name: /Search Unsplash/i }));
- await screen.findByText('No images found');
+ expect(await screen.findByText('No images found')).toBeInTheDocument();
});
it('FE-COMP-TRIPFORM-054: a failing search shows the server error', async () => {
@@ -735,7 +735,7 @@ describe('TripFormModal', () => {
await user.type(screen.getByPlaceholderText('Search destination photos'), 'alps');
await user.click(screen.getByRole('button', { name: /Search Unsplash/i }));
- await screen.findByText('Unsplash key missing');
+ expect(await screen.findByText('Unsplash key missing')).toBeInTheDocument();
});
it('FE-COMP-TRIPFORM-055: a photo without a photographer falls back in the label and drops the credit', async () => {
@@ -1104,7 +1104,7 @@ describe('TripFormModal', () => {
await user.type(screen.getByPlaceholderText(/Summer in Japan/i), 'Broken');
await submitNewTrip(user);
- await screen.findByText('Failed to save');
+ expect(await screen.findByText('Failed to save')).toBeInTheDocument();
});
it('FE-COMP-TRIPFORM-077: a save error from the date-shift step is shown on that step', async () => {
diff --git a/client/src/components/Trips/TripMembersModal.test.tsx b/client/src/components/Trips/TripMembersModal.test.tsx
index cff2f05a6..d0dfb2e97 100644
--- a/client/src/components/Trips/TripMembersModal.test.tsx
+++ b/client/src/components/Trips/TripMembersModal.test.tsx
@@ -99,18 +99,18 @@ describe('TripMembersModal', () => {
it('FE-COMP-MEMBERS-003: shows owner username after load', async () => {
render();
- await screen.findByText('owner');
+ expect(await screen.findByText('owner')).toBeInTheDocument();
});
it('FE-COMP-MEMBERS-004: shows Owner label', async () => {
render();
- await screen.findByText('Owner');
+ expect(await screen.findByText('Owner')).toBeInTheDocument();
});
it('FE-COMP-MEMBERS-005: shows Access section heading', async () => {
render();
// Text is "Access (1 person)" so use regex
- await screen.findByText(/Access/i);
+ expect(await screen.findByText(/Access/i)).toBeInTheDocument();
});
it('FE-COMP-MEMBERS-006: shows member when members are loaded', async () => {
@@ -124,17 +124,17 @@ describe('TripMembersModal', () => {
)
);
render();
- await screen.findByText('alice');
+ expect(await screen.findByText('alice')).toBeInTheDocument();
});
it('FE-COMP-MEMBERS-007: shows Invite User section', async () => {
render();
- await screen.findByText('Invite User');
+ expect(await screen.findByText('Invite User')).toBeInTheDocument();
});
it('FE-COMP-MEMBERS-008: shows Invite button', async () => {
render();
- await screen.findByRole('button', { name: /Invite/i });
+ expect(await screen.findByRole('button', { name: /Invite/i })).toBeInTheDocument();
});
it('FE-COMP-MEMBERS-009: Cancel/close button is present', () => {
@@ -148,7 +148,7 @@ describe('TripMembersModal', () => {
it('FE-COMP-MEMBERS-010: shows member count of 1 with owner', async () => {
render();
// 1 person (just owner)
- await screen.findByText(/1 person/i);
+ expect(await screen.findByText(/1 person/i)).toBeInTheDocument();
});
it('FE-COMP-MEMBERS-011: members count increases when member is added', async () => {
@@ -162,13 +162,13 @@ describe('TripMembersModal', () => {
)
);
render();
- await screen.findByText(/2 persons/i);
+ expect(await screen.findByText(/2 persons/i)).toBeInTheDocument();
});
it('FE-COMP-MEMBERS-012: shows "you" label next to current user', async () => {
render();
// Rendered as "(you)" — use regex to find it
- await screen.findByText(/\(you\)/i);
+ expect(await screen.findByText(/\(you\)/i)).toBeInTheDocument();
});
it('FE-COMP-MEMBERS-013: shows remove access button for members (not owner)', async () => {
@@ -237,7 +237,7 @@ describe('TripMembersModal', () => {
seedStore(useTripStore, { trip: buildTrip({ id: 1, user_id: ownerUser.id }) });
render();
- await screen.findByText('Public Link');
+ expect(await screen.findByText('Public Link')).toBeInTheDocument();
});
it('FE-COMP-MEMBERS-018: create share link shows URL after clicking create', async () => {
@@ -462,7 +462,7 @@ describe('TripMembersModal', () => {
);
render();
- await screen.findByText('All users already have access.');
+ expect(await screen.findByText('All users already have access.')).toBeInTheDocument();
});
it('FE-COMP-MEMBERS-026: owner sees the guests section and can add a guest (#1362)', async () => {
@@ -519,7 +519,7 @@ describe('TripMembersModal', () => {
mockRoster([], { id: ownerUser.id, username: '', avatar_url: null });
render();
- await screen.findByText('?');
+ expect(await screen.findByText('?')).toBeInTheDocument();
});
it('FE-COMP-MEMBERS-030: a failing roster load shows an error toast and an empty list', async () => {
diff --git a/client/tests/unit/mobile/admin/MAdminPackingTemplateManager.test.tsx b/client/tests/unit/mobile/admin/MAdminPackingTemplateManager.test.tsx
index eb808e101..a73c6deb7 100644
--- a/client/tests/unit/mobile/admin/MAdminPackingTemplateManager.test.tsx
+++ b/client/tests/unit/mobile/admin/MAdminPackingTemplateManager.test.tsx
@@ -136,7 +136,7 @@ describe('MAdminPackingTemplateManager', () => {
await user.click(screen.getByRole('button', { name: 'New Template' }));
await user.type(screen.getByPlaceholderText('Template name (e.g. Beach Holiday)'), 'Boom{Enter}');
- await screen.findByText('Failed to create template');
+ expect(await screen.findByText('Failed to create template')).toBeInTheDocument();
});
it('FE-MOB-APKG-007: Escape and the cancel button both close the create field', async () => {
@@ -369,7 +369,7 @@ describe('MAdminPackingTemplateManager', () => {
await user.click(await screen.findByRole('button', { name: 'Add category' }));
await user.type(screen.getByPlaceholderText('Category name (e.g. Clothing)'), 'Electronics{Enter}');
- await screen.findByText('Failed to save');
+ expect(await screen.findByText('Failed to save')).toBeInTheDocument();
});
it('FE-MOB-APKG-018: renaming a category updates its header', async () => {
@@ -586,7 +586,7 @@ describe('MAdminPackingTemplateManager', () => {
await user.click(within(categoryHeader('Clothing')).getByRole('button', { name: 'Item name' }));
await user.type(screen.getByPlaceholderText('Item name'), 'Sandals{Enter}');
- await screen.findByText('Failed to save');
+ expect(await screen.findByText('Failed to save')).toBeInTheDocument();
});
it('FE-MOB-APKG-026: renaming an item persists via the save button', async () => {
diff --git a/client/tests/unit/mobile/settings/MAirTrailConnectionSection.test.tsx b/client/tests/unit/mobile/settings/MAirTrailConnectionSection.test.tsx
index cd38dfb23..ef1d605a4 100644
--- a/client/tests/unit/mobile/settings/MAirTrailConnectionSection.test.tsx
+++ b/client/tests/unit/mobile/settings/MAirTrailConnectionSection.test.tsx
@@ -162,7 +162,7 @@ describe('MAirTrailConnectionSection', () => {
renderSection();
await user.click(await screen.findByRole('button', { name: /Save/ }));
- await screen.findByText('Not connected');
+ expect(await screen.findByText('Not connected')).toBeInTheDocument();
});
it('FE-MOB-SETAIR-010: a rejected save surfaces the server error message', async () => {
@@ -176,7 +176,7 @@ describe('MAirTrailConnectionSection', () => {
renderSection();
await user.click(await screen.findByRole('button', { name: /Save/ }));
- await screen.findByText('Instance unreachable');
+ expect(await screen.findByText('Instance unreachable')).toBeInTheDocument();
});
it('FE-MOB-SETAIR-011: a successful test reports the flight count and flips the badge', async () => {
@@ -214,6 +214,6 @@ describe('MAirTrailConnectionSection', () => {
renderSection();
await user.click(await screen.findByRole('button', { name: /Test connection/ }));
- await screen.findByText('Connection failed');
+ expect(await screen.findByText('Connection failed')).toBeInTheDocument();
});
});
diff --git a/client/tests/unit/mobile/settings/MLlmConnectionSection.test.tsx b/client/tests/unit/mobile/settings/MLlmConnectionSection.test.tsx
index 056891da2..7a868666d 100644
--- a/client/tests/unit/mobile/settings/MLlmConnectionSection.test.tsx
+++ b/client/tests/unit/mobile/settings/MLlmConnectionSection.test.tsx
@@ -170,7 +170,7 @@ describe('MLlmConnectionSection', () => {
renderSection();
await user.click(screen.getByRole('button', { name: /Save/ }));
- await screen.findByText('Could not save AI settings');
+ expect(await screen.findByText('Could not save AI settings')).toBeInTheDocument();
});
it('FE-MOB-SETLLM-013: a stored local provider falls back to OpenAI without saving anything', () => {
diff --git a/client/tests/unit/mobile/settings/MSettingsAccount.test.tsx b/client/tests/unit/mobile/settings/MSettingsAccount.test.tsx
index 050bb281c..850e8400a 100644
--- a/client/tests/unit/mobile/settings/MSettingsAccount.test.tsx
+++ b/client/tests/unit/mobile/settings/MSettingsAccount.test.tsx
@@ -153,7 +153,7 @@ describe('MSettingsAccount – profile', () => {
fireEvent.change(fileInput(), { target: { files: [new File(['x'], 'a.png', { type: 'image/png' })] } });
- await screen.findByText('Upload failed');
+ expect(await screen.findByText('Upload failed')).toBeInTheDocument();
});
it('FE-MOB-SETACC-009: a change event without a file is ignored', () => {
@@ -191,7 +191,7 @@ describe('MSettingsAccount – profile', () => {
await user.click(screen.getByRole('button', { name: 'Remove Profile Picture' }));
- await screen.findByText('Removal failed');
+ expect(await screen.findByText('Removal failed')).toBeInTheDocument();
});
it('FE-MOB-SETACC-012: Save sends the edited fields through updateProfile', async () => {
@@ -230,7 +230,7 @@ describe('MSettingsAccount – profile', () => {
await user.click(screen.getByRole('button', { name: 'Save' }));
- await screen.findByText('Error');
+ expect(await screen.findByText('Error')).toBeInTheDocument();
});
it('FE-MOB-SETACC-013c: without a signed-in user both profile fields start empty', () => {
@@ -251,7 +251,7 @@ describe('MSettingsAccount – profile', () => {
await user.click(screen.getByRole('button', { name: 'Save' }));
- await screen.findByText('Email already taken');
+ expect(await screen.findByText('Email already taken')).toBeInTheDocument();
});
it('FE-MOB-SETACC-014: Save is disabled while the request is in flight', async () => {
@@ -291,7 +291,7 @@ describe('MSettingsAccount – password', () => {
await user.click(screen.getByRole('button', { name: 'Update password' }));
- await screen.findByText('Current password is required');
+ expect(await screen.findByText('Current password is required')).toBeInTheDocument();
});
it('FE-MOB-SETACC-018: an empty new password is rejected', async () => {
@@ -301,7 +301,7 @@ describe('MSettingsAccount – password', () => {
await user.type(screen.getByPlaceholderText('Current password'), 'old-secret');
await user.click(screen.getByRole('button', { name: 'Update password' }));
- await screen.findByText('Please enter current and new password');
+ expect(await screen.findByText('Please enter current and new password')).toBeInTheDocument();
});
it('FE-MOB-SETACC-019: a new password below eight characters is rejected', async () => {
@@ -313,7 +313,7 @@ describe('MSettingsAccount – password', () => {
await user.type(screen.getByPlaceholderText('Confirm new password'), 'short');
await user.click(screen.getByRole('button', { name: 'Update password' }));
- await screen.findByText('Password must be at least 8 characters');
+ expect(await screen.findByText('Password must be at least 8 characters')).toBeInTheDocument();
});
it('FE-MOB-SETACC-020: a mismatching confirmation is rejected', async () => {
@@ -325,7 +325,7 @@ describe('MSettingsAccount – password', () => {
await user.type(screen.getByPlaceholderText('Confirm new password'), 'NewPassword2!');
await user.click(screen.getByRole('button', { name: 'Update password' }));
- await screen.findByText('Passwords do not match');
+ expect(await screen.findByText('Passwords do not match')).toBeInTheDocument();
});
it('FE-MOB-SETACC-021: a valid change hits the API and clears the three fields', async () => {
@@ -361,7 +361,7 @@ describe('MSettingsAccount – password', () => {
await user.type(screen.getByPlaceholderText('Confirm new password'), 'NewPassword1!');
await user.click(screen.getByRole('button', { name: 'Update password' }));
- await screen.findByText('Wrong password');
+ expect(await screen.findByText('Wrong password')).toBeInTheDocument();
});
});
@@ -453,7 +453,7 @@ describe('MSettingsAccount – MFA', () => {
await user.click(screen.getByRole('button', { name: 'Set up authenticator' }));
- await screen.findByText('MFA is off');
+ expect(await screen.findByText('MFA is off')).toBeInTheDocument();
});
it('FE-MOB-SETACC-032: enabling stores the backup codes and renders them', async () => {
@@ -493,7 +493,7 @@ describe('MSettingsAccount – MFA', () => {
await user.type(screen.getByPlaceholderText('6-digit code'), '123456');
await user.click(screen.getByRole('button', { name: 'Enable 2FA' }));
- await screen.findByText('Invalid code');
+ expect(await screen.findByText('Invalid code')).toBeInTheDocument();
});
it('FE-MOB-SETACC-035: the disable form needs both a password and a six-digit code', async () => {
@@ -547,7 +547,7 @@ describe('MSettingsAccount – MFA', () => {
await user.type(screen.getByPlaceholderText('6-digit code'), '123456');
await user.click(screen.getByRole('button', { name: 'Disable 2FA' }));
- await screen.findByText('Code expired');
+ expect(await screen.findByText('Code expired')).toBeInTheDocument();
});
});
@@ -614,7 +614,7 @@ describe('MSettingsAccount – backup codes', () => {
await user.click(await screen.findByRole('button', { name: 'Copy codes' }));
- await screen.findByText('Error');
+ expect(await screen.findByText('Error')).toBeInTheDocument();
});
it('FE-MOB-SETACC-044: Download TXT builds a blob and revokes the object URL', async () => {
@@ -1020,7 +1020,7 @@ describe('MSettingsAccount – passkeys', () => {
await user.type(within(row).getByDisplayValue('iPhone'), '2');
await user.click(within(row).getByRole('button', { name: 'Save' }));
- await screen.findByText('Name taken');
+ expect(await screen.findByText('Name taken')).toBeInTheDocument();
});
it('FE-MOB-SETACC-068: deleting asks for the password and removes the credential', async () => {
@@ -1068,7 +1068,7 @@ describe('MSettingsAccount – passkeys', () => {
await user.type(within(box).getByPlaceholderText('Current password'), 'nope');
await user.click(within(box).getByRole('button', { name: 'Delete' }));
- await screen.findByText('Wrong password');
+ expect(await screen.findByText('Wrong password')).toBeInTheDocument();
});
it('FE-MOB-SETACC-070: cancelling the delete step-up closes the box', async () => {
@@ -1151,7 +1151,7 @@ describe('MSettingsAccount – passkeys', () => {
await user.type(within(form).getByPlaceholderText('Current password'), 'my-password');
await user.click(within(form).getByRole('button', { name: 'Add a passkey' }));
- await screen.findByText('Passkey setup cancelled');
+ expect(await screen.findByText('Passkey setup cancelled')).toBeInTheDocument();
});
it('FE-MOB-SETACC-073: a rejected password step-up shows the server message', async () => {
diff --git a/client/tests/unit/mobile/settings/MSettingsAppearance.test.tsx b/client/tests/unit/mobile/settings/MSettingsAppearance.test.tsx
index bb39734d0..add74896b 100644
--- a/client/tests/unit/mobile/settings/MSettingsAppearance.test.tsx
+++ b/client/tests/unit/mobile/settings/MSettingsAppearance.test.tsx
@@ -110,7 +110,7 @@ describe('MSettingsAppearance', () => {
renderAppearance();
await user.click(screen.getByRole('button', { name: 'Dark' }));
- await screen.findByText('Server down');
+ expect(await screen.findByText('Server down')).toBeInTheDocument();
});
it('FE-MOB-SETAPP-006: choosing a scheme applies it to the DOM and persists it debounced', async () => {
@@ -146,7 +146,7 @@ describe('MSettingsAppearance', () => {
renderAppearance();
await user.click(screen.getByRole('button', { name: 'Amber' }));
- await screen.findByText('Blob too large');
+ expect(await screen.findByText('Blob too large')).toBeInTheDocument();
});
it('FE-MOB-SETAPP-009: the custom scheme reveals the accent presets and the contrast badge', async () => {
diff --git a/client/tests/unit/mobile/settings/MSettingsGeneral.test.tsx b/client/tests/unit/mobile/settings/MSettingsGeneral.test.tsx
index d7fed651b..d1a7a99ed 100644
--- a/client/tests/unit/mobile/settings/MSettingsGeneral.test.tsx
+++ b/client/tests/unit/mobile/settings/MSettingsGeneral.test.tsx
@@ -142,7 +142,7 @@ describe('MSettingsGeneral', () => {
render(<>>);
await user.click(screen.getByRole('button', { name: '°F Fahrenheit' }));
- await screen.findByText('Error saving setting');
+ expect(await screen.findByText('Error saving setting')).toBeInTheDocument();
});
it('FE-MOB-SET-011: a non-Error rejection falls back to the generic message', async () => {
@@ -154,7 +154,7 @@ describe('MSettingsGeneral', () => {
render(<>>);
await user.click(screen.getByRole('button', { name: '°F Fahrenheit' }));
- await screen.findByText('Error');
+ expect(await screen.findByText('Error')).toBeInTheDocument();
});
it('FE-MOB-SET-012: the startup card defaults to the dashboard and hides the tab row', () => {
diff --git a/client/tests/unit/mobile/settings/MSettingsPlugins.test.tsx b/client/tests/unit/mobile/settings/MSettingsPlugins.test.tsx
index b58f62d2c..c1e871d79 100644
--- a/client/tests/unit/mobile/settings/MSettingsPlugins.test.tsx
+++ b/client/tests/unit/mobile/settings/MSettingsPlugins.test.tsx
@@ -123,7 +123,7 @@ describe('MSettingsPlugins', () => {
server.use(http.get('/api/plugin-activity', () => HttpResponse.json({}, { status: 500 })));
renderScreen();
- await screen.findByText('No plugin activity yet.');
+ expect(await screen.findByText('No plugin activity yet.')).toBeInTheDocument();
});
it('FE-MOB-SETPLG-005: declared user fields render as the matching mobile controls', async () => {
@@ -296,7 +296,7 @@ describe('MSettingsPlugins', () => {
await screen.findByText('Success');
await user.click(screen.getByRole('button', { name: 'Probe' }));
- await screen.findByText('Error');
+ expect(await screen.findByText('Error')).toBeInTheDocument();
});
it('FE-MOB-SETPLG-014: unlabelled fields fall back to their key, hints render below', async () => {
@@ -335,7 +335,7 @@ describe('MSettingsPlugins', () => {
await screen.findByText('Actions');
await user.click(screen.getByRole('button', { name: 'Test connection' }));
- await screen.findByText('Error');
+ expect(await screen.findByText('Error')).toBeInTheDocument();
});
it('FE-MOB-SETPLG-016: a danger action asks for confirmation before it runs', async () => {
diff --git a/client/tests/unit/slices/filesSlice.test.ts b/client/tests/unit/slices/filesSlice.test.ts
index 7f5adc8c5..dce431680 100644
--- a/client/tests/unit/slices/filesSlice.test.ts
+++ b/client/tests/unit/slices/filesSlice.test.ts
@@ -41,14 +41,21 @@ describe('filesSlice', () => {
});
it('FE-FILES-002: loadFiles silently catches errors', async () => {
+ const existing = buildTripFile({ trip_id: 1, filename: 'existing.pdf' });
+ seedStore(useTripStore, { files: [existing] });
+
server.use(
http.get('/api/trips/1/files', () =>
HttpResponse.json({ message: 'Error' }, { status: 500 })
),
);
- // Should not throw
- await useTripStore.getState().loadFiles(1);
+ // Swallows the failure instead of rejecting, and leaves the list alone
+ await expect(useTripStore.getState().loadFiles(1)).resolves.toBeUndefined();
+
+ const files = useTripStore.getState().files;
+ expect(files).toHaveLength(1);
+ expect(files[0].filename).toBe('existing.pdf');
});
});
diff --git a/server/tests/unit/nest/journey-domain.service.test.ts b/server/tests/unit/nest/journey-domain.service.test.ts
index a9c7f3373..b6d445715 100644
--- a/server/tests/unit/nest/journey-domain.service.test.ts
+++ b/server/tests/unit/nest/journey-domain.service.test.ts
@@ -1161,8 +1161,12 @@ describe('onPlaceDeleted', () => {
const trip = createTrip(testDb, user.id, { title: 'Unlinked' });
const place = createPlace(testDb, trip.id, { name: 'Nowhere' });
- // Should not throw
- svc.onPlaceDeleted(place.id);
+ expect(() => svc.onPlaceDeleted(place.id)).not.toThrow();
+
+ const orphaned = testDb.prepare(
+ "SELECT COUNT(*) AS n FROM journey_entries WHERE source_place_id = ?"
+ ).get(place.id) as { n: number };
+ expect(orphaned.n).toBe(0);
});
});