Commit a568cb88 authored by Lukas Eipert's avatar Lukas Eipert

Fix pride flag emoji rendering

In our emojis.json (version 1) the pride flag is missing a zero-width
joiner between flag and rainbow. Therefore the flag will be rendering
the fallback instead of the native rainbow flag. By adding the
zero-width join, we enable native rendering of the flag.

Changelog: fixed
parent 90467765
......@@ -16,3 +16,6 @@ export const CATEGORY_ICON_MAP = {
export const EMOJIS_PER_ROW = 9;
export const EMOJI_ROW_HEIGHT = 34;
export const CATEGORY_ROW_HEIGHT = 37;
export const CACHE_VERSION_KEY = 'gl-emoji-map-version';
export const CACHE_KEY = 'gl-emoji-map';
......@@ -3,7 +3,7 @@ import emojiRegexFactory from 'emoji-regex';
import emojiAliases from 'emojis/aliases.json';
import AccessorUtilities from '../lib/utils/accessor';
import axios from '../lib/utils/axios_utils';
import { CATEGORY_ICON_MAP, FREQUENTLY_USED_KEY } from './constants';
import { CACHE_KEY, CACHE_VERSION_KEY, CATEGORY_ICON_MAP, FREQUENTLY_USED_KEY } from './constants';
let emojiMap = null;
let validEmojiNames = null;
......@@ -17,10 +17,15 @@ const isLocalStorageAvailable = AccessorUtilities.canUseLocalStorage();
async function loadEmoji() {
if (
isLocalStorageAvailable &&
window.localStorage.getItem('gl-emoji-map-version') === EMOJI_VERSION &&
window.localStorage.getItem('gl-emoji-map')
window.localStorage.getItem(CACHE_VERSION_KEY) === EMOJI_VERSION &&
window.localStorage.getItem(CACHE_KEY)
) {
return JSON.parse(window.localStorage.getItem('gl-emoji-map'));
const emojis = JSON.parse(window.localStorage.getItem(CACHE_KEY));
// Workaround because the pride flag is broken in EMOJI_VERSION = '1'
if (emojis.gay_pride_flag) {
emojis.gay_pride_flag.e = '🏳️‍🌈';
}
return emojis;
}
// We load the JSON file direct from the server
......@@ -29,8 +34,8 @@ async function loadEmoji() {
const { data } = await axios.get(
`${gon.relative_url_root || ''}/-/emojis/${EMOJI_VERSION}/emojis.json`,
);
window.localStorage.setItem('gl-emoji-map-version', EMOJI_VERSION);
window.localStorage.setItem('gl-emoji-map', JSON.stringify(data));
window.localStorage.setItem(CACHE_VERSION_KEY, EMOJI_VERSION);
window.localStorage.setItem(CACHE_KEY, JSON.stringify(data));
return data;
}
......
import MockAdapter from 'axios-mock-adapter';
import { initEmojiMap, EMOJI_VERSION } from '~/emoji';
import axios from '~/lib/utils/axios_utils';
import { CACHE_VERSION_KEY, CACHE_KEY } from '~/emoji/constants';
export const validEmoji = {
atom: {
......@@ -91,11 +90,14 @@ export const mockEmojiData = Object.keys(emojiFixtureMap).reduce((acc, k) => {
return acc;
}, {});
export async function initEmojiMock(mockData = mockEmojiData) {
const mock = new MockAdapter(axios);
mock.onGet(`/-/emojis/${EMOJI_VERSION}/emojis.json`).reply(200, JSON.stringify(mockData));
export function clearEmojiMock() {
localStorage.clear();
initEmojiMap.promise = null;
}
export async function initEmojiMock(mockData = mockEmojiData) {
clearEmojiMock();
localStorage.setItem(CACHE_VERSION_KEY, EMOJI_VERSION);
localStorage.setItem(CACHE_KEY, JSON.stringify(mockData));
await initEmojiMap();
return mock;
}
import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
import Cookies from 'js-cookie';
import { initEmojiMock, clearEmojiMock } from 'helpers/emoji';
import { useFakeRequestAnimationFrame } from 'helpers/fake_request_animation_frame';
import loadAwardsHandler from '~/awards_handler';
import { EMOJI_VERSION } from '~/emoji';
import axios from '~/lib/utils/axios_utils';
window.gl = window.gl || {};
window.gon = window.gon || {};
let mock;
let awardsHandler = null;
const urlRoot = gon.relative_url_root;
......@@ -76,8 +73,7 @@ describe('AwardsHandler', () => {
};
beforeEach(async () => {
mock = new MockAdapter(axios);
mock.onGet(`/-/emojis/${EMOJI_VERSION}/emojis.json`).reply(200, emojiData);
await initEmojiMock(emojiData);
loadFixtures('snippets/show.html');
......@@ -89,7 +85,7 @@ describe('AwardsHandler', () => {
// restore original url root value
gon.relative_url_root = urlRoot;
mock.restore();
clearEmojiMock();
// Undo what we did to the shared <body>
$('body').removeAttr('data-page');
......
import MockAdapter from 'axios-mock-adapter';
import { initEmojiMock, clearEmojiMock } from 'helpers/emoji';
import waitForPromises from 'helpers/wait_for_promises';
import installGlEmojiElement from '~/behaviors/gl_emoji';
import { initEmojiMap, EMOJI_VERSION } from '~/emoji';
import { EMOJI_VERSION } from '~/emoji';
import * as EmojiUnicodeSupport from '~/emoji/support';
import axios from '~/lib/utils/axios_utils';
jest.mock('~/emoji/support');
describe('gl_emoji', () => {
let mock;
const emojiData = {
grey_question: {
c: 'symbols',
......@@ -38,15 +36,12 @@ describe('gl_emoji', () => {
return div.firstElementChild;
}
beforeEach(() => {
mock = new MockAdapter(axios);
mock.onGet(`/-/emojis/${EMOJI_VERSION}/emojis.json`).reply(200, emojiData);
return initEmojiMap().catch(() => {});
beforeEach(async () => {
await initEmojiMock(emojiData);
});
afterEach(() => {
mock.restore();
clearEmojiMock();
document.body.innerHTML = '';
});
......
......@@ -4,6 +4,7 @@ import {
initEmojiMock,
validEmoji,
invalidEmoji,
clearEmojiMock,
} from 'helpers/emoji';
import { trimText } from 'helpers/text_helper';
import {
......@@ -14,6 +15,7 @@ import {
initEmojiMap,
getAllEmoji,
} from '~/emoji';
import isEmojiUnicodeSupported, {
isFlagEmoji,
isRainbowFlagEmoji,
......@@ -43,14 +45,12 @@ const emptySupportMap = {
};
describe('emoji', () => {
let mock;
beforeEach(async () => {
mock = await initEmojiMock();
await initEmojiMock();
});
afterEach(() => {
mock.restore();
clearEmojiMock();
});
describe('initEmojiMap', () => {
......@@ -71,6 +71,29 @@ describe('emoji', () => {
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', () => {
......
......@@ -3,7 +3,7 @@ import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
import labelsFixture from 'test_fixtures/autocomplete_sources/labels.json';
import GfmAutoComplete, { membersBeforeSave, highlighter } from 'ee_else_ce/gfm_auto_complete';
import { initEmojiMock } from 'helpers/emoji';
import { initEmojiMock, clearEmojiMock } from 'helpers/emoji';
import '~/lib/utils/jquery_at_who';
import { TEST_HOST } from 'helpers/test_constants';
import waitForPromises from 'helpers/wait_for_promises';
......@@ -803,8 +803,6 @@ describe('GfmAutoComplete', () => {
});
describe('emoji', () => {
let mock;
const mockItem = {
'atwho-at': ':',
emoji: {
......@@ -818,14 +816,14 @@ describe('GfmAutoComplete', () => {
};
beforeEach(async () => {
mock = await initEmojiMock();
await initEmojiMock();
await new GfmAutoComplete({}).loadEmojiData({ atwho() {}, trigger() {} }, ':');
if (!GfmAutoComplete.glEmojiTag) throw new Error('emoji not loaded');
});
afterEach(() => {
mock.restore();
clearEmojiMock();
});
describe('Emoji.templateFunction', () => {
......
import { GlModal, GlFormCheckbox } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { initEmojiMock } from 'helpers/emoji';
import { initEmojiMock, clearEmojiMock } from 'helpers/emoji';
import * as UserApi from '~/api/user_api';
import EmojiPicker from '~/emoji/components/picker.vue';
import createFlash from '~/flash';
......@@ -12,7 +12,6 @@ jest.mock('~/flash');
describe('SetStatusModalWrapper', () => {
let wrapper;
let mockEmoji;
const $toast = {
show: jest.fn(),
};
......@@ -63,12 +62,12 @@ describe('SetStatusModalWrapper', () => {
afterEach(() => {
wrapper.destroy();
mockEmoji.restore();
clearEmojiMock();
});
describe('with minimum props', () => {
beforeEach(async () => {
mockEmoji = await initEmojiMock();
await initEmojiMock();
wrapper = createComponent();
return initModal();
});
......@@ -112,7 +111,7 @@ describe('SetStatusModalWrapper', () => {
describe('improvedEmojiPicker is true', () => {
beforeEach(async () => {
mockEmoji = await initEmojiMock();
await initEmojiMock();
wrapper = createComponent({}, true);
return initModal();
});
......@@ -126,7 +125,7 @@ describe('SetStatusModalWrapper', () => {
describe('with no currentMessage set', () => {
beforeEach(async () => {
mockEmoji = await initEmojiMock();
await initEmojiMock();
wrapper = createComponent({ currentMessage: '' });
return initModal();
});
......@@ -146,7 +145,7 @@ describe('SetStatusModalWrapper', () => {
describe('with no currentEmoji set', () => {
beforeEach(async () => {
mockEmoji = await initEmojiMock();
await initEmojiMock();
wrapper = createComponent({ currentEmoji: '' });
return initModal();
});
......@@ -161,7 +160,7 @@ describe('SetStatusModalWrapper', () => {
describe('with no currentMessage set', () => {
beforeEach(async () => {
mockEmoji = await initEmojiMock();
await initEmojiMock();
wrapper = createComponent({ currentEmoji: '', currentMessage: '' });
return initModal();
});
......@@ -174,7 +173,7 @@ describe('SetStatusModalWrapper', () => {
describe('with currentClearStatusAfter set', () => {
beforeEach(async () => {
mockEmoji = await initEmojiMock();
await initEmojiMock();
wrapper = createComponent({ currentClearStatusAfter: '2021-01-01 00:00:00 UTC' });
return initModal();
});
......@@ -190,7 +189,7 @@ describe('SetStatusModalWrapper', () => {
describe('update status', () => {
describe('succeeds', () => {
beforeEach(async () => {
mockEmoji = await initEmojiMock();
await initEmojiMock();
wrapper = createComponent();
await initModal();
......@@ -246,7 +245,7 @@ describe('SetStatusModalWrapper', () => {
describe('success message', () => {
beforeEach(async () => {
mockEmoji = await initEmojiMock();
await initEmojiMock();
wrapper = createComponent({ currentEmoji: '', currentMessage: '' });
jest.spyOn(UserApi, 'updateUserStatus').mockResolvedValue();
return initModal({ mockOnUpdateSuccess: false });
......@@ -262,7 +261,7 @@ describe('SetStatusModalWrapper', () => {
describe('with errors', () => {
beforeEach(async () => {
mockEmoji = await initEmojiMock();
await initEmojiMock();
wrapper = createComponent();
await initModal();
......@@ -279,7 +278,7 @@ describe('SetStatusModalWrapper', () => {
describe('error message', () => {
beforeEach(async () => {
mockEmoji = await initEmojiMock();
await initEmojiMock();
wrapper = createComponent({ currentEmoji: '', currentMessage: '' });
jest.spyOn(UserApi, 'updateUserStatus').mockRejectedValue();
return initModal({ mockOnUpdateFailure: false });
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment