status-desktop/storybook/qmlTests/TestComponents/tst_StatusChatInput.qml

727 lines
39 KiB
QML

import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQml 2.14
import QtTest 1.0
import utils 1.0
import shared.status 1.0
import shared.stores 1.0
import TextUtils 1.0
Item {
id: root
width: 600
height: 400
Component {
id: componentUnderTest
StatusChatInput {
property var globalUtils: globalUtilsMock
width: parent.width
height: implicitHeight
anchors.bottom: parent.bottom
usersStore: QtObject {
property var usersModel: ListModel {}
}
}
}
QtObject {
id: testData
readonly property var multiLineText: ["Typing on first row", "Typing on the second row", "Typing on the third row"]
}
TestCase {
name: "StatusChatInputInitialization"
when: windowShown
property StatusChatInput controlUnderTest: null
function init() {
controlUnderTest = createTemporaryObject(componentUnderTest, root)
}
//Scenario: StatusChatInput initialisation
//Given a new instance of StatusChatInput
//When there is no keyboard input
//Then the StatucChatInput raw text is empty
//But the StatusChatInput is configured to support Rich Text
function test_empty_chat_input() {
verify(controlUnderTest.textInput.length == 0, `Expected 0 text length, received: ${controlUnderTest.textInput.length}`)
verify(controlUnderTest.getPlainText() == "", `Expected empty string, received: ${controlUnderTest.getPlainText()}`)
verify(controlUnderTest.textInput.textFormat == TextEdit.RichText, "Expected text input format to be Rich")
verify(controlUnderTest.textInput.text.startsWith("<!DOCTYPE HTML PUBLIC"), "Expected text input format to be Rich")
}
}
TestCase {
id: statusChatInputKeyboardInputExpectedAsText
name: "StatusChatInputKeyboardInputExpectedAsText"
when: windowShown
property StatusChatInput controlUnderTest: null
function init() {
Utils.globalUtilsInst = globalUtilsMock
controlUnderTest = createTemporaryObject(componentUnderTest, root)
}
//Scenario: StatusChatInput will display any typed Ascii visible character
//Given a new instance of StatusChatInput
//When the user is typing any Ascii visible character as <text>
//Then the text is displayed as it is typed
//And the input text is not modified by mentions processor
//And the input text is not modified by emoji processor
//
//Example:
//text
//(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~)
function test_keyboard_any_ascii_input() {
var expectations_after_typed_text = (typedText) => {
verify(controlUnderTest.getPlainText() === typedText,
`Expected text: ${typedText}, received: ${controlUnderTest.getPlainText()}`)
verify(controlUnderTest.textInput.text === controlUnderTest.getTextWithPublicKeys(),
`Expected text: ${controlUnderTest.textInput.text}, received: ${controlUnderTest.getTextWithPublicKeys()}`)
}
testHelper.when_text_is_typed(statusChatInputKeyboardInputExpectedAsText,
testHelper.get_all_ascii_characters(),
expectations_after_typed_text);
}
// Scenario outline: User can navigate in chat input using keyboard
// Given the user has typed text on multiple lines in StatusChatInput
// """
// Typing on first row
// Typing on the second row
// Typing on the third row
//
// """
// When the user hits <direction>
// The cursor will move inside the text area to the <direction>
//
// Examples:
// |direction|
// |left|
// |right|
// |up|
// |down|
// When the user hits "left"
// The cursor will move inside the text area to the "left"
// When the user hits "right"
// The cursor will move inside the text area to the "right"
// When the user hits "up"
// The cursor will move "up" inside the text area, on the previous line if any
// When the user hits "down"
// The cursor will move "down" inside the text area, on the next line if any
function test_user_can_navigate_with_keys() {
var expect_row_to_be_added = (rowNb) => {
const expectedText = testData.multiLineText.slice(0, rowNb).join("\n") + "\n"
compare(controlUnderTest.getPlainText(), expectedText)
compare(controlUnderTest.textInput.lineCount, rowNb + 1)
}
testHelper.when_multiline_text_is_typed(statusChatInputKeyboardInputExpectedAsText,
testData.multiLineText,
expect_row_to_be_added,
(typedRowText) => {})
compare(controlUnderTest.textInput.cursorPosition,
controlUnderTest.textInput.length,
"Expected cursor position at the end of text")
keyClick(Qt.Key_Right)
compare(controlUnderTest.textInput.cursorPosition,
controlUnderTest.textInput.length,
"Expected cursor not to move as we already are at the end of text")
keyClick(Qt.Key_Left)
compare(controlUnderTest.textInput.cursorPosition,
controlUnderTest.textInput.length - 1,
"Expected cursor to move left")
keyClick(Qt.Key_Up)
compare(controlUnderTest.textInput.cursorPosition, 42,
"Expected cursor to be on the on the second row, two chars from the end")
keyClick(Qt.Key_Up)
compare(controlUnderTest.textInput.cursorPosition, 19,
"Expected cursor to be on the on end of the first line")
keyClick(Qt.Key_Down)
compare(controlUnderTest.textInput.cursorPosition,
42,
"Expected cursor to be back on the second row, two chars from the end")
keyClick(Qt.Key_Down)
compare(controlUnderTest.textInput.cursorPosition,
controlUnderTest.textInput.length - 1,
"Expected cursor to be back at the end of second line")
keyClick(Qt.Key_Down)
compare(controlUnderTest.textInput.cursorPosition,
controlUnderTest.textInput.length,
"Expected cursor to be back at the end of text")
}
// Scenario: User can select text using keyboard
// Given the user has typed text on multiple lines in StatusChatInput
// """
// Typing on first row
// Typing on the second row
// Typing on the third row
// """
// When the user holds shift key
// When the user hits <direction>
// The cursor will move inside the text area to the <direction>
// And the selected text will be <selectedText>
// Examples:
// |direction| selectedText |
// |right| "" |
// |left| "\u2028" |
// |up| "ow\u2028Typing on the third row\u2028" |
// |down| "\u2028" |
function test_user_can_select_text() {
testHelper.given_multiline_text_is_typed(statusChatInputKeyboardInputExpectedAsText,
testData.multiLineText)
keyClick(Qt.Key_Right, Qt.ShiftModifier)
compare(controlUnderTest.textInput.selectedText, "", "No selection expected")
keyClick(Qt.Key_Left, Qt.ShiftModifier)
compare(controlUnderTest.textInput.selectedText, "\u2028", "Expected line separator.")
keyClick(Qt.Key_Up, Qt.ShiftModifier)
compare(controlUnderTest.textInput.selectedText, "ow\u2028Typing on the third row\u2028")
keyClick(Qt.Key_Down, Qt.ShiftModifier)
compare(controlUnderTest.textInput.selectedText, "\u2028", "Expected line separator.")
}
// Scenario: The user can select all text in StatusChatInput
// Given the user has typed text in StatusChatInput
// """
// Typing on first row
// Typing on the second row
// Typing on the third row
// """
// When the user hits select all shortcut
// Then all the text is selected
function test_user_can_select_all_text() {
testHelper.given_multiline_text_is_typed(statusChatInputKeyboardInputExpectedAsText,
testData.multiLineText)
keySequence(StandardKey.SelectAll)
compare(controlUnderTest.textInput.selectedText, testData.multiLineText.join("\u2028") + "\u2028")
}
// Scenario: The user can cut text in StatusChatInput
// Given the user has selected all text in StatusChatInput
// """
// Typing on first row
// Typing on the second row
// Typing on the third row
// """
// When the user hits cut shortcut
// Then all selected text is removed
// And his clipboard contains the initially selected text
function test_user_can_cut_text() {
testHelper.given_multiline_text_is_typed(statusChatInputKeyboardInputExpectedAsText,
testData.multiLineText)
keySequence(StandardKey.SelectAll)
keySequence(StandardKey.Cut)
compare(controlUnderTest.getPlainText(), "")
keySequence(StandardKey.Paste)
compare(controlUnderTest.getPlainText(), testData.multiLineText.join("\n") + "\n")
}
// Given the user has contact JohnDoe
// And the user types a message "Hello @JohnDoe!"
// Then the user can mention his contact as @JohnDoe
// And mentions suggestions openes when "@" is typed
// And the mentions suggestions will contain @JohnDoe as the mention is typed
// And the mentions suggestions will close once "!" is typed
// And the mention is sepparated by the next input once the mention is added to TextArea
// And the contact name from the mention can be replaced with "'0x0JohnDoe'" public key
function test_keyboard_mentions_input() {
testHelper.when_the_user_has_contact(controlUnderTest, "JohnDoe", (contact) => {
verify(controlUnderTest.usersStore.usersModel.count == 1, `Expected user to have 1 contact`)
})
let expect_visible_suggestions_on_mention_typing = (typedText) => {
if(typedText.startsWith("Hello @") && !typedText.endsWith("!")) {
verify(controlUnderTest.suggestions.visible == true,
`Expected the mention suggestions to be visible`)
verify(controlUnderTest.suggestions.listView.currentItem.objectName === "JohnDoe",
`Expected the mention suggestions current item to be JohnDoe, received ${controlUnderTest.suggestions.listView.currentItem.objectName}`)
}
}
testHelper.when_text_is_typed(statusChatInputKeyboardInputExpectedAsText,
"Hello @JohnDoe!", expect_visible_suggestions_on_mention_typing)
verify(controlUnderTest.suggestions.visible == false,
`Expected the mention suggestions to be closed`)
compare(controlUnderTest.getPlainText(),
"Hello \[\[mention\]\]@JohnDoe\[\[mention\]\] !",
"Expected the mention to be inserted")
var textWithPubKey = controlUnderTest.getTextWithPublicKeys()
verify(textWithPubKey.includes("@0x0JohnDoe"),
"Expected @pubKey to replace @contactName")
}
}
TestCase {
id: statusChatInputStandardKeySequence
name: "StatusChatInputStandardKeySequence"
when: windowShown
property var controlUnderTest: null
property TextArea standardInput: null
Component {
id: standardTextAreaComponent
TextArea {
width: 400
height: 100
textFormat: Qt.RichText
selectByMouse: true
}
}
function initTestCase() {
Utils.globalUtilsInst = globalUtilsMock
}
// Scenario: Default TextArea keyboard shortcuts are not altered by StatusChatInput
// Given a new StatusChatInput instance
// And a new standard Qt TextArea instance
// When the user is typing multiline text
// """
// Typing on first row
// Typing on the second row
// Typing on the third row
// """
// And is using standard key shortcutss
// Then the StatusChatInput will behave as standard TextArea
function init() {
controlUnderTest = createTemporaryObject(componentUnderTest, root)
standardInput = createTemporaryObject(standardTextAreaComponent, root)
standardInput.forceActiveFocus()
testHelper.given_multiline_text_is_typed(statusChatInputStandardKeySequence, testData.multiLineText)
controlUnderTest.textInput.forceActiveFocus()
testHelper.given_multiline_text_is_typed(statusChatInputStandardKeySequence, testData.multiLineText)
}
function test_standard_key_shortcuts_data() {
return [
{ tag: "Delete", key: StandardKey.Delete, initialCursorPosition: 0, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true },
{ tag: "Undo", key: StandardKey.Undo, initialCursorPosition: 0, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true },
{ tag: "Redo", key: StandardKey.Redo, initialCursorPosition: 0, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true },
{ tag: "SelectAll", key: StandardKey.SelectAll, initialCursorPosition: 0, initialSelectionStart: 0, initialSelectionEnd: 0 , expectIdenticalPlainText: true },
{ tag: "Bold", key: StandardKey.Bold, initialCursorPosition: 0, initialSelectionStart: 0, initialSelectionEnd: 3, expectIdenticalPlainText: false, expectIdenticalSelection: false },
{ tag: "Italic", key: StandardKey.Italic, initialCursorPosition: 0, initialSelectionStart: 8, initialSelectionEnd: 10, expectIdenticalPlainText: false, expectIdenticalSelection: false },
{ tag: "MoveToNextChar", key: StandardKey.MoveToNextChar, initialCursorPosition: 0, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true },
{ tag: "MoveToPreviousChar", key: StandardKey.MoveToPreviousChar, initialCursorPosition: 5, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true },
{ tag: "MoveToNextWord", key: StandardKey.MoveToNextWord, initialCursorPosition: 2, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true},
{ tag: "MoveToPreviousWord", key: StandardKey.MoveToPreviousWord, initialCursorPosition: 15, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true},
{ tag: "MoveToNextLine", key: StandardKey.MoveToNextLine, initialCursorPosition: 0, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true},
{ tag: "MoveToPreviousLine", key: StandardKey.MoveToPreviousLine, initialCursorPosition: 30, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true},
{ tag: "MoveToStartOfLine", key: StandardKey.MoveToStartOfLine, initialCursorPosition: 10, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true },
{ tag: "MoveToEndOfLine", key: StandardKey.MoveToEndOfLine, initialCursorPosition: 0, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true },
{ tag: "MoveToStartOfBlock", key: StandardKey.MoveToStartOfBlock, initialCursorPosition: 40, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true },
{ tag: "MoveToEndOfBlock", key: StandardKey.MoveToEndOfBlock, initialCursorPosition: 0, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true },
{ tag: "MoveToStartOfDocument", key: StandardKey.MoveToStartOfDocument, initialCursorPosition: 40, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true },
{ tag: "MoveToEndOfDocument", key: StandardKey.MoveToEndOfDocument, initialCursorPosition: 0, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true },
{ tag: "SelectNextChar", key: StandardKey.SelectNextChar, initialCursorPosition: 5, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true },
{ tag: "SelectPreviousChar", key: StandardKey.SelectPreviousChar, initialCursorPosition: 5, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true },
{ tag: "SelectNextWord", key: StandardKey.SelectNextWord, initialCursorPosition: 2, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true },
{ tag: "SelectPreviousWord", key: StandardKey.SelectPreviousWord, initialCursorPosition: 15, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true },
{ tag: "SelectNextLine", key: StandardKey.SelectNextLine, initialCursorPosition: 0, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true },
{ tag: "SelectPreviousLine", key: StandardKey.SelectPreviousLine, initialCursorPosition: 30, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true },
{ tag: "SelectStartOfLine", key: StandardKey.SelectStartOfLine, initialCursorPosition: 0, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true },
{ tag: "SelectEndOfLine", key: StandardKey.SelectEndOfLine, initialCursorPosition: 0, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true },
{ tag: "SelectStartOfBlock", key: StandardKey.SelectStartOfBlock, initialCursorPosition: 40, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true },
{ tag: "SelectEndOfBlock", key: StandardKey.SelectEndOfBlock, initialCursorPosition: 0, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true },
{ tag: "SelectStartOfDocument", key: StandardKey.SelectStartOfDocument, initialCursorPosition: 40, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true },
{ tag: "SelectEndOfDocument", key: StandardKey.SelectEndOfDocument, initialCursorPosition: 0, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true },
{ tag: "DeleteStartOfWord", key: StandardKey.DeleteStartOfWord, initialCursorPosition: 4, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true },
{ tag: "DeleteEndOfWord", key: StandardKey.DeleteEndOfWord, initialCursorPosition: 4, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true },
{ tag: "DeleteEndOfLine", key: StandardKey.DeleteEndOfLine, initialCursorPosition: 0, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true },
{ tag: "InsertLineSeparator", key: StandardKey.InsertLineSeparator, initialCursorPosition: 0, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true },
{ tag: "Deselect", key: StandardKey.Deselect, initialCursorPosition: 0, initialSelectionStart: 0, initialSelectionEnd: 10, expectIdenticalPlainText: true },
{ tag: "DeleteCompleteLine", key: StandardKey.DeleteCompleteLine, initialCursorPosition: 0, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true },
{ tag: "Backspace", key: StandardKey.Backspace, initialCursorPosition: 5, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true },
{ tag: "AddTab", key: StandardKey.AddTab, initialCursorPosition: 0, initialSelectionStart: 0, initialSelectionEnd: 0, expectIdenticalPlainText: true }
]
}
function test_standard_key_shortcuts(data) {
standardInput.forceActiveFocus()
standardInput.cursorPosition = data.initialCursorPosition
standardInput.select(data.initialSelectionStart, data.initialSelectionEnd)
keySequence(data.key)
let standardControlSelection = standardInput.selectedText
let standardControlCursorPosition = standardInput.cursorPosition
controlUnderTest.textInput.forceActiveFocus()
controlUnderTest.textInput.cursorPosition = data.initialCursorPosition
controlUnderTest.textInput.select(data.initialSelectionStart, data.initialSelectionEnd)
keySequence(data.key)
let controlUnderTestSelection = controlUnderTest.textInput.selectedText
let controlUnderTestCursorPosition = controlUnderTest.textInput.cursorPosition
if(data.expectIdenticalPlainText) {
compare(controlUnderTest.textInput.length, standardInput.length, "Expected identical text length")
compare(controlUnderTest.textInput.getText(0, controlUnderTest.textInput.length),
standardInput.getText(0, standardInput.length),
"Expected identical text")
compare(controlUnderTestCursorPosition, standardControlCursorPosition, "Expected identical cursor position")
compare(controlUnderTestSelection, standardControlSelection, "Expected identical selected text")
} else {
//the text is expected to be different due to custom processor
//Ex: bold or italic where text is wrapped in specific symbols
verify(controlUnderTest.textInput.getText(0, controlUnderTest.textInput.length) !==
standardInput.getText(0, standardInput.length),
"Expected different text")
}
}
}
TestCase {
id: statusChatInputEmojiAndMentions
name: "StatusChatInputEmojiAndMentions"
when: windowShown
property StatusChatInput controlUnderTest: null
function init() {
Utils.globalUtilsInst = globalUtilsMock
controlUnderTest = createTemporaryObject(componentUnderTest, root)
}
// Scenario: The user can type text, mention and emoji
// Given a new instance of StatusChatInput
// And the user has <mention> as contact
// When the user is typing <text>
// Then the text is displayed in the input field as <expectedText>
// And the <mention> is inserted in the input field
// And the <emoji> is inserted in the input field
// And the <mention> can be replaced with contact public key
// Examples:
// text | mention | expectedPlainText
// “Hello John:D” | | “Hello John\uD83D\uDE04“
// “Hello @JohnDoe” | @JohnDoe | “Hello @JohnDoe ”
// “Hello:D@JohnDoe“ | @JohnDoe | “Hello\uD83D\uDE04 @JohnDoe “
// “:DHello@JohnDoe” | @JohnDoe | “\uD83D\uDE04 Hello @JohnDoe ”
// “:D:D:D:D:D:D@JohnDoe:D:D:D” | @JohnDoe | “\uD83D\uDE04 \uD83D\uDE04 \uD83D\uDE04 \uD83D\uDE04 \uD83D\uDE04 \uD83D\uDE04 @JohnDoe \uD83D\uDE04 \uD83D\uDE04 \uD83D\uDE04 ”
// “Hello:@JohnDoe1:D@JohnDoe2:D | @JohnDoe1 @JohnDoe2 | “Hello:@JohnDoe1 \uD83D\uDE04 @JohnDoe2 \uD83D\uDE04 ”
// “Hello:@JohnDoe1:D@JohnDoe2:D | | “Hello:@JohnDoe1\uD83D\uDE04 @JohnDoe2\uD83D\uDE04 ”
function test_text_mention_emoji_input_data() {
return [
{ tag: "Hello John:D", text: "Hello John:D", mention: [], expectedText: "Hello John\uD83D\uDE04 " },
{ tag: "Hello @JohnDoe", text: "Hello @JohnDoe", mention: ["JohnDoe"], expectedText: "Hello @JohnDoe " },
{ tag: "Hello:D@JohnDoe", text: "Hello:D@JohnDoe", mention: ["JohnDoe"], expectedText: "Hello\uD83D\uDE04 @JohnDoe " },
{ tag: ":DHello@JohnDoe", text: ":DHello@JohnDoe", mention: ["JohnDoe"], expectedText: "\uD83D\uDE04 Hello@JohnDoe " },
{ tag: ":D:D:D:D:D:D@JohnDoe:D:D:D", text: ":D:D:D:D:D:D@JohnDoe:D:D:D", mention: ["JohnDoe"], expectedText: "\uD83D\uDE04 \uD83D\uDE04 \uD83D\uDE04 \uD83D\uDE04 \uD83D\uDE04 \uD83D\uDE04 @JohnDoe \uD83D\uDE04 \uD83D\uDE04 \uD83D\uDE04 " },
{ tag: "Hello:@JohnDoe1:D@JohnDoe2:D with contact", text: "Hello @JohnDoe1:D@JohnDoe2:D", mention: ["JohnDoe1", "JohnDoe2"], expectedText: "Hello @JohnDoe1 \uD83D\uDE04 @JohnDoe2 \uD83D\uDE04 " },
{ tag: "Hello:@JohnDoe1:D@JohnDoe2:D without contact", text: "Hello @JohnDoe1:D@JohnDoe2:D", mention: [], expectedText: "Hello @JohnDoe1\uD83D\uDE04 @JohnDoe2\uD83D\uDE04 " },
]
}
function test_text_mention_emoji_input(data) {
data.mention.forEach(contact => testHelper.when_the_user_has_contact(controlUnderTest, contact, (addedContact) => {}))
testHelper.when_text_is_typed(statusChatInputEmojiAndMentions,
data.text, (typedText) => { keyClick(Qt.Key_Shift) /*mentions will be inserted only after a key input*/})
var plainText = controlUnderTest.removeMentions(controlUnderTest.getPlainText())
compare(plainText,
data.expectedText)
}
}
TestCase {
id: statusChatInputMentions
name: "StatusChatInputMentions"
when: windowShown
property StatusChatInput controlUnderTest: null
function init() {
Utils.globalUtilsInst = globalUtilsMock
controlUnderTest = createTemporaryObject(componentUnderTest, root)
}
//Scenario: Mention behaves like a single character when moving cursor by keyboard
//Given the user has contact <mention>
//And typed message <textInput>
//And has placed cursor at mention <initialPosition>
//When the user hits <key>
//The cursor moves to <expectedPosition>
//Example:
//| textInput | mention | initialPosition | key | expectedPosition |
//| Hello @JohnDoe! | | 6 | right | 7 |
//| Hello @JohnDoe! | | 14 | left | 13 |
//| Hello @JohnDoe! | JohnDoe | 6 | right | 14 |
//| Hello @JohnDoe! | JohnDoe | 14 | left | 6 |
function test_mention_is_skipped_by_cursor_data() {
return [
{tag: "MoveRightNoMention", textInput: "Hello @JohnDoe!", mention: "", initialPosition: 6, key: Qt.Key_Right, expectedPosition: 7},
{tag: "MoveLeftNoMention", textInput: "Hello @JohnDoe!", mention: "", initialPosition: 14, key: Qt.Key_Left, expectedPosition: 13},
{tag: "MoveRightWithMention", textInput: "Hello @JohnDoe!", mention: "JohnDoe", initialPosition: 6, key: Qt.Key_Right, expectedPosition: 14},
{tag: "MoveLeftWithMention", textInput: "Hello @JohnDoe!", mention: "JohnDoe", initialPosition: 14, key: Qt.Key_Left, expectedPosition: 6}
]
}
function test_mention_is_skipped_by_cursor(data) {
if(data.mention !== "") {
testHelper.when_the_user_has_contact(controlUnderTest, data.mention, (contact) => {})
}
testHelper.when_text_is_typed(statusChatInputMentions,
data.textInput, (typedText) => {})
controlUnderTest.textInput.cursorPosition = data.initialPosition
compare(controlUnderTest.textInput.cursorPosition, data.initialPosition)
keyClick(data.key)
compare(controlUnderTest.textInput.cursorPosition, data.expectedPosition)
}
//Scenario: Mention behaves like a single character when selecting by keyboard
//Given the user has contact <mention>
//And typed message <textInput>
//And has placed cursor at mention <initialPosition>
//When the user hits <key> holding Shift
//The selected text is <expectedSelection>
//Example:
//| textInput | mention | initialPosition | key | expectedSelection |
//| Hello @JohnDoe! | | 6 | right | @ |
//| Hello @JohnDoe! | | 14 | left | e |
//| Hello @JohnDoe! | JohnDoe | 6 | right | @JohnDoe |
//| Hello @JohnDoe! | JohnDoe | 14 | left | @JohnDoe |
function test_mention_is_selected_by_keyboard_data() {
return [
{tag: "SelectRightNoMention", textInput: "Hello @JohnDoe!", mention: "", initialPosition: 6, key: Qt.Key_Right, expectedSelection: "@"},
{tag: "SelectLeftNoMention", textInput: "Hello @JohnDoe!", mention: "", initialPosition: 14, key: Qt.Key_Left, expectedSelection: "e"},
{tag: "SelectRightWithMention", textInput: "Hello @JohnDoe!", mention: "JohnDoe", initialPosition: 6, key: Qt.Key_Right, expectedSelection: "@JohnDoe"},
{tag: "SelectLeftWithMention", textInput: "Hello @JohnDoe!", mention: "JohnDoe", initialPosition: 14, key: Qt.Key_Left, expectedSelection: "@JohnDoe"}
]
}
function test_mention_is_selected_by_keyboard(data) {
if(data.mention !== "") {
testHelper.when_the_user_has_contact(controlUnderTest, data.mention, (contact) => {})
}
testHelper.when_text_is_typed(statusChatInputMentions,
data.textInput, (typedText) => {})
controlUnderTest.textInput.cursorPosition = data.initialPosition
compare(controlUnderTest.textInput.cursorPosition, data.initialPosition)
keyClick(data.key, Qt.ShiftModifier)
compare(controlUnderTest.textInput.selectedText, data.expectedSelection)
}
//Scenario: Clicking mention will select the mention
//Given the user has contact JohnDoe
//And has typed message Hello @JohnDoe!
//When the user clicks @JohnDoe text
//Then the text @JohnDoe is selected
function test_mention_click_is_selecting_mention() {
testHelper.when_the_user_has_contact(controlUnderTest, "JohnDoe", (contact) => {})
testHelper.when_text_is_typed(statusChatInputMentions,
"Hello @JohnDoe!", (typedText) => {})
controlUnderTest.textInput.cursorPosition = 6
const cursorRectangle = controlUnderTest.textInput.cursorRectangle
mouseClick(controlUnderTest.textInput, cursorRectangle.x + 5, controlUnderTest.textInput.height / 2)
compare(controlUnderTest.textInput.selectedText,
"@JohnDoe")
}
//Scenario: Mention cannot be invalidated by user actions
//Given the user has contact JohnDoe
//And has typed message Hello @JohnDoe!
//When the user is performing <actions>
//And hits enter
//Then the mention is still valid
//And can be replaced with publicKey
//Example:
//|Action|
//|The space after mention is deleted and mention suggestion is closed key left|
//|The space after mention is deleted and mention suggestion is closed key "S"|
function test_mention_cannot_be_invalidated() {
testHelper.when_the_user_has_contact(controlUnderTest, "JohnDoe", (contact) => {})
testHelper.when_text_is_typed(statusChatInputMentions,
"Hello @JohnDoe!", (typedText) => {})
controlUnderTest.textInput.cursorPosition = 15
keyClick(Qt.Key_Backspace)
compare(controlUnderTest.textInput.getText(0, controlUnderTest.textInput.length), "Hello @JohnDoe!")
keyClick(Qt.Key_Left)
compare(controlUnderTest.textInput.getText(0, controlUnderTest.textInput.length), "Hello @JohnDoe !")
var plainTextWithPubKey = TextUtils.htmlToPlainText(controlUnderTest.getTextWithPublicKeys())
compare(plainTextWithPubKey, "Hello @0x0JohnDoe !")
controlUnderTest.textInput.cursorPosition = 15
keyClick(Qt.Key_Backspace)
compare(controlUnderTest.textInput.getText(0, controlUnderTest.textInput.length), "Hello @JohnDoe!")
keyClick(Qt.Key_S)
plainTextWithPubKey = TextUtils.htmlToPlainText(controlUnderTest.getTextWithPublicKeys())
compare(plainTextWithPubKey, "Hello @0x0JohnDoe s!")
}
//Scenario: User can remove mention by replacing a larger selected text section with a letter
//Given the user has contact JohnDoe
//And has typed "Hello @JohnDoe!"
//And has selected "lo @JohnDoe !"
//And has typed "s"
//Then the text is "Hells"
//And the mention is removed
function test_mention_is_deleted_with_large_selection() {
testHelper.when_the_user_has_contact(controlUnderTest, "JohnDoe", (contact) => {})
testHelper.when_text_is_typed(statusChatInputMentions,
"Hello @JohnDoe!", (typedText) => {})
controlUnderTest.textInput.select(3, 16)
compare(controlUnderTest.textInput.selectedText,
"lo @JohnDoe !")
keyClick(Qt.Key_S)
compare(controlUnderTest.textInput.getText(0, controlUnderTest.textInput.length),
"Hels")
const plainTextWithPubKey = TextUtils.htmlToPlainText(controlUnderTest.getTextWithPublicKeys())
compare(plainTextWithPubKey,
"Hels")
}
}
QtObject {
id: testHelper
function get_all_ascii_characters() {
let result = '';
for( let i = 32; i <= 126; i++ )
{
result += String.fromCharCode( i );
}
return result
}
function when_the_user_has_contact(controlUnderTest: StatusChatInput, contact: string, expectationAfterContactAdded) {
controlUnderTest.usersStore.usersModel.append({
pubKey: `0x0${contact}`,
onlineStatus: 1,
isContact: true,
isVerified: true,
isAdmin: false,
isUntrustworthy: false,
displayName: contact,
alias: `${contact}-alias`,
localNickname: `${contact}-local-nickname`,
ensName: `${contact}.stateofus.eth`,
icon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAlklEQVR4nOzW0QmDQBAG4SSkl7SUQlJGCrElq9F3QdjjVhh/5nv3cFhY9vUIYQiNITSG0BhCExPynn1gWf9bx498P7/
nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2ImYgiNITTlTdG1nUZ5a92VITQxITFiJmIIjSE0htAYQrMHAAD//+wwFVpz+yqXAAAAAElFTkSuQmCC",
colorId: 7
})
expectationAfterContactAdded(contact)
}
function when_text_is_typed(testCase: TestCase, textInput: string, expectationAfterTextInput) {
for (let i = 0; i < textInput.length; i++) {
const typedText = textInput.slice(0,i+1)
const key = textInput[i];
testCase.keyClick(key)
expectationAfterTextInput(typedText)
}
}
function when_multiline_text_is_typed(testCase: TestCase, textLines: list<string>, expectationAfterNewLine, expectationAfterTextInput) {
for(let i = 0; i < textLines.length; i++) {
when_text_is_typed(testCase, textLines[i], expectationAfterTextInput)
testCase.keyClick(Qt.Key_Enter, Qt.ShiftModifier)
expectationAfterNewLine(i+1)
}
}
function given_multiline_text_is_typed(testCase: TestCase, textLines: list<string>) {
when_multiline_text_is_typed(testCase, textLines, (lineNb) => {}, (typedText) => {})
}
}
QtObject {
id: globalUtilsMock
function plainText(htmlText) {
return TextUtils.htmlToPlainText(htmlText)
}
function isCompressedPubKey(publicKey) {
return false
}
}
QtObject {
id: rootStoreMock
property ListModel gifColumnA: ListModel {}
readonly property var formationChars: (["*", "`", "~"])
property bool isTenorWarningAccepted: true
function getSelectedTextWithFormationChars(messageInputField) {
let i = 1
let text = ""
while (true) {
if (messageInputField.selectionStart - i < 0 && messageInputField.selectionEnd + i > messageInputField.length) {
break
}
text = messageInputField.getText(messageInputField.selectionStart - i, messageInputField.selectionEnd + i)
if (!formationChars.includes(text.charAt(0)) ||
!formationChars.includes(text.charAt(text.length - 1))) {
break
}
i++
}
return text
}
Component.onCompleted: {
RootStore.isGifWidgetEnabled = true
RootStore.isWalletEnabled = true
RootStore.isTenorWarningAccepted = rootStoreMock.isTenorWarningAccepted
RootStore.getSelectedTextWithFormationChars = rootStoreMock.getSelectedTextWithFormationChars
RootStore.gifColumnA = rootStoreMock.gifColumnA
Global.dragArea = root
}
}
}