Skip to content
This repository has been archived by the owner on Sep 27, 2024. It is now read-only.

Fix inline predictive text and keyboard suggestion toolbar use cases. #890

Merged
merged 62 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
5239d95
Move from reconciliate to committed/uncommitted text.
langleyd Nov 24, 2023
817a9b1
Push uncommited changes.
langleyd Nov 24, 2023
0eb0577
Handle double space to dot conversion as the system does not do this …
langleyd Nov 24, 2023
3517e84
Merge branch 'main' of github.com:matrix-org/matrix-rich-text-editor …
langleyd Jun 12, 2024
b447ad1
Merge main and fix crash checkForDoubleSpaceToDotConversion
langleyd Jun 18, 2024
56c24e3
Put back reconciliate but don't use it if the diff is just latin char…
langleyd Jun 19, 2024
ba64d68
Remove comment
langleyd Jun 19, 2024
2a37312
Fix typos and move withNBSP
langleyd Jun 19, 2024
e1e3e94
Fix plain text mode.
langleyd Jun 19, 2024
a0e22cc
Fix selection + tests
langleyd Jun 19, 2024
fc41527
Merge branch 'main' of github.com:matrix-org/matrix-rich-text-editor …
langleyd Jul 5, 2024
3b49f18
Fix test that falls into the lastReplaceTextUpdate if statement in Wy…
langleyd Jul 7, 2024
c418d63
Fix clearing of plain text.
langleyd Jul 8, 2024
d0a6acc
Fix dictation, by correcting character check for reconciliate.
langleyd Jul 8, 2024
00e9667
added some tests
Velin92 Jul 10, 2024
9d538c6
added a script to force software keyboard
Velin92 Jul 10, 2024
2a15124
improve tests
Velin92 Jul 10, 2024
209c73a
fixed the tests
Velin92 Jul 10, 2024
c524832
test fix
Velin92 Jul 10, 2024
84bb1d0
added a test for deleting when text prediction is present
Velin92 Jul 10, 2024
865f967
improved the tests
Velin92 Jul 10, 2024
7314be8
better docs in tests
Velin92 Jul 10, 2024
fda08e0
improved the test
Velin92 Jul 10, 2024
e637ecb
Add comment to object replacement character test
langleyd Jul 11, 2024
eb70a6c
Add documentation for the replaceText return value
langleyd Jul 11, 2024
f9d1592
Fix naming of functions, clearer control flow and and re-enable cyclo…
langleyd Jul 11, 2024
df0275c
Add logging line and correct replaceText logic
langleyd Jul 12, 2024
c2a4783
Add another failing test for predictive text.
langleyd Jul 12, 2024
deb7c2c
keyboards based testing
Velin92 Jul 12, 2024
51b2090
moved the keyboard setup
Velin92 Jul 12, 2024
b143872
print the added keyboards
Velin92 Jul 12, 2024
0c93ce0
rework dot conversion and handle case of dot after an inline text pre…
langleyd Jul 12, 2024
49893ef
Merge branch 'langleyd/fix_predictive_text_and_suggestions' of github…
langleyd Jul 12, 2024
2f97a0e
moved the setup to the earliest possible
Velin92 Jul 12, 2024
f815b1c
Merge branch 'langleyd/fix_predictive_text_and_suggestions' of https:…
Velin92 Jul 12, 2024
9961ceb
fix
Velin92 Jul 12, 2024
c9c9e86
removed the use of the keyboard language change for now
Velin92 Jul 12, 2024
2f6e24b
restored keyboard based tests
Velin92 Jul 12, 2024
eb264df
adding language during the test itself!
Velin92 Jul 12, 2024
31d32b3
sped up the tests a bit
Velin92 Jul 12, 2024
9baac8e
better docs
Velin92 Jul 12, 2024
44edc08
Moving the dot back after predictive text was introduced in 17.5
langleyd Jul 12, 2024
ee0795e
Fix test.
langleyd Jul 12, 2024
d5bc8bd
Merge branch 'langleyd/fix_predictive_text_and_suggestions' into maur…
Velin92 Jul 12, 2024
13f6cda
better docs and improved the code
Velin92 Jul 15, 2024
bf53631
Merge branch 'mauroromito/cjk_testing' of https://github.com/matrix-o…
Velin92 Jul 15, 2024
81d7d8f
Merge pull request #1015 from matrix-org/mauroromito/cjk_testing
Velin92 Jul 15, 2024
ad8d163
fix
Velin92 Jul 15, 2024
1f06539
fix
Velin92 Jul 15, 2024
c7cd881
fix
Velin92 Jul 15, 2024
a961188
revert
Velin92 Jul 15, 2024
10942c6
added some delay for the CI
Velin92 Jul 15, 2024
282f746
fix cyclomatic_complexity check
langleyd Jul 15, 2024
dbafe83
Merge branch 'langleyd/fix_predictive_text_and_suggestions' of github…
langleyd Jul 15, 2024
9e810c8
possible fix by delay
Velin92 Jul 15, 2024
a499178
Merge branch 'langleyd/fix_predictive_text_and_suggestions' of https:…
Velin92 Jul 15, 2024
3fc7e64
more delay
Velin92 Jul 15, 2024
8f2a68b
possible fix
Velin92 Jul 15, 2024
0045c3b
disabled for now
Velin92 Jul 15, 2024
7868e2e
disabled for now
Velin92 Jul 15, 2024
f11702e
restored
Velin92 Jul 15, 2024
14f1889
improved the test code
Velin92 Jul 15, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/ios.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ jobs:

