fix: sync deletion propagation, add base64 util, timeout, and merge improvements

Critical:
- Add deleted column: deleteMeasurement now soft-deletes, merge filters
  out deleted items based on updatedAt (was: deletions were resurrected)
- Replace btoa with UTF-8-safe encodeBase64 utility for Hermes compat

Important:
- Preserve createdAt when overwriting duplicate-date measurements
- Add 15s AbortController timeout to all WebDAV fetch calls
- Revert broken date prefilling regression (MeasurementForm)
- getAllMeasurements/latest/byDate now filter deleted records
- Replace Dimensions with useWindowDimensions for rotation awareness
- Replace silent catch with console.warn in HistoryScreen

Tests: 8 new tests for deletion merge cases and soft-delete queries
This commit is contained in:
Eddy de Vink 2026-08-05 20:55:14 +02:00
commit 0513db46ac
10 changed files with 207 additions and 58 deletions

View file

@ -19,6 +19,7 @@ const base: Measurement = {
createdAt: '2024-06-01T08:00:00.000Z',
updatedAt: '2024-06-01T08:00:00.000Z',
syncStatus: 'local',
deleted: 0,
}
describe('mergeMeasurements', () => {
@ -54,4 +55,48 @@ describe('mergeMeasurements', () => {
expect(result).toHaveLength(1)
expect(result[0].weight).toBe(77)
})
it('filters out locally deleted items even if present on remote', () => {
const local = { ...base, deleted: 1 as const, updatedAt: '2024-06-05T08:00:00.000Z' }
const remote = { ...base, deleted: 0 as const, updatedAt: '2024-06-03T08:00:00.000Z' }
const result = mergeMeasurements([local], [remote])
expect(result).toHaveLength(0)
})
it('filters out remotely deleted items when remote is newer', () => {
const local = { ...base, deleted: 0 as const, updatedAt: '2024-06-04T08:00:00.000Z' }
const remote = { ...base, deleted: 1 as const, updatedAt: '2024-06-05T08:00:00.000Z' }
const result = mergeMeasurements([local], [remote])
expect(result).toHaveLength(0)
})
it('keeps local active item when local is newer than remote deletion', () => {
const local = { ...base, deleted: 0 as const, weight: 78, updatedAt: '2024-06-06T08:00:00.000Z' }
const remote = { ...base, deleted: 1 as const, updatedAt: '2024-06-05T08:00:00.000Z' }
const result = mergeMeasurements([local], [remote])
expect(result).toHaveLength(1)
expect(result[0].weight).toBe(78)
})
it('keeps active item when local deleted is older than remote active', () => {
const local = { ...base, deleted: 1 as const, updatedAt: '2024-06-01T08:00:00.000Z' }
const remote = { ...base, deleted: 0 as const, updatedAt: '2024-06-05T08:00:00.000Z', syncStatus: 'synced' as const }
const result = mergeMeasurements([local], [remote])
expect(result).toHaveLength(1)
expect(result[0].weight).toBe(75)
})
it('merges multiple active and deleted items correctly', () => {
const localActive = { ...base, id: 'a', weight: 70, updatedAt: '2024-06-05T08:00:00.000Z' }
const localDeleted = { ...base, id: 'b', weight: 80, deleted: 1 as const, updatedAt: '2024-06-05T08:00:00.000Z' }
const remoteActive = { ...base, id: 'c', weight: 90, updatedAt: '2024-06-03T08:00:00.000Z', syncStatus: 'synced' as const }
const remoteDeleted = { ...base, id: 'd', weight: 100, deleted: 1 as const, updatedAt: '2024-06-05T08:00:00.000Z' }
const result = mergeMeasurements(
[localActive, localDeleted],
[remoteActive, remoteDeleted]
)
expect(result).toHaveLength(2)
expect(result.map(m => m.id).sort()).toEqual(['a', 'c'])
})
})