First part of migrating Topo2 to GUI2
Change-Id: I316dd34cba161688e01dfb7b340bff5f2c3c57d4
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/consolelogger.service.spec.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/consolelogger.service.spec.ts
new file mode 100644
index 0000000..bbb8974
--- /dev/null
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/consolelogger.service.spec.ts
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { TestBed, inject } from '@angular/core/testing';
+
+import { ConsoleLoggerService } from './consolelogger.service';
+
+/**
+ * ONOS GUI -- Console Logger Service - Unit Tests
+ */
+describe('ConsoleloggerService', () => {
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [ConsoleLoggerService]
+ });
+ });
+
+ it('should be created', inject([ConsoleLoggerService], (service: ConsoleLoggerService) => {
+ expect(service).toBeTruthy();
+ }));
+});
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/detectbrowser.directive.spec.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/detectbrowser.directive.spec.ts
new file mode 100644
index 0000000..3faa7f1
--- /dev/null
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/detectbrowser.directive.spec.ts
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { TestBed, inject } from '@angular/core/testing';
+
+import { LogService } from './log.service';
+import { ConsoleLoggerService } from './consolelogger.service';
+import { DetectBrowserDirective } from './detectbrowser.directive';
+import { ActivatedRoute, Params } from '@angular/router';
+import { FnService } from './util/fn.service';
+import { OnosService } from './onos.service';
+import { of } from 'rxjs';
+
+class MockFnService extends FnService {
+ constructor(ar: ActivatedRoute, log: LogService, w: Window) {
+ super(ar, log, w);
+ }
+}
+
+class MockOnosService {}
+
+class MockActivatedRoute extends ActivatedRoute {
+ constructor(params: Params) {
+ super();
+ this.queryParams = of(params);
+ }
+}
+
+/**
+ * ONOS GUI -- Detect Browser Directive - Unit Tests
+ */
+describe('DetectBrowserDirective', () => {
+ let log: LogService;
+ let ar: ActivatedRoute;
+ let mockWindow: Window;
+
+ beforeEach(() => {
+ log = new ConsoleLoggerService();
+ ar = new MockActivatedRoute(['debug', 'DetectBrowserDirective']);
+ mockWindow = <any>{
+ navigator: {
+ userAgent: 'HeadlessChrome',
+ vendor: 'Google Inc.'
+ }
+ };
+
+ TestBed.configureTestingModule({
+ providers: [ DetectBrowserDirective,
+ { provide: FnService, useValue: new MockFnService(ar, log, mockWindow) },
+ { provide: LogService, useValue: log },
+ { provide: OnosService, useClass: MockOnosService },
+ { provide: Document, useValue: document },
+ { provide: 'Window', useFactory: (() => mockWindow ) }
+ ]
+ });
+ });
+
+ afterEach(() => {
+ log = null;
+ });
+
+ it('should create an instance', inject([DetectBrowserDirective], (directive: DetectBrowserDirective) => {
+ expect(directive).toBeTruthy();
+ }));
+});
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/layer/loading.service.spec.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/layer/loading.service.spec.ts
new file mode 100644
index 0000000..404439f
--- /dev/null
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/layer/loading.service.spec.ts
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { TestBed, inject } from '@angular/core/testing';
+
+import { LogService } from '../log.service';
+import { ConsoleLoggerService } from '../consolelogger.service';
+import { LoadingService } from './loading.service';
+import { FnService } from '../util/fn.service';
+import { ThemeService } from '../util/theme.service';
+import { WebSocketService } from '../remote/websocket.service';
+
+class MockFnService {
+ debug() {
+ }
+}
+
+class MockThemeService {}
+
+class MockWebSocketService {}
+
+/**
+ * ONOS GUI -- Layer -- Loading Service - Unit Tests
+ */
+describe('LoadingService', () => {
+ let log: LogService;
+
+ beforeEach(() => {
+ log = new ConsoleLoggerService();
+
+ TestBed.configureTestingModule({
+ providers: [LoadingService,
+ { provide: LogService, useValue: log },
+ { provide: FnService, useClass: MockFnService },
+ { provide: ThemeService, useClass: MockThemeService },
+ { provide: WebSocketService, useClass: MockWebSocketService },
+ ]
+ });
+ });
+
+ it('should be created', inject([LoadingService], (service: LoadingService) => {
+ expect(service).toBeTruthy();
+ }));
+});
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/log.service.spec.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/log.service.spec.ts
new file mode 100644
index 0000000..e2998f8
--- /dev/null
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/log.service.spec.ts
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { TestBed, inject } from '@angular/core/testing';
+
+import { LogService } from './log.service';
+
+/**
+ * ONOS GUI -- Log Service - Unit Tests
+ */
+describe('LogService', () => {
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [LogService]
+ });
+ });
+
+ it('should be created', inject([LogService], (service: LogService) => {
+ expect(service).toBeTruthy();
+ }));
+});
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/mast/mast.service.spec.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/mast/mast.service.spec.ts
new file mode 100644
index 0000000..ca99af8
--- /dev/null
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/mast/mast.service.spec.ts
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { TestBed, inject } from '@angular/core/testing';
+
+import { MastService } from './mast.service';
+import { LogService } from '../log.service';
+import { ConsoleLoggerService } from '../consolelogger.service';
+import { FnService } from '../util/fn.service';
+
+class MockFnService {
+ isMobile() {}
+}
+
+/**
+ * ONOS GUI -- Masthead Service - Unit Tests
+ */
+describe('MastService', () => {
+ let log: LogService;
+
+ beforeEach(() => {
+ log = new ConsoleLoggerService();
+
+ TestBed.configureTestingModule({
+ providers: [MastService,
+ { provide: FnService, useClass: MockFnService },
+ { provide: LogService, useValue: log },
+ ]
+ });
+ });
+
+ it('should be created', inject([MastService], (service: MastService) => {
+ expect(service).toBeTruthy();
+ }));
+});
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/mast/mast/mast.component.css b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/mast/mast/mast.component.css
index 5b2d464..a767b2e 100644
--- a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/mast/mast/mast.component.css
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/mast/mast/mast.component.css
@@ -1,5 +1,5 @@
/*
- * Copyright 2014-present Open Networking Foundation
+ * Copyright 2018-present Open Networking Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/mast/mast/mast.component.html b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/mast/mast/mast.component.html
index 2d4e606..437f96d 100644
--- a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/mast/mast/mast.component.html
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/mast/mast/mast.component.html
@@ -1,5 +1,5 @@
<!--
-~ Copyright 2014-present Open Networking Foundation
+~ Copyright 2018-present Open Networking Foundation
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/mast/mast/mast.component.spec.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/mast/mast/mast.component.spec.ts
index fe424e7..fca2dd9 100644
--- a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/mast/mast/mast.component.spec.ts
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/mast/mast/mast.component.spec.ts
@@ -1,5 +1,5 @@
/*
- * Copyright 2015-present Open Networking Foundation
+ * Copyright 2018-present Open Networking Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/mast/mast/mast.component.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/mast/mast/mast.component.ts
index e8e5fec..41e2f3e 100644
--- a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/mast/mast/mast.component.ts
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/mast/mast/mast.component.ts
@@ -1,5 +1,5 @@
/*
- * Copyright 2014-present Open Networking Foundation
+ * Copyright 2018-present Open Networking Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/mast/mast/mast.theme.css b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/mast/mast/mast.theme.css
index 6b92beb..968aefa 100644
--- a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/mast/mast/mast.theme.css
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/mast/mast/mast.theme.css
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-present Open Networking Foundation
+ * Copyright 2018-present Open Networking Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/nav/nav.service.spec.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/nav/nav.service.spec.ts
new file mode 100644
index 0000000..88ab5fa
--- /dev/null
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/nav/nav.service.spec.ts
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { TestBed, inject } from '@angular/core/testing';
+import { HttpClient, HttpErrorResponse } from '@angular/common/http';
+
+import { LogService } from '../log.service';
+import { ConsoleLoggerService } from '../consolelogger.service';
+import { NavService } from './nav.service';
+import { FnService } from '../util/fn.service';
+
+class MockFnService {}
+
+class MockHttpClient {}
+
+
+/**
+ * ONOS GUI -- Util -- Navigation Service - Unit Tests
+ */
+describe('NavService', () => {
+ let log: LogService;
+
+ beforeEach(() => {
+ log = new ConsoleLoggerService();
+
+ TestBed.configureTestingModule({
+ providers: [NavService,
+ { provide: HttpClient, useClass: MockHttpClient },
+ { provide: FnService, useClass: MockFnService },
+ { provide: LogService, useValue: log },
+ ]
+ });
+ });
+
+ it('should be created', inject([NavService], (service: NavService) => {
+ expect(service).toBeTruthy();
+ }));
+});
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/nav/nav.service.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/nav/nav.service.ts
index a24f242..6e96309 100644
--- a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/nav/nav.service.ts
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/nav/nav.service.ts
@@ -1,5 +1,5 @@
/*
- * Copyright 2015-present Open Networking Foundation
+ * Copyright 2018-present Open Networking Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/glyph.service.spec.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/glyph.service.spec.ts
new file mode 100644
index 0000000..5b4669d
--- /dev/null
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/glyph.service.spec.ts
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { TestBed, inject } from '@angular/core/testing';
+
+import { LogService } from '../log.service';
+import { ConsoleLoggerService } from '../consolelogger.service';
+import { GlyphService } from './glyph.service';
+import { FnService } from '../util/fn.service';
+
+class MockFnService {}
+
+/**
+ * ONOS GUI -- SVG -- Glyph Service - Unit Tests
+ */
+describe('GlyphService', () => {
+ let log: LogService;
+
+ beforeEach(() => {
+ log = new ConsoleLoggerService();
+
+ TestBed.configureTestingModule({
+ providers: [GlyphService,
+ { provide: FnService, useClass: MockFnService },
+ { provide: LogService, useValue: log },
+ ]
+ });
+ });
+
+ it('should be created', inject([GlyphService], (service: GlyphService) => {
+ expect(service).toBeTruthy();
+ }));
+});
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/glyphdata.service.spec.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/glyphdata.service.spec.ts
new file mode 100644
index 0000000..ab770d5
--- /dev/null
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/glyphdata.service.spec.ts
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { TestBed, inject } from '@angular/core/testing';
+
+import { LogService } from '..//log.service';
+import { ConsoleLoggerService } from '../consolelogger.service';
+import { GlyphDataService } from './glyphdata.service';
+
+/**
+ * ONOS GUI -- SVG -- Glyph Data Service - Unit Tests
+ */
+describe('GlyphDataService', () => {
+ let log: LogService;
+
+ beforeEach(() => {
+ log = new ConsoleLoggerService();
+
+ TestBed.configureTestingModule({
+ providers: [GlyphDataService,
+ { provide: LogService, useValue: log },
+ ]
+ });
+ });
+
+ it('should be created', inject([GlyphDataService], (service: GlyphDataService) => {
+ expect(service).toBeTruthy();
+ }));
+});
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon.service.spec.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon.service.spec.ts
new file mode 100644
index 0000000..094baef
--- /dev/null
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon.service.spec.ts
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { TestBed, inject } from '@angular/core/testing';
+
+import { LogService } from '../log.service';
+import { ConsoleLoggerService } from '../consolelogger.service';
+import { IconService } from './icon.service';
+import { GlyphService } from './glyph.service';
+import { SvgUtilService } from './svgutil.service';
+
+class MockGlyphService {}
+
+class MockSvgUtilService {}
+
+/**
+ * ONOS GUI -- SVG -- Icon Service - Unit Tests
+ */
+describe('IconService', () => {
+
+ let log: LogService;
+
+ beforeEach(() => {
+ log = new ConsoleLoggerService();
+
+ TestBed.configureTestingModule({
+ providers: [IconService,
+ { provide: LogService, useValue: log },
+ { provide: GlyphService, useClass: MockGlyphService },
+ { provide: SvgUtilService, useClass: MockSvgUtilService },
+ ]
+ });
+ });
+
+ it('should be created', inject([IconService], (service: IconService) => {
+ expect(service).toBeTruthy();
+ }));
+});
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon.service.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon.service.ts
index 6ced162..73480ed 100644
--- a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon.service.ts
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon.service.ts
@@ -43,6 +43,7 @@
['m_ports', 'm_ports'],
['topo', 'topo'],
+ ['bird', 'bird'],
['refresh', 'refresh'],
['query', 'query'],
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon/icon.component.spec.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon/icon.component.spec.ts
new file mode 100644
index 0000000..8234551
--- /dev/null
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon/icon.component.spec.ts
@@ -0,0 +1,30 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { LogService } from '../../log.service';
+import { ConsoleLoggerService } from '../../consolelogger.service';
+import { IconComponent } from './icon.component';
+import { IconService } from '../icon.service';
+
+class MockIconService {}
+
+describe('IconComponent', () => {
+ let log: LogService;
+
+ beforeEach(() => {
+ log = new ConsoleLoggerService();
+
+ TestBed.configureTestingModule({
+ declarations: [ IconComponent ],
+ providers: [
+ { provide: LogService, useValue: log },
+ { provide: IconService, useClass: MockIconService },
+ ]
+ });
+ });
+
+ it('should create', () => {
+ const fixture = TestBed.createComponent(IconComponent);
+ const component = fixture.componentInstance;
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/svgutil.service.spec.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/svgutil.service.spec.ts
new file mode 100644
index 0000000..7165b33
--- /dev/null
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/svgutil.service.spec.ts
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { TestBed, inject } from '@angular/core/testing';
+
+import { LogService } from '../log.service';
+import { ConsoleLoggerService } from '../consolelogger.service';
+import { SvgUtilService } from './svgutil.service';
+import { FnService } from '../util/fn.service';
+
+class MockFnService {}
+
+/**
+ * ONOS GUI -- SVG -- Svg Util Service - Unit Tests
+ */
+describe('SvgUtilService', () => {
+ let log: LogService;
+
+ beforeEach(() => {
+ log = new ConsoleLoggerService();
+
+ TestBed.configureTestingModule({
+ providers: [SvgUtilService,
+ { provide: LogService, useValue: log },
+ { provide: FnService, useClass: MockFnService },
+ ]
+ });
+ });
+
+ it('should be created', inject([SvgUtilService], (service: SvgUtilService) => {
+ expect(service).toBeTruthy();
+ }));
+});
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/svgutil.service.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/svgutil.service.ts
index 13327fe..6107d16 100644
--- a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/svgutil.service.ts
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/svgutil.service.ts
@@ -16,6 +16,7 @@
import { Injectable } from '@angular/core';
import { FnService } from '../util/fn.service';
import { LogService } from '../log.service';
+import * as d3 from 'd3';
/**
* ONOS GUI -- SVG -- Util Service
@@ -26,11 +27,45 @@
providedIn: 'root',
})
export class SvgUtilService {
+ lightNorm: string[];
+ lightMute: string[];
+ darkNorm: string[];
+ darkMute: string[];
+ colors: any;
constructor(
private fs: FnService,
private log: LogService
) {
+
+ // --- Ordinal scales for 7 values.
+ // TODO: migrate these colors to the theme service.
+
+ // Colors per Mojo-Design's color palette.. (version one)
+ // blue red dk grey steel lt blue lt red lt grey
+ // var lightNorm = ['#5b99d2', '#d05a55', '#716b6b', '#7e9aa8', '#66cef6', '#db7773', '#aeada8' ],
+ // lightMute = ['#a8cceb', '#f1a7a7', '#b9b5b5', '#bdcdd5', '#a8e9fd', '#f8c9c9', '#d7d6d4' ],
+
+ // Colors per Mojo-Design's color palette.. (version two)
+ // blue lt blue red green brown teal lime
+ this.lightNorm = ['#5b99d2', '#66cef6', '#d05a55', '#0f9d58', '#ba7941', '#3dc0bf', '#56af00'];
+ this.lightMute = ['#9ebedf', '#abdef5', '#d79a96', '#7cbe99', '#cdab8d', '#96d5d5', '#a0c96d'];
+
+ this.darkNorm = ['#5b99d2', '#66cef6', '#d05a55', '#0f9d58', '#ba7941', '#3dc0bf', '#56af00'];
+ this.darkMute = ['#9ebedf', '#abdef5', '#d79a96', '#7cbe99', '#cdab8d', '#96d5d5', '#a0c96d'];
+
+
+ this.colors = {
+ light: {
+ norm: d3.scaleOrdinal().range(this.lightNorm),
+ mute: d3.scaleOrdinal().range(this.lightMute),
+ },
+ dark: {
+ norm: d3.scaleOrdinal().range(this.darkNorm),
+ mute: d3.scaleOrdinal().range(this.darkMute),
+ },
+ };
+
this.log.debug('SvgUtilService constructed');
}
@@ -40,4 +75,91 @@
}
return 'translate(' + x + ',' + y + ')';
}
+
+ scale(x, y) {
+ return 'scale(' + x + ',' + y + ')';
+ }
+
+ skewX(x) {
+ return 'skewX(' + x + ')';
+ }
+
+ rotate(deg) {
+ return 'rotate(' + deg + ')';
+ }
+
+ cat7() {
+ const tcid = 'd3utilTestCard';
+
+ function getColor(id, muted, theme) {
+ // NOTE: since we are lazily assigning domain ids, we need to
+ // get the color from all 4 scales, to keep the domains
+ // in sync.
+ const ln = this.colors.light.norm(id);
+ const lm = this.colors.light.mute(id);
+ const dn = this.colors.dark.norm(id);
+ const dm = this.colors.dark.mute(id);
+ if (theme === 'dark') {
+ return muted ? dm : dn;
+ } else {
+ return muted ? lm : ln;
+ }
+ }
+
+ function testCard(svg) {
+ let g = svg.select('g#' + tcid);
+ const dom = d3.range(7);
+ let k;
+ let muted;
+ let theme;
+ let what;
+
+ if (!g.empty()) {
+ g.remove();
+
+ } else {
+ g = svg.append('g')
+ .attr('id', tcid)
+ .attr('transform', 'scale(4)translate(20,20)');
+
+ for (k = 0; k < 4; k++) {
+ muted = k % 2;
+ what = muted ? ' muted' : ' normal';
+ theme = k < 2 ? 'light' : 'dark';
+ dom.forEach(function (id, i) {
+ const x = i * 20;
+ const y = k * 20;
+ const f = getColor(id, muted, theme);
+ g.append('circle').attr({
+ cx: x,
+ cy: y,
+ r: 5,
+ fill: f,
+ });
+ });
+ g.append('rect').attr({
+ x: 140,
+ y: k * 20 - 5,
+ width: 32,
+ height: 10,
+ rx: 2,
+ fill: '#888',
+ });
+ g.append('text').text(theme + what)
+ .attr({
+ x: 142,
+ y: k * 20 + 2,
+ fill: 'white',
+ })
+ .style('font-size', '4pt');
+ }
+ }
+ }
+
+ return {
+ testCard: testCard,
+ getColor: getColor,
+ };
+ }
+
}
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/zoom.service.spec.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/zoom.service.spec.ts
new file mode 100644
index 0000000..fe25860
--- /dev/null
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/zoom.service.spec.ts
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+*
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { TestBed, inject } from '@angular/core/testing';
+import { ActivatedRoute, Params } from '@angular/router';
+import { of } from 'rxjs';
+import * as d3 from 'd3';
+
+import { LogService } from '../log.service';
+import { FnService } from '../util/fn.service';
+
+import { ZoomService, CZ, D3S, ZoomOpts, Zoomer } from './zoom.service';
+
+class MockActivatedRoute extends ActivatedRoute {
+ constructor(params: Params) {
+ super();
+ this.queryParams = of(params);
+ }
+}
+
+
+/**
+ * ONOS GUI -- SVG -- Zoom Service - Unit Tests
+ */
+describe('ZoomService', () => {
+ let zs: ZoomService;
+ let ar: ActivatedRoute;
+ let fs: FnService;
+ let mockWindow: Window;
+ let logServiceSpy: jasmine.SpyObj<LogService>;
+
+ const svg = d3.select('body').append('svg').attr('id', 'mySvg');
+ const zoomLayer = svg.append('g').attr('id', 'myZoomlayer');
+
+ beforeEach(() => {
+ const logSpy = jasmine.createSpyObj('LogService', ['debug', 'warn', 'error']);
+ ar = new MockActivatedRoute({'debug': 'TestService'});
+ mockWindow = <any>{
+ innerWidth: 400,
+ innerHeight: 200,
+ navigator: {
+ userAgent: 'defaultUA'
+ }
+ };
+ fs = new FnService(ar, logSpy, mockWindow);
+
+ TestBed.configureTestingModule({
+ providers: [ ZoomService,
+ { provide: FnService, useValue: fs },
+ { provide: LogService, useValue: logSpy },
+ { provide: ActivatedRoute, useValue: ar },
+ { provide: 'Window', useFactory: (() => mockWindow ) }
+ ]
+ });
+
+ zs = TestBed.get(ZoomService);
+ logServiceSpy = TestBed.get(LogService);
+ });
+
+ it('should be created', () => {
+ expect(zs).toBeTruthy();
+ });
+
+ it('should define ZoomService', function () {
+ expect(zs).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(zs, [
+ 'createZoomer',
+ 'zoomed',
+ 'adjustZoomLayer'
+ ])).toBeTruthy();
+ });
+
+ function verifyZoomerApi() {
+ expect(fs.areFunctions(zs.zoomer, [
+ 'panZoom', 'reset', 'translate', 'scale', 'scaleExtent'
+ ])).toBeTruthy();
+ }
+
+ it('should fail gracefully with no option object', function () {
+ expect(() => zs.createZoomer(<ZoomOpts>{}))
+ .toThrow(new Error(CZ + 'No "svg" (svg tag)' + D3S));
+ expect(logServiceSpy.error)
+ .toHaveBeenCalledWith(CZ + 'No "svg" (svg tag)' + D3S);
+ });
+
+ it('should complain if we miss required options', function () {
+ expect(() => zs.createZoomer(<ZoomOpts>{svg: svg}))
+ .toThrow(new Error(CZ + 'No "zoomLayer" (g tag)' + D3S));
+ expect(logServiceSpy.error).toHaveBeenCalledWith(CZ + 'No "zoomLayer" (g tag)' + D3S);
+ });
+
+ it('should work with minimal parameters', function () {
+ const zoomer = zs.createZoomer(<ZoomOpts>{
+ svg: svg,
+ zoomLayer: zoomLayer
+ });
+ expect(logServiceSpy.error).not.toHaveBeenCalled();
+ verifyZoomerApi();
+ });
+
+ it('should start at scale 1 and translate 0,0', function () {
+ const zoomer = zs.createZoomer(<ZoomOpts>{
+ svg: svg,
+ zoomLayer: zoomLayer
+ });
+ verifyZoomerApi();
+ expect(zoomer.translate()).toEqual([0, 0]);
+ expect(zoomer.scale()).toEqual(1);
+ });
+
+ it('should allow programmatic pan/zoom', function () {
+ const zoomer: Zoomer = zs.createZoomer(<ZoomOpts>{
+ svg: svg,
+ zoomLayer: zoomLayer
+ });
+ verifyZoomerApi();
+
+ expect(zoomer.translate()).toEqual([0, 0]);
+ expect(zoomer.scale()).toEqual(1);
+
+ zoomer.panZoom([20, 30], 1);
+ expect(zoomer.translate()).toEqual([20, 30]);
+ expect(zoomer.scale()).toEqual(1);
+
+ zoomer.reset();
+ expect(zoomer.translate()).toEqual([0, 0]);
+ expect(zoomer.scale()).toEqual(1);
+
+
+ });
+
+ it('should provide default scale extent', function () {
+ const zoomer = zs.createZoomer(<ZoomOpts>{
+ svg: svg,
+ zoomLayer: zoomLayer
+ });
+ expect(zoomer.scaleExtent()).toEqual([0.05, 50]);
+ });
+
+ it('should allow us to override the minimum zoom', function () {
+ const zoomer = zs.createZoomer(<ZoomOpts>{
+ svg: svg,
+ zoomLayer: zoomLayer,
+ zoomMin: 1.23
+ });
+ expect(zoomer.scaleExtent()).toEqual([1.23, 50]);
+ });
+
+ it('should allow us to override the maximum zoom', function () {
+ const zoomer = zs.createZoomer(<ZoomOpts>{
+ svg: svg,
+ zoomLayer: zoomLayer,
+ zoomMax: 13
+ });
+ expect(zoomer.scaleExtent()).toEqual([0.05, 13]);
+ });
+
+ // TODO: test zoomed() where we fake out the d3.event.sourceEvent etc...
+ // need to check default enabled (true) and custom enabled predicate
+ // need to check that the callback is invoked also
+
+ it('should invoke the callback on programmatic pan/zoom', function () {
+ const foo = { cb() { return; } };
+ spyOn(foo, 'cb');
+
+ const zoomer = zs.createZoomer(<ZoomOpts>{
+ svg: svg,
+ zoomMin: 0.25,
+ zoomMax: 10,
+ zoomLayer: zoomLayer,
+ zoomEnabled: (ev) => true,
+ zoomCallback: foo.cb,
+ });
+
+ zoomer.panZoom([0, 0], 2);
+ expect(foo.cb).toHaveBeenCalled();
+ });
+});
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/zoom.service.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/zoom.service.ts
new file mode 100644
index 0000000..fdf08f9
--- /dev/null
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/zoom.service.ts
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { Injectable } from '@angular/core';
+import * as d3 from 'd3';
+import { LogService } from '../log.service';
+
+export interface ZoomOpts {
+ svg: any; // D3 selection of <svg> element
+ zoomLayer: any; // D3 selection of <g> element
+ zoomMin: number; // Min zoom level - usually 0.25
+ zoomMax: number; // Max zoom level - usually 10
+ zoomEnabled(ev: any): boolean; // Function that takes event and returns boolean
+ zoomCallback(translate: number[], scale: number): void; // Function that is called on zoom
+}
+
+export interface Zoomer {
+ panZoom(translate: number[], scale: number, transition?: number): void;
+ reset(): void;
+ translate(): number[];
+ scale(): number;
+ scaleExtent(): number[];
+}
+
+export const CZ: string = 'ZoomService.createZoomer(): ';
+export const D3S: string = ' (D3 selection) property defined';
+
+/**
+ * ONOS GUI -- Topology Zoom Service Module.
+ */
+@Injectable({
+ providedIn: 'root',
+})
+export class ZoomService {
+ defaultSettings: ZoomOpts;
+
+ zoom: any;
+ public zoomer: Zoomer;
+ settings: ZoomOpts;
+
+ constructor(
+ protected log: LogService,
+ ) {
+ this.defaultSettings = <ZoomOpts>{
+ zoomMin: 0.05,
+ zoomMax: 50,
+ zoomEnabled: (ev) => true,
+ zoomCallback: (t, s) => { return; }
+ };
+
+ this.log.debug('ZoomService constructed');
+ }
+
+ createZoomer(opts: ZoomOpts): Zoomer {
+ this.settings = Object.assign(this.defaultSettings, opts);
+
+ if (!this.settings.svg) {
+ this.log.error(CZ + 'No "svg" (svg tag)' + D3S);
+ throw new Error(CZ + 'No "svg" (svg tag)' + D3S);
+ }
+ if (!this.settings.zoomLayer) {
+ this.log.error(CZ + 'No "zoomLayer" (g tag)' + D3S);
+ throw new Error(CZ + 'No "zoomLayer" (g tag)' + D3S);
+ }
+
+ this.zoom = d3.zoom()
+ .scaleExtent([this.settings.zoomMin, this.settings.zoomMax])
+ .extent([[0, 0], [1000, 1000]])
+ .on('zoom', () => this.zoomed);
+
+
+ this.zoomer = <Zoomer>{
+ panZoom: (translate: number[], scale: number, transition?: number) => {
+ this.settings.svg.call(this.zoom.translateBy, translate[0], translate[1]);
+ this.settings.svg.call(this.zoom.scaleTo, scale);
+ this.adjustZoomLayer(translate, scale, transition);
+ },
+
+ reset: () => {
+ this.settings.svg.call(this.zoom.translateTo, 500, 500);
+ this.settings.svg.call(this.zoom.scaleTo, 1);
+ this.adjustZoomLayer([0, 0], 1, 0);
+ },
+
+ translate: () => {
+ const trans = d3.zoomTransform(this.settings.svg.node());
+ return [trans.x, trans.y];
+ },
+
+ scale: () => {
+ const trans = d3.zoomTransform(this.settings.svg.node());
+ return trans.k;
+ },
+
+ scaleExtent: () => {
+ return this.zoom.scaleExtent();
+ },
+ };
+
+ // apply the zoom behavior to the SVG element
+/*
+ if (this.settings.svg ) {
+ this.settings.svg.call(this.zoom);
+ }
+*/
+ // Remove zoom on double click (prevents a
+ // false zoom navigating regions)
+ this.settings.svg.on('dblclick.zoom', null);
+
+ return this.zoomer;
+ }
+
+ /**
+ * zoom events from mouse gestures...
+ */
+ zoomed() {
+ const ev = d3.event.sourceEvent;
+ if (this.settings.zoomEnabled(ev)) {
+ this.adjustZoomLayer(d3.event.translate, d3.event.scale);
+ }
+ }
+
+ /**
+ * Adjust the zoom layer
+ */
+ adjustZoomLayer(translate: number[], scale: number, transition?: any): void {
+
+ this.settings.zoomLayer.transition()
+ .duration(transition || 0)
+ .attr('transform',
+ 'translate(' + translate + ') scale(' + scale + ')');
+
+ this.settings.zoomCallback(translate, scale);
+ }
+
+}
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/util/fn.service.spec.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/util/fn.service.spec.ts
index 5b86d56..e9b7c2a 100644
--- a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/util/fn.service.spec.ts
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/util/fn.service.spec.ts
@@ -235,7 +235,8 @@
'isFirefox', 'parseDebugFlags',
'debugOn', 'debug', 'find', 'inArray', 'removeFromArray',
'isEmptyObject', 'cap', 'noPx', 'noPxStyle', 'endsWith',
- 'inEvilList', 'analyze', 'sanitize', 'sameObjProps', 'containsObj'
+ 'inEvilList', 'analyze', 'sanitize', 'sameObjProps', 'containsObj',
+ 'addToTrie', 'removeFromTrie', 'trieLookup'
// 'find', 'inArray', 'removeFromArray', 'isEmptyObject', 'sameObjProps', 'containsObj', 'cap',
// 'eecode', 'noPx', 'noPxStyle', 'endsWith', 'addToTrie', 'removeFromTrie', 'trieLookup',
// 'classNames', 'extend', 'sanitize'
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/util/fn.service.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/util/fn.service.ts
index 506ce74..60d4950 100644
--- a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/util/fn.service.ts
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/util/fn.service.ts
@@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { Injectable, Inject } from '@angular/core';
-import { ActivatedRoute, Router} from '@angular/router';
-import { LogService } from '../log.service';
+import {Inject, Injectable} from '@angular/core';
+import {ActivatedRoute} from '@angular/router';
+import {LogService} from '../log.service';
+import {Trie, TrieOp} from './trie';
// Angular>=2 workaround for missing definition
declare const InstallTrigger: any;
@@ -40,112 +41,6 @@
name: string;
}
-// TODO Move all this trie stuff to its own class
-// Angular>=2 Tightened up on types to avoid compiler errors
-interface TrieC {
- p: any;
- s: string[];
-}
-// trie operation
-function _trieOp(op: string, trie, word: string, data) {
- const p = trie;
- const w: string = word.toUpperCase();
- const s: Array<string> = w.split('');
- let c: TrieC = { p: p, s: s };
- let t = [];
- let x = 0;
- const f1 = op === '+' ? add : probe;
- const f2 = op === '+' ? insert : remove;
-
- function add(cAdded): TrieC {
- const q = cAdded.s.shift();
- let np = cAdded.p[q];
-
- if (!np) {
- cAdded.p[q] = {};
- np = cAdded.p[q];
- x = 1;
- }
- return { p: np, s: cAdded.s };
- }
-
- function probe(cProbed): TrieC {
- const q = cProbed.s.shift();
- const k: number = Object.keys(cProbed.p).length;
- const np = cProbed.p[q];
-
- t.push({ q: q, k: k, p: cProbed.p });
- if (!np) {
- t = [];
- return { p: [], s: [] };
- }
- return { p: np, s: cProbed.s };
- }
-
- function insert() {
- c.p._data = data;
- return x ? 'added' : 'updated';
- }
-
- function remove() {
- if (t.length) {
- t = t.reverse();
- while (t.length) {
- const d = t.shift();
- delete d.p[d.q];
- if (d.k > 1) {
- t = [];
- }
- }
- return 'removed';
- }
- return 'absent';
- }
-
- while (c.s.length) {
- c = f1(c);
- }
- return f2();
-}
-
-// add word to trie (word will be converted to uppercase)
-// data associated with the word
-// returns 'added' or 'updated'
-function addToTrie(trie, word, data) {
- return _trieOp('+', trie, word, data);
-}
-
-// remove word from trie (word will be converted to uppercase)
-// returns 'removed' or 'absent'
-// Angular>=2 added in quotes for data. error TS2554: Expected 4 arguments, but got 3.
-function removeFromTrie(trie, word) {
- return _trieOp('-', trie, word, '');
-}
-
-// lookup word (converted to uppercase) in trie
-// returns:
-// undefined if the word is not in the trie
-// -1 for a partial match (word is a prefix to an existing word)
-// data for the word for an exact match
-function trieLookup(trie, word) {
- const s = word.toUpperCase().split('');
- let p = trie;
- let n;
-
- while (s.length) {
- n = s.shift();
- p = p[n];
- if (!p) {
- return undefined;
- }
- }
- if (p._data) {
- return p._data;
- }
- return -1;
-}
-
-
/**
* ONOS GUI -- Util -- General Purpose Functions
*/
@@ -561,4 +456,46 @@
return html;
}
+ /**
+ * add word to trie (word will be converted to uppercase)
+ * data associated with the word
+ * returns 'added' or 'updated'
+ */
+ addToTrie(trie, word, data) {
+ return new Trie(TrieOp.PLUS, trie, word, data);
+ }
+
+ /**
+ * remove word from trie (word will be converted to uppercase)
+ * returns 'removed' or 'absent'
+ */
+ removeFromTrie(trie, word) {
+ return new Trie(TrieOp.MINUS, trie, word);
+ }
+
+ /**
+ * lookup word (converted to uppercase) in trie
+ * returns:
+ * undefined if the word is not in the trie
+ * -1 for a partial match (word is a prefix to an existing word)
+ * data for the word for an exact match
+ */
+ trieLookup(trie, word) {
+ const s = word.toUpperCase().split('');
+ let p = trie;
+ let n;
+
+ while (s.length) {
+ n = s.shift();
+ p = p[n];
+ if (!p) {
+ return undefined;
+ }
+ }
+ if (p._data) {
+ return p._data;
+ }
+ return -1;
+ }
+
}
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/util/keys.service.spec.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/util/keys.service.spec.ts
new file mode 100644
index 0000000..5f4b349
--- /dev/null
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/util/keys.service.spec.ts
@@ -0,0 +1,316 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { TestBed, inject } from '@angular/core/testing';
+import {ActivatedRoute, Params} from '@angular/router';
+
+import { KeysService, KeysToken } from './keys.service';
+import { FnService } from './fn.service';
+import { LogService } from '../log.service';
+import { NavService } from '../nav/nav.service';
+
+import {of} from 'rxjs';
+import * as d3 from 'd3';
+
+class MockActivatedRoute extends ActivatedRoute {
+ constructor(params: Params) {
+ super();
+ this.queryParams = of(params);
+ }
+}
+
+class MockNavService {}
+
+/*
+ ONOS GUI -- Key Handler Service - Unit Tests
+ */
+describe('KeysService', () => {
+ let ar: ActivatedRoute;
+ let fs: FnService;
+ let ks: KeysService;
+ let mockWindow: Window;
+ let logServiceSpy: jasmine.SpyObj<LogService>;
+
+ const qhs: any = {};
+ let d3Elem: any;
+ let elem: any;
+ let last: any;
+
+ beforeEach(() => {
+ const logSpy = jasmine.createSpyObj('LogService', ['debug', 'warn', 'info']);
+ ar = new MockActivatedRoute({'debug': 'TestService'});
+ mockWindow = <any>{
+ innerWidth: 400,
+ innerHeight: 200,
+ navigator: {
+ userAgent: 'defaultUA'
+ },
+ location: <any>{
+ hostname: 'foo',
+ host: 'foo',
+ port: '80',
+ protocol: 'http',
+ search: { debug: 'true' },
+ href: 'ws://foo:123/onos/ui/websock/path',
+ absUrl: 'ws://foo:123/onos/ui/websock/path'
+ }
+ };
+ fs = new FnService(ar, logSpy, mockWindow);
+
+ d3Elem = d3.select('body').append('p').attr('id', 'ptest');
+ elem = d3Elem.node();
+ last = {
+ view: null,
+ key: null,
+ code: null,
+ ev: null
+ };
+
+ TestBed.configureTestingModule({
+ providers: [KeysService,
+ { provide: FnService, useValue: fs},
+ { provide: LogService, useValue: logSpy },
+ { provide: ActivatedRoute, useValue: ar },
+ { provide: NavService, useClass: MockNavService},
+ { provide: 'Window', useFactory: (() => mockWindow ) }
+ ]
+ });
+ ks = TestBed.get(KeysService);
+ ks.installOn(d3Elem);
+ ks.bindQhs(qhs);
+ logServiceSpy = TestBed.get(LogService);
+ });
+
+ afterEach(() => {
+ d3.select('#ptest').remove();
+ });
+
+ it('should be created', () => {
+ expect(ks).toBeTruthy();
+ });
+
+ it('should define api functions', () => {
+ expect(fs.areFunctions(ks, [
+ 'bindQhs', 'installOn', 'keyBindings', 'unbindKeys', 'dialogKeys',
+ 'addSeq', 'remSeq', 'gestureNotes', 'enableKeys', 'enableGlobalKeys',
+ 'checkNotGlobal', 'getKeyBindings',
+ 'matchSeq', 'whatKey', 'textFieldInput', 'keyIn', 'qhlion', 'qhlionShowHide',
+ 'qhlionHintEsc', 'qhlionHintT', 'setupGlobalKeys', 'quickHelp',
+ 'escapeKey', 'toggleTheme', 'filterMaskedKeys', 'unexParam',
+ 'setKeyBindings', 'bindDialogKeys', 'unbindDialogKeys'
+ ])).toBeTruthy();
+ });
+
+ function jsKeyDown(element, code: string, keyName: string) {
+ const ev = new KeyboardEvent('keydown',
+ { code: code, key: keyName });
+
+ // Chromium Hack
+ // if (navigator.userAgent.toLowerCase().indexOf('chrome') > -1) {
+ // Object.defineProperty(ev, 'keyCode', {
+ // get: () => { return this.keyCodeVal; }
+ // });
+ // Object.defineProperty(ev, 'which', {
+ // get: () => { return this.keyCodeVal; }
+ // });
+ // }
+
+ if (ev.code !== code.toString()) {
+ console.warn('keyCode mismatch ' + ev.code +
+ '(' + ev.which + ') -> ' + code);
+ }
+ element.dispatchEvent(ev);
+ }
+
+ // === Key binding related tests
+ it('should start with default key bindings', () => {
+ const state = ks.getKeyBindings();
+ const gk = state.globalKeys;
+ const mk = state.maskedKeys;
+ const vk = state.viewKeys;
+ const vf = state.viewFunction;
+
+ expect(gk.length).toEqual(4);
+ ['backSlash', 'slash', 'esc', 'T'].forEach((k) => {
+ expect(fs.contains(gk, k)).toBeTruthy();
+ });
+
+ expect(mk.length).toEqual(3);
+ ['backSlash', 'slash', 'T'].forEach((k) => {
+ expect(fs.contains(mk, k)).toBeTruthy();
+ });
+
+ expect(vk.length).toEqual(0);
+ expect(vf).toBeFalsy();
+ });
+
+ function bindTestKeys(withDescs?) {
+ const keys = ['A', '1', 'F5', 'equals'];
+ const kb = {};
+
+ function cb(view, key, code, ev) {
+ last.view = view;
+ last.key = key;
+ last.code = code;
+ last.ev = ev;
+ }
+
+ function bind(k) {
+ return withDescs ?
+ [(view, key, code, ev) => {cb(view, key, code, ev); }, 'desc for key ' + k] :
+ (view, key, code, ev) => {cb(view, key, code, ev); };
+ }
+
+ keys.forEach((k) => {
+ kb[k] = bind(k);
+ });
+
+ ks.keyBindings(kb);
+ }
+
+ function verifyCall(key, code) {
+ // TODO: update expectation, when view tokens are implemented
+ expect(last.view).toEqual(KeysToken.KEYEV);
+ last.view = null;
+
+ expect(last.key).toEqual(key);
+ last.key = null;
+
+ expect(last.code).toEqual(code);
+ last.code = null;
+
+ expect(last.ev).toBeTruthy();
+ last.ev = null;
+ }
+
+ function verifyNoCall() {
+ expect(last.view).toBeNull();
+ expect(last.key).toBeNull();
+ expect(last.code).toBeNull();
+ expect(last.ev).toBeNull();
+ }
+
+ function verifyTestKeys() {
+ jsKeyDown(elem, '65', 'A'); // 'A'
+ verifyCall('A', '65');
+ jsKeyDown(elem, '66', 'B'); // 'B'
+ verifyNoCall();
+
+ jsKeyDown(elem, '49', '1'); // '1'
+ verifyCall('1', '49');
+ jsKeyDown(elem, '50', '2'); // '2'
+ verifyNoCall();
+
+ jsKeyDown(elem, '116', 'F5'); // 'F5'
+ verifyCall('F5', '116');
+ jsKeyDown(elem, '117', 'F6'); // 'F6'
+ verifyNoCall();
+
+ jsKeyDown(elem, '187', '='); // 'equals'
+ verifyCall('equals', '187');
+ jsKeyDown(elem, '189', '-'); // 'dash'
+ verifyNoCall();
+
+ const vk = ks.getKeyBindings().viewKeys;
+
+ expect(vk.length).toEqual(4);
+ ['A', '1', 'F5', 'equals'].forEach((k) => {
+ expect(fs.contains(vk, k)).toBeTruthy();
+ });
+
+ expect(ks.getKeyBindings().viewFunction).toBeFalsy();
+ }
+
+ it('should allow specific key bindings', () => {
+ bindTestKeys();
+ verifyTestKeys();
+ });
+
+ it('should allow specific key bindings with descriptions', () => {
+ bindTestKeys(true);
+ verifyTestKeys();
+ });
+
+ it('should warn about masked keys', () => {
+ const k = {
+ 'space': (token, key, code, ev) => cb(token, key, code, ev),
+ 'T': (token, key, code, ev) => cb(token, key, code, ev)
+ };
+ let count = 0;
+
+ function cb(token, key, code, ev) {
+ count++;
+ // console.debug('count = ' + count, token, key, code);
+ }
+
+ ks.keyBindings(k);
+
+ expect(logServiceSpy.warn).toHaveBeenCalledWith('setKeyBindings()\n: Key "T" is reserved');
+
+ // the 'T' key should NOT invoke our callback
+ expect(count).toEqual(0);
+ jsKeyDown(elem, '84', 'T'); // 'T'
+ expect(count).toEqual(0);
+
+ // but the 'space' key SHOULD invoke our callback
+ jsKeyDown(elem, '32', ' '); // 'space'
+ expect(count).toEqual(1);
+ });
+
+ it('should block keys when disabled', () => {
+ let cbCount = 0;
+
+ function cb() { cbCount++; }
+
+ function pressA() { jsKeyDown(elem, '65', 'A'); } // 65 == 'A' keycode
+
+ ks.keyBindings({ A: () => cb() });
+
+ expect(cbCount).toBe(0);
+
+ pressA();
+ expect(cbCount).toBe(1);
+
+ ks.enableKeys(false);
+ pressA();
+ expect(cbCount).toBe(1);
+
+ ks.enableKeys(true);
+ pressA();
+ expect(cbCount).toBe(2);
+ });
+
+ // === Gesture notes related tests
+ it('should start with no notes', () => {
+ expect(ks.gestureNotes()).toEqual([]);
+ });
+
+ it('should allow us to add nodes', () => {
+ const notes = [
+ ['one', 'something about one'],
+ ['two', 'description of two']
+ ];
+ ks.gestureNotes(notes);
+
+ expect(ks.gestureNotes()).toEqual(notes);
+ });
+
+ it('should ignore non-arrays', () => {
+ ks.gestureNotes({foo: 4});
+ expect(ks.gestureNotes()).toEqual([]);
+ });
+
+ // Consider adding test to ensure array contains 2-tuples of strings
+});
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/util/keys.service.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/util/keys.service.ts
new file mode 100644
index 0000000..ac68c1a
--- /dev/null
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/util/keys.service.ts
@@ -0,0 +1,407 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { Injectable } from '@angular/core';
+import * as d3 from 'd3';
+import { LogService } from '../log.service';
+import { FnService } from '../util/fn.service';
+import { LionService } from './lion.service';
+import { NavService } from '../nav/nav.service';
+
+export interface KeyHandler {
+ globalKeys: Object;
+ maskedKeys: Object;
+ dialogKeys: Object;
+ viewKeys: any;
+ viewFn: any;
+ viewGestures: string[][];
+}
+
+export enum KeysToken {
+ KEYEV = 'keyev'
+}
+
+/**
+ * ONOS GUI -- Keys Service Module.
+ */
+@Injectable({
+ providedIn: 'root',
+})
+export class KeysService {
+ enabled: boolean = true;
+ globalEnabled: boolean = true;
+ keyHandler: KeyHandler = <KeyHandler>{
+ globalKeys: {},
+ maskedKeys: {},
+ dialogKeys: {},
+ viewKeys: {},
+ viewFn: null,
+ viewGestures: [],
+ };
+
+ seq: any = {};
+ matching: boolean = false;
+ matched: string = '';
+ lookup: any;
+ textFieldDoesNotBlock: any = {
+ enter: 1,
+ esc: 1,
+ };
+ qhs: any; // Quick Help Service ??
+
+ constructor(
+ protected log: LogService,
+ protected fs: FnService,
+ protected ls: LionService,
+ protected ns: NavService
+ ) {
+ this.log.debug('KeyService constructed');
+ }
+
+ bindQhs(_qhs_) {
+ this.qhs = _qhs_;
+ }
+
+ installOn(elem) {
+ this.log.debug('Installing keys handler');
+ elem.on('keydown', () => { this.keyIn(); });
+ this.setupGlobalKeys();
+ }
+
+ keyBindings(x) {
+ if (x === undefined) {
+ return this.getKeyBindings();
+ } else {
+ this.setKeyBindings(x);
+ }
+ }
+
+ unbindKeys() {
+ this.keyHandler.viewKeys = {};
+ this.keyHandler.viewFn = null;
+ this.keyHandler.viewGestures = [];
+ }
+
+ dialogKeys(x) {
+ if (x === undefined) {
+ this.unbindDialogKeys();
+ } else {
+ this.bindDialogKeys(x);
+ }
+ }
+
+ addSeq(word, data) {
+ this.fs.addToTrie(this.seq, word, data);
+ }
+
+ remSeq(word) {
+ this.fs.removeFromTrie(this.seq, word);
+ }
+
+ gestureNotes(g?) {
+ if (g === undefined) {
+ return this.keyHandler.viewGestures;
+ } else {
+ this.keyHandler.viewGestures = this.fs.isA(g) || [];
+ }
+ }
+
+ enableKeys(b) {
+ this.enabled = b;
+ }
+
+ enableGlobalKeys(b) {
+ this.globalEnabled = b;
+ }
+
+ checkNotGlobal(o) {
+ const oops = [];
+ if (this.fs.isO(o)) {
+ o.forEach((val, key) => {
+ if (this.keyHandler.globalKeys[key]) {
+ oops.push(key);
+ }
+ });
+ if (oops.length) {
+ this.log.warn('Ignoring reserved global key(s):', oops.join(','));
+ oops.forEach((key) => {
+ delete o[key];
+ });
+ }
+ }
+ }
+
+ protected matchSeq(key) {
+ if (!this.matching && key === 'shift-shift') {
+ this.matching = true;
+ return true;
+ }
+ if (this.matching) {
+ this.matched += key;
+ this.lookup = this.fs.trieLookup(this.seq, this.matched);
+ if (this.lookup === -1) {
+ return true;
+ }
+ this.matching = false;
+ this.matched = '';
+ if (!this.lookup) {
+ return;
+ }
+ // ee.cluck(lookup);
+ return true;
+ }
+ }
+
+ protected whatKey(code: number): string {
+ switch (code) {
+ case 8: return 'delete';
+ case 9: return 'tab';
+ case 13: return 'enter';
+ case 16: return 'shift';
+ case 27: return 'esc';
+ case 32: return 'space';
+ case 37: return 'leftArrow';
+ case 38: return 'upArrow';
+ case 39: return 'rightArrow';
+ case 40: return 'downArrow';
+ case 186: return 'semicolon';
+ case 187: return 'equals';
+ case 188: return 'comma';
+ case 189: return 'dash';
+ case 190: return 'dot';
+ case 191: return 'slash';
+ case 192: return 'backQuote';
+ case 219: return 'openBracket';
+ case 220: return 'backSlash';
+ case 221: return 'closeBracket';
+ case 222: return 'quote';
+ default:
+ if ((code >= 48 && code <= 57) ||
+ (code >= 65 && code <= 90)) {
+ return String.fromCharCode(code);
+ } else if (code >= 112 && code <= 123) {
+ return 'F' + (code - 111);
+ }
+ return null;
+ }
+ }
+
+ protected textFieldInput() {
+ const t = d3.event.target.tagName.toLowerCase();
+ return t === 'input' || t === 'textarea';
+ }
+
+ protected keyIn() {
+ const event = d3.event;
+ // d3.events can set the keyCode, but unit tests based on KeyboardEvent
+ // cannot set keyCode since the attribute has been deprecated
+ const code = event.keyCode ? event.keyCode : event.code;
+ let key = this.whatKey(Number.parseInt(code));
+ this.log.debug('Key detected', event, key, event.code, event.keyCode);
+ const textBlockable = !this.textFieldDoesNotBlock[key];
+ const modifiers = [];
+
+ if (event.metaKey) {
+ modifiers.push('cmd');
+ }
+ if (event.altKey) {
+ modifiers.push('alt');
+ }
+ if (event.shiftKey) {
+ modifiers.push('shift');
+ }
+
+ if (!key) {
+ return;
+ }
+
+ modifiers.push(key);
+ key = modifiers.join('-');
+
+ if (textBlockable && this.textFieldInput()) {
+ return;
+ }
+
+ const kh: KeyHandler = this.keyHandler;
+ const gk = kh.globalKeys[key];
+ const gcb = this.fs.isF(gk) || (this.fs.isA(gk) && this.fs.isF(gk[0]));
+ const dk = kh.dialogKeys[key];
+ const dcb = this.fs.isF(dk);
+ const vk = kh.viewKeys[key];
+ const kl = this.fs.isF(kh.viewKeys._keyListener);
+ const vcb = this.fs.isF(vk) || (this.fs.isA(vk) && this.fs.isF(vk[0])) || this.fs.isF(kh.viewFn);
+ const token: KeysToken = KeysToken.KEYEV; // indicate this was a key-pressed event
+
+ event.stopPropagation();
+
+ if (this.enabled) {
+ if (this.matchSeq(key)) {
+ return;
+ }
+
+ // global callback?
+ if (gcb && gcb(token, key, code, event)) {
+ // if the event was 'handled', we are done
+ return;
+ }
+ // dialog callback?
+ if (dcb) {
+ dcb(token, key, code, event);
+ // assume dialog handled the event
+ return;
+ }
+ // otherwise, let the view callback have a shot
+ if (vcb) {
+ this.log.debug('Letting view callback have a shot', vcb, token, key, code, event );
+ vcb(token, key, code, event);
+ }
+ if (kl) {
+ kl(key);
+ }
+ }
+ }
+
+ // functions to obtain localized strings deferred from the setup of the
+ // global key data structures.
+ protected qhlion() {
+ return this.ls.bundle('core.fw.QuickHelp');
+ }
+ protected qhlionShowHide() {
+ return this.qhlion()('qh_hint_show_hide_qh');
+ }
+
+ protected qhlionHintEsc() {
+ return this.qhlion()('qh_hint_esc');
+ }
+
+ protected qhlionHintT() {
+ return this.qhlion()('qh_hint_t');
+ }
+
+ protected setupGlobalKeys() {
+ Object.assign(this.keyHandler, {
+ globalKeys: {
+ backSlash: [(view, key, code, ev) => this.quickHelp(view, key, code, ev), this.qhlionShowHide],
+ slash: [(view, key, code, ev) => this.quickHelp(view, key, code, ev), this.qhlionShowHide],
+ esc: [(view, key, code, ev) => this.escapeKey(view, key, code, ev), this.qhlionHintEsc],
+ T: [(view, key, code, ev) => this.toggleTheme(view, key, code, ev), this.qhlionHintT],
+ },
+ globalFormat: ['backSlash', 'slash', 'esc', 'T'],
+
+ // Masked keys are global key handlers that always return true.
+ // That is, the view will never see the event for that key.
+ maskedKeys: {
+ slash: 1,
+ backSlash: 1,
+ T: 1,
+ },
+ });
+ }
+
+ protected quickHelp(view, key, code, ev) {
+ if (!this.globalEnabled) {
+ return false;
+ }
+ this.qhs.showQuickHelp(this.keyHandler);
+ return true;
+ }
+
+ // returns true if we 'consumed' the ESC keypress, false otherwise
+ protected escapeKey(view, key, code, ev) {
+ return this.ns.hideNav() || this.qhs.hideQuickHelp();
+ }
+
+ protected toggleTheme(view, key, code, ev) {
+ if (!this.globalEnabled) {
+ return false;
+ }
+ // ts.toggleTheme();
+ return true;
+ }
+
+ protected filterMaskedKeys(map: any, caller: any, remove: boolean): any[] {
+ const masked = [];
+ const msgs = [];
+
+ d3.map(map).keys().forEach((key) => {
+ if (this.keyHandler.maskedKeys[key]) {
+ masked.push(key);
+ msgs.push(caller, ': Key "' + key + '" is reserved');
+ }
+ });
+
+ if (msgs.length) {
+ this.log.warn(msgs.join('\n'));
+ }
+
+ if (remove) {
+ masked.forEach((k) => {
+ delete map[k];
+ });
+ }
+ return masked;
+ }
+
+ protected unexParam(fname, x) {
+ this.log.warn(fname, ': unexpected parameter-- ', x);
+ }
+
+ protected setKeyBindings(keyArg) {
+ const fname = 'setKeyBindings()';
+ const kFunc = this.fs.isF(keyArg);
+ const kMap = this.fs.isO(keyArg);
+
+ if (kFunc) {
+ // set general key handler callback
+ this.keyHandler.viewFn = kFunc;
+ } else if (kMap) {
+ this.filterMaskedKeys(kMap, fname, true);
+ this.keyHandler.viewKeys = kMap;
+ } else {
+ this.unexParam(fname, keyArg);
+ }
+ }
+
+ getKeyBindings() {
+ const gkeys = d3.map(this.keyHandler.globalKeys).keys();
+ const masked = d3.map(this.keyHandler.maskedKeys).keys();
+ const vkeys = d3.map(this.keyHandler.viewKeys).keys();
+ const vfn = !!this.fs.isF(this.keyHandler.viewFn);
+
+ return {
+ globalKeys: gkeys,
+ maskedKeys: masked,
+ viewKeys: vkeys,
+ viewFunction: vfn,
+ };
+ }
+
+ protected bindDialogKeys(map) {
+ const fname = 'bindDialogKeys()';
+ const kMap = this.fs.isO(map);
+
+ if (kMap) {
+ this.filterMaskedKeys(map, fname, true);
+ this.keyHandler.dialogKeys = kMap;
+ } else {
+ this.unexParam(fname, map);
+ }
+ }
+
+ protected unbindDialogKeys() {
+ this.keyHandler.dialogKeys = {};
+ }
+
+}
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/util/lion.service.spec.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/util/lion.service.spec.ts
new file mode 100644
index 0000000..b0c252c
--- /dev/null
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/util/lion.service.spec.ts
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import { TestBed, inject } from '@angular/core/testing';
+import { of } from 'rxjs';
+
+import { LogService } from '../log.service';
+import { ConsoleLoggerService } from '../consolelogger.service';
+import { ActivatedRoute, Params } from '@angular/router';
+import { FnService } from '../util/fn.service';
+import { GlyphService } from '../svg/glyph.service';
+import { LionService } from './lion.service';
+import { UrlFnService } from '../remote/urlfn.service';
+import { WSock } from '../remote/wsock.service';
+import { WebSocketService, WsOptions } from '../remote/websocket.service';
+
+class MockActivatedRoute extends ActivatedRoute {
+ constructor(params: Params) {
+ super();
+ this.queryParams = of(params);
+ }
+}
+
+class MockWSock {}
+
+class MockGlyphService {}
+
+class MockUrlFnService {}
+
+/**
+ * ONOS GUI -- Lion -- Localization Utilities - Unit Tests
+ */
+describe('LionService', () => {
+ let log: LogService;
+ let fs: FnService;
+ let ar: MockActivatedRoute;
+ let windowMock: Window;
+
+ beforeEach(() => {
+ log = new ConsoleLoggerService();
+ ar = new MockActivatedRoute({'debug': 'TestService'});
+ windowMock = <any>{
+ location: <any> {
+ hostname: '',
+ host: '',
+ port: '',
+ protocol: '',
+ search: { debug: 'true'},
+ href: ''
+ }
+ };
+ fs = new FnService(ar, log, windowMock);
+
+ TestBed.configureTestingModule({
+ providers: [LionService,
+ { provide: FnService, useValue: fs },
+ { provide: GlyphService, useClass: MockGlyphService },
+ { provide: LogService, useValue: log },
+ { provide: UrlFnService, useClass: MockUrlFnService },
+ { provide: WSock, useClass: MockWSock },
+ { provide: WebSocketService, useClass: WebSocketService },
+ { provide: 'Window', useFactory: (() => windowMock ) },
+ ]
+ });
+ });
+
+ it('should be created', inject([LionService], (service: LionService) => {
+ expect(service).toBeTruthy();
+ }));
+});
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/util/prefs.service.spec.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/util/prefs.service.spec.ts
new file mode 100644
index 0000000..d925ecf
--- /dev/null
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/util/prefs.service.spec.ts
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { TestBed, inject } from '@angular/core/testing';
+
+import { LogService } from '../log.service';
+import { ConsoleLoggerService } from '../consolelogger.service';
+import { PrefsService } from '../util/prefs.service';
+import { FnService } from '../util/fn.service';
+import { WebSocketService } from '../remote/websocket.service';
+
+class MockFnService {}
+
+class MockWebSocketService {
+ createWebSocket() {}
+ isConnected() { return false; }
+ unbindHandlers() {}
+ bindHandlers() {}
+}
+
+/**
+ * ONOS GUI -- Util -- User Preference Service - Unit Tests
+ */
+describe('PrefsService', () => {
+ let log: LogService;
+
+ beforeEach(() => {
+ log = new ConsoleLoggerService();
+
+ TestBed.configureTestingModule({
+ providers: [PrefsService,
+ { provide: LogService, useValue: log },
+ { provide: FnService, useClass: MockFnService },
+ { provide: WebSocketService, useClass: MockWebSocketService },
+ ]
+ });
+ });
+
+ it('should be created', inject([PrefsService], (service: PrefsService) => {
+ expect(service).toBeTruthy();
+ }));
+});
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/util/theme.service.spec.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/util/theme.service.spec.ts
new file mode 100644
index 0000000..16d38e3
--- /dev/null
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/util/theme.service.spec.ts
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { TestBed, inject } from '@angular/core/testing';
+
+import { LogService } from '../log.service';
+import { ConsoleLoggerService } from '../consolelogger.service';
+import { ThemeService } from './theme.service';
+
+/**
+ * ONOS GUI -- Util -- Theme Service - Unit Tests
+ */
+describe('ThemeService', () => {
+ let log: LogService;
+
+ beforeEach(() => {
+ log = new ConsoleLoggerService();
+
+ TestBed.configureTestingModule({
+ providers: [ThemeService,
+ { provide: LogService, useValue: log },
+ ]
+ });
+ });
+
+ it('should be created', inject([ThemeService], (service: ThemeService) => {
+ expect(service).toBeTruthy();
+ }));
+});
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/util/trie.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/util/trie.ts
new file mode 100644
index 0000000..5e08061
--- /dev/null
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/util/trie.ts
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export interface TrieC {
+ p: any;
+ s: string[];
+}
+
+export interface TrieT {
+ k: any;
+ p: any;
+ q: any;
+}
+
+export enum TrieRemoved {
+ REMOVED = 'removed',
+ ABSENT = 'absent'
+}
+
+export enum TrieInsert {
+ ADDED = 'added',
+ UPDATED = 'updated'
+}
+
+/**
+ * Combine TrieRemoved and TrieInsert in to a union type
+ */
+export type TrieActions = TrieRemoved | TrieInsert;
+
+export enum TrieOp {
+ PLUS = '+',
+ MINUS = '-'
+}
+
+
+export class Trie {
+ p: any;
+ w: string;
+ s: string[];
+ c: TrieC;
+ t: TrieT[];
+ x: number;
+ f1: (TrieC) => TrieC;
+ f2: () => TrieActions;
+ data: any;
+
+
+ constructor(
+ op: TrieOp,
+ trie: any,
+ word: string,
+ data?: any
+ ) {
+ this.p = trie;
+ this.w = word.toUpperCase();
+ this.s = this.w.split('');
+ this.c = { p: this.p, s: this.s },
+ this.t = [];
+ this.x = 0;
+ this.f1 = op === TrieOp.PLUS ? this.add : this.probe;
+ this.f2 = op === TrieOp.PLUS ? this.insert : this.remove;
+ this.data = data;
+ while (this.c.s.length) {
+ this.c = this.f1(this.c);
+ }
+ }
+
+ add(cAdded: TrieC): TrieC {
+ const q = cAdded.s.shift();
+ let np = cAdded.p[q];
+
+ if (!np) {
+ cAdded.p[q] = {};
+ np = cAdded.p[q];
+ this.x = 1;
+ }
+ return { p: np, s: cAdded.s };
+ }
+
+ probe(cProbed: TrieC): TrieC {
+ const q = cProbed.s.shift();
+ const k: number = Object.keys(cProbed.p).length;
+ const np = cProbed.p[q];
+
+ this.t.push({ q: q, k: k, p: cProbed.p });
+ if (!np) {
+ this.t = [];
+ return { p: [], s: [] };
+ }
+ return { p: np, s: cProbed.s };
+ }
+
+ insert(): TrieInsert {
+ this.c.p._data = this.data;
+ return this.x ? TrieInsert.ADDED : TrieInsert.UPDATED;
+ }
+
+ remove(): TrieRemoved {
+ if (this.t.length) {
+ this.t = this.t.reverse();
+ while (this.t.length) {
+ const d = this.t.shift();
+ delete d.p[d.q];
+ if (d.k > 1) {
+ this.t = [];
+ }
+ }
+ return TrieRemoved.REMOVED;
+ }
+ return TrieRemoved.ABSENT;
+ }
+}
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/widget/detailspanel.base.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/widget/detailspanel.base.ts
index 11dc8c9..03c681b 100644
--- a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/widget/detailspanel.base.ts
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/widget/detailspanel.base.ts
@@ -55,7 +55,7 @@
protected wss: WebSocketService,
protected tag: string,
) {
- super(fs, ls, log, wss, {});
+ super(fs, ls, log);
this.root = tag + 's';
this.req = tag + 'DetailsRequest';
this.resp = tag + 'DetailsResponse';
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/widget/panel.base.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/widget/panel.base.ts
index 90cdfd5..0377c47 100644
--- a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/widget/panel.base.ts
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/widget/panel.base.ts
@@ -16,41 +16,6 @@
import { FnService } from '../util/fn.service';
import { LoadingService } from '../layer/loading.service';
import { LogService } from '../log.service';
-import { WebSocketService } from '../remote/websocket.service';
-
-
-const noop = (): any => undefined;
-
-/**
- ********* Static functions *********
- */
-function margin(p) {
- return p.settings.margin;
-}
-
-function hideMargin(p) {
- return p.settings.hideMargin;
-}
-
-function noPx(p, what) {
- return Number(p.el.style(what).replace(/px$/, ''));
-}
-
-function widthVal(p) {
- return noPx(p, 'width');
-}
-
-function heightVal(p) {
- return noPx(p, 'height');
-}
-
-function pxShow(p) {
- return margin(p) + 'px';
-}
-
-function pxHide(p) {
- return (-hideMargin(p) - widthVal(p) - (noPx(p, 'padding') * 2)) + 'px';
-}
/**
@@ -60,14 +25,7 @@
showPanel(cb: any): void;
hidePanel(cb: any): void;
togglePanel(cb: any): void;
- emptyPanel(): void;
- appendPanel(what: any): void;
- panelWidth(w: number): number;
- panelHeight(h: number): number;
- panelBBox(): string;
panelIsVisible(): boolean;
- classed(cls: any, bool: boolean): boolean;
- panelEl(): any;
}
/**
@@ -77,36 +35,22 @@
*/
export abstract class PanelBaseImpl implements PanelBase {
- protected on: boolean;
- protected el: any;
+ on: boolean;
constructor(
protected fs: FnService,
protected ls: LoadingService,
protected log: LogService,
- protected wss: WebSocketService,
- protected settings: any
) {
// this.log.debug('Panel base class constructed');
}
showPanel(cb) {
- const endCb = this.fs.isF(cb) || noop;
this.on = true;
- this.el.transition().duration(this.settings.xtnTime)
- .each('end', endCb)
- .style(this.settings.edge, pxShow(this))
- .style('opacity', 1);
}
hidePanel(cb) {
- const endCb = this.fs.isF(cb) || noop;
- const endOpacity = this.settings.fade ? 0 : 1;
this.on = false;
- this.el.transition().duration(this.settings.xtnTime)
- .each('end', endCb)
- .style(this.settings.edge, pxHide(this))
- .style('opacity', endOpacity);
}
togglePanel(cb): boolean {
@@ -118,45 +62,10 @@
return this.on;
}
- emptyPanel(): string {
- return this.el.text('');
- }
-
- appendPanel(what) {
- return this.el.append(what);
- }
-
- panelWidth(w: number): number {
- if (w === undefined) {
- return widthVal(this);
- }
- this.el.style('width', w + 'px');
- }
-
- panelHeight(h: number): number {
- if (h === undefined) {
- return heightVal(this);
- }
- this.el.style('height', h + 'px');
- }
-
- panelBBox(): string {
- return this.el.node().getBoundingClientRect();
- }
-
panelIsVisible(): boolean {
return this.on;
}
- classed(cls, bool): boolean {
- return this.el.classed(cls, bool);
- }
-
- panelEl() {
- return this.el;
- }
-
-
/**
* A dummy implementation of the lionFn until the response is received and the LION
* bundle is received from the WebSocket
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/widget/tableresize.directive.spec.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/widget/tableresize.directive.spec.ts
new file mode 100644
index 0000000..b1e87e6
--- /dev/null
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/widget/tableresize.directive.spec.ts
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { TestBed, inject } from '@angular/core/testing';
+import { ActivatedRoute, Params } from '@angular/router';
+import { of } from 'rxjs';
+import { TableResizeDirective } from './tableresize.directive';
+import { LogService } from '..//log.service';
+import { ConsoleLoggerService } from '../consolelogger.service';
+import { MastService } from '../mast/mast.service';
+import { FnService } from '../util/fn.service';
+
+class MockMastService {}
+
+class MockFnService extends FnService {
+ constructor(ar: ActivatedRoute, log: LogService, w: Window) {
+ super(ar, log, w);
+ }
+}
+
+class MockActivatedRoute extends ActivatedRoute {
+ constructor(params: Params) {
+ super();
+ this.queryParams = of(params);
+ }
+}
+
+/**
+ * ONOS GUI -- Widget -- Table Resize Directive - Unit Tests
+ */
+describe('TableResizeDirective', () => {
+ let log: LogService;
+ let mockWindow: Window;
+ let ar: ActivatedRoute;
+
+ beforeEach(() => {
+ log = new ConsoleLoggerService();
+ ar = new MockActivatedRoute(['debug', 'DetectBrowserDirective']);
+ mockWindow = <any>{
+ navigator: {
+ userAgent: 'HeadlessChrome',
+ vendor: 'Google Inc.'
+ }
+ };
+ TestBed.configureTestingModule({
+ providers: [ TableResizeDirective,
+ { provide: FnService, useValue: new MockFnService(ar, log, mockWindow) },
+ { provide: LogService, useValue: log },
+ { provide: MastService, useClass: MockMastService },
+ { provide: 'Window', useFactory: (() => mockWindow ) },
+ ]
+ });
+ });
+
+ afterEach(() => {
+ log = null;
+ });
+
+ it('should create an instance', inject([TableResizeDirective], (directive: TableResizeDirective) => {
+ expect(directive).toBeTruthy();
+ }));
+});
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/widget/tableresize.directive.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/widget/tableresize.directive.ts
index 0678583..5d3a9eb 100644
--- a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/widget/tableresize.directive.ts
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/widget/tableresize.directive.ts
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { AfterContentChecked, Directive, ElementRef, Inject } from '@angular/core';
+import { AfterContentChecked, Directive, Inject } from '@angular/core';
import { FnService } from '../util/fn.service';
import { LogService } from '../log.service';
import { MastService } from '../mast/mast.service';
@@ -34,7 +34,6 @@
constructor(protected fs: FnService,
protected log: LogService,
protected mast: MastService,
- protected el: ElementRef,
@Inject('Window') private w: any) {
log.info('TableResizeDirective constructed');
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/public_api.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/public_api.ts
index 51cf201..5f162d5 100644
--- a/web/gui2-fw-lib/projects/gui2-fw-lib/src/public_api.ts
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/public_api.ts
@@ -36,10 +36,14 @@
export * from './lib/svg/svgutil.service';
export * from './lib/svg/glyphdata.service';
export * from './lib/svg/glyph.service';
+export * from './lib/svg/zoom.service';
+
export * from './lib/util/prefs.service';
export * from './lib/util/fn.service';
export * from './lib/util/lion.service';
export * from './lib/util/theme.service';
+export * from './lib/util/keys.service';
+export * from './lib/util/trie';
export * from './lib/mast/mast/mast.component';
export * from './lib/layer/veil/veil.component';