feat: outbox-pattern sync + progressive dashboard loading
- Sync herschreven: push-vóór-pull, outbox met syncStatus (pending/pending-delete/synced), local-first merge - Dashboard: progressieve loading — laatste 2 metingen direct, grafieken volgen daarna - measurementStore versimpeld naar refreshCache/getCached/getVersion (20 regels) - db.ts: getPendingRecords(), updateSyncStatus(), migratie local→synced, syncStatus DEFAULT 'pending' - WeightChart: filtert 0-waarden uit vetmassa/spiermassa, verbergt chart bij <2 datapunten - HistoryScreen/MeasurementDetailScreen: delete flow naar DB-only (setDeletedAt + updateSyncStatus) - eas.json: appVersionSource toegevoegd tegen waarschuwing - README bijgewerkt met outbox-pattern documentatie - 16/16 tests, TypeScript clean
This commit is contained in:
parent
a32b9e0a88
commit
a77b31f95c
13 changed files with 257 additions and 213 deletions
|
|
@ -7,7 +7,7 @@ const make = (overrides: Partial<Measurement> = {}): Measurement => ({
|
|||
id: 'id-1', weight: 75, bmi: 22, bodyFatPercentage: 15, fatMass: 10,
|
||||
musclePercentage: 40, muscleMass: 30, visceralFat: 8, bmr: 1600,
|
||||
measuredAt: '2024-06-01T08:00:00.000Z', createdAt: '2024-06-01T08:00:00.000Z',
|
||||
updatedAt: '2024-06-01T08:00:00.000Z', ...overrides,
|
||||
updatedAt: '2024-06-01T08:00:00.000Z', syncStatus: 'synced', ...overrides,
|
||||
})
|
||||
|
||||
describe('mergeMeasurements', () => {
|
||||
|
|
@ -40,37 +40,47 @@ describe('mergeMeasurements', () => {
|
|||
expect(r[0].weight).toBe(77)
|
||||
})
|
||||
|
||||
it('remote deleted wins over older local', () => {
|
||||
it('remote deleted wins over synced local', () => {
|
||||
const local = make({ updatedAt: '2024-06-01T08:00:00.000Z' })
|
||||
const remote = make({ updatedAt: '2024-06-03T08:00:00.000Z', deletedAt: '2024-06-03T08:00:00.000Z' })
|
||||
const remote: any = { ...make({ updatedAt: '2024-06-03T08:00:00.000Z' }), deleted: true }
|
||||
const r = mergeMeasurements([local], [remote])
|
||||
expect(r[0].deletedAt).toBe('2024-06-03T08:00:00.000Z')
|
||||
})
|
||||
|
||||
it('local wins over older remote deleted', () => {
|
||||
it('local synced wins over older remote deleted', () => {
|
||||
const local = make({ updatedAt: '2024-06-03T08:00:00.000Z' })
|
||||
const remote = make({ updatedAt: '2024-06-01T08:00:00.000Z', deletedAt: '2024-06-01T08:00:00.000Z' })
|
||||
const remote: any = { ...make({ updatedAt: '2024-06-01T08:00:00.000Z' }), deleted: true }
|
||||
const r = mergeMeasurements([local], [remote])
|
||||
expect(r[0].deletedAt).toBeUndefined()
|
||||
})
|
||||
|
||||
it('server deleted flag mapped to deletedAt when remote wins', () => {
|
||||
const local = make({ updatedAt: '2024-06-01T08:00:00.000Z' })
|
||||
it('local pending-delete wins over remote deleted', () => {
|
||||
const local = make({ updatedAt: '2024-06-01T08:00:00.000Z', syncStatus: 'pending-delete', deletedAt: '2024-06-05T08:00:00.000Z' })
|
||||
const remote: any = { ...make({ updatedAt: '2024-06-03T08:00:00.000Z' }), deleted: true }
|
||||
const r = mergeMeasurements([local], [remote])
|
||||
expect(r[0].deletedAt).toBe('2024-06-03T08:00:00.000Z')
|
||||
expect(r[0].syncStatus).toBe('pending-delete')
|
||||
expect(r[0].deletedAt).toBe('2024-06-05T08:00:00.000Z')
|
||||
})
|
||||
|
||||
it('server deleted flag ignored when local wins', () => {
|
||||
const local = make({ updatedAt: '2024-06-05T08:00:00.000Z' })
|
||||
const remote: any = { ...make({ updatedAt: '2024-06-03T08:00:00.000Z' }), deleted: true }
|
||||
it('local pending wins over remote newer', () => {
|
||||
const local = make({ weight: 75, updatedAt: '2024-06-01T08:00:00.000Z', syncStatus: 'pending' })
|
||||
const remote: any = { ...make({ weight: 76, updatedAt: '2024-06-03T08:00:00.000Z' }) }
|
||||
const r = mergeMeasurements([local], [remote])
|
||||
expect(r[0].deletedAt).toBeUndefined()
|
||||
expect(r[0].weight).toBe(75)
|
||||
expect(r[0].syncStatus).toBe('pending')
|
||||
})
|
||||
|
||||
it('server deleted flag works with no local record', () => {
|
||||
it('remote deleted with no local record is skipped', () => {
|
||||
const remote: any = { ...make({ id: 'r1', updatedAt: '2024-06-03T08:00:00.000Z' }), deleted: true }
|
||||
const r = mergeMeasurements([], [remote])
|
||||
expect(r[0].deletedAt).toBe('2024-06-03T08:00:00.000Z')
|
||||
expect(r).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('local without syncStatus treated as synced', () => {
|
||||
const local = make({ weight: 75, updatedAt: '2024-06-01T08:00:00.000Z' })
|
||||
delete local.syncStatus
|
||||
const remote = make({ weight: 76, updatedAt: '2024-06-03T08:00:00.000Z' })
|
||||
const r = mergeMeasurements([local], [remote])
|
||||
expect(r[0].weight).toBe(76)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue