→ querySelector for dashed button
const bagNameEl = screen.getAllByText('Day Pack')[0];
const bagCardOuter = bagNameEl.parentElement!.parentElement!;
const bagCardPlusBtn = bagCardOuter.querySelector('button[style*="dashed"]') as HTMLElement;
expect(bagCardPlusBtn).toBeTruthy();
await user.click(bagCardPlusBtn);
// User picker dropdown appears with member names (tripMembers already loaded)
await screen.findByText('bob');
expect(screen.getByText('owner')).toBeInTheDocument();
});
it('FE-COMP-PACKING-067: BagCard user picker member click calls setBagMembers', async () => {
let membersBody: Record
| null = null;
server.use(
http.get('/api/trips/:id/members', () =>
HttpResponse.json({
owner: { id: 1, username: 'owner', avatar_url: null },
members: [{ id: 3, username: 'carol', avatar_url: null }],
current_user_id: 1,
})
),
http.get('/api/admin/bag-tracking', () => HttpResponse.json({ enabled: true })),
http.get('/api/trips/:id/packing/bags', () =>
HttpResponse.json({ bags: [{ id: 13, name: 'Weekend Bag', color: '#f97316', weight_limit_grams: null, members: [] }] })
),
http.put('/api/trips/1/packing/bags/13/members', async ({ request }) => {
membersBody = await request.json() as Record;
return HttpResponse.json({ members: [{ user_id: 3, username: 'carol', avatar: null }] });
})
);
const items = [buildPackingItem({ name: 'Laptop', category: 'Tech' })];
const { container } = render();
// Wait for the BagCard to render and tripMembers to load
await waitFor(() => {
expect(screen.getAllByText('Weekend Bag').length).toBeGreaterThan(0);
});
await waitFor(() => {
expect(container.querySelector('svg.lucide-user-plus')).toBeTruthy();
});
// Find BagCard Plus button within the BagCard's DOM subtree:
// bag name → header row → outer BagCard
→ find dashed button
const bagNameEl = screen.getAllByText('Weekend Bag')[0];
const bagCardOuter = bagNameEl.parentElement!.parentElement!;
const bagCardPlusBtn = bagCardOuter.querySelector('button[style*="dashed"]') as HTMLElement;
expect(bagCardPlusBtn).toBeTruthy();
fireEvent.click(bagCardPlusBtn);
// Click 'carol' in the picker (accessible name: "C carol" from avatar initial + username)
const carolBtn = await screen.findByText('carol');
fireEvent.click(carolBtn.closest('button')!);
await waitFor(() => expect(membersBody).toMatchObject({ user_ids: [3] }));
});
it('FE-COMP-PACKING-068: inline bag create in item row picker creates bag and assigns it', async () => {
let createBody: Record
| null = null;
server.use(
http.get('/api/admin/bag-tracking', () => HttpResponse.json({ enabled: true })),
http.get('/api/trips/:id/packing/bags', () => HttpResponse.json({ bags: [] })),
http.post('/api/trips/1/packing/bags', async ({ request }) => {
createBody = await request.json() as Record;
return HttpResponse.json({ bag: { id: 20, name: 'New Bag', color: '#6366f1', weight_limit_grams: null, members: [] } });
}),
http.put('/api/trips/1/packing/150', async () =>
HttpResponse.json({ item: buildPackingItem({ id: 150 }) })
)
);
const items = [buildPackingItem({ id: 150, name: 'Sunglasses', category: 'Accessories' })];
const { container } = render();
// Wait for Package icon (bag button in item row)
await waitFor(() => expect(container.querySelector('svg.lucide-package')).toBeTruthy());
// Use fireEvent to open picker (avoids mouseLeave pointer events)
const packageBtn = container.querySelector('svg.lucide-package')?.closest('button');
fireEvent.click(packageBtn!);
// Click "Add bag" inside picker to show inline create
const addBagInPickerBtns = await screen.findAllByText('Add bag');
fireEvent.click(addBagInPickerBtns[addBagInPickerBtns.length - 1]);
// Inline input appears in picker
const inlineInput = await screen.findByPlaceholderText('Bag name...');
fireEvent.change(inlineInput, { target: { value: 'New Bag' } });
fireEvent.keyDown(inlineInput, { key: 'Enter' });
await waitFor(() => expect(createBody).toMatchObject({ name: 'New Bag' }));
});
it('FE-COMP-PACKING-069: Load CSV/TXT button clicks the hidden file input', async () => {
const user = userEvent.setup();
const { container } = render();
// Open import modal
const importBtn = container.querySelector('svg.lucide-upload')?.closest('button');
await user.click(importBtn!);
await screen.findByText('Import Packing List');
// Spy on the hidden file input's click method
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
expect(fileInput).toBeTruthy();
const clickSpy = vi.spyOn(fileInput, 'click').mockImplementation(() => {});
// Click the "Load CSV/TXT" button
await user.click(screen.getByText('Load CSV/TXT'));
expect(clickSpy).toHaveBeenCalled();
clickSpy.mockRestore();
});
});