signal-desktop/ts/test-electron/quill/mentions/completion_test.tsx

255 lines
7.1 KiB
TypeScript
Raw Normal View History

// Copyright 2020-2021 Signal Messenger, LLC
2020-11-03 01:19:52 +00:00
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai';
import Delta from 'quill-delta';
import type { SinonStub } from 'sinon';
import sinon from 'sinon';
import type { Quill, KeyboardStatic } from 'quill';
2020-11-03 01:19:52 +00:00
import type { MutableRefObject } from 'react';
import type { MentionCompletionOptions } from '../../../quill/mentions/completion';
import { MentionCompletion } from '../../../quill/mentions/completion';
import type { ConversationType } from '../../../state/ducks/conversations';
2020-11-04 22:04:48 +00:00
import { MemberRepository } from '../../../quill/memberRepository';
2021-11-17 18:38:52 +00:00
import { ThemeType } from '../../../types/Util';
2021-10-26 22:59:08 +00:00
import { getDefaultConversationWithUuid } from '../../../test-both/helpers/getDefaultConversation';
2020-11-03 01:19:52 +00:00
2021-10-26 22:59:08 +00:00
const me: ConversationType = getDefaultConversationWithUuid({
2020-11-03 01:19:52 +00:00
id: '666777',
title: 'Fred Savage',
firstName: 'Fred',
profileName: 'Fred S.',
type: 'direct',
lastUpdated: Date.now(),
markedUnread: false,
areWeAdmin: false,
2021-05-07 22:21:10 +00:00
isMe: true,
});
2020-11-03 01:19:52 +00:00
const members: Array<ConversationType> = [
2021-10-26 22:59:08 +00:00
getDefaultConversationWithUuid({
2020-11-03 01:19:52 +00:00
id: '555444',
title: 'Mahershala Ali',
firstName: 'Mahershala',
profileName: 'Mahershala A.',
type: 'direct',
lastUpdated: Date.now(),
markedUnread: false,
areWeAdmin: false,
2021-05-07 22:21:10 +00:00
}),
2021-10-26 22:59:08 +00:00
getDefaultConversationWithUuid({
2020-11-03 01:19:52 +00:00
id: '333222',
title: 'Shia LaBeouf',
firstName: 'Shia',
profileName: 'Shia L.',
type: 'direct',
lastUpdated: Date.now(),
markedUnread: false,
areWeAdmin: false,
2021-05-07 22:21:10 +00:00
}),
2020-11-03 01:19:52 +00:00
me,
];
describe('MentionCompletion', () => {
let mockQuill: Omit<
Partial<{ [K in keyof Quill]: SinonStub }>,
'keyboard'
> & {
keyboard: Partial<{ [K in keyof KeyboardStatic]: SinonStub }>;
};
let mentionCompletion: MentionCompletion;
2020-11-03 01:19:52 +00:00
beforeEach(function beforeEach() {
const memberRepositoryRef: MutableRefObject<MemberRepository> = {
current: new MemberRepository(members),
};
const options: MentionCompletionOptions = {
2021-11-17 18:38:52 +00:00
getPreferredBadge: () => undefined,
i18n: Object.assign(sinon.stub(), {
getLocale: sinon.stub(),
getIntl: sinon.stub(),
isLegacyFormat: sinon.stub(),
}),
2020-11-03 01:19:52 +00:00
me,
memberRepositoryRef,
2021-01-04 18:28:32 +00:00
setMentionPickerElement: sinon.stub(),
2021-11-17 18:38:52 +00:00
theme: ThemeType.dark,
2020-11-03 01:19:52 +00:00
};
mockQuill = {
getContents: sinon.stub(),
getLeaf: sinon.stub(),
getSelection: sinon.stub(),
keyboard: { addBinding: sinon.stub() },
on: sinon.stub(),
setSelection: sinon.stub(),
updateContents: sinon.stub(),
};
2020-11-03 01:19:52 +00:00
mentionCompletion = new MentionCompletion(
2021-11-11 22:43:05 +00:00
mockQuill as unknown as Quill,
options
);
2020-11-03 01:19:52 +00:00
sinon.stub(mentionCompletion, 'render');
2020-11-03 01:19:52 +00:00
});
describe('onTextChange', () => {
let possiblyShowMemberResultsStub: sinon.SinonStub<
[],
ReadonlyArray<ConversationType>
>;
2020-11-03 01:19:52 +00:00
beforeEach(() => {
possiblyShowMemberResultsStub = sinon.stub(
mentionCompletion,
'possiblyShowMemberResults'
);
2020-11-03 01:19:52 +00:00
});
describe('given a change that should show members', () => {
const newContents = new Delta().insert('@a');
2020-11-03 01:19:52 +00:00
beforeEach(() => {
mockQuill.getContents?.returns(newContents);
2020-11-03 01:19:52 +00:00
possiblyShowMemberResultsStub.returns(members);
2020-11-03 01:19:52 +00:00
});
it('shows member results', () => {
2020-11-03 01:19:52 +00:00
mentionCompletion.onTextChange();
assert.equal(mentionCompletion.results, members);
assert.equal(mentionCompletion.index, 0);
2020-11-03 01:19:52 +00:00
});
});
describe('given a change that should clear results', () => {
const newContents = new Delta().insert('foo ');
2020-11-03 01:19:52 +00:00
let clearResultsStub: SinonStub<[], void>;
2020-11-03 01:19:52 +00:00
beforeEach(() => {
mentionCompletion.results = members;
2020-11-03 01:19:52 +00:00
mockQuill.getContents?.returns(newContents);
2020-11-03 01:19:52 +00:00
possiblyShowMemberResultsStub.returns([]);
2020-11-03 01:19:52 +00:00
clearResultsStub = sinon.stub(mentionCompletion, 'clearResults');
});
2020-11-03 01:19:52 +00:00
it('clears member results', () => {
2020-11-03 01:19:52 +00:00
mentionCompletion.onTextChange();
assert.equal(clearResultsStub.called, true);
2020-11-03 01:19:52 +00:00
});
});
});
describe('completeMention', () => {
describe('given a completable mention', () => {
let insertMentionStub: SinonStub<
[ConversationType, number, number, (boolean | undefined)?],
void
>;
beforeEach(() => {
mentionCompletion.results = members;
mockQuill.getSelection?.returns({ index: 5 });
mockQuill.getLeaf?.returns([{ text: '@shia' }, 5]);
insertMentionStub = sinon.stub(mentionCompletion, 'insertMention');
2020-11-03 01:19:52 +00:00
});
it('inserts the currently selected mention at the current cursor position', () => {
mentionCompletion.completeMention(1);
const [
member,
distanceFromCursor,
adjustCursorAfterBy,
withTrailingSpace,
] = insertMentionStub.getCall(0).args;
assert.equal(member, members[1]);
assert.equal(distanceFromCursor, 0);
assert.equal(adjustCursorAfterBy, 5);
assert.equal(withTrailingSpace, true);
2020-11-03 01:19:52 +00:00
});
it('can infer the member to complete with', () => {
mentionCompletion.index = 1;
2020-11-03 01:19:52 +00:00
mentionCompletion.completeMention();
const [
member,
distanceFromCursor,
adjustCursorAfterBy,
withTrailingSpace,
] = insertMentionStub.getCall(0).args;
assert.equal(member, members[1]);
assert.equal(distanceFromCursor, 0);
assert.equal(adjustCursorAfterBy, 5);
assert.equal(withTrailingSpace, true);
2020-11-03 01:19:52 +00:00
});
describe('from the middle of a string', () => {
beforeEach(() => {
mockQuill.getSelection?.returns({ index: 9 });
mockQuill.getLeaf?.returns([{ text: 'foo @shia bar' }, 9]);
2020-11-03 01:19:52 +00:00
});
it('inserts correctly', () => {
mentionCompletion.completeMention(1);
2020-11-03 01:19:52 +00:00
const [
member,
distanceFromCursor,
adjustCursorAfterBy,
withTrailingSpace,
] = insertMentionStub.getCall(0).args;
2020-11-03 01:19:52 +00:00
assert.equal(member, members[1]);
assert.equal(distanceFromCursor, 4);
assert.equal(adjustCursorAfterBy, 5);
assert.equal(withTrailingSpace, true);
});
2020-11-03 01:19:52 +00:00
});
describe('given a completable mention starting with a capital letter', () => {
const text = '@Sh';
const index = text.length;
2020-11-03 01:19:52 +00:00
beforeEach(function beforeEach() {
mockQuill.getSelection?.returns({ index });
2020-11-03 01:19:52 +00:00
const blot = {
text,
};
mockQuill.getLeaf?.returns([blot, index]);
2020-11-03 01:19:52 +00:00
mentionCompletion.completeMention(1);
});
2020-11-03 01:19:52 +00:00
it('inserts the currently selected mention at the current cursor position', () => {
const [
member,
distanceFromCursor,
adjustCursorAfterBy,
withTrailingSpace,
] = insertMentionStub.getCall(0).args;
assert.equal(member, members[1]);
assert.equal(distanceFromCursor, 0);
assert.equal(adjustCursorAfterBy, 3);
assert.equal(withTrailingSpace, true);
});
2020-11-03 01:19:52 +00:00
});
});
});
});