diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8819729..dfcba15 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+
+# 1.9.0 (2016-07-10)
+
+### Features
+
+* **Unit Test**: Break out storage and use DI to inject it, attempting to address [#86](https://github.com/lathonez/clicker/issues/86) ([](https://github.com/lathonez/clicker/commit/))
+
# 1.8.1 (2016-07-04)
diff --git a/app/app.ts b/app/app.ts
index e738a90..1066a24 100644
--- a/app/app.ts
+++ b/app/app.ts
@@ -1,9 +1,10 @@
'use strict';
-import { Component, Type, ViewChild } from '@angular/core';
-import { ionicBootstrap, MenuController, Nav, Platform } from 'ionic-angular';
-import { StatusBar } from 'ionic-native';
-import { ClickerList, Page2 } from './pages';
+import { Component, provide, Type, ViewChild } from '@angular/core';
+import { ionicBootstrap, MenuController, Nav, Platform } from 'ionic-angular';
+import { StatusBar } from 'ionic-native';
+import { Clickers, Storage } from './services';
+import { ClickerList, Page2 } from './pages';
@Component({
templateUrl: 'build/app.html',
@@ -48,4 +49,4 @@ export class ClickerApp {
};
}
-ionicBootstrap(ClickerApp);
+ionicBootstrap(ClickerApp, [Clickers, provide('Storage', {useClass: Storage})]);
diff --git a/app/pages/clickerList/clickerList.spec.ts b/app/pages/clickerList/clickerList.spec.ts
index 80b7d69..3e1be1a 100644
--- a/app/pages/clickerList/clickerList.spec.ts
+++ b/app/pages/clickerList/clickerList.spec.ts
@@ -9,7 +9,7 @@ this.fixture = null;
this.instance = null;
let clickerListProviders: Array = [
- provide(Clickers, {useClass: ClickersMock}),
+ provide(Clickers, {useClass: ClickersMock}),
];
describe('ClickerList', () => {
diff --git a/app/pages/clickerList/clickerList.ts b/app/pages/clickerList/clickerList.ts
index e3b7559..6dcaeec 100644
--- a/app/pages/clickerList/clickerList.ts
+++ b/app/pages/clickerList/clickerList.ts
@@ -7,7 +7,6 @@ import { ClickerButton, ClickerForm } from '../../components';
@Component({
templateUrl: 'build/pages/clickerList/clickerList.html',
- providers: [Clickers],
directives: [ClickerButton, ClickerForm],
})
diff --git a/app/services/clickers.mock.ts b/app/services/clickers.mock.ts
index 6d6e304..7ca1665 100644
--- a/app/services/clickers.mock.ts
+++ b/app/services/clickers.mock.ts
@@ -8,4 +8,8 @@ export class ClickersMock {
public newClicker(): boolean {
return true;
}
+
+ public getClickers(): Array {
+ return [];
+ }
}
diff --git a/app/services/clickers.spec.ts b/app/services/clickers.spec.ts
index 08168c6..9b8f87d 100644
--- a/app/services/clickers.spec.ts
+++ b/app/services/clickers.spec.ts
@@ -1,138 +1,95 @@
-import { Clickers } from './clickers';
-import { Clicker } from '../models/clicker';
-
-const CLICKER_IDS: Array = ['yy5d8klsj0', 'q20iexxg4a', 'wao2xajl8a'];
-let clickers: Clickers = null;
-
-function storageGetStub(key: string): Promise<{}> {
- 'use strict';
-
- let rtn: string = null;
-
- switch (key) {
- case 'ids':
- rtn = JSON.stringify(CLICKER_IDS);
- break;
- case CLICKER_IDS[0]:
- rtn = '{"id":"' + CLICKER_IDS[0] + '","name":"test1","clicks":[{"time":1450410168819,"location":"TODO"}]}';
- break;
- case CLICKER_IDS[1]:
- rtn = '{"id":"' + CLICKER_IDS[1] + '","name":"test2","clicks":[{"time":1450410168819,"location":"TODO"},{"time":1450410168945,"location":"TODO"}]}';
- break;
- case CLICKER_IDS[2]:
- rtn = '{"id":"' + 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);
- });
-}
-
-function storageSetStub(): Promise<{}> {
- 'use strict';
-
- return new Promise((resolve: Function) => {
- resolve(true);
- });
-}
-
-function storageRemoveStub(): Promise<{}> {
- 'use strict';
-
- return new Promise((resolve: Function) => {
- resolve(true);
- });
-}
-
-let mockSqlStorage: Object = {
- get: storageGetStub,
- set: storageSetStub,
- remove: storageRemoveStub,
-};
+import { beforeEach, beforeEachProviders, describe, expect, it } from '@angular/core/testing';
+import { provide } from '@angular/core';
+import { asyncCallbackFactory, injectAsyncWrapper, providers } from '../../test/diExports';
+import { Clickers } from './clickers';
+import { Clicker } from '../models/clicker';
+import { ClickerList } from '../pages/clickerList/clickerList';
+import { StorageMock } from './mocks';
+
+this.fixture = null;
+this.instance = null;
+this.clickers = null;
+
+let clickerListProviders: Array = [
+ Clickers,
+ provide('Storage', { useClass: StorageMock }),
+];
+
+let beforeEachFn: Function = ((testSpec) => {
+ testSpec.clickers = testSpec.instance.clickerService;
+ spyOn(testSpec.clickers.storage, 'set').and.callThrough();
+});
describe('Clickers', () => {
- beforeEach(() => {
- spyOn(Clickers, 'initStorage').and.returnValue(mockSqlStorage);
- clickers = new Clickers();
- spyOn(clickers['storage'], 'set');
- });
+ beforeEachProviders(() => providers.concat(clickerListProviders));
+ beforeEach(injectAsyncWrapper(asyncCallbackFactory(ClickerList, this, false, beforeEachFn)));
- it('initialises with empty clickers', () => {
- expect(clickers.getClickers()).toEqual([]);
+ it('initialises', () => {
+ expect(this.clickers).not.toBeNull();
+ expect(this.instance).not.toBeNull();
+ expect(this.fixture).not.toBeNull();
});
- it('creates an instance of SqlStorage', () => {
- expect((Clickers).initStorage()).toEqual(mockSqlStorage);
- });
-
- it('has empty ids with no storage', (done: Function) => {
- (clickers).initIds()
- .then(() => {
- expect(clickers.getClickers()).toEqual([]);
- done();
- });
+ it('initialises with empty clickers', () => {
+ expect(new Clickers(null).getClickers()).toEqual([]);
});
- it('has empty clickers with no storage', (done: Function) => {
- (clickers).initClickers([])
+ it('initialises with clickers from mock storage', (done: Function) => {
+ this.clickers['initClickers']([])
.then(() => {
- expect(clickers.getClickers()).toEqual([]);
+ expect(this.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);
+ let clicker: Clicker = this.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();
+ expect(this.clickers.getClicker('dave')).not.toBeDefined();
});
it('adds a new clicker with the correct name', () => {
- let idAdded: string = clickers.newClicker('dave');
- expect(clickers['storage'].set).toHaveBeenCalledWith(idAdded, jasmine.any(String));
- expect(clickers.getClickers()[0].getName()).toEqual('dave');
+ let idAdded: string = this.clickers.newClicker('dave');
+ expect(this.clickers['storage'].set).toHaveBeenCalledWith(idAdded, jasmine.any(String));
+ expect(this.clickers.getClickers()[3].getName()).toEqual('dave');
});
it('removes a clicker by id', () => {
- let idToRemove: string = clickers.newClicker('dave');
- clickers.removeClicker(idToRemove);
- expect(clickers['storage'].set).toHaveBeenCalledWith(idToRemove, jasmine.any(String));
- expect(clickers.getClickers()).toEqual([]);
+ let idToRemove: string = this.clickers.newClicker('dave');
+ this.clickers.removeClicker(idToRemove);
+ expect(this.clickers['storage'].set).toHaveBeenCalledWith(idToRemove, jasmine.any(String));
});
it('does a click', () => {
- let idToClick: string = clickers.newClicker('dave');
+ let idToClick: string = this.clickers.newClicker('dave');
let clickedClicker: Clicker = null;
- clickers.doClick(idToClick);
- expect(clickers['storage'].set).toHaveBeenCalledWith(idToClick, jasmine.any(String));
- clickedClicker = clickers.getClicker(idToClick);
+ this.clickers.doClick(idToClick);
+ expect(this.clickers['storage'].set).toHaveBeenCalledWith(idToClick, jasmine.any(String));
+ clickedClicker = this.clickers.getClicker(idToClick);
expect(clickedClicker.getCount()).toEqual(1);
});
it('loads IDs from storage', (done: Function) => {
- (clickers).initIds()
+ this.clickers.initIds()
.then((ids: Array) => {
- expect(ids).toEqual(CLICKER_IDS);
+ expect(ids).toEqual(StorageMock.CLICKER_IDS);
done();
});
});
it('loads clickers from storage', (done: Function) => {
- (clickers).initClickers(CLICKER_IDS)
+ this.clickers.initClickers(StorageMock.CLICKER_IDS)
.then((resolvedClickers: Array) => {
expect(resolvedClickers.length).toEqual(3);
- expect(resolvedClickers[0].getId()).toEqual(CLICKER_IDS[0]);
- expect(resolvedClickers[1].getId()).toEqual(CLICKER_IDS[1]);
- expect(resolvedClickers[2].getId()).toEqual(CLICKER_IDS[2]);
+ 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/services/clickers.ts b/app/services/clickers.ts
index 8308676..310cb79 100644
--- a/app/services/clickers.ts
+++ b/app/services/clickers.ts
@@ -1,18 +1,20 @@
'use strict';
-import { Injectable } from '@angular/core';
-import { SqlStorage } from 'ionic-angular';
-import { Click, Clicker } from '../models';
+import { Inject, Injectable } from '@angular/core';
+import { Storage } from './';
+import { Click, Clicker } from '../models';
@Injectable()
export class Clickers {
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: SqlStorage;
+ private storage: Storage;
- constructor() {
- this.storage = Clickers.initStorage(); // typeof SqlStorage is not assignable to type StorageEngine seems to be an ionic issue
+ // don't know why Injection isn't working without @Inject:
+ // http://stackoverflow.com/questions/34449486/angular-2-0-injected-http-service-is-undefined
+ constructor(@Inject('Storage') storage: Storage) {
+ this.storage = storage;
this.ids = [];
this.clickers = [];
this.initIds()
@@ -49,6 +51,7 @@ export class Clickers {
clickers.push(this.initClicker(clicker));
});
}
+ // TODO - this is a bug it will resolve before the loop has completed
resolve(clickers);
});
}
@@ -66,10 +69,6 @@ export class Clickers {
return newClicker;
}
- private static initStorage(): SqlStorage {
- return new SqlStorage();
- }
-
public getClicker(id: string): Clicker {
return this.clickers['find']((clicker: Clicker) => { return clicker.getId() === id; } );
}
diff --git a/app/services/index.ts b/app/services/index.ts
index 38105e8..5c8b138 100644
--- a/app/services/index.ts
+++ b/app/services/index.ts
@@ -1,2 +1,3 @@
export * from './clickers';
+export * from './storage';
export * from './utils';
diff --git a/app/services/mocks.ts b/app/services/mocks.ts
index 04e7683..851dcb2 100644
--- a/app/services/mocks.ts
+++ b/app/services/mocks.ts
@@ -1 +1,2 @@
export * from './clickers.mock';
+export * from './storage.mock';
diff --git a/app/services/storage.mock.ts b/app/services/storage.mock.ts
new file mode 100644
index 0000000..6c34b76
--- /dev/null
+++ b/app/services/storage.mock.ts
@@ -0,0 +1,43 @@
+'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/services/storage.spec.ts b/app/services/storage.spec.ts
new file mode 100644
index 0000000..9e1c80f
--- /dev/null
+++ b/app/services/storage.spec.ts
@@ -0,0 +1,34 @@
+import { Storage } from './';
+import { StorageMock } from './mocks';
+
+let storage: Storage = null;
+
+describe('Storage', () => {
+
+ beforeEach(() => {
+ spyOn(Storage, 'initStorage').and.returnValue(new StorageMock());
+ storage = new Storage();
+ spyOn(storage['storage'], 'get').and.callThrough();
+ spyOn(storage['storage'], 'set').and.callThrough();
+ spyOn(storage['storage'], 'remove').and.callThrough();
+ });
+
+ it('initialises', () => {
+ expect(storage).not.toBeNull();
+ });
+
+ it('gets', () => {
+ storage.get('dave');
+ expect(storage['storage'].get).toHaveBeenCalledWith('dave');
+ });
+
+ it('sets', () => {
+ storage.set('dave', 'test');
+ expect(storage['storage'].set).toHaveBeenCalledWith('dave', 'test');
+ });
+
+ it('removes', () => {
+ storage.remove('dave');
+ expect(storage['storage'].remove).toHaveBeenCalledWith('dave');
+ });
+});
diff --git a/app/services/storage.ts b/app/services/storage.ts
new file mode 100644
index 0000000..104173b
--- /dev/null
+++ b/app/services/storage.ts
@@ -0,0 +1,28 @@
+'use strict';
+
+import { SqlStorage } from 'ionic-angular';
+
+export class Storage {
+
+ private storage: SqlStorage;
+
+ constructor() {
+ this.storage = Storage.initStorage();
+ }
+
+ private static initStorage(): SqlStorage {
+ return new SqlStorage();
+ }
+
+ public get(key: string): Promise<{}> {
+ return this.storage.get(key);
+ }
+
+ public set(key: string, value: string): Promise<{}> {
+ return this.storage.set(key, value);
+ }
+
+ public remove(key: string): Promise<{}> {
+ return this.storage.remove(key);
+ }
+}
diff --git a/app/services/utils.spec.ts b/app/services/utils.spec.ts
index 7667a7b..796bdae 100644
--- a/app/services/utils.spec.ts
+++ b/app/services/utils.spec.ts
@@ -1,10 +1,6 @@
-import { Utils } from './utils';
+import { Utils } from './';
import { AbstractControl, Control } from '@angular/common';
-import {
- describe,
- expect,
- it,
-} from '@angular/core/testing';
+
describe('Utils', () => {
it('resets a control', () => {