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