From 653ac1c134f814cb57da85ed6b1ecdaa9eec3751 Mon Sep 17 00:00:00 2001
From: Xiao Gui <xgui3783@gmail.com>
Date: Wed, 1 Jun 2022 10:25:35 +0200
Subject: [PATCH] bugfix: navigateTo region

---
 src/atlasComponents/sapi/constants.ts    |   3 +
 src/state/atlasSelection/effects.spec.ts | 225 ++++++++++++++++++++---
 src/state/atlasSelection/effects.ts      |  58 +++++-
 3 files changed, 255 insertions(+), 31 deletions(-)

diff --git a/src/atlasComponents/sapi/constants.ts b/src/atlasComponents/sapi/constants.ts
index c9ba1fc1d..e9822b4a3 100644
--- a/src/atlasComponents/sapi/constants.ts
+++ b/src/atlasComponents/sapi/constants.ts
@@ -1,4 +1,7 @@
 export const IDS = {
+  ATLAES: {
+    HUMAN: "juelich/iav/atlas/v1.0.0/1"
+  },
   TEMPLATES: {
     BIG_BRAIN: "minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588",
     MNI152: "minds/core/referencespace/v1.0.0/dafcffc5-4826-4bf1-8ff6-46b8a31ff8e2",
diff --git a/src/state/atlasSelection/effects.spec.ts b/src/state/atlasSelection/effects.spec.ts
index 511cf1b53..61110e26b 100644
--- a/src/state/atlasSelection/effects.spec.ts
+++ b/src/state/atlasSelection/effects.spec.ts
@@ -1,6 +1,71 @@
+import { HttpClientTestingModule, HttpTestingController } from "@angular/common/http/testing"
+import { TestBed } from "@angular/core/testing"
+import { provideMockActions } from "@ngrx/effects/testing"
+import { Action } from "@ngrx/store"
+import { MockStore, provideMockStore } from "@ngrx/store/testing"
+import { hot } from "jasmine-marbles"
+import { Observable, of, throwError } from "rxjs"
+import { SAPI, SAPIModule, SapiRegionModel, SAPIParcellation, SapiAtlasModel, SapiSpaceModel, SapiParcellationModel } from "src/atlasComponents/sapi"
+import { IDS } from "src/atlasComponents/sapi/constants"
+import { actions, selectors } from "."
+import { Effect } from "./effects"
+import * as mainActions from "../actions"
+import { take } from "rxjs/operators"
+
 describe("> effects.ts", () => {
   describe("> Effect", () => {
 
+    let actions$ = new Observable<Action>()
+    let hoc1left: SapiRegionModel
+    let hoc1leftCentroid: SapiRegionModel
+    let hoc1leftCentroidWrongSpc: SapiRegionModel
+
+    beforeEach(async () => {
+      TestBed.configureTestingModule({
+        imports: [
+          // HttpClientTestingModule,
+          SAPIModule
+        ],
+        providers: [
+          Effect,
+          provideMockStore(),
+          provideMockActions(() => actions$)
+        ]
+      })
+
+      /**
+       * only need to populate hoc1 left once
+       */
+      if (!hoc1left) {
+
+        const sapisvc = TestBed.inject(SAPI)
+        const regions = await sapisvc.getParcRegions(IDS.ATLAES.HUMAN, IDS.PARCELLATION.JBA29, IDS.TEMPLATES.MNI152).toPromise()
+  
+        hoc1left = regions.find(r => /hoc1/i.test(r.name) && /left/i.test(r.name))
+        if (!hoc1left) throw new Error(`cannot find hoc1 left`)
+        hoc1leftCentroid = JSON.parse(JSON.stringify(hoc1left)) 
+        hoc1leftCentroid.hasAnnotation.bestViewPoint = {
+          coordinateSpace: {
+            '@id': IDS.TEMPLATES.BIG_BRAIN
+          },
+          coordinates: [{
+            value: 1
+          }, {
+            value: 2
+          }, {
+            value: 3
+          }]
+        }
+        hoc1leftCentroidWrongSpc = JSON.parse(JSON.stringify(hoc1leftCentroid))
+        hoc1leftCentroidWrongSpc.hasAnnotation.bestViewPoint.coordinateSpace['@id'] = IDS.TEMPLATES.COLIN27
+      }
+    })
+
+    it('> can be init', () => {
+      const effects = TestBed.inject(Effect)
+      expect(effects).toBeTruthy()
+    })
+
     describe('> selectTemplate$', () => {
 
       describe('> when transiting from template A to template B', () => {
@@ -77,61 +142,167 @@ describe("> effects.ts", () => {
     })
 
     describe('> onNavigateToRegion', () => {
+      beforeEach(async () => {
+        actions$ = hot('a', {
+          a: actions.navigateToRegion({
+            region: hoc1left
+          })
+        })
+        const mockStore = TestBed.inject(MockStore)
+        mockStore.overrideSelector(selectors.selectedAtlas, {
+          "@id": IDS.ATLAES.HUMAN
+        } as SapiAtlasModel)
+        mockStore.overrideSelector(selectors.selectedTemplate, {
+          "@id": IDS.TEMPLATES.MNI152
+        } as SapiSpaceModel)
+        mockStore.overrideSelector(selectors.selectedParcellation, {
+          "@id": IDS.PARCELLATION.JBA29
+        } as SapiParcellationModel)
+      })
 
       describe('> if atlas, template, parc is not set', () => {
+        const atp = [{
+          name: 'atlas',
+          atpSelector: selectors.selectedAtlas
+        },{
+          name: 'template',
+          atpSelector: selectors.selectedTemplate
+        },{
+          name: 'parcellation',
+          atpSelector: selectors.selectedParcellation
+        }]
+        for (const { name, atpSelector } of atp) {
+          describe(`> if ${name} is unset`, () => {
 
-        describe('> if atlas is unset', () => {
-          it('> returns general error', () => {
-          })
-        })
-        describe('> if template is unset', () => {
-          it('> returns general error', () => {
-          })
-        })
-        describe('> if parc is unset', () => {
-          it('> returns general error', () => {
+            beforeEach(() => {
+              const mockStore = TestBed.inject(MockStore)
+              mockStore.overrideSelector(atpSelector, null)
+            })
+
+            it('> returns general error', () => {
+              const effect = TestBed.inject(Effect)
+              expect(effect.onNavigateToRegion).toBeObservable(
+                hot('a', {
+                  a: mainActions.generalActionError({
+                    message: `atlas, template, parcellation or region not set`
+                  })
+                })
+              )
+            })
           })
-        })
+        }
       })
+
       describe('> if atlas, template, parc is set, but region unset', () => {
+
+        beforeEach(() => {
+          actions$ = hot('a', {
+            a: actions.navigateToRegion({
+              region: null
+            })
+          })
+        })
         it('> returns general error', () => {
+          const effect = TestBed.inject(Effect)
+          expect(effect.onNavigateToRegion).toBeObservable(
+            hot('a', {
+              a: mainActions.generalActionError({
+                message: `atlas, template, parcellation or region not set`
+              })
+            })
+          )
         })
       })
 
       describe('> if inputs are fine', () => {
-        it('> getRegionDetailSpy is called', () => {
+        let getRegionSpy: jasmine.Spy
+        let regionGetDetailSpy: jasmine.Spy = jasmine.createSpy()
+        beforeEach(() => {
+          const sapi = TestBed.inject(SAPI)
+          getRegionSpy = spyOn(sapi, 'getRegion')
+          getRegionSpy.and.returnValue({
+            getDetail: regionGetDetailSpy
+          })
+          regionGetDetailSpy.and.returnValue(
+            of(hoc1leftCentroid)
+          )
+        })
+        afterEach(() => {
+          if (getRegionSpy) getRegionSpy.calls.reset()
+          if (regionGetDetailSpy) regionGetDetailSpy.calls.reset()
+        })
+        it('> getRegionDetailSpy is called, and calls navigateTo', () => {
+          const eff = TestBed.inject(Effect)
+          expect(eff.onNavigateToRegion).toBeObservable(
+            hot(`a`, {
+              a: actions.navigateTo({
+                navigation: {
+                  position: [1e6, 2e6, 3e6]
+                },
+                animation: true
+              })
+            })
+          )
+          expect(getRegionSpy).toHaveBeenCalledTimes(1)
+          expect(getRegionSpy).toHaveBeenCalledWith(IDS.ATLAES.HUMAN, IDS.PARCELLATION.JBA29, hoc1left["@id"])
         })
 
         describe('> mal formed return', () => {
           describe('> returns null', () => {
+            beforeEach(() => {
+
+              regionGetDetailSpy.and.returnValue(
+                of(null)
+              )
+            })
             it('> generalactionerror', () => {
+
+              const eff = TestBed.inject(Effect)
+              expect(eff.onNavigateToRegion).toBeObservable(
+                hot(`a`, {
+                  a: mainActions.generalActionError({
+                    message: `getting region detail error! cannot get coordinates`
+                  })
+                })
+              )
             })
           })
           describe('> general throw', () => {
-
-            it('> generalactionerror', () => {
+            beforeEach(() => {
+              regionGetDetailSpy.and.returnValue(
+                throwError(`oh noes`)
+              )
             })
-
-          })
-          describe('> does not contain props attr', () => {
             it('> generalactionerror', () => {
-            })
-          })
 
-          describe('> does not contain props.length === 0', () => {
-            it('> generalactionerror', () => {
+              const eff = TestBed.inject(Effect)
+              expect(eff.onNavigateToRegion).toBeObservable(
+                hot(`a`, {
+                  a: mainActions.generalActionError({
+                    message: `Error getting region centroid`
+                  })
+                })
+              )
             })
-          })
-        })
 
-        describe('> wellformed response', () => {
-          beforeEach(() => {
+          })
+          describe('> does not contain props attr', () => {
 
             beforeEach(() => {
+              regionGetDetailSpy.and.returnValue(
+                of(hoc1left)
+              )
             })
+            it('> generalactionerror', () => {
 
-            it('> emits navigateTo', () => {
-
+              const eff = TestBed.inject(Effect)
+              expect(eff.onNavigateToRegion).toBeObservable(
+                hot(`a`, {
+                  a: mainActions.generalActionError({
+                    message: `getting region detail error! cannot get coordinates`
+                  })
+                })
+              )
             })
           })
         })
diff --git a/src/state/atlasSelection/effects.ts b/src/state/atlasSelection/effects.ts
index e134e7d4b..041775216 100644
--- a/src/state/atlasSelection/effects.ts
+++ b/src/state/atlasSelection/effects.ts
@@ -1,7 +1,7 @@
 import { Injectable } from "@angular/core";
 import { Actions, createEffect, ofType } from "@ngrx/effects";
 import { concat, forkJoin, merge, Observable, of } from "rxjs";
-import { filter, map, mapTo, switchMap, switchMapTo, take, tap, withLatestFrom } from "rxjs/operators";
+import { catchError, filter, map, mapTo, switchMap, switchMapTo, take, withLatestFrom } from "rxjs/operators";
 import { SAPI, SapiAtlasModel, SapiParcellationModel, SAPIRegion, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi";
 import * as mainActions from "../actions"
 import { select, Store } from "@ngrx/store";
@@ -337,9 +337,59 @@ export class Effect {
 
   onNavigateToRegion = createEffect(() => this.action.pipe(
     ofType(actions.navigateToRegion),
-    mapTo(mainActions.generalActionError({
-      message: `NYI onNavigateToRegion`
-    }))
+    withLatestFrom(
+      this.store.pipe(
+        select(selectors.selectedTemplate)
+      ),
+      this.store.pipe(
+        select(selectors.selectedAtlas)
+      ),
+      this.store.pipe(
+        select(selectors.selectedParcellation)
+      )
+    ),
+    switchMap(([{ region }, selectedTemplate, selectedAtlas, selectedParcellation]) => {
+      if (!selectedAtlas || !selectedTemplate || !selectedParcellation || !region)  {
+        return of(
+          mainActions.generalActionError({
+            message: `atlas, template, parcellation or region not set`
+          })
+        )
+      }
+
+      if (region.hasAnnotation?.bestViewPoint && region.hasAnnotation.bestViewPoint.coordinateSpace['@id'] === selectedTemplate["@id"]) {
+        return of(
+          actions.navigateTo({
+            animation: true,
+            navigation: {
+              position: region.hasAnnotation.bestViewPoint.coordinates.map(v => v.value * 1e6)
+            }
+          })
+        )
+      }
+      
+      console.log('bla2?')
+      return this.sapiSvc.getRegion(selectedAtlas['@id'], selectedParcellation['@id'], region["@id"]).getDetail(selectedTemplate["@id"]).pipe(
+        map(detailedRegion => {
+          if (!detailedRegion?.hasAnnotation?.bestViewPoint?.coordinates) {
+            return mainActions.generalActionError({
+              message: `getting region detail error! cannot get coordinates`
+            })
+          }
+          return actions.navigateTo({
+            animation: true,
+            navigation: {
+              position: detailedRegion.hasAnnotation.bestViewPoint.coordinates.map(v => v.value * 1e6)
+            }
+          })
+        }),
+        catchError((_err, _obs) => of(
+          mainActions.generalActionError({
+            message: `Error getting region centroid`
+          })
+        ))
+      )
+    })
   ))
 
   onSelAtlasTmplParcClearRegion = createEffect(() => merge(
-- 
GitLab