summaryrefslogtreecommitdiff
path: root/spec/frontend
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2019-12-03 00:06:28 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2019-12-03 00:06:28 +0000
commit10d0e5693c0eed9fd9c40f4fadeda187237db6b5 (patch)
tree82a723f14e4a44146c7c5e2259b9c7d6d9b834bb /spec/frontend
parenta19a376bf35b2009566e86b8190662c21ed7e2ba (diff)
downloadgitlab-ce-10d0e5693c0eed9fd9c40f4fadeda187237db6b5.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
-rw-r--r--spec/frontend/lib/utils/accessor_spec.js85
-rw-r--r--spec/frontend/lib/utils/dom_utils_spec.js115
-rw-r--r--spec/frontend/lib/utils/file_upload_spec.js64
-rw-r--r--spec/frontend/lib/utils/highlight_spec.js43
-rw-r--r--spec/frontend/lib/utils/icon_utils_spec.js60
-rw-r--r--spec/frontend/lib/utils/text_markdown_spec.js308
-rw-r--r--spec/frontend/lib/utils/users_cache_spec.js265
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);
+ });
+ });
+});