Fix a batch of reported bugs (#1145)

* fix(maps): fall back to OSM/Wikipedia for place photos and normalize non-standard language codes (#1137)

* fix(auth): refuse password reset for OIDC/SSO-linked accounts (#1129)

* fix(docker): ship server/assets (airports + atlas geo) in the runtime image (#1133, #1119)

* fix(unraid): point the template at a PNG icon Unraid can render (#1073)

* fix(offline): serve cached file blobs when offline or on network failure (#1046, #1069)

* fix(map): centre the selected pin in the visible map area above the bottom panel (#1125)

* fix(pdf): render persisted place-photo proxy URLs as images (#1130)

* fix(planner): show the selected place category in the edit form (#1134)

* fix(dashboard): collapse list-view trip cards to a compact row on mobile (#1132)
This commit is contained in:
Maurice
2026-06-11 13:31:43 +02:00
committed by GitHub
parent 3c040fab11
commit e65acb3de7
17 changed files with 385 additions and 105 deletions
@@ -1,5 +1,9 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { downloadFile, openFile } from '../../../src/utils/fileDownload'
import { getCachedBlob } from '../../../src/db/offlineDb'
// Mock the offline DB so these tests never touch Dexie/IndexedDB.
vi.mock('../../../src/db/offlineDb', () => ({ getCachedBlob: vi.fn() }))
function makeFetchMock(status: number, blob: Blob = new Blob(['data'], { type: 'application/pdf' })) {
return vi.fn().mockResolvedValue({
@@ -170,3 +174,52 @@ describe('openFile', () => {
}
})
})
describe('offline fallback (#1046)', () => {
function setOnline(value: boolean) {
Object.defineProperty(navigator, 'onLine', { value, configurable: true })
}
beforeEach(() => vi.mocked(getCachedBlob).mockReset())
afterEach(() => setOnline(true))
it('serves the cached blob without a network call when offline', async () => {
setOnline(false)
const blob = new Blob(['x'], { type: 'application/pdf' })
vi.mocked(getCachedBlob).mockResolvedValue(blob)
const fetchSpy = vi.fn()
vi.stubGlobal('fetch', fetchSpy)
vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => {})
await downloadFile('/uploads/files/cached.pdf')
expect(fetchSpy).not.toHaveBeenCalled()
expect(getCachedBlob).toHaveBeenCalledWith('/uploads/files/cached.pdf')
expect(URL.createObjectURL).toHaveBeenCalledWith(blob)
})
it('falls back to the cache when a live fetch rejects (network error) while online', async () => {
setOnline(true)
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('network down')))
const blob = new Blob(['x'], { type: 'application/pdf' })
vi.mocked(getCachedBlob).mockResolvedValue(blob)
vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => {})
await downloadFile('/uploads/files/cached.pdf')
expect(getCachedBlob).toHaveBeenCalledWith('/uploads/files/cached.pdf')
expect(URL.createObjectURL).toHaveBeenCalledWith(blob)
})
it('throws when offline and the file was never cached', async () => {
setOnline(false)
vi.mocked(getCachedBlob).mockResolvedValue(null)
await expect(downloadFile('/uploads/files/missing.pdf')).rejects.toThrow(/offline/i)
})
it('does not consult the cache on an HTTP error — a 401 still surfaces', async () => {
setOnline(true)
vi.stubGlobal('fetch', makeFetchMock(401))
await expect(downloadFile('/uploads/files/secret.pdf')).rejects.toThrow('Unauthorized')
expect(getCachedBlob).not.toHaveBeenCalled()
})
})