Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/emoji/index_spec.js')
-rw-r--r--spec/frontend/emoji/index_spec.js266
1 files changed, 220 insertions, 46 deletions
diff --git a/spec/frontend/emoji/index_spec.js b/spec/frontend/emoji/index_spec.js
index 1a12bd303f1..7d6a45fbf30 100644
--- a/spec/frontend/emoji/index_spec.js
+++ b/spec/frontend/emoji/index_spec.js
@@ -1,10 +1,11 @@
+import MockAdapter from 'axios-mock-adapter';
import {
emojiFixtureMap,
- mockEmojiData,
initEmojiMock,
validEmoji,
invalidEmoji,
clearEmojiMock,
+ mockEmojiData,
} from 'helpers/emoji';
import { trimText } from 'helpers/text_helper';
import { createMockClient } from 'helpers/mock_apollo_helper';
@@ -14,9 +15,10 @@ import {
getEmojiInfo,
sortEmoji,
initEmojiMap,
- getAllEmoji,
+ getEmojiMap,
emojiFallbackImageSrc,
loadCustomEmojiWithNames,
+ EMOJI_VERSION,
} from '~/emoji';
import isEmojiUnicodeSupported, {
@@ -27,8 +29,11 @@ import isEmojiUnicodeSupported, {
isHorceRacingSkinToneComboEmoji,
isPersonZwjEmoji,
} from '~/emoji/support/is_emoji_unicode_supported';
-import { NEUTRAL_INTENT_MULTIPLIER } from '~/emoji/constants';
+import { CACHE_KEY, CACHE_VERSION_KEY, NEUTRAL_INTENT_MULTIPLIER } from '~/emoji/constants';
import customEmojiQuery from '~/emoji/queries/custom_emoji.query.graphql';
+import axios from '~/lib/utils/axios_utils';
+import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
+import { useLocalStorageSpy } from 'jest/__helpers__/local_storage_helper';
let mockClient;
jest.mock('~/lib/graphql', () => {
@@ -75,6 +80,195 @@ function createMockEmojiClient() {
document.body.dataset.groupFullPath = 'test-group';
}
+describe('retrieval of emojis.json', () => {
+ useLocalStorageSpy();
+
+ let mock;
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ mock.onGet(/emojis\.json$/).reply(HTTP_STATUS_OK, mockEmojiData);
+ initEmojiMap.promise = null;
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ const assertCorrectLocalStorage = () => {
+ expect(localStorage.length).toBe(1);
+ expect(localStorage.getItem(CACHE_KEY)).toBe(
+ JSON.stringify({ data: mockEmojiData, EMOJI_VERSION }),
+ );
+ };
+
+ const assertEmojiBeingLoadedCorrectly = () => {
+ expect(Object.keys(getEmojiMap())).toEqual(Object.keys(validEmoji));
+ };
+
+ it('should remove the old `CACHE_VERSION_KEY`', async () => {
+ localStorage.setItem(CACHE_VERSION_KEY, EMOJI_VERSION);
+
+ await initEmojiMap();
+
+ expect(localStorage.getItem(CACHE_VERSION_KEY)).toBe(null);
+ });
+
+ describe('when the localStorage is empty', () => {
+ it('should call the API and store results in localStorage', async () => {
+ await initEmojiMap();
+
+ assertEmojiBeingLoadedCorrectly();
+ expect(mock.history.get.length).toBe(1);
+ assertCorrectLocalStorage();
+ });
+ });
+
+ describe('when the localStorage stores the correct version', () => {
+ beforeEach(async () => {
+ localStorage.setItem(CACHE_KEY, JSON.stringify({ data: mockEmojiData, EMOJI_VERSION }));
+ localStorage.setItem.mockClear();
+ await initEmojiMap();
+ });
+
+ it('should not call the API and not mutate the localStorage', () => {
+ assertEmojiBeingLoadedCorrectly();
+ expect(mock.history.get.length).toBe(0);
+ expect(localStorage.setItem).not.toHaveBeenCalled();
+ assertCorrectLocalStorage();
+ });
+ });
+
+ describe('when the localStorage stores an incorrect version', () => {
+ beforeEach(async () => {
+ localStorage.setItem(
+ CACHE_KEY,
+ JSON.stringify({ data: mockEmojiData, EMOJI_VERSION: `${EMOJI_VERSION}-different` }),
+ );
+ localStorage.setItem.mockClear();
+ await initEmojiMap();
+ });
+
+ it('should call the API and store results in localStorage', () => {
+ assertEmojiBeingLoadedCorrectly();
+ expect(mock.history.get.length).toBe(1);
+ assertCorrectLocalStorage();
+ });
+ });
+
+ describe('when the localStorage stores corrupted data', () => {
+ beforeEach(async () => {
+ localStorage.setItem(CACHE_KEY, "[invalid: 'INVALID_JSON");
+ localStorage.setItem.mockClear();
+ await initEmojiMap();
+ });
+
+ it('should call the API and store results in localStorage', () => {
+ assertEmojiBeingLoadedCorrectly();
+ expect(mock.history.get.length).toBe(1);
+ assertCorrectLocalStorage();
+ });
+ });
+
+ describe('when the localStorage stores data in a different format', () => {
+ beforeEach(async () => {
+ localStorage.setItem(CACHE_KEY, JSON.stringify([]));
+ localStorage.setItem.mockClear();
+ await initEmojiMap();
+ });
+
+ it('should call the API and store results in localStorage', () => {
+ assertEmojiBeingLoadedCorrectly();
+ expect(mock.history.get.length).toBe(1);
+ assertCorrectLocalStorage();
+ });
+ });
+
+ describe('when the localStorage is full', () => {
+ beforeEach(async () => {
+ const oldSetItem = localStorage.setItem;
+ localStorage.setItem = jest.fn().mockImplementationOnce((key, value) => {
+ if (key === CACHE_KEY) {
+ throw new Error('Storage Full');
+ }
+ oldSetItem(key, value);
+ });
+ await initEmojiMap();
+ });
+
+ it('should call API but not store the results', () => {
+ assertEmojiBeingLoadedCorrectly();
+ expect(mock.history.get.length).toBe(1);
+ expect(localStorage.length).toBe(0);
+ expect(localStorage.setItem).toHaveBeenCalledTimes(1);
+ expect(localStorage.setItem).toHaveBeenCalledWith(
+ CACHE_KEY,
+ JSON.stringify({ data: mockEmojiData, EMOJI_VERSION }),
+ );
+ });
+ });
+
+ describe('backwards compatibility', () => {
+ // As per: https://gitlab.com/gitlab-org/gitlab/-/blob/62b66abd3bb7801e7c85b4e42a1bbd51fbb37c1b/app/assets/javascripts/emoji/index.js#L27-52
+ async function prevImplementation() {
+ if (
+ window.localStorage.getItem(CACHE_VERSION_KEY) === EMOJI_VERSION &&
+ window.localStorage.getItem(CACHE_KEY)
+ ) {
+ return JSON.parse(window.localStorage.getItem(CACHE_KEY));
+ }
+
+ // We load the JSON file direct from the server
+ // because it can't be loaded from a CDN due to
+ // cross domain problems with JSON
+ const { data } = await axios.get(
+ `${gon.relative_url_root || ''}/-/emojis/${EMOJI_VERSION}/emojis.json`,
+ );
+
+ try {
+ window.localStorage.setItem(CACHE_VERSION_KEY, EMOJI_VERSION);
+ window.localStorage.setItem(CACHE_KEY, JSON.stringify(data));
+ } catch {
+ // Setting data in localstorage may fail when storage quota is exceeded.
+ // We should continue even when this fails.
+ }
+
+ return data;
+ }
+
+ it('Old -> New -> Old should not break', async () => {
+ // The follow steps simulate a multi-version deployment. e.g.
+ // Hitting a page on "regular" .com, then canary, and then "regular" again
+
+ // Load emoji the old way to pre-populate the cache
+ let res = await prevImplementation();
+ expect(res).toEqual(mockEmojiData);
+ expect(mock.history.get.length).toBe(1);
+ localStorage.setItem.mockClear();
+
+ // Load emoji the new way
+ await initEmojiMap();
+ expect(mock.history.get.length).toBe(2);
+ assertEmojiBeingLoadedCorrectly();
+ assertCorrectLocalStorage();
+ localStorage.setItem.mockClear();
+
+ // Load emoji the old way to pre-populate the cache
+ res = await prevImplementation();
+ expect(res).toEqual(mockEmojiData);
+ expect(mock.history.get.length).toBe(3);
+ expect(localStorage.setItem.mock.calls).toEqual([
+ [CACHE_VERSION_KEY, EMOJI_VERSION],
+ [CACHE_KEY, JSON.stringify(mockEmojiData)],
+ ]);
+
+ // Load emoji the old way should work again (and be taken from the cache)
+ res = await prevImplementation();
+ expect(res).toEqual(mockEmojiData);
+ expect(mock.history.get.length).toBe(3);
+ });
+ });
+});
+
describe('emoji', () => {
beforeEach(async () => {
await initEmojiMock();
@@ -90,7 +284,7 @@ describe('emoji', () => {
it('should contain valid emoji', async () => {
await initEmojiMap();
- const allEmoji = Object.keys(getAllEmoji());
+ const allEmoji = Object.keys(getEmojiMap());
Object.keys(validEmoji).forEach((key) => {
expect(allEmoji.includes(key)).toBe(true);
});
@@ -99,34 +293,11 @@ describe('emoji', () => {
it('should not contain invalid emoji', async () => {
await initEmojiMap();
- const allEmoji = Object.keys(getAllEmoji());
+ const allEmoji = Object.keys(getEmojiMap());
Object.keys(invalidEmoji).forEach((key) => {
expect(allEmoji.includes(key)).toBe(false);
});
});
-
- it('fixes broken pride emoji', async () => {
- clearEmojiMock();
- await initEmojiMock({
- gay_pride_flag: {
- c: 'flags',
- // Without a zero-width joiner
- e: '🏳🌈',
- name: 'gay_pride_flag',
- u: '6.0',
- },
- });
-
- expect(getAllEmoji()).toEqual({
- gay_pride_flag: {
- c: 'flags',
- // With a zero-width joiner
- e: '🏳️‍🌈',
- name: 'gay_pride_flag',
- u: '6.0',
- },
- });
- });
});
describe('glEmojiTag', () => {
@@ -448,11 +619,11 @@ describe('emoji', () => {
describe('getEmojiInfo', () => {
it.each(['atom', 'five', 'black_heart'])("should return a correct emoji for '%s'", (name) => {
- expect(getEmojiInfo(name)).toEqual(mockEmojiData[name]);
+ expect(getEmojiInfo(name)).toEqual(getEmojiMap()[name]);
});
it('should return fallback emoji by default', () => {
- expect(getEmojiInfo('atjs')).toEqual(mockEmojiData.grey_question);
+ expect(getEmojiInfo('atjs')).toEqual(getEmojiMap().grey_question);
});
it('should return null when fallback is false', () => {
@@ -461,7 +632,7 @@ describe('emoji', () => {
describe('when query is undefined', () => {
it('should return fallback emoji by default', () => {
- expect(getEmojiInfo()).toEqual(mockEmojiData.grey_question);
+ expect(getEmojiInfo()).toEqual(getEmojiMap().grey_question);
});
it('should return null when fallback is false', () => {
@@ -489,9 +660,9 @@ describe('emoji', () => {
}
return {
- emoji: mockEmojiData[name],
+ emoji: getEmojiMap()[name],
field: 'd',
- fieldValue: mockEmojiData[name].d,
+ fieldValue: getEmojiMap()[name].d,
score,
};
})
@@ -542,7 +713,7 @@ describe('emoji', () => {
const { field, score, fieldValue, name } = item;
return {
- emoji: mockEmojiData[name],
+ emoji: getEmojiMap()[name],
field,
fieldValue,
score,
@@ -669,9 +840,9 @@ describe('emoji', () => {
const { field, score, name } = item;
return {
- emoji: mockEmojiData[name],
+ emoji: getEmojiMap()[name],
field,
- fieldValue: mockEmojiData[name][field],
+ fieldValue: getEmojiMap()[name][field],
score,
};
});
@@ -737,7 +908,7 @@ describe('emoji', () => {
it.each`
emoji | src
- ${'thumbsup'} | ${'/-/emojis/2/thumbsup.png'}
+ ${'thumbsup'} | ${'/-/emojis/3/thumbsup.png'}
${'parrot'} | ${'parrot.gif'}
`('returns $src for emoji with name $emoji', ({ emoji, src }) => {
expect(emojiFallbackImageSrc(emoji)).toBe(src);
@@ -757,7 +928,7 @@ describe('emoji', () => {
it('returns empty object', async () => {
const result = await loadCustomEmojiWithNames();
- expect(result).toEqual({});
+ expect(result).toEqual({ emojis: {}, names: [] });
});
});
@@ -769,7 +940,7 @@ describe('emoji', () => {
it('returns empty object', async () => {
const result = await loadCustomEmojiWithNames();
- expect(result).toEqual({});
+ expect(result).toEqual({ emojis: {}, names: [] });
});
});
@@ -778,14 +949,17 @@ describe('emoji', () => {
const result = await loadCustomEmojiWithNames();
expect(result).toEqual({
- parrot: {
- c: 'custom',
- d: 'parrot',
- e: undefined,
- name: 'parrot',
- src: 'parrot.gif',
- u: 'custom',
+ emojis: {
+ parrot: {
+ c: 'custom',
+ d: 'parrot',
+ e: undefined,
+ name: 'parrot',
+ src: 'parrot.gif',
+ u: 'custom',
+ },
},
+ names: ['parrot'],
});
});
});