steps:
- uses: actions/checkout@v4

- name: Setup keyboards
working-directory: platforms/ios/example
run: exec ./ios-keyboards-setup.sh

- name: Install xcresultparser
run: brew install a7ex/homebrew-formulae/xcresultparser
Expand Down Expand Up @@ -69,7 +73,7 @@ jobs:
flags: unittests-ios, unittests
# https://github.com/codecov/codecov-action/issues/557#issuecomment-1216749652
token: ${{ secrets.CODECOV_TOKEN }}

- name: UI test coverage
working-directory: platforms/ios/example
run: exec ./ios-ui-test-coverage.sh
Expand Down
23 changes: 23 additions & 0 deletions platforms/ios/example/Wysiwyg.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
A6F4D0CF29AE0C1500087A3E /* Users.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6F4D0CE29AE0C1500087A3E /* Users.swift */; };
A6F4D0D129AE0C3200087A3E /* Rooms.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6F4D0D029AE0C3200087A3E /* Rooms.swift */; };
A6F4D0D329AE0C5100087A3E /* Commands.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6F4D0D229AE0C5100087A3E /* Commands.swift */; };
A75C6AD22C3E989D0096D3A4 /* WysiwygUITests+Keyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = A75C6AD12C3E989D0096D3A4 /* WysiwygUITests+Keyboard.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -97,6 +98,7 @@
A6F4D0CE29AE0C1500087A3E /* Users.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Users.swift; sourceTree = "<group>"; };
A6F4D0D029AE0C3200087A3E /* Rooms.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Rooms.swift; sourceTree = "<group>"; };
A6F4D0D229AE0C5100087A3E /* Commands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Commands.swift; sourceTree = "<group>"; };
A75C6AD12C3E989D0096D3A4 /* WysiwygUITests+Keyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WysiwygUITests+Keyboard.swift"; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -178,6 +180,7 @@
A64AB145296C759A00F08494 /* WysiwygUITests+Quotes.swift */,
A661FDA629B0ACB400E799A6 /* WysiwygUITests+Suggestions.swift */,
A64AB13D296C732500F08494 /* WysiwygUITests+Typing.swift */,
A75C6AD12C3E989D0096D3A4 /* WysiwygUITests+Keyboard.swift */,
);
path = WysiwygUITests;
sourceTree = "<group>";
Expand Down Expand Up @@ -280,6 +283,7 @@
isa = PBXNativeTarget;
buildConfigurationList = A6472CD12886CF840021A0E8 /* Build configuration list for PBXNativeTarget "WysiwygUITests" */;
buildPhases = (
A75C6AD32C3ECA450096D3A4 /* Force software keyboard on simulator */,
A6472CBD2886CF840021A0E8 /* Sources */,
A6472CBE2886CF840021A0E8 /* Frameworks */,
A6472CBF2886CF840021A0E8 /* Resources */,
Expand Down Expand Up @@ -392,6 +396,24 @@
shellPath = /bin/sh;
shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\nif which swiftformat >/dev/null; then\n swiftformat $PROJECT_DIR\n swiftformat ../lib/WysiwygComposer\nelse\n echo \"warning: SwiftFormat not installed, download from https://github.com/nicklockwood/SwiftFormat\"\nfi\n";
};
A75C6AD32C3ECA450096D3A4 /* Force software keyboard on simulator */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Force software keyboard on simulator";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "external_kb_connected=false\n\nosascript -e 'quit app \"Simulator\"'\n\nSIMUS_KEYBOARD=$(/usr/libexec/PlistBuddy -c \"Print :DevicePreferences\" ~/Library/Preferences/com.apple.iphonesimulator.plist | perl -lne 'print $1 if /^ (\\S*) =/')\n\necho \"$SIMUS_KEYBOARD\" | while read -r a; do /usr/libexec/PlistBuddy -c \"Set :DevicePreferences:$a:ConnectHardwareKeyboard $external_kb_connected\" ~/Library/Preferences/com.apple.iphonesimulator.plist || /usr/libexec/PlistBuddy -c \"Add :DevicePreferences:$a:ConnectHardwareKeyboard bool $external_kb_connected\" ~/Library/Preferences/com.apple.iphonesimulator.plist; done\n";
Velin92 marked this conversation as resolved.
Show resolved Hide resolved
};
/* End PBXShellScriptBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
Expand Down Expand Up @@ -432,6 +454,7 @@
A6BB18D729F9191C00EB6366 /* WysiwygUITests+Autocorrection.swift in Sources */,
A6472CC62886CF840021A0E8 /* WysiwygUITests.swift in Sources */,
A64AB140296C73CE00F08494 /* WysiwygUITests+Links.swift in Sources */,
A75C6AD22C3E989D0096D3A4 /* WysiwygUITests+Keyboard.swift in Sources */,
A661FDA729B0ACB400E799A6 /* WysiwygUITests+Suggestions.swift in Sources */,
A68E7141291D40710023CC04 /* WysiwygSharedConstants.swift in Sources */,
A64AB144296C747C00F08494 /* WysiwygUITests+Format.swift in Sources */,
Expand Down
172 changes: 172 additions & 0 deletions platforms/ios/example/WysiwygUITests/WysiwygUITests+Keyboard.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
//
// Copyright 2024 The Matrix.org Foundation C.I.C
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import XCTest

