diff --git a/karma.conf.js b/karma.conf.js index 98114ba2f..841933cd6 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -27,7 +27,7 @@ module.exports = function(config) { // static/lib resources 'static/lib/**/*.js', - // Application resources + // Tests 'static/js/**/*.spec.ts*', // Tests utils @@ -37,13 +37,13 @@ module.exports = function(config) { preprocessors: { 'static/lib/ngReact/react.ngReact.min.js': ['webpack'], 'static/lib/angular-moment.min.js': ['webpack'], - 'static/js/**/*.ts*': ['webpack'], + 'static/js/**/*.spec.ts*': ['webpack'], }, webpack: webpackConfig, webpackMiddleware: { stats: 'errors-only' }, - reporters: ['dots', 'coverage', 'karma-typescript'], + reporters: ['dots', 'coverage'], coverageReporter: { dir: 'coverage', type: 'html' diff --git a/package.json b/package.json index dbc5c0019..a0da58391 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "@types/angular": "1.5.16", "@types/angular-mocks": "^1.5.8", "@types/angular-route": "^1.3.3", + "@types/angular-sanitize": "^1.3.4", "@types/es6-shim": "^0.31.32", "@types/jasmine": "^2.5.41", "@types/react": "0.14.39", diff --git a/static/js/quay.module.ts b/static/js/quay.module.ts index b0fb55d44..a21188a25 100644 --- a/static/js/quay.module.ts +++ b/static/js/quay.module.ts @@ -13,6 +13,7 @@ import { LinearWorkflowSectionComponent } from './directives/ui/linear-workflow/ import { QuayConfig } from './quay-config.module'; import { QuayRun } from './quay-run.module'; import { BuildServiceImpl } from './services/build/build.service.impl'; +import { AvatarServiceImpl } from './services/avatar/avatar.service.impl'; /** @@ -35,6 +36,7 @@ import { BuildServiceImpl } from './services/build/build.service.impl'; providers: [ ViewArrayImpl, BuildServiceImpl, + AvatarServiceImpl, ], }) export class quay { diff --git a/static/js/services/avatar-service.js b/static/js/services/avatar-service.js deleted file mode 100644 index 0b1503bfe..000000000 --- a/static/js/services/avatar-service.js +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Service which provides helper methods for retrieving the avatars displayed in the app. - */ -angular.module('quay').factory('AvatarService', ['Config', '$sanitize', 'md5', - function(Config, $sanitize, md5) { - var avatarService = {}; - var cache = {}; - - avatarService.getAvatar = function(hash, opt_size, opt_notfound) { - var size = opt_size || 16; - switch (Config['AVATAR_KIND']) { - case 'local': - return '/avatar/' + hash + '?size=' + size; - break; - - case 'gravatar': - var notfound = opt_notfound || '404'; - return '//www.gravatar.com/avatar/' + hash + '?d=' + notfound + '&size=' + size; - break; - } - }; - - avatarService.computeHash = function(opt_email, opt_name) { - var email = opt_email || ''; - var name = opt_name || ''; - - var cacheKey = email + ':' + name; - if (!cacheKey) { return '-'; } - - if (cache[cacheKey]) { - return cache[cacheKey]; - } - - var hash = md5.createHash(email.toString().toLowerCase()); - switch (Config['AVATAR_KIND']) { - case 'local': - if (name) { - hash = name[0] + hash; - } else if (email) { - hash = email[0] + hash; - } - break; - } - - return cache[cacheKey] = hash; - }; - - return avatarService; -}]); \ No newline at end of file diff --git a/static/js/services/avatar/avatar.service.impl.spec.ts b/static/js/services/avatar/avatar.service.impl.spec.ts new file mode 100644 index 000000000..060fa8b7d --- /dev/null +++ b/static/js/services/avatar/avatar.service.impl.spec.ts @@ -0,0 +1,97 @@ +import { AvatarServiceImpl } from './avatar.service.impl'; + + +describe("AvatarServiceImpl", () => { + var avatarServiceImpl: AvatarServiceImpl; + var configMock: any; + var md5Mock: any; + + beforeEach(() => { + configMock = {AVATAR_KIND: 'local'}; + md5Mock = jasmine.createSpyObj('md5Mock', ['createHash']); + avatarServiceImpl = new AvatarServiceImpl(configMock, md5Mock); + }); + + describe("getAvatar", () => { + var hash: string; + + beforeEach(() => { + hash = "a1b2c3d4e5f6"; + }); + + it("returns a local avatar URL if given config has avatar kind set to local", () => { + var avatarURL: string = avatarServiceImpl.getAvatar(hash); + + expect(avatarURL).toEqual(`/avatar/${hash}?size=16`); + }); + + it("returns a Gravatar URL if given config has avatar kind set to Gravatar", () => { + configMock['AVATAR_KIND'] = 'gravatar'; + var avatarURL: string = avatarServiceImpl.getAvatar(hash); + + expect(avatarURL).toEqual(`//www.gravatar.com/avatar/${hash}?d=404&size=16`); + }); + + it("uses 16 as default size query parameter if not provided", () => { + var size: number = 16; + var avatarURL: string = avatarServiceImpl.getAvatar(hash); + + expect(avatarURL).toEqual(`/avatar/${hash}?size=${size}`); + }); + + it("uses 404 as default not found query parameter for Gravatar URL if not provided", () => { + configMock['AVATAR_KIND'] = 'gravatar'; + var notFound: string = '404'; + var avatarURL: string = avatarServiceImpl.getAvatar(hash); + + expect(avatarURL).toEqual(`//www.gravatar.com/avatar/${hash}?d=${notFound}&size=16`); + }); + }); + + describe("computeHash", () => { + var email: string; + var name: string; + var expectedHash: string; + + beforeEach(() => { + email = "some_example@gmail.com"; + name = "example"; + expectedHash = "a1b2c3d4e5f6"; + md5Mock.createHash = jasmine.createSpy('createHashSpy').and.returnValue(expectedHash); + }); + + it("returns hash from cache if it exists", () => { + // Call once to set the cache + avatarServiceImpl.computeHash(email, name); + md5Mock.createHash.calls.reset(); + avatarServiceImpl.computeHash(email, name); + + expect(md5Mock.createHash).not.toHaveBeenCalled(); + }); + + it("calls MD5 service to create hash using given email if cache is not set", () => { + avatarServiceImpl.computeHash(email, name); + + expect(md5Mock.createHash.calls.argsFor(0)[0]).toEqual(email.toString().toLowerCase()); + }); + + it("adds first character of given name to hash if config has avatar kind set to local", () => { + var hash: string = avatarServiceImpl.computeHash(email, name); + + expect(hash[0]).toEqual(name[0]); + }); + + it("adds first character of given email to hash if config has avatar kind set to local and not given name", () => { + var hash: string = avatarServiceImpl.computeHash(email); + + expect(hash[0]).toEqual(email[0]); + }); + + it("adds nothing to hash if config avatar kind is not set to local", () => { + configMock['AVATAR_KIND'] = 'gravatar'; + var hash: string = avatarServiceImpl.computeHash(email); + + expect(hash).toEqual(expectedHash); + }); + }); +}); \ No newline at end of file diff --git a/static/js/services/avatar/avatar.service.impl.ts b/static/js/services/avatar/avatar.service.impl.ts new file mode 100644 index 000000000..673cec473 --- /dev/null +++ b/static/js/services/avatar/avatar.service.impl.ts @@ -0,0 +1,49 @@ +import { AvatarService } from './avatar.service'; +import { Injectable } from 'angular-ts-decorators'; + + +@Injectable(AvatarService.name) +export class AvatarServiceImpl implements AvatarService { + + private cache: {[cacheKey: string]: string} = {}; + + constructor(private Config: any, private md5: any) { + + } + + public getAvatar(hash: string, size: number = 16, notFound: string = '404'): string { + var avatarURL: string; + switch (this.Config['AVATAR_KIND']) { + case 'local': + avatarURL = `/avatar/${hash}?size=${size}`; + break; + + case 'gravatar': + avatarURL = `//www.gravatar.com/avatar/${hash}?d=${notFound}&size=${size}`; + break; + } + + return avatarURL; + } + + public computeHash(email: string = '', name: string = ''): string { + const cacheKey: string = email + ':' + name; + + if (this.cache[cacheKey]) { + return this.cache[cacheKey]; + } + + var hash: string = this.md5.createHash(email.toString().toLowerCase()); + switch (this.Config['AVATAR_KIND']) { + case 'local': + if (name) { + hash = name[0] + hash; + } else if (email) { + hash = email[0] + hash; + } + break; + } + + return this.cache[cacheKey] = hash; + } +} \ No newline at end of file diff --git a/static/js/services/avatar/avatar.service.ts b/static/js/services/avatar/avatar.service.ts new file mode 100644 index 000000000..ec817e9fd --- /dev/null +++ b/static/js/services/avatar/avatar.service.ts @@ -0,0 +1,22 @@ +/** + * Service which provides helper methods for retrieving the avatars displayed in the app. + */ +export abstract class AvatarService { + + /** + * Retrieve URL for avatar image with given hash. + * @param hash Avatar image hash. + * @param size Avatar image size. + * @param notFound URL parameter if avatar image is not found. + * @return avatarURL The URL for the avatar image. + */ + public abstract getAvatar(hash: string, size?: number, notFound?: string): string; + + /** + * Compute the avatar image hash. + * @param email Email for avatar user. + * @param name Username for avatar user. + * @return hash The hash for the avatar image. + */ + public abstract computeHash(email?: string, name?: string): string; +} \ No newline at end of file diff --git a/static/js/services/page/page.service.impl.spec.ts b/static/js/services/page/page.service.impl.spec.ts index 61f9e51f1..efce5c752 100644 --- a/static/js/services/page/page.service.impl.spec.ts +++ b/static/js/services/page/page.service.impl.spec.ts @@ -9,14 +9,14 @@ describe("Service: PageServiceImpl", () => { }); describe("create", () => { - + // TODO }); describe("get", () => { - + // TODO }); describe("$get", () => { - + // TODO }); }); \ No newline at end of file