diff --git a/app/.angular-cli.json b/app/.angular-cli.json new file mode 100644 index 0000000..d64f84b --- /dev/null +++ b/app/.angular-cli.json @@ -0,0 +1,62 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "project": { + "name": "my-ng-app" + }, + "apps": [ + { + "root": "src", + "outDir": "dist", + "assets": [ + "assets", + "favicon.ico" + ], + "index": "index.html", + "main": "app/main.ts", + "polyfills": "polyfills.ts", + "test": "test.ts", + "tsconfig": "tsconfig.app.json", + "testTsconfig": "tsconfig.spec.json", + "prefix": "app", + "scripts": [], + "environmentSource": "environments/environment.ts", + "environments": { + "dev": "environments/environment.ts", + "prod": "environments/environment.prod.ts" + } + } + ], + "e2e": { + "protractor": { + "config": "./protractor.conf.js" + } + }, + "lint": [ + { + "project": "src/tsconfig.app.json" + }, + { + "project": "src/tsconfig.spec.json" + }, + { + "project": "e2e/tsconfig.e2e.json" + } + ], + "test": { + "karma": { + "config": "./karma.conf.js" + }, + "codeCoverage": { + "exclude": [ + "src/polyfills.ts", + "src/test.ts", + "src/mocks.ts", + "**/*.mock.ts" + ] + } + }, + "defaults": { + "styleExt": "css", + "component": {} + } +} diff --git a/app/.editorconfig b/app/.editorconfig new file mode 100644 index 0000000..51873bc --- /dev/null +++ b/app/.editorconfig @@ -0,0 +1,17 @@ +# EditorConfig helps developers define and maintain consistent coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] +indent_style = space +indent_size = 2 + +# We recommend you to keep these unchanged +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..c58fd64 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,38 @@ +# Specifies intentionally untracked files to ignore when using Git +# http://git-scm.com/docs/gitignore + +*~ +*.sw[mnpcod] +*.log +*.tmp +*.tmp.* +log.txt +*.sublime-project +*.sublime-workspace +.vscode/ +npm-debug.log* + +.idea/ +.sass-cache/ +.tmp/ +.versions/ +coverage/ +dist/ +node_modules/ +tmp/ +temp/ +hooks/ +platforms/ +plugins/ +plugins/android.json +plugins/ios.json +www/ +$RECYCLE.BIN/ + +# e2e +/e2e/*.js +/e2e/*.map + +.DS_Store +Thumbs.db +UserInterfaceState.xcuserstate diff --git a/app/.travis.yml b/app/.travis.yml new file mode 100644 index 0000000..ed93b2f --- /dev/null +++ b/app/.travis.yml @@ -0,0 +1,70 @@ +# +# Configuration +# + +sudo: required +dist: trusty + +notifications: + email: true + +addons: + apt: + sources: + - ubuntu-toolchain-r-test + - google-chrome + packages: + - google-chrome-stable + - lib32stdc++6 + - lib32z1 + +branches: + only: + - master + - rc0 + +# +# Build Lifecycle: +# + +before_install: + - nvm install --lts + - node --version + - npm install -g cordova@6.4.0 ionic@3.2.0 + +install: npm install + +before_script: + - export DISPLAY=:99.0 + - sh -e /etc/init.d/xvfb start + +script: + - npm run test-ci # unit tests + - npm run e2e # run e2e tests against ionic + +after_success: + # Send coverage info off to cloud ppl + - ./node_modules/.bin/codecov + +before_deploy: + # start install android + - wget http://dl.google.com/android/android-sdk_r24.2-linux.tgz + - tar -xzvf android-sdk_r24.2-linux.tgz + - echo "y" | ./android-sdk-linux/tools/android update sdk --no-ui --filter android-23,build-tools-23.0.1 + - export ANDROID_HOME=${PWD}/android-sdk-linux + # license fail workaround: http://stackoverflow.com/questions/38096225/automatically-accept-all-sdk-licences + - mkdir -p ${ANDROID_HOME}/licenses + - echo -e "\n8933bad161af4178b1185d1a37fbf41ea5269c55" > "${ANDROID_HOME}/licenses/android-sdk-license" + - echo -e "\n84831b9409646a918e30573bab4c9c91346d8abd" > "${ANDROID_HOME}/licenses/android-sdk-preview-license" + # end install android + - cordova build android + # `ionic build android` wraps `cordova build android`, which gives a legit exit code if it fails. Ionic does not; test apk exists. + - ls ./platforms/android/build/outputs/apk/android*.apk + # test a browser prod build with ionic (#236) + - cordova build browser --prod + - ls ./platforms/browser/build/package.zip + +deploy: + provider: script + script: .travis/deploy.sh + skip_cleanup: true diff --git a/app/.travis/deploy.sh b/app/.travis/deploy.sh new file mode 100755 index 0000000..9d75d6f --- /dev/null +++ b/app/.travis/deploy.sh @@ -0,0 +1,9 @@ +#! /usr/bin/env sh +# +# Helper script run from travis to deploy the app +# +# deploy to davonez in addition to ionic view for debug +# Ionic upload failing see https://github.com/ionic-team/ionic/issues/11872 +ionic upload --email $IONIC_EMAIL --password $IONIC_PASSWORD +# ssh-keyscan 176.58.107.25 >> ~/.ssh/known_hosts +# scp -i .travis/travis_rsa.pem platforms/android/build/outputs/apk/android-debug.apk shazleto@176.58.107.25:~/clicker \ No newline at end of file diff --git a/app/.travis/travis_rsa.pem.enc b/app/.travis/travis_rsa.pem.enc new file mode 100644 index 0000000..3141fa0 Binary files /dev/null and b/app/.travis/travis_rsa.pem.enc differ diff --git a/app/CHANGELOG.md b/app/CHANGELOG.md new file mode 100644 index 0000000..ade2e81 --- /dev/null +++ b/app/CHANGELOG.md @@ -0,0 +1,618 @@ + +# 2.15.0 (2017-06-11) + +### Features + +* **Unit**: Add alert example and coverage PR [#252](https://github.com/lathonez/clicker/pull/252) ([310a2e9](https://github.com/lathonez/clicker/commit/310a2e9)) + + +# 2.14.0 (2017-05-31) + +### Features + +* **Update**: Update to Ionic 3.3.0 and @angular/cli 1.0.6 (implying @angular 4.1.2) ([f9928ab](https://github.com/lathonez/clicker/commit/f9928ab)) + + +# 2.13.2 (2017-05-11) + +### Bug Fixes + +* **Build**: Use explicit version of ionic cli [#246](https://github.com/lathonez/clicker/pull/246) ([b334fe5](https://github.com/lathonez/clicker/commit/b334fe5)) + + +# 2.13.1 (2017-04-30) + +### Bug Fixes + +* **Unit**: Remove unused storage mock import [#244](https://github.com/lathonez/clicker/pull/244) ([daa3172](https://github.com/lathonez/clicker/commit/daa3172)) +* **Services**: Add typedef for load: ([105295a](https://github.com/lathonez/clicker/commit/105295a)) + + +# 2.13.0 (2017-04-25) + +### Features + +* **Unit**: Improve test coverage [#243](https://github.com/lathonez/clicker/pull/243) ([3e2551f](https://github.com/lathonez/clicker/commit/3e2551f)) + + +# 2.12.1 (2017-04-20) + +### Bug Fixes + +* **Unit**: Remove unused mocks code [#241](https://github.com/lathonez/clicker/issues/241) ([e97c14e](https://github.com/lathonez/clicker/commit/e97c14e)) + + +# 2.12.0 (2017-04-11) + +### Features + +* **Update**: Update to Ionic 3.0.1 and @angular/cli 1.0.0 (implying @angular 4.0.0) ([0ed7a16](https://github.com/lathonez/clicker/commit/0ed7a16)) +* **Unit**: Exclude mocks and config from coverage [#221](https://github.com/lathonez/clicker/issues/221) ([096e063](https://github.com/lathonez/clicker/commit/096e063)) + + +# 2.11.0 (2017-04-11) + +### Revert Deprecation + +The reverted code now lives on the [ionic-unit-testing-example branch](https://github.com/lathonez/clicker/commits/ionic-unit-testing-example) + +* **Update**: REVERT: Look at deprecating this repo [#239](https://github.com/lathonez/clicker/issues/239): ([bf946c7..7dbd3b8](https://github.com/lathonez/clicker/compare/bf946c7b819a3900f12947da7c92896329caa7ff...7dbd3b83a1301c9aaaa9b790b7c33655cb73e0ca)) + + +# 2.10.0 (2017-03-28) + +### Deprecate! + +(This version never really existed - I should have branced off but I didn't) + +* **Update**: Look at deprecating this repo [#239](https://github.com/lathonez/clicker/issues/239): ([689c02f..d56c7d6](https://github.com/lathonez/clicker/compare/689c02f381724822a25816677ae4c41fd554b46d...d56c7d6eca37f5064e63e2748b06e72138b0c99d)) + + +# 2.9.1 (2017-03-14) + +### Bug Fixes + +* **Build**: Reinstate tsconfig.json excludes for `ionic build --prod` [#236](https://github.com/lathonez/clicker/issues/236) ([f75ec74](https://github.com/lathonez/clicker/commit/f75ec74)) + + +# 2.9.0 (2017-02-27) + +### Features + +* **Update**: Update to Ionic 2.1.0 and @angular/cli rc.0 (implying @angular 2.4.0) ([13a6cd6](https://github.com/lathonez/clicker/commit/13a6cd6)) + + +# 2.8.0 (2016-02-24) + +### Features + +* **Unit**: Add functions to PlatforMock to facilitate testing of Slides [#227](https://github.com/lathonez/clicker/issues/227) ([d7ee4d4](https://github.com/lathonez/clicker/commit/d7ee4d4)) + + +# 2.7.0 (2017-01-26) + +### Features + +* **Update**: Update to Ionic 2.0.0 FINAL ([bac5ce3](https://github.com/lathonez/clicker/commit/bac5ce3)) + + +# 2.6.1 (2017-01-25) + +### Bug Fixes + +* **Build**: Remove [#135](https://github.com/lathonez/clicker/pull/135), it's been obsolute since ionic went back to webpack (and we moved to ng-cli) will be implemented by Ionic in https://github.com/driftyco/ionic-cli/issues/1205 [#217](https://github.com/lathonez/clicker/issues/217) ([5a3e177](https://github.com/lathonez/clicker/commit/5a3e177)) + + +# 2.6.0 (2017-01-18) + +### Features + +* **Update**: Update to Ionic RC5 ([7328aad](https://github.com/lathonez/clicker/commit/7328aad)) + +The above update includes creating a mock for Ionic's platform class and using it as a provider in test.ts. I have not attempted to mock out the whole class, what is there is sufficient for clicker. If anything is missing for your app, you can add easily by referencing the signature in node_modules/ionic-angular/platform/platform.d.ts. + +* **E2E**: Use serve-static to run protractor with a single commmand. closes [#210](https://github.com/lathonez/clicker/issues/210) ([8331382](https://github.com/lathonez/clicker/commit/8331382)) + + +# 2.5.3 (2016-01-03) + +### Bug Fixes + +* **Unit**: Add separate script for coverage as it breaks sourcemaps [#203](https://github.com/lathonez/clicker/issues/203) ([1f9993b](https://github.com/lathonez/clicker/commit/1f9993b)) + + +# 2.5.2 (2016-01-02) + +### Bug Fixes + +* **Unit**: Remove redundant "initialises with app" test [#202](https://github.com/lathonez/clicker/issues/202) ([d12d8ca](https://github.com/lathonez/clicker/commit/d12d8ca)) + + +# 2.5.1 (2016-12-26) + +### Bug Fixes + +* **Unit**: Fixing codecov integration [#200](https://github.com/lathonez/clicker/issues/200) ([ecf5d87](https://github.com/lathonez/clicker/commit/ecf5d87)) +* **Unit**: Reverting to typescript 2.0.10 to fix Jasmie typings misisng when ionic:build'ing [#159](https://github.com/lathonez/clicker/issues/159) ([973292a](https://github.com/lathonez/clicker/commit/973292a)) + + +# 2.5.0 (2016-12-22) + +### Features + +* **Update**: Update to Ionic RC4 ([b27aade](https://github.com/lathonez/clicker/commit/b27aade)) + +The above update includes a change to angular-cli which means you need to add the current working directory to template urls: + +For example: + +```javascript + templateUrl: 'hello-ionic.html', +``` + +becomes + +```javascript + templateUrl: './hello-ionic.html', +``` + +Also Ionic's DomController needs adding into test.ts as a provider + + + +# 2.4.0 (2016-12-08) + +### Features + +* **Unit**: Add example of TestBed component compiling manually in the component (Page2) as opposed to calling TestUtils closes [#179](https://github.com/lathonez/clicker/issues/179) ([51b5e23](https://github.com/lathonez/clicker/commit/51b5e23)) + +### Bug Fixes + +* **Unit**: Add mime type to karma.conf.js, closes [#178](https://github.com/lathonez/clicker/issues/178) ([5c0ffeb](https://github.com/lathonez/clicker/commit/5c0ffeb)) + +# 2.3.0 (2016-11-18) + +### Bug Fixes + +* **Unit/E2E**: Reverting to using jasmine types in main tsconfig.json as excluding spec means editors don't resolve modules, closes [#174](https://github.com/lathonez/clicker/issues/174) ([0ae024b](https://github.com/lathonez/clicker/commit/0ae024b)) + +### Features + +* **Update**: Update to Ionic RC3 ([d1a7df2](https://github.com/lathonez/clicker/commit/d1a7df2)) +* **Update**: Update Angular-CLI PR [#176](https://github.com/lathonez/clicker/pull/176)([b7486d2](https://github.com/lathonez/clicker/commit/b7486d2)) + + +# 2.2.1 (2016-11-17) + +### Bug Fixes + +* **Unit**: Don't mock Ionic when configuring the testing module [#175](https://github.com/lathonez/clicker/issues/175) ([2f49f96](https://github.com/lathonez/clicker/commit/2f49f96)) + + +# 2.2.0 (2016-11-07) + +### Features + +* **Update**: Update to Ionic RC2 ([694be18](https://github.com/lathonez/clicker/commit/694be18)) + + +# 2.1.2 (2016-11-03) + +### Bug Fixes + +* **Build**: Using environment variables for travis build directory [#162](https://github.com/lathonez/clicker/issues/162) ([9f81dc4](https://github.com/lathonez/clicker/commit/9f81dc4)) + + +# 2.1.1 (2016-10-31) + +### Bug Fixes + +* **Unit**: Windows 10 needs explicit excludes [#159](https://github.com/lathonez/clicker/issues/159) ([0e55cc1](https://github.com/lathonez/clicker/commit/0e55cc1)) + + +# 2.1.0 (2016-10-30) + +### Features + +* **E2E**: Re-implement e2e post rc1 ([a5ea70c](https://github.com/lathonez/clicker/commit/a5ea70c)) +* **Update**: Update deps ([b0a95a6](https://github.com/lathonez/clicker/commit/b0a95a6)) +* **Unit**: Rationalise beforeEach boilerplate into TestUtils.beforeEachCompiler ([a5fef51](https://github.com/lathonez/clicker/commit/a5fef51)) +* **Build**: Using workaround for missing SDK license ([b6751ac](https://github.com/lathonez/clicker/commit/a5fef51)) + +### Bug Fixes + +* **Unit**: Barrels aren't working with modules since latest upgrade: ([0c0993b](https://github.com/lathonez/clicker/commit/0c0993b)) +* **Unit**: Add compileComponents to beforeEach, use ng-cli's tsconfig.json for testing for [#155](https://github.com/lathonez/clicker/issues/155) ([d59d968](https://github.com/lathonez/clicker/commit/d59d968)) + + +# 2.0.0 (2016-10-16) + +### Features + +**Re-write project for to Ionic 2.0.0.rc0**: + +* restructure app for new directory structure: ([3e92e3c](https://github.com/lathonez/clicker/commit/3e92e3c)) ([bcb1c80](https://github.com/lathonez/clicker/commit/bcb1c80)) ([a992f38](https://github.com/lathonez/clicker/commit/a992f38)) +* merge config ([055bf22](https://github.com/lathonez/clicker/commit/055bf22)) +* build errors ([58d4732](https://github.com/lathonez/clicker/commit/58d4732)) +* remove ngrx ([07d5e6c](https://github.com/lathonez/clicker/commit/07d5e6c)) +* POC for ng-cli setup ([7b1a80c](https://github.com/lathonez/clicker/commit/7b1a80c)) +* centralise boilerplate ([c7e9b57](https://github.com/lathonez/clicker/commit/c7e9b57)) +* update *.spec ([819caf5](https://github.com/lathonez/clicker/commit/819caf5)) +* all unit tests working ([02b0901](https://github.com/lathonez/clicker/commit/02b0901)) +* tidy up and update to rc1 ([f66d45a](https://github.com/lathonez/clicker/commit/f66d45a)) + + +# 1.14.0 (2016-09-08) + +### Features + +* **Update**: Update deps PR [#143]([e69fad2](https://github.com/lathonez/clicker/commit/e69fad2)) + + +# 1.13.1 (2016-09-07) + +### Bug Fixes + +* **Unit**: Remove coverage transform on debug PR [#140](https://github.com/lathonez/clicker/pull/140) ([4eab003](https://github.com/lathonez/clicker/commit/4eab003)) + + +# 1.13.0 (2016-09-01) + +### Features + +* **Build**: Dynamic config based on environment PR [#135](https://github.com/lathonez/clicker/pull/135) ([b1a9489](https://github.com/lathonez/clicker/commit/b1a9489)) + + + +# 1.12.1 (2016-08-25) + +### Bug Fixes + +* **E2E**: Make test windows friendly PR [#136](https://github.com/lathonez/clicker/issues/136) ([7fcf752](https://github.com/lathonez/clicker/commit/7fcf752)) + + +# 1.12.0 (2016-08-24) + +### Features + +* **App**: Implement ngrx PR [#133](https://github.com/lathonez/clicker/pull/133) ([ab2cd9a](https://github.com/lathonez/clicker/commit/ab2cd9a)) + + +# 1.11.0 (2016-08-17) + +### Features + +* **E2E**: Add screenshot reporter for Protractor PR [#132](https://github.com/lathonez/clicker/pull/132) ([dd1242d](https://github.com/lathonez/clicker/commit/dd1242d)) + + +# 1.10.0 (2016-08-09) + +### Features + +* **Update**: Update to Ionic 2.0.0.beta.11 ([01530a7](https://github.com/lathonez/clicker/commit/01530a7)) + + +# 1.9.2 (2016-07-12) + +### Bug Fixes + +* **Unit Test**: Make (source) source maps available to debug mode by remove the coverage transform which seems to be mangling them [#119](https://github.com/lathonez/clicker/issues/119) ([74ec6fa](https://github.com/lathonez/clicker/commit/74ec6fa)) + + +# 1.9.1 (2016-07-11) + +### Bug Fixes + +* **Unit Test**: Make sure we're cleaning before running unit tests [#86](https://github.com/lathonez/clicker/issues/86) ([1f25717](https://github.com/lathonez/clicker/commit/1f25717)) + + +# 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) ([297dd00](https://github.com/lathonez/clicker/commit/297dd00)) + + +# 1.8.1 (2016-07-04) + +### Bug Fixes + +* **Unit Test**: Remove coverage from debug mode [#115](https://github.com/lathonez/clicker/issues/115) ([0bdc6e4](https://github.com/lathonez/clicker/commit/0bdc6e4)) + + +# 1.8.0 (2016-06-30) + +### Features + +* **Unit Test**: Upgrade karma to 1.1.0 [#110](https://github.com/lathonez/clicker/issues/110) ([1a061b7](https://github.com/lathonez/clicker/commit/1a061b7)) + + +# 1.7.1 (2016-06-29) + +### Bug Fixes + +* **E2E**: Problems introduced with 1.7.0, missing menu, incorrect title, and transition timing on Page2 ([c0562f0](https://github.com/lathonez/clicker/commit/c0562f0)) + + +# 1.7.0 (2016-06-29) + +### Features + +* **Unit Test**: Implement Barrels and move mocks to individual files PR [#109](https://github.com/lathonez/clicker/pull/109) ([0734e45](https://github.com/lathonez/clicker/commit/0734e45)) +* **Update**: Update to Ionic 2.0.0.beta.10 ([1b1b2e3](https://github.com/lathonez/clicker/commit/1b1b2e3)) + + +# 1.6.0(2016-06-19) + +### Features + +* **Unit Test**: Centralise Testing DI Boilerplate ([#103](https://github.com/lathonez/clicker/issues/103)) ([4769372](https://github.com/lathonez/clicker/commit/4769372)) + + +# 1.5.0 (2016-06-18) + +### Features + +* **Update**: Update to Ionic 2.0.0.beta.9 ([a61432c](https://github.com/lathonez/clicker/commit/a61432c)) + + +# 1.4.0 (2016-06-07) + +### Features + +* **Update**: Update to Ionic 2.0.0.beta.8 PR [#98](https://github.com/lathonez/clicker/pull/98) ([5f8d5fb](https://github.com/lathonez/clicker/commit/5f8d5fb20f456d2e8b98d9db1098c51588e6568e)) + + +# 1.3.1 (2016-05-25) + +### Bug Fixes + +* **App**: Duplicate include of Reflect.js ([#96](https://github.com/lathonez/clicker/issues/96)) ([15bd2e6](https://github.com/lathonez/clicker/commit/15bd2e6)) + + +# 1.3.0 (2016-05-22) + +### Features + +* **Update**: Update to Ionic 2.0.0.beta.7 and Angular 2.0.0.rc.1 PR [#93](https://github.com/lathonez/clicker/pull/93) big update due to Angular RC1 ([bc392c3](https://github.com/lathonez/clicker/commit/bc392c3)) + + +# 1.2.0 (2016-04-28) + +### Features + +* **Unit Test**: Use browserify within Karma (see below) ([5f096f7](https://github.com/lathonez/clicker/commit/5f096f7)) +* **Unit Test**: Remove glob, gulp-load-plugins and gulp-util dependencies, refactor gulpfile to remove most typing dependencies ([c67beae](https://github.com/lathonez/clicker/commit/c67beae)) + +### Browserify in Karma + +* actually enables sourcemaps in Karma +* quicker builds +* remove the requirement for remapping coverage as it happens with browserify +* remove `test/config.ts` as it was only being used in gulpfile.ts, and as we're making everything lighter it seems unlikely to be extended +* remove `test/app.stub.js` Now using a workaround in Karma instead. See ([#79](https://github.com/lathonez/clicker/issues/79)) + + +# 1.1.1 (2016-04-27) + +### Bug Fixes + +* **Unit Test**: Merge PR [#87](https://github.com/lathonez/clicker/pull/87) which sorts out Karma's random "Formatting Errors" ([f3b6641](https://github.com/lathonez/clicker/commit/f3b6641)) +* **E2E**: Remove absolute path for protractor ([#83](https://github.com/lathonez/clicker/issues/83)) ([5894a5a](https://github.com/lathonez/clicker/commit/5894a5a)) +* **Unit Test**: Increase browser timeout ([#82](https://github.com/lathonez/clicker/issues/82)) ([ff59b33](https://github.com/lathonez/clicker/commit/ff59b33)) + + +# 1.1.0 (2016-04-23) + +### Features + +* **Update**: Update to Ionic 2.0.0.beta.6 ([a571e7c](https://github.com/lathonez/clicker/commit/a571e7c)) + +### Bug Fixes + +* **Unit Test**: Merge PR [#80](https://github.com/lathonez/clicker/pull/80) removing a trailing `;` ([e8fb205](https://github.com/lathonez/clicker/commit/e8fb205)) + + +# 1.0.0 (2016-04-20) + +### Features + +* **Unit Test**: [Lightweightify](https://github.com/lathonez/clicker/issues/68) ([440ac08](https://github.com/lathonez/clicker/commit/440ac08)) + +### BREAKING CHANGES + +Lightweightify is a major refactor to the framework. See [the issue](https://github.com/lathonez/clicker/issues/68) for further info. + +**The main changes are listed below** +* unit tests are now bundled into `test.bundle.js` +* sourcemaps are now produced @ `test.bundle.js.map` +* coverage is remapped onto source Typescript meaning that transpiled javascript is no longer pushed to `./coverage/source` +* gulpfile.ts has had a [large refactor](https://github.com/lathonez/clicker/commit/3119b92) away from the ng2-seed style and back toward what everyone else seems to be doing +* `app.stub.ts` has been renamed and largely re-written in `app.stub.js`, as was required by a change in the process + +**The following files have been removed as they are no longer required** +* .travis/push_built_tests.sh +* coverage/source/**.js +* test/ionic-angular.js +* test/test-main.js + +**Migration** + +I strongly suggest adopting the new framework as it brings with it many benefits. I will update this if firsthand information is forthcoming +* nuke the `./test` and replace it with the new version. Note that any proxies for external modules in `karma.conig.js` are no longer required, thus there should be no project specifics in that folder +* remove the `export function main()` wrapper from your `*.spec`: [example](https://github.com/lathonez/clicker/commit/f1b72ee) +* merge `package.json` (just for dependencies), some have been removed, some added. Note dependencies are now documented in README.md + +Hopefully that should be it. + + +# 0.12.0 (2016-04-13) + +### Features + +* **Deps**: Merge PR [#71](https://github.com/lathonez/clicker/pull/71) correcting `ionic-gulp-webpack` dependency ([efc9cdf](https://github.com/lathonez/clicker/commit/efc9cdf)) +* **Update**: Upgrade deps (angular2, etc) to latest Ionic 2.0.0.beta.4 ([5649061](https://github.com/lathonez/clicker/commit/5649061)) + + +# 0.11.0 (2016-03-25) + +### Bug Fixes + +* **Docs**: Add "two shell windows" to README.md after someone misread it ([69b5ccb](https://github.com/lathonez/clicker/commit/69b5ccb)) +* **Travis**: Adding install for Phantom manually to get around this 403 in Travis ([aa6173a](https://github.com/lathonez/clicker/commit/aa6173a)) + +### Features + +* **Update**: Merge PR [#62](https://github.com/lathonez/clicker/pull/62) update to Ionic 2.0.0.beta.3 ([f2f66d9](https://github.com/lathonez/clicker/commit/f2f66d9)) + + +# 0.10.1 (2016-03-22) + +### Bug Fixes + +* **Deps**: Merge PR [#60](https://github.com/lathonez/clicker/pull/60) missing `gulp-util` ([4398038](https://github.com/lathonez/clicker/commit/4398038)) + + +# 0.10.0 (2016-03-18) + +### Bug Fixes + +* **E2E**: Use sendKeys workaround to send delayed keystrokes for randomly failing e2e ([642973d](https://github.com/lathonez/clicker/commit/642973d)) +* **Build**: Merge PR [#56](https://github.com/lathonez/clicker/pull/56) remove `;` ([9ecdbf4](https://github.com/lathonez/clicker/commit/9ecdbf4)) + +### Features + +* **Travis**: Testing e2e in Travis ([4a6f5eb](https://github.com/lathonez/clicker/commit/4a6f5eb)) +* **Unit Test**: DOM test skeleton for @Page Clicker List ([65bf5d5](https://github.com/lathonez/clicker/commit/65bf5d5)) + + +# 0.9.0 (2016-03-12) + +### Features + +* **Build**: Hook into Ionic's gulpfile directly and remove ionic-app-lib ([6a4b17c](https://github.com/lathonez/clicker/commit/6a4b17c)) + + +# 0.8.0 (2016-03-11) + +### Features + +* **E2E**: Merge PR [#50](https://github.com/lathonez/clicker/pull/50) containing e2e tests with protractor ([440d463](https://github.com/lathonez/clicker/commit/440d463)) + + +# 0.7.0 (2016-03-09) + +### Bug Fixes + +* **Update**: Fixing CSS references broken in upgrade (again) ([40d682c](https://github.com/lathonez/clicker/commit/40d682c)) + +### Features + +* **Update**: Upgrade deps (angular2, etc) to latest Ionic 2.0.0.beta.2 ([1e9cc11](https://github.com/lathonez/clicker/commit/1e9cc11)) +* **E2E**: Starting groundwork for e2e testing based on ng2 seed ([36572b6](https://github.com/lathonez/clicker/commit/36572b6)) + + +# 0.6.1 (2016-03-06) + +### Bug Fixes + +* **Build**: Count source and target files in buildlocker rather than relying on existance of a specific file in the target ([6137fd8](https://github.com/lathonez/clicker/commit/6137fd8)) + + +# 0.6.0 (2016-03-05) + +### Bug Fixes + +* **Travis**: Ionic Framework .beta.18 was broken on Travis ([#24](https://github.com/lathonez/clicker/issues/24)), upgrade to .19 ([6003691](https://github.com/lathonez/clicker/commit/6003691)) +* **Update**: Upgrade angular2 to beta.9 which supports typescript 1.8.* ([#9](https://github.com/lathonez/clicker/issues/9) ([d46f017]) + +### Features + +* **Build**: Using Ionic's build tools in `ionic-app-lib` ([9005006](https://github.com/lathonez/clicker/commit/9005006)) +* **Unit Test**: Enable form tests again after our PR is merged into Ionic ([#12](https://github.com/lathonez/clicker/issues/12)) ([aa726486](https://github.com/lathonez/clicker/commit/aa726486)) +* **Build**: Improve workaround for `ionic-angular/index` by creating own references in `ionic-angular.js` ([#27](https://github.com/lathonez/clicker/issues/27)) ([21ba465](https://github.com/lathonez/clicker/commit/21ba465)) + + +# 0.5.1 (2016-03-03) + +### Bug Fixes + +* **Update**: Update webpack css font reference ([#28](https://github.com/lathonez/clicker/pull/28)) ([c333f56](https://github.com/lathonez/clicker/commit/c333f56)) + + +# 0.5.0 (2016-03-02) + +### Features + +* **Build**: Move conifg to `./test` ([#27](https://github.com/lathonez/clicker/issues/26)) ([f78363d](https://github.com/lathonez/clicker/commit/f78363d)) +* **Update**: Update to Ionic 2.0.0.beta.1 ([#25](https://github.com/lathonez/clicker/issues/25)) ([67bad59](https://github.com/lathonez/clicker/commit/67bad59)) +* **Build**: Use Cordova Hooks to prevent test files from ending up inside the apk ([82be26a](https://github.com/lathonez/clicker/commit/82be26a)) + + +# 0.4.0 (2016-02-25) + +### Bug Fixes + +* **Deps**: Use explicit versioning in npm ([#17]](https://github.com/lathonez/clicker/issues/17)) ([90f677d](https://github.com/lathonez/clicker/commit/90f677d)) + +### Features + +* **Deps**: Dependency Badge ([#13](https://github.com/lathonez/clicker/issues/13)) ([b30e24d](https://github.com/lathonez/clicker/commit/b30e24d)) +* **Deps**: Migrate from tsd to typings ([#10](https://github.com/lathonez/clicker/issues/10)) ([120c02e](https://github.com/lathonez/clicker/commit/120c02e)) +* **Build**: Move spec to source directory ([#11](https://github.com/lathonez/clicker/issues/11)) ([3d3650f4](https://github.com/lathonez/clicker/commit/3d3650f4)) + + +# 0.3.1 (2016-02-24) + +### Bug Fixes + +* **Build**: Merge PR [#8](https://github.com/lathonez/clicker/pull/8) Fixing test file name ([f524bcf](https://github.com/lathonez/clicker/commit/f524bcf)) + + +# 0.3.0 (2016-02-19) + +### Bug Fixes + +* **Unit Test**: Previously using a bunch of hacks accessing private control variables ([cc5c0a5](https://github.com/lathonez/clicker/commit/cc5c0a5)) +* **App**: Need to use one or the other when typing ([cc2d919](https://github.com/lathonez/clicker/commit/cc2d919)) + +### Features + +* **App**: Update tslint to enforce strict typing and change typescript to be strictly typed ([cc2d919](https://github.com/lathonez/clicker/commit/cc2d919)) + + +# 0.2.0 (2016-02-18) + +### Bug Fixes + +* **Unit Test**: Misconfiguration in Karma ([5af7958](https://github.com/lathonez/clicker/commit/5af7958)) +* **Unit Test**: Previously using toBeDefined, but they will always be defined! ([d8e1cf5](https://github.com/lathonez/clicker/commit/d8e1cf5)) + +### Features + +* **Unit Test**: Using Angular2's DI to test component's in the DOM ([07f60bf](https://github.com/lathonez/clicker/commit/07f60bf)) +* **App**: Utility function should be placed in `.catch` in any promise to debug errors ([0621169](https://github.com/lathonez/clicker/commit/07f60bf)) + + +# 0.1.0 (2016-02-17) + +### Bug Fixes + +* **Update**: CSS was broken in Ionic upgrade ([98c13c4](https://github.com/lathonez/clicker/commit/98c13c4)) +* **Update**: Icons were broken in Ionic upgrade ([c954c15](https://github.com/lathonez/clicker/commit/c954c15)) + +### Features + +* **Update**: Upgrade deps (angular2, etc) to Ionic 2.0.0.beta.0 ([830ae8b](https://github.com/lathonez/clicker/commit/830ae8b)) +* **App**: Adding MIT License ([a3475cf](https://github.com/lathonez/clicker/commit/a3475cf)) + + +# 0.0.1 (2016-01-08) + +### Features + +* **App**: ([f8865b0](https://github.com/lathonez/clicker/commit/f8865b0)) +* **Travis**: Build and deploy using Travis CI ([fb045f6](https://github.com/lathonez/clicker/commit/fb045f6)) +* **Unit Test**: Initial unit test set up now working ([04a1028](https://github.com/lathonez/clicker/commit/04a1028)) +* **App**: Clickers functionality including local storage ([9d04f8d](https://github.com/lathonez/clicker/commit/9d04f8d)) +* **App**: Skeleton app using `ionic start --v2 --ts clickers` ([8bde84f](https://github.com/lathonez/clicker/commit/8bde84f)) diff --git a/app/LICENSE b/app/LICENSE new file mode 100644 index 0000000..424fc94 --- /dev/null +++ b/app/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2015-2016 Stephen Hazleton + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/app/README.md b/app/README.md new file mode 100644 index 0000000..0e327b7 --- /dev/null +++ b/app/README.md @@ -0,0 +1,73 @@ +## Ionic 2 Demo / Seed Project : Karma + Protractor + Travis +[![Build Status](https://travis-ci.org/lathonez/clicker.svg?branch=master)](https://travis-ci.org/lathonez/clicker) [![Build status](https://ci.appveyor.com/api/projects/status/github/lathonez/clicker?svg=true)](https://ci.appveyor.com/project/lathonez/clicker) [![codecov.io](https://codecov.io/github/lathonez/clicker/coverage.svg?branch=master)](https://codecov.io/github/lathonez/clicker?branch=master) [![Code Climate](https://codeclimate.com/github/lathonez/clicker/badges/gpa.svg)](https://codeclimate.com/github/lathonez/clicker) [![MIT license](http://img.shields.io/badge/license-MIT-brightgreen.svg)](http://opensource.org/licenses/MIT) [![Dependency Status](https://david-dm.org/lathonez/clicker/status.svg)](https://david-dm.org/lathonez/clicker) [![devDependency Status](https://david-dm.org/lathonez/clicker/dev-status.svg)](https://david-dm.org/lathonez/clicker#info=devDependencies) +

