Implemented WebSockets for GUI2
Change-Id: I4776ce392b1e8e94ebee938cf7df22791a1e0b8f
diff --git a/web/gui2/src/main/webapp/tests/app/detectbrowser.directive.spec.ts b/web/gui2/src/main/webapp/tests/app/detectbrowser.directive.spec.ts
index 06715cd..9183536 100644
--- a/web/gui2/src/main/webapp/tests/app/detectbrowser.directive.spec.ts
+++ b/web/gui2/src/main/webapp/tests/app/detectbrowser.directive.spec.ts
@@ -24,8 +24,8 @@
import { of } from 'rxjs';
class MockFnService extends FnService {
- constructor(ar: ActivatedRoute, log: LogService) {
- super(ar, log);
+ constructor(ar: ActivatedRoute, log: LogService, w: Window) {
+ super(ar, log, w);
}
}
@@ -44,17 +44,25 @@
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) },
+ { provide: FnService, useValue: new MockFnService(ar, log, mockWindow) },
{ provide: LogService, useValue: log },
{ provide: OnosService, useClass: MockOnosService },
{ provide: Document, useValue: document },
+ { provide: Window, useFactory: (() => mockWindow ) }
]
});
});
diff --git a/web/gui2/src/main/webapp/tests/app/fw/layer/veil.service.spec.ts b/web/gui2/src/main/webapp/tests/app/fw/layer/veil.service.spec.ts
deleted file mode 100644
index 3d5509b..0000000
--- a/web/gui2/src/main/webapp/tests/app/fw/layer/veil.service.spec.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2015-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 '../../../../app/log.service';
-import { ConsoleLoggerService } from '../../../../app/consolelogger.service';
-import { FnService } from '../../../../app/fw/util/fn.service';
-import { GlyphService } from '../../../../app/fw/svg/glyph.service';
-import { KeyService } from '../../../../app/fw/util/key.service';
-import { VeilService } from '../../../../app/fw/layer/veil.service';
-import { WebSocketService } from '../../../../app/fw/remote/websocket.service';
-
-class MockFnService {}
-
-class MockGlyphService {}
-
-class MockKeyService {}
-
-class MockWebSocketService {}
-
-/**
- * ONOS GUI -- Layer -- Veil Service - Unit Tests
- */
-describe('VeilService', () => {
- let log: LogService;
-
- beforeEach(() => {
- log = new ConsoleLoggerService();
-
- TestBed.configureTestingModule({
- providers: [VeilService,
- { provide: FnService, useClass: MockFnService },
- { provide: GlyphService, useClass: MockGlyphService },
- { provide: KeyService, useClass: MockKeyService },
- { provide: LogService, useValue: log },
- { provide: WebSocketService, useClass: MockWebSocketService },
- ]
- });
- });
-
- it('should be created', inject([VeilService], (service: VeilService) => {
- expect(service).toBeTruthy();
- }));
-});
diff --git a/web/gui2/src/main/webapp/tests/app/fw/remote/urlfn.service.spec.ts b/web/gui2/src/main/webapp/tests/app/fw/remote/urlfn.service.spec.ts
index a54458b..6b229bd 100644
--- a/web/gui2/src/main/webapp/tests/app/fw/remote/urlfn.service.spec.ts
+++ b/web/gui2/src/main/webapp/tests/app/fw/remote/urlfn.service.spec.ts
@@ -18,26 +18,106 @@
import { LogService } from '../../../../app/log.service';
import { ConsoleLoggerService } from '../../../../app/consolelogger.service';
import { UrlFnService } from '../../../../app/fw/remote/urlfn.service';
+import { FnService } from '../../../../app/fw/util/fn.service';
+import { ActivatedRoute, Params } from '@angular/router';
+import { of } from 'rxjs';
+
+class MockActivatedRoute extends ActivatedRoute {
+ constructor(params: Params) {
+ super();
+ this.queryParams = of(params);
+ }
+}
/**
* ONOS GUI -- Remote -- General Functions - Unit Tests
*/
describe('UrlFnService', () => {
let log: LogService;
- const windowMock = <any>{ location: <any> { hostname: 'localhost' } };
+ let ufs: UrlFnService;
+ 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: [UrlFnService,
{ provide: LogService, useValue: log },
- { provide: Window, useValue: windowMock },
+ { provide: Window, useFactory: (() => windowMock ) },
]
});
+
+ ufs = TestBed.get(UrlFnService);
});
- it('should be created', inject([UrlFnService], (service: UrlFnService) => {
- expect(service).toBeTruthy();
- }));
+ function setLoc(prot: string, h: string, p: string, ctx: string = '') {
+ windowMock.location.host = h;
+ windowMock.location.hostname = h;
+ windowMock.location.port = p;
+ windowMock.location.protocol = prot;
+ windowMock.location.href = prot + '://' + h + ':' + p +
+ ctx + '/onos/ui/';
+ }
+
+ it('should define UrlFnService', () => {
+ expect(ufs).toBeDefined();
+ });
+
+ it('should define api functions', () => {
+ expect(fs.areFunctions(ufs, [
+ 'rsUrl', 'wsUrl', 'urlBase', 'httpPrefix',
+ 'wsPrefix', 'matchSecure'
+ ])).toBeTruthy();
+ });
+
+ it('should return the correct (http) RS url', () => {
+ setLoc('http', 'foo', '123');
+ expect(ufs.rsUrl('path')).toEqual('http://foo:123/onos/ui/rs/path');
+ });
+
+ it('should return the correct (https) RS url', () => {
+ setLoc('https', 'foo', '123');
+ expect(ufs.rsUrl('path')).toEqual('https://foo:123/onos/ui/rs/path');
+ });
+
+ it('should return the correct (ws) WS url', () => {
+ setLoc('http', 'foo', '123');
+ expect(ufs.wsUrl('path')).toEqual('ws://foo:123/onos/ui/websock/path');
+ });
+
+ it('should return the correct (wss) WS url', () => {
+ setLoc('https', 'foo', '123');
+ expect(ufs.wsUrl('path')).toEqual('wss://foo:123/onos/ui/websock/path');
+ });
+
+ it('should allow us to define an alternate WS port', () => {
+ setLoc('http', 'foo', '123');
+ expect(ufs.wsUrl('xyyzy', '456')).toEqual('ws://foo:456/onos/ui/websock/xyyzy');
+ });
+
+ it('should allow us to define an alternate host', () => {
+ setLoc('http', 'foo', '123');
+ expect(ufs.wsUrl('core', '456', 'bar')).toEqual('ws://bar:456/onos/ui/websock/core');
+ });
+
+ it('should allow us to inject an app context', () => {
+ setLoc('http', 'foo', '123', '/my/app');
+ expect(ufs.wsUrl('path')).toEqual('ws://foo:123/my/app/onos/ui/websock/path');
+ });
+
});
diff --git a/web/gui2/src/main/webapp/tests/app/fw/remote/websocket.service.spec.ts b/web/gui2/src/main/webapp/tests/app/fw/remote/websocket.service.spec.ts
index d18dda4..5c8d6b7 100644
--- a/web/gui2/src/main/webapp/tests/app/fw/remote/websocket.service.spec.ts
+++ b/web/gui2/src/main/webapp/tests/app/fw/remote/websocket.service.spec.ts
@@ -16,44 +16,266 @@
import { TestBed, inject } from '@angular/core/testing';
import { LogService } from '../../../../app/log.service';
-import { ConsoleLoggerService } from '../../../../app/consolelogger.service';
-import { WebSocketService } from '../../../../app/fw/remote/websocket.service';
+import { WebSocketService, WsOptions, Callback, EventType } from '../../../../app/fw/remote/websocket.service';
import { FnService } from '../../../../app/fw/util/fn.service';
import { GlyphService } from '../../../../app/fw/svg/glyph.service';
+import { ActivatedRoute, Params } from '@angular/router';
import { UrlFnService } from '../../../../app/fw/remote/urlfn.service';
import { WSock } from '../../../../app/fw/remote/wsock.service';
+import { of } from 'rxjs';
-class MockFnService {}
+class MockActivatedRoute extends ActivatedRoute {
+ constructor(params: Params) {
+ super();
+ this.queryParams = of(params);
+ }
+}
class MockGlyphService {}
-class MockUrlFnService {}
-
class MockWSock {}
/**
* ONOS GUI -- Remote -- Web Socket Service - Unit Tests
*/
describe('WebSocketService', () => {
- let log: LogService;
- const windowMock = <any>{ location: <any> { hostname: 'localhost' } };
+ let wss: WebSocketService;
+ let fs: FnService;
+ let ar: MockActivatedRoute;
+ let windowMock: Window;
+ let logServiceSpy: jasmine.SpyObj<LogService>;
+
+ const noop = () => ({});
+ const send = jasmine.createSpy('send')
+ .and.callFake((ev) => ev);
+ const mockWebSocket = {
+ send: send,
+ onmessage: (msgEvent) => ({}),
+ onopen: () => ({}),
+ onclose: () => ({}),
+ };
beforeEach(() => {
- log = new ConsoleLoggerService();
+ const logSpy = jasmine.createSpyObj('LogService', ['info', 'debug', 'warn', 'error']);
+ ar = new MockActivatedRoute({'debug': 'txrx'});
+
+ windowMock = <any>{
+ 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, windowMock);
TestBed.configureTestingModule({
providers: [WebSocketService,
- { provide: FnService, useClass: MockFnService },
- { provide: LogService, useValue: log },
+ { provide: FnService, useValue: fs },
+ { provide: LogService, useValue: logSpy },
{ provide: GlyphService, useClass: MockGlyphService },
- { provide: UrlFnService, useClass: MockUrlFnService },
- { provide: WSock, useClass: MockWSock },
- { provide: Window, useValue: windowMock },
+ { provide: UrlFnService, useValue: new UrlFnService(logSpy, windowMock) },
+ { provide: Window, useFactory: (() => windowMock ) },
+ { provide: WSock, useFactory: (() => {
+ return {
+ newWebSocket: (() => mockWebSocket)
+ };
+ })
+ }
]
});
+
+ wss = TestBed.get(WebSocketService);
+ logServiceSpy = TestBed.get(LogService);
});
- it('should be created', inject([WebSocketService], (service: WebSocketService) => {
- expect(service).toBeTruthy();
- }));
+ it('should define WebSocketService', () => {
+ expect(wss).toBeDefined();
+ });
+
+ it('should define api functions', () => {
+ expect(fs.areFunctions(wss, ['bootstrap', 'error',
+ 'handleOpen', 'handleMessage', 'handleClose',
+ 'findGuiSuccessor', 'informListeners', 'send',
+ 'noHandlersWarn', 'resetState',
+ 'createWebSocket', 'bindHandlers', 'unbindHandlers',
+ 'addOpenListener', 'removeOpenListener', 'sendEvent',
+ 'setVeilDelegate', 'setLoadingDelegate'
+ ])).toBeTruthy();
+ });
+
+ it('should use the appropriate URL, createWebsocket', () => {
+ const url = wss.createWebSocket();
+ expect(url).toEqual('ws://foo:80/onos/ui/websock/core');
+ });
+
+ it('should use the appropriate URL with modified port, createWebsocket',
+ () => {
+ const url = wss.createWebSocket(<WsOptions>{ wsport: 1243 });
+ expect(url).toEqual('ws://foo:1243/onos/ui/websock/core');
+ });
+
+ it('should verify websocket event handlers, createWebsocket', () => {
+ wss.createWebSocket({ wsport: 1234 });
+ expect(fs.isF(mockWebSocket.onopen)).toBeTruthy();
+ expect(fs.isF(mockWebSocket.onmessage)).toBeTruthy();
+ expect(fs.isF(mockWebSocket.onclose)).toBeTruthy();
+ });
+
+ it('should invoke listener callbacks when websocket is up, handleOpen',
+ () => {
+ let num = 0;
+ function incrementNum(host: string, url: string) {
+ expect(host).toEqual('foo');
+ num++;
+ }
+ wss.addOpenListener(incrementNum);
+ wss.createWebSocket({ wsport: 1234 });
+
+ mockWebSocket.onopen();
+ expect(num).toBe(1);
+ });
+
+ it('should send pending events, handleOpen', () => {
+ const fakeEvent = {
+ event: 'mockEv',
+ payload: { mock: 'thing' }
+ };
+ wss.sendEvent(fakeEvent.event, fakeEvent.payload);
+ // on opening the socket, a single authentication event should have
+ // been sent already...
+ expect(mockWebSocket.send.calls.count()).toEqual(1);
+
+ wss.createWebSocket({ wsport: 1234 });
+ mockWebSocket.onopen();
+ expect(mockWebSocket.send).toHaveBeenCalledWith(JSON.stringify(fakeEvent));
+ });
+
+ it('should handle an incoming bad JSON message, handleMessage', () => {
+ const badMsg = {
+ data: 'bad message'
+ };
+ wss.createWebSocket({ wsport: 1234 });
+ expect(mockWebSocket.onmessage(badMsg)).toBeNull();
+ expect(logServiceSpy.error).toHaveBeenCalled();
+ });
+
+ it('should verify message was handled, handleMessage', () => {
+ let num = 0;
+ function fakeHandler(data1: Object) { num++; }
+ const data = JSON.stringify(<EventType>{
+ event: 'mockEvResp',
+ payload: {}
+ });
+ const event = {
+ data: data
+ };
+
+ wss.createWebSocket({ wsport: 1234 });
+ wss.bindHandlers(new Map<string, (data) => void>([
+ ['mockEvResp', (data2) => fakeHandler(data2)]
+ ]));
+ expect(mockWebSocket.onmessage(event)).toBe(undefined);
+ expect(num).toBe(1);
+ });
+
+ it('should warn if there is an unhandled event, handleMessage', () => {
+ const data = { foo: 'bar', bar: 'baz'};
+ const dataString = JSON.stringify(data);
+ const badEv = {
+ data: dataString
+ };
+ wss.createWebSocket({ wsport: 1234 });
+ mockWebSocket.onmessage(badEv);
+ expect(logServiceSpy.warn).toHaveBeenCalledWith('Unhandled event:', data);
+ });
+
+ it('should not warn if valid input, bindHandlers', () => {
+ expect(wss.bindHandlers(new Map<string, (data) => void>([
+ ['test', noop ],
+ ['bar', noop ]
+ ]))).toBe(undefined);
+
+ expect(logServiceSpy.warn).not.toHaveBeenCalled();
+ });
+
+ it('should warn if no arguments, bindHandlers', () => {
+ expect(wss.bindHandlers(
+ new Map<string, (data) => void>([])
+ )).toBeNull();
+ expect(logServiceSpy.warn).toHaveBeenCalledWith(
+ 'WSS.bindHandlers(): no event handlers'
+ );
+ });
+
+ it('should warn if duplicate handlers were given, bindHandlers',
+ () => {
+ wss.bindHandlers(
+ new Map<string, (data) => void>([
+ ['noop', noop ]
+ ])
+ );
+ expect(wss.bindHandlers(
+ new Map<string, (data) => void>([
+ ['noop', noop ]
+ ])
+ )).toBe(undefined);
+ expect(logServiceSpy.warn).toHaveBeenCalledWith('duplicate bindings ignored:',
+ ['noop']);
+ });
+
+ it('should warn if no arguments, unbindHandlers', () => {
+ expect(wss.unbindHandlers(
+ new Map<string, (data) => void>([])
+ )).toBeNull();
+ expect(logServiceSpy.warn).toHaveBeenCalledWith(
+ 'WSS.unbindHandlers(): no event handlers'
+ );
+ });
+ // Note: cannot test unbindHandlers' forEach due to it using closure variable
+
+ it('should not warn if valid argument, addOpenListener', () => {
+ let o = wss.addOpenListener(noop);
+ expect(o.id === 1);
+ expect(o.cb === noop);
+ expect(logServiceSpy.warn).not.toHaveBeenCalled();
+ o = wss.addOpenListener(noop);
+ expect(o.id === 2);
+ expect(o.cb === noop);
+ expect(logServiceSpy.warn).not.toHaveBeenCalled();
+ });
+
+ it('should log error if callback not a function, addOpenListener',
+ () => {
+ const o = wss.addOpenListener(null);
+ expect(o.id === 1);
+ expect(o.cb === null);
+ expect(o.error === 'No callback defined');
+ expect(logServiceSpy.error).toHaveBeenCalledWith(
+ 'WSS.addOpenListener(): callback not a function'
+ );
+ });
+
+ it('should not warn if valid listener object, removeOpenListener', () => {
+ expect(wss.removeOpenListener(<Callback>{
+ id: 1,
+ error: 'error',
+ cb: noop
+ })).toBe(undefined);
+ expect(logServiceSpy.warn).not.toHaveBeenCalled();
+ });
+
+ it('should warn if listener is invalid, removeOpenListener', () => {
+ expect(wss.removeOpenListener(<Callback>{})).toBeNull();
+ expect(logServiceSpy.warn).toHaveBeenCalledWith(
+ 'WSS.removeOpenListener(): invalid listener', {}
+ );
+ });
+
+ // Note: handleClose is not currently tested due to all work it does relies
+ // on closure variables that cannot be mocked
+
});
diff --git a/web/gui2/src/main/webapp/tests/app/fw/util/fn.service.spec.ts b/web/gui2/src/main/webapp/tests/app/fw/util/fn.service.spec.ts
index ff6ceaf..84b5f094 100644
--- a/web/gui2/src/main/webapp/tests/app/fw/util/fn.service.spec.ts
+++ b/web/gui2/src/main/webapp/tests/app/fw/util/fn.service.spec.ts
@@ -20,6 +20,7 @@
import { FnService } from '../../../../app/fw/util/fn.service';
import { ActivatedRoute, Params } from '@angular/router';
import { of } from 'rxjs';
+import * as d3 from 'd3';
class MockActivatedRoute extends ActivatedRoute {
constructor(params: Params) {
@@ -32,22 +33,460 @@
* ONOS GUI -- Util -- General Purpose Functions - Unit Tests
*/
describe('FnService', () => {
- let log: LogService;
let ar: ActivatedRoute;
+ let fs: FnService;
+ let mockWindow: Window;
+ let logServiceSpy: jasmine.SpyObj<LogService>;
+
+ const someFunction = () => {};
+ const someArray = [1, 2, 3];
+ const someObject = { foo: 'bar'};
+ const someNumber = 42;
+ const someString = 'xyyzy';
+ const someDate = new Date();
+ const stringArray = ['foo', 'bar'];
beforeEach(() => {
- log = new ConsoleLoggerService();
+ const logSpy = jasmine.createSpyObj('LogService', ['debug', 'warn']);
ar = new MockActivatedRoute({'debug': 'TestService'});
+ mockWindow = <any>{
+ innerWidth: 400,
+ innerHeight: 200,
+ navigator: {
+ userAgent: 'defaultUA'
+ }
+ };
+
TestBed.configureTestingModule({
providers: [FnService,
- { provide: LogService, useValue: log },
+ { provide: LogService, useValue: logSpy },
{ provide: ActivatedRoute, useValue: ar },
+ { provide: Window, useFactory: (() => mockWindow ) }
]
});
+
+ fs = TestBed.get(FnService);
+ logServiceSpy = TestBed.get(LogService);
});
- it('should be created', inject([FnService], (service: FnService) => {
- expect(service).toBeTruthy();
- }));
+ it('should be created', () => {
+ expect(fs).toBeTruthy();
+ });
+
+ // === Tests for isF()
+ it('isF(): null for undefined', () => {
+ expect(fs.isF(undefined)).toBeNull();
+ });
+
+ it('isF(): null for null', () => {
+ expect(fs.isF(null)).toBeNull();
+ });
+ it('isF(): the reference for function', () => {
+ expect(fs.isF(someFunction)).toBe(someFunction);
+ });
+ it('isF(): null for string', () => {
+ expect(fs.isF(someString)).toBeNull();
+ });
+ it('isF(): null for number', () => {
+ expect(fs.isF(someNumber)).toBeNull();
+ });
+ it('isF(): null for Date', () => {
+ expect(fs.isF(someDate)).toBeNull();
+ });
+ it('isF(): null for array', () => {
+ expect(fs.isF(someArray)).toBeNull();
+ });
+ it('isF(): null for object', () => {
+ expect(fs.isF(someObject)).toBeNull();
+ });
+
+ // === Tests for isA()
+ it('isA(): null for undefined', () => {
+ expect(fs.isA(undefined)).toBeNull();
+ });
+ it('isA(): null for null', () => {
+ expect(fs.isA(null)).toBeNull();
+ });
+ it('isA(): null for function', () => {
+ expect(fs.isA(someFunction)).toBeNull();
+ });
+ it('isA(): null for string', () => {
+ expect(fs.isA(someString)).toBeNull();
+ });
+ it('isA(): null for number', () => {
+ expect(fs.isA(someNumber)).toBeNull();
+ });
+ it('isA(): null for Date', () => {
+ expect(fs.isA(someDate)).toBeNull();
+ });
+ it('isA(): the reference for array', () => {
+ expect(fs.isA(someArray)).toBe(someArray);
+ });
+ it('isA(): null for object', () => {
+ expect(fs.isA(someObject)).toBeNull();
+ });
+
+ // === Tests for isS()
+ it('isS(): null for undefined', () => {
+ expect(fs.isS(undefined)).toBeNull();
+ });
+ it('isS(): null for null', () => {
+ expect(fs.isS(null)).toBeNull();
+ });
+ it('isS(): null for function', () => {
+ expect(fs.isS(someFunction)).toBeNull();
+ });
+ it('isS(): the reference for string', () => {
+ expect(fs.isS(someString)).toBe(someString);
+ });
+ it('isS(): null for number', () => {
+ expect(fs.isS(someNumber)).toBeNull();
+ });
+ it('isS(): null for Date', () => {
+ expect(fs.isS(someDate)).toBeNull();
+ });
+ it('isS(): null for array', () => {
+ expect(fs.isS(someArray)).toBeNull();
+ });
+ it('isS(): null for object', () => {
+ expect(fs.isS(someObject)).toBeNull();
+ });
+
+ // === Tests for isO()
+ it('isO(): null for undefined', () => {
+ expect(fs.isO(undefined)).toBeNull();
+ });
+ it('isO(): null for null', () => {
+ expect(fs.isO(null)).toBeNull();
+ });
+ it('isO(): null for function', () => {
+ expect(fs.isO(someFunction)).toBeNull();
+ });
+ it('isO(): null for string', () => {
+ expect(fs.isO(someString)).toBeNull();
+ });
+ it('isO(): null for number', () => {
+ expect(fs.isO(someNumber)).toBeNull();
+ });
+ it('isO(): null for Date', () => {
+ expect(fs.isO(someDate)).toBeNull();
+ });
+ it('isO(): null for array', () => {
+ expect(fs.isO(someArray)).toBeNull();
+ });
+ it('isO(): the reference for object', () => {
+ expect(fs.isO(someObject)).toBe(someObject);
+ });
+
+
+ // === Tests for contains()
+ it('contains(): false for non-array', () => {
+ expect(fs.contains(null, 1)).toBeFalsy();
+ });
+ it('contains(): true for contained item', () => {
+ expect(fs.contains(someArray, 1)).toBeTruthy();
+ expect(fs.contains(stringArray, 'bar')).toBeTruthy();
+ });
+ it('contains(): false for non-contained item', () => {
+ expect(fs.contains(someArray, 109)).toBeFalsy();
+ expect(fs.contains(stringArray, 'zonko')).toBeFalsy();
+ });
+
+ // === Tests for areFunctions()
+ it('areFunctions(): true for empty-array', () => {
+ expect(fs.areFunctions({}, [])).toBeTruthy();
+ });
+ it('areFunctions(): true for some api', () => {
+ expect(fs.areFunctions({
+ a: () => {},
+ b: () => {}
+ }, ['b', 'a'])).toBeTruthy();
+ });
+ it('areFunctions(): false for some other api', () => {
+ expect(fs.areFunctions({
+ a: () => {},
+ b: 'not-a-function'
+ }, ['b', 'a'])).toBeFalsy();
+ });
+ it('areFunctions(): extraneous stuff NOT ignored', () => {
+ expect(fs.areFunctions({
+ a: () => {},
+ b: () => {},
+ c: 1,
+ d: 'foo'
+ }, ['a', 'b'])).toBeFalsy();
+ });
+ it('areFunctions(): extraneous stuff ignored (alternate fn)', () => {
+ expect(fs.areFunctionsNonStrict({
+ a: () => {},
+ b: () => {},
+ c: 1,
+ d: 'foo'
+ }, ['a', 'b'])).toBeTruthy();
+ });
+
+ // == use the now-tested areFunctions() on our own api:
+ it('should define api functions', () => {
+ expect(fs.areFunctions(fs, [
+ 'isF', 'isA', 'isS', 'isO', 'contains',
+ 'areFunctions', 'areFunctionsNonStrict', 'windowSize',
+ 'isMobile', 'isChrome', 'isChromeHeadless', 'isSafari',
+ 'isFirefox', 'parseDebugFlags',
+ 'debugOn', 'debug', 'find', 'inArray', 'removeFromArray',
+ 'isEmptyObject', 'cap', 'noPx', 'noPxStyle', 'endsWith',
+ 'inEvilList', 'analyze', 'sanitize'
+// 'find', 'inArray', 'removeFromArray', 'isEmptyObject', 'sameObjProps', 'containsObj', 'cap',
+// 'eecode', 'noPx', 'noPxStyle', 'endsWith', 'addToTrie', 'removeFromTrie', 'trieLookup',
+// 'classNames', 'extend', 'sanitize'
+ ])).toBeTruthy();
+ });
+
+
+ // === Tests for windowSize()
+ it('windowSize(): adjust height', () => {
+ const dim = fs.windowSize(50);
+ expect(dim.width).toEqual(400);
+ expect(dim.height).toEqual(150);
+ });
+
+ it('windowSize(): adjust width', () => {
+ const dim = fs.windowSize(0, 50);
+ expect(dim.width).toEqual(350);
+ expect(dim.height).toEqual(200);
+ });
+
+ it('windowSize(): adjust width and height', () => {
+ const dim = fs.windowSize(101, 201);
+ expect(dim.width).toEqual(199);
+ expect(dim.height).toEqual(99);
+ });
+
+ // === Tests for isMobile()
+ const uaMap = {
+ chrome: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) ' +
+ 'AppleWebKit/537.36 (KHTML, like Gecko) ' +
+ 'Chrome/41.0.2272.89 Safari/537.36',
+
+ iPad: 'Mozilla/5.0 (iPad; CPU OS 7_0 like Mac OS X) ' +
+ 'AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 ' +
+ 'Mobile/11A465 Safari/9537.53',
+
+ iPhone: 'Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X) ' +
+ 'AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 ' +
+ 'Mobile/11A465 Safari/9537.53'
+ };
+
+ function setUa(key) {
+ const str = uaMap[key];
+ expect(str).toBeTruthy();
+ (<any>mockWindow.navigator).userAgent = str;
+ }
+
+ it('isMobile(): should be false for Chrome on Mac OS X', () => {
+ setUa('chrome');
+ expect(fs.isMobile()).toBe(false);
+ });
+ it('isMobile(): should be true for Safari on iPad', () => {
+ setUa('iPad');
+ expect(fs.isMobile()).toBe(true);
+ });
+ it('isMobile(): should be true for Safari on iPhone', () => {
+ setUa('iPhone');
+ expect(fs.isMobile()).toBe(true);
+ });
+
+ // === Tests for find()
+ const dataset = [
+ { id: 'foo', name: 'Furby'},
+ { id: 'bar', name: 'Barbi'},
+ { id: 'baz', name: 'Basil'},
+ { id: 'goo', name: 'Gabby'},
+ { id: 'zoo', name: 'Zevvv'}
+ ];
+
+ it('should not find ooo', () => {
+ expect(fs.find('ooo', dataset)).toEqual(-1);
+ });
+ it('should find foo', () => {
+ expect(fs.find('foo', dataset)).toEqual(0);
+ });
+ it('should find zoo', () => {
+ expect(fs.find('zoo', dataset)).toEqual(4);
+ });
+
+ it('should not find Simon', () => {
+ expect(fs.find('Simon', dataset, 'name')).toEqual(-1);
+ });
+ it('should find Furby', () => {
+ expect(fs.find('Furby', dataset, 'name')).toEqual(0);
+ });
+ it('should find Zevvv', () => {
+ expect(fs.find('Zevvv', dataset, 'name')).toEqual(4);
+ });
+
+
+ // === Tests for inArray()
+ const objRef = { x: 1, y: 2 };
+ const array = [1, 3.14, 'hey', objRef, 'there', true];
+ const array2 = ['b', 'a', 'd', 'a', 's', 's'];
+
+ it('should not find HOO', () => {
+ expect(fs.inArray('HOO', array)).toEqual(-1);
+ });
+ it('should find 1', () => {
+ expect(fs.inArray(1, array)).toEqual(0);
+ });
+ it('should find pi', () => {
+ expect(fs.inArray(3.14, array)).toEqual(1);
+ });
+ it('should find hey', () => {
+ expect(fs.inArray('hey', array)).toEqual(2);
+ });
+ it('should find the object', () => {
+ expect(fs.inArray(objRef, array)).toEqual(3);
+ });
+ it('should find there', () => {
+ expect(fs.inArray('there', array)).toEqual(4);
+ });
+ it('should find true', () => {
+ expect(fs.inArray(true, array)).toEqual(5);
+ });
+
+ it('should find the first occurrence A', () => {
+ expect(fs.inArray('a', array2)).toEqual(1);
+ });
+ it('should find the first occurrence S', () => {
+ expect(fs.inArray('s', array2)).toEqual(4);
+ });
+ it('should not find X', () => {
+ expect(fs.inArray('x', array2)).toEqual(-1);
+ });
+
+ // === Tests for removeFromArray()
+ it('should keep the array the same, for non-match', () => {
+ const array1 = [1, 2, 3];
+ expect(fs.removeFromArray(4, array1)).toBe(false);
+ expect(array1).toEqual([1, 2, 3]);
+ });
+ it('should remove a value', () => {
+ const array1a = [1, 2, 3];
+ expect(fs.removeFromArray(2, array1a)).toBe(true);
+ expect(array1a).toEqual([1, 3]);
+ });
+ it('should remove the first occurrence', () => {
+ const array1b = ['x', 'y', 'z', 'z', 'y'];
+ expect(fs.removeFromArray('y', array1b)).toBe(true);
+ expect(array1b).toEqual(['x', 'z', 'z', 'y']);
+ expect(fs.removeFromArray('x', array1b)).toBe(true);
+ expect(array1b).toEqual(['z', 'z', 'y']);
+ });
+
+ // === Tests for isEmptyObject()
+ it('should return true if an object is empty', () => {
+ expect(fs.isEmptyObject({})).toBe(true);
+ });
+ it('should return false if an object is not empty', () => {
+ expect(fs.isEmptyObject({foo: 'bar'})).toBe(false);
+ });
+
+ // === Tests for cap()
+ it('should ignore non-alpha', () => {
+ expect(fs.cap('123')).toEqual('123');
+ });
+ it('should capitalize first char', () => {
+ expect(fs.cap('Foo')).toEqual('Foo');
+ expect(fs.cap('foo')).toEqual('Foo');
+ expect(fs.cap('foo bar')).toEqual('Foo bar');
+ expect(fs.cap('FOO BAR')).toEqual('Foo bar');
+ expect(fs.cap('foo Bar')).toEqual('Foo bar');
+ });
+
+ // === Tests for noPx()
+ it('should return the value without px suffix', () => {
+ expect(fs.noPx('10px')).toBe(10);
+ expect(fs.noPx('500px')).toBe(500);
+ expect(fs.noPx('-80px')).toBe(-80);
+ });
+
+ // === Tests for noPxStyle()
+ it('should give a style\'s property without px suffix', () => {
+ const d3Elem = d3.select('body')
+ .append('div')
+ .attr('id', 'fooElem')
+ .style('width', '500px')
+ .style('height', '200px')
+ .style('font-size', '12px');
+ expect(fs.noPxStyle(d3Elem, 'width')).toBe(500);
+ expect(fs.noPxStyle(d3Elem, 'height')).toBe(200);
+ expect(fs.noPxStyle(d3Elem, 'font-size')).toBe(12);
+ d3.select('#fooElem').remove();
+ });
+
+ // === Tests for endsWith()
+ it('should return true if string ends with foo', () => {
+ expect(fs.endsWith('barfoo', 'foo')).toBe(true);
+ });
+
+ it('should return false if string doesnt end with foo', () => {
+ expect(fs.endsWith('barfood', 'foo')).toBe(false);
+ });
+
+ // === Tests for sanitize()
+ it('should return foo', () => {
+ expect(fs.sanitize('foo')).toEqual('foo');
+ });
+ it('should retain < b > tags', () => {
+ const str = 'foo <b>bar</b> baz';
+ expect(fs.sanitize(str)).toEqual(str);
+ });
+ it('should retain < i > tags', () => {
+ const str = 'foo <i>bar</i> baz';
+ expect(fs.sanitize(str)).toEqual(str);
+ });
+ it('should retain < p > tags', () => {
+ const str = 'foo <p>bar</p> baz';
+ expect(fs.sanitize(str)).toEqual(str);
+ });
+ it('should retain < em > tags', () => {
+ const str = 'foo <em>bar</em> baz';
+ expect(fs.sanitize(str)).toEqual(str);
+ });
+ it('should retain < strong > tags', () => {
+ const str = 'foo <strong>bar</strong> baz';
+ expect(fs.sanitize(str)).toEqual(str);
+ });
+
+ it('should reject < a > tags', () => {
+ expect(fs.sanitize('test <a href="hah">something</a> this'))
+ .toEqual('test something this');
+ });
+
+ it('should log a warning for < script > tags', () => {
+ expect(fs.sanitize('<script>alert("foo");</script>'))
+ .toEqual('');
+ expect(logServiceSpy.warn).toHaveBeenCalledWith(
+ 'Unsanitary HTML input -- <script> detected!'
+ );
+ });
+ it('should log a warning for < style > tags', () => {
+ expect(fs.sanitize('<style> h1 {color:red;} </style>'))
+ .toEqual('');
+ expect(logServiceSpy.warn).toHaveBeenCalledWith(
+ 'Unsanitary HTML input -- <style> detected!'
+ );
+ });
+
+ it('should log a warning for < iframe > tags', () => {
+ expect(fs.sanitize('Foo<iframe><body><h1>fake</h1></body></iframe>Bar'))
+ .toEqual('FooBar');
+ expect(logServiceSpy.warn).toHaveBeenCalledWith(
+ 'Unsanitary HTML input -- <iframe> detected!'
+ );
+ });
+
+ it('should completely strip < script >, remove < a >, retain < i >', () => {
+ expect(fs.sanitize('Hey <i>this</i> is <script>alert("foo");</script> <a href="meh">cool</a>'))
+ .toEqual('Hey <i>this</i> is cool');
+ });
});
diff --git a/web/gui2/src/main/webapp/tests/app/fw/util/lion.service.spec.ts b/web/gui2/src/main/webapp/tests/app/fw/util/lion.service.spec.ts
index d471005..6535f07 100644
--- a/web/gui2/src/main/webapp/tests/app/fw/util/lion.service.spec.ts
+++ b/web/gui2/src/main/webapp/tests/app/fw/util/lion.service.spec.ts
@@ -15,27 +15,64 @@
*
*/
import { TestBed, inject } from '@angular/core/testing';
+import { of } from 'rxjs';
import { LogService } from '../../../../app/log.service';
import { ConsoleLoggerService } from '../../../../app/consolelogger.service';
+import { ActivatedRoute, Params } from '@angular/router';
+import { FnService } from '../../../../app/fw/util/fn.service';
+import { GlyphService } from '../../../../app/fw/svg/glyph.service';
import { LionService } from '../../../../app/fw/util/lion.service';
-import { WebSocketService } from '../../../../app/fw/remote/websocket.service';
+import { UrlFnService } from '../../../../app/fw/remote/urlfn.service';
+import { WSock } from '../../../../app/fw/remote/wsock.service';
+import { WebSocketService, WsOptions } from '../../../../app/fw/remote/websocket.service';
-class MockWebSocketService {}
+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: WebSocketService, useClass: MockWebSocketService },
+ { provide: UrlFnService, useClass: MockUrlFnService },
+ { provide: WSock, useClass: MockWSock },
+ { provide: WebSocketService, useClass: WebSocketService },
+ { provide: Window, useFactory: (() => windowMock ) },
]
});
});
diff --git a/web/gui2/src/main/webapp/tests/app/onos.component.spec.ts b/web/gui2/src/main/webapp/tests/app/onos.component.spec.ts
index 2831d97..7a15504 100644
--- a/web/gui2/src/main/webapp/tests/app/onos.component.spec.ts
+++ b/web/gui2/src/main/webapp/tests/app/onos.component.spec.ts
@@ -14,15 +14,21 @@
* limitations under the License.
*/
import { TestBed, async } from '@angular/core/testing';
-import { RouterModule, RouterOutlet, ChildrenOutletContexts } from '@angular/router';
+import { RouterModule, RouterOutlet, ChildrenOutletContexts, ActivatedRoute, Params } from '@angular/router';
+import { of } from 'rxjs';
+
import { LogService } from '../../app/log.service';
import { ConsoleLoggerService } from '../../app/consolelogger.service';
+
import { IconComponent } from '../../app/fw/svg/icon/icon.component';
import { MastComponent } from '../../app/fw/mast/mast/mast.component';
import { NavComponent } from '../../app/fw/nav/nav/nav.component';
import { OnosComponent } from '../../app/onos.component';
+import { VeilComponent } from '../../app/fw/layer/veil/veil.component';
+
import { DialogService } from '../../app/fw/layer/dialog.service';
import { EeService } from '../../app/fw/util/ee.service';
+import { FnService } from '../../app/fw/util/fn.service';
import { GlyphService } from '../../app/fw/svg/glyph.service';
import { IconService } from '../../app/fw/svg/icon.service';
import { KeyService } from '../../app/fw/util/key.service';
@@ -31,10 +37,17 @@
import { OnosService } from '../../app/onos.service';
import { PanelService } from '../../app/fw/layer/panel.service';
import { QuickHelpService } from '../../app/fw/layer/quickhelp.service';
+import { SvgUtilService } from '../../app/fw/svg/svgutil.service';
import { ThemeService } from '../../app/fw/util/theme.service';
import { SpriteService } from '../../app/fw/svg/sprite.service';
-import { VeilService } from '../../app/fw/layer/veil.service';
-import { WebSocketService } from '../../app/fw/remote/websocket.service';
+import { WebSocketService, WsOptions } from '../../app/fw/remote/websocket.service';
+
+class MockActivatedRoute extends ActivatedRoute {
+ constructor(params: Params) {
+ super();
+ this.queryParams = of(params);
+ }
+}
class MockDialogService {}
@@ -46,8 +59,6 @@
class MockKeyService {}
-class MockLionService {}
-
class MockNavService {}
class MockOnosService {}
@@ -60,18 +71,34 @@
class MockThemeService {}
-class MockVeilService {}
-
-class MockWebSocketService {}
+class MockVeilComponent {}
/**
* ONOS GUI -- Onos Component - Unit Tests
*/
describe('OnosComponent', () => {
let log: LogService;
+ let fs: FnService;
+ let ar: MockActivatedRoute;
+ let windowMock: Window;
beforeEach(async(() => {
log = new ConsoleLoggerService();
+ ar = new MockActivatedRoute({'debug': 'TestService'});
+
+ windowMock = <any>{
+ location: <any> {
+ hostname: '',
+ host: '',
+ port: '',
+ protocol: '',
+ search: { debug: 'true'},
+ href: ''
+ },
+ innerHeight: 240,
+ innerWidth: 320
+ };
+ fs = new FnService(ar, log, windowMock);
TestBed.configureTestingModule({
declarations: [
@@ -79,16 +106,17 @@
MastComponent,
NavComponent,
OnosComponent,
+ VeilComponent,
RouterOutlet
],
providers: [
{ provide: ChildrenOutletContexts, useClass: ChildrenOutletContexts },
{ provide: DialogService, useClass: MockDialogService },
{ provide: EeService, useClass: MockEeService },
+ { provide: FnService, useValue: fs },
{ provide: GlyphService, useClass: MockGlyphService },
{ provide: IconService, useClass: MockIconService },
{ provide: KeyService, useClass: MockKeyService },
- { provide: LionService, useClass: MockLionService },
{ provide: LogService, useValue: log },
{ provide: NavService, useClass: MockNavService },
{ provide: OnosService, useClass: MockOnosService },
@@ -96,8 +124,7 @@
{ provide: PanelService, useClass: MockPanelService },
{ provide: SpriteService, useClass: MockSpriteService },
{ provide: ThemeService, useClass: MockThemeService },
- { provide: VeilService, useClass: MockVeilService },
- { provide: WebSocketService, useClass: MockWebSocketService },
+ { provide: Window, useFactory: (() => windowMock ) },
]
}).compileComponents();
}));
diff --git a/web/gui2/src/main/webapp/tests/app/view/device/device.component.spec.ts b/web/gui2/src/main/webapp/tests/app/view/device/device.component.spec.ts
index edfaed4..960d241 100644
--- a/web/gui2/src/main/webapp/tests/app/view/device/device.component.spec.ts
+++ b/web/gui2/src/main/webapp/tests/app/view/device/device.component.spec.ts
@@ -18,24 +18,36 @@
import { LogService } from '../../../../app/log.service';
import { ConsoleLoggerService } from '../../../../app/consolelogger.service';
import { DeviceComponent } from '../../../../app/view/device/device.component';
+
import { DetailsPanelService } from '../../../../app/fw/layer/detailspanel.service';
-import { FnService } from '../../../../app/fw/util/fn.service';
+import { FnService, WindowSize } from '../../../../app/fw/util/fn.service';
import { IconService } from '../../../../app/fw/svg/icon.service';
+import { GlyphService } from '../../../../app/fw/svg/glyph.service';
import { KeyService } from '../../../../app/fw/util/key.service';
import { LoadingService } from '../../../../app/fw/layer/loading.service';
import { NavService } from '../../../../app/fw/nav/nav.service';
import { MastService } from '../../../../app/fw/mast/mast.service';
import { PanelService } from '../../../../app/fw/layer/panel.service';
+import { SvgUtilService } from '../../../../app/fw/svg/svgutil.service';
import { TableBuilderService } from '../../../../app/fw/widget/tablebuilder.service';
import { TableDetailService } from '../../../../app/fw/widget/tabledetail.service';
import { WebSocketService } from '../../../../app/fw/remote/websocket.service';
class MockDetailsPanelService {}
-class MockFnService {}
+class MockFnService {
+ windowSize(offH: number = 0, offW: number = 0): WindowSize {
+ return {
+ height: 123,
+ width: 456
+ };
+ }
+}
class MockIconService {}
+class MockGlyphService {}
+
class MockKeyService {}
class MockLoadingService {
@@ -74,6 +86,7 @@
{ provide: DetailsPanelService, useClass: MockDetailsPanelService },
{ provide: FnService, useClass: MockFnService },
{ provide: IconService, useClass: MockIconService },
+ { provide: GlyphService, useClass: MockGlyphService },
{ provide: KeyService, useClass: MockKeyService },
{ provide: LoadingService, useClass: MockLoadingService },
{ provide: MastService, useClass: MockMastService },
@@ -91,7 +104,7 @@
beforeEach(() => {
fixture = TestBed.createComponent(DeviceComponent);
- component = fixture.componentInstance;
+ component = fixture.componentInstance;
fixture.detectChanges();
});