continue scaffolding, add blocksService
This commit is contained in:
parent
d784be5d7e
commit
57ec79a46a
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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];
|
||||
});
|
||||
});
|
||||
@ -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));
|
||||
});
|
||||
});
|
||||
@ -57,5 +57,8 @@
|
||||
"tslint-eslint-rules": "4.1.1",
|
||||
"typescript": "2.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
|
||||
@ -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}
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Clickers</title>
|
||||
<title>Insight</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="msapplication-tap-highlight" content="no">
|
||||
@ -10,7 +10,7 @@
|
||||
<link rel="icon" type="image/x-icon" href="assets/icon/favicon.ico">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<meta name="theme-color" content="#4e8ef7">
|
||||
|
||||
|
||||
<!-- cordova.js required for cordova apps -->
|
||||
<script src="cordova.js"></script>
|
||||
|
||||
|
||||
46
app/src/models/block.spec.ts
Normal file
46
app/src/models/block.spec.ts
Normal file
@ -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);
|
||||
});
|
||||
});
|
||||
34
app/src/models/block.ts
Normal file
34
app/src/models/block.ts
Normal file
@ -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
|
||||
};
|
||||
}
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
export class ClickerMock {
|
||||
public getCount(): number { return 10; };
|
||||
public getId(): string { return 'UUUUUID'; };
|
||||
public getName(): string { return 'TEST CLICKER'; };
|
||||
}
|
||||
@ -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');
|
||||
});
|
||||
});
|
||||
@ -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<Click>;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -1,2 +1 @@
|
||||
export * from './click';
|
||||
export * from './clicker';
|
||||
export * from './block';
|
||||
|
||||
@ -1 +1 @@
|
||||
export * from './clicker.mock';
|
||||
// export * from './X.mock';
|
||||
|
||||
@ -14,16 +14,16 @@
|
||||
|
||||
<ion-content padding>
|
||||
<ion-list>
|
||||
<ion-item *ngFor="let block of blocks">
|
||||
<ion-item *ngFor="let block of blocks | async">
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
#{{block.height}}
|
||||
</ion-col>
|
||||
<ion-col>
|
||||
{{block.txlength}} transactions
|
||||
{{block.transactionCount}} transactions
|
||||
</ion-col>
|
||||
<ion-col>
|
||||
<a *ngIf="block.poolInfo.poolName" href="{{block.poolInfo.url}}">{{block.poolInfo.poolName}}</a>
|
||||
<a *ngIf="block.poolName" href="{{block.url}}">{{block.poolName}}</a>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-item>
|
||||
|
||||
@ -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<Block[]>;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
24
app/src/services/blocksService.mock.ts
Normal file
24
app/src/services/blocksService.mock.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { Subject } from 'rxjs';
|
||||
import { Block } from '../models';
|
||||
|
||||
export class BlocksServiceMock {
|
||||
|
||||
public readonly latestBlocks: Subject<Array<Block>> = 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/'
|
||||
}
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
17
app/src/services/blocksService.spec.ts
Normal file
17
app/src/services/blocksService.spec.ts
Normal file
@ -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();
|
||||
// });
|
||||
|
||||
// });
|
||||
26
app/src/services/blocksService.ts
Normal file
26
app/src/services/blocksService.ts
Normal file
@ -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<Array<Block>> = 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);
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
export class ClickersServiceMock {
|
||||
|
||||
public doClick(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public newClicker(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public getClickers(): Array<string> {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@ -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(<any>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<string>) => {
|
||||
expect(ids).toEqual([]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('loads IDs from storage', (done: Function) => {
|
||||
clickers['initIds']()
|
||||
.then((ids: Array<string>) => {
|
||||
expect(ids).toEqual(StorageMock.CLICKER_IDS);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('loads clickers from storage', (done: Function) => {
|
||||
clickers['initClickers'](StorageMock.CLICKER_IDS)
|
||||
.then((resolvedClickers: Array<Clicker>) => {
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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<Clicker>;
|
||||
private ids: Array<string>; // 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<string>) => { this.ids = ids; })
|
||||
.then(() => this.initClickers(this.ids))
|
||||
.then((clickers: Array<Clicker>) => 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<string>): Promise<{}> {
|
||||
// get all existing ids
|
||||
let proms: Array<Promise<string>> = [];
|
||||
|
||||
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<Clicker> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -1,2 +1,2 @@
|
||||
export * from './clickers';
|
||||
export * from './storage';
|
||||
export * from './blocksService';
|
||||
export * from './storageService';
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
export * from './clickers.mock';
|
||||
export * from './storage.mock';
|
||||
export * from './blocksService.mock';
|
||||
export * from './storageService.mock';
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
export class StorageMock {
|
||||
|
||||
public static CLICKER_IDS: Array<string> = ['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});
|
||||
});
|
||||
}
|
||||
}
|
||||
42
app/src/services/storageService.mock.ts
Normal file
42
app/src/services/storageService.mock.ts
Normal file
@ -0,0 +1,42 @@
|
||||
export class StorageServiceMock {
|
||||
|
||||
public static CLICKER_IDS: Array<string> = ['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});
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,3 @@
|
||||
'use strict';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Storage } from '@ionic/storage';
|
||||
|
||||
@ -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<any>): 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
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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"
|
||||
}
|
||||
],
|
||||
|
||||
Loading…
Reference in New Issue
Block a user