Read and write preferred reactions to storage

This commit is contained in:
Evan Hahn 2021-09-15 13:59:51 -05:00 committed by GitHub
parent 4e3b64ef64
commit 20be8a11fe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 208 additions and 71 deletions

View file

@ -1,43 +0,0 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai';
import { DEFAULT_PREFERRED_REACTION_EMOJI_SHORT_NAMES } from '../../reactions/constants';
import { getPreferredReactionEmoji } from '../../reactions/getPreferredReactionEmoji';
describe('getPreferredReactionEmoji', () => {
it('returns the default set if passed anything invalid', () => {
[
// Invalid types
undefined,
null,
DEFAULT_PREFERRED_REACTION_EMOJI_SHORT_NAMES.join(','),
// Invalid lengths
[],
DEFAULT_PREFERRED_REACTION_EMOJI_SHORT_NAMES.slice(0, 3),
[...DEFAULT_PREFERRED_REACTION_EMOJI_SHORT_NAMES, '✨'],
// Non-strings in the array
['❤️', '👍', undefined, '😂', '😮', '😢'],
['❤️', '👍', 99, '😂', '😮', '😢'],
// Invalid emoji
['❤️', '👍', 'x', '😂', '😮', '😢'],
['❤️', '👍', 'garbage!!', '😂', '😮', '😢'],
['❤️', '👍', '✨✨', '😂', '😮', '😢'],
].forEach(input => {
assert.deepStrictEqual(getPreferredReactionEmoji(input, 2), [
'❤️',
'👍🏼',
'👎🏼',
'😂',
'😮',
'😢',
]);
});
});
it('returns a custom set if passed a valid value', () => {
const input = ['✨', '❇️', '🎇', '🦈', '💖', '🅿️'];
assert.deepStrictEqual(getPreferredReactionEmoji(input, 3), input);
});
});

View file

@ -0,0 +1,82 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai';
import {
canBeSynced,
getPreferredReactionEmoji,
} from '../../reactions/preferredReactionEmoji';
describe('preferred reaction emoji utilities', () => {
describe('getPreferredReactionEmoji', () => {
const defaultsForSkinTone2 = ['❤️', '👍🏼', '👎🏼', '😂', '😮', '😢'];
it('returns the default set if passed a non-array', () => {
[undefined, null, '❤️👍🏼👎🏼😂😮😢'].forEach(input => {
assert.deepStrictEqual(
getPreferredReactionEmoji(input, 2),
defaultsForSkinTone2
);
});
});
it('returns the default set if passed an empty array', () => {
assert.deepStrictEqual(
getPreferredReactionEmoji([], 2),
defaultsForSkinTone2
);
});
it('falls back to defaults if passed an array that is too short', () => {
const input = ['✨', '❇️'];
const expected = ['✨', '❇️', '👎🏽', '😂', '😮', '😢'];
assert.deepStrictEqual(getPreferredReactionEmoji(input, 3), expected);
});
it('falls back to defaults when passed an array with some invalid values', () => {
const input = ['✨', 'invalid', '🎇', '🦈', undefined, ''];
const expected = ['✨', '👍🏼', '🎇', '🦈', '😮', '😢'];
assert.deepStrictEqual(getPreferredReactionEmoji(input, 2), expected);
});
it('returns a custom set if passed a valid value', () => {
const input = ['✨', '❇️', '🎇', '🦈', '💖', '🅿️'];
assert.deepStrictEqual(getPreferredReactionEmoji(input, 3), input);
});
it('only returns the first few emoji if passed a value that is too long', () => {
const expected = ['✨', '❇️', '🎇', '🦈', '💖', '🅿️'];
const input = [...expected, '💅', '💅', '💅', '💅'];
assert.deepStrictEqual(getPreferredReactionEmoji(input, 3), expected);
});
});
describe('canBeSynced', () => {
it('returns false for non-arrays', () => {
assert.isFalse(canBeSynced(undefined));
assert.isFalse(canBeSynced(null));
assert.isFalse(canBeSynced('❤️👍🏼👎🏼😂😮😢'));
});
it('returns false for arrays that are too long', () => {
assert.isFalse(canBeSynced(Array(21).fill('🦊')));
});
it('returns false for arrays that have items that are too long', () => {
const input = ['✨', '❇️', 'x'.repeat(21), '🦈', '💖', '🅿️'];
assert.isFalse(canBeSynced(input));
});
it('returns true for valid values', () => {
[
[],
['💅'],
['✨', '❇️', '🎇', '🦈', '💖', '🅿️'],
['this', 'array', 'has', 'no', 'emoji', 'but', "that's", 'okay'],
].forEach(input => {
assert.isTrue(canBeSynced(input));
});
});
});
});

View file

@ -243,13 +243,33 @@ describe('preferred reactions duck', () => {
describe('savePreferredReactions', () => {
const { savePreferredReactions } = actions;
// We want to create a fake ConversationController for testing purposes, and we need
// to sidestep typechecking to do that.
/* eslint-disable @typescript-eslint/no-explicit-any */
let storagePutStub: sinon.SinonStub;
let captureChangeStub: sinon.SinonStub;
let oldConversationController: any;
beforeEach(() => {
storagePutStub = sinonSandbox.stub(window.storage, 'put').resolves();
oldConversationController = window.ConversationController;
captureChangeStub = sinonSandbox.stub();
window.ConversationController = {
getOurConversationOrThrow: (): any => ({
captureChange: captureChangeStub,
}),
} as any;
});
afterEach(() => {
window.ConversationController = oldConversationController;
});
describe('thunk', () => {
it('saves the preferred reaction emoji to storage', async () => {
it('saves the preferred reaction emoji to local storage', async () => {
await savePreferredReactions()(
sinon.spy(),
() => getRootState(stateWithOpenCustomizationModal),
@ -264,6 +284,16 @@ describe('preferred reactions duck', () => {
);
});
it('on success, enqueues a storage service upload', async () => {
await savePreferredReactions()(
sinon.spy(),
() => getRootState(stateWithOpenCustomizationModal),
null
);
sinon.assert.calledOnce(captureChangeStub);
});
it('on success, dispatches a pending action followed by a fulfilled action', async () => {
const dispatch = sinon.spy();
await savePreferredReactions()(
@ -299,6 +329,18 @@ describe('preferred reactions duck', () => {
type: 'preferredReactions/SAVE_PREFERRED_REACTIONS_REJECTED',
});
});
it('on failure, does not enqueue a storage service upload', async () => {
storagePutStub.rejects(new Error('something went wrong'));
await savePreferredReactions()(
sinon.spy(),
() => getRootState(stateWithOpenCustomizationModal),
null
);
sinon.assert.notCalled(captureChangeStub);
});
});
describe('SAVE_PREFERRED_REACTIONS_FULFILLED', () => {