Preferred reactions: store raw emoji, gate on feature flag

This commit is contained in:
Evan Hahn 2021-09-09 18:47:30 -05:00 committed by GitHub
parent 9b45b3dae2
commit e2392433e0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 168 additions and 192 deletions

View file

@ -8,6 +8,7 @@ import type { WebAPIType } from './textsecure/WebAPI';
export type ConfigKeyType =
| 'desktop.announcementGroup'
| 'desktop.clientExpiration'
| 'desktop.customizePreferredReactions'
| 'desktop.disableGV1'
| 'desktop.groupCallOutboundRing'
| 'desktop.groupCalling'

View file

@ -23,26 +23,12 @@ const defaultProps: ComponentProps<
'cancelCustomizePreferredReactionsModal'
),
deselectDraftEmoji: action('deselectDraftEmoji'),
draftPreferredReactions: [
'sparkles',
'sparkle',
'sparkler',
'shark',
'sparkling_heart',
'thumbsup',
],
draftPreferredReactions: ['✨', '❇️', '🎇', '🦈', '💖', '🅿️'],
hadSaveError: false,
i18n,
isSaving: false,
onSetSkinTone: action('onSetSkinTone'),
originalPreferredReactions: [
'heart',
'thumbsup',
'thumbsdown',
'joy',
'open_mouth',
'cry',
],
originalPreferredReactions: ['❤️', '👍', '👎', '😂', '😮', '😢'],
replaceSelectedDraftEmoji: action('replaceSelectedDraftEmoji'),
resetDraftEmoji: action('resetDraftEmoji'),
savePreferredReactions: action('savePreferredReactions'),

View file

