├── .gitignore ├── LICENSE ├── README.md ├── index.html ├── karma-test-shim.js ├── karma.conf.js ├── package.json ├── src ├── bootstrap.ts ├── components │ ├── gh-card.spec.ts │ ├── gh-card.ts │ ├── gh-repo.spec.ts │ └── gh-repo.ts ├── git-happ.ts ├── pipes │ ├── thousands.spec.ts │ └── thousands.ts ├── services │ ├── gh-repository.ts │ ├── gh-service.spec.ts │ ├── gh-service.ts │ └── gh-user.ts └── tsconfig.json ├── system-config.js └── typings.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | typings 4 | coverage 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Raúl Jiménez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unit Testing with Angular 2 2 | 3 | Unit Testing with Angular 2 - Fair Day Workshop for ngConf 2016. 4 | 5 | https://www.ng-conf.org/#/sessions/elecashFD 6 | 7 | ## Installation 8 | 9 | Install dependencies: 10 | 11 | ``` 12 | npm install -g typings 13 | npm install 14 | ``` 15 | 16 | Try it! 17 | 18 | ``` 19 | npm run dev 20 | ``` 21 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular2 Github Cards 6 | 7 | 8 | 9 | 10 | 11 | 16 | 17 | 18 | 19 | 20 | Loading... 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /karma-test-shim.js: -------------------------------------------------------------------------------- 1 | // Tun on full stack traces in errors to help debugging 2 | Error.stackTraceLimit = Infinity; 3 | 4 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000; 5 | 6 | // // Cancel Karma's synchronous start, 7 | // // we will call `__karma__.start()` later, once all the specs are loaded. 8 | __karma__.loaded = function() {}; 9 | 10 | function isJsFile(path) { 11 | return path.slice(-3) == '.js'; 12 | } 13 | 14 | function isSpecFile(path) { 15 | return path.slice(-8) == '.spec.js'; 16 | } 17 | 18 | function isBuiltFile(path) { 19 | var builtPath = '/base/dist/'; 20 | return isJsFile(path) && (path.substr(0, builtPath.length) == builtPath); 21 | } 22 | 23 | var allSpecFiles = Object.keys(window.__karma__.files) 24 | .filter(isSpecFile) 25 | .filter(isBuiltFile); 26 | 27 | System.config({ 28 | baseURL: '/base' 29 | }); 30 | 31 | System.config({ 32 | map: { 33 | 'rxjs': 'node_modules/rxjs', 34 | '@angular': 'node_modules/@angular', 35 | 'app': 'dist' 36 | }, 37 | packages: { 38 | 'app': { 39 | main: 'bootstrap.js', 40 | defaultExtension: 'js' 41 | }, 42 | '@angular/core': { 43 | main: 'index.js', 44 | defaultExtension: 'js' 45 | }, 46 | '@angular/compiler': { 47 | main: 'index.js', 48 | defaultExtension: 'js' 49 | }, 50 | '@angular/common': { 51 | main: 'index.js', 52 | defaultExtension: 'js' 53 | }, 54 | '@angular/http': { 55 | main: 'index.js', 56 | defaultExtension: 'js' 57 | }, 58 | '@angular/testing': { 59 | main: 'index.js', 60 | defaultExtension: 'js' 61 | }, 62 | '@angular/platform-browser': { 63 | main: 'index.js', 64 | defaultExtension: 'js' 65 | }, 66 | '@angular/platform-browser-dynamic': { 67 | main: 'index.js', 68 | defaultExtension: 'js' 69 | }, 70 | 'rxjs': { 71 | defaultExtension: 'js' 72 | } 73 | } 74 | }); 75 | 76 | Promise.all([ 77 | System.import('@angular/core/testing'), 78 | System.import('@angular/platform-browser-dynamic/testing') 79 | ]) 80 | .then( 81 | function (providers) { 82 | var testing = providers[0]; 83 | var testingBrowser = providers[1]; 84 | 85 | testing.setBaseTestProviders( 86 | testingBrowser.TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS, 87 | testingBrowser.TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS 88 | ); 89 | } 90 | ) 91 | .then( 92 | function() { 93 | // Finally, load all spec files. 94 | // This will run the tests directly. 95 | return Promise.all( 96 | allSpecFiles.map( 97 | function (moduleName) { 98 | return System.import(moduleName); 99 | } 100 | ) 101 | ); 102 | } 103 | ) 104 | .then( 105 | __karma__.start, __karma__.error 106 | ); 107 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.set({ 3 | 4 | basePath: '.', 5 | 6 | frameworks: ['jasmine'], 7 | 8 | files: [ 9 | // Polyfills 10 | 'node_modules/es6-shim/es6-shim.js', 11 | 'node_modules/reflect-metadata/Reflect.js', 12 | 13 | // System.js 14 | 'node_modules/systemjs/dist/system-polyfills.js', 15 | 'node_modules/systemjs/dist/system.src.js', 16 | 17 | // Zone.js 18 | 'node_modules/zone.js/dist/zone.js', 19 | 'node_modules/zone.js/dist/jasmine-patch.js', 20 | 'node_modules/zone.js/dist/async-test.js', 21 | 'node_modules/zone.js/dist/fake-async-test.js', 22 | 23 | // RxJs 24 | {pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false}, 25 | {pattern: 'node_modules/rxjs/**/*.js.map', included: false, watched: false}, 26 | 27 | // Karma shim 28 | {pattern: 'karma-test-shim.js', included: true, watched: true}, 29 | 30 | // Angular 31 | {pattern: 'node_modules/@angular/**/*.js', included: false, watched: true}, 32 | {pattern: 'node_modules/@angular/**/*.js.map', included: false, watched: true}, 33 | 34 | // Our app 35 | {pattern: 'dist/**/*.js', included: false, watched: true}, 36 | 37 | // Typescript files and map files 38 | {pattern: 'src/**/*.ts', included: false, watched: false}, 39 | {pattern: 'dist/**/*.js.map', included: false, watched: false} 40 | ], 41 | 42 | // proxied base paths 43 | proxies: { 44 | // required for component assests fetched by Angular's compiler 45 | '/src/': '/base/src/' 46 | }, 47 | 48 | port: 9876, 49 | 50 | logLevel: config.LOG_INFO, 51 | 52 | colors: true, 53 | 54 | autoWatch: true, 55 | 56 | browsers: ['Chrome'], 57 | 58 | // Karma plugins loaded 59 | plugins: [ 60 | 'karma-jasmine', 61 | 'karma-coverage', 62 | 'karma-chrome-launcher' 63 | ], 64 | 65 | // Coverage reporter generates the coverage 66 | reporters: ['progress', 'dots', 'coverage'], 67 | 68 | // Source files that you wanna generate coverage for. 69 | // Do not include tests or libraries (these files will be instrumented by Istanbul) 70 | preprocessors: { 71 | 'dist/**/!(*spec).js': ['coverage'] 72 | }, 73 | 74 | coverageReporter: { 75 | reporters:[ 76 | {type: 'json', subdir: '.', file: 'coverage-final.json'} 77 | ] 78 | }, 79 | 80 | singleRun: true 81 | }) 82 | }; 83 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-conf-testing", 3 | "version": "1.0.0", 4 | "description": "Angular2 Testing Workshop for ngConf", 5 | "main": "index.js", 6 | "scripts": { 7 | "postinstall": "typings install", 8 | "start": "npm run build && http-server -c-1 -o -s -p 8875 .", 9 | "dev": "npm run test && http-server -c-1 -o -s -p 8875 .", 10 | "build": "rm -rf dist && tsc -p src", 11 | "pretest": "npm run build", 12 | "test": "karma start karma.conf.js", 13 | "posttest": "node_modules/.bin/remap-istanbul -i coverage/coverage-final.json -o coverage -t html", 14 | "coverage": "http-server -c-1 -o -s -p 9875 ./coverage" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://elecash@github.com/Elecash/ng-conf-testing.git" 19 | }, 20 | "author": "Raul Jimenez (http://twofuckingedevelopers.com/)", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/Elecash/ng-conf-testing/issues" 24 | }, 25 | "homepage": "https://github.com/Elecash/ng-conf-testing#readme", 26 | "dependencies": { 27 | "@angular/common": "2.0.0-rc.2", 28 | "@angular/compiler": "2.0.0-rc.2", 29 | "@angular/core": "2.0.0-rc.2", 30 | "@angular/http": "2.0.0-rc.2", 31 | "@angular/platform-browser": "2.0.0-rc.2", 32 | "@angular/platform-browser-dynamic": "2.0.0-rc.2", 33 | "es6-promise": "3.1.2", 34 | "es6-shim": "0.35.0", 35 | "reflect-metadata": "0.1.2", 36 | "rxjs": "5.0.0-beta.6", 37 | "systemjs": "0.19.26", 38 | "zone.js": "0.6.12" 39 | }, 40 | "devDependencies": { 41 | "http-server": "0.9.0", 42 | "jasmine-core": "2.4.1", 43 | "karma": "0.13.22", 44 | "karma-chrome-launcher": "0.2.2", 45 | "karma-coverage": "0.5.3", 46 | "karma-jasmine": "0.3.6", 47 | "remap-istanbul": "0.6.3", 48 | "typescript": "1.8.10", 49 | "typings": "0.8.1" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/bootstrap.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import {bootstrap} from "@angular/platform-browser-dynamic"; 4 | import {HTTP_PROVIDERS} from "@angular/http"; 5 | 6 | import {GitHapp} from "./git-happ"; 7 | 8 | bootstrap(GitHapp, [ 9 | HTTP_PROVIDERS 10 | ]); 11 | -------------------------------------------------------------------------------- /src/components/gh-card.spec.ts: -------------------------------------------------------------------------------- 1 | import {it, describe, expect, async, inject, beforeEachProviders, beforeEach} from "@angular/core/testing"; 2 | import {TestComponentBuilder} from "@angular/compiler/testing"; 3 | import {Component, provide} from "@angular/core"; 4 | import {HTTP_PROVIDERS, XHRBackend, ResponseOptions, Response} from "@angular/http"; 5 | import {MockBackend, MockConnection} from "@angular/http/testing"; 6 | import {GithubCard} from "./gh-card"; 7 | 8 | @Component({ 9 | template: '', 10 | directives: [GithubCard] 11 | }) 12 | class TestGithubCard {} 13 | 14 | describe('Github Card Tests', () => { 15 | var builder; 16 | 17 | beforeEachProviders(() => { 18 | return [ 19 | HTTP_PROVIDERS, 20 | provide(XHRBackend, {useClass: MockBackend}) 21 | ] 22 | }); 23 | 24 | beforeEach( 25 | inject([XHRBackend, TestComponentBuilder], (backend, tcb) => { 26 | builder = tcb; 27 | 28 | backend.connections.subscribe( 29 | (connection:MockConnection) => { 30 | var options = new ResponseOptions({ 31 | body: { 32 | "login": "johnpapa", 33 | "id": 1202528, 34 | "avatar_url": "https://avatars.githubusercontent.com/u/1202528?v=3", 35 | "gravatar_id": "", 36 | "url": "https://api.github.com/users/johnpapa", 37 | "html_url": "https://github.com/johnpapa", 38 | "followers_url": "https://api.github.com/users/johnpapa/followers", 39 | "following_url": "https://api.github.com/users/johnpapa/following{/other_user}", 40 | "gists_url": "https://api.github.com/users/johnpapa/gists{/gist_id}", 41 | "starred_url": "https://api.github.com/users/johnpapa/starred{/owner}{/repo}", 42 | "subscriptions_url": "https://api.github.com/users/johnpapa/subscriptions", 43 | "organizations_url": "https://api.github.com/users/johnpapa/orgs", 44 | "repos_url": "https://api.github.com/users/johnpapa/repos", 45 | "events_url": "https://api.github.com/users/johnpapa/events{/privacy}", 46 | "received_events_url": "https://api.github.com/users/johnpapa/received_events", 47 | "type": "User", 48 | "site_admin": false, 49 | "name": "John Papa", 50 | "company": "JohnPapa.net, LLC", 51 | "blog": "http://johnpapa.net", 52 | "location": "Orlando, FL", 53 | "email": null, 54 | "hireable": null, 55 | "bio": null, 56 | "public_repos": 62, 57 | "public_gists": 28, 58 | "followers": 4612, 59 | "following": 0, 60 | "created_at": "2011-11-17T17:05:03Z", 61 | "updated_at": "2016-04-03T09:51:44Z" 62 | } 63 | }); 64 | 65 | var response:Response = new Response(options); 66 | 67 | connection.mockRespond(response); 68 | } 69 | ); 70 | }) 71 | ); 72 | 73 | it('Should create a GithubCard component', 74 | async(() => { 75 | builder.createAsync(TestGithubCard).then((fixture) => { 76 | fixture.detectChanges(); 77 | 78 | let compiled = fixture.debugElement.nativeElement; 79 | let name = compiled.querySelector('.avatar strong'); 80 | let username = compiled.querySelector('.avatar span'); 81 | let list = compiled.querySelectorAll('.status li a strong'); 82 | let repos = list[0]; 83 | let gists = list[1]; 84 | let followers = list[2]; 85 | 86 | expect(name.innerHTML).toContain('John Papa'); 87 | expect(username.innerHTML).toContain('@johnpapa'); 88 | expect(repos.innerHTML).toContain('62'); 89 | expect(gists.innerHTML).toContain('28'); 90 | expect(followers.innerHTML).toContain('4.6K'); 91 | }); 92 | }) 93 | ); 94 | }); 95 | -------------------------------------------------------------------------------- /src/components/gh-card.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input, OnChanges} from "@angular/core"; 2 | import {GithubUser} from "../services/gh-user"; 3 | import {Thousands} from "../pipes/thousands"; 4 | import {GithubService} from "../services/gh-service"; 5 | import "rxjs/add/operator/map"; 6 | 7 | @Component({ 8 | selector: 'gh-card', 9 | template: ` 10 | {{ loadingMessage }} 11 | 43 | `, 44 | styles: [` 45 | :host { 46 | display: block; 47 | max-width: 400px; 48 | padding: 0; 49 | margin: 0; 50 | font-size: 14px; 51 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 52 | overflow: hidden; 53 | border: 1px solid #eee; 54 | border-radius: 5px; 55 | border-color: #eee #ddd #bbb; 56 | box-shadow: rgba(0, 0, 0, 0.14) 0 1px 3px; 57 | } 58 | 59 | .github-card { 60 | border-radius: 5px; 61 | padding: 8px 8px 0; 62 | background: #fff; 63 | color: #555; 64 | position: relative; 65 | } 66 | 67 | .github-card a { 68 | text-decoration: none; 69 | color: #4183c4; 70 | outline: 0; 71 | } 72 | .github-card a:hover { 73 | text-decoration: underline; 74 | } 75 | 76 | .github-card .header { 77 | position: relative; 78 | } 79 | 80 | .github-card .button { 81 | position: absolute; 82 | top: 0; 83 | right: 0; 84 | padding: 4px 8px 4px 7px; 85 | color: #555; 86 | text-shadow: 0 1px 0 #fff; 87 | border: 1px solid #d4d4d4; 88 | border-radius: 3px; 89 | font-size: 13px; 90 | font-weight: bold; 91 | line-height: 14px; 92 | background-color: #e6e6e6; 93 | background-image: -webkit-linear-gradient(#fafafa, #eaeaea); 94 | background-image: -moz-linear-gradient(#fafafa, #eaeaea); 95 | background-image: -ms-linear-gradient(#fafafa, #eaeaea); 96 | background-image: linear-gradient(#fafafa, #eaeaea); 97 | } 98 | 99 | .github-card .button:hover { 100 | color: #fff; 101 | text-decoration: none; 102 | background-color: #3072b3; 103 | background-image: -webkit-linear-gradient(#599bdc, #3072b3); 104 | background-image: -moz-linear-gradient(#599bdc, #3072b3); 105 | background-image: -ms-linear-gradient(#599bdc, #3072b3); 106 | background-image: linear-gradient(#599bdc, #3072b3); 107 | border-color: #518cc6 #518cc6 #2a65a0; 108 | text-shadow: 0 -1px 0 rgba(0,0,0,.25); 109 | } 110 | 111 | /* user card */ 112 | .user-card .header { 113 | padding: 3px 0 4px 57px; 114 | min-height: 48px; 115 | } 116 | 117 | .user-card .header a { 118 | text-decoration: none; 119 | } 120 | 121 | .user-card .header a:hover strong { 122 | text-decoration: underline; 123 | } 124 | 125 | .user-card img { 126 | position: absolute; 127 | top: 0; 128 | left: 0; 129 | width: 48px; 130 | height: 48px; 131 | background: #fff; 132 | border-radius: 4px; 133 | } 134 | 135 | .user-card strong { 136 | display: block; 137 | color: #292f33; 138 | font-size: 16px; 139 | line-height: 1.6; 140 | } 141 | 142 | .user-card ul { 143 | text-transform: uppercase; 144 | font-size: 12px; 145 | color: #707070; 146 | list-style-type: none; 147 | margin: 0; 148 | padding: 0; 149 | border-top: 1px solid #eee; 150 | border-bottom: 1px solid #eee; 151 | zoom: 1; 152 | } 153 | 154 | .user-card ul:after { 155 | display: block; 156 | content: ''; 157 | clear: both; 158 | } 159 | 160 | .user-card .status a { 161 | color: #707070; 162 | text-decoration: none; 163 | } 164 | 165 | .user-card .status a:hover { 166 | color: #4183c4; 167 | } 168 | 169 | .user-card .status li { 170 | float: left; 171 | padding: 4px 18px; 172 | border-left: 1px solid #eee; 173 | } 174 | 175 | .user-card .status li:first-child { 176 | border-left: 0; 177 | padding-left: 0; 178 | } 179 | 180 | .user-card .footer { 181 | font-size: 12px; 182 | font-weight: 700; 183 | padding: 11px 0 10px; 184 | color: #646464; 185 | } 186 | 187 | .user-card .footer a { 188 | color: #646464; 189 | } 190 | `], 191 | providers: [GithubService], 192 | pipes: [Thousands] 193 | }) 194 | export class GithubCard implements OnChanges { 195 | @Input() ghUser:string; 196 | 197 | user:GithubUser; 198 | loadingMessage:string = ''; 199 | 200 | constructor(public service:GithubService) {} 201 | 202 | ngOnChanges(changeRecord) { 203 | if (changeRecord.ghUser.currentValue) { 204 | this.searchUser(changeRecord.ghUser.currentValue); 205 | } 206 | } 207 | 208 | searchUser(username:string) { 209 | this.loadingMessage = 'loading user...'; 210 | 211 | this.service.getUserByUsername(username) 212 | .subscribe( 213 | this.onLoadUser.bind(this), 214 | this.onLoadUserError.bind(this) 215 | ); 216 | } 217 | 218 | onLoadUser(user) { 219 | this.loadingMessage = ''; 220 | this.user = user; 221 | } 222 | 223 | onLoadUserError(user) { 224 | this.loadingMessage = "User doesn't exist"; 225 | this.user = null; 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /src/components/gh-repo.spec.ts: -------------------------------------------------------------------------------- 1 | import {it, describe, expect, async, inject, beforeEachProviders, beforeEach} from "@angular/core/testing"; 2 | import {TestComponentBuilder} from '@angular/compiler/testing'; 3 | import {Component, provide} from "@angular/core"; 4 | import {HTTP_PROVIDERS, XHRBackend, ResponseOptions, Response} from "@angular/http"; 5 | import {MockBackend, MockConnection} from "@angular/http/testing"; 6 | import {GithubRepo} from "./gh-repo"; 7 | 8 | @Component({ 9 | template: '', 10 | directives: [GithubRepo] 11 | }) 12 | class TestGithubRepo {} 13 | 14 | describe('Github Repo Tests', () => { 15 | var builder; 16 | 17 | beforeEachProviders(() => { 18 | return [ 19 | HTTP_PROVIDERS, 20 | provide(XHRBackend, {useClass: MockBackend}) 21 | ] 22 | }); 23 | 24 | beforeEach( 25 | inject([XHRBackend, TestComponentBuilder], (backend, tcb) => { 26 | builder = tcb; 27 | 28 | backend.connections.subscribe( 29 | (connection:MockConnection) => { 30 | var options = new ResponseOptions({ 31 | body: { 32 | "id": 11823977, 33 | "name": "videogular", 34 | "full_name": "videogular/videogular", 35 | "owner": { 36 | "login": "videogular", 37 | "id": 15369882, 38 | "avatar_url": "https://avatars.githubusercontent.com/u/15369882?v=3", 39 | "gravatar_id": "", 40 | "url": "https://api.github.com/users/videogular", 41 | "html_url": "https://github.com/videogular", 42 | "followers_url": "https://api.github.com/users/videogular/followers", 43 | "following_url": "https://api.github.com/users/videogular/following{/other_user}", 44 | "gists_url": "https://api.github.com/users/videogular/gists{/gist_id}", 45 | "starred_url": "https://api.github.com/users/videogular/starred{/owner}{/repo}", 46 | "subscriptions_url": "https://api.github.com/users/videogular/subscriptions", 47 | "organizations_url": "https://api.github.com/users/videogular/orgs", 48 | "repos_url": "https://api.github.com/users/videogular/repos", 49 | "events_url": "https://api.github.com/users/videogular/events{/privacy}", 50 | "received_events_url": "https://api.github.com/users/videogular/received_events", 51 | "type": "Organization", 52 | "site_admin": false 53 | }, 54 | "private": false, 55 | "html_url": "https://github.com/videogular/videogular", 56 | "description": "The HTML5 video player for AngularJS", 57 | "fork": false, 58 | "url": "https://api.github.com/repos/videogular/videogular", 59 | "forks_url": "https://api.github.com/repos/videogular/videogular/forks", 60 | "keys_url": "https://api.github.com/repos/videogular/videogular/keys{/key_id}", 61 | "collaborators_url": "https://api.github.com/repos/videogular/videogular/collaborators{/collaborator}", 62 | "teams_url": "https://api.github.com/repos/videogular/videogular/teams", 63 | "hooks_url": "https://api.github.com/repos/videogular/videogular/hooks", 64 | "issue_events_url": "https://api.github.com/repos/videogular/videogular/issues/events{/number}", 65 | "events_url": "https://api.github.com/repos/videogular/videogular/events", 66 | "assignees_url": "https://api.github.com/repos/videogular/videogular/assignees{/user}", 67 | "branches_url": "https://api.github.com/repos/videogular/videogular/branches{/branch}", 68 | "tags_url": "https://api.github.com/repos/videogular/videogular/tags", 69 | "blobs_url": "https://api.github.com/repos/videogular/videogular/git/blobs{/sha}", 70 | "git_tags_url": "https://api.github.com/repos/videogular/videogular/git/tags{/sha}", 71 | "git_refs_url": "https://api.github.com/repos/videogular/videogular/git/refs{/sha}", 72 | "trees_url": "https://api.github.com/repos/videogular/videogular/git/trees{/sha}", 73 | "statuses_url": "https://api.github.com/repos/videogular/videogular/statuses/{sha}", 74 | "languages_url": "https://api.github.com/repos/videogular/videogular/languages", 75 | "stargazers_url": "https://api.github.com/repos/videogular/videogular/stargazers", 76 | "contributors_url": "https://api.github.com/repos/videogular/videogular/contributors", 77 | "subscribers_url": "https://api.github.com/repos/videogular/videogular/subscribers", 78 | "subscription_url": "https://api.github.com/repos/videogular/videogular/subscription", 79 | "commits_url": "https://api.github.com/repos/videogular/videogular/commits{/sha}", 80 | "git_commits_url": "https://api.github.com/repos/videogular/videogular/git/commits{/sha}", 81 | "comments_url": "https://api.github.com/repos/videogular/videogular/comments{/number}", 82 | "issue_comment_url": "https://api.github.com/repos/videogular/videogular/issues/comments{/number}", 83 | "contents_url": "https://api.github.com/repos/videogular/videogular/contents/{+path}", 84 | "compare_url": "https://api.github.com/repos/videogular/videogular/compare/{base}...{head}", 85 | "merges_url": "https://api.github.com/repos/videogular/videogular/merges", 86 | "archive_url": "https://api.github.com/repos/videogular/videogular/{archive_format}{/ref}", 87 | "downloads_url": "https://api.github.com/repos/videogular/videogular/downloads", 88 | "issues_url": "https://api.github.com/repos/videogular/videogular/issues{/number}", 89 | "pulls_url": "https://api.github.com/repos/videogular/videogular/pulls{/number}", 90 | "milestones_url": "https://api.github.com/repos/videogular/videogular/milestones{/number}", 91 | "notifications_url": "https://api.github.com/repos/videogular/videogular/notifications{?since,all,participating}", 92 | "labels_url": "https://api.github.com/repos/videogular/videogular/labels{/name}", 93 | "releases_url": "https://api.github.com/repos/videogular/videogular/releases{/id}", 94 | "deployments_url": "https://api.github.com/repos/videogular/videogular/deployments", 95 | "created_at": "2013-08-01T18:05:20Z", 96 | "updated_at": "2016-04-29T01:33:33Z", 97 | "pushed_at": "2016-04-21T23:45:13Z", 98 | "git_url": "git://github.com/videogular/videogular.git", 99 | "ssh_url": "git@github.com:videogular/videogular.git", 100 | "clone_url": "https://github.com/videogular/videogular.git", 101 | "svn_url": "https://github.com/videogular/videogular", 102 | "homepage": "http://www.videogular.com/demo", 103 | "size": 146733, 104 | "stargazers_count": 1166, 105 | "watchers_count": 1166, 106 | "language": "JavaScript", 107 | "has_issues": true, 108 | "has_downloads": true, 109 | "has_wiki": true, 110 | "has_pages": false, 111 | "forks_count": 286, 112 | "mirror_url": null, 113 | "open_issues_count": 20, 114 | "forks": 286, 115 | "open_issues": 20, 116 | "watchers": 1166, 117 | "default_branch": "master", 118 | "organization": { 119 | "login": "videogular", 120 | "id": 15369882, 121 | "avatar_url": "https://avatars.githubusercontent.com/u/15369882?v=3", 122 | "gravatar_id": "", 123 | "url": "https://api.github.com/users/videogular", 124 | "html_url": "https://github.com/videogular", 125 | "followers_url": "https://api.github.com/users/videogular/followers", 126 | "following_url": "https://api.github.com/users/videogular/following{/other_user}", 127 | "gists_url": "https://api.github.com/users/videogular/gists{/gist_id}", 128 | "starred_url": "https://api.github.com/users/videogular/starred{/owner}{/repo}", 129 | "subscriptions_url": "https://api.github.com/users/videogular/subscriptions", 130 | "organizations_url": "https://api.github.com/users/videogular/orgs", 131 | "repos_url": "https://api.github.com/users/videogular/repos", 132 | "events_url": "https://api.github.com/users/videogular/events{/privacy}", 133 | "received_events_url": "https://api.github.com/users/videogular/received_events", 134 | "type": "Organization", 135 | "site_admin": false 136 | }, 137 | "network_count": 286, 138 | "subscribers_count": 77 139 | } 140 | }); 141 | 142 | var response:Response = new Response(options); 143 | 144 | connection.mockRespond(response); 145 | } 146 | ); 147 | }) 148 | ); 149 | 150 | it('Should create a GithubRepo component', 151 | async(inject([TestComponentBuilder], (tcb) => { 152 | tcb.createAsync(TestGithubRepo).then((fixture) => { 153 | fixture.detectChanges(); 154 | 155 | let compiled = fixture.debugElement.nativeElement; 156 | let name = compiled.querySelector('.header .name a'); 157 | let language = compiled.querySelector('.header .name .language'); 158 | let description = compiled.querySelector('.content p'); 159 | let link = compiled.querySelector('.content p a'); 160 | let stats = compiled.querySelectorAll('.footer .status strong'); 161 | let forks = stats[0]; 162 | let stars = stats[1]; 163 | 164 | expect(name.innerHTML).toContain('videogular'); 165 | expect(language.innerHTML).toContain('JavaScript'); 166 | expect(description.innerHTML).toContain('The HTML5 video player for AngularJS'); 167 | expect(link.innerHTML).toContain('http://www.videogular.com/demo'); 168 | expect(forks.innerHTML).toContain('286'); 169 | expect(stars.innerHTML).toContain('1166'); 170 | }); 171 | })) 172 | ); 173 | }); 174 | -------------------------------------------------------------------------------- /src/components/gh-repo.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input, OnChanges} from "@angular/core"; 2 | import {GithubRepository} from "../services/gh-repository"; 3 | import {Thousands} from "../pipes/thousands"; 4 | import {GithubService} from "../services/gh-service"; 5 | import "rxjs/add/operator/map"; 6 | 7 | @Component({ 8 | selector: 'gh-repo', 9 | template: ` 10 | {{ loadingMessage }} 11 |
12 |
13 | 14 | 15 | 16 | 17 | {{ repo.name }} 18 | {{ repo.language }} 19 | 20 | Created by {{ repo.owner.login }} 21 | Star 22 |
23 | 24 |
25 |