// These tests work on the assunmption that we always have the software keyboard enabled
// which is handled through the run script
// and that the simulator is configured with the following 2 keyboards: English (US) and Japanese Kana
// Such configuration is done through a script called ios-keyboard-setup.sh but can also be done manually
extension WysiwygUITests {
func testInlinePredictiveText() {
sleep(3)
// app.setupKeyboardLanguage(named: "English (US)")

// Sometimes autocorrection can break capitalisation, so we need to make sure the first letter is lowercase
app.keyboards.buttons["shift"].tap()
app.typeTextCharByCharUsingKeyboard("hello how a")
// We assert both the tree and textview content because the text view is containing the predictive text at that moment
// Which in the ui test is seen as part of the static text
assertTextViewContent("hello how are you")
assertTreeEquals(
"""
└>"hello how a"
"""
)
app.keys["space"].tap()
assertTextViewContent("hello how are you ")
assertTreeEquals(
"""
└>"hello how are you "
"""
)
}

func testInlinePredictiveTextIsIgnoredWhenSending() {
sleep(3)
// app.setupKeyboardLanguage(named: "English (US)")

// Sometimes autocorrection can break capitalisation, so we need to make sure the first letter is lowercase
app.keyboards.buttons["shift"].tap()
app.typeTextCharByCharUsingKeyboard("hello how")
// We assert both the tree and textview content because the text view is containing the predictive text at that moment
// Which in the ui test is seen as part of the static text
assertTextViewContent("hello how are you")
assertTreeEquals(
"""
└>"hello how"
"""
)
button(.sendButton).tap()
sleep(1)
assertContentText(plainText: "hello how", htmlText: "hello how")
}

func testInlinePredictiveTextIsIgnoredWhenDeleting() {
sleep(3)
// app.setupKeyboardLanguage(named: "English (US)")

// Sometimes autocorrection can break capitalisation, so we need to make sure the first letter is lowercase
app.keyboards.buttons["shift"].tap()
app.typeTextCharByCharUsingKeyboard("hello how")
app.keys["delete"].tap()
// We assert both the tree and textview content because the text view is containing the predictive text at that moment
// Which in the ui test is seen as part of the static text
assertTextViewContent("hello how are you")
assertTreeEquals(
"""
└>"hello ho"
"""
)
button(.sendButton).tap()
sleep(1)
assertContentText(plainText: "hello ho", htmlText: "hello ho")
}

func testDoubleSpaceIntoDot() {
sleep(3)
// app.setupKeyboardLanguage(named: "English (US)")

// Sometimes autocorrection can break capitalisation, so we need to make sure the first letter is lowercase
app.keyboards.buttons["shift"].tap()
app.typeTextCharByCharUsingKeyboard("hello")
app.keys["space"].tap()
app.keys["space"].tap()
assertTextViewContent("hello. ")
assertTreeEquals(
"""
└>"hello. "
"""
)
}

func testDotAfterInlinePredictiveText() {
sleep(3)
// app.setupKeyboardLanguage(named: "English (US)")

// Sometimes autocorrection can break capitalisation, so we need to make sure the first letter is lowercase
app.keyboards.buttons["shift"].tap()
app.typeTextCharByCharUsingKeyboard("hello how a")
// We assert both the tree and textview content because the text view is containing the predictive text at that moment
// Which in the ui test is seen as part of the static text
assertTextViewContent("hello how are you")
app.keys["space"].tap()
app.keys["more"].tap()
app.keys["."].tap()

// This optimisation to predictive inline text was introduced in 17.5
let correctText: String
if #available(iOS 17.5, *) {
correctText = "hello how are you."
} else {
correctText = "hello how are you ."
}
assertTextViewContent(correctText)
// In the failure case a second dot is added in the tree.
assertTreeEquals(
"""
└>"\(correctText)"
"""
)
}