@ -13,8 +13,8 @@ import {
ReactionPickerSelectionStyle,
} from './conversation/ReactionPicker';
import { EmojiPicker } from './emoji/EmojiPicker';
import { DEFAULT_PREFERRED_REACTION_EMOJI_SHORT_NAMES } from '../reactions/constants';
import { convertShortName } from './emoji/lib';
import { DEFAULT_PREFERRED_REACTION_EMOJI } from '../reactions/constants';
import { offsetDistanceModifier } from '../util/popperUtil';
type PropsType = {
@ -94,20 +94,16 @@ export function CustomizingPreferredReactionsModal({
};
}, [isSomethingSelected, popperElement, deselectDraftEmoji]);
const emojis = draftPreferredReactions.map(shortName =>
convertShortName(shortName, skinTone)
);
const selected =
typeof selectedDraftEmojiIndex === 'number'
? emojis[selectedDraftEmojiIndex]
? draftPreferredReactions[selectedDraftEmojiIndex]
: undefined;
const onPick = isSaving
? noop
: (pickedEmoji: string) => {
selectDraftEmojiToBeReplaced(
emojis.findIndex(emoji => emoji === pickedEmoji)
draftPreferredReactions.findIndex(emoji => emoji === pickedEmoji)
);
};
@ -117,7 +113,12 @@ export function CustomizingPreferredReactionsModal({
);
const canReset =
!isSaving &&
!isEqual(DEFAULT_PREFERRED_REACTION_EMOJI, draftPreferredReactions);
!isEqual(
DEFAULT_PREFERRED_REACTION_EMOJI_SHORT_NAMES.map(shortName =>
convertShortName(shortName, skinTone)
),
draftPreferredReactions
);
const canSave = !isSaving && hasChanged;
return (
@ -140,7 +141,6 @@ export function CustomizingPreferredReactionsModal({
selected={selected}
selectionStyle={ReactionPickerSelectionStyle.Menu}
renderEmojiPicker={shouldNotBeCalled}
skinTone={skinTone}
/>
{hadSaveError
? i18n('CustomizingPreferredReactions__had-save-error')
@ -155,8 +155,12 @@ export function CustomizingPreferredReactionsModal({
>
<EmojiPicker
i18n={i18n}
onPickEmoji={({ shortName }) => {
replaceSelectedDraftEmoji(shortName);
onPickEmoji={pickedEmoji => {
const emoji = convertShortName(
pickedEmoji.shortName,
pickedEmoji.skinTone
);
replaceSelectedDraftEmoji(emoji);
}}
skinTone={skinTone}
onSetSkinTone={onSetSkinTone}

View file

@ -6,7 +6,6 @@ import * as React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { select } from '@storybook/addon-knobs';
import { setup as setupI18n } from '../../../js/modules/i18n';
import enMessages from '../../../_locales/en/messages.json';
import {
@ -18,14 +17,7 @@ import { EmojiPicker } from '../emoji/EmojiPicker';
const i18n = setupI18n('en', enMessages);
const preferredReactionEmoji = [
'heart',
'thumbsup',
'thumbsdown',
'joy',
'open_mouth',
'cry',
];
const preferredReactionEmoji = ['❤️', '👍', '👎', '😂', '😮', '😢'];
const renderEmojiPicker: ReactionPickerProps['renderEmojiPicker'] = ({
onClose,
@ -56,7 +48,6 @@ storiesOf('Components/Conversation/ReactionPicker', module)
preferredReactionEmoji={preferredReactionEmoji}
renderEmojiPicker={renderEmojiPicker}
selectionStyle={ReactionPickerSelectionStyle.Picker}
skinTone={0}
/>
);
})
@ -74,30 +65,6 @@ storiesOf('Components/Conversation/ReactionPicker', module)
preferredReactionEmoji={preferredReactionEmoji}
renderEmojiPicker={renderEmojiPicker}
selectionStyle={ReactionPickerSelectionStyle.Picker}
skinTone={0}
/>
</div>
));
})
.add('Skin Tones', () => {
return ['❤️', '👍', '👎', '😂', '😮', '😢', '😡'].map(e => (
<div key={e} style={{ height: '100px' }}>
<ReactionPicker
i18n={i18n}
selected={e}
onPick={action('onPick')}
onSetSkinTone={action('onSetSkinTone')}
openCustomizePreferredReactionsModal={action(
'openCustomizePreferredReactionsModal'
)}
preferredReactionEmoji={preferredReactionEmoji}
renderEmojiPicker={renderEmojiPicker}
selectionStyle={ReactionPickerSelectionStyle.Picker}
skinTone={select(
'skinTone',
{ 0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5 },
5
)}
/>
</div>
));

View file

@ -10,6 +10,7 @@ import { Props as EmojiPickerProps } from '../emoji/EmojiPicker';
import { missingCaseError } from '../../util/missingCaseError';
import { useRestoreFocus } from '../../util/hooks/useRestoreFocus';
import { LocalizerType } from '../../types/Util';
import { canCustomizePreferredReactions } from '../../util/canCustomizePreferredReactions';
export enum ReactionPickerSelectionStyle {
Picker,
@ -35,7 +36,6 @@ export type OwnProps = {
openCustomizePreferredReactionsModal?: () => unknown;
preferredReactionEmoji: Array<string>;
renderEmojiPicker: (props: RenderEmojiPickerProps) => React.ReactElement;
skinTone: number;
};
export type Props = OwnProps & Pick<React.HTMLProps<HTMLDivElement>, 'style'>;
@ -81,7 +81,6 @@ export const ReactionPicker = React.forwardRef<HTMLDivElement, Props>(
renderEmojiPicker,
selected,
selectionStyle,
skinTone,
style,
},
ref
@ -116,7 +115,9 @@ export const ReactionPicker = React.forwardRef<HTMLDivElement, Props>(
if (pickingOther) {
return renderEmojiPicker({
onClickSettings: openCustomizePreferredReactionsModal,
onClickSettings: canCustomizePreferredReactions()
? openCustomizePreferredReactionsModal
: undefined,
onClose,
onPickEmoji,
onSetSkinTone,
@ -125,11 +126,8 @@ export const ReactionPicker = React.forwardRef<HTMLDivElement, Props>(
});
}
const emojis = preferredReactionEmoji.map(shortName =>
convertShortName(shortName, skinTone)
);
const otherSelected = selected && !emojis.includes(selected);
const otherSelected =
selected && !preferredReactionEmoji.includes(selected);
let moreButton: React.ReactNode;
if (!hasMoreButton) {
@ -189,7 +187,7 @@ export const ReactionPicker = React.forwardRef<HTMLDivElement, Props>(
selected ? 'module-ReactionPicker--something-selected' : undefined
)}
>
{emojis.map((emoji, index) => {
{preferredReactionEmoji.map((emoji, index) => {
const maybeFocusRef = index === 0 ? focusRef : undefined;
return (

View file

@ -1,7 +1,7 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
export const DEFAULT_PREFERRED_REACTION_EMOJI = [
export const DEFAULT_PREFERRED_REACTION_EMOJI_SHORT_NAMES = [
'heart',
'thumbsup',
'thumbsdown',

View file

@ -1,18 +1,27 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { DEFAULT_PREFERRED_REACTION_EMOJI } from './constants';
import * as emoji from '../components/emoji/lib';
import { DEFAULT_PREFERRED_REACTION_EMOJI_SHORT_NAMES } from './constants';
import { convertShortName } from '../components/emoji/lib';
import { isValidReactionEmoji } from './isValidReactionEmoji';
const PREFERRED_REACTION_EMOJI_COUNT = DEFAULT_PREFERRED_REACTION_EMOJI.length;
const PREFERRED_REACTION_EMOJI_COUNT =
DEFAULT_PREFERRED_REACTION_EMOJI_SHORT_NAMES.length;
export function getPreferredReactionEmoji(storedValue: unknown): Array<string> {
export function getPreferredReactionEmoji(
storedValue: unknown,
skinTone: number
): Array<string> {
const isStoredValueValid =
Array.isArray(storedValue) &&
storedValue.length === PREFERRED_REACTION_EMOJI_COUNT &&
storedValue.every(emoji.isShortName) &&
storedValue.every(isValidReactionEmoji) &&
!hasDuplicates(storedValue);
return isStoredValueValid ? storedValue : DEFAULT_PREFERRED_REACTION_EMOJI;
return isStoredValueValid
? storedValue
: DEFAULT_PREFERRED_REACTION_EMOJI_SHORT_NAMES.map(shortName =>
convertShortName(shortName, skinTone)
);
}
function hasDuplicates(arr: ReadonlyArray<unknown>): boolean {

View file

@ -8,8 +8,10 @@ import * as Errors from '../../types/errors';
import { replaceIndex } from '../../util/replaceIndex';
import { useBoundActions } from '../../util/hooks';
import type { StateType as RootStateType } from '../reducer';
import { DEFAULT_PREFERRED_REACTION_EMOJI } from '../../reactions/constants';
import { DEFAULT_PREFERRED_REACTION_EMOJI_SHORT_NAMES } from '../../reactions/constants';
import { getPreferredReactionEmoji } from '../../reactions/getPreferredReactionEmoji';
import { getEmojiSkinTone } from '../selectors/items';
import { convertShortName } from '../../components/emoji/lib';
// State
@ -61,7 +63,10 @@ type ReplaceSelectedDraftEmojiActionType = {
payload: string;
};
type ResetDraftEmojiActionType = { type: typeof RESET_DRAFT_EMOJI };
type ResetDraftEmojiActionType = {
type: typeof RESET_DRAFT_EMOJI;
payload: { skinTone: number };
};
type SavePreferredReactionsFulfilledActionType = {
type: typeof SAVE_PREFERRED_REACTIONS_FULFILLED;
@ -109,8 +114,10 @@ function openCustomizePreferredReactionsModal(): ThunkAction<
OpenCustomizePreferredReactionsModalActionType
> {
return (dispatch, getState) => {
const state = getState();
const originalPreferredReactions = getPreferredReactionEmoji(
getState().items.preferredReactionEmoji
getState().items.preferredReactionEmoji,
getEmojiSkinTone(state)
);
dispatch({
type: OPEN_CUSTOMIZE_PREFERRED_REACTIONS_MODAL,
@ -128,8 +135,16 @@ function replaceSelectedDraftEmoji(
};
}
function resetDraftEmoji(): ResetDraftEmojiActionType {
return { type: RESET_DRAFT_EMOJI };
function resetDraftEmoji(): ThunkAction<
void,
RootStateType,
unknown,
ResetDraftEmojiActionType
> {
return (dispatch, getState) => {
const skinTone = getEmojiSkinTone(getState());
dispatch({ type: RESET_DRAFT_EMOJI, payload: { skinTone } });
};
}
function savePreferredReactions(): ThunkAction<
@ -253,7 +268,8 @@ export function reducer(
},
};
}
case RESET_DRAFT_EMOJI:
case RESET_DRAFT_EMOJI: {
const { skinTone } = action.payload;
if (!state.customizePreferredReactionsModal) {
return state;
}
@ -261,10 +277,13 @@ export function reducer(
...state,
customizePreferredReactionsModal: {
...state.customizePreferredReactionsModal,
draftPreferredReactions: DEFAULT_PREFERRED_REACTION_EMOJI,
draftPreferredReactions: DEFAULT_PREFERRED_REACTION_EMOJI_SHORT_NAMES.map(
shortName => convertShortName(shortName, skinTone)
),
selectedDraftEmojiIndex: undefined,
},
};
}
case SAVE_PREFERRED_REACTIONS_PENDING:
if (!state.customizePreferredReactionsModal) {
return state;

View file

@ -65,6 +65,10 @@ export const getEmojiSkinTone = createSelector(
export const getPreferredReactionEmoji = createSelector(
getItems,
(state: Readonly<ItemsStateType>): Array<string> =>
getPreferredReactionEmojiFromStoredValue(state.preferredReactionEmoji)
getEmojiSkinTone,
(state: Readonly<ItemsStateType>, skinTone: number): Array<string> =>
getPreferredReactionEmojiFromStoredValue(
state.preferredReactionEmoji,
skinTone
)
);

View file

@ -8,10 +8,7 @@ import { useActions as usePreferredReactionsActions } from '../ducks/preferredRe
import { useActions as useItemsActions } from '../ducks/items';
import { getIntl } from '../selectors/user';
import {
getEmojiSkinTone,
getPreferredReactionEmoji,
} from '../selectors/items';
import { getPreferredReactionEmoji } from '../selectors/items';
import { LocalizerType } from '../../types/Util';
import {
@ -45,10 +42,6 @@ export const SmartReactionPicker = React.forwardRef<
getPreferredReactionEmoji
);
const skinTone = useSelector<StateType, number>(state =>
getEmojiSkinTone(state)
);
return (
<ReactionPicker
i18n={i18n}
@ -59,7 +52,6 @@ export const SmartReactionPicker = React.forwardRef<
preferredReactionEmoji={preferredReactionEmoji}
ref={ref}
selectionStyle={ReactionPickerSelectionStyle.Picker}
skinTone={skinTone}
{...props}
/>
);

View file

@ -2,7 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai';
import { DEFAULT_PREFERRED_REACTION_EMOJI } from '../../reactions/constants';
import { DEFAULT_PREFERRED_REACTION_EMOJI_SHORT_NAMES } from '../../reactions/constants';
import { getPreferredReactionEmoji } from '../../reactions/getPreferredReactionEmoji';
@ -12,35 +12,34 @@ describe('getPreferredReactionEmoji', () => {
// Invalid types
undefined,
null,
DEFAULT_PREFERRED_REACTION_EMOJI.join(','),
DEFAULT_PREFERRED_REACTION_EMOJI_SHORT_NAMES.join(','),
// Invalid lengths
[],
DEFAULT_PREFERRED_REACTION_EMOJI.slice(0, 3),
[...DEFAULT_PREFERRED_REACTION_EMOJI, 'sparkles'],
DEFAULT_PREFERRED_REACTION_EMOJI_SHORT_NAMES.slice(0, 3),
[...DEFAULT_PREFERRED_REACTION_EMOJI_SHORT_NAMES, '✨'],
// Non-strings in the array
['heart', 'thumbsdown', undefined, 'joy', 'open_mouth', 'cry'],
['heart', 'thumbsdown', 99, 'joy', 'open_mouth', 'cry'],
['❤️', '👍', undefined, '😂', '😮', '😢'],
['❤️', '👍', 99, '😂', '😮', '😢'],
// Invalid emoji
['heart', 'thumbsdown', 'gorbage!!', 'joy', 'open_mouth', 'cry'],
['❤️', '👍', 'x', '😂', '😮', '😢'],
['❤️', '👍', 'garbage!!', '😂', '😮', '😢'],
['❤️', '👍', '✨✨', '😂', '😮', '😢'],
// Has duplicates
['heart', 'thumbsdown', 'joy', 'joy', 'open_mouth', 'cry'],
['❤️', '👍', '👍', '😂', '😮', '😢'],
].forEach(input => {
assert.deepStrictEqual(
getPreferredReactionEmoji(input),
DEFAULT_PREFERRED_REACTION_EMOJI
);
assert.deepStrictEqual(getPreferredReactionEmoji(input, 2), [
'❤️',
'👍🏼',
'👎🏼',
'😂',
'😮',
'😢',
]);
});
});
it('returns a custom set if passed a valid value', () => {
const input = [
'sparkles',
'sparkle',
'sparkler',
'shark',
'sparkling_heart',
'parking',
];
assert.deepStrictEqual(getPreferredReactionEmoji(input), input);
const input = ['✨', '❇️', '🎇', '🦈', '💖', '🅿️'];
assert.deepStrictEqual(getPreferredReactionEmoji(input, 3), input);
});
});

View file

@ -26,6 +26,8 @@ describe('isValidReactionEmoji', () => {
it('returns true for strings that are exactly 1 emoji', () => {
assert.isTrue(isValidReactionEmoji('🇺🇸'));
assert.isTrue(isValidReactionEmoji('👍'));
assert.isTrue(isValidReactionEmoji('👍🏾'));
assert.isTrue(isValidReactionEmoji('👩‍❤️‍👩'));
});
});

View file

@ -3,9 +3,8 @@
import { assert } from 'chai';
import * as sinon from 'sinon';
import { reducer as rootReducer } from '../../../state/reducer';
import { StateType, reducer as rootReducer } from '../../../state/reducer';
import { noopAction } from '../../../state/ducks/noop';
import { DEFAULT_PREFERRED_REACTION_EMOJI } from '../../../reactions/constants';
import {
PreferredReactionsStateType,
@ -15,9 +14,12 @@ import {
} from '../../../state/ducks/preferredReactions';
describe('preferred reactions duck', () => {
const getEmptyRootState = () => rootReducer(undefined, noopAction());
const getEmptyRootState = (): StateType =>
rootReducer(undefined, noopAction());
const getRootState = (preferredReactions: PreferredReactionsStateType) => ({
const getRootState = (
preferredReactions: PreferredReactionsStateType
): StateType => ({
...getEmptyRootState(),
preferredReactions,
});
@ -25,22 +27,8 @@ describe('preferred reactions duck', () => {
const stateWithOpenCustomizationModal = {
...getInitialState(),
customizePreferredReactionsModal: {
draftPreferredReactions: [
'sparkles',
'sparkle',
'sparkler',
'shark',
'sparkling_heart',
'parking',
],
originalPreferredReactions: [
'blue_heart',
'thumbsup',
'thumbsdown',
'joy',
'open_mouth',
'cry',
],
draftPreferredReactions: ['✨', '❇️', '🎇', '🦈', '💖', '🅿️'],
originalPreferredReactions: ['💙', '👍', '👎', '😂', '😮', '😢'],
selectedDraftEmojiIndex: undefined,
isSaving: false as const,
hadSaveError: false,
@ -120,15 +108,26 @@ describe('preferred reactions duck', () => {
const { openCustomizePreferredReactionsModal } = actions;
it('opens the customization modal with defaults if no value was stored', () => {
const emptyRootState = getEmptyRootState();
const rootState = {
...emptyRootState,
items: {
...emptyRootState.items,
skinTone: 5,
},
};
const dispatch = sinon.spy();
openCustomizePreferredReactionsModal()(dispatch, getEmptyRootState, null);
openCustomizePreferredReactionsModal()(dispatch, () => rootState, null);
const [action] = dispatch.getCall(0).args;
const result = reducer(getEmptyRootState().preferredReactions, action);
const result = reducer(rootState.preferredReactions, action);
const expectedEmoji = ['❤️', '👍🏿', '👎🏿', '😂', '😮', '😢'];
assert.deepEqual(result.customizePreferredReactionsModal, {
draftPreferredReactions: DEFAULT_PREFERRED_REACTION_EMOJI,
originalPreferredReactions: DEFAULT_PREFERRED_REACTION_EMOJI,
draftPreferredReactions: expectedEmoji,
originalPreferredReactions: expectedEmoji,
selectedDraftEmojiIndex: undefined,
isSaving: false,
hadSaveError: false,
@ -136,14 +135,7 @@ describe('preferred reactions duck', () => {
});
it('opens the customization modal with stored values', () => {
const storedPreferredReactionEmoji = [
'sparkles',
'sparkle',
'sparkler',
'shark',
'sparkling_heart',
'parking',
];
const storedPreferredReactionEmoji = ['✨', '❇️', '🎇', '🦈', '💖', '🅿️'];
const emptyRootState = getEmptyRootState();
const state = {
@ -175,21 +167,21 @@ describe('preferred reactions duck', () => {
it('is a no-op if the customization modal is not open', () => {
const state = getInitialState();
const action = replaceSelectedDraftEmoji('cat');
const action = replaceSelectedDraftEmoji('🦈');
const result = reducer(state, action);
assert.strictEqual(result, state);
});
it('is a no-op if no emoji is selected', () => {
const action = replaceSelectedDraftEmoji('cat');
const action = replaceSelectedDraftEmoji('💅');
const result = reducer(stateWithOpenCustomizationModal, action);
assert.strictEqual(result, stateWithOpenCustomizationModal);
});
it('is a no-op if the new emoji is already in the list', () => {
const action = replaceSelectedDraftEmoji('shark');
const action = replaceSelectedDraftEmoji('');
const result = reducer(
stateWithOpenCustomizationModalAndSelectedEmoji,
action
@ -202,7 +194,7 @@ describe('preferred reactions duck', () => {
});
it('replaces the selected draft emoji and deselects', () => {
const action = replaceSelectedDraftEmoji('cat');
const action = replaceSelectedDraftEmoji('🐱');
const result = reducer(
stateWithOpenCustomizationModalAndSelectedEmoji,
action
@ -210,7 +202,7 @@ describe('preferred reactions duck', () => {
assert.deepStrictEqual(
result.customizePreferredReactionsModal?.draftPreferredReactions,
['sparkles', 'cat', 'sparkler', 'shark', 'sparkling_heart', 'parking']
['✨', '🐱', '🎇', '🦈', '💖', '🅿️']
);
assert.isUndefined(
result.customizePreferredReactionsModal?.selectedDraftEmojiIndex
@ -221,30 +213,39 @@ describe('preferred reactions duck', () => {
describe('resetDraftEmoji', () => {
const { resetDraftEmoji } = actions;
function getAction(rootState: Readonly<StateType>) {
const dispatch = sinon.spy();
resetDraftEmoji()(dispatch, () => rootState, null);
const [action] = dispatch.getCall(0).args;
return action;
}
it('is a no-op if the customization modal is not open', () => {
const state = getInitialState();
const action = resetDraftEmoji();
const rootState = getEmptyRootState();
const state = rootState.preferredReactions;
const action = getAction(rootState);
const result = reducer(state, action);
assert.strictEqual(result, state);
});
it('resets the draft emoji to the defaults', () => {
const action = resetDraftEmoji();
const result = reducer(stateWithOpenCustomizationModal, action);
const rootState = getRootState(stateWithOpenCustomizationModal);
const action = getAction(rootState);
const result = reducer(rootState.preferredReactions, action);
assert.deepEqual(
result.customizePreferredReactionsModal?.draftPreferredReactions,
DEFAULT_PREFERRED_REACTION_EMOJI
['❤️', '👍', '👎', '😂', '😮', '😢']
);
});
it('deselects any selected emoji', () => {
const action = resetDraftEmoji();
const result = reducer(
stateWithOpenCustomizationModalAndSelectedEmoji,
action
const rootState = getRootState(
stateWithOpenCustomizationModalAndSelectedEmoji
);
const action = getAction(rootState);
const result = reducer(rootState.preferredReactions, action);
assert.isUndefined(
result.customizePreferredReactionsModal?.selectedDraftEmojiIndex

View file

@ -9,7 +9,6 @@ import {
} from '../../../state/selectors/items';
import type { StateType } from '../../../state/reducer';
import type { ItemsStateType } from '../../../state/ducks/items';
import { DEFAULT_PREFERRED_REACTION_EMOJI } from '../../../reactions/constants';
describe('both/state/selectors/items', () => {
// Note: we would like to use the full reducer here, to get a real empty state object
@ -74,30 +73,28 @@ describe('both/state/selectors/items', () => {
describe('#getPreferredReactionEmoji', () => {
// See also: the tests for the `getPreferredReactionEmoji` helper.
const expectedDefault = ['❤️', '👍🏿', '👎🏿', '😂', '😮', '😢'];
it('returns the default set if no value is stored', () => {
const state = getRootState({});
const state = getRootState({ skinTone: 5 });
const actual = getPreferredReactionEmoji(state);
assert.deepStrictEqual(actual, DEFAULT_PREFERRED_REACTION_EMOJI);
assert.deepStrictEqual(actual, expectedDefault);
});
it('returns the default set if the stored value is invalid', () => {
const state = getRootState({ preferredReactionEmoji: ['garbage!!'] });
const state = getRootState({
skinTone: 5,
preferredReactionEmoji: ['garbage!!'],
});
const actual = getPreferredReactionEmoji(state);
assert.deepStrictEqual(actual, DEFAULT_PREFERRED_REACTION_EMOJI);
assert.deepStrictEqual(actual, expectedDefault);
});
it('returns a custom set of emoji', () => {
const preferredReactionEmoji = [
'sparkles',
'sparkle',
'sparkler',
'shark',
'sparkling_heart',
'parking',
];
const state = getRootState({ preferredReactionEmoji });
const preferredReactionEmoji = ['✨', '❇️', '🤙🏻', '🦈', '💖', '🅿️'];
const state = getRootState({ skinTone: 5, preferredReactionEmoji });
const actual = getPreferredReactionEmoji(state);
assert.deepStrictEqual(actual, preferredReactionEmoji);

View file

@ -28,22 +28,8 @@ describe('both/state/selectors/preferredReactions', () => {
getIsCustomizingPreferredReactions(
getRootState({
customizePreferredReactionsModal: {
draftPreferredReactions: [
'sparkles',
'sparkle',
'sparkler',
'shark',
'sparkling_heart',
'parking',
],
originalPreferredReactions: [
'blue_heart',
'thumbsup',
'thumbsdown',
'joy',
'open_mouth',
'cry',
],
draftPreferredReactions: ['✨', '❇️', '🎇', '🦈', '💖', '🅿️'],
originalPreferredReactions: ['💙', '👍', '👎', '😂', '😮', '😢'],
selectedDraftEmojiIndex: undefined,
isSaving: false as const,
hadSaveError: false,

View file

@ -0,0 +1,11 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import * as RemoteConfig from '../RemoteConfig';
export function canCustomizePreferredReactions(): boolean {
return Boolean(
RemoteConfig.isEnabled('desktop.internalUser') ||
RemoteConfig.isEnabled('desktop.customizePreferredReactions')
);
}