From 8cbe8a7f3980536aad74890d6f23d0218c95077d Mon Sep 17 00:00:00 2001
From: Xiao Gui <xgui3783@gmail.com>
Date: Thu, 13 Feb 2020 13:58:02 +0100
Subject: [PATCH] bugfix:
 https://github.com/HumanBrainProject/interactive-viewer/issues/475 chore:
 added test to prevent regression

---
 e2e/chromeOpts.js                             |   2 +-
 e2e/src/selecting/template.e2e-spec.js        |  41 ++++++
 e2e/src/util.js                               | 138 +++++++++++++++++-
 package.json                                  |   4 +-
 src/services/logging.service.ts               |   6 +-
 .../nehubaContainer.component.ts              |   5 +-
 .../nehubaViewer/nehubaViewer.component.ts    |   5 +-
 .../viewerState.template.html                 |   1 +
 8 files changed, 193 insertions(+), 9 deletions(-)
 create mode 100644 e2e/src/selecting/template.e2e-spec.js

diff --git a/e2e/chromeOpts.js b/e2e/chromeOpts.js
index fc42e95cc..b6900fed9 100644
--- a/e2e/chromeOpts.js
+++ b/e2e/chromeOpts.js
@@ -1,5 +1,5 @@
 module.exports = [
-  '--headless',
+  ...(process.env.DISABLE_CHROME_HEADLESS ?  [] : ['--headless']),
   '--no-sandbox',
   '--disable-gpu',
   '--disable-setuid-sandbox',
diff --git a/e2e/src/selecting/template.e2e-spec.js b/e2e/src/selecting/template.e2e-spec.js
new file mode 100644
index 000000000..55baf251a
--- /dev/null
+++ b/e2e/src/selecting/template.e2e-spec.js
@@ -0,0 +1,41 @@
+const { AtlasPage } = require("../util")
+
+describe('selecting template', () => {
+  let iavPage
+  
+  beforeEach(async () => {
+    iavPage = new AtlasPage()
+    await iavPage.init()
+  })
+
+  it('can select template by clicking main card', async () => {
+    await iavPage.goto()
+    await iavPage.wait(200)
+    await iavPage.dismissModal()
+    await iavPage.wait(200)
+
+    await iavPage.selectTitleCard('ICBM 2009c Nonlinear Asymmetric')
+    await iavPage.wait(1000)
+
+    const viewerIsPopulated = await iavPage.viewerIsPopulated()
+    expect(viewerIsPopulated).toBeTrue
+  })
+  
+
+  it('switching template after template init by clicking select should work', async () => {
+
+    await iavPage.goto()
+    await iavPage.wait(200)
+    await iavPage.dismissModal()
+    await iavPage.wait(200)
+
+    await iavPage.selectTitleCard('ICBM 2009c Nonlinear Asymmetric')
+    await iavPage.wait(1000)
+
+    await iavPage.selectDropdownTemplate('Big Brain (Histology)')
+    await iavPage.wait(7000)
+
+    const viewerIsPopulated = await iavPage.viewerIsPopulated()
+    expect(viewerIsPopulated).toBeTrue
+  })
+})
diff --git a/e2e/src/util.js b/e2e/src/util.js
index 149e3edc2..bcf8e0a2f 100644
--- a/e2e/src/util.js
+++ b/e2e/src/util.js
@@ -1,3 +1,137 @@
+const chromeOpts = require('../chromeOpts')
+const pptr = require('puppeteer')
+const ATLAS_URL = (process.env.ATLAS_URL || 'http://localhost:3000').replace(/\/$/, '')
+const USE_SELENIUM = !!process.env.SELENIUM_ADDRESS
+if (ATLAS_URL.length === 0) throw new Error(`ATLAS_URL must either be left unset or defined.`)
+if (ATLAS_URL[ATLAS_URL.length - 1] === '/') throw new Error(`ATLAS_URL should not trail with a slash: ${ATLAS_URL}`)
+const { By } = require('selenium-webdriver')
+
+function getActualUrl(url) {
+  return /^http\:\/\//.test(url) ? url : `${ATLAS_URL}/${url.replace(/^\//, '')}`
+}
+
+async function getIndexFromArrayOfWebElements(search, webElements) {
+  const texts = await Promise.all(
+    webElements.map(e => e.getText())
+  )
+  return texts.findIndex(text => text.indexOf(search) >= 0)
+}
+
+class WdIavPage{
+  constructor(){
+    
+  }
+
+  async init() {
+
+  }
+
+  async goto(url = '/'){
+    const actualUrl = getActualUrl(url)
+    await browser.get(actualUrl)
+  }
+
+  async wait(ms) {
+    if (!ms) throw new Error(`wait duration must be specified!`)
+    await browser.sleep(ms)
+  }
+
+  async getModal() {
+    return await browser.findElement( By.tagName('mat-dialog-container') )
+  }
+
+  async dismissModal() {
+    try {
+      const modal = await this.getModal()
+      const okBtn = await modal
+        .findElement( By.tagName('mat-dialog-actions') )
+        .findElement( By.css('button[color="primary"] > span.mat-button-wrapper') )
+      await okBtn.click()
+    } catch (e) {
+      console.log(e)
+    }
+  }
+
+  async findTitleCards() {
+    return await browser
+      .findElement( By.tagName('ui-splashscreen') )
+      .findElements( By.tagName('mat-card') )
+  }
+
+  async selectTitleCard( title ) {
+    const titleCards = await this.findTitleCards()
+    const idx = await getIndexFromArrayOfWebElements(title, titleCards)
+    if (idx >= 0) await titleCards[idx].click()
+    else throw new Error(`${title} does not fit any titleCards`)
+  }
+
+  async selectDropdownTemplate(title) {
+    const templateBtn = await (await this.getSideNav())
+      .findElement( By.tagName('viewer-state-controller') )
+      .findElement( By.css('[aria-label="Select a new template"]') )
+    await templateBtn.click()
+
+    const options = await browser.findElements(
+      By.tagName('mat-option')
+    )
+    const idx = await getIndexFromArrayOfWebElements(title, options)
+    if (idx >= 0) await options[idx].click()
+    else throw new Error(`${title} is not found as one of the dropdown templates`)
+  }
+
+  async screenshotViewer() {
+    const ngContainer = await this.getViewerContainer()
+    if (!ngContainer) throw new Error(`neuroglancer-container not defined`)
+    return await ngContainer.takeScreenshot(false)
+  }
+
+  async getViewerContainer() {
+    return await browser.findElement(
+      By.id('neuroglancer-container')
+    )
+  }
+
+  async viewerIsPopulated() {
+    const ngContainer = await this.getViewerContainer()
+    const canvas = await ngContainer.findElement(
+      By.tagName('canvas')
+    )
+    return !!canvas
+  }
+
+  async getSideNav() {
+    return await browser.findElement( By.tagName('search-side-nav') )
+  }
+}
+
+class PptrIAVPage{
+
+  constructor(){
+    this.browser = null
+    this.page = null
+  }
+
+  async init() {
+    this.browser = browser
+
+    this.page = await this.browser.newPage()
+    await this.page.setViewport({
+      width: 1600,
+      height: 900
+    })
+  }
+
+  async goto(url = '/') {
+    const actualUrl = getActualUrl(url)
+    await this.page.goto(actualUrl, { waitUntil: 'networkidle2' })
+  }
+
+  async wait(ms) {
+    if (!ms) throw new Error(`wait duration must be specified!`)
+    await this.page.waitFor(ms)
+  }
+}
+
 exports.getSearchParam = page => {
   return page.evaluate(`window.location.search`)
 }
@@ -12,4 +146,6 @@ exports.wait = (browser) => new Promise(resolve => {
     .then(resolve)
 })
 
-exports.waitMultiple = process.env.WAIT_ULTIPLE || 1
\ No newline at end of file
+exports.waitMultiple = process.env.WAIT_ULTIPLE || 1
+
+exports.AtlasPage = WdIavPage
diff --git a/package.json b/package.json
index ccc3cf586..f0352b0a2 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,7 @@
     "plugin-server": "node ./src/plugin_examples/server.js",
     "dev-server": "BACKEND_URL=${BACKEND_URL:-http://localhost:3000/} webpack-dev-server --config webpack.dev.js --mode development",
     "dev": "npm run dev-server & (cd deploy; node server.js)",
-    "dev-server-aot": "PRODUCTION=true GIT_HASH=`git log --pretty=format:'%h' --invert-grep --grep=^.ignore -1` webpack-dev-server --config webpack.aot.js",
+    "dev-server-aot": "BACKEND_URL=${BACKEND_URL:-http://localhost:3000/} PRODUCTION=true GIT_HASH=`git log --pretty=format:'%h' --invert-grep --grep=^.ignore -1` webpack-dev-server --config webpack.aot.js",
     "dev-server-all-interfaces": "webpack-dev-server --config webpack.dev.js --mode development --hot --host 0.0.0.0",
     "test": "karma start spec/karma.conf.js",
     "e2e": "protractor e2e/protractor.conf",
@@ -21,7 +21,7 @@
   },
   "keywords": [],
   "author": "",
