diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..3d06bd6ba3cafc8f1692a8b3573927c5660d188f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,62 @@ +name: '[ci]' + +on: + push: + branches-ignore: + - dev + - staging + - master + +jobs: + lint: + if: always() + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js 14.x for lint + uses: actions/setup-node@v1 + with: + node-version: '14.x' + - run: npm i + - run: npm run lint + + frontend: + if: always() + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [10.x, 12.x, 14.x] + + env: + NODE_ENV: test + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm i + - run: npm run test + + backend: + if: always() + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [10.x, 12.x, 14.x] + + env: + NODE_ENV: test + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: cd deploy + - run: npm i + - run: npm run test diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a147c22ea7030e03630dd1c3129aa1561ed65763..0000000000000000000000000000000000000000 --- a/.travis.yml +++ /dev/null @@ -1,104 +0,0 @@ -language: node_js - -os: linux -dist: xenial -node_js: -- 12 - -git: - depth: 3 - -# skipping install and script at root -# as otherwise, travis will run npm install / npm test -install: skip -script: skip - -jobs: - include: - - stage: Unit tests, Lint & test build - if: | - type = push AND \ - branch NOT IN (master, staging, dev) - name: Unit test frontend / Lint - install: - - npm i - script: - - npm run lint - - npm test - - npm run build-aot - env: - - NODE_ENV=test - - - name: Unit test backend - if: | - type = push AND \ - branch NOT IN (master, staging, dev) - before_install: - - cd deploy - install: - - npm i - script: - - npm test - env: - - NODE_ENV=test - - PORT=12234 - - - # Temporarily disabling browserstack e2e tests. They seem to fail without any reason - - # - stage: browserstack e2e - # # only triggered via API, where env can be overwritten - # if: NOT env(LOCAL_TEST_E2E) = 1 - # name: e2e (with browserstack) - # install: - # - npm i - # script: - # - PROTRACTOR_SPECS=./src/navigating/*.e2e-spec.js BROWSERSTACK_TEST_NAME=e2e_navigating npm run e2e - # env: - # - ATLAS_URL=https://interactive-viewer-next.apps-dev.hbp.eu/ - # - BROWSERSTACK_USERNAME=xiao33 - # - secure: "YD2hDBnWzcMs9mTJCsKkJimd+mYKP8V1lTaCnxNvspJUxTuBWFmr8cvryIs9G9DhwgxkC3YL7hugsGkwMg6OIq27vLlo8mgoKS7/qrkWAJApGvDW4jc4CHpI2iE/ryrwG1bI3u9TuG0kSw+2sN/786LBgArlf5NbmwB9zmW4zyzjXXzSME34cwYdfEP96L2cob/uGiIj9YdaA1k3zfBhQQlp328i/xzYbIAcwfIia1AKYh/wgCzj+yfWDQ0Rtg9VcI2JiNfcbzMCgvDEBzshgeXuubFd9GPqPsc8zJhYqAi/15ge+WiB8R50MnZsYHO39JJihQzKz6WxIZQDeOQ2xd600NhFFLg6WPdE3jxAyENouTAd+0zJgXEeUU71YBDBl6RViagf8k7eOe9oMPW5ZlevdD3vcI8BC/qUL6Evye8QDDNi0s8gbIvcnJl5QMRBpeYcm/QaRUow1YeJobpccj/3tb7qTbc7T4Rha/NRBNhbhp/WzDSO/BUSEtpgJ3YwSEPTiEeSocTRT8ylnhEtBB70h4vQSClV73lW4vn7WjdZUTRACdxFNJ1MteQJ+3bgzyWMhDtdQo6BSz2UxF0mQFayAu2p9j0+MbB7x2zW9tksSw+6B6EjzPhQw6eOs2K0+syxWg09MTW1Fy6n0Zgchn0RWSnEPqPvss6kB2pkAR4=" - -env: - global: - - CHROMIUM_VERSION=80.0.3987.106 - - PPTR_VERSION=2.1.0 - - PROTRACTOR_SPECS=./src/navigating/*.e2e-spec.js - - -# addons: -# browserstack: -# username: "xiao33" -# access_key: -# secure: "j0NdVLyNwm1gDclEeE/xYrXAYiYAlx3HQxNRHMFhJyFml5R22spEMTwrTRl/vzyhv1FwfJAKfh4qbOn99cZ5Dzm7fWc8+Kq1zpp/1PRTzbFaLluJkV1wCwoODZkzmSVPj43M6070FhCJvOfe5VRUV440CgZH8IWRm7xaxRnN/MVyFMErMV/GIczEBB7D7E4mMhe6c9pBxjmojDDP4rGvKLGOYU7oVQKgZtbHtP/BxjQ7uzMysdTHZGZ/2c/XW/2bKVSADi4vFzge5PMVF3nSH+vzA09ro180Q5aoaek4XQPoIza0s0cqtqkbvkbJ+lWRE+Q7wJDhQLM4WNx5GX3fegJiqJRT7272EgGAUy6C+e2F+D5nPucf3w6Uov9vBn5zZjbfXdNah3GZEXOTRNAVzstySiwiZe7/f4bk0vWIiEhHC+iutjn8skMxFnuw2eM3SJ5ayjxskHOdRux+1fuDya32ctx8y9a3XLhuFcuGTaeMSAn5Dw5qOlI5Qoc+xRSARoRWKmlEuxTUudD0e+b8xqfZgmOP7D3GZ6QX2W4yFrOLGqUzEySHr8hxxzhIlfwSvVdJ15AtN2AtPFQYQXb7M+XX1L7fr39Z/5ctr7DDgljSE3F2U5ofyWV2hh54aGMBQe76cZfVzF4bi98X3r6u0b3Knyti2pvx5jIoxP46nOA=" - - -# to run e2e tests, send API request with the following payload -# { -# "request": { -# "message": "API request: e2e on deployed staging build", -# "branch":"chore_debugTravisE2e", -# "merge_mode": "deep_merge", -# "config": { -# "env": { -# "global": [ -# "PROTRACTOR_SPECS=./src/navigating/*.e2e-spec.js", -# "CHROMIUM_VERSION=80.0.3987.106", -# "PPTR_VERSION=2.1.0" -# ], -# "jobs": [ -# "ATLAS_URL=https://interactive-viewer-staging.apps.hbp.eu/", -# "ATLAS_URL=https://atlases.ebrains.eu/viewer-staging/" -# ] -# }, -# "install": [ -# "npm i", -# "npm run wd -- update --versions.chrome=${CHROMIUM_VERSION}", -# "npm i --no-save puppeteer@${PPTR_VERSION}" -# ], -# "script": [ -# "echo Running e2e on $ATLAS_URL with protractor specs $PROTRACTOR_SPECS", -# "npm run e2e" -# ] -# } -# }} diff --git a/src/glue.spec.ts b/src/glue.spec.ts index 02e5c00049375fd3c0100bfd0f2ca1f86d5ee944..92466c534536e6df36120aa565b7355639cbf7da 100644 --- a/src/glue.spec.ts +++ b/src/glue.spec.ts @@ -1,5 +1,5 @@ import { TestBed, tick, fakeAsync, discardPeriodicTasks } from "@angular/core/testing" -import { DatasetPreviewGlue, glueSelectorGetUiStatePreviewingFiles, glueActionRemoveDatasetPreview, datasetPreviewMetaReducer, glueActionAddDatasetPreview, GlueEffects } from "./glue" +import { DatasetPreviewGlue, glueSelectorGetUiStatePreviewingFiles, glueActionRemoveDatasetPreview, datasetPreviewMetaReducer, glueActionAddDatasetPreview, GlueEffects, ClickInterceptorService } from "./glue" import { ACTION_TO_WIDGET_TOKEN, EnumActionToWidget } from "./widget" import { provideMockStore, MockStore } from "@ngrx/store/testing" import { getRandomHex } from 'common/util' @@ -1211,65 +1211,101 @@ describe('> glue.ts', () => { /** * TODO finish writing the test for ClickInterceptorService */ + let interceptorService: ClickInterceptorService - it('can obtain override fn', () => { - + beforeEach(() => { + interceptorService = new ClickInterceptorService() }) - describe('> if getUserToSelectRegion.length === 0', () => { - - it('by default, next fn will be called', () => { - + describe('> #addInterceptor', () => { + it('> adds interceptor fn', () => { + const fn = (ev: any, next: Function) => {} + interceptorService.addInterceptor(fn) + expect(interceptorService['clickInterceptorStack'].indexOf(fn)).toBeGreaterThanOrEqual(0) }) - - it('if apiService.getUserToSelectRegion.length === 0, and mouseoversegment.length > 0 calls next', () => { + it('> when config not supplied, or last not present, will add fn to the first of the queue', () => { - }) - }) - describe('> if getUserToSelectRegion.length > 0', () => { - it('if both apiService.getUserToSelectRegion.length > 0 and mouseoverSegment.length >0, then next will not be called, but rs will be', () => { + const dummy = (ev: any, next: Function) => {} + interceptorService.addInterceptor(dummy) + const fn = (ev: any, next: Function) => {} + interceptorService.addInterceptor(fn) + expect(interceptorService['clickInterceptorStack'].indexOf(fn)).toEqual(0) + + const fn2 = (ev: any, next: Function) => {} + interceptorService.addInterceptor(fn2, {}) + expect(interceptorService['clickInterceptorStack'].indexOf(fn)).toEqual(1) + expect(interceptorService['clickInterceptorStack'].indexOf(fn2)).toEqual(0) }) - it('if multiple getUserToSelectRegion handler exists, it resolves in a LIFO manner', () => { + it('> when last is supplied as a config param, will add the fn at the end', () => { - }) + const dummy = (ev: any, next: Function) => {} + interceptorService.addInterceptor(dummy) - describe('> if spec is not set (defaults to parcellation region mode)', () => { + const fn = (ev: any, next: Function) => {} + interceptorService.addInterceptor(fn, { last: true }) + expect(interceptorService['clickInterceptorStack'].indexOf(fn)).toEqual(1) - it('if apiService.getUserToSelectRegion.length > 0, but mouseoversegment.length ===0, will not call next, will not rs, will not call rj', () => { - - }) }) + }) - describe('> if spec is set', () => { - describe('> if spec is set to PARCELLATION_REGION', () => { + describe('> deregister', () => { + it('> if the fn exist in the register, it will be removed', () => { - it('> mouseoversegment.length === 0, will not call next, will not rs, will not call rj', () => { + const fn = (ev: any, next: Function) => {} + const fn2 = (ev: any, next: Function) => {} + interceptorService.addInterceptor(fn) + expect(interceptorService['clickInterceptorStack'].indexOf(fn)).toBeGreaterThanOrEqual(0) + expect(interceptorService['clickInterceptorStack'].length).toEqual(1) - }) - - it('> mouseoversegment.length > 0, will not call next, will call rs', () => { - - }) - }) - - describe('> if spec is set to POINT', () => { - it('> rs is called if mouseoversegment.length === 0', () => { + interceptorService.removeInterceptor(fn) + expect(interceptorService['clickInterceptorStack'].indexOf(fn)).toBeLessThan(0) + expect(interceptorService['clickInterceptorStack'].length).toEqual(0) + }) - }) - it('> rs is called with correct arg if mouseoversegment.length > 0', () => { + it('> if fn does not exist in register, it will not be removed', () => { + + const fn = (ev: any, next: Function) => {} + const fn2 = (ev: any, next: Function) => {} + interceptorService.addInterceptor(fn) + expect(interceptorService['clickInterceptorStack'].indexOf(fn)).toBeGreaterThanOrEqual(0) + expect(interceptorService['clickInterceptorStack'].length).toEqual(1) + + interceptorService.removeInterceptor(fn2) + expect(interceptorService['clickInterceptorStack'].indexOf(fn)).toBeGreaterThanOrEqual(0) + expect(interceptorService['clickInterceptorStack'].length).toEqual(1) + }) + }) - }) - }) + describe('> # run', () => { + it('> will run fns from first idx to last idx', () => { + const callNext = (ev: any, next: Function) => next() + const fn = jasmine.createSpy().and.callFake(callNext) + const fn2 = jasmine.createSpy().and.callFake(callNext) - describe('> if multiple getUserToSelectRegion exist', () => { - it('> only the last Promise will be evaluated', () => { + interceptorService.addInterceptor(fn) + interceptorService.addInterceptor(fn2) + interceptorService.run({}) - - }) - }) + expect(fn2).toHaveBeenCalledBefore(fn) + }) + it('> will stop at when next is not called', () => { + + const callNext = (ev: any, next: Function) => next() + const halt = (ev: any, next: Function) => {} + const fn = jasmine.createSpy().and.callFake(callNext) + const fn2 = jasmine.createSpy().and.callFake(halt) + const fn3 = jasmine.createSpy().and.callFake(callNext) + + interceptorService.addInterceptor(fn) + interceptorService.addInterceptor(fn2) + interceptorService.addInterceptor(fn3) + interceptorService.run({}) + + expect(fn3).toHaveBeenCalled() + expect(fn2).toHaveBeenCalled() + expect(fn).not.toHaveBeenCalled() }) }) - }) }) diff --git a/src/glue.ts b/src/glue.ts index f0d9430a1a4bf6bf90bad761b6ca7946aaf65fee..c982af2d5ddf5ae7d712a95cb7e9fce1bc4717b9 100644 --- a/src/glue.ts +++ b/src/glue.ts @@ -18,6 +18,7 @@ import { viewerStateSelectedRegionsSelector, viewerStateSelectedTemplateSelector import { ngViewerSelectorClearView } from "./services/state/ngViewerState/selectors" import { ngViewerActionClearView } from './services/state/ngViewerState/actions' import { generalActionError } from "./services/stateStore.helper" +import { TClickInterceptorConfig } from "./util/injectionTokens" const PREVIEW_FILE_TYPES_NO_UI = [ EnumPreviewFileTypes.NIFTI, @@ -737,8 +738,8 @@ export class ClickInterceptorService{ this.clickInterceptorStack.splice(idx, 1) } } - addInterceptor(fn: Function, atTheEnd?: boolean) { - if (atTheEnd) { + addInterceptor(fn: Function, config?: TClickInterceptorConfig) { + if (config?.last) { this.clickInterceptorStack.push(fn) } else { this.clickInterceptorStack.unshift(fn) diff --git a/src/util/injectionTokens.ts b/src/util/injectionTokens.ts index c910f15350fec0dd087c674d54688dd4ccfe882f..acd5142e83ad88f25f76622c2b53bf282b53cce1 100644 --- a/src/util/injectionTokens.ts +++ b/src/util/injectionTokens.ts @@ -2,7 +2,11 @@ import { InjectionToken } from "@angular/core"; export const CLICK_INTERCEPTOR_INJECTOR = new InjectionToken<ClickInterceptor>('CLICK_INTERCEPTOR_INJECTOR') +export type TClickInterceptorConfig = { + last?: boolean +} + export interface ClickInterceptor{ - register: (interceptorFunction: (ev: any, next: Function) => void) => void + register: (interceptorFunction: (ev: any, next: Function) => void, config?: TClickInterceptorConfig) => void deregister: (interceptorFunction: Function) => void } diff --git a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts index 60364768746e4b061108558dcd48c7918b0959cf..042b97bc443d9edede37fc2473a3bdc6f372e044 100644 --- a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts +++ b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts @@ -277,7 +277,7 @@ export class NehubaGlueCmp implements IViewer, OnChanges, OnDestroy{ if (clickInterceptor) { const { deregister, register } = clickInterceptor const selOnhoverRegion = this.selectHoveredRegion.bind(this) - register(selOnhoverRegion) + register(selOnhoverRegion, { last: true }) this.onDestroyCb.push(() => deregister(selOnhoverRegion)) }