Reorganize test cases

This commit is contained in:
trevor-signal
2025-06-26 12:24:07 -04:00
committed by GitHub
parent 3a745f2b6e
commit 843f545ceb
271 changed files with 236 additions and 245 deletions
File diff suppressed because it is too large Load Diff
+156
View File
@@ -0,0 +1,156 @@
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai';
import {
getAreWeASubscriber,
getEmojiSkinToneDefault,
getPinnedConversationIds,
getPreferredLeftPaneWidth,
getPreferredReactionEmoji,
} from '../../../state/selectors/items';
import type { StateType } from '../../../state/reducer';
import type { ItemsStateType } from '../../../state/ducks/items';
import {
EMOJI_SKIN_TONE_ORDER,
EmojiSkinTone,
} from '../../../components/fun/data/emojis';
describe('both/state/selectors/items', () => {
// Note: we would like to use the full reducer here, to get a real empty state object
// but we cannot load the full reducer inside of electron-mocha.
function getRootState(items: ItemsStateType): StateType {
return {
items,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any;
}
describe('#getAreWeASubscriber', () => {
it('returns false if the value is not in storage', () => {
assert.isFalse(getAreWeASubscriber(getRootState({})));
});
it('returns the value in storage', () => {
assert.isFalse(
getAreWeASubscriber(getRootState({ areWeASubscriber: false }))
);
assert.isTrue(
getAreWeASubscriber(getRootState({ areWeASubscriber: true }))
);
});
});
describe('#getEmojiSkinTone', () => {
it('returns null if passed anything invalid', () => {
[
// Invalid types
undefined,
null,
'2',
[2],
// Numbers out of range
-1,
6,
Infinity,
// Invalid numbers
0.1,
1.2,
NaN,
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- for testing
].forEach((emojiSkinToneDefault: any) => {
const state = getRootState({ emojiSkinToneDefault });
assert.strictEqual(getEmojiSkinToneDefault(state), null);
});
});
it('returns all valid skin tones', () => {
EMOJI_SKIN_TONE_ORDER.forEach(skinTone => {
const state = getRootState({ emojiSkinToneDefault: skinTone });
assert.strictEqual(getEmojiSkinToneDefault(state), skinTone);
});
});
});
describe('#getPreferredLeftPaneWidth', () => {
it('returns a default if no value is present', () => {
const state = getRootState({});
assert.strictEqual(getPreferredLeftPaneWidth(state), 320);
});
it('returns a default value if passed something invalid', () => {
[undefined, null, '250', [250], 250.123].forEach(
preferredLeftPaneWidth => {
const state = getRootState({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
preferredLeftPaneWidth: preferredLeftPaneWidth as any,
});
assert.strictEqual(getPreferredLeftPaneWidth(state), 320);
}
);
});
it('returns the value in storage if it is valid', () => {
const state = getRootState({
preferredLeftPaneWidth: 345,
});
assert.strictEqual(getPreferredLeftPaneWidth(state), 345);
});
});
describe('#getPinnedConversationIds', () => {
it('returns pinnedConversationIds key from items', () => {
const expected = ['one', 'two'];
const state: StateType = getRootState({
pinnedConversationIds: expected,
});
const actual = getPinnedConversationIds(state);
assert.deepEqual(actual, expected);
});
it('returns empty array if no saved data', () => {
const expected: Array<string> = [];
const state = getRootState({});
const actual = getPinnedConversationIds(state);
assert.deepEqual(actual, expected);
});
});
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({
emojiSkinToneDefault: EmojiSkinTone.Type5,
});
const actual = getPreferredReactionEmoji(state);
assert.deepStrictEqual(actual, expectedDefault);
});
it('returns the default set if the stored value is invalid', () => {
const state = getRootState({
emojiSkinToneDefault: EmojiSkinTone.Type5,
preferredReactionEmoji: ['garbage!!'],
});
const actual = getPreferredReactionEmoji(state);
assert.deepStrictEqual(actual, expectedDefault);
});
it('returns a custom set of emoji', () => {
const preferredReactionEmoji = ['✨', '❇️', '🤙🏻', '🦈', '💖', '🅿️'];
const state = getRootState({
emojiSkinToneDefault: EmojiSkinTone.Type5,
preferredReactionEmoji,
});
const actual = getPreferredReactionEmoji(state);
assert.deepStrictEqual(actual, preferredReactionEmoji);
});
});
});
@@ -0,0 +1,42 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai';
import { reducer as rootReducer } from '../../../state/reducer';
import { noopAction } from '../../../state/ducks/noop';
import type { StateType } from '../../../state/reducer';
import type { PreferredReactionsStateType } from '../../../state/ducks/preferredReactions';
import { getIsCustomizingPreferredReactions } from '../../../state/selectors/preferredReactions';
describe('both/state/selectors/preferredReactions', () => {
const getEmptyRootState = (): StateType =>
rootReducer(undefined, noopAction());
const getRootState = (preferredReactions: PreferredReactionsStateType) => ({
...getEmptyRootState(),
preferredReactions,
});
describe('getIsCustomizingPreferredReactions', () => {
it('returns false if the modal is closed', () => {
assert.isFalse(getIsCustomizingPreferredReactions(getEmptyRootState()));
});
it('returns true if the modal is open', () => {
assert.isTrue(
getIsCustomizingPreferredReactions(
getRootState({
customizePreferredReactionsModal: {
draftPreferredReactions: ['✨', '❇️', '🎇', '🦈', '💖', '🅿️'],
originalPreferredReactions: ['💙', '👍', '👎', '😂', '😮', '😢'],
selectedDraftEmojiIndex: undefined,
isSaving: false as const,
hadSaveError: false,
},
})
)
);
});
});
});
+527
View File
@@ -0,0 +1,527 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai';
import sinon from 'sinon';
import type {
ConversationType,
MessageType,
} from '../../../state/ducks/conversations';
import { getEmptyState as getEmptyConversationState } from '../../../state/ducks/conversations';
import { noopAction } from '../../../state/ducks/noop';
import type { MessageSearchResultType } from '../../../state/ducks/search';
import { getEmptyState as getEmptySearchState } from '../../../state/ducks/search';
import { getEmptyState as getEmptyUserState } from '../../../state/ducks/user';
import {
getIsSearching,
getIsSearchingGlobally,
getIsSearchingInAConversation,
getMessageSearchResultSelector,
getSearchResults,
} from '../../../state/selectors/search';
import { makeLookup } from '../../../util/makeLookup';
import { generateAci } from '../../../types/ServiceId';
import {
getDefaultConversation,
getDefaultConversationWithServiceId,
} from '../../../test-helpers/getDefaultConversation';
import { ReadStatus } from '../../../messages/MessageReadStatus';
import type { StateType } from '../../../state/reducer';
import { reducer as rootReducer } from '../../../state/reducer';
describe('both/state/selectors/search', () => {
const NOW = 1_000_000;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let clock: any;
beforeEach(() => {
clock = sinon.useFakeTimers({
now: NOW,
});
});
afterEach(() => {
clock.restore();
});
const getEmptyRootState = (): StateType => {
return rootReducer(undefined, noopAction());
};
function getDefaultMessage(id: string): MessageType {
return {
attachments: [],
conversationId: 'conversationId',
id,
received_at: NOW,
sent_at: NOW,
source: 'source',
sourceServiceId: generateAci(),
timestamp: NOW,
type: 'incoming' as const,
readStatus: ReadStatus.Read,
};
}
function getDefaultSearchMessage(id: string): MessageSearchResultType {
return {
...getDefaultMessage(id),
body: 'foo bar',
bodyRanges: [],
snippet: 'foo bar',
};
}
describe('#getIsSearchingInAConversation', () => {
it('returns false if not searching in a conversation', () => {
const state = getEmptyRootState();
assert.isFalse(getIsSearchingInAConversation(state));
});
it('returns true if searching in a conversation', () => {
const state = {
...getEmptyRootState(),
search: {
...getEmptySearchState(),
searchConversationId: 'abc123',
searchConversationName: 'Test Conversation',
},
};
assert.isTrue(getIsSearchingInAConversation(state));
});
});
describe('#getIsSearchingGlobally', () => {
it('returns false if not searching', () => {
const state = getEmptyRootState();
assert.isFalse(getIsSearchingGlobally(state));
});
it('returns true if searching globally', () => {
const state = {
...getEmptyRootState(),
search: {
...getEmptySearchState(),
globalSearch: true,
},
};
assert.isTrue(getIsSearchingGlobally(state));
});
});
describe('#getIsSearching', () => {
it('returns false if not searching in any manner', () => {
const state = getEmptyRootState();
assert.isFalse(getIsSearching(state));
});
it('returns true if searching in a conversation', () => {
const state = {
...getEmptyRootState(),
search: {
...getEmptySearchState(),
searchConversationId: 'abc123',
searchConversationName: 'Test Conversation',
globalSearch: false,
},
};
assert.isTrue(getIsSearching(state));
});
it('returns true if searching globally', () => {
const state = {
...getEmptyRootState(),
search: {
...getEmptySearchState(),
searchConversationId: undefined,
globalSearch: true,
},
};
assert.isTrue(getIsSearchingGlobally(state));
});
});
describe('#getMessageSearchResultSelector', () => {
it('returns undefined if message not found in lookup', () => {
const state = getEmptyRootState();
const selector = getMessageSearchResultSelector(state);
const actual = selector('random-id');
assert.strictEqual(actual, undefined);
});
it('returns undefined if type is unexpected', () => {
const id = 'message-id';
const state = {
...getEmptyRootState(),
search: {
...getEmptySearchState(),
messageLookup: {
[id]: {
...getDefaultMessage(id),
type: 'keychange' as const,
snippet: 'snippet',
body: 'snippet',
bodyRanges: [],
},
},
},
};
const selector = getMessageSearchResultSelector(state);
const actual = selector(id);
assert.strictEqual(actual, undefined);
});
it('returns incoming message', () => {
const searchId = 'search-id';
const toId = 'to-id';
const from = getDefaultConversationWithServiceId();
const to = getDefaultConversation({ id: toId });
const state = {
...getEmptyRootState(),
conversations: {
...getEmptyConversationState(),
conversationLookup: {
[from.id]: from,
[toId]: to,
},
conversationsByServiceId: {
[from.serviceId]: from,
},
},
search: {
...getEmptySearchState(),
messageLookup: {
[searchId]: {
...getDefaultMessage(searchId),
type: 'incoming' as const,
sourceServiceId: from.serviceId,
conversationId: toId,
snippet: 'snippet',
body: 'snippet',
bodyRanges: [],
},
},
},
};
const selector = getMessageSearchResultSelector(state);
const actual = selector(searchId);
const expected = {
from,
to,
id: searchId,
conversationId: toId,
sentAt: NOW,
snippet: 'snippet',
body: 'snippet',
bodyRanges: [],
isSelected: false,
isSearchingInConversation: false,
};
assert.deepEqual(actual, expected);
});
it('returns the correct "from" and "to" when sent to me', () => {
const searchId = 'search-id';
const myId = 'my-id';
const from = getDefaultConversationWithServiceId();
const toId = from.serviceId;
const meAsRecipient = getDefaultConversation({ id: myId });
const state = {
...getEmptyRootState(),
conversations: {
...getEmptyConversationState(),
conversationLookup: {
[from.id]: from,
[myId]: meAsRecipient,
},
conversationsByServiceId: {
[from.serviceId]: from,
},
},
ourConversationId: myId,
search: {
...getEmptySearchState(),
messageLookup: {
[searchId]: {
...getDefaultMessage(searchId),
type: 'incoming' as const,
sourceServiceId: from.serviceId,
conversationId: toId,
snippet: 'snippet',
body: 'snippet',
bodyRanges: [],
},
},
},
user: {
...getEmptyUserState(),
ourConversationId: myId,
},
};
const selector = getMessageSearchResultSelector(state);
const actual = selector(searchId);
assert.deepEqual(actual?.from, from);
assert.deepEqual(actual?.to, meAsRecipient);
});
it('returns outgoing message and caches appropriately', () => {
const searchId = 'search-id';
const toId = 'to-id';
const from = getDefaultConversationWithServiceId();
const to = getDefaultConversation({ id: toId });
const state = {
...getEmptyRootState(),
user: {
...getEmptyUserState(),
ourConversationId: from.id,
},
conversations: {
...getEmptyConversationState(),
conversationLookup: {
[from.id]: from,
[toId]: to,
},
conversationsByServiceId: {
[from.serviceId]: from,
},
},
search: {
...getEmptySearchState(),
messageLookup: {
[searchId]: {
...getDefaultMessage(searchId),
type: 'outgoing' as const,
conversationId: toId,
snippet: 'snippet',
body: 'snippet',
bodyRanges: [],
},
},
},
};
const selector = getMessageSearchResultSelector(state);
const actual = selector(searchId);
const expected = {
from,
to,
id: searchId,
conversationId: toId,
sentAt: NOW,
snippet: 'snippet',
body: 'snippet',
bodyRanges: [],
isSelected: false,
isSearchingInConversation: false,
};
assert.deepEqual(actual, expected);
// Update the conversation lookup, but not the conversations in question
const secondState = {
...state,
conversations: {
...state.conversations,
},
};
const secondSelector = getMessageSearchResultSelector(secondState);
const secondActual = secondSelector(searchId);
assert.strictEqual(secondActual, actual);
// Update a conversation involved in rendering this search result
const thirdState = {
...state,
conversations: {
...state.conversations,
conversationsByServiceId: {
...state.conversations.conversationsByServiceId,
[from.serviceId]: {
...from,
name: 'new-name',
},
},
},
};
const thirdSelector = getMessageSearchResultSelector(thirdState);
const thirdActual = thirdSelector(searchId);
assert.notStrictEqual(actual, thirdActual);
});
});
describe('#getSearchResults', () => {
it("returns loading search results when they're loading", () => {
const state = {
...getEmptyRootState(),
search: {
...getEmptySearchState(),
query: 'foo bar',
discussionsLoading: true,
messagesLoading: true,
},
};
assert.deepEqual(getSearchResults(state), {
conversationResults: { isLoading: true },
contactResults: { isLoading: true },
messageResults: { isLoading: true },
searchConversationName: undefined,
searchTerm: 'foo bar',
filterByUnread: false,
});
});
it('returns loaded search results', () => {
const conversations: Array<ConversationType> = [
getDefaultConversation({ id: '1' }),
getDefaultConversation({ id: '2' }),
];
const contacts: Array<ConversationType> = [
getDefaultConversation({ id: '3' }),
getDefaultConversation({ id: '4' }),
getDefaultConversation({ id: '5' }),
];
const messages: Array<MessageSearchResultType> = [
getDefaultSearchMessage('a'),
getDefaultSearchMessage('b'),
getDefaultSearchMessage('c'),
];
const getId = ({ id }: Readonly<{ id: string }>) => id;
const state: StateType = {
...getEmptyRootState(),
conversations: {
// This test state is invalid, but is good enough for this test.
...getEmptyConversationState(),
conversationLookup: makeLookup([...conversations, ...contacts], 'id'),
},
search: {
...getEmptySearchState(),
query: 'foo bar',
conversationIds: conversations.map(getId),
contactIds: contacts.map(getId),
messageIds: messages.map(getId),
messageLookup: makeLookup(messages, 'id'),
discussionsLoading: false,
messagesLoading: false,
},
};
assert.deepEqual(getSearchResults(state), {
conversationResults: {
isLoading: false,
results: conversations,
},
contactResults: {
isLoading: false,
results: contacts,
},
messageResults: {
isLoading: false,
results: messages,
},
searchConversationName: undefined,
searchTerm: 'foo bar',
filterByUnread: false,
});
});
it('adds isSelected flag to conversations when filterByUnread is true', () => {
const conversations: Array<ConversationType> = [
getDefaultConversation({ id: '1' }),
getDefaultConversation({ id: 'selected-id' }),
];
const state: StateType = {
...getEmptyRootState(),
conversations: {
...getEmptyConversationState(),
conversationLookup: makeLookup(conversations, 'id'),
selectedConversationId: 'selected-id',
},
search: {
...getEmptySearchState(),
query: 'foo bar',
conversationIds: conversations.map(({ id }) => id),
discussionsLoading: false,
filterByUnread: true,
},
};
const searchResults = getSearchResults(state);
assert.deepEqual(searchResults.conversationResults, {
isLoading: false,
results: [
{
...conversations[0],
isSelected: false,
},
{
...conversations[1],
isSelected: true,
},
],
});
});
it('does not add isSelected flag to conversations when filterByUnread is false', () => {
const conversations: Array<ConversationType> = [
getDefaultConversation({ id: '1' }),
getDefaultConversation({ id: '2' }),
];
const state: StateType = {
...getEmptyRootState(),
conversations: {
...getEmptyConversationState(),
conversationLookup: makeLookup(conversations, 'id'),
selectedConversationId: '2',
},
search: {
...getEmptySearchState(),
query: 'foo bar',
conversationIds: conversations.map(({ id }) => id),
discussionsLoading: false,
filterByUnread: false,
},
};
const searchResults = getSearchResults(state);
assert.deepEqual(searchResults.conversationResults, {
isLoading: false,
results: conversations,
});
});
});
});
+58
View File
@@ -0,0 +1,58 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai';
import type { StateType } from '../../../state/reducer';
import type { UserStateType } from '../../../state/ducks/user';
import { getEmptyState } from '../../../state/ducks/user';
import { getIsNightly, getIsBeta } from '../../../state/selectors/user';
describe('both/state/selectors/user', () => {
function getRootState(
overrides: Readonly<Partial<UserStateType>>
): StateType {
return {
user: {
...getEmptyState(),
...overrides,
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any;
}
describe('#getIsNightly', () => {
it('returns false for beta', () => {
const state = getRootState({ version: '1.23.4-beta.5' });
assert.isFalse(getIsNightly(state));
});
it('returns false for production', () => {
const state = getRootState({ version: '1.23.4' });
assert.isFalse(getIsNightly(state));
});
it('returns true for alpha', () => {
const state = getRootState({ version: '1.23.4-alpha.987' });
assert.isTrue(getIsNightly(state));
});
});
describe('#getIsBeta', () => {
it('returns false for alpha', () => {
const state = getRootState({ version: '1.23.4-alpha.987' });
assert.isFalse(getIsBeta(state));
});
it('returns false for production', () => {
const state = getRootState({ version: '1.23.4' });
assert.isFalse(getIsBeta(state));
});
it('returns true for beta', () => {
const state = getRootState({ version: '1.23.4-beta.5' });
assert.isTrue(getIsBeta(state));
});
});
});