mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 16:03:02 +00:00
fix: the last red checks after the tour, home and place picture merges
A GPX file that mixes routes and tracks gives each its own text: the pieces are matched to the reader's rows by kind, routes first as the reader gives them, where they were paired by index and a route got a track's points. The place pictures reach the network through a small repo instead of the api module, the wanderer parity case and the tool catalog test allow wanderer, and the knip and theme baselines follow the lower counts.
This commit is contained in:
@@ -6,12 +6,12 @@ import type { PlacePhotoCandidate } from '@trek/shared';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { buildTripFile } from '../../../tests/helpers/factories';
|
||||
import { fetchImageAsBlob } from '../../api/authUrl';
|
||||
import { placePictureRepo } from '../../repo/placePictureRepo';
|
||||
import { normalizeImageFile } from '../../utils/convertHeic';
|
||||
import PlaceImagePicker, { PhotoCredit, type PlaceImageOwn } from './PlaceImagePicker';
|
||||
import type { ChosenImage } from './placeImageChoice';
|
||||
|
||||
vi.mock('../../api/authUrl', () => ({ fetchImageAsBlob: vi.fn() }));
|
||||
vi.mock('../../repo/placePictureRepo', () => ({ placePictureRepo: { attachedImage: vi.fn() } }));
|
||||
vi.mock('../../utils/convertHeic', () => ({
|
||||
normalizeImageFile: vi.fn(async (file: File) => new File([file], `normalized-${file.name}`, { type: 'image/jpeg' })),
|
||||
}));
|
||||
@@ -81,8 +81,8 @@ function renderPicker(
|
||||
const fileInput = () => document.querySelector('input[type="file"]') as HTMLInputElement;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.mocked(fetchImageAsBlob).mockReset();
|
||||
vi.mocked(fetchImageAsBlob).mockResolvedValue('blob:attached');
|
||||
vi.mocked(placePictureRepo.attachedImage).mockReset();
|
||||
vi.mocked(placePictureRepo.attachedImage).mockResolvedValue('blob:attached');
|
||||
vi.mocked(normalizeImageFile).mockClear();
|
||||
});
|
||||
|
||||
@@ -168,19 +168,19 @@ describe('PlaceImagePicker', () => {
|
||||
renderPicker({ own: mine });
|
||||
const tile = screen.getByRole('button', { name: 'facade.png' });
|
||||
await waitFor(() => expect(tile.querySelector('img')).toHaveAttribute('src', 'blob:attached'));
|
||||
expect(fetchImageAsBlob).toHaveBeenCalledWith('/api/files/41');
|
||||
expect(placePictureRepo.attachedImage).toHaveBeenCalledWith('/api/files/41');
|
||||
|
||||
fireEvent.click(tile);
|
||||
expect(mine.onPickAttached).toHaveBeenCalledWith(ATTACHED);
|
||||
});
|
||||
|
||||
it('FE-PLANNER-PLIMGPICK-009: a chosen attached image is ticked and named under the grid; one that fails to load stays blank', async () => {
|
||||
vi.mocked(fetchImageAsBlob).mockRejectedValue(new Error('gone'));
|
||||
vi.mocked(placePictureRepo.attachedImage).mockRejectedValue(new Error('gone'));
|
||||
renderPicker({ own: own({ attached: [ATTACHED] }), chosen: { kind: 'attached', fileId: 41 } });
|
||||
const tile = screen.getByRole('button', { name: 'facade.png' });
|
||||
expect(tile).toHaveAttribute('aria-pressed', 'true');
|
||||
expect(screen.getByText('facade.png')).toBeInTheDocument();
|
||||
await waitFor(() => expect(fetchImageAsBlob).toHaveBeenCalled());
|
||||
await waitFor(() => expect(placePictureRepo.attachedImage).toHaveBeenCalled());
|
||||
expect(tile.querySelector('img')).toBeNull();
|
||||
|
||||
// Focusing it names it as well, and the name stays once it is left.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { PlacePhotoCandidate } from '@trek/shared';
|
||||
import { Check, ImageOff, ImagePlus, Loader2 } from 'lucide-react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { fetchImageAsBlob } from '../../api/authUrl';
|
||||
import { placePictureRepo } from '../../repo/placePictureRepo';
|
||||
import type { TranslationFn, TripFile } from '../../types';
|
||||
import { normalizeImageFile } from '../../utils/convertHeic';
|
||||
import { Tooltip } from '../shared/Tooltip';
|
||||
@@ -308,7 +308,8 @@ function AttachedTile({
|
||||
const [src, setSrc] = useState<string | null>(null);
|
||||
useEffect(() => {
|
||||
let current = true;
|
||||
fetchImageAsBlob(file.url)
|
||||
placePictureRepo
|
||||
.attachedImage(file.url)
|
||||
.then((url) => {
|
||||
if (current) setSrc(url);
|
||||
})
|
||||
|
||||
@@ -13,10 +13,8 @@ import { __clearEnrichmentCacheForTests, readCachedEnrichment, storeEnrichment }
|
||||
import { usePlaceGallery } from './usePlaceGallery';
|
||||
|
||||
const placeEnrichment = vi.fn();
|
||||
// Partial: the store registry behind resetAllStores needs the real default client.
|
||||
vi.mock('../../api/client', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import('../../api/client')>()),
|
||||
mapsApi: { placeEnrichment: (...a: unknown[]) => placeEnrichment(...a) },
|
||||
vi.mock('../../repo/placePictureRepo', () => ({
|
||||
placePictureRepo: { enrichment: (...a: unknown[]) => placeEnrichment(...a) },
|
||||
}));
|
||||
// One toast for every render, the way the real hook memoises it.
|
||||
const toast = vi.hoisted(() => ({ success: vi.fn(), error: vi.fn(), warning: vi.fn(), info: vi.fn() }));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { PlacePhotoCandidate } from '@trek/shared';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { mapsApi } from '../../api/client';
|
||||
import { translateApiError, useTranslation } from '../../i18n';
|
||||
import { placePictureRepo } from '../../repo/placePictureRepo';
|
||||
import { useAuthStore } from '../../store/authStore';
|
||||
import type { Place } from '../../types';
|
||||
import { useToast } from '../shared/Toast';
|
||||
@@ -117,8 +117,8 @@ export function usePlaceGallery(place: Place, language: string): UsePlaceGallery
|
||||
// An answer for a place the user has left is kept for that place, but opens nothing and says nothing.
|
||||
const current = () => shownRef.current === placeId;
|
||||
setLoadingFor(placeId);
|
||||
mapsApi
|
||||
.placeEnrichment({ ...selection, lang: language })
|
||||
placePictureRepo
|
||||
.enrichment({ ...selection, lang: language })
|
||||
.then((result) => {
|
||||
storeEnrichment(selection, language, result);
|
||||
if (current()) apply(result.photos);
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// FE-PLACE-PICTURE-REPO-001 to FE-PLACE-PICTURE-REPO-002: the pictures a place can be given,
|
||||
// asked of the maps route and the attached files behind the repository.
|
||||
import type { MapsPlaceEnrichmentResult } from '@trek/shared';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { fetchImageAsBlob } from '../api/authUrl';
|
||||
import { mapsApi } from '../api/client';
|
||||
import { placePictureRepo } from './placePictureRepo';
|
||||
|
||||
vi.mock('../api/client', () => ({ mapsApi: { placeEnrichment: vi.fn() } }));
|
||||
vi.mock('../api/authUrl', () => ({ fetchImageAsBlob: vi.fn() }));
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('place pictures', () => {
|
||||
it('FE-PLACE-PICTURE-REPO-001: asks the maps route for the enrichment with the query and the signal', async () => {
|
||||
const answer = { photos: [] } as unknown as MapsPlaceEnrichmentResult;
|
||||
vi.mocked(mapsApi.placeEnrichment).mockResolvedValue(answer);
|
||||
const signal = new AbortController().signal;
|
||||
const query = { placeId: 'way:2', lat: 48.1, lng: 11.5, name: 'Rathaus', lang: 'de' };
|
||||
|
||||
await expect(placePictureRepo.enrichment(query, signal)).resolves.toBe(answer);
|
||||
expect(mapsApi.placeEnrichment).toHaveBeenCalledWith(query, signal);
|
||||
});
|
||||
|
||||
it('FE-PLACE-PICTURE-REPO-002: reads an attached image as an object URL', async () => {
|
||||
vi.mocked(fetchImageAsBlob).mockResolvedValue('blob:attached');
|
||||
|
||||
await expect(placePictureRepo.attachedImage('/api/files/41')).resolves.toBe('blob:attached');
|
||||
expect(fetchImageAsBlob).toHaveBeenCalledWith('/api/files/41');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
import type { MapsPlaceEnrichmentResult } from '@trek/shared';
|
||||
import { fetchImageAsBlob } from '../api/authUrl';
|
||||
import { mapsApi } from '../api/client';
|
||||
|
||||
/** What the sources are asked for one place: where it is, what it is called, in which language. */
|
||||
interface PlaceEnrichmentQuery {
|
||||
placeId?: string;
|
||||
lat: number;
|
||||
lng: number;
|
||||
name: string;
|
||||
lang?: string;
|
||||
details?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The pictures a place can be given: the sources' photos and the images
|
||||
* attached to it. Online only by nature, and nothing here is cached on the
|
||||
* device; the session cache for enrichment lives with the planner view.
|
||||
*/
|
||||
export const placePictureRepo = {
|
||||
/** The sources' pictures and description for a place, as the maps route answers them. */
|
||||
enrichment(query: PlaceEnrichmentQuery, signal?: AbortSignal): Promise<MapsPlaceEnrichmentResult> {
|
||||
return mapsApi.placeEnrichment(query, signal);
|
||||
},
|
||||
|
||||
/** An attached image as an object URL the view can show; empty when it could not be read. */
|
||||
attachedImage(url: string): Promise<string> {
|
||||
return fetchImageAsBlob(url);
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user