also looking at this
feat: native trail maps and complete native mobile screens
#1017
60 files
+2505
-823
Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.
| @@ -11,7 +11,7 @@ async function recover(userId=7, keychain=new Map([['auth_token','record-token'] | ||
| 11 | 11 | } |
| 12 | 12 | describe('native Record checkpoint recovery',()=>{ |
| 13 | 13 | beforeAll(compileNativeBundle,30000) |
| 14 | it('durably registers a system recording ID before starting native GPS',async()=>{ | |
| 14 | it('registers a system ID before GPS and retains it through pause, resume and save',async()=>{ | |
| 15 | 15 | const screen=await start('Record',{},['fetch','secureStorage','storage','storageCompareAndSet','storageSetIfAbsent','crypto','geolocation','locationRecordingOwnership','locationRecordingIntentIdentity','lifecycle','liveActivity'],new Map([['auth_token','record-token'],['wildloop-account',JSON.stringify({token:'record-token',user:{id:7}})]])) |
| 16 | 16 | const stored=await waitForRequest(screen,0,r=>r.payload.module==='Storage' && r.payload.method==='get') |
| 17 | 17 | answer(screen,stored,null) |
| @@ -29,6 +29,62 @@ describe('native Record checkpoint recovery',()=>{ | ||
| 29 | 29 | answer(screen,registration,true) |
| 30 | 30 | const nativeStart=await waitForRequest(screen,0,r=>r.payload.method==='startRecording') |
| 31 | 31 | expect(nativeStart.payload.args[0]).toMatchObject({recordingId:'782f0d4a-f626-41bc-8f17-2f67e9a099bf'}) |
| 32 | const id='782f0d4a-f626-41bc-8f17-2f67e9a099bf' | |
| 33 | const startedAt=Date.now()-30000 | |
| 34 | const locations=[{latitude:35.699,longitude:-82.38,timestamp:startedAt+1000,accuracy:5},{latitude:35.6991,longitude:-82.3801,timestamp:startedAt+2000,accuracy:5}] | |
| 35 | answer(screen,nativeStart,{id,active:true,paused:false,startedAt}) | |
| 36 | const started=await waitForRequest(screen,requests(screen).indexOf(nativeStart)+1,r=>r.payload.method==='compareAndSet') | |
| 37 | expect(started.payload.args[2]).toMatchObject({id,phase:'recording',startedAt}) | |
| 38 | answer(screen,started,true) | |
| 39 | await waitForText(screen,'Recording continues with your phone locked.') | |
| 40 | let before=requests(screen).length | |
| 41 | await screen.event('record-pause','onPress') | |
| 42 | const pause=await waitForRequest(screen,before,r=>r.payload.method==='pauseRecording') | |
| 43 | expect(pause.payload.args[0]).toEqual({expectedId:id}) | |
| 44 | answer(screen,pause,{id,active:true,paused:true,startedAt}) | |
| 45 | const read=await waitForRequest(screen,before,r=>r.payload.method==='readRecording') | |
| 46 | expect(read.payload.args[0]).toEqual({expectedId:id}) | |
| 47 | answer(screen,read,locations) | |
| 48 | const paused=await waitForRequest(screen,before,r=>r.payload.method==='compareAndSet') | |
| 49 | expect(paused.payload.args[2].pauses).toHaveLength(1) | |
| 50 | answer(screen,paused,true) | |
| 51 | await Bun.sleep(0) | |
| 52 | before=requests(screen).length | |
| 53 | await screen.event('record-pause','onPress') | |
| 54 | const resume=await waitForRequest(screen,before,r=>r.payload.method==='resumeRecording') | |
| 55 | expect(resume.payload.args[0]).toEqual({expectedId:id}) | |
| 56 | answer(screen,resume,{id,active:true,paused:false,startedAt}) | |
| 57 | const resumed=await waitForRequest(screen,before,r=>r.payload.method==='compareAndSet') | |
| 58 | expect(resumed.payload.args[2].pauses[0].end).toBeNumber() | |
| 59 | answer(screen,resumed,true) | |
| 60 | await Bun.sleep(0) | |
| 61 | before=requests(screen).length | |
| 62 | await screen.event('record-finish','onPress') | |
| 63 | const stop=await waitForRequest(screen,before,r=>r.payload.method==='stopRecording') | |
| 64 | expect(stop.payload.args[0]).toEqual({expectedId:id}) | |
| 65 | answer(screen,stop,{id,active:false,paused:false,startedAt,locations}) | |
| 66 | const stopped=await waitForRequest(screen,before,r=>r.payload.method==='compareAndSet') | |
| 67 | expect(stopped.payload.args[2]).toMatchObject({id,phase:'stopped'}) | |
| 68 | answer(screen,stopped,true) | |
| 69 | const pending=await waitForRequest(screen,requests(screen).indexOf(stopped)+1,r=>r.payload.method==='compareAndSet') | |
| 70 | expect(pending.payload.args[2].pending.upload_id).toBe(`run:${id}`) | |
| 71 | expect(requests(screen).some(r=>r.payload.method==='acknowledgeRecording')).toBe(false) | |
| 72 | answer(screen,pending,true) | |
| 73 | const me=await waitForRequest(screen,before,r=>r.payload.module==='Network') | |
| 74 | answer(screen,me,ok({user:{id:7}})) | |
| 75 | const post=await waitForRequest(screen,requests(screen).indexOf(me)+1,r=>r.payload.module==='Network') | |
| 76 | expect(JSON.parse(post.payload.args[0].body).upload_id).toBe(`run:${id}`) | |
| 77 | answer(screen,post,ok({activity:{id:99,captureEligible:false}},201)) | |
| 78 | const ack=await waitForRequest(screen,before,r=>r.payload.method==='acknowledgeRecording') | |
| 79 | expect(ack.payload.args[0]).toEqual({expectedId:id}) | |
| 80 | answer(screen,ack,true) | |
| 81 | const clear=await waitForRequest(screen,requests(screen).indexOf(pending)+1,r=>r.payload.method==='compareAndSet') | |
| 82 | expect(clear.payload.args[1]).toEqual(pending.payload.args[2]) | |
| 83 | expect(clear.payload.args[2]).toBeNull() | |
| 84 | answer(screen,clear,true) | |
| 85 | await Bun.sleep(0) | |
| 86 | expect(screen.sent.some(r=>r.type==='NAVIGATE_OPEN' && JSON.stringify(r.payload).includes('/activity/99'))).toBe(true) | |
| 87 | expect(requests(screen).some(r=>r.payload.method==='set' || r.payload.method==='remove')).toBe(false) | |
| 32 | 88 | }) |
| 33 | 89 | it('recovers a finished upload offline without starting another GPS recording',async()=>{ |
| 34 | 90 | const screen=await recover() |