From 57ec79a46a780d92642e683ccb6275e5af84cb70 Mon Sep 17 00:00:00 2001 From: Jason Dreyzehner Date: Fri, 7 Jul 2017 16:46:53 -0400 Subject: [PATCH] continue scaffolding, add blocksService --- app/e2e/app.e2e-spec.ts | 18 +-- ...2e-spec.ts => broadcastTxPage.e2e-spec.ts} | 8 +- app/e2e/clickerList.e2e-spec.ts | 42 ------- app/package.json | 3 + app/src/app/app.module.ts | 32 +++-- app/src/index.html | 4 +- app/src/models/block.spec.ts | 46 +++++++ app/src/models/block.ts | 34 ++++++ app/src/models/click.spec.ts | 26 ---- app/src/models/click.ts | 20 --- app/src/models/clicker.mock.ts | 5 - app/src/models/clicker.spec.ts | 11 -- app/src/models/clicker.ts | 37 ------ app/src/models/index.ts | 3 +- app/src/models/mocks.ts | 2 +- app/src/pages/blocksPage/blocksPage.html | 6 +- app/src/pages/blocksPage/blocksPage.ts | 20 +-- app/src/services/blocksService.mock.ts | 24 ++++ app/src/services/blocksService.spec.ts | 17 +++ app/src/services/blocksService.ts | 26 ++++ app/src/services/clickers.mock.ts | 14 --- app/src/services/clickers.spec.ts | 88 -------------- app/src/services/clickers.ts | 114 ------------------ app/src/services/index.ts | 4 +- app/src/services/mocks.ts | 4 +- app/src/services/storage.mock.ts | 43 ------- app/src/services/storageService.mock.ts | 42 +++++++ ...storage.spec.ts => storageService.spec.ts} | 0 .../{storage.ts => storageService.ts} | 1 - app/src/test.ts | 15 ++- app/tslint.json | 4 +- 31 files changed, 250 insertions(+), 463 deletions(-) rename app/e2e/{page2.e2e-spec.ts => broadcastTxPage.e2e-spec.ts} (64%) delete mode 100644 app/e2e/clickerList.e2e-spec.ts create mode 100644 app/src/models/block.spec.ts create mode 100644 app/src/models/block.ts delete mode 100644 app/src/models/click.spec.ts delete mode 100644 app/src/models/click.ts delete mode 100644 app/src/models/clicker.mock.ts delete mode 100644 app/src/models/clicker.spec.ts delete mode 100644 app/src/models/clicker.ts create mode 100644 app/src/services/blocksService.mock.ts create mode 100644 app/src/services/blocksService.spec.ts create mode 100644 app/src/services/blocksService.ts delete mode 100644 app/src/services/clickers.mock.ts delete mode 100644 app/src/services/clickers.spec.ts delete mode 100644 app/src/services/clickers.ts delete mode 100644 app/src/services/storage.mock.ts create mode 100644 app/src/services/storageService.mock.ts rename app/src/services/{storage.spec.ts => storageService.spec.ts} (100%) rename app/src/services/{storage.ts => storageService.ts} (97%) diff --git a/app/e2e/app.e2e-spec.ts b/app/e2e/app.e2e-spec.ts index a1cf299..d1e06d8 100644 --- a/app/e2e/app.e2e-spec.ts +++ b/app/e2e/app.e2e-spec.ts @@ -7,7 +7,7 @@ describe('InsightApp', () => { }); it('should have a title', () => { - expect(browser.getTitle()).toEqual('Clickers'); + expect(browser.getTitle()).toEqual('Blocks'); }); it('should have {nav}', () => { @@ -15,30 +15,22 @@ describe('InsightApp', () => { }); it('should have correct nav text for Home', () => { - expect(element(by.css('ion-navbar:first-child')).getText()).toContain('Clickers'); + expect(element(by.css('ion-navbar:first-child')).getText()).toContain('Blocks'); }); it('has a menu button that displays the left menu', () => { element(by.css('.bar-button-menutoggle')).click() .then(() => { browser.driver.sleep(2000); // wait for the animation - expect(element.all(by.css('.toolbar-title')).first().getText()).toEqual('Pages'); + expect(element(by.css('ion-menu')).isPresent()).toEqual(true); }); }); - it('the left menu has a link with title Clickers', () => { + it('the left menu has a link with title Blocks', () => { element(by.css('.bar-button-menutoggle')).click() .then(() => { browser.driver.sleep(2000); // wait for the animation - expect(element.all(by.css('ion-label')).first().getText()).toEqual('Clickers'); - }); - }); - - it('the left menu has a link with title Goodbye Ionic', () => { - element(by.css('.bar-button-menutoggle')).click() - .then(() => { - browser.driver.sleep(2000); // wait for the animation - expect(element.all(by.css('ion-label')).last().getText()).toEqual('Goodbye Ionic'); + expect(element.all(by.css('ion-label')).first().getText()).toEqual('Blocks'); }); }); }); diff --git a/app/e2e/page2.e2e-spec.ts b/app/e2e/broadcastTxPage.e2e-spec.ts similarity index 64% rename from app/e2e/page2.e2e-spec.ts rename to app/e2e/broadcastTxPage.e2e-spec.ts index 3b7647c..070ec27 100644 --- a/app/e2e/page2.e2e-spec.ts +++ b/app/e2e/broadcastTxPage.e2e-spec.ts @@ -1,20 +1,20 @@ import { browser, element, by, ElementFinder } from 'protractor'; -let message: ElementFinder = element(by.className('message')); +let heading: ElementFinder = element(by.css('h1')); -describe('Page2', () => { +describe('BroadcastTxPage', () => { beforeEach(() => { browser.get(''); }); - it('should have correct text when Goodbye Ionic is selected', () => { + it('should have the temporary heading', () => { element(by.css('.bar-button-menutoggle')).click().then(() => { browser.driver.sleep(2000); // wait for the animation element.all(by.className('input-wrapper')).then((items) => { items[1].click(); browser.driver.sleep(2000); // wait for the animation - expect(message.getText()).toEqual('SHOW SIMPLE ALERT\nSHOW MORE ADVANCED ALERT'); + expect(heading.getText()).toEqual('Broadcast Transaction'); return items[1]; }); }); diff --git a/app/e2e/clickerList.e2e-spec.ts b/app/e2e/clickerList.e2e-spec.ts deleted file mode 100644 index d332ca6..0000000 --- a/app/e2e/clickerList.e2e-spec.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { browser, element, by, ElementFinder } from 'protractor'; - -let clickerField: ElementFinder = element(by.css('.text-input')); -let addButton: ElementFinder = element.all(by.className('button-outline')).first(); -let removeButton: ElementFinder = element.all(by.css('.button-outline-md-danger')).first(); -let firstClicker: ElementFinder = element.all(by.tagName('clicker-button')).first().element(by.tagName('button')); - -describe('ClickerList', () => { - - beforeEach(() => { - browser.get(''); - }); - - it('should switch into clickers page from menu', () => { - element(by.css('.bar-button-menutoggle')).click(); - expect(element.all(by.css('.toolbar-title')).last().getText()).toEqual('Clickers'); - }); - - it('has an input box for new Clickers', () => { - expect(element(by.css('.text-input')).isPresent()).toEqual(true); - }); - - it('should add a Clicker', () => { - 'test clicker one'.split('').forEach((c) => clickerField.sendKeys(c)); - addButton.click(); - browser.driver.sleep(1000); - expect(firstClicker.getText()).toEqual('TEST CLICKER ONE (0)'); - }); - - it('should click a Clicker', () => { - firstClicker.click(); - browser.driver.sleep(1000); - expect(firstClicker.getText()).toEqual('TEST CLICKER ONE (1)'); - }); - - it('should delete a Clicker', () => { - removeButton.click(); - browser.driver.sleep(1000); - element.all(by.className('clickerList')).count() - .then((count) => expect(count).toEqual(0)); - }); -}); diff --git a/app/package.json b/app/package.json index cd750d3..401b92f 100644 --- a/app/package.json +++ b/app/package.json @@ -57,5 +57,8 @@ "tslint-eslint-rules": "4.1.1", "typescript": "2.3.3" }, + "engines": { + "node": ">=8" + }, "license": "MIT" } diff --git a/app/src/app/app.module.ts b/app/src/app/app.module.ts index dbc1eb7..2f6d8c5 100644 --- a/app/src/app/app.module.ts +++ b/app/src/app/app.module.ts @@ -1,26 +1,22 @@ -import { NgModule, ErrorHandler } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; +import { HttpModule } from '@angular/http'; +import { NgModule, ErrorHandler } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular'; -import { StatusBar } from '@ionic-native/status-bar'; -import { SplashScreen } from '@ionic-native/splash-screen'; -import { InsightApp } from './app.component'; -import { - PagesModule, - BlocksPage, - BroadcastTxPage, - NodeStatusPage, - VerifyMessagePage, -} from '../pages'; -import { ClickersService, StorageService } from '../services'; +import { StatusBar } from '@ionic-native/status-bar'; +import { SplashScreen } from '@ionic-native/splash-screen'; +import { InsightApp } from './app.component'; +import { PagesModule, BlocksPage, BroadcastTxPage, NodeStatusPage, VerifyMessagePage } from '../pages'; +import { BlocksService, StorageService } from '../services'; @NgModule({ declarations: [ - InsightApp, + InsightApp ], imports: [ BrowserModule, + HttpModule, PagesModule, - IonicModule.forRoot(InsightApp), + IonicModule.forRoot(InsightApp) ], bootstrap: [IonicApp], entryComponents: [ @@ -28,14 +24,14 @@ import { ClickersService, StorageService } from '../services'; BlocksPage, BroadcastTxPage, NodeStatusPage, - VerifyMessagePage, + VerifyMessagePage ], providers: [ StatusBar, SplashScreen, - ClickersService, StorageService, - {provide: ErrorHandler, useClass: IonicErrorHandler}, + BlocksService, + {provide: ErrorHandler, useClass: IonicErrorHandler} ], }) diff --git a/app/src/index.html b/app/src/index.html index 3ec7764..dcd5b1e 100644 --- a/app/src/index.html +++ b/app/src/index.html @@ -2,7 +2,7 @@ - Clickers + Insight @@ -10,7 +10,7 @@ - + diff --git a/app/src/models/block.spec.ts b/app/src/models/block.spec.ts new file mode 100644 index 0000000..5264d19 --- /dev/null +++ b/app/src/models/block.spec.ts @@ -0,0 +1,46 @@ +import { Block, InsightBlockObject } from './block'; + +describe('Block', () => { + it('initializes', () => { + let obj: InsightBlockObject = { + height: 474504, + size: 998221, + hash: '000000000000000001763ebcea127d82b5c49b620960e2d881c4ace719d5fe46', + time: 1499346191, + txlength: 1904, + poolInfo: { + poolName: 'AntMiner', + url: 'https://bitmaintech.com/' + } + }; + + let block: Block = new Block(obj); + + expect(block.height).toEqual(obj.height); + expect(block.size).toEqual(obj.size); + expect(block.hash).toEqual(obj.hash); + expect(block.timestamp).toEqual(obj.time); + expect(block.transactionCount).toEqual(obj.txlength); + expect(block.poolName).toEqual(obj.poolInfo.poolName); + }); + + it('can handle empty poolInfo', () => { + let obj: InsightBlockObject = { + height: 474504, + size: 998221, + hash: '000000000000000001763ebcea127d82b5c49b620960e2d881c4ace719d5fe46', + time: 1499346191, + txlength: 1904, + poolInfo: { } + }; + + let block: Block = new Block(obj); + + expect(block.height).toEqual(obj.height); + expect(block.size).toEqual(obj.size); + expect(block.hash).toEqual(obj.hash); + expect(block.timestamp).toEqual(obj.time); + expect(block.transactionCount).toEqual(obj.txlength); + expect(block.poolName).toEqual(obj.poolInfo.poolName); + }); +}); diff --git a/app/src/models/block.ts b/app/src/models/block.ts new file mode 100644 index 0000000..193d847 --- /dev/null +++ b/app/src/models/block.ts @@ -0,0 +1,34 @@ +export class Block { + + public readonly height: number; + public readonly size: number; + public readonly hash: string; + public readonly timestamp: number; + public readonly transactionCount: number; + public readonly poolName: string; + + constructor(properties: InsightBlockObject) { + this.height = properties.height; + this.size = properties.size; + this.hash = properties.hash; + this.timestamp = properties.time; + this.transactionCount = properties.txlength; + this.poolName = properties.poolInfo && properties.poolInfo.poolName; + } + + public getDate(): Date { + return new Date(this.timestamp * 1000); + } +} + +export interface InsightBlockObject { + height?: number; + size?: number; + hash?: string; + time?: number; + txlength?: number; + poolInfo?: { + poolName?: string, + url?: string + }; +} diff --git a/app/src/models/click.spec.ts b/app/src/models/click.spec.ts deleted file mode 100644 index d4e148f..0000000 --- a/app/src/models/click.spec.ts +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; - -import { Click } from './click'; - -describe('Click', () => { - - it('initialises with defaults', () => { - let click: Click = new Click(); - - // toString() prints out something like "Thu Jan 07 2016 14:05:14 GMT+1300 (NZDT)" - // comparing millis directly sometimes fails test (as it will be one milli too late!) - let currentDateString: string = new Date().toString(); - let defaultDateString: string = new Date(click.getTime()).toString(); - - expect(currentDateString).toEqual(defaultDateString); - expect(click.getLocation()).toEqual('TODO'); - }); - - it('initialises with overrides', () => { - let current: number = new Date().getTime(); - let location: string = 'MY LOCATION'; - let click: Click = new Click(current, location); - expect(click.getTime()).toEqual(current); - expect(click.getLocation()).toEqual(location); - }); -}); diff --git a/app/src/models/click.ts b/app/src/models/click.ts deleted file mode 100644 index 0d60220..0000000 --- a/app/src/models/click.ts +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -export class Click { - - private time: number; - private location: string; - - constructor(time?: number, location?: string) { - this.time = time || new Date().getTime(); - this.location = location || 'TODO'; - } - - public getTime(): number { - return this.time; - } - - public getLocation(): string { - return this.location; - } -} diff --git a/app/src/models/clicker.mock.ts b/app/src/models/clicker.mock.ts deleted file mode 100644 index c69dc14..0000000 --- a/app/src/models/clicker.mock.ts +++ /dev/null @@ -1,5 +0,0 @@ -export class ClickerMock { - public getCount(): number { return 10; }; - public getId(): string { return 'UUUUUID'; }; - public getName(): string { return 'TEST CLICKER'; }; -} diff --git a/app/src/models/clicker.spec.ts b/app/src/models/clicker.spec.ts deleted file mode 100644 index 395e8da..0000000 --- a/app/src/models/clicker.spec.ts +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -import { Clicker } from './clicker'; - -describe('Clicker', () => { - - it('initialises with the correct name', () => { - let clicker: Clicker = new Clicker('12434', 'testClicker'); - expect(clicker.getName()).toEqual('testClicker'); - }); -}); diff --git a/app/src/models/clicker.ts b/app/src/models/clicker.ts deleted file mode 100644 index ddadec7..0000000 --- a/app/src/models/clicker.ts +++ /dev/null @@ -1,37 +0,0 @@ -'use strict'; - -import { Click } from './'; - -// Represents a single Clicker -export class Clicker { - - private id: string; - private name: string; - private clicks: Array; - - constructor(id: string, name: string) { - this.id = id; - this.name = name; - this.clicks = []; - } - - public doClick(): void { - this.clicks.push(new Click()); - } - - public addClick(click: Click): void { - this.clicks.push(click); - } - - public getCount(): number { - return this.clicks.length; - } - - public getId(): string { - return this.id; - } - - public getName(): string { - return this.name; - } -} diff --git a/app/src/models/index.ts b/app/src/models/index.ts index 00d5248..d814a5a 100644 --- a/app/src/models/index.ts +++ b/app/src/models/index.ts @@ -1,2 +1 @@ -export * from './click'; -export * from './clicker'; +export * from './block'; diff --git a/app/src/models/mocks.ts b/app/src/models/mocks.ts index 799ce86..6b30123 100644 --- a/app/src/models/mocks.ts +++ b/app/src/models/mocks.ts @@ -1 +1 @@ -export * from './clicker.mock'; +// export * from './X.mock'; diff --git a/app/src/pages/blocksPage/blocksPage.html b/app/src/pages/blocksPage/blocksPage.html index 155b627..faf5819 100644 --- a/app/src/pages/blocksPage/blocksPage.html +++ b/app/src/pages/blocksPage/blocksPage.html @@ -14,16 +14,16 @@ - + #{{block.height}} - {{block.txlength}} transactions + {{block.transactionCount}} transactions - {{block.poolInfo.poolName}} + {{block.poolName}} diff --git a/app/src/pages/blocksPage/blocksPage.ts b/app/src/pages/blocksPage/blocksPage.ts index 897d652..c7e83c6 100644 --- a/app/src/pages/blocksPage/blocksPage.ts +++ b/app/src/pages/blocksPage/blocksPage.ts @@ -1,19 +1,25 @@ -import { Component } from '@angular/core'; -import { NavController } from 'ionic-angular'; +import { Component } from '@angular/core'; +import { NavController } from 'ionic-angular'; +import { Observable } from 'rxjs'; +import { Block } from '../../models'; +import { BlocksService } from '../../services'; @Component({ - templateUrl: './blocksPage.html', + templateUrl: './blocksPage.html' }) export class BlocksPage { public title: string; - private nav: NavController; - private blocks: any; + public blocks: Observable; - constructor(nav: NavController) { + constructor(private nav: NavController, private blocksService: BlocksService) { this.nav = nav; this.title = 'Blocks'; - this.blocks = [{"height":471569,"size":999095,"hash":"00000000000000000007bf61c6cf9efbb1353b919621768b553bd1ffb2948240","time":1497633772,"txlength":2561,"poolInfo":{}},{"height":471568,"size":999994,"hash":"000000000000000000fc2c10f1b4e55c9b86b8f9e804dc66683312b7d7d955dd","time":1497632268,"txlength":2011,"poolInfo":{}},{"height":471567,"size":989172,"hash":"000000000000000000d70effc2ec625999960bd7929d7a4298124456ecb29c23","time":1497632133,"txlength":1651,"poolInfo":{"poolName":"BTCC Pool","url":"https://pool.btcc.com/"}},{"height":471566,"size":998155,"hash":"00000000000000000127cbc53d017400eb9d17122a8ae36986c077ac7eea25fe","time":1497629915,"txlength":1130,"poolInfo":{"poolName":"AntMiner","url":"https://bitmaintech.com/"}},{"height":471565,"size":998103,"hash":"00000000000000000045844f32f6e9fc6e7575bb96eb71a9ce9fba2849892d5e","time":1497629731,"txlength":1986,"poolInfo":{}},{"height":471564,"size":998101,"hash":"0000000000000000009a5b3fc7ac517f8a275be73f4492adb0cb88943f5c1b9e","time":1497629348,"txlength":2378,"poolInfo":{}},{"height":471563,"size":998258,"hash":"00000000000000000048a5506f3415fcf790106a838953e04d86d8f2583ebd6a","time":1497628552,"txlength":1481,"poolInfo":{"poolName":"AntMiner","url":"https://bitmaintech.com/"}},{"height":471562,"size":998257,"hash":"0000000000000000016f6a483730bd9c1c48c8345782b8365762592a475aa330","time":1497628159,"txlength":646,"poolInfo":{"poolName":"AntMiner","url":"https://bitmaintech.com/"}},{"height":471561,"size":999016,"hash":"000000000000000001434fb0e5cc5e521de59bff70c325185a5c67bd5418c61a","time":1497627986,"txlength":1831,"poolInfo":{}},{"height":471560,"size":998229,"hash":"000000000000000000a1782a9d78672672e4291347c47ac3206ed8716bb4952b","time":1497627524,"txlength":791,"poolInfo":{"poolName":"SlushPool","url":"https://slushpool.com/"}},{"height":471559,"size":999152,"hash":"000000000000000000d58ba85b2ddedc1f467b1e1c49c47cf62d4d970dad7ab5","time":1497627347,"txlength":87,"poolInfo":{}},{"height":471558,"size":778220,"hash":"0000000000000000009a4e72a1863e42fdfe1e08e6d547d4528049065d69454c","time":1497627287,"txlength":1685,"poolInfo":{"poolName":"BTCC Pool","url":"https://pool.btcc.com/"}},{"height":471557,"size":707456,"hash":"00000000000000000068a8adc72fc41299105053e6059a3f6dcd2e0dc2fd2fcb","time":1497626859,"txlength":1661,"poolInfo":{"poolName":"BTCC Pool","url":"https://pool.btcc.com/"}}]; + this.blocks = blocksService.latestBlocks; + // this.blocks.subscribe((blocks) => { + // console.log(blocks); + // }); + blocksService.getLatestBlocks(); } } diff --git a/app/src/services/blocksService.mock.ts b/app/src/services/blocksService.mock.ts new file mode 100644 index 0000000..b23a89b --- /dev/null +++ b/app/src/services/blocksService.mock.ts @@ -0,0 +1,24 @@ +import { Subject } from 'rxjs'; +import { Block } from '../models'; + +export class BlocksServiceMock { + + public readonly latestBlocks: Subject> = new Subject(); + + public getLatestBlocks(): void { + this.latestBlocks.next([ + new Block({ + height: 474504, + size: 998221, + hash: '000000000000000001763ebcea127d82b5c49b620960e2d881c4ace719d5fe46', + time: 1499346191, + txlength: 1904, + poolInfo: { + poolName: 'AntMiner', + url: 'https://bitmaintech.com/' + } + }) + ]); + } + +} diff --git a/app/src/services/blocksService.spec.ts b/app/src/services/blocksService.spec.ts new file mode 100644 index 0000000..cf99bfb --- /dev/null +++ b/app/src/services/blocksService.spec.ts @@ -0,0 +1,17 @@ +import { BlocksService } from './blocksService'; +import { Block } from '../models'; +import { TestUtils } from '../test'; + +let blocks: BlocksService = null; + +// describe('BlocksService', () => { + +// beforeEach(() => { +// blocks = new BlocksService(); +// }); + +// it('initializes', () => { +// expect(blocks).not.toBeNull(); +// }); + +// }); diff --git a/app/src/services/blocksService.ts b/app/src/services/blocksService.ts new file mode 100644 index 0000000..747f49d --- /dev/null +++ b/app/src/services/blocksService.ts @@ -0,0 +1,26 @@ +import { Http, Response } from '@angular/http'; +import { Injectable } from '@angular/core'; +import { Observable, Subject } from 'rxjs'; +import { Block, InsightBlockObject } from '../models'; + +@Injectable() +export class BlocksService { + + public readonly latestBlocks: Subject> = new Subject(); + + constructor(private http: Http) {} + + public getLatestBlocks(): void { + this.http.request('https://insight.bitpay.com/api/blocks').subscribe((res: Response) => { + const data: { + blocks: InsightBlockObject[], + length: number, + pagination: {} + } = res.json(); + this.latestBlocks.next(data.blocks.map((obj) => { + return new Block(obj); + })); + }); + } + +} diff --git a/app/src/services/clickers.mock.ts b/app/src/services/clickers.mock.ts deleted file mode 100644 index 1d8206a..0000000 --- a/app/src/services/clickers.mock.ts +++ /dev/null @@ -1,14 +0,0 @@ -export class ClickersServiceMock { - - public doClick(): boolean { - return true; - } - - public newClicker(): boolean { - return true; - } - - public getClickers(): Array { - return []; - } -} diff --git a/app/src/services/clickers.spec.ts b/app/src/services/clickers.spec.ts deleted file mode 100644 index 73d8faf..0000000 --- a/app/src/services/clickers.spec.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { ClickersService } from './clickers'; -import { Clicker } from '../models'; -import { StorageMock } from './mocks'; - -let clickers: ClickersService = null; - -describe('ClickersService', () => { - - beforeEach(() => { - clickers = new ClickersService(new StorageMock()); - spyOn(clickers['storage'], 'set').and.callThrough(); - }); - - it('initialises', () => { - expect(clickers).not.toBeNull(); - }); - - it('initialises with clickers from mock storage', (done: Function) => { - clickers['init']() - .then(() => { - expect(clickers.getClickers().length).toEqual(StorageMock.CLICKER_IDS.length); - done(); - }); - }); - - it('can initialise a clicker from string', () => { - let clickerString: string = '{"id":"0g2vt8qtlm","name":"harold","clicks":[{"time":1450410168819,"location":"TODO"},{"time":1450410168945,"location":"TODO"}]}'; - let clicker: Clicker = clickers['initClicker'](clickerString); - expect(clicker.getName()).toEqual('harold'); - expect(clicker.getCount()).toEqual(2); - }); - - it('returns undefined for a bad id', () => { - expect(clickers.getClicker('dave')).not.toBeDefined(); - }); - - it('adds a new clicker with the correct name', (done: Function) => { - clickers['init']() - .then(() => { - let idAdded: string = clickers.newClicker('dave'); - expect(clickers['storage'].set).toHaveBeenCalledWith(idAdded, jasmine.any(String)); - expect(clickers.getClickers()[3].getName()).toEqual('dave'); - done(); - }); - }); - - it('removes a clicker by id', () => { - let idToRemove: string = clickers.newClicker('dave'); - clickers.removeClicker(idToRemove); - expect(clickers['storage'].set).toHaveBeenCalledWith(idToRemove, jasmine.any(String)); - }); - - it('does a click', () => { - let idToClick: string = clickers.newClicker('dave'); - let clickedClicker: Clicker = null; - clickers.doClick(idToClick); - expect(clickers['storage'].set).toHaveBeenCalledWith(idToClick, jasmine.any(String)); - clickedClicker = clickers.getClicker(idToClick); - expect(clickedClicker.getCount()).toEqual(1); - }); - - it('loads empty list if given no argument', (done: Function) => { - clickers['initIds'](false) - .then((ids: Array) => { - expect(ids).toEqual([]); - done(); - }); - }); - - it('loads IDs from storage', (done: Function) => { - clickers['initIds']() - .then((ids: Array) => { - expect(ids).toEqual(StorageMock.CLICKER_IDS); - done(); - }); - }); - - it('loads clickers from storage', (done: Function) => { - clickers['initClickers'](StorageMock.CLICKER_IDS) - .then((resolvedClickers: Array) => { - expect(resolvedClickers.length).toEqual(3); - expect(resolvedClickers[0].getId()).toEqual(StorageMock.CLICKER_IDS[0]); - expect(resolvedClickers[1].getId()).toEqual(StorageMock.CLICKER_IDS[1]); - expect(resolvedClickers[2].getId()).toEqual(StorageMock.CLICKER_IDS[2]); - done(); - }); - }); -}); diff --git a/app/src/services/clickers.ts b/app/src/services/clickers.ts deleted file mode 100644 index f936f8b..0000000 --- a/app/src/services/clickers.ts +++ /dev/null @@ -1,114 +0,0 @@ -'use strict'; - -import { Injectable } from '@angular/core'; -import { StorageService } from './storage'; -import { Click, Clicker } from '../models'; - -@Injectable() -export class ClickersService { - - private clickers: Array; - private ids: Array; // we need to keep a separate reference to ids so we can lookup when the app loads from scratch - private storage: StorageService; - - // don't know why Injection isn't working without @Inject: - // http://stackoverflow.com/questions/34449486/angular-2-0-injected-http-service-is-undefined - constructor(storage: StorageService) { - this.storage = storage; - this.ids = []; - this.clickers = []; - this.init(); - } - - // as init is async separate logic here so it's testable - private init(): Promise<{}> { - return this.initIds() - .then((ids: Array) => { this.ids = ids; }) - .then(() => this.initClickers(this.ids)) - .then((clickers: Array) => this.clickers = clickers); - } - - // initialise Ids from SQL storage - private initIds(load: boolean = true): Promise<{}> { - return this.storage.get('ids') // return the promise so we can chain initClickers - .then((rawIds: string) => { - if (!rawIds || !load) return []; - // ids are stored as stringified JSON array - return JSON.parse(rawIds); - }); - } - - // initialise Clickers from SQL storage given an array of ids - private initClickers(ids: Array): Promise<{}> { - // get all existing ids - let proms: Array> = []; - - proms = ids.map(id => this.storage.get(id)); - - return Promise.all(proms) - .then(clickers => clickers.map(clicker => this.initClicker(clicker))); - } - - // initialise a clicker from a raw JSON string out of the DB - private initClicker(clicker: string): Clicker { - const parsedClicker: Object = JSON.parse(clicker); - const newClicker: Clicker = new Clicker(parsedClicker['id'], parsedClicker['name']); - - // add the clicks - need to re-instantiate object - for (let click of parsedClicker['clicks']) { - newClicker.addClick(new Click(click.time, click.location)); - } - - return newClicker; - } - - public getClicker(id: string): Clicker { - return this.clickers['find']((clicker: Clicker) => { return clicker.getId() === id; } ); - } - - public getClickers(): Array { - return this.clickers; - } - - public newClicker(name: string): string { - const id: string = this.uid(); - const clicker: Clicker = new Clicker(id, name); - - // add the clicker to the service - this.clickers.push(clicker); - // add the id to the service (need to keep a separate reference of IDs so we can cold load clickers) - this.ids.push(id); - // save the clicker by id - this.storage.set(id, JSON.stringify(clicker)); - // save the service's ids array - this.storage.set('ids', JSON.stringify(this.ids)); - - return id; - } - - public removeClicker(id: string): void { - - // remove clicker from the service - this.clickers = this.clickers.filter((clicker: Clicker) => { return clicker.getId() !== id; }); - - // remove from ids array - this.ids = this.ids.filter((filterId: string) => { return filterId !== id; }); - - // null id in db - this.storage.remove(id); - - // update service's ids array - this.storage.set('ids', JSON.stringify(this.ids)); - } - - public doClick(id: string): void { - const clicker: Clicker = this.getClicker(id); - clicker.doClick(); - // save the clicker with updated click in storage - this.storage.set(clicker.getId(), JSON.stringify(clicker)); - } - - private uid(): string { - return Math.random().toString(35).substr(2, 10); - } -} diff --git a/app/src/services/index.ts b/app/src/services/index.ts index 2c57205..c6ee35d 100644 --- a/app/src/services/index.ts +++ b/app/src/services/index.ts @@ -1,2 +1,2 @@ -export * from './clickers'; -export * from './storage'; +export * from './blocksService'; +export * from './storageService'; diff --git a/app/src/services/mocks.ts b/app/src/services/mocks.ts index 851dcb2..aaa47e6 100644 --- a/app/src/services/mocks.ts +++ b/app/src/services/mocks.ts @@ -1,2 +1,2 @@ -export * from './clickers.mock'; -export * from './storage.mock'; +export * from './blocksService.mock'; +export * from './storageService.mock'; diff --git a/app/src/services/storage.mock.ts b/app/src/services/storage.mock.ts deleted file mode 100644 index 6c34b76..0000000 --- a/app/src/services/storage.mock.ts +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; - -export class StorageMock { - - public static CLICKER_IDS: Array = ['yy5d8klsj0', 'q20iexxg4a', 'wao2xajl8a']; - - public get(key: string): Promise<{}> { - let rtn: string = null; - - switch (key) { - case 'ids': - rtn = JSON.stringify(StorageMock.CLICKER_IDS); - break; - case StorageMock.CLICKER_IDS[0]: - rtn = `{"id":"${StorageMock.CLICKER_IDS[0]}","name":"test1","clicks":[{"time":1450410168819,"location":"TODO"}]}`; - break; - case StorageMock.CLICKER_IDS[1]: - rtn = `{"id":"${StorageMock.CLICKER_IDS[1]}","name":"test2","clicks":[{"time":1450410168819,"location":"TODO"},{"time":1450410168945,"location":"TODO"}]}`; - break; - case StorageMock.CLICKER_IDS[2]: - rtn = `{"id":"${StorageMock.CLICKER_IDS[2]}","name":"test3", "clicks":[{ "time": 1450410168819, "location": "TODO" }, { "time": 1450410168945, "location": "TODO" }] }`; - break; - default: - rtn = 'SHOULD NOT BE HERE!'; - } - - return new Promise((resolve: Function) => { - resolve(rtn); - }); - } - - public set(key: string, value: string): Promise<{}> { - return new Promise((resolve: Function) => { - resolve({key: key, value: value}); - }); - } - - public remove(key: string): Promise<{}> { - return new Promise((resolve: Function) => { - resolve({key: key}); - }); - } -} diff --git a/app/src/services/storageService.mock.ts b/app/src/services/storageService.mock.ts new file mode 100644 index 0000000..e15e968 --- /dev/null +++ b/app/src/services/storageService.mock.ts @@ -0,0 +1,42 @@ +export class StorageServiceMock { + + public static CLICKER_IDS: Array = ['yy5d8klsj0', 'q20iexxg4a', 'wao2xajl8a']; + + public get(key: string): Promise<{}> { + let rtn: string = null; + + switch (key) { + case 'ids': + rtn = JSON.stringify(StorageServiceMock.CLICKER_IDS); + break; + case StorageServiceMock.CLICKER_IDS[0]: + rtn = `{"id":"${StorageServiceMock.CLICKER_IDS[0]}","name":"test1","clicks":[{"time":1450410168819,"location":"TODO"}]}`; + break; + case StorageServiceMock.CLICKER_IDS[1]: + rtn = `{"id":"${StorageServiceMock.CLICKER_IDS[1]}","name":"test2","clicks":[{"time":1450410168819,"location":"TODO"},{"time":1450410168945,"location":"TODO"}]}`; + break; + case StorageServiceMock.CLICKER_IDS[2]: + rtn = `{"id":"${StorageServiceMock.CLICKER_IDS[2]}","name":"test3", "clicks":[{ "time": 1450410168819, "location": "TODO" }, + { "time": 1450410168945, "location": "TODO" }] }`; + break; + default: + rtn = 'SHOULD NOT BE HERE!'; + } + + return new Promise((resolve: Function) => { + resolve(rtn); + }); + } + + public set(key: string, value: string): Promise<{}> { + return new Promise((resolve: Function) => { + resolve({key: key, value: value}); + }); + } + + public remove(key: string): Promise<{}> { + return new Promise((resolve: Function) => { + resolve({key: key}); + }); + } +} diff --git a/app/src/services/storage.spec.ts b/app/src/services/storageService.spec.ts similarity index 100% rename from app/src/services/storage.spec.ts rename to app/src/services/storageService.spec.ts diff --git a/app/src/services/storage.ts b/app/src/services/storageService.ts similarity index 97% rename from app/src/services/storage.ts rename to app/src/services/storageService.ts index c0772f0..85d394a 100644 --- a/app/src/services/storage.ts +++ b/app/src/services/storageService.ts @@ -1,4 +1,3 @@ -'use strict'; import { Injectable } from '@angular/core'; import { Storage } from '@ionic/storage'; diff --git a/app/src/test.ts b/app/src/test.ts index 7a4cb7e..ee17df7 100644 --- a/app/src/test.ts +++ b/app/src/test.ts @@ -10,10 +10,12 @@ import 'zone.js/dist/fake-async-test'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { getTestBed, TestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; +import { HttpModule } from '@angular/http'; +import { MockBackend } from '@angular/http/testing'; import { App, Config, Form, IonicModule, Keyboard, DomController, MenuController, NavController, Platform, GestureController } from 'ionic-angular'; import { ConfigMock, PlatformMock } from './mocks'; -import { ClickersServiceMock } from './services/clickers.mock'; -import { ClickersService } from './services'; +import { BlocksServiceMock } from './services/mocks'; +import { BlocksService } from './services'; // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. declare var __karma__: any; @@ -44,7 +46,7 @@ export class TestUtils { let fixture: any = TestBed.createComponent(components[0]); return { fixture: fixture, - instance: fixture.debugElement.componentInstance, + instance: fixture.debugElement.componentInstance }; }); } @@ -52,19 +54,20 @@ export class TestUtils { public static configureIonicTestingModule(components: Array): typeof TestBed { return TestBed.configureTestingModule({ declarations: [ - ...components, + ...components ], providers: [ App, Form, Keyboard, DomController, MenuController, NavController, GestureController, {provide: Platform, useClass: PlatformMock}, {provide: Config, useClass: ConfigMock}, - {provide: ClickersService, useClass: ClickersServiceMock}, + {provide: BlocksService, useClass: BlocksServiceMock} ], imports: [ FormsModule, IonicModule, ReactiveFormsModule, - ], + HttpModule + ] }); } diff --git a/app/tslint.json b/app/tslint.json index c717a98..a96fe0a 100644 --- a/app/tslint.json +++ b/app/tslint.json @@ -36,7 +36,7 @@ "no-consecutive-blank-lines": true, "no-console": [false], "no-construct": false, - "no-constructor-vars": true, + "no-constructor-vars": false, "no-debugger": true, "no-duplicate-key": true, "no-duplicate-variable": true, @@ -75,7 +75,7 @@ "trailing-comma": [ true, { - "multiline": "always", + "multiline": "never", "singleline": "never" } ],