diff --git a/frontend/src/services/html-helpers/html-helpers.js b/frontend/src/services/html-helpers/html-helpers.js index 7956a569c..92d3168b5 100644 --- a/frontend/src/services/html-helpers/html-helpers.js +++ b/frontend/src/services/html-helpers/html-helpers.js @@ -11,7 +11,7 @@ export class HtmlHelpers { .then( (responseText) => { const docFrag = document.createRange().createContextualFragment(responseText); - let idPrefix; + let idPrefix = ''; if (!options.keepIds) { idPrefix = this._getIdPrefix(); this._prefixIds(docFrag, idPrefix); diff --git a/frontend/src/services/html-helpers/html-helpers.spec.js b/frontend/src/services/html-helpers/html-helpers.spec.js index cd13b5cac..c77015b26 100644 --- a/frontend/src/services/html-helpers/html-helpers.spec.js +++ b/frontend/src/services/html-helpers/html-helpers.spec.js @@ -2,6 +2,7 @@ import { HtmlHelpers } from "./html-helpers"; describe('HtmlHelpers', () => { let htmlHelpers; + beforeEach(() => { htmlHelpers = new HtmlHelpers(); }); @@ -10,13 +11,17 @@ describe('HtmlHelpers', () => { expect(htmlHelpers).toBeTruthy(); }); - describe('parseResponse', () => { - it('should return a promise with idPrefix and element', (done) => { - const response = { - text: () => Promise.resolve('
Test
'), - }; + describe('parseResponse()', () => { + let fakeHttpResponse; - htmlHelpers.parseResponse(response).then(result => { + beforeEach(() => { + fakeHttpResponse = { + text: () => Promise.resolve('
Test
'), + }; + }); + + it('should return a promise with idPrefix and element', (done) => { + htmlHelpers.parseResponse(fakeHttpResponse).then(result => { expect(result.idPrefix).toBeDefined(); expect(result.element).toBeDefined(); expect(result.element.textContent).toMatch('Test'); @@ -25,34 +30,25 @@ describe('HtmlHelpers', () => { }); it('should nudge IDs', (done) => { - const response = { - text: () => Promise.resolve('
Test
'), - }; - - htmlHelpers.parseResponse(response).then(result => { + htmlHelpers.parseResponse(fakeHttpResponse).then(result => { expect(result.idPrefix).toBeDefined(); expect(result.element).toBeDefined(); - const origElement = result.element.querySelector('#test-div'); - expect(origElement).toBeFalsy(); - const nudgedElement = result.element.querySelector('#' + result.idPrefix + 'test-div'); - expect(nudgedElement).toBeTruthy(); + const elementWithOrigId = result.element.querySelector('#test-div'); + expect(elementWithOrigId).toBeFalsy(); + const elementWithNudgedId = result.element.querySelector('#' + result.idPrefix + 'test-div'); + expect(elementWithNudgedId).toBeTruthy(); done(); }); }); it('should not nudge IDs with option "keepIds"', (done) => { - const response = { - text: () => Promise.resolve('
Test
'), - }; const options = { keepIds: true }; - htmlHelpers.parseResponse(response, options).then(result => { - expect(result.idPrefix).not.toBeDefined(); + htmlHelpers.parseResponse(fakeHttpResponse, options).then(result => { + expect(result.idPrefix).toBe(''); expect(result.element).toBeDefined(); - const origElement = result.element.querySelector('#test-div'); - expect(origElement).toBeTruthy(); - const nudgedElement = result.element.querySelector('#' + result.idPrefix + 'test-div'); - expect(nudgedElement).toBeFalsy(); + const elementWithOrigId = result.element.querySelector('#test-div'); + expect(elementWithOrigId).toBeTruthy(); done(); }); }); diff --git a/frontend/src/services/http-client/http-client.spec.js b/frontend/src/services/http-client/http-client.spec.js new file mode 100644 index 000000000..e61e248f0 --- /dev/null +++ b/frontend/src/services/http-client/http-client.spec.js @@ -0,0 +1,116 @@ +import { HttpClient } from "./http-client"; + +const TEST_URL = 'http://example.com'; +const FAKE_RESPONSE = { + data: 'data', +}; + +describe('HttpClient', () => { + let httpClient; + + beforeEach(() => { + httpClient = new HttpClient(); + + // setup and spy on fake fetch API + spyOn(window, 'fetch').and.returnValue(Promise.resolve(FAKE_RESPONSE)); + }); + + it('should create', () => { + expect(httpClient).toBeTruthy(); + }); + + describe('get()', () => { + let params; + + beforeEach(() => { + params = { + url: TEST_URL, + }; + }); + + it('should GET the given url', () => { + httpClient.get(params); + expect(window.fetch).toHaveBeenCalledWith(params.url, jasmine.objectContaining({ method: 'GET' })); + }); + + it('should return a promise', (done) => { + const result = httpClient.get(params); + result.then((response) => { + expect(response).toEqual(FAKE_RESPONSE); + done(); + }); + }); + }); + + describe('post()', () => { + let params; + + beforeEach(() => { + params = { + url: TEST_URL, + }; + }); + + it('should POST the given url', () => { + httpClient.post(params); + expect(window.fetch).toHaveBeenCalledWith(params.url, jasmine.objectContaining({ method: 'POST' })); + }); + + it('should return a promise', (done) => { + const result = httpClient.post(params); + result.then((response) => { + expect(response).toEqual(FAKE_RESPONSE); + done(); + }); + }); + }); + + describe('Response Interceptors', () => { + it('can be added', () => { + const interceptor = () => {}; + expect(httpClient._responseInterceptors.length).toBe(0); + httpClient.addResponseInterceptor(interceptor); + expect(httpClient._responseInterceptors.length).toBe(1); + httpClient.addResponseInterceptor(interceptor); + expect(httpClient._responseInterceptors.length).toBe(2); + }); + + describe('get called', () => { + let intercepted1; + let intercepted2; + const interceptors = { + interceptor1: () => intercepted1 = true, + interceptor2: () => intercepted2 = true, + }; + + beforeEach(() => { + intercepted1 = false; + intercepted2 = false; + spyOn(interceptors, 'interceptor1').and.callThrough(); + spyOn(interceptors, 'interceptor2').and.callThrough(); + httpClient.addResponseInterceptor(interceptors.interceptor1); + httpClient.addResponseInterceptor(interceptors.interceptor2); + }); + + it('for GET requests', (done) => { + httpClient.get({ url: TEST_URL }).then(() => { + expect(intercepted1).toBeTruthy(); + expect(intercepted2).toBeTruthy(); + expect(interceptors.interceptor1).toHaveBeenCalledWith(FAKE_RESPONSE, jasmine.any(Object)); + expect(interceptors.interceptor2).toHaveBeenCalledWith(FAKE_RESPONSE, jasmine.any(Object)); + done(); + }); + }); + + it('for POST requests', (done) => { + httpClient.post({ url: TEST_URL }).then(() => { + expect(intercepted1).toBeTruthy(); + expect(intercepted2).toBeTruthy(); + expect(interceptors.interceptor1).toHaveBeenCalledWith(FAKE_RESPONSE, jasmine.any(Object)); + expect(interceptors.interceptor2).toHaveBeenCalledWith(FAKE_RESPONSE, jasmine.any(Object)); + done(); + }); + }); + }); + }); +}); diff --git a/frontend/src/services/i18n/i18n.spec.js b/frontend/src/services/i18n/i18n.spec.js new file mode 100644 index 000000000..76e5348c0 --- /dev/null +++ b/frontend/src/services/i18n/i18n.spec.js @@ -0,0 +1,51 @@ +import { I18n } from "./i18n"; + +describe('I18n', () => { + let i18n; + + beforeEach(() => { + i18n = new I18n(); + }); + + // helper function + function expectTranslation(id, value) { + expect(i18n.translations[id]).toMatch(value); + } + + it('should create', () => { + expect(i18n).toBeTruthy(); + }); + + describe('add()', () => { + it('should add the translation', () => { + i18n.add('id1', 'translated-id1'); + expectTranslation('id1', 'translated-id1'); + }); + }); + + describe('addMany()', () => { + it('should add many translations', () => { + i18n.addMany({ + id1: 'translated-id1', + id2: 'translated-id2', + id3: 'translated-id3', + }); + expectTranslation('id1', 'translated-id1'); + expectTranslation('id2', 'translated-id2'); + expectTranslation('id3', 'translated-id3'); + }); + }); + + describe('get()', () => { + it('should return stored translations', () => { + i18n.translations.id1 = 'something'; + expect(i18n.get('id1')).toMatch('something'); + }); + + it('should throw error if translation is missing', () => { + expect(() => { + i18n.get('id1'); + }).toThrow(); + }); + }); +}); diff --git a/frontend/src/services/util-registry/util-registry.js b/frontend/src/services/util-registry/util-registry.js index 001bb1e6b..18b656b8c 100644 --- a/frontend/src/services/util-registry/util-registry.js +++ b/frontend/src/services/util-registry/util-registry.js @@ -46,10 +46,10 @@ export class UtilRegistry { } setApp(appInstance) { - this.appInstance = appInstance; + this._appInstance = appInstance; } - setupAll = (scope) => { + setupAll(scope) { if (DEBUG_MODE > 1) { console.info('registered js utilities:'); console.table(this._registeredUtils); @@ -70,7 +70,7 @@ export class UtilRegistry { let utilInstance = null; try { - utilInstance = util.setup(element, this.appInstance); + utilInstance = util.setup(element, this._appInstance); } catch(err) { if (DEBUG_MODE > 0) { console.warn('Error while trying to initialize a utility!', { util , element, err }); diff --git a/frontend/src/services/util-registry/util-registry.spec.js b/frontend/src/services/util-registry/util-registry.spec.js new file mode 100644 index 000000000..2e4afedf2 --- /dev/null +++ b/frontend/src/services/util-registry/util-registry.spec.js @@ -0,0 +1,126 @@ +import { UtilRegistry } from "./util-registry"; + +const TEST_UTILS = [{ + name: 'util1', + selector: '#some-id', + setup: () => {}, +}, { + name: 'util2', + selector: '[uw-util]', + setup: () => {}, +}]; + +describe('UtilRegistry', () => { + let utilRegistry; + + beforeEach(() => { + utilRegistry = new UtilRegistry(); + }); + + it('should create', () => { + expect(utilRegistry).toBeTruthy(); + }); + + describe('register()', () => { + it('should allow to add utilities', () => { + utilRegistry.register(TEST_UTILS[0]); + + const foundUtil = utilRegistry.find(TEST_UTILS[0].name); + expect(foundUtil).toEqual(TEST_UTILS[0]); + }); + }); + + describe('deregister()', () => { + it('should remove util', () => { + // register util + utilRegistry.register(TEST_UTILS[0]); + let foundUtil = utilRegistry.find('util1'); + expect(foundUtil).toBeTruthy(); + + // deregister util + utilRegistry.deregister(TEST_UTILS[0].name); + foundUtil = utilRegistry.find('util1'); + expect(foundUtil).toBeFalsy(); + }); + + it('should destroy util instances if requested', () => { + pending('TBD'); + }); + }); + + describe('setup()', () => { + it('should catch errors thrown by the utility', () => { + spyOn(TEST_UTILS[0], 'setup').and.throwError('some error'); + expect(() => { + utilRegistry.setup(TEST_UTILS[0]); + }).not.toThrow(); + }); + + it('should pass the app instance', () => { + const scope = document.createElement('div'); + const utilElement = document.createElement('div'); + utilElement.id = 'some-id'; + scope.appendChild(utilElement); + const fakeApp = { fn: () => {} }; + utilRegistry.setApp(fakeApp); + spyOn(TEST_UTILS[0], 'setup'); + utilRegistry.setup(TEST_UTILS[0], scope); + expect(TEST_UTILS[0].setup).toHaveBeenCalledWith(utilElement, fakeApp); + }); + + describe('given no scope', () => { + it('should use fallback scope', () => { + spyOn(TEST_UTILS[0], 'setup'); + utilRegistry.setup(TEST_UTILS[0]); + expect(TEST_UTILS[0].setup).not.toHaveBeenCalled(); + }); + }); + + describe('given a scope', () => { + let scope; + let utilElement1; + let utilElement2; + + beforeEach(() => { + scope = document.createElement('div'); + utilElement1 = document.createElement('div'); + utilElement2 = document.createElement('div'); + utilElement1.setAttribute('uw-util', ''); + utilElement2.setAttribute('uw-util', ''); + scope.appendChild(utilElement1); + scope.appendChild(utilElement2); + }); + + it('should call the utilities\' setup function for each matching element', () => { + spyOn(TEST_UTILS[1], 'setup'); + utilRegistry.setup(TEST_UTILS[1], scope); + // 2 matching elements in scope + expect(TEST_UTILS[1].setup.calls.count()).toBe(2); + expect(TEST_UTILS[1].setup.calls.argsFor(0)).toEqual([utilElement1, undefined]); + expect(TEST_UTILS[1].setup.calls.argsFor(1)).toEqual([utilElement2, undefined]); + }); + }); + }); + + describe('setupAll()', () => { + it('should setup all the utilities', () => { + spyOn(utilRegistry, 'setup'); + utilRegistry.register(TEST_UTILS[0]); + utilRegistry.register(TEST_UTILS[1]); + utilRegistry.setupAll(); + + expect(utilRegistry.setup.calls.count()).toBe(2); + expect(utilRegistry.setup.calls.argsFor(0)).toEqual([TEST_UTILS[0], undefined]); + expect(utilRegistry.setup.calls.argsFor(1)).toEqual([TEST_UTILS[1], undefined]); + }); + + it('should pass the given scope', () => { + spyOn(utilRegistry, 'setup'); + utilRegistry.register(TEST_UTILS[0]); + const scope = document.createElement('div'); + utilRegistry.setupAll(scope); + + expect(utilRegistry.setup).toHaveBeenCalledWith(TEST_UTILS[0], scope); + }); + }); +}); diff --git a/package.json b/package.json index 9b4914798..494be6bb6 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "yesod:test": "./test.sh", "frontend:lint": "eslint frontend/src", "frontend:test": "karma start --conf karma.conf.js", + "frontend:test:watch": "karma start --conf karma.conf.js --single-run false", "frontend:build": "webpack", "frontend:build:watch": "webpack --watch" },