+ +

+ +## Ionic's official repo + +Ionic have created an official unit testing example over at [driftyco/ionic-unit-testing-example](https://github.com/driftyco/ionic-unit-testing-example). To understand why this repo still exists, see [#239](https://github.com/lathonez/clicker/issues/239), where we looked at deprecating `clicker` in favour of `ionic-unit-testing-example`. + +Broadly, the official example repo: + +* Is not mature or production ready +* Is intended as a simple example only / will not be supported by Ionic +* Does not support e2e +* Does not use `@angular/cli` and thus lacks testing support from the wider ng2 community + +For ~large or production projects, I suggest using clicker. For small apps / side projects the official example should suffice. + +## Install & Start + +You need to be running [the latest node LTS](https://nodejs.org/en/download/) or newer + +```bash +git clone https://github.com/lathonez/clicker.git +cd clicker +npm install +npm start # start the application (ionic serve) +``` + +Running as root? You probably shouldn't be. If you need to: `npm run postinstall` before `npm start`. [#111](https://github.com/lathonez/clicker/issues/111) for more info. + +## Run Unit Tests +```bash +npm test # run unit tests +``` + +## Run E2E +``` +npm run e2e +``` + +## Blog Topics + +* [Unit testing walkthrough](http://lathonez.com/2017/ionic-2-unit-testing/) +* [E2E testing walkthrough](http://lathonez.com/2017/ionic-2-e2e-testing/) +* [Removing assets from the APK](http://lathonez.com/2016/cordova-remove-assets/) + +## Contribute +PRs are welcome, see the [roadmap sticky](https://github.com/lathonez/clicker/issues/38) + +## Help + +* If you can't get the testing working, raise an issue +* If you have a general question about unit testing (e.g. how can I write a unit test for `some-module`), see [#191](https://github.com/lathonez/clicker/issues/191) + +## Acks + +* This started out as a fork of [Angular 2 Seed](https://github.com/mgechev/angular2-seed) and would not be possible without it +* @bengro for the lightweightify inspiration (#68) +* @ric9176 and @DanielaGSB for E2E tests (#50) +* @tja4472 for the ngrx implementation (#133) +* [Everyone else](https://github.com/lathonez/clicker/graphs/contributors) for the advice, help, PRs etc + +## Changelog + +See the changelog [here](https://github.com/lathonez/clicker/blob/master/CHANGELOG.md) + +## Updated for: + +* **@angular/*:** 4.1.2 +* **@angular/cli:**: 1.0.6 +* **@ionic-angular:** 3.3.0 diff --git a/app/appveyor.yml b/app/appveyor.yml new file mode 100644 index 0000000..9f3c9c7 --- /dev/null +++ b/app/appveyor.yml @@ -0,0 +1,24 @@ +# Test against this version of Node.js +environment: + nodejs_version: "6.5" + +# Install scripts. (runs after repo cloning) +install: + # Get the latest stable version of Node.js or io.js + - ps: Install-Product node $env:nodejs_version + # install modules + - npm install -g cordova@6.4.0 ionic@3.2.0 + - npm install + +build: none + +# Post-install test scripts. +test_script: + # Output useful info for debugging. + - node --version + - npm --version + # run tests + - npm run test-ci + - npm run postinstall # this shouldn't be necessary, but it is + - npm run e2e + diff --git a/app/config.xml b/app/config.xml new file mode 100644 index 0000000..cd962c7 --- /dev/null +++ b/app/config.xml @@ -0,0 +1,35 @@ + + + Clicker + An awesome Ionic/Cordova app. + Stephen Hazleton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/e2e/app.e2e-spec.ts b/app/e2e/app.e2e-spec.ts new file mode 100644 index 0000000..b7b56af --- /dev/null +++ b/app/e2e/app.e2e-spec.ts @@ -0,0 +1,44 @@ +import { browser, element, by } from 'protractor'; + +describe('ClickerApp', () => { + + beforeEach(() => { + browser.get(''); + }); + + it('should have a title', () => { + expect(browser.getTitle()).toEqual('Clickers'); + }); + + it('should have {nav}', () => { + expect(element(by.css('ion-navbar')).isPresent()).toEqual(true); + }); + + it('should have correct nav text for Home', () => { + expect(element(by.css('ion-navbar:first-child')).getText()).toContain('Clickers'); + }); + + 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'); + }); + }); + + it('the left menu has a link with title Clickers', () => { + 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'); + }); + }); +}); diff --git a/app/e2e/clickerList.e2e-spec.ts b/app/e2e/clickerList.e2e-spec.ts new file mode 100644 index 0000000..d332ca6 --- /dev/null +++ b/app/e2e/clickerList.e2e-spec.ts @@ -0,0 +1,42 @@ +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/e2e/page2.e2e-spec.ts b/app/e2e/page2.e2e-spec.ts new file mode 100644 index 0000000..3b7647c --- /dev/null +++ b/app/e2e/page2.e2e-spec.ts @@ -0,0 +1,22 @@ +import { browser, element, by, ElementFinder } from 'protractor'; + +let message: ElementFinder = element(by.className('message')); + +describe('Page2', () => { + + beforeEach(() => { + browser.get(''); + }); + + it('should have correct text when Goodbye Ionic is selected', () => { + 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'); + return items[1]; + }); + }); + }); +}); diff --git a/app/e2e/tsconfig.e2e.json b/app/e2e/tsconfig.e2e.json new file mode 100644 index 0000000..0dc33f8 --- /dev/null +++ b/app/e2e/tsconfig.e2e.json @@ -0,0 +1,12 @@ +{ + "extends": "../tsconfig.ng-cli.json", + "compilerOptions": { + "outDir": "../out-tsc/e2e", + "module": "commonjs", + "target": "es5", + "types": [ + "jasmine", + "node" + ] + } +} diff --git a/app/ionic.config.json b/app/ionic.config.json new file mode 100644 index 0000000..19a581a --- /dev/null +++ b/app/ionic.config.json @@ -0,0 +1,5 @@ +{ + "name": "clicker", + "app_id": "f0281363", + "type": "ionic-angular" +} diff --git a/app/karma.conf.js b/app/karma.conf.js new file mode 100644 index 0000000..84b4cd5 --- /dev/null +++ b/app/karma.conf.js @@ -0,0 +1,44 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/0.13/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular/cli'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage-istanbul-reporter'), + require('@angular/cli/plugins/karma') + ], + client:{ + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + files: [ + { pattern: './src/test.ts', watched: false } + ], + preprocessors: { + './src/test.ts': ['@angular/cli'] + }, + mime: { + 'text/x-typescript': ['ts','tsx'] + }, + coverageIstanbulReporter: { + reports: [ 'html', 'lcovonly' ], + fixWebpackSourcePaths: true + }, + angularCli: { + environment: 'dev' + }, + reporters: config.angularCli && config.angularCli.codeCoverage + ? ['progress', 'coverage-istanbul'] + : ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false + }); +}; diff --git a/app/package.json b/app/package.json new file mode 100644 index 0000000..3bdb664 --- /dev/null +++ b/app/package.json @@ -0,0 +1,76 @@ +{ + "name": "Clicker", + "author": "Stephen Hazleton", + "homepage": "https://github.com/lathonez/clicker", + "private": true, + "scripts": { + "clean": "ionic-app-scripts clean", + "build": "ionic-app-scripts build", + "ionic:build": "ionic-app-scripts build", + "ionic:serve": "ionic-app-scripts serve", + "e2e": "ionic-app-scripts build && protractor", + "postinstall": "mkdirp www && cordova prepare && webdriver-manager update", + "start": "ionic serve", + "test": "ng test", + "test-coverage": "ng test --code-coverage", + "test-ci": "ng test --watch=false --code-coverage" + }, + "version": "2.15.0", + "dependencies": { + "@angular/common": "4.1.2", + "@angular/compiler": "4.1.2", + "@angular/compiler-cli": "4.1.2", + "@angular/core": "4.1.2", + "@angular/forms": "4.1.2", + "@angular/http": "4.1.2", + "@angular/platform-browser": "4.1.2", + "@angular/platform-browser-dynamic": "4.1.2", + "@angular/router": "4.1.2", + "@ionic-native/core": "3.10.2", + "@ionic-native/splash-screen": "3.10.2", + "@ionic-native/status-bar": "3.10.2", + "@ionic/storage": "2.0.1", + "ionic-angular": "3.3.0", + "ionicons": "3.0.0", + "rxjs": "5.1.1", + "sw-toolbox": "3.6.0", + "zone.js": "0.8.11" + }, + "devDependencies": { + "@angular/cli": "1.0.6", + "@ionic/app-scripts": "1.3.7", + "@ionic/cli-plugin-ionic-angular": "1.2.0", + "@types/jasmine": "2.5.41", + "@types/node": "7.0.4", + "codecov": "2.2.0", + "jasmine-core": "2.5.2", + "jasmine-spec-reporter": "3.2.0", + "karma": "1.4.1", + "karma-chrome-launcher": "2.1.1", + "karma-cli": "1.0.1", + "karma-coverage-istanbul-reporter": "0.2.0", + "karma-jasmine": "1.1.0", + "karma-jasmine-html-reporter": "0.2.2", + "mkdirp": "0.5.1", + "protractor": "5.1.0", + "ts-node": "2.0.0", + "tslint": "4.5.0", + "tslint-eslint-rules": "3.2.3", + "typescript": "2.3.3" + }, + "cordovaPlugins": [ + "cordova-plugin-whitelist", + "cordova-plugin-console", + "cordova-plugin-statusbar", + "cordova-plugin-device", + "cordova-plugin-splashscreen", + "ionic-plugin-keyboard" + ], + "cordovaPlatforms": [], + "repository": { + "type": "git", + "url": "https://github.com/lathonez/clicker.git" + }, + "license": "MIT", + "description": "Clicker: An Ionic project" +} diff --git a/app/protractor.conf.js b/app/protractor.conf.js new file mode 100644 index 0000000..be4322d --- /dev/null +++ b/app/protractor.conf.js @@ -0,0 +1,33 @@ +// Protractor configuration file, see link for more information +// https://github.com/angular/protractor/blob/master/lib/config.ts + +const { SpecReporter } = require('jasmine-spec-reporter'); + +exports.config = { + allScriptsTimeout: 11000, + specs: [ + './e2e/**/*.e2e-spec.ts' + ], + capabilities: { + 'browserName': 'chrome' + }, + directConnect: true, + baseUrl: 'http://localhost:4200/', + framework: 'jasmine', + jasmineNodeOpts: { + showColors: true, + defaultTimeoutInterval: 30000, + print: function() {} + }, + useAllAngular2AppRoots: true, + beforeLaunch: function() { + require('ts-node').register({ + project: 'e2e/tsconfig.e2e.json' + }); + + require('connect')().use(require('serve-static')('www')).listen(4200); + }, + onPrepare() { + jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); + } +}; diff --git a/app/resources/android/icon/drawable-hdpi-icon.png b/app/resources/android/icon/drawable-hdpi-icon.png new file mode 100644 index 0000000..b910093 Binary files /dev/null and b/app/resources/android/icon/drawable-hdpi-icon.png differ diff --git a/app/resources/android/icon/drawable-ldpi-icon.png b/app/resources/android/icon/drawable-ldpi-icon.png new file mode 100644 index 0000000..16cd5db Binary files /dev/null and b/app/resources/android/icon/drawable-ldpi-icon.png differ diff --git a/app/resources/android/icon/drawable-mdpi-icon.png b/app/resources/android/icon/drawable-mdpi-icon.png new file mode 100644 index 0000000..64a6cbc Binary files /dev/null and b/app/resources/android/icon/drawable-mdpi-icon.png differ diff --git a/app/resources/android/icon/drawable-xhdpi-icon.png b/app/resources/android/icon/drawable-xhdpi-icon.png new file mode 100644 index 0000000..1605f69 Binary files /dev/null and b/app/resources/android/icon/drawable-xhdpi-icon.png differ diff --git a/app/resources/android/icon/drawable-xxhdpi-icon.png b/app/resources/android/icon/drawable-xxhdpi-icon.png new file mode 100644 index 0000000..56fb29e Binary files /dev/null and b/app/resources/android/icon/drawable-xxhdpi-icon.png differ diff --git a/app/resources/android/icon/drawable-xxxhdpi-icon.png b/app/resources/android/icon/drawable-xxxhdpi-icon.png new file mode 100644 index 0000000..e4a9152 Binary files /dev/null and b/app/resources/android/icon/drawable-xxxhdpi-icon.png differ diff --git a/app/resources/android/splash/drawable-land-hdpi-screen.png b/app/resources/android/splash/drawable-land-hdpi-screen.png new file mode 100644 index 0000000..66b12fe Binary files /dev/null and b/app/resources/android/splash/drawable-land-hdpi-screen.png differ diff --git a/app/resources/android/splash/drawable-land-ldpi-screen.png b/app/resources/android/splash/drawable-land-ldpi-screen.png new file mode 100644 index 0000000..7dceec7 Binary files /dev/null and b/app/resources/android/splash/drawable-land-ldpi-screen.png differ diff --git a/app/resources/android/splash/drawable-land-mdpi-screen.png b/app/resources/android/splash/drawable-land-mdpi-screen.png new file mode 100644 index 0000000..0dc2ba7 Binary files /dev/null and b/app/resources/android/splash/drawable-land-mdpi-screen.png differ diff --git a/app/resources/android/splash/drawable-land-xhdpi-screen.png b/app/resources/android/splash/drawable-land-xhdpi-screen.png new file mode 100644 index 0000000..39ae00c Binary files /dev/null and b/app/resources/android/splash/drawable-land-xhdpi-screen.png differ diff --git a/app/resources/android/splash/drawable-land-xxhdpi-screen.png b/app/resources/android/splash/drawable-land-xxhdpi-screen.png new file mode 100644 index 0000000..3f591b1 Binary files /dev/null and b/app/resources/android/splash/drawable-land-xxhdpi-screen.png differ diff --git a/app/resources/android/splash/drawable-land-xxxhdpi-screen.png b/app/resources/android/splash/drawable-land-xxxhdpi-screen.png new file mode 100644 index 0000000..253e6f1 Binary files /dev/null and b/app/resources/android/splash/drawable-land-xxxhdpi-screen.png differ diff --git a/app/resources/android/splash/drawable-port-hdpi-screen.png b/app/resources/android/splash/drawable-port-hdpi-screen.png new file mode 100644 index 0000000..e0dbb62 Binary files /dev/null and b/app/resources/android/splash/drawable-port-hdpi-screen.png differ diff --git a/app/resources/android/splash/drawable-port-ldpi-screen.png b/app/resources/android/splash/drawable-port-ldpi-screen.png new file mode 100644 index 0000000..8e93c2d Binary files /dev/null and b/app/resources/android/splash/drawable-port-ldpi-screen.png differ diff --git a/app/resources/android/splash/drawable-port-mdpi-screen.png b/app/resources/android/splash/drawable-port-mdpi-screen.png new file mode 100644 index 0000000..0aaad62 Binary files /dev/null and b/app/resources/android/splash/drawable-port-mdpi-screen.png differ diff --git a/app/resources/android/splash/drawable-port-xhdpi-screen.png b/app/resources/android/splash/drawable-port-xhdpi-screen.png new file mode 100644 index 0000000..64c27f8 Binary files /dev/null and b/app/resources/android/splash/drawable-port-xhdpi-screen.png differ diff --git a/app/resources/android/splash/drawable-port-xxhdpi-screen.png b/app/resources/android/splash/drawable-port-xxhdpi-screen.png new file mode 100644 index 0000000..f605e6a Binary files /dev/null and b/app/resources/android/splash/drawable-port-xxhdpi-screen.png differ diff --git a/app/resources/android/splash/drawable-port-xxxhdpi-screen.png b/app/resources/android/splash/drawable-port-xxxhdpi-screen.png new file mode 100644 index 0000000..2b993cf Binary files /dev/null and b/app/resources/android/splash/drawable-port-xxxhdpi-screen.png differ diff --git a/app/resources/icon.png b/app/resources/icon.png new file mode 100644 index 0000000..bee7766 Binary files /dev/null and b/app/resources/icon.png differ diff --git a/app/resources/ios/icon/icon-40.png b/app/resources/ios/icon/icon-40.png new file mode 100644 index 0000000..76cc53c Binary files /dev/null and b/app/resources/ios/icon/icon-40.png differ diff --git a/app/resources/ios/icon/icon-40@2x.png b/app/resources/ios/icon/icon-40@2x.png new file mode 100644 index 0000000..64b4906 Binary files /dev/null and b/app/resources/ios/icon/icon-40@2x.png differ diff --git a/app/resources/ios/icon/icon-40@3x.png b/app/resources/ios/icon/icon-40@3x.png new file mode 100644 index 0000000..1291a2f Binary files /dev/null and b/app/resources/ios/icon/icon-40@3x.png differ diff --git a/app/resources/ios/icon/icon-50.png b/app/resources/ios/icon/icon-50.png new file mode 100644 index 0000000..8bd51df Binary files /dev/null and b/app/resources/ios/icon/icon-50.png differ diff --git a/app/resources/ios/icon/icon-50@2x.png b/app/resources/ios/icon/icon-50@2x.png new file mode 100644 index 0000000..2676f8f Binary files /dev/null and b/app/resources/ios/icon/icon-50@2x.png differ diff --git a/app/resources/ios/icon/icon-60.png b/app/resources/ios/icon/icon-60.png new file mode 100644 index 0000000..11f3912 Binary files /dev/null and b/app/resources/ios/icon/icon-60.png differ diff --git a/app/resources/ios/icon/icon-60@2x.png b/app/resources/ios/icon/icon-60@2x.png new file mode 100644 index 0000000..b521048 Binary files /dev/null and b/app/resources/ios/icon/icon-60@2x.png differ diff --git a/app/resources/ios/icon/icon-60@3x.png b/app/resources/ios/icon/icon-60@3x.png new file mode 100644 index 0000000..dbc8303 Binary files /dev/null and b/app/resources/ios/icon/icon-60@3x.png differ diff --git a/app/resources/ios/icon/icon-72.png b/app/resources/ios/icon/icon-72.png new file mode 100644 index 0000000..4e5a827 Binary files /dev/null and b/app/resources/ios/icon/icon-72.png differ diff --git a/app/resources/ios/icon/icon-72@2x.png b/app/resources/ios/icon/icon-72@2x.png new file mode 100644 index 0000000..56fb29e Binary files /dev/null and b/app/resources/ios/icon/icon-72@2x.png differ diff --git a/app/resources/ios/icon/icon-76.png b/app/resources/ios/icon/icon-76.png new file mode 100644 index 0000000..e66a90e Binary files /dev/null and b/app/resources/ios/icon/icon-76.png differ diff --git a/app/resources/ios/icon/icon-76@2x.png b/app/resources/ios/icon/icon-76@2x.png new file mode 100644 index 0000000..3f5c942 Binary files /dev/null and b/app/resources/ios/icon/icon-76@2x.png differ diff --git a/app/resources/ios/icon/icon-83.5@2x.png b/app/resources/ios/icon/icon-83.5@2x.png new file mode 100644 index 0000000..1dca548 Binary files /dev/null and b/app/resources/ios/icon/icon-83.5@2x.png differ diff --git a/app/resources/ios/icon/icon-small.png b/app/resources/ios/icon/icon-small.png new file mode 100644 index 0000000..de3146d Binary files /dev/null and b/app/resources/ios/icon/icon-small.png differ diff --git a/app/resources/ios/icon/icon-small@2x.png b/app/resources/ios/icon/icon-small@2x.png new file mode 100644 index 0000000..916a02e Binary files /dev/null and b/app/resources/ios/icon/icon-small@2x.png differ diff --git a/app/resources/ios/icon/icon-small@3x.png b/app/resources/ios/icon/icon-small@3x.png new file mode 100644 index 0000000..0efa99d Binary files /dev/null and b/app/resources/ios/icon/icon-small@3x.png differ diff --git a/app/resources/ios/icon/icon.png b/app/resources/ios/icon/icon.png new file mode 100644 index 0000000..89f8c00 Binary files /dev/null and b/app/resources/ios/icon/icon.png differ diff --git a/app/resources/ios/icon/icon@2x.png b/app/resources/ios/icon/icon@2x.png new file mode 100644 index 0000000..a6687a1 Binary files /dev/null and b/app/resources/ios/icon/icon@2x.png differ diff --git a/app/resources/ios/splash/Default-568h@2x~iphone.png b/app/resources/ios/splash/Default-568h@2x~iphone.png new file mode 100644 index 0000000..d2128a6 Binary files /dev/null and b/app/resources/ios/splash/Default-568h@2x~iphone.png differ diff --git a/app/resources/ios/splash/Default-667h.png b/app/resources/ios/splash/Default-667h.png new file mode 100644 index 0000000..fc23e64 Binary files /dev/null and b/app/resources/ios/splash/Default-667h.png differ diff --git a/app/resources/ios/splash/Default-736h.png b/app/resources/ios/splash/Default-736h.png new file mode 100644 index 0000000..71b16ca Binary files /dev/null and b/app/resources/ios/splash/Default-736h.png differ diff --git a/app/resources/ios/splash/Default-Landscape-736h.png b/app/resources/ios/splash/Default-Landscape-736h.png new file mode 100644 index 0000000..aaff74a Binary files /dev/null and b/app/resources/ios/splash/Default-Landscape-736h.png differ diff --git a/app/resources/ios/splash/Default-Landscape@2x~ipad.png b/app/resources/ios/splash/Default-Landscape@2x~ipad.png new file mode 100644 index 0000000..19770a2 Binary files /dev/null and b/app/resources/ios/splash/Default-Landscape@2x~ipad.png differ diff --git a/app/resources/ios/splash/Default-Landscape~ipad.png b/app/resources/ios/splash/Default-Landscape~ipad.png new file mode 100644 index 0000000..6fe8925 Binary files /dev/null and b/app/resources/ios/splash/Default-Landscape~ipad.png differ diff --git a/app/resources/ios/splash/Default-Portrait@2x~ipad.png b/app/resources/ios/splash/Default-Portrait@2x~ipad.png new file mode 100644 index 0000000..3d06d86 Binary files /dev/null and b/app/resources/ios/splash/Default-Portrait@2x~ipad.png differ diff --git a/app/resources/ios/splash/Default-Portrait~ipad.png b/app/resources/ios/splash/Default-Portrait~ipad.png new file mode 100644 index 0000000..53ad4c4 Binary files /dev/null and b/app/resources/ios/splash/Default-Portrait~ipad.png differ diff --git a/app/resources/ios/splash/Default@2x~iphone.png b/app/resources/ios/splash/Default@2x~iphone.png new file mode 100644 index 0000000..6a13316 Binary files /dev/null and b/app/resources/ios/splash/Default@2x~iphone.png differ diff --git a/app/resources/ios/splash/Default~iphone.png b/app/resources/ios/splash/Default~iphone.png new file mode 100644 index 0000000..0aaad62 Binary files /dev/null and b/app/resources/ios/splash/Default~iphone.png differ diff --git a/app/resources/splash.png b/app/resources/splash.png new file mode 100644 index 0000000..cbddba0 Binary files /dev/null and b/app/resources/splash.png differ diff --git a/app/src/app/app.component.ts b/app/src/app/app.component.ts new file mode 100644 index 0000000..a3ad537 --- /dev/null +++ b/app/src/app/app.component.ts @@ -0,0 +1,54 @@ +import { Component, ViewChild } from '@angular/core'; +import { Platform, MenuController, Nav } from 'ionic-angular'; +import { StatusBar } from '@ionic-native/status-bar'; +import { SplashScreen } from '@ionic-native/splash-screen'; +import { ClickerList, Page2 } from '../pages'; + +@Component({ + templateUrl: './app.html', +}) +export class ClickerApp { + + @ViewChild(Nav) public nav: Nav; + + private menu: MenuController; + private platform: Platform; + private splash: SplashScreen; + private status: StatusBar; + + public rootPage: any; + public pages: Array<{ title: string, component: any }>; + + constructor(platform: Platform, menu: MenuController, splash: SplashScreen, status: StatusBar) { + + this.menu = menu; + this.platform = platform; + this.splash = splash; + this.status = status; + + this.rootPage = ClickerList; + this.initializeApp(); + + // set our app's pages + this.pages = [ + { title: 'Clickers', component: ClickerList }, + { title: 'Goodbye Ionic', component: Page2 }, + ]; + } + + private initializeApp(): void { + this.platform.ready().then(() => { + // Okay, so the platform is ready and our plugins are available. + // Here you can do any higher level native things you might need. + this.status.styleDefault(); + this.splash.hide(); + }); + } + + public openPage(page: any): void { + // close the menu when clicking a link from the menu + this.menu.close(); + // navigate to the new page if it is not the current page + this.nav.setRoot(page.component); + }; +} diff --git a/app/src/app/app.html b/app/src/app/app.html new file mode 100644 index 0000000..5c20128 --- /dev/null +++ b/app/src/app/app.html @@ -0,0 +1,19 @@ + + + + + Pages + + + + + + + + + + + + diff --git a/app/src/app/app.module.ts b/app/src/app/app.module.ts new file mode 100644 index 0000000..9e12a23 --- /dev/null +++ b/app/src/app/app.module.ts @@ -0,0 +1,34 @@ +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 { ClickerApp } from './app.component'; +import { ClickerList, PagesModule, Page2 } from '../pages'; +import { ClickersService, StorageService } from '../services'; + +@NgModule({ + declarations: [ + ClickerApp, + ], + imports: [ + BrowserModule, + PagesModule, + IonicModule.forRoot(ClickerApp), + ], + bootstrap: [IonicApp], + entryComponents: [ + ClickerApp, + ClickerList, + Page2, + ], + providers: [ + StatusBar, + SplashScreen, + ClickersService, + StorageService, + {provide: ErrorHandler, useClass: IonicErrorHandler}, + ], +}) + +export class AppModule {} diff --git a/app/src/app/app.scss b/app/src/app/app.scss new file mode 100644 index 0000000..a967d6f --- /dev/null +++ b/app/src/app/app.scss @@ -0,0 +1,16 @@ +// http://ionicframework.com/docs/v2/theming/ + + +// App Global Sass +// -------------------------------------------------- +// Put style rules here that you want to apply globally. These +// styles are for the entire app and not just one component. +// Additionally, this file can be also used as an entry point +// to import other Sass files to be included in the output CSS. +// +// Shared Sass variables, which can be used to adjust Ionic's +// default Sass variables, belong in "theme/variables.scss". +// +// To declare rules for a specific mode, create a child rule +// for the .md, .ios, or .wp mode classes. The mode class is +// automatically applied to the element in the app. diff --git a/app/src/app/app.spec.ts b/app/src/app/app.spec.ts new file mode 100644 index 0000000..5ad54e0 --- /dev/null +++ b/app/src/app/app.spec.ts @@ -0,0 +1,29 @@ +import { ClickerApp } from './app.component'; +import { MenuMock, NavMock, PlatformMock, SplashMock, StatusMock } from '../mocks'; +import { Page2 } from '../pages'; + +let instance: ClickerApp = null; + +describe('ClickerApp', () => { + + beforeEach(() => { + instance = new ClickerApp(( new PlatformMock), ( new MenuMock), (new SplashMock()), (new StatusMock())); + instance['nav'] = (new NavMock()); + }); + + it('initialises with two possible pages', () => { + expect(instance['pages'].length).toEqual(2); + }); + + it('initialises with a root page', () => { + expect(instance['rootPage']).not.toBe(null); + }); + + it('opens a page', () => { + spyOn(instance['menu'], 'close'); + spyOn(instance['nav'], 'setRoot'); + instance.openPage(instance['pages'][1]); + expect(instance['menu']['close']).toHaveBeenCalled(); + expect(instance['nav'].setRoot).toHaveBeenCalledWith(Page2); + }); +}); diff --git a/app/src/app/main.ts b/app/src/app/main.ts new file mode 100644 index 0000000..6af7a5b --- /dev/null +++ b/app/src/app/main.ts @@ -0,0 +1,5 @@ +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/app/src/assets/icon/favicon.ico b/app/src/assets/icon/favicon.ico new file mode 100644 index 0000000..d76fa29 Binary files /dev/null and b/app/src/assets/icon/favicon.ico differ diff --git a/app/src/components/clickerButton/clickerButton.html b/app/src/components/clickerButton/clickerButton.html new file mode 100644 index 0000000..ac71288 --- /dev/null +++ b/app/src/components/clickerButton/clickerButton.html @@ -0,0 +1 @@ + diff --git a/app/src/components/clickerButton/clickerButton.spec.ts b/app/src/components/clickerButton/clickerButton.spec.ts new file mode 100644 index 0000000..3e75c1e --- /dev/null +++ b/app/src/components/clickerButton/clickerButton.spec.ts @@ -0,0 +1,36 @@ +import { ComponentFixture, async } from '@angular/core/testing'; +import { TestUtils } from '../../test'; +import { ClickerButton } from './clickerButton'; +import { ClickerMock } from '../../models/clicker.mock'; + +let fixture: ComponentFixture = null; +let instance: any = null; + +describe('ClickerButton', () => { + + beforeEach(async(() => TestUtils.beforeEachCompiler([ClickerButton]).then(compiled => { + fixture = compiled.fixture; + instance = compiled.instance; + instance.clicker = new ClickerMock(); + }))); + + afterEach(() => { + fixture.destroy(); + }); + + it('initialises', () => { + expect(instance).not.toBeNull(); + }); + + it('displays the clicker name and count', () => { + fixture.detectChanges(); + expect(fixture.nativeElement.querySelectorAll('.button-inner')[0].innerHTML).toEqual('TEST CLICKER (10)'); + }); + + it('does a click', () => { + fixture.detectChanges(); + spyOn(instance['clickerService'], 'doClick'); + TestUtils.eventFire(fixture.nativeElement.querySelectorAll('button')[0], 'click'); + expect(instance['clickerService'].doClick).toHaveBeenCalled(); + }); +}); diff --git a/app/src/components/clickerButton/clickerButton.ts b/app/src/components/clickerButton/clickerButton.ts new file mode 100644 index 0000000..f91d0fe --- /dev/null +++ b/app/src/components/clickerButton/clickerButton.ts @@ -0,0 +1,20 @@ +'use strict'; + +import { Component, Input } from '@angular/core'; +import { ClickersService } from '../../services'; +import { Clicker } from '../../models'; + +@Component({ + selector: 'clicker-button', + templateUrl: './clickerButton.html', +}) + +export class ClickerButton { + @Input() public clicker: Clicker; + + public clickerService: ClickersService; + + constructor(clickerService: ClickersService) { + this.clickerService = clickerService; + } +} diff --git a/app/src/components/clickerForm/clickerForm.html b/app/src/components/clickerForm/clickerForm.html new file mode 100644 index 0000000..e60713d --- /dev/null +++ b/app/src/components/clickerForm/clickerForm.html @@ -0,0 +1,14 @@ +
+ + + + + + + + + + +
diff --git a/app/src/components/clickerForm/clickerForm.spec.ts b/app/src/components/clickerForm/clickerForm.spec.ts new file mode 100644 index 0000000..7e3a66a --- /dev/null +++ b/app/src/components/clickerForm/clickerForm.spec.ts @@ -0,0 +1,44 @@ +import { FormBuilder } from '@angular/forms'; +import { ComponentFixture, async } from '@angular/core/testing'; +import { TestUtils } from '../../test'; +import { ClickerForm } from './clickerForm'; + +let fixture: ComponentFixture = null; +let instance: any = null; + +describe('ClickerForm', () => { + + beforeEach(async(() => TestUtils.beforeEachCompiler([ClickerForm]).then(compiled => { + fixture = compiled.fixture; + instance = compiled.instance; + instance.clicker = { name: 'TEST CLICKER' }; + instance.clicker.getCount = function(): number { return 10; }; + fixture.autoDetectChanges(true); + }))); + + afterEach(() => { + fixture.destroy(); + }); + + it('initialises', () => { + expect(fixture).not.toBeNull(); + expect(instance).not.toBeNull(); + }); + + it('passes new clicker through to service', () => { + let clickerName: string = 'dave'; + instance.form = new FormBuilder().group({clickerNameInput: [clickerName]}); + spyOn(instance, 'newClicker').and.callThrough(); + spyOn(instance['clickerService'], 'newClicker').and.callThrough(); + fixture.detectChanges(); + fixture.nativeElement.querySelectorAll('button')[1].click(); + expect(instance.newClicker).toHaveBeenCalledWith(Object({ clickerNameInput: clickerName })); + expect(instance['clickerService'].newClicker).toHaveBeenCalledWith(clickerName); + }); + + it('doesn\'t try to add a clicker with no name', () => { + spyOn(instance['clickerService'], 'newClicker').and.callThrough(); + instance.newClicker({}); + expect(instance['clickerService'].newClicker).not.toHaveBeenCalled(); + }); +}); diff --git a/app/src/components/clickerForm/clickerForm.ts b/app/src/components/clickerForm/clickerForm.ts new file mode 100644 index 0000000..0b5fab8 --- /dev/null +++ b/app/src/components/clickerForm/clickerForm.ts @@ -0,0 +1,41 @@ +'use strict'; + +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; +import { Component } from '@angular/core'; +import { ClickersService } from '../../services'; + +@Component({ + selector: 'clicker-form', + templateUrl: './clickerForm.html', +}) + +export class ClickerForm { + + private clickerService: ClickersService; + public form: FormGroup; + + constructor(clickerService: ClickersService, fb: FormBuilder) { + this.clickerService = clickerService; + + this.form = fb.group({ + clickerNameInput: ['', Validators.required], + }); + } + + public newClicker(formValue: Object): boolean { + + // need to mark the clickerName control as touched so validation + // will apply after the user has tried to add a clicker + this.form.controls['clickerNameInput'].markAsTouched(); + + if (!this.form.controls['clickerNameInput'].valid) { + return false; + } + + this.clickerService.newClicker(formValue['clickerNameInput']); + + this.form.reset(); + + return true; + } +} diff --git a/app/src/components/components.module.ts b/app/src/components/components.module.ts new file mode 100644 index 0000000..e7aaedc --- /dev/null +++ b/app/src/components/components.module.ts @@ -0,0 +1,25 @@ +import { NgModule } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { IonicModule } from 'ionic-angular'; +import { ClickerButton } from './clickerButton/clickerButton'; +import { ClickerForm } from './clickerForm/clickerForm'; + +@NgModule({ + declarations: [ + ClickerForm, + ClickerButton, + ], + imports: [ + FormsModule, + IonicModule, + ReactiveFormsModule, + ], + exports: [ + ClickerButton, + ClickerForm, + ], + entryComponents: [], + providers: [ ], +}) + +export class ComponentsModule {} diff --git a/app/src/components/index.ts b/app/src/components/index.ts new file mode 100644 index 0000000..3324dc1 --- /dev/null +++ b/app/src/components/index.ts @@ -0,0 +1,3 @@ +export * from './clickerButton/clickerButton'; +export * from './clickerForm/clickerForm'; +export * from './components.module'; diff --git a/app/src/declarations.d.ts b/app/src/declarations.d.ts new file mode 100644 index 0000000..423e2f1 --- /dev/null +++ b/app/src/declarations.d.ts @@ -0,0 +1,14 @@ +/* + Declaration files are how the Typescript compiler knows about the type information(or shape) of an object. + They're what make intellisense work and make Typescript know all about your code. + + A wildcard module is declared below to allow third party libraries to be used in an app even if they don't + provide their own type declarations. + + To learn more about using third party libraries in an Ionic app, check out the docs here: + http://ionicframework.com/docs/v2/resources/third-party-libs/ + + For more info on type definition files, check out the Typescript docs here: + https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html +*/ +declare module '*'; \ No newline at end of file diff --git a/app/src/index.html b/app/src/index.html new file mode 100644 index 0000000..3ec7764 --- /dev/null +++ b/app/src/index.html @@ -0,0 +1,41 @@ + + + + + Clickers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/manifest.json b/app/src/manifest.json new file mode 100644 index 0000000..f6456bb --- /dev/null +++ b/app/src/manifest.json @@ -0,0 +1,13 @@ +{ + "name": "Ionic", + "short_name": "Ionic", + "start_url": "index.html", + "display": "standalone", + "icons": [{ + "src": "assets/imgs/logo.png", + "sizes": "512x512", + "type": "image/png" + }], + "background_color": "#4e8ef7", + "theme_color": "#4e8ef7" +} \ No newline at end of file diff --git a/app/src/mocks.ts b/app/src/mocks.ts new file mode 100644 index 0000000..f6e7a88 --- /dev/null +++ b/app/src/mocks.ts @@ -0,0 +1,187 @@ +/* tslint:disable */ +// IONIC: +import { EventEmitter} from '@angular/core'; +import { FormBuilder } from '@angular/forms'; + +// from +// https://github.com/stonelasley/ionic-mocks/ +// should the package be incorporated? + +declare var jasmine: any; + +export class AlertMock { + public static instance(): any { + let instance = jasmine.createSpyObj('Alert', ['present', 'dismiss']); + instance.present.and.returnValue(Promise.resolve()); + instance.dismiss.and.returnValue(Promise.resolve()); + + return instance; + } +} + +export class AlertControllerMock { + public static instance(alertMock?: AlertMock): any { + + let instance = jasmine.createSpyObj('AlertController', ['create']); + instance.create.and.returnValue(alertMock || AlertMock.instance()); + + return instance; + } +} + +export class ToastMock { + + public create(): any { + let rtn: Object = {}; + rtn['present'] = (() => true); + return rtn; + } +} + +export class ConfigMock { + + public get(): any { + return ''; + } + + public getBoolean(): boolean { + return true; + } + + public getNumber(): number { + return 1; + } + + public setTransition(): void { + return; + } +} + +export class FormMock { + public register(): any { + return true; + } +} + +export class NavMock { + + public pop(): any { + return new Promise(function(resolve: Function): void { + resolve(); + }); + } + + public push(): any { + return new Promise(function(resolve: Function): void { + resolve(); + }); + } + + public getActive(): any { + return { + 'instance': { + 'model': 'something', + }, + }; + } + + public setRoot(): any { + return true; + } + + public popToRoot(): any { + return true; + } +} + +export class PlatformMock { + public ready(): Promise<{String}> { + return new Promise((resolve) => { + resolve('READY'); + }); + } + + public registerBackButtonAction(fn: Function, priority?: number): Function { + return (() => true); + } + + public hasFocus(ele: HTMLElement): boolean { + return true; + } + + public doc(): HTMLDocument { + return document; + } + + public is(): boolean { + return true; + } + + public getElementComputedStyle(container: any): any { + return { + paddingLeft: '10', + paddingTop: '10', + paddingRight: '10', + paddingBottom: '10', + }; + } + + public onResize(callback: any) { + return callback; + } + + public registerListener(ele: any, eventName: string, callback: any): Function { + return (() => true); + } + + public win(): Window { + return window; + } + + public raf(callback: any): number { + return 1; + } + + public timeout(callback: any, timer: number): any { + return setTimeout(callback, timer); + } + + public cancelTimeout(id: any) { + // do nothing + } + + public getActiveElement(): any { + return document['activeElement']; + } +} + +export class SplashMock { + + public hide() { + return Promise.resolve(true); + } +} + +export class StatusMock { + + public styleDefault() { + return Promise.resolve(true); + } +} + +export class MenuMock { + public close(): any { + return new Promise((resolve: Function) => { + resolve(); + }); + } +} + +export class AppMock { + + public getActiveNav(): NavMock { + return new NavMock(); + } +} + +/* tslint:enable */ diff --git a/app/src/models/click.spec.ts b/app/src/models/click.spec.ts new file mode 100644 index 0000000..d4e148f --- /dev/null +++ b/app/src/models/click.spec.ts @@ -0,0 +1,26 @@ +'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 new file mode 100644 index 0000000..0d60220 --- /dev/null +++ b/app/src/models/click.ts @@ -0,0 +1,20 @@ +'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 new file mode 100644 index 0000000..c69dc14 --- /dev/null +++ b/app/src/models/clicker.mock.ts @@ -0,0 +1,5 @@ +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 new file mode 100644 index 0000000..395e8da --- /dev/null +++ b/app/src/models/clicker.spec.ts @@ -0,0 +1,11 @@ +'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 new file mode 100644 index 0000000..ddadec7 --- /dev/null +++ b/app/src/models/clicker.ts @@ -0,0 +1,37 @@ +'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 new file mode 100644 index 0000000..00d5248 --- /dev/null +++ b/app/src/models/index.ts @@ -0,0 +1,2 @@ +export * from './click'; +export * from './clicker'; diff --git a/app/src/models/mocks.ts b/app/src/models/mocks.ts new file mode 100644 index 0000000..799ce86 --- /dev/null +++ b/app/src/models/mocks.ts @@ -0,0 +1 @@ +export * from './clicker.mock'; diff --git a/app/src/pages/clickerList/clickerList.html b/app/src/pages/clickerList/clickerList.html new file mode 100644 index 0000000..9cae7c0 --- /dev/null +++ b/app/src/pages/clickerList/clickerList.html @@ -0,0 +1,18 @@ + + + + {{title}} + + + + + + + + + + + + diff --git a/app/src/pages/clickerList/clickerList.scss b/app/src/pages/clickerList/clickerList.scss new file mode 100644 index 0000000..5d457a3 --- /dev/null +++ b/app/src/pages/clickerList/clickerList.scss @@ -0,0 +1,3 @@ +.clickerList { + +} \ No newline at end of file diff --git a/app/src/pages/clickerList/clickerList.spec.ts b/app/src/pages/clickerList/clickerList.spec.ts new file mode 100644 index 0000000..fc360f7 --- /dev/null +++ b/app/src/pages/clickerList/clickerList.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, async } from '@angular/core/testing'; +import { TestUtils } from '../../test'; +import { ClickerList } from './clickerList'; +import { ClickerButton, ClickerForm } from '../../components'; + +let fixture: ComponentFixture = null; +let instance: any = null; + +describe('ClickerList', () => { + + beforeEach(async(() => TestUtils.beforeEachCompiler([ClickerList, ClickerForm, ClickerButton]).then(compiled => { + fixture = compiled.fixture; + instance = compiled.instance; + fixture.detectChanges(); + }))); + + afterEach(() => { + fixture.destroy(); + }); + + it('initialises', () => { + expect(instance).toBeTruthy(); + }); +}); diff --git a/app/src/pages/clickerList/clickerList.ts b/app/src/pages/clickerList/clickerList.ts new file mode 100644 index 0000000..f1e7adf --- /dev/null +++ b/app/src/pages/clickerList/clickerList.ts @@ -0,0 +1,20 @@ +import { Component } from '@angular/core'; +import { NavController } from 'ionic-angular'; +import { ClickersService } from '../../services'; + +@Component({ + templateUrl: './clickerList.html', +}) + +export class ClickerList { + + public clickerService: ClickersService; + public title: string; + private nav: NavController; + + constructor(nav: NavController, clickerService: ClickersService) { + this.nav = nav; + this.clickerService = clickerService; + this.title = 'Clickers'; + } +} diff --git a/app/src/pages/index.ts b/app/src/pages/index.ts new file mode 100644 index 0000000..ba27bc3 --- /dev/null +++ b/app/src/pages/index.ts @@ -0,0 +1,3 @@ +export * from './clickerList/clickerList'; +export * from './page2/page2'; +export * from './pages.module'; diff --git a/app/src/pages/page2/page2.html b/app/src/pages/page2/page2.html new file mode 100644 index 0000000..1056322 --- /dev/null +++ b/app/src/pages/page2/page2.html @@ -0,0 +1,13 @@ + + + + {{title}} + + + + + + + diff --git a/app/src/pages/page2/page2.scss b/app/src/pages/page2/page2.scss new file mode 100644 index 0000000..c0a7adb --- /dev/null +++ b/app/src/pages/page2/page2.scss @@ -0,0 +1,8 @@ +.page2 { + +} + +.bye-ionic { + background-image: url('../../images/ionic.png'); + background-repeat: no-repeat; +} \ No newline at end of file diff --git a/app/src/pages/page2/page2.spec.ts b/app/src/pages/page2/page2.spec.ts new file mode 100644 index 0000000..b910aa0 --- /dev/null +++ b/app/src/pages/page2/page2.spec.ts @@ -0,0 +1,95 @@ +import { async, fakeAsync, tick, ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { App, Config, Form, IonicModule, Keyboard, DomController, MenuController, NavController, Platform, AlertController } from 'ionic-angular'; +import { ConfigMock, PlatformMock, AlertControllerMock } from '../../mocks'; +import { Page2 } from './page2'; + +const alertControllerMock: AlertController = AlertControllerMock.instance(); + +let fixture: ComponentFixture = null; +let instance: any = null; + +let alertSpy: any; +let alertControllerSpy: any; + +describe('Pages: Page2', () => { + + // demonstration on how to manually compile the test bed (as opposed to calling TestUtils) + beforeEach(async(() => { + + TestBed.configureTestingModule({ + declarations: [Page2], + providers: [ + App, DomController, Form, Keyboard, MenuController, NavController, + {provide: Config, useClass: ConfigMock}, + {provide: Platform, useClass: PlatformMock}, + {provide: AlertController, useValue: alertControllerMock}, + ], + imports: [ + FormsModule, + IonicModule, + ReactiveFormsModule, + ], + }) + .compileComponents().then(() => { + fixture = TestBed.createComponent(Page2); + instance = fixture; + fixture.detectChanges(); + fixture.componentInstance.onGainChange(); + + alertSpy = fixture.componentInstance.alertController; + alertControllerSpy = fixture.componentInstance.alertController.create(); + + }); + })); + + afterEach(() => { + fixture.destroy(); + }); + + it('should create page2', () => { + expect(fixture).toBeTruthy(); + expect(instance).toBeTruthy(); + }); + + it('should fire the simple alert', fakeAsync(() => { + + alertSpy.create.calls.reset(); + alertControllerSpy.present.calls.reset(); + + expect(alertSpy.create).not.toHaveBeenCalledTimes(1); + expect(alertControllerSpy.present).not.toHaveBeenCalledTimes(1); + + expect(alertSpy.create).not.toHaveBeenCalled(); + expect(alertControllerSpy.present).not.toHaveBeenCalled(); + + fixture.componentInstance.showSimpleAlert(); + tick(); + + expect(alertSpy.create).toHaveBeenCalledTimes(1); + expect(alertControllerSpy.present).toHaveBeenCalledTimes(1); + + expect(alertSpy.create).toHaveBeenCalled(); + expect(alertControllerSpy.present).toHaveBeenCalled(); + + })); + + it('should fire the more advanced alert', fakeAsync(() => { + + alertSpy.create.calls.reset(); + alertControllerSpy.present.calls.reset(); + + fixture.componentInstance.okEd = false; + + expect(fixture.componentInstance.okEd).toBeFalsy(); + + fixture.componentInstance.showMoreAdvancedAlert(); + tick(); + + fixture.componentInstance.OK(); + + expect(fixture.componentInstance.okEd).toBeTruthy(); + + })); + +}); diff --git a/app/src/pages/page2/page2.ts b/app/src/pages/page2/page2.ts new file mode 100644 index 0000000..648d8f3 --- /dev/null +++ b/app/src/pages/page2/page2.ts @@ -0,0 +1,55 @@ +'use strict'; + +import { Component } from '@angular/core'; +import { Alert, AlertController } from 'ionic-angular'; + +@Component({ + templateUrl: './page2.html', +}) + +export class Page2 { + + public okEd: boolean; + public alert1: Alert; + public alertController: AlertController; + + constructor(alertController: AlertController) { + this.alertController = alertController; + + }; + + public title: string = 'Page 2'; + + public onGainChange(): void { + return; + } + + public showSimpleAlert(): any { + + this.alert1 = this.alertController.create({ + title: 'This is an example for an alert', + buttons: ['Ok', 'Dismiss'], + }); + + this.alert1.present(); + } + + public showMoreAdvancedAlert(): any { + + this.alert1 = this.alertController.create({ + title: 'This is an example for an alert', + buttons: [{ + text: 'More Advanced Ok', + handler: this.OK, + } + , 'Dismiss'], + }); + + this.alert1.present(); + } + + public OK = () => { + + this.okEd = true; + } +} diff --git a/app/src/pages/pages.module.ts b/app/src/pages/pages.module.ts new file mode 100644 index 0000000..2127f5c --- /dev/null +++ b/app/src/pages/pages.module.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; +import { IonicModule } from 'ionic-angular'; +import { ComponentsModule } from '../components'; +import { ClickerList } from './clickerList/clickerList'; +import { Page2 } from './page2/page2'; + +@NgModule({ + declarations: [ + ClickerList, + Page2, + ], + imports: [ IonicModule, ComponentsModule ], + exports: [ + ClickerList, + // Page2, + ], + entryComponents: [], + providers: [ ], +}) + +export class PagesModule {} diff --git a/app/src/polyfills.ts b/app/src/polyfills.ts new file mode 100644 index 0000000..64c2ab2 --- /dev/null +++ b/app/src/polyfills.ts @@ -0,0 +1,74 @@ +/* tslint:disable */ + +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** IE9, IE10 and IE11 requires all of the following polyfills. **/ +// import 'core-js/es6/symbol'; +// import 'core-js/es6/object'; +// import 'core-js/es6/function'; +// import 'core-js/es6/parse-int'; +// import 'core-js/es6/parse-float'; +// import 'core-js/es6/number'; +// import 'core-js/es6/math'; +// import 'core-js/es6/string'; +// import 'core-js/es6/date'; +// import 'core-js/es6/array'; +// import 'core-js/es6/regexp'; +// import 'core-js/es6/map'; +// import 'core-js/es6/set'; + +/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** IE10 and IE11 requires the following to support `@angular/animation`. */ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + + +/** Evergreen browsers require these. **/ +import 'core-js/es6/reflect'; +import 'core-js/es7/reflect'; + + +/** ALL Firefox browsers require the following to support `@angular/animation`. **/ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + + + +/*************************************************************************************************** + * Zone JS is required by Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ + +/** + * Date, currency, decimal and percent pipes. + * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 + */ +// import 'intl'; // Run `npm install --save intl`. +/** + * Need to import at least one locale-data with intl. + */ +// import 'intl/locale-data/jsonp/en'; diff --git a/app/src/service-worker.js b/app/src/service-worker.js new file mode 100644 index 0000000..ed2d400 --- /dev/null +++ b/app/src/service-worker.js @@ -0,0 +1,30 @@ +/** + * Check out https://googlechrome.github.io/sw-toolbox/ for + * more info on how to use sw-toolbox to custom configure your service worker. + */ + + +'use strict'; +importScripts('./build/sw-toolbox.js'); + +self.toolbox.options.cache = { + name: 'ionic-cache' +}; + +// pre-cache our key assets +self.toolbox.precache( + [ + './build/main.js', + './build/main.css', + './build/polyfills.js', + 'index.html', + 'manifest.json' + ] +); + +// dynamically cache any other local assets +self.toolbox.router.any('/*', self.toolbox.cacheFirst); + +// for any other requests go to the network, cache, +// and then only use that cached resource if your user goes offline +self.toolbox.router.default = self.toolbox.networkFirst; diff --git a/app/src/services/clickers.mock.ts b/app/src/services/clickers.mock.ts new file mode 100644 index 0000000..1d8206a --- /dev/null +++ b/app/src/services/clickers.mock.ts @@ -0,0 +1,14 @@ +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 new file mode 100644 index 0000000..73d8faf --- /dev/null +++ b/app/src/services/clickers.spec.ts @@ -0,0 +1,88 @@ +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 new file mode 100644 index 0000000..f936f8b --- /dev/null +++ b/app/src/services/clickers.ts @@ -0,0 +1,114 @@ +'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 new file mode 100644 index 0000000..2c57205 --- /dev/null +++ b/app/src/services/index.ts @@ -0,0 +1,2 @@ +export * from './clickers'; +export * from './storage'; diff --git a/app/src/services/mocks.ts b/app/src/services/mocks.ts new file mode 100644 index 0000000..851dcb2 --- /dev/null +++ b/app/src/services/mocks.ts @@ -0,0 +1,2 @@ +export * from './clickers.mock'; +export * from './storage.mock'; diff --git a/app/src/services/storage.mock.ts b/app/src/services/storage.mock.ts new file mode 100644 index 0000000..6c34b76 --- /dev/null +++ b/app/src/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/src/services/storage.spec.ts b/app/src/services/storage.spec.ts new file mode 100644 index 0000000..be6ecca --- /dev/null +++ b/app/src/services/storage.spec.ts @@ -0,0 +1,32 @@ +import { StorageService } from './'; + +let storage: StorageService = null; + +describe('StorageService', () => { + + beforeEach(() => { + storage = new StorageService(); + 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/src/services/storage.ts b/app/src/services/storage.ts new file mode 100644 index 0000000..c0772f0 --- /dev/null +++ b/app/src/services/storage.ts @@ -0,0 +1,29 @@ +'use strict'; +import { Injectable } from '@angular/core'; +import { Storage } from '@ionic/storage'; + +@Injectable() +export class StorageService { + + private storage: Storage; + + constructor() { + this.storage = StorageService.initStorage(); + } + + public static initStorage(): Storage { + return new Storage({}); + } + + 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/src/test.ts b/app/src/test.ts new file mode 100644 index 0000000..829baab --- /dev/null +++ b/app/src/test.ts @@ -0,0 +1,81 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/dist/long-stack-trace-zone'; +import 'zone.js/dist/proxy.js'; +import 'zone.js/dist/sync-test'; +import 'zone.js/dist/jasmine-patch'; +import 'zone.js/dist/async-test'; +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 { App, Config, Form, IonicModule, Keyboard, DomController, MenuController, NavController, Platform } from 'ionic-angular'; +import { ConfigMock, PlatformMock } from './mocks'; +import { ClickersServiceMock } from './services/clickers.mock'; +import { ClickersService } from './services'; + +// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. +declare var __karma__: any; +declare var require: any; + +// Prevent Karma from running prematurely. +__karma__.loaded = function (): void { + // noop +}; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting(), +); +// Then we find all the tests. +const context: any = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); +// Finally, start Karma to run the tests. +__karma__.start(); + +export class TestUtils { + + public static beforeEachCompiler(components: Array): Promise<{fixture: any, instance: any}> { + return TestUtils.configureIonicTestingModule(components) + .compileComponents().then(() => { + let fixture: any = TestBed.createComponent(components[0]); + return { + fixture: fixture, + instance: fixture.debugElement.componentInstance, + }; + }); + } + + public static configureIonicTestingModule(components: Array): typeof TestBed { + return TestBed.configureTestingModule({ + declarations: [ + ...components, + ], + providers: [ + App, Form, Keyboard, DomController, MenuController, NavController, + {provide: Platform, useClass: PlatformMock}, + {provide: Config, useClass: ConfigMock}, + {provide: ClickersService, useClass: ClickersServiceMock}, + ], + imports: [ + FormsModule, + IonicModule, + ReactiveFormsModule, + ], + }); + } + + // http://stackoverflow.com/questions/2705583/how-to-simulate-a-click-with-javascript + public static eventFire(el: any, etype: string): void { + if (el.fireEvent) { + el.fireEvent('on' + etype); + } else { + let evObj: any = document.createEvent('Events'); + evObj.initEvent(etype, true, false); + el.dispatchEvent(evObj); + } + } +} diff --git a/app/src/theme/variables.scss b/app/src/theme/variables.scss new file mode 100644 index 0000000..12c82d5 --- /dev/null +++ b/app/src/theme/variables.scss @@ -0,0 +1,78 @@ +// Ionic Variables and Theming. For more info, please see: +// http://ionicframework.com/docs/v2/theming/ +$font-path: "../assets/fonts"; + +@import "ionic.globals"; + + +// Shared Variables +// -------------------------------------------------- +// To customize the look and feel of this app, you can override +// the Sass variables found in Ionic's source scss files. +// To view all the possible Ionic variables, see: +// http://ionicframework.com/docs/v2/theming/overriding-ionic-variables/ + + + + +// Named Color Variables +// -------------------------------------------------- +// Named colors makes it easy to reuse colors on various components. +// It's highly recommended to change the default colors +// to match your app's branding. Ionic uses a Sass map of +// colors so you can add, rename and remove colors as needed. +// The "primary" color is the only required color in the map. + +$colors: ( + primary: #387ef5, + secondary: #32db64, + danger: #f53d3d, + light: #f4f4f4, + dark: #222 +); + + +// App iOS Variables +// -------------------------------------------------- +// iOS only Sass variables can go here + + + + +// App Material Design Variables +// -------------------------------------------------- +// Material Design only Sass variables can go here + + + + +// App Windows Variables +// -------------------------------------------------- +// Windows only Sass variables can go here + + + + +// App Theme +// -------------------------------------------------- +// Ionic apps can have different themes applied, which can +// then be future customized. This import comes last +// so that the above variables are used and Ionic's +// default are overridden. + +@import "ionic.theme.default"; + + +// Ionicons +// -------------------------------------------------- +// The premium icon font for Ionic. For more info, please see: +// http://ionicframework.com/docs/v2/ionicons/ + +@import "ionic.ionicons"; + + +// Fonts +// -------------------------------------------------- + +@import "roboto"; +@import "noto-sans"; diff --git a/app/src/tsconfig.spec.json b/app/src/tsconfig.spec.json new file mode 100644 index 0000000..e2155c2 --- /dev/null +++ b/app/src/tsconfig.spec.json @@ -0,0 +1,20 @@ +{ + "extends": "../tsconfig.ng-cli.json", + "compilerOptions": { + "outDir": "../out-tsc/spec", + "module": "commonjs", + "target": "es5", + "baseUrl": "", + "types": [ + "jasmine", + "node" + ] + }, + "files": [ + "test.ts" + ], + "include": [ + "**/*.spec.ts", + "**/*.d.ts" + ] +} diff --git a/app/tsconfig.json b/app/tsconfig.json new file mode 100644 index 0000000..5f3617f --- /dev/null +++ b/app/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "declaration": false, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": [ + "dom", + "es2015" + ], + "module": "es2015", + "moduleResolution": "node", + "sourceMap": true, + "target": "es5" + }, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "node_modules", + "src/test.ts", + "**/*.spec.ts", + "e2e" + ], + "compileOnSave": false, + "atom": { + "rewriteTsconfig": false + } +} \ No newline at end of file diff --git a/app/tsconfig.ng-cli.json b/app/tsconfig.ng-cli.json new file mode 100644 index 0000000..07938ce --- /dev/null +++ b/app/tsconfig.ng-cli.json @@ -0,0 +1,20 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "outDir": "./dist/out-tsc", + "baseUrl": "src", + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es5", + "typeRoots": [ + "node_modules/@types" + ], + "lib": [ + "es2016", + "dom" + ] + } +} \ No newline at end of file diff --git a/app/tslint.json b/app/tslint.json new file mode 100644 index 0000000..c717a98 --- /dev/null +++ b/app/tslint.json @@ -0,0 +1,121 @@ +{ + "rules": { + "align": [ + true, + "parameters", + "arguments", + "statements" + ], + "ban": false, + "class-name": true, + "comment-format": [ + true, + "check-space" + ], + "curly": false, + "eofline": true, + "forin": false, + "indent": [ + true, + "spaces" + ], + "interface-name": false, + "jsdoc-format": true, + "label-position": true, + "label-undefined": true, + "max-line-length": [ + true, + 180 + ], + "member-access": true, + "member-ordering": [false], + "no-any": false, + "no-arg": true, + "no-bitwise": true, + "no-conditional-assignment": true, + "no-consecutive-blank-lines": true, + "no-console": [false], + "no-construct": false, + "no-constructor-vars": true, + "no-debugger": true, + "no-duplicate-key": true, + "no-duplicate-variable": true, + "no-empty": true, + "no-eval": true, + "no-inferrable-types": false, + "no-internal-module": true, + "no-require-imports": false, + "no-shadowed-variable": true, + "no-string-literal": false, + "no-switch-case-fall-through": true, + "no-trailing-whitespace": true, + "no-unreachable": true, + "no-unused-variable": [ + true, + "check-parameters", + { "ignore-pattern": "^ng*|^on*" } + ], + "no-use-before-declare": true, + "no-var-keyword": true, + "no-var-requires": true, + "object-literal-sort-keys": false, + "one-line": [ + true, + "check-open-brace", + "check-whitespace" + ], + "quotemark": [ + true, + "single", + "avoid-escape" + ], + "radix": false, + "semicolon": true, + "switch-default": false, + "trailing-comma": [ + true, + { + "multiline": "always", + "singleline": "never" + } + ], + "triple-equals": [true], + "typedef": [ + true, + "call-signature", + "parameter", + "property-declaration", + "variable-declaration", + "member-variable-declaration" + ], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "use-strict": [ + true, + "check-module", + "check-function" + ], + "variable-name": [ + true, + "check-format", + "allow-leading-underscore", + "ban-keywords" + ], + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ] + } +}