mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-21 06:11:45 +00:00
feat(places): unified file import modal with drag-and-drop and deduplication
- Replace separate GPX and KML/KMZ import buttons with a single "Import file" modal accepting all three formats, with a drag-and-drop drop zone - Support dragging files directly onto the Places sidebar panel; overlay appears on hover and pre-loads the file into the modal on drop - Fix [object Object] description bug in KML imports caused by fast-xml-parser returning mixed-content nodes as objects; add stopNodes config and object guard in asTrimmedString - Fix CDATA sections leaking into descriptions (e.g. "text.]]>") by unwrapping CDATA markers before tag stripping - Add import deduplication across all import paths (GPX, KML/KMZ, Google list, Naver list): reimporting skips places already in the trip by name (case-insensitive) or by coordinates (within ~11 m tolerance), with intra-batch dedup so duplicate placemarks within the same file are also collapsed - Fix KML route returning 400 "No valid Placemarks found" when all placemarks were valid but deduplicated; 400 now only fires when the file contains zero placemarks - Show a warning toast "All places were already in the trip" instead of a misleading success toast when a reimport produces zero new places (GPX, KML/KMZ, Google list, Naver list) - Add 8 new i18n keys across all 14 locales; remove 11 keys made unused by the modal consolidation
This commit is contained in:
@@ -433,29 +433,29 @@ describe('Mobile day-picker (portal)', () => {
|
||||
// ── GPX import ────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('GPX import', () => {
|
||||
it('FE-PLANNER-SIDEBAR-038: GPX import button triggers file input click', async () => {
|
||||
it('FE-PLANNER-SIDEBAR-038: "Import file" button opens the file import modal', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<PlacesSidebar {...defaultProps} />);
|
||||
const fileInput = document.querySelector('input[type="file"][accept=".gpx"]') as HTMLInputElement;
|
||||
expect(fileInput).toBeTruthy();
|
||||
const clickSpy = vi.spyOn(fileInput, 'click');
|
||||
await user.click(screen.getByText(/GPX/i));
|
||||
expect(clickSpy).toHaveBeenCalled();
|
||||
await user.click(screen.getByText(/Import file/i));
|
||||
expect(await screen.findByText(/\.gpx.*\.kml.*\.kmz/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('FE-PLANNER-SIDEBAR-039: successful GPX import shows success toast', async () => {
|
||||
// FormData POST hangs on CI — mock at the API boundary instead of MSW.
|
||||
it('FE-PLANNER-SIDEBAR-039: successful GPX import via modal shows success toast', async () => {
|
||||
const importSpy = vi.spyOn(placesApi, 'importGpx').mockResolvedValueOnce({ count: 2, places: [{ id: 10 }, { id: 11 }] });
|
||||
const loadTrip = vi.fn().mockResolvedValue(undefined);
|
||||
seedStore(useTripStore, { loadTrip });
|
||||
const addToast = vi.fn();
|
||||
(window as any).__addToast = addToast;
|
||||
const user = userEvent.setup();
|
||||
render(<PlacesSidebar {...defaultProps} pushUndo={vi.fn()} />);
|
||||
const fileInput = document.querySelector('input[type="file"][accept=".gpx"]') as HTMLInputElement;
|
||||
await user.click(screen.getByText(/Import file/i));
|
||||
const fileInput = document.querySelector('input[type="file"][accept=".gpx,.kml,.kmz"]') as HTMLInputElement;
|
||||
expect(fileInput).toBeTruthy();
|
||||
const file = new File(['track data'], 'route.gpx', { type: 'application/gpx+xml' });
|
||||
await act(async () => {
|
||||
fireEvent.change(fileInput, { target: { files: [file] } });
|
||||
});
|
||||
await user.click(screen.getByRole('button', { name: /^import$/i }));
|
||||
await waitFor(() => {
|
||||
expect(addToast).toHaveBeenCalledWith(
|
||||
expect.stringContaining('2'),
|
||||
|
||||
Reference in New Issue
Block a user