Refactor frontend components for improved functionality and accessibility

- Consolidated global error handling in app.vue to redirect users to the login page on API authentication failure.
- Enhanced MapView component by reintroducing event listeners for selected map and marker updates, improving interactivity.
- Updated PasswordInput and various modal components to ensure proper input handling and accessibility compliance.
- Refactored MapControls and MapControlsContent to streamline prop management and enhance user experience.
- Improved error handling in local storage operations within useMapBookmarks and useRecentLocations composables.
- Standardized input elements across forms for consistency in user interaction.
This commit is contained in:
2026-03-04 14:06:27 +03:00
parent 761fbaed55
commit fd624c2357
30 changed files with 109 additions and 97 deletions

View File

@@ -1,10 +1,10 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { useAppPaths } from '../useAppPaths'
const useRuntimeConfigMock = vi.fn()
vi.stubGlobal('useRuntimeConfig', useRuntimeConfigMock)
import { useAppPaths } from '../useAppPaths'
describe('useAppPaths with default base /', () => {
beforeEach(() => {
useRuntimeConfigMock.mockReturnValue({ app: { baseURL: '/' } })

View File

@@ -1,12 +1,12 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { useMapApi } from '../useMapApi'
vi.stubGlobal('useRuntimeConfig', () => ({
app: { baseURL: '/' },
public: { apiBase: '/map/api' },
}))
import { useMapApi } from '../useMapApi'
function mockFetch(status: number, body: unknown, contentType = 'application/json') {
return vi.fn().mockResolvedValue({
ok: status >= 200 && status < 300,

View File

@@ -1,6 +1,8 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { ref } from 'vue'
import { useMapBookmarks } from '../useMapBookmarks'
const stateByKey: Record<string, ReturnType<typeof ref>> = {}
const useStateMock = vi.fn((key: string, init: () => unknown) => {
if (!stateByKey[key]) {
@@ -18,15 +20,13 @@ const localStorageMock = {
storage[key] = value
}),
clear: vi.fn(() => {
for (const k of Object.keys(storage)) delete storage[k]
delete storage['hnh-map-bookmarks']
}),
}
vi.stubGlobal('localStorage', localStorageMock)
vi.stubGlobal('import.meta.server', false)
vi.stubGlobal('import.meta.client', true)
import { useMapBookmarks } from '../useMapBookmarks'
describe('useMapBookmarks', () => {
beforeEach(() => {
storage['hnh-map-bookmarks'] = '[]'

View File

@@ -1,11 +1,12 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { ref, reactive } from 'vue'
import type { Map } from 'leaflet'
import { useMapLogic } from '../useMapLogic'
vi.stubGlobal('ref', ref)
vi.stubGlobal('reactive', reactive)
import { useMapLogic } from '../useMapLogic'
describe('useMapLogic', () => {
beforeEach(() => {
vi.clearAllMocks()
@@ -27,7 +28,7 @@ describe('useMapLogic', () => {
it('zoomIn calls map.zoomIn', () => {
const { zoomIn } = useMapLogic()
const mockMap = { zoomIn: vi.fn() }
zoomIn(mockMap as unknown as import('leaflet').Map)
zoomIn(mockMap as unknown as Map)
expect(mockMap.zoomIn).toHaveBeenCalled()
})
@@ -39,7 +40,7 @@ describe('useMapLogic', () => {
it('zoomOutControl calls map.zoomOut', () => {
const { zoomOutControl } = useMapLogic()
const mockMap = { zoomOut: vi.fn() }
zoomOutControl(mockMap as unknown as import('leaflet').Map)
zoomOutControl(mockMap as unknown as Map)
expect(mockMap.zoomOut).toHaveBeenCalled()
})
@@ -47,7 +48,7 @@ describe('useMapLogic', () => {
const { state, resetView } = useMapLogic()
state.trackingCharacterId.value = 42
const mockMap = { setView: vi.fn() }
resetView(mockMap as unknown as import('leaflet').Map)
resetView(mockMap as unknown as Map)
expect(state.trackingCharacterId.value).toBe(-1)
expect(mockMap.setView).toHaveBeenCalledWith([0, 0], 1, { animate: false })
})
@@ -59,7 +60,7 @@ describe('useMapLogic', () => {
getCenter: vi.fn(() => ({ lat: 0, lng: 0 })),
getZoom: vi.fn(() => 3),
}
updateDisplayCoords(mockMap as unknown as import('leaflet').Map)
updateDisplayCoords(mockMap as unknown as Map)
expect(state.displayCoords.value).toEqual({ x: 5, y: 3, z: 3 })
})
@@ -72,7 +73,7 @@ describe('useMapLogic', () => {
it('toLatLng calls map.unproject', () => {
const { toLatLng } = useMapLogic()
const mockMap = { unproject: vi.fn(() => ({ lat: 1, lng: 2 })) }
const result = toLatLng(mockMap as unknown as import('leaflet').Map, 100, 200)
const result = toLatLng(mockMap as unknown as Map, 100, 200)
expect(mockMap.unproject).toHaveBeenCalledWith([100, 200], 6)
expect(result).toEqual({ lat: 1, lng: 2 })
})

View File

@@ -1,6 +1,8 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { ref } from 'vue'
import { useToast } from '../useToast'
const stateByKey: Record<string, ReturnType<typeof ref>> = {}
const useStateMock = vi.fn((key: string, init: () => unknown) => {
if (!stateByKey[key]) {
@@ -10,8 +12,6 @@ const useStateMock = vi.fn((key: string, init: () => unknown) => {
})
vi.stubGlobal('useState', useStateMock)
import { useToast } from '../useToast'
describe('useToast', () => {
beforeEach(() => {
stateByKey['hnh-map-toasts'] = ref([])

View File

@@ -29,7 +29,7 @@ function saveBookmarks(bookmarks: MapBookmark[]) {
if (import.meta.server) return
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(bookmarks.slice(0, MAX_BOOKMARKS)))
} catch (_) {}
} catch { /* ignore */ }
}
export function useMapBookmarks() {

View File

@@ -12,6 +12,7 @@ import {
import type { SmartTileLayer } from '~/lib/SmartTileLayer'
import type { Marker as ApiMarker, Character as ApiCharacter } from '~/types/api'
type LeafletModule = L
type SmartTileLayerInstance = InstanceType<typeof SmartTileLayer>
function escapeHtml(s: string): string {
@@ -25,7 +26,7 @@ function escapeHtml(s: string): string {
export interface MapLayersOptions {
/** Leaflet API (from dynamic import). Required for creating markers and characters without static leaflet import. */
L: typeof import('leaflet')
L: LeafletModule
map: L.Map
markerLayer: L.LayerGroup
layer: SmartTileLayerInstance
@@ -33,7 +34,7 @@ export interface MapLayersOptions {
getCurrentMapId: () => number
setCurrentMapId: (id: number) => void
setSelectedMapId: (id: number) => void
getAuths: () => string[]
getAuths?: () => string[]
getTrackingCharacterId: () => number
setTrackingCharacterId: (id: number) => void
onMarkerContextMenu: (clientX: number, clientY: number, id: number, name: string) => void
@@ -69,7 +70,7 @@ export function createMapLayers(options: MapLayersOptions): MapLayersManager {
getCurrentMapId,
setCurrentMapId,
setSelectedMapId,
getAuths,
getAuths: _getAuths,
getTrackingCharacterId,
setTrackingCharacterId,
onMarkerContextMenu,

View File

@@ -26,7 +26,7 @@ function saveRecent(list: RecentLocation[]) {
if (import.meta.server) return
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(list.slice(0, MAX_RECENT)))
} catch (_) {}
} catch { /* ignore */ }
}
export function useRecentLocations() {