{{ repo.description }} - {{ repo.homepage }}

26 |
27 | 28 | 37 |
38 | `, 39 | styles: [` 40 | :host { 41 | display: block; 42 | max-width: 400px; 43 | padding: 0; 44 | margin: 0; 45 | font-size: 14px; 46 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 47 | overflow: hidden; 48 | border: 1px solid #eee; 49 | border-radius: 5px; 50 | border-color: #eee #ddd #bbb; 51 | box-shadow: rgba(0, 0, 0, 0.14) 0 1px 3px; 52 | } 53 | 54 | .github-card { 55 | border-radius: 5px; 56 | padding: 8px 8px 0; 57 | background: #fff; 58 | color: #555; 59 | position: relative; 60 | } 61 | 62 | .github-card a { 63 | text-decoration: none; 64 | color: #4183c4; 65 | outline: 0; 66 | } 67 | .github-card a:hover { 68 | text-decoration: underline; 69 | } 70 | 71 | .github-card .header { 72 | position: relative; 73 | } 74 | 75 | .github-card .button { 76 | position: absolute; 77 | top: 0; 78 | right: 0; 79 | padding: 4px 8px 4px 7px; 80 | color: #555; 81 | text-shadow: 0 1px 0 #fff; 82 | border: 1px solid #d4d4d4; 83 | border-radius: 3px; 84 | font-size: 13px; 85 | font-weight: bold; 86 | line-height: 14px; 87 | background-color: #e6e6e6; 88 | background-image: -webkit-linear-gradient(#fafafa, #eaeaea); 89 | background-image: -moz-linear-gradient(#fafafa, #eaeaea); 90 | background-image: -ms-linear-gradient(#fafafa, #eaeaea); 91 | background-image: linear-gradient(#fafafa, #eaeaea); 92 | } 93 | 94 | .github-card .button:hover { 95 | color: #fff; 96 | text-decoration: none; 97 | background-color: #3072b3; 98 | background-image: -webkit-linear-gradient(#599bdc, #3072b3); 99 | background-image: -moz-linear-gradient(#599bdc, #3072b3); 100 | background-image: -ms-linear-gradient(#599bdc, #3072b3); 101 | background-image: linear-gradient(#599bdc, #3072b3); 102 | border-color: #518cc6 #518cc6 #2a65a0; 103 | text-shadow: 0 -1px 0 rgba(0,0,0,.25); 104 | } 105 | 106 | /* repo card */ 107 | .repo-card .header { 108 | padding: 3px 0 4px 57px; 109 | } 110 | .repo-card .avatar, .repo-card .avatar img { 111 | position: absolute; 112 | top: 0; 113 | left: 0; 114 | width: 48px; 115 | height: 48px; 116 | background: #fff; 117 | border-radius: 4px; 118 | } 119 | .repo-card .header a { 120 | color: #707070; 121 | } 122 | .repo-card .header a:hover { 123 | color: #FFF; 124 | } 125 | .repo-card .header strong { 126 | display: block; 127 | font-size: 18px; 128 | line-height: 1.4; 129 | } 130 | .repo-card .header strong a { 131 | color: #292f33; 132 | } 133 | .repo-card .header sup { 134 | font-size: 10px; 135 | margin-left: 3px; 136 | color: #797979; 137 | } 138 | .repo-card .content { 139 | padding: 6px 0 10px; 140 | } 141 | .repo-card .content p { 142 | margin: 0 5px 0 0; 143 | font: 18px/24px Georgia, "Times New Roman", Palatino, serif; 144 | overflow: hidden; 145 | clear: both; 146 | word-wrap: break-word; 147 | } 148 | .repo-card .footer { 149 | border-top: 1px solid #eee; 150 | padding: 8px 0 6px; 151 | } 152 | .repo-card .status { 153 | font-size: 10px; 154 | padding-right: 10px; 155 | text-transform: uppercase; 156 | } 157 | .repo-card .status strong { 158 | font-size: 12px; 159 | padding-right: 5px; 160 | } 161 | `], 162 | providers: [GithubService], 163 | pipes: [Thousands] 164 | }) 165 | export class GithubRepo implements OnChanges { 166 | @Input() ghRepo:string; 167 | 168 | repo:GithubRepository; 169 | loadingMessage:string = ''; 170 | 171 | constructor(public service:GithubService) {} 172 | 173 | ngOnChanges(changeRecord) { 174 | if (changeRecord.ghRepo.currentValue) { 175 | this.searchRepo(changeRecord.ghRepo.currentValue); 176 | } 177 | } 178 | 179 | searchRepo(repo:string) { 180 | this.loadingMessage = 'loading repository...'; 181 | 182 | this.service.getRepository(repo) 183 | .subscribe( 184 | this.onLoadRepo.bind(this), 185 | this.onLoadRepoError.bind(this) 186 | ); 187 | } 188 | 189 | onLoadRepo(repo) { 190 | this.loadingMessage = ''; 191 | this.repo = repo; 192 | } 193 | 194 | onLoadRepoError(repo) { 195 | this.loadingMessage = "Repository doesn't exist"; 196 | this.repo = null; 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/git-happ.ts: -------------------------------------------------------------------------------- 1 | import {Component} from "@angular/core"; 2 | import {GithubCard} from "./components/gh-card"; 3 | import {GithubRepo} from "./components/gh-repo"; 4 | 5 | @Component({ 6 | selector: 'git-happ', 7 | template: ` 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | `, 16 | directives: [GithubCard, GithubRepo] 17 | }) 18 | export class GitHapp { 19 | ghUser:string; 20 | ghRepo:string; 21 | 22 | constructor() {} 23 | 24 | searchUser(input) { 25 | this.ghUser = input.value; 26 | } 27 | 28 | searchRepo(input) { 29 | this.ghRepo = input.value; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/pipes/thousands.spec.ts: -------------------------------------------------------------------------------- 1 | import {it, describe, expect, beforeEach} from '@angular/core/testing'; 2 | import {Thousands} from "./thousands"; 3 | 4 | describe('Thousands pipe tests', () => { 5 | let pipe:Thousands; 6 | 7 | beforeEach(() => { 8 | pipe = new Thousands(); 9 | }); 10 | 11 | it('Should transform 300 to 300', () => { 12 | var result = pipe.transform('300', null); 13 | 14 | expect(result).toEqual('300'); 15 | }); 16 | 17 | it('Should transform 1300 to 1.3K', () => { 18 | var result = pipe.transform('1300', null); 19 | 20 | expect(result).toEqual('1.3K'); 21 | }); 22 | 23 | it('Should transform 13000 to 13K', () => { 24 | var result = pipe.transform('13000', null); 25 | 26 | expect(result).toEqual('13K'); 27 | }); 28 | 29 | it('Should transform 13500 to 13.5K', () => { 30 | var result = pipe.transform('13500', null); 31 | 32 | expect(result).toEqual('13.5K'); 33 | }); 34 | 35 | it('Should transform 13599 to 13.6K', () => { 36 | var result = pipe.transform('13599', null); 37 | 38 | expect(result).toEqual('13.6K'); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/pipes/thousands.ts: -------------------------------------------------------------------------------- 1 | import {PipeTransform, Pipe} from '@angular/core'; 2 | 3 | @Pipe({name: 'thousands'}) 4 | export class Thousands implements PipeTransform { 5 | constructor() { 6 | } 7 | 8 | transform(text:string, args:any[]):any { 9 | let num:number = parseInt(text); 10 | let result:string; 11 | 12 | if (isNaN(num) || num < 1000) { 13 | result = text; 14 | } 15 | else { 16 | let exp:number = (Math.floor(Math.log(num) / Math.log(1000))); 17 | let value:number = (num / Math.pow(1000, exp)); 18 | let decimals:number = (value % 1 != 0) ? 1 : 0; 19 | result = value.toFixed(decimals) + 'K'; 20 | } 21 | 22 | return result; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/services/gh-repository.ts: -------------------------------------------------------------------------------- 1 | export class GithubRepository { 2 | id: number; 3 | name: string; 4 | full_name: string; 5 | owner: any; 6 | private: boolean; 7 | html_url: string; 8 | description: string; 9 | fork: boolean; 10 | url: string; 11 | forks_url: string; 12 | keys_url: string; 13 | collaborators_url: string; 14 | teams_url: string; 15 | hooks_url: string; 16 | issue_events_url: string; 17 | events_url: string; 18 | assignees_url: string; 19 | branches_url: string; 20 | tags_url: string; 21 | blobs_url: string; 22 | git_tags_url: string; 23 | git_refs_url: string; 24 | trees_url: string; 25 | statuses_url: string; 26 | languages_url: string; 27 | stargazers_url: string; 28 | contributors_url: string; 29 | subscribers_url: string; 30 | subscription_url: string; 31 | commits_url: string; 32 | git_commits_url: string; 33 | comments_url: string; 34 | issue_comment_url: string; 35 | contents_url: string; 36 | compare_url: string; 37 | merges_url: string; 38 | archive_url: string; 39 | downloads_url: string; 40 | issues_url: string; 41 | pulls_url: string; 42 | milestones_url: string; 43 | notifications_url: string; 44 | labels_url: string; 45 | releases_url: string; 46 | deployments_url: string; 47 | created_at: string; 48 | updated_at: string; 49 | pushed_at: string; 50 | git_url: string; 51 | ssh_url: string; 52 | clone_url: string; 53 | svn_url: string; 54 | homepage: string; 55 | size: number; 56 | stargazers_count: number; 57 | watchers_count: number; 58 | language: string; 59 | has_issues: boolean; 60 | has_downloads: boolean; 61 | has_wiki: boolean; 62 | has_pages: boolean; 63 | forks_count: number; 64 | mirror_url: string; 65 | open_issues_count: number; 66 | forks: number; 67 | open_issues: number; 68 | watchers: number; 69 | default_branch: string; 70 | organization: any; 71 | network_count: number; 72 | subscribers_count: number 73 | } 74 | -------------------------------------------------------------------------------- /src/services/gh-service.spec.ts: -------------------------------------------------------------------------------- 1 | import {it, describe, expect, async, inject, beforeEachProviders} from "@angular/core/testing"; 2 | import {provide} from "@angular/core"; 3 | import {HTTP_PROVIDERS, XHRBackend, ResponseOptions, Response} from "@angular/http"; 4 | import {MockBackend, MockConnection} from "@angular/http/testing"; 5 | import {GithubService} from "./gh-service"; 6 | 7 | describe('Github Service Tests', () => { 8 | beforeEachProviders(() => { 9 | return [ 10 | HTTP_PROVIDERS, 11 | provide(XHRBackend, {useClass: MockBackend}), 12 | GithubService 13 | ] 14 | }); 15 | 16 | it('Should get a user by username', 17 | async(inject([XHRBackend, GithubService], (backend, service) => { 18 | backend.connections.subscribe( 19 | (connection:MockConnection) => { 20 | var options = new ResponseOptions({ 21 | body: { 22 | "login": "johnpapa", 23 | "id": 1202528, 24 | "avatar_url": "https://avatars.githubusercontent.com/u/1202528?v=3", 25 | "gravatar_id": "", 26 | "url": "https://api.github.com/users/johnpapa", 27 | "html_url": "https://github.com/johnpapa", 28 | "followers_url": "https://api.github.com/users/johnpapa/followers", 29 | "following_url": "https://api.github.com/users/johnpapa/following{/other_user}", 30 | "gists_url": "https://api.github.com/users/johnpapa/gists{/gist_id}", 31 | "starred_url": "https://api.github.com/users/johnpapa/starred{/owner}{/repo}", 32 | "subscriptions_url": "https://api.github.com/users/johnpapa/subscriptions", 33 | "organizations_url": "https://api.github.com/users/johnpapa/orgs", 34 | "repos_url": "https://api.github.com/users/johnpapa/repos", 35 | "events_url": "https://api.github.com/users/johnpapa/events{/privacy}", 36 | "received_events_url": "https://api.github.com/users/johnpapa/received_events", 37 | "type": "User", 38 | "site_admin": false, 39 | "name": "John Papa", 40 | "company": "JohnPapa.net, LLC", 41 | "blog": "http://johnpapa.net", 42 | "location": "Orlando, FL", 43 | "email": null, 44 | "hireable": null, 45 | "bio": null, 46 | "public_repos": 62, 47 | "public_gists": 28, 48 | "followers": 4612, 49 | "following": 0, 50 | "created_at": "2011-11-17T17:05:03Z", 51 | "updated_at": "2016-04-03T09:51:44Z" 52 | } 53 | }); 54 | 55 | var response:Response = new Response(options); 56 | 57 | connection.mockRespond(response); 58 | }); 59 | 60 | service.getUserByUsername('johnpapa').subscribe((user) => { 61 | expect(user.name).toBe('John Papa'); 62 | expect(user.login).toBe('johnpapa'); 63 | expect(user.public_repos).toBe(62); 64 | expect(user.public_gists).toBe(28); 65 | expect(user.followers).toBe(4612); 66 | }); 67 | })) 68 | ); 69 | 70 | it('Should get a repo by username/repository', 71 | async(inject([XHRBackend, GithubService], (backend, service) => { 72 | backend.connections.subscribe( 73 | (connection:MockConnection) => { 74 | var options = new ResponseOptions({ 75 | body: { 76 | "id": 11823977, 77 | "name": "videogular", 78 | "full_name": "videogular/videogular", 79 | "owner": { 80 | "login": "videogular", 81 | "id": 15369882, 82 | "avatar_url": "https://avatars.githubusercontent.com/u/15369882?v=3", 83 | "gravatar_id": "", 84 | "url": "https://api.github.com/users/videogular", 85 | "html_url": "https://github.com/videogular", 86 | "followers_url": "https://api.github.com/users/videogular/followers", 87 | "following_url": "https://api.github.com/users/videogular/following{/other_user}", 88 | "gists_url": "https://api.github.com/users/videogular/gists{/gist_id}", 89 | "starred_url": "https://api.github.com/users/videogular/starred{/owner}{/repo}", 90 | "subscriptions_url": "https://api.github.com/users/videogular/subscriptions", 91 | "organizations_url": "https://api.github.com/users/videogular/orgs", 92 | "repos_url": "https://api.github.com/users/videogular/repos", 93 | "events_url": "https://api.github.com/users/videogular/events{/privacy}", 94 | "received_events_url": "https://api.github.com/users/videogular/received_events", 95 | "type": "Organization", 96 | "site_admin": false 97 | }, 98 | "private": false, 99 | "html_url": "https://github.com/videogular/videogular", 100 | "description": "The HTML5 video player for AngularJS", 101 | "fork": false, 102 | "url": "https://api.github.com/repos/videogular/videogular", 103 | "forks_url": "https://api.github.com/repos/videogular/videogular/forks", 104 | "keys_url": "https://api.github.com/repos/videogular/videogular/keys{/key_id}", 105 | "collaborators_url": "https://api.github.com/repos/videogular/videogular/collaborators{/collaborator}", 106 | "teams_url": "https://api.github.com/repos/videogular/videogular/teams", 107 | "hooks_url": "https://api.github.com/repos/videogular/videogular/hooks", 108 | "issue_events_url": "https://api.github.com/repos/videogular/videogular/issues/events{/number}", 109 | "events_url": "https://api.github.com/repos/videogular/videogular/events", 110 | "assignees_url": "https://api.github.com/repos/videogular/videogular/assignees{/user}", 111 | "branches_url": "https://api.github.com/repos/videogular/videogular/branches{/branch}", 112 | "tags_url": "https://api.github.com/repos/videogular/videogular/tags", 113 | "blobs_url": "https://api.github.com/repos/videogular/videogular/git/blobs{/sha}", 114 | "git_tags_url": "https://api.github.com/repos/videogular/videogular/git/tags{/sha}", 115 | "git_refs_url": "https://api.github.com/repos/videogular/videogular/git/refs{/sha}", 116 | "trees_url": "https://api.github.com/repos/videogular/videogular/git/trees{/sha}", 117 | "statuses_url": "https://api.github.com/repos/videogular/videogular/statuses/{sha}", 118 | "languages_url": "https://api.github.com/repos/videogular/videogular/languages", 119 | "stargazers_url": "https://api.github.com/repos/videogular/videogular/stargazers", 120 | "contributors_url": "https://api.github.com/repos/videogular/videogular/contributors", 121 | "subscribers_url": "https://api.github.com/repos/videogular/videogular/subscribers", 122 | "subscription_url": "https://api.github.com/repos/videogular/videogular/subscription", 123 | "commits_url": "https://api.github.com/repos/videogular/videogular/commits{/sha}", 124 | "git_commits_url": "https://api.github.com/repos/videogular/videogular/git/commits{/sha}", 125 | "comments_url": "https://api.github.com/repos/videogular/videogular/comments{/number}", 126 | "issue_comment_url": "https://api.github.com/repos/videogular/videogular/issues/comments{/number}", 127 | "contents_url": "https://api.github.com/repos/videogular/videogular/contents/{+path}", 128 | "compare_url": "https://api.github.com/repos/videogular/videogular/compare/{base}...{head}", 129 | "merges_url": "https://api.github.com/repos/videogular/videogular/merges", 130 | "archive_url": "https://api.github.com/repos/videogular/videogular/{archive_format}{/ref}", 131 | "downloads_url": "https://api.github.com/repos/videogular/videogular/downloads", 132 | "issues_url": "https://api.github.com/repos/videogular/videogular/issues{/number}", 133 | "pulls_url": "https://api.github.com/repos/videogular/videogular/pulls{/number}", 134 | "milestones_url": "https://api.github.com/repos/videogular/videogular/milestones{/number}", 135 | "notifications_url": "https://api.github.com/repos/videogular/videogular/notifications{?since,all,participating}", 136 | "labels_url": "https://api.github.com/repos/videogular/videogular/labels{/name}", 137 | "releases_url": "https://api.github.com/repos/videogular/videogular/releases{/id}", 138 | "deployments_url": "https://api.github.com/repos/videogular/videogular/deployments", 139 | "created_at": "2013-08-01T18:05:20Z", 140 | "updated_at": "2016-04-29T01:33:33Z", 141 | "pushed_at": "2016-04-21T23:45:13Z", 142 | "git_url": "git://github.com/videogular/videogular.git", 143 | "ssh_url": "git@github.com:videogular/videogular.git", 144 | "clone_url": "https://github.com/videogular/videogular.git", 145 | "svn_url": "https://github.com/videogular/videogular", 146 | "homepage": "http://www.videogular.com/demo", 147 | "size": 146733, 148 | "stargazers_count": 1166, 149 | "watchers_count": 1166, 150 | "language": "JavaScript", 151 | "has_issues": true, 152 | "has_downloads": true, 153 | "has_wiki": true, 154 | "has_pages": false, 155 | "forks_count": 286, 156 | "mirror_url": null, 157 | "open_issues_count": 20, 158 | "forks": 286, 159 | "open_issues": 20, 160 | "watchers": 1166, 161 | "default_branch": "master", 162 | "organization": { 163 | "login": "videogular", 164 | "id": 15369882, 165 | "avatar_url": "https://avatars.githubusercontent.com/u/15369882?v=3", 166 | "gravatar_id": "", 167 | "url": "https://api.github.com/users/videogular", 168 | "html_url": "https://github.com/videogular", 169 | "followers_url": "https://api.github.com/users/videogular/followers", 170 | "following_url": "https://api.github.com/users/videogular/following{/other_user}", 171 | "gists_url": "https://api.github.com/users/videogular/gists{/gist_id}", 172 | "starred_url": "https://api.github.com/users/videogular/starred{/owner}{/repo}", 173 | "subscriptions_url": "https://api.github.com/users/videogular/subscriptions", 174 | "organizations_url": "https://api.github.com/users/videogular/orgs", 175 | "repos_url": "https://api.github.com/users/videogular/repos", 176 | "events_url": "https://api.github.com/users/videogular/events{/privacy}", 177 | "received_events_url": "https://api.github.com/users/videogular/received_events", 178 | "type": "Organization", 179 | "site_admin": false 180 | }, 181 | "network_count": 286, 182 | "subscribers_count": 78 183 | } 184 | }); 185 | 186 | var response:Response = new Response(options); 187 | 188 | connection.mockRespond(response); 189 | }); 190 | 191 | service.getRepository('videogular/videogular').subscribe((repo) => { 192 | expect(repo.name).toBe('videogular'); 193 | expect(repo.full_name).toBe('videogular/videogular'); 194 | expect(repo.network_count).toBe(286); 195 | expect(repo.subscribers_count).toBe(78); 196 | expect(repo.watchers).toBe(1166); 197 | }); 198 | })) 199 | ); 200 | }); 201 | -------------------------------------------------------------------------------- /src/services/gh-service.ts: -------------------------------------------------------------------------------- 1 | import {Http} from '@angular/http'; 2 | import {Observable} from 'rxjs/Observable'; 3 | import {Inject} from '@angular/core'; 4 | import {GithubUser} from './gh-user'; 5 | 6 | export class GithubService { 7 | static GITHUB_API:string = 'https://api.github.com'; 8 | static GITHUB_PARAMS:string = '?client_id=aaa27ad06049163bd846&client_secret=d478f87d9bdf95efa72015b77b26c5b6a17127a0'; 9 | http:Http; 10 | user:GithubUser; 11 | 12 | constructor(@Inject(Http) http:Http) { 13 | this.http = http; 14 | } 15 | 16 | getUserByUsername(username:string):Observable { 17 | let url:string = GithubService.GITHUB_API + '/users/' + username + GithubService.GITHUB_PARAMS; 18 | 19 | return this.http.get(url).map(response => response.json()); 20 | } 21 | 22 | getRepository(repo:string):Observable { 23 | let url:string = GithubService.GITHUB_API + '/repos/' + repo + GithubService.GITHUB_PARAMS; 24 | 25 | return this.http.get(url).map(response => response.json()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/services/gh-user.ts: -------------------------------------------------------------------------------- 1 | export class GithubUser { 2 | login:string; 3 | id:number; 4 | avatar_url:string; 5 | gravatar_id:string; 6 | url:string; 7 | html_url:string; 8 | followers_url:string; 9 | following_url:string; 10 | gists_url:string; 11 | starred_url:string; 12 | subscriptions_url:string; 13 | organizations_url:string; 14 | repos_url:string; 15 | events_url:string; 16 | received_events_url:string; 17 | type:string; 18 | site_admin:boolean; 19 | name:string; 20 | company:string; 21 | blog:string; 22 | location:string; 23 | email:string; 24 | hireable:boolean; 25 | bio:string; 26 | public_repos:number; 27 | public_gists:number; 28 | followers:number; 29 | following:number; 30 | created_at:string; 31 | updated_at:string; 32 | } 33 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES5", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "sourceMap": true, 9 | "removeComments": true, 10 | "declaration": true, 11 | "outDir": "../dist" 12 | }, 13 | "exclude": [ 14 | "node_modules" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /system-config.js: -------------------------------------------------------------------------------- 1 | System.config({ 2 | map: { 3 | 'rxjs': 'node_modules/rxjs', 4 | '@angular': 'node_modules/@angular', 5 | 'app': 'dist' 6 | }, 7 | packages: { 8 | 'app': { 9 | main: 'bootstrap.js', 10 | defaultExtension: 'js' 11 | }, 12 | '@angular/core': { 13 | main: 'index.js', 14 | defaultExtension: 'js' 15 | }, 16 | '@angular/compiler': { 17 | main: 'index.js', 18 | defaultExtension: 'js' 19 | }, 20 | '@angular/common': { 21 | main: 'index.js', 22 | defaultExtension: 'js' 23 | }, 24 | '@angular/http': { 25 | main: 'index.js', 26 | defaultExtension: 'js' 27 | }, 28 | '@angular/platform-browser': { 29 | main: 'index.js', 30 | defaultExtension: 'js' 31 | }, 32 | '@angular/platform-browser-dynamic': { 33 | main: 'index.js', 34 | defaultExtension: 'js' 35 | }, 36 | 'rxjs': { 37 | defaultExtension: 'js' 38 | } 39 | } 40 | }); 41 | -------------------------------------------------------------------------------- /typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-conf-testing", 3 | "version": false, 4 | "dependencies": {}, 5 | "ambientDependencies": { 6 | "es6-shim": "registry:dt/es6-shim#0.31.2+20160317120654" 7 | }, 8 | "ambientDevDependencies": { 9 | "jasmine": "registry:dt/jasmine#2.2.0+20160412134438" 10 | } 11 | } 12 | --------------------------------------------------------------------------------