diff options
Diffstat (limited to 'spec/frontend')
-rw-r--r-- | spec/frontend/lib/utils/accessor_spec.js | 85 | ||||
-rw-r--r-- | spec/frontend/lib/utils/dom_utils_spec.js | 115 | ||||
-rw-r--r-- | spec/frontend/lib/utils/file_upload_spec.js | 64 | ||||
-rw-r--r-- | spec/frontend/lib/utils/highlight_spec.js | 43 | ||||
-rw-r--r-- | spec/frontend/lib/utils/icon_utils_spec.js | 60 | ||||
-rw-r--r-- | spec/frontend/lib/utils/text_markdown_spec.js | 308 | ||||
-rw-r--r-- | spec/frontend/lib/utils/users_cache_spec.js | 265 |
7 files changed, 940 insertions, 0 deletions
diff --git a/spec/frontend/lib/utils/accessor_spec.js b/spec/frontend/lib/utils/accessor_spec.js new file mode 100644 index 00000000000..752a88296e6 --- /dev/null +++ b/spec/frontend/lib/utils/accessor_spec.js @@ -0,0 +1,85 @@ +import { useLocalStorageSpy } from 'helpers/local_storage_helper'; +import AccessorUtilities from '~/lib/utils/accessor'; + +describe('AccessorUtilities', () => { + useLocalStorageSpy(); + + const testError = new Error('test error'); + + describe('isPropertyAccessSafe', () => { + let base; + + it('should return `true` if access is safe', () => { + base = { + testProp: 'testProp', + }; + expect(AccessorUtilities.isPropertyAccessSafe(base, 'testProp')).toBe(true); + }); + + it('should return `false` if access throws an error', () => { + base = { + get testProp() { + throw testError; + }, + }; + + expect(AccessorUtilities.isPropertyAccessSafe(base, 'testProp')).toBe(false); + }); + + it('should return `false` if property is undefined', () => { + base = {}; + + expect(AccessorUtilities.isPropertyAccessSafe(base, 'testProp')).toBe(false); + }); + }); + + describe('isFunctionCallSafe', () => { + const base = {}; + + it('should return `true` if calling is safe', () => { + base.func = () => {}; + + expect(AccessorUtilities.isFunctionCallSafe(base, 'func')).toBe(true); + }); + + it('should return `false` if calling throws an error', () => { + base.func = () => { + throw new Error('test error'); + }; + + expect(AccessorUtilities.isFunctionCallSafe(base, 'func')).toBe(false); + }); + + it('should return `false` if function is undefined', () => { + base.func = undefined; + + expect(AccessorUtilities.isFunctionCallSafe(base, 'func')).toBe(false); + }); + }); + + describe('isLocalStorageAccessSafe', () => { + it('should return `true` if access is safe', () => { + expect(AccessorUtilities.isLocalStorageAccessSafe()).toBe(true); + }); + + it('should return `false` if access to .setItem isnt safe', () => { + window.localStorage.setItem.mockImplementation(() => { + throw testError; + }); + + expect(AccessorUtilities.isLocalStorageAccessSafe()).toBe(false); + }); + + it('should set a test item if access is safe', () => { + AccessorUtilities.isLocalStorageAccessSafe(); + + expect(window.localStorage.setItem).toHaveBeenCalledWith('isLocalStorageAccessSafe', 'true'); + }); + + it('should remove the test item if access is safe', () => { + AccessorUtilities.isLocalStorageAccessSafe(); + + expect(window.localStorage.removeItem).toHaveBeenCalledWith('isLocalStorageAccessSafe'); + }); + }); +}); diff --git a/spec/frontend/lib/utils/dom_utils_spec.js b/spec/frontend/lib/utils/dom_utils_spec.js new file mode 100644 index 00000000000..10b4a10a8ff --- /dev/null +++ b/spec/frontend/lib/utils/dom_utils_spec.js @@ -0,0 +1,115 @@ +import { addClassIfElementExists, canScrollUp, canScrollDown } from '~/lib/utils/dom_utils'; + +const TEST_MARGIN = 5; + +describe('DOM Utils', () => { + describe('addClassIfElementExists', () => { + const className = 'biology'; + const fixture = ` + <div class="parent"> + <div class="child"></div> + </div> + `; + + let parentElement; + + beforeEach(() => { + setFixtures(fixture); + parentElement = document.querySelector('.parent'); + }); + + it('adds class if element exists', () => { + const childElement = parentElement.querySelector('.child'); + + expect(childElement).not.toBe(null); + + addClassIfElementExists(childElement, className); + + expect(childElement.classList).toContainEqual(className); + }); + + it('does not throw if element does not exist', () => { + const childElement = parentElement.querySelector('.other-child'); + + expect(childElement).toBe(null); + + addClassIfElementExists(childElement, className); + }); + }); + + describe('canScrollUp', () => { + [1, 100].forEach(scrollTop => { + it(`is true if scrollTop is > 0 (${scrollTop})`, () => { + expect( + canScrollUp({ + scrollTop, + }), + ).toBe(true); + }); + }); + + [0, -10].forEach(scrollTop => { + it(`is false if scrollTop is <= 0 (${scrollTop})`, () => { + expect( + canScrollUp({ + scrollTop, + }), + ).toBe(false); + }); + }); + + it('is true if scrollTop is > margin', () => { + expect( + canScrollUp( + { + scrollTop: TEST_MARGIN + 1, + }, + TEST_MARGIN, + ), + ).toBe(true); + }); + + it('is false if scrollTop is <= margin', () => { + expect( + canScrollUp( + { + scrollTop: TEST_MARGIN, + }, + TEST_MARGIN, + ), + ).toBe(false); + }); + }); + + describe('canScrollDown', () => { + let element; + + beforeEach(() => { + element = { + scrollTop: 7, + offsetHeight: 22, + scrollHeight: 30, + }; + }); + + it('is true if element can be scrolled down', () => { + expect(canScrollDown(element)).toBe(true); + }); + + it('is false if element cannot be scrolled down', () => { + element.scrollHeight -= 1; + + expect(canScrollDown(element)).toBe(false); + }); + + it('is true if element can be scrolled down, with margin given', () => { + element.scrollHeight += TEST_MARGIN; + + expect(canScrollDown(element, TEST_MARGIN)).toBe(true); + }); + + it('is false if element cannot be scrolled down, with margin given', () => { + expect(canScrollDown(element, TEST_MARGIN)).toBe(false); + }); + }); +}); diff --git a/spec/frontend/lib/utils/file_upload_spec.js b/spec/frontend/lib/utils/file_upload_spec.js new file mode 100644 index 00000000000..1255d6fc14f --- /dev/null +++ b/spec/frontend/lib/utils/file_upload_spec.js @@ -0,0 +1,64 @@ +import fileUpload from '~/lib/utils/file_upload'; + +describe('File upload', () => { + beforeEach(() => { + setFixtures(` + <form> + <button class="js-button" type="button">Click me!</button> + <input type="text" class="js-input" /> + <span class="js-filename"></span> + </form> + `); + }); + + describe('when there is a matching button and input', () => { + beforeEach(() => { + fileUpload('.js-button', '.js-input'); + }); + + it('clicks file input after clicking button', () => { + const btn = document.querySelector('.js-button'); + const input = document.querySelector('.js-input'); + + jest.spyOn(input, 'click').mockReturnValue(); + + btn.click(); + + expect(input.click).toHaveBeenCalled(); + }); + + it('updates file name text', () => { + const input = document.querySelector('.js-input'); + + input.value = 'path/to/file/index.js'; + + input.dispatchEvent(new CustomEvent('change')); + + expect(document.querySelector('.js-filename').textContent).toEqual('index.js'); + }); + }); + + it('fails gracefully when there is no matching button', () => { + const input = document.querySelector('.js-input'); + const btn = document.querySelector('.js-button'); + fileUpload('.js-not-button', '.js-input'); + + jest.spyOn(input, 'click').mockReturnValue(); + + btn.click(); + + expect(input.click).not.toHaveBeenCalled(); + }); + + it('fails gracefully when there is no matching input', () => { + const input = document.querySelector('.js-input'); + const btn = document.querySelector('.js-button'); + fileUpload('.js-button', '.js-not-input'); + + jest.spyOn(input, 'click').mockReturnValue(); + + btn.click(); + + expect(input.click).not.toHaveBeenCalled(); + }); +}); diff --git a/spec/frontend/lib/utils/highlight_spec.js b/spec/frontend/lib/utils/highlight_spec.js new file mode 100644 index 00000000000..638bbf65ae9 --- /dev/null +++ b/spec/frontend/lib/utils/highlight_spec.js @@ -0,0 +1,43 @@ +import highlight from '~/lib/utils/highlight'; + +describe('highlight', () => { + it(`should appropriately surround substring matches`, () => { + const expected = 'g<b>i</b><b>t</b>lab'; + + expect(highlight('gitlab', 'it')).toBe(expected); + }); + + it(`should return an empty string in the case of invalid inputs`, () => { + [null, undefined].forEach(input => { + expect(highlight(input, 'match')).toBe(''); + }); + }); + + it(`should return the original value if match is null, undefined, or ''`, () => { + [null, undefined].forEach(match => { + expect(highlight('gitlab', match)).toBe('gitlab'); + }); + }); + + it(`should highlight matches in non-string inputs`, () => { + const expected = '123<b>4</b><b>5</b>6'; + + expect(highlight(123456, 45)).toBe(expected); + }); + + it(`should sanitize the input string before highlighting matches`, () => { + const expected = 'hello <b>w</b>orld'; + + expect(highlight('hello <b>world</b>', 'w')).toBe(expected); + }); + + it(`should not highlight anything if no matches are found`, () => { + expect(highlight('gitlab', 'hello')).toBe('gitlab'); + }); + + it(`should allow wrapping elements to be customized`, () => { + const expected = '1<hello>2</hello>3'; + + expect(highlight('123', '2', '<hello>', '</hello>')).toBe(expected); + }); +}); diff --git a/spec/frontend/lib/utils/icon_utils_spec.js b/spec/frontend/lib/utils/icon_utils_spec.js new file mode 100644 index 00000000000..816d634ad15 --- /dev/null +++ b/spec/frontend/lib/utils/icon_utils_spec.js @@ -0,0 +1,60 @@ +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import * as iconUtils from '~/lib/utils/icon_utils'; + +describe('Icon utils', () => { + describe('getSvgIconPathContent', () => { + let spriteIcons; + + beforeAll(() => { + spriteIcons = gon.sprite_icons; + gon.sprite_icons = 'mockSpriteIconsEndpoint'; + }); + + afterAll(() => { + gon.sprite_icons = spriteIcons; + }); + + let axiosMock; + let mockEndpoint; + const mockName = 'mockIconName'; + const mockPath = 'mockPath'; + const getIcon = () => iconUtils.getSvgIconPathContent(mockName); + + beforeEach(() => { + axiosMock = new MockAdapter(axios); + mockEndpoint = axiosMock.onGet(gon.sprite_icons); + }); + + afterEach(() => { + axiosMock.restore(); + }); + + it('extracts svg icon path content from sprite icons', () => { + mockEndpoint.replyOnce( + 200, + `<svg><symbol id="${mockName}"><path d="${mockPath}"/></symbol></svg>`, + ); + + return getIcon().then(path => { + expect(path).toBe(mockPath); + }); + }); + + it('returns null if icon path content does not exist', () => { + mockEndpoint.replyOnce(200, ``); + + return getIcon().then(path => { + expect(path).toBe(null); + }); + }); + + it('returns null if an http error occurs', () => { + mockEndpoint.replyOnce(500); + + return getIcon().then(path => { + expect(path).toBe(null); + }); + }); + }); +}); diff --git a/spec/frontend/lib/utils/text_markdown_spec.js b/spec/frontend/lib/utils/text_markdown_spec.js new file mode 100644 index 00000000000..ba3e4020e66 --- /dev/null +++ b/spec/frontend/lib/utils/text_markdown_spec.js @@ -0,0 +1,308 @@ +import { insertMarkdownText } from '~/lib/utils/text_markdown'; + +describe('init markdown', () => { + let textArea; + + beforeAll(() => { + textArea = document.createElement('textarea'); + document.querySelector('body').appendChild(textArea); + textArea.focus(); + }); + + afterAll(() => { + textArea.parentNode.removeChild(textArea); + }); + + describe('textArea', () => { + describe('without selection', () => { + it('inserts the tag on an empty line', () => { + const initialValue = ''; + + textArea.value = initialValue; + textArea.selectionStart = 0; + textArea.selectionEnd = 0; + + insertMarkdownText({ + textArea, + text: textArea.value, + tag: '*', + blockTag: null, + selected: '', + wrap: false, + }); + + expect(textArea.value).toEqual(`${initialValue}* `); + }); + + it('inserts the tag on a new line if the current one is not empty', () => { + const initialValue = 'some text'; + + textArea.value = initialValue; + textArea.setSelectionRange(initialValue.length, initialValue.length); + + insertMarkdownText({ + textArea, + text: textArea.value, + tag: '*', + blockTag: null, + selected: '', + wrap: false, + }); + + expect(textArea.value).toEqual(`${initialValue}\n* `); + }); + + it('inserts the tag on the same line if the current line only contains spaces', () => { + const initialValue = ' '; + + textArea.value = initialValue; + textArea.setSelectionRange(initialValue.length, initialValue.length); + + insertMarkdownText({ + textArea, + text: textArea.value, + tag: '*', + blockTag: null, + selected: '', + wrap: false, + }); + + expect(textArea.value).toEqual(`${initialValue}* `); + }); + + it('inserts the tag on the same line if the current line only contains tabs', () => { + const initialValue = '\t\t\t'; + + textArea.value = initialValue; + textArea.setSelectionRange(initialValue.length, initialValue.length); + + insertMarkdownText({ + textArea, + text: textArea.value, + tag: '*', + blockTag: null, + selected: '', + wrap: false, + }); + + expect(textArea.value).toEqual(`${initialValue}* `); + }); + + it('places the cursor inside the tags', () => { + const start = 'lorem '; + const end = ' ipsum'; + const tag = '*'; + + textArea.value = `${start}${end}`; + textArea.setSelectionRange(start.length, start.length); + + insertMarkdownText({ + textArea, + text: textArea.value, + tag, + blockTag: null, + selected: '', + wrap: true, + }); + + expect(textArea.value).toEqual(`${start}**${end}`); + + // cursor placement should be between tags + expect(textArea.selectionStart).toBe(start.length + tag.length); + }); + }); + + describe('with selection', () => { + const text = 'initial selected value'; + const selected = 'selected'; + beforeEach(() => { + textArea.value = text; + const selectedIndex = text.indexOf(selected); + textArea.setSelectionRange(selectedIndex, selectedIndex + selected.length); + }); + + it('applies the tag to the selected value', () => { + const selectedIndex = text.indexOf(selected); + const tag = '*'; + + insertMarkdownText({ + textArea, + text: textArea.value, + tag, + blockTag: null, + selected, + wrap: true, + }); + + expect(textArea.value).toEqual(text.replace(selected, `*${selected}*`)); + + // cursor placement should be after selection + 2 tag lengths + expect(textArea.selectionStart).toBe(selectedIndex + selected.length + 2 * tag.length); + }); + + it('replaces the placeholder in the tag', () => { + insertMarkdownText({ + textArea, + text: textArea.value, + tag: '[{text}](url)', + blockTag: null, + selected, + wrap: false, + }); + + expect(textArea.value).toEqual(text.replace(selected, `[${selected}](url)`)); + }); + + describe('and text to be selected', () => { + const tag = '[{text}](url)'; + const select = 'url'; + + it('selects the text', () => { + insertMarkdownText({ + textArea, + text: textArea.value, + tag, + blockTag: null, + selected, + wrap: false, + select, + }); + + const expectedText = text.replace(selected, `[${selected}](url)`); + + expect(textArea.value).toEqual(expectedText); + expect(textArea.selectionStart).toEqual(expectedText.indexOf(select)); + expect(textArea.selectionEnd).toEqual(expectedText.indexOf(select) + select.length); + }); + + it('selects the right text when multiple tags are present', () => { + const initialValue = `${tag} ${tag} ${selected}`; + textArea.value = initialValue; + const selectedIndex = initialValue.indexOf(selected); + textArea.setSelectionRange(selectedIndex, selectedIndex + selected.length); + insertMarkdownText({ + textArea, + text: textArea.value, + tag, + blockTag: null, + selected, + wrap: false, + select, + }); + + const expectedText = initialValue.replace(selected, `[${selected}](url)`); + + expect(textArea.value).toEqual(expectedText); + expect(textArea.selectionStart).toEqual(expectedText.lastIndexOf(select)); + expect(textArea.selectionEnd).toEqual(expectedText.lastIndexOf(select) + select.length); + }); + + it('should support selected urls', () => { + const expectedUrl = 'http://www.gitlab.com'; + const expectedSelectionText = 'text'; + const expectedText = `text [${expectedSelectionText}](${expectedUrl}) text`; + const initialValue = `text ${expectedUrl} text`; + + textArea.value = initialValue; + const selectedIndex = initialValue.indexOf(expectedUrl); + textArea.setSelectionRange(selectedIndex, selectedIndex + expectedUrl.length); + + insertMarkdownText({ + textArea, + text: textArea.value, + tag, + blockTag: null, + selected: expectedUrl, + wrap: false, + select, + }); + + expect(textArea.value).toEqual(expectedText); + expect(textArea.selectionStart).toEqual(expectedText.indexOf(expectedSelectionText, 1)); + expect(textArea.selectionEnd).toEqual( + expectedText.indexOf(expectedSelectionText, 1) + expectedSelectionText.length, + ); + }); + }); + }); + }); + + describe('Ace Editor', () => { + let editor; + + beforeEach(() => { + editor = { + getSelectionRange: () => ({ + start: 0, + end: 0, + }), + getValue: () => 'this is text \n in two lines', + insert: () => {}, + navigateLeft: () => {}, + }; + }); + + it('uses ace editor insert text when editor is passed in', () => { + jest.spyOn(editor, 'insert').mockReturnValue(); + + insertMarkdownText({ + text: editor.getValue, + tag: '*', + blockTag: null, + selected: '', + wrap: false, + editor, + }); + + expect(editor.insert).toHaveBeenCalled(); + }); + + it('adds block tags on line above and below selection', () => { + jest.spyOn(editor, 'insert').mockReturnValue(); + + const selected = 'this text \n is multiple \n lines'; + const text = `before \n ${selected} \n after`; + + insertMarkdownText({ + text, + tag: '', + blockTag: '***', + selected, + wrap: true, + editor, + }); + + expect(editor.insert).toHaveBeenCalledWith(`***\n${selected}\n***`); + }); + + it('uses ace editor to navigate back tag length when nothing is selected', () => { + jest.spyOn(editor, 'navigateLeft').mockReturnValue(); + + insertMarkdownText({ + text: editor.getValue, + tag: '*', + blockTag: null, + selected: '', + wrap: true, + editor, + }); + + expect(editor.navigateLeft).toHaveBeenCalledWith(1); + }); + + it('ace editor does not navigate back when there is selected text', () => { + jest.spyOn(editor, 'navigateLeft').mockReturnValue(); + + insertMarkdownText({ + text: editor.getValue, + tag: '*', + blockTag: null, + selected: 'foobar', + wrap: true, + editor, + }); + + expect(editor.navigateLeft).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/frontend/lib/utils/users_cache_spec.js b/spec/frontend/lib/utils/users_cache_spec.js new file mode 100644 index 00000000000..7ed87123482 --- /dev/null +++ b/spec/frontend/lib/utils/users_cache_spec.js @@ -0,0 +1,265 @@ +import Api from '~/api'; +import UsersCache from '~/lib/utils/users_cache'; + +describe('UsersCache', () => { + const dummyUsername = 'win'; + const dummyUserId = 123; + const dummyUser = { + name: 'has a farm', + username: 'farmer', + }; + const dummyUserStatus = 'my status'; + + beforeEach(() => { + UsersCache.internalStorage = {}; + }); + + describe('get', () => { + it('returns undefined for empty cache', () => { + expect(UsersCache.internalStorage).toEqual({}); + + const user = UsersCache.get(dummyUsername); + + expect(user).toBe(undefined); + }); + + it('returns undefined for missing user', () => { + UsersCache.internalStorage['no body'] = 'no data'; + + const user = UsersCache.get(dummyUsername); + + expect(user).toBe(undefined); + }); + + it('returns matching user', () => { + UsersCache.internalStorage[dummyUsername] = dummyUser; + + const user = UsersCache.get(dummyUsername); + + expect(user).toBe(dummyUser); + }); + }); + + describe('hasData', () => { + it('returns false for empty cache', () => { + expect(UsersCache.internalStorage).toEqual({}); + + expect(UsersCache.hasData(dummyUsername)).toBe(false); + }); + + it('returns false for missing user', () => { + UsersCache.internalStorage['no body'] = 'no data'; + + expect(UsersCache.hasData(dummyUsername)).toBe(false); + }); + + it('returns true for matching user', () => { + UsersCache.internalStorage[dummyUsername] = dummyUser; + + expect(UsersCache.hasData(dummyUsername)).toBe(true); + }); + }); + + describe('remove', () => { + it('does nothing if cache is empty', () => { + expect(UsersCache.internalStorage).toEqual({}); + + UsersCache.remove(dummyUsername); + + expect(UsersCache.internalStorage).toEqual({}); + }); + + it('does nothing if cache contains no matching data', () => { + UsersCache.internalStorage['no body'] = 'no data'; + UsersCache.remove(dummyUsername); + + expect(UsersCache.internalStorage['no body']).toBe('no data'); + }); + + it('removes matching data', () => { + UsersCache.internalStorage[dummyUsername] = dummyUser; + UsersCache.remove(dummyUsername); + + expect(UsersCache.internalStorage).toEqual({}); + }); + }); + + describe('retrieve', () => { + let apiSpy; + + beforeEach(() => { + jest.spyOn(Api, 'users').mockImplementation((query, options) => apiSpy(query, options)); + }); + + it('stores and returns data from API call if cache is empty', done => { + apiSpy = (query, options) => { + expect(query).toBe(''); + expect(options).toEqual({ + username: dummyUsername, + }); + + return Promise.resolve({ + data: [dummyUser], + }); + }; + + UsersCache.retrieve(dummyUsername) + .then(user => { + expect(user).toBe(dummyUser); + expect(UsersCache.internalStorage[dummyUsername]).toBe(dummyUser); + }) + .then(done) + .catch(done.fail); + }); + + it('returns undefined if Ajax call fails and cache is empty', done => { + const dummyError = new Error('server exploded'); + + apiSpy = (query, options) => { + expect(query).toBe(''); + expect(options).toEqual({ + username: dummyUsername, + }); + + return Promise.reject(dummyError); + }; + + UsersCache.retrieve(dummyUsername) + .then(user => done.fail(`Received unexpected user: ${JSON.stringify(user)}`)) + .catch(error => { + expect(error).toBe(dummyError); + }) + .then(done) + .catch(done.fail); + }); + + it('makes no Ajax call if matching data exists', done => { + UsersCache.internalStorage[dummyUsername] = dummyUser; + + apiSpy = () => done.fail(new Error('expected no Ajax call!')); + + UsersCache.retrieve(dummyUsername) + .then(user => { + expect(user).toBe(dummyUser); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('retrieveById', () => { + let apiSpy; + + beforeEach(() => { + jest.spyOn(Api, 'user').mockImplementation(id => apiSpy(id)); + }); + + it('stores and returns data from API call if cache is empty', done => { + apiSpy = id => { + expect(id).toBe(dummyUserId); + + return Promise.resolve({ + data: dummyUser, + }); + }; + + UsersCache.retrieveById(dummyUserId) + .then(user => { + expect(user).toBe(dummyUser); + expect(UsersCache.internalStorage[dummyUserId]).toBe(dummyUser); + }) + .then(done) + .catch(done.fail); + }); + + it('returns undefined if Ajax call fails and cache is empty', done => { + const dummyError = new Error('server exploded'); + + apiSpy = id => { + expect(id).toBe(dummyUserId); + + return Promise.reject(dummyError); + }; + + UsersCache.retrieveById(dummyUserId) + .then(user => done.fail(`Received unexpected user: ${JSON.stringify(user)}`)) + .catch(error => { + expect(error).toBe(dummyError); + }) + .then(done) + .catch(done.fail); + }); + + it('makes no Ajax call if matching data exists', done => { + UsersCache.internalStorage[dummyUserId] = dummyUser; + + apiSpy = () => done.fail(new Error('expected no Ajax call!')); + + UsersCache.retrieveById(dummyUserId) + .then(user => { + expect(user).toBe(dummyUser); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('retrieveStatusById', () => { + let apiSpy; + + beforeEach(() => { + jest.spyOn(Api, 'userStatus').mockImplementation(id => apiSpy(id)); + }); + + it('stores and returns data from API call if cache is empty', done => { + apiSpy = id => { + expect(id).toBe(dummyUserId); + + return Promise.resolve({ + data: dummyUserStatus, + }); + }; + + UsersCache.retrieveStatusById(dummyUserId) + .then(userStatus => { + expect(userStatus).toBe(dummyUserStatus); + expect(UsersCache.internalStorage[dummyUserId].status).toBe(dummyUserStatus); + }) + .then(done) + .catch(done.fail); + }); + + it('returns undefined if Ajax call fails and cache is empty', done => { + const dummyError = new Error('server exploded'); + + apiSpy = id => { + expect(id).toBe(dummyUserId); + + return Promise.reject(dummyError); + }; + + UsersCache.retrieveStatusById(dummyUserId) + .then(userStatus => done.fail(`Received unexpected user: ${JSON.stringify(userStatus)}`)) + .catch(error => { + expect(error).toBe(dummyError); + }) + .then(done) + .catch(done.fail); + }); + + it('makes no Ajax call if matching data exists', done => { + UsersCache.internalStorage[dummyUserId] = { + status: dummyUserStatus, + }; + + apiSpy = () => done.fail(new Error('expected no Ajax call!')); + + UsersCache.retrieveStatusById(dummyUserId) + .then(userStatus => { + expect(userStatus).toBe(dummyUserStatus); + }) + .then(done) + .catch(done.fail); + }); + }); +}); |