diff --git a/docs/releases/v2.4.0.md b/docs/releases/v2.4.0.md index 73423ab6788e74e17478347e8796b778e961c6d9..4204e56f644b676abcb5295e08bf152af6241b73 100644 --- a/docs/releases/v2.4.0.md +++ b/docs/releases/v2.4.0.md @@ -5,6 +5,7 @@ - fixes UI issue, where chips wraps on smaller screen (#740) - regions with the same labelIndex without colour will no longer have the same colour (#750) - fixes gpuLimit not applying properly (#765) +- fixes minor issue when switching back to multi version parc (#831) - fixes chips wrapping and scrolling on smaller devices (#720) ## New features @@ -14,6 +15,7 @@ - Preliminary support for displaying of SWC (#907) - Preliminary support for freesurfer (#900) - Allow for finer controls over meshes display (#883, #470) +- Added `quick tour` feature (#899) ## Under the hood stuff diff --git a/package.json b/package.json index 9ebe7d01be6b7f372ca6d919b72d85a2baf50ed2..1297909f46e45ece59d07e3ba48eed20094126ce 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "webpack": "^4.41.2", "webpack-cli": "^3.3.2", "webpack-closure-compiler": "^2.1.6", - "webpack-dev-server": "^3.10.3", + "webpack-dev-server": "^3.11.2", "webpack-merge": "^4.1.2" }, "dependencies": { @@ -80,7 +80,7 @@ "@ngrx/effects": "^9.1.1", "@ngrx/store": "^9.1.1", "@types/node": "12.12.39", - "export-nehuba": "0.0.10", + "export-nehuba": "0.0.11", "hbp-connectivity-component": "^0.3.18", "zone.js": "^0.10.2" } diff --git a/src/services/state/viewerState.store.helper.spec.ts b/src/services/state/viewerState.store.helper.spec.ts index c31955d5498e5a2b37f1e78fb26c84a3337b0ec3..fa0845554d221ea0051658ba8d096145913be883 100644 --- a/src/services/state/viewerState.store.helper.spec.ts +++ b/src/services/state/viewerState.store.helper.spec.ts @@ -1,6 +1,252 @@ -import { isNewerThan } from "./viewerState.store.helper" +import { TestBed } from "@angular/core/testing" +import { Action } from "@ngrx/store" +import { provideMockActions } from "@ngrx/effects/testing" +import { MockStore, provideMockStore } from "@ngrx/store/testing" +import { Observable, of } from "rxjs" +import { isNewerThan, ViewerStateHelperEffect } from "./viewerState.store.helper" +import { viewerStateGetSelectedAtlas, viewerStateSelectedTemplateSelector } from "./viewerState/selectors" +import { viewerStateHelperSelectParcellationWithId, viewerStateRemoveAdditionalLayer } from "./viewerState/actions" +import { generalActionError } from "../stateStore.helper" +import { hot } from "jasmine-marbles" describe('> viewerState.store.helper.ts', () => { + const tmplId = 'test-tmpl-id' + const tmplId0 = 'test-tmpl-id-0' + describe('> ViewerStateHelperEffect', () => { + let effect: ViewerStateHelperEffect + let mockStore: MockStore + let actions$: Observable<Action> + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + ViewerStateHelperEffect, + provideMockStore(), + provideMockActions(() => actions$) + ] + }) + + mockStore = TestBed.inject(MockStore) + mockStore.overrideSelector(viewerStateSelectedTemplateSelector, { + ['@id']: tmplId + }) + + actions$ = of( + viewerStateRemoveAdditionalLayer({ + payload: { + ['@id']: 'bla' + } + }) + ) + }) + + describe('> if selected atlas has no matching tmpl space', () => { + beforeEach(() => { + mockStore.overrideSelector(viewerStateGetSelectedAtlas, { + templateSpaces: [{ + ['@id']: tmplId0 + }] + }) + }) + it('> should emit gernal error', () => { + effect = TestBed.inject(ViewerStateHelperEffect) + effect.onRemoveAdditionalLayer$.subscribe(val => { + expect(val.type === generalActionError.type) + }) + }) + }) + + describe('> if selected atlas has matching tmpl', () => { + + const parcId0 = 'test-parc-id-0' + const parcId1 = 'test-parc-id-1' + const tmpSp = { + ['@id']: tmplId, + availableIn: [{ + ['@id']: parcId0 + }], + } + beforeEach(() => { + mockStore.overrideSelector(viewerStateGetSelectedAtlas, { + templateSpaces: [ + tmpSp + ], + parcellations: [], + }) + }) + + describe('> if parc is empty array', () => { + it('> should emit with falsy as payload', () => { + effect = TestBed.inject(ViewerStateHelperEffect) + expect( + effect.onRemoveAdditionalLayer$ + ).toBeObservable( + hot('(a|)', { + a: viewerStateHelperSelectParcellationWithId({ + payload: undefined + }) + }) + ) + }) + }) + describe('> if no parc has eligible @id', () => { + beforeEach(() => { + mockStore.overrideSelector(viewerStateGetSelectedAtlas, { + templateSpaces: [ + tmpSp + ], + parcellations: [{ + ['@id']: parcId1 + }] + }) + }) + it('> should emit with falsy as payload', () => { + effect = TestBed.inject(ViewerStateHelperEffect) + expect( + effect.onRemoveAdditionalLayer$ + ).toBeObservable( + hot('(a|)', { + a: viewerStateHelperSelectParcellationWithId({ + payload: undefined + }) + }) + ) + }) + }) + + describe('> if some parc has eligible @id', () => { + describe('> if no @version is available', () => { + const parc1 = { + ['@id']: parcId0, + name: 'p0-0', + baseLayer: true + } + const parc2 = { + ['@id']: parcId0, + name: 'p0-1', + baseLayer: true + } + beforeEach(() => { + + mockStore.overrideSelector(viewerStateGetSelectedAtlas, { + templateSpaces: [ + tmpSp + ], + parcellations: [ + parc1, + parc2 + ] + }) + }) + it('> selects the first parc', () => { + + effect = TestBed.inject(ViewerStateHelperEffect) + expect( + effect.onRemoveAdditionalLayer$ + ).toBeObservable( + hot('(a|)', { + a: viewerStateHelperSelectParcellationWithId({ + payload: parc1 + }) + }) + ) + }) + }) + + describe('> if @version is available', () => { + + describe('> if there exist an entry without @next attribute', () => { + + const parc1 = { + ['@id']: parcId0, + name: 'p0-0', + baseLayer: true, + ['@version']: { + ['@next']: 'random-value' + } + } + const parc2 = { + ['@id']: parcId0, + name: 'p0-1', + baseLayer: true, + ['@version']: { + ['@next']: null + } + } + beforeEach(() => { + + mockStore.overrideSelector(viewerStateGetSelectedAtlas, { + templateSpaces: [ + tmpSp + ], + parcellations: [ + parc1, + parc2 + ] + }) + }) + it('> selects the first one without @next attribute', () => { + + effect = TestBed.inject(ViewerStateHelperEffect) + expect( + effect.onRemoveAdditionalLayer$ + ).toBeObservable( + hot('(a|)', { + a: viewerStateHelperSelectParcellationWithId({ + payload: parc2 + }) + }) + ) + }) + }) + describe('> if there exist no entry without @next attribute', () => { + + const parc1 = { + ['@id']: parcId0, + name: 'p0-0', + baseLayer: true, + ['@version']: { + ['@next']: 'random-value' + } + } + const parc2 = { + ['@id']: parcId0, + name: 'p0-1', + baseLayer: true, + ['@version']: { + ['@next']: 'another-random-value' + } + } + beforeEach(() => { + + mockStore.overrideSelector(viewerStateGetSelectedAtlas, { + templateSpaces: [ + tmpSp + ], + parcellations: [ + parc1, + parc2 + ] + }) + }) + it('> selects the first one without @next attribute', () => { + + effect = TestBed.inject(ViewerStateHelperEffect) + expect( + effect.onRemoveAdditionalLayer$ + ).toBeObservable( + hot('(a|)', { + a: viewerStateHelperSelectParcellationWithId({ + payload: parc1 + }) + }) + ) + }) + }) + }) + }) + }) + }) + describe('> isNewerThan', () => { describe('> ill formed versions', () => { it('> in circular references, throws', () => { diff --git a/src/services/state/viewerState.store.helper.ts b/src/services/state/viewerState.store.helper.ts index 0e3b6dc333de2d8dc85b8fa1fb7d57c4dd47feb5..c636776f348b7233366e222de22d6faa6122e74b 100644 --- a/src/services/state/viewerState.store.helper.ts +++ b/src/services/state/viewerState.store.helper.ts @@ -112,7 +112,7 @@ export const viewerStateMetaReducers = [ export class ViewerStateHelperEffect{ @Effect() - selectParcellationWithId$: Observable<any> = this.actions$.pipe( + onRemoveAdditionalLayer$: Observable<any> = this.actions$.pipe( ofType(viewerStateRemoveAdditionalLayer.type), withLatestFrom( this.store$.pipe( @@ -133,7 +133,8 @@ export class ViewerStateHelperEffect{ const eligibleParcIdSet = new Set( tmpl.availableIn.map(p => p['@id']) ) - const baseLayer = selectedAtlas['parcellations'].find(fullP => fullP['baseLayer'] && eligibleParcIdSet.has(fullP['@id'])) + const baseLayers = selectedAtlas['parcellations'].filter(fullP => fullP['baseLayer'] && eligibleParcIdSet.has(fullP['@id'])) + const baseLayer = baseLayers.find(layer => !!layer['@version'] && !layer['@version']['@next']) || baseLayers[0] return viewerStateHelperSelectParcellationWithId({ payload: baseLayer }) }) )