-  "license": "ISC",
+  "license": "TO BE DECIDED",
   "devDependencies": {
     "@angular/animations": "^9.0.0",
     "@angular/cdk": "^9.0.0",
diff --git a/src/services/logging.service.ts b/src/services/logging.service.ts
index 718b96a71..0f4d879e2 100644
--- a/src/services/logging.service.ts
+++ b/src/services/logging.service.ts
@@ -7,7 +7,11 @@ import { Injectable } from "@angular/core";
 })
 
 export class LoggingService {
-  private loggingFlag: boolean = !PRODUCTION
+
+  get loggingFlag(){
+    return window['__IAV_LOGGING_FLAG__'] || !PRODUCTION
+  }
+
   public log(...arg) {
     if (this.loggingFlag) { console.log(...arg) }
   }
diff --git a/src/ui/nehubaContainer/nehubaContainer.component.ts b/src/ui/nehubaContainer/nehubaContainer.component.ts
index 15ab15977..694ff1e98 100644
--- a/src/ui/nehubaContainer/nehubaContainer.component.ts
+++ b/src/ui/nehubaContainer/nehubaContainer.component.ts
@@ -1,4 +1,4 @@
-import { Component, ComponentFactory, ComponentFactoryResolver, ComponentRef, ElementRef, Input, OnChanges, OnDestroy, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
+import { Component, ComponentFactory, ComponentFactoryResolver, ComponentRef, ElementRef, Input, OnChanges, OnDestroy, OnInit, ViewChild, ViewContainerRef, ChangeDetectorRef } from "@angular/core";
 import { select, Store } from "@ngrx/store";
 import { combineLatest, fromEvent, merge, Observable, of, Subscription, from } from "rxjs";
 import { pipeFromArray } from "rxjs/internal/util/pipe";
@@ -153,6 +153,7 @@ export class NehubaContainer implements OnInit, OnChanges, OnDestroy {
     private store: Store<ViewerStateInterface>,
     private elementRef: ElementRef,
     private log: LoggingService,
+    private cdr: ChangeDetectorRef
   ) {
 
     this.useMobileUI$ = this.constantService.useMobileUI$
@@ -984,6 +985,8 @@ export class NehubaContainer implements OnInit, OnChanges, OnDestroy {
 
     this.viewerLoaded = false
     this.nehubaViewer = null
+
+    this.cdr.detectChanges()
   }
 
   private createNewNehuba(template: any, overwriteInitNavigation: any) {
diff --git a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts b/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts
index 4b1b28cc7..09b8cd4ed 100644
--- a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts
+++ b/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts
@@ -1,4 +1,4 @@
-import { Component, ElementRef, EventEmitter, NgZone, OnDestroy, OnInit, Output, Renderer2 } from "@angular/core";
+import { Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output, Renderer2 } from "@angular/core";
 import { fromEvent, Subscription, ReplaySubject } from 'rxjs'
 import { pipeFromArray } from "rxjs/internal/util/pipe";
 import { debounceTime, filter, map, scan } from "rxjs/operators";
@@ -118,7 +118,6 @@ export class NehubaViewerUnit implements OnInit, OnDestroy {
     private rd: Renderer2,
     public elementRef: ElementRef,
     private workerService: AtlasWorkerService,
-    private zone: NgZone,
     public constantService: AtlasViewerConstantsServices,
     private log: LoggingService,
   ) {
@@ -341,7 +340,7 @@ export class NehubaViewerUnit implements OnInit, OnDestroy {
   public loadNehuba() {
     this.nehubaViewer = this.exportNehuba.createNehubaViewer(this.config, (err) => {
       /* print in debug mode */
-      this.log.log(err)
+      this.log.error(err)
     })
 
     /**
diff --git a/src/ui/viewerStateController/viewerStateCFull/viewerState.template.html b/src/ui/viewerStateController/viewerStateCFull/viewerState.template.html
index 32675891b..3da00c0c9 100644
--- a/src/ui/viewerStateController/viewerStateCFull/viewerState.template.html
+++ b/src/ui/viewerStateController/viewerStateCFull/viewerState.template.html
@@ -9,6 +9,7 @@
           Template
         </mat-label>
         <mat-select
+          aria-label="Select a new template"
           panelClass="no-max-width"
           [value]="(templateSelected$ | async)?.name"
           (selectionChange)="handleTemplateChange($event)"
-- 
GitLab