// func testJapaneseKanaDeletion() {
// sleep(3)
// app.setupKeyboardLanguage(named: "日本語かな")
//
// app.typeTextCharByCharUsingKeyboard("は")
// assertTextViewContent("は")
// assertTreeEquals(
// """
// └>"は"
// """
// )
// app.keys["delete"].tap()
// assertTextViewContent("")
// XCTAssertEqual(staticText(.treeText).label, "\n")
// }
}

private extension XCUIApplication {
func typeTextCharByCharUsingKeyboard(_ text: String) {
for char in text {
if char == " " {
keys["space"].tap()
continue
}
keys[String(char)].tap()
}
}

func setupKeyboardLanguage(named language: String) {
let nextKeyboard = buttons["Next keyboard"]
while let value = nextKeyboard.value as? String,
value != language {
nextKeyboard.tap()
}
nextKeyboard.tap()
}
}
5 changes: 5 additions & 0 deletions platforms/ios/example/WysiwygUITests/WysiwygUITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@ extension WysiwygUITests {
XCTAssertTrue(pill.exists)
XCTAssertEqual(pill.label, displayName)
}

func assertContentText(plainText: String, htmlText: String) {
XCTAssert(staticText(.contentText).label == plainText)
XCTAssert(staticText(.htmlContentText).label == htmlText)
}
}

extension XCUIElement {
Expand Down
27 changes: 27 additions & 0 deletions platforms/ios/example/ios-keyboards-setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/bin/bash

# Get the list of available simulators
simulators=$(xcrun simctl list devices 17.2)

# Find the current booted simulator
test_simulator=$(echo "$simulators" | grep "iPhone 15 (" | awk -F '[()]' '{print $2}')

echo "Found simulator: $test_simulator"

# Define the path to the preferences plist file for the booted simulator
plist_path=~/Library/Developer/CoreSimulator/Devices/$test_simulator/data/Library/Preferences/.GlobalPreferences.plist
echo "Path: $plist_path"

# Check if the plist file exists
if [ ! -f "$plist_path" ]; then
echo "Preferences plist file not found: $plist_path"
exit 1
fi

# Clear the current keyboard layouts
/usr/libexec/PlistBuddy -c "Delete :AppleKeyboards" "$plist_path" 2>/dev/null
/usr/libexec/PlistBuddy -c "Add :AppleKeyboards array" "$plist_path"
/usr/libexec/PlistBuddy -c "Add :AppleKeyboards:0 string 'en_US@sw=QWERTY;hw=Automatic'" "$plist_path"
/usr/libexec/PlistBuddy -c "Add :AppleKeyboards:1 string 'ja_JP-Kana@sw=Kana;hw=Automatic'" "$plist_path"

/usr/libexec/PlistBuddy -c "Print :AppleKeyboards array" "$plist_path" 2>/dev/null
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,8 @@ extension NSMutableAttributedString {
/// - Returns: self (discardable)
@discardableResult
func removeDiscardableContent() -> Self {
discardableTextRanges().reversed().forEach {
replaceCharacters(in: $0, with: "")
for discardableTextRange in discardableTextRanges().reversed() {
replaceCharacters(in: discardableTextRange, with: "")
}

return self
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ public struct WysiwygComposerView: View {

@ViewBuilder
private var placeholderView: some View {
if viewModel.isContentEmpty, !viewModel.textView.isDictationRunning {
// The content can be empty but the textview not, e.g. if you start dictation
// but have not committed the text yet.
if viewModel.textView.attributedText.length == 0 {
Text(placeholder)
.font(Font(UIFont.preferredFont(forTextStyle: .body)))
.foregroundColor(placeholderColor)
Expand Down Expand Up @@ -191,7 +193,10 @@ struct UITextViewWrapper: UIViewRepresentable {
textView.logText,
"Replacement: \"\(text)\""],
functionName: #function)
return replaceText(range, text)
let change = replaceText(range, text)
Logger.textView.logDebug(["change: \(change)"],
functionName: #function)
return change
}

func textViewDidChange(_ textView: UITextView) {
Expand Down
Loading
Loading