diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ea209248..01947d3e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,7 @@ jobs: - uses: actions/checkout@v2 - name: Build and Test Proton run: xcodebuild -resultBundlePath TestResults -scheme "Proton" -destination "platform=iOS Simulator,name=iPhone 14 Pro,OS=16.4" clean test - - name: Compile Test results + - name: Compile Test results uses: kishikawakatsumi/xcresulttool@v1 with: path: TestResults.xcresult diff --git a/Proton/Proton.xcodeproj/project.pbxproj b/Proton/Proton.xcodeproj/project.pbxproj index e17463b8..a329fae1 100644 --- a/Proton/Proton.xcodeproj/project.pbxproj +++ b/Proton/Proton.xcodeproj/project.pbxproj @@ -139,6 +139,8 @@ 1BFFEF1C23C334D200D2BA35 /* InlineEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BFFEF1B23C334D200D2BA35 /* InlineEditorView.swift */; }; 1BFFEF1F23C3366200D2BA35 /* MockAttachmentOffsetProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BFFEF1E23C3366200D2BA35 /* MockAttachmentOffsetProvider.swift */; }; 1BFFEF2123C33EA900D2BA35 /* PanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BFFEF2023C33EA900D2BA35 /* PanelView.swift */; }; + 7D0C8C6B2ACFD56100D2F5D1 /* MockKeyboardPress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D0C8C6A2ACFD56100D2F5D1 /* MockKeyboardPress.swift */; }; + 7D0C8C6D2AD25D4A00D2F5D1 /* EditorKeyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D0C8C6C2AD25D4A00D2F5D1 /* EditorKeyTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -307,6 +309,8 @@ 1BFFEF1B23C334D200D2BA35 /* InlineEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineEditorView.swift; sourceTree = ""; }; 1BFFEF1E23C3366200D2BA35 /* MockAttachmentOffsetProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAttachmentOffsetProvider.swift; sourceTree = ""; }; 1BFFEF2023C33EA900D2BA35 /* PanelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PanelView.swift; sourceTree = ""; }; + 7D0C8C6A2ACFD56100D2F5D1 /* MockKeyboardPress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockKeyboardPress.swift; sourceTree = ""; }; + 7D0C8C6C2AD25D4A00D2F5D1 /* EditorKeyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorKeyTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -460,6 +464,7 @@ 1B45CDDA23C04BE3001EB196 /* RichTextViewSnapshotTests.swift */, 1B4B60C5247F692D002B63CF /* ListsSnapshotTests.swift */, 1BFDC810254AA11F00BD83BD /* ListParserTests.swift */, + 7D0C8C6C2AD25D4A00D2F5D1 /* EditorKeyTests.swift */, ); path = Core; sourceTree = ""; @@ -487,6 +492,7 @@ 1B82570323C48C350033A0A9 /* MockRichTextViewDelegate.swift */, 1B30A3632489E11F00FA1D48 /* MockRichTextViewListDelegate.swift */, 1B30A3612489DC7B00FA1D48 /* MockListFormattingProvider.swift */, + 7D0C8C6A2ACFD56100D2F5D1 /* MockKeyboardPress.swift */, ); path = Mocks; sourceTree = ""; @@ -1094,6 +1100,7 @@ 1BFFEF1723C333B400D2BA35 /* EditorTestViewController.swift in Sources */, 1B45CDCE23BF18A5001EB196 /* XCTestHelpers.swift in Sources */, 1B45CDDB23C04BE3001EB196 /* RichTextViewSnapshotTests.swift in Sources */, + 7D0C8C6B2ACFD56100D2F5D1 /* MockKeyboardPress.swift in Sources */, 1B2BC0D823CF17E300407DEE /* EditorContentTransformerTests.swift in Sources */, 1B45CDD923C0484A001EB196 /* RichTextViewTests.swift in Sources */, 1BC25B542448768200FF88AC /* EditorContextDelegateTests.swift in Sources */, @@ -1108,6 +1115,7 @@ 1B45CDCB23BF1621001EB196 /* MockDefaultTextFormattingProvider.swift in Sources */, 1BD185D0284D839C001F4FBC /* GridViewAttachmentSnapshotTests.swift in Sources */, 1B45CDA923BEED0E001EB196 /* AutogrowingTextViewTests.swift in Sources */, + 7D0C8C6D2AD25D4A00D2F5D1 /* EditorKeyTests.swift in Sources */, 1BD185BF284C2A66001F4FBC /* GridTests.swift in Sources */, 1B3D19CA2483484A00B3AB59 /* EditorListsSnapshotTests.swift in Sources */, 1B183D9223CEEED900AE83E5 /* EditorContentEncoderTests.swift in Sources */, diff --git a/Proton/Sources/Swift/Core/RichTextView.swift b/Proton/Sources/Swift/Core/RichTextView.swift index 21b9f920..db579b98 100644 --- a/Proton/Sources/Swift/Core/RichTextView.swift +++ b/Proton/Sources/Swift/Core/RichTextView.swift @@ -117,6 +117,21 @@ class RichTextView: AutogrowingTextView { getNestedEditors(for: self) } + override public func pressesBegan(_ presses: Set, with event: UIPressesEvent?) { + guard #available(iOS 13.4, *) else { return } + var handled: Bool = false + presses.forEach { press in + guard + let key = press.key, + let editorKey = EditorKey(key.charactersIgnoringModifiers) + else { return } + richTextViewDelegate?.richTextView(self, shouldHandle: editorKey, modifierFlags: key.modifierFlags, at: self.selectedRange, handled: &handled) + } + if handled == false { + super.pressesBegan(presses, with: event) + } + } + private func getNestedEditors(for containerView: UIView) -> [RichTextView] { var textViews = [RichTextView]() for view in containerView.subviews { diff --git a/Proton/Sources/Swift/Core/RichTextViewDelegate.swift b/Proton/Sources/Swift/Core/RichTextViewDelegate.swift index 1ab46de2..9a3af962 100644 --- a/Proton/Sources/Swift/Core/RichTextViewDelegate.swift +++ b/Proton/Sources/Swift/Core/RichTextViewDelegate.swift @@ -25,6 +25,10 @@ public enum EditorKey { case enter case backspace case tab + case left + case right + case up + case down init?(_ string: String) { switch string { @@ -32,6 +36,14 @@ public enum EditorKey { self = .tab case "\n", "\r": self = .enter + case UIKeyCommand.inputUpArrow: + self = .up + case UIKeyCommand.inputDownArrow: + self = .down + case UIKeyCommand.inputLeftArrow: + self = .left + case UIKeyCommand.inputRightArrow: + self = .right default: return nil } diff --git a/Proton/Sources/Swift/Editor/EditorView.swift b/Proton/Sources/Swift/Editor/EditorView.swift index a8552db2..30afaa1a 100644 --- a/Proton/Sources/Swift/Editor/EditorView.swift +++ b/Proton/Sources/Swift/Editor/EditorView.swift @@ -232,6 +232,10 @@ open class EditorView: UIView { set { richTextView.inputView = newValue } } + open override var isFirstResponder: Bool { + richTextView.isFirstResponder + } + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/Proton/Sources/Swift/TextProcessors/TextProcessors/ListTextProcessor.swift b/Proton/Sources/Swift/TextProcessors/TextProcessors/ListTextProcessor.swift index 830295ba..358cafbc 100644 --- a/Proton/Sources/Swift/TextProcessors/TextProcessors/ListTextProcessor.swift +++ b/Proton/Sources/Swift/TextProcessors/TextProcessors/ListTextProcessor.swift @@ -97,6 +97,9 @@ public class ListTextProcessor: TextProcessing { else { return } editor.deleteBackward() + + default: + break } } diff --git a/Proton/Tests/Core/EditorKeyTests.swift b/Proton/Tests/Core/EditorKeyTests.swift new file mode 100644 index 00000000..a1b819d7 --- /dev/null +++ b/Proton/Tests/Core/EditorKeyTests.swift @@ -0,0 +1,53 @@ +// +// EditorKeyTests.swift +// ProtonTests +// +// Created by Hon Thi on 8/10/2023. +// Copyright © 2023 Rajdeep Kwatra. All rights reserved. +// +// 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 +@testable import Proton + +final class EditorKeyTests: XCTestCase { + func test_ReturnsTab() { + XCTAssertEqual(EditorKey("\t"), EditorKey.tab) + } + + func test_ReturnsEnter() { + XCTAssertEqual(EditorKey("\r"), EditorKey.enter) + XCTAssertEqual(EditorKey("\n"), EditorKey.enter) + } + + func test_ReturnsUp() { + XCTAssertEqual(EditorKey(UIKeyCommand.inputUpArrow), EditorKey.up) + } + + func test_ReturnsDown() { + XCTAssertEqual(EditorKey(UIKeyCommand.inputDownArrow), EditorKey.down) + } + + func test_ReturnsLeft() { + XCTAssertEqual(EditorKey(UIKeyCommand.inputLeftArrow), EditorKey.left) + } + + func test_ReturnsRight() { + XCTAssertEqual(EditorKey(UIKeyCommand.inputRightArrow), EditorKey.right) + } + + func testReturnsNil() { + XCTAssertNil(EditorKey("any")) + } +} diff --git a/Proton/Tests/Core/Mocks/MockKeyboardPress.swift b/Proton/Tests/Core/Mocks/MockKeyboardPress.swift new file mode 100644 index 00000000..26f031bc --- /dev/null +++ b/Proton/Tests/Core/Mocks/MockKeyboardPress.swift @@ -0,0 +1,48 @@ +// +// MockKeyboardPress.swift +// ProtonTests +// +// Created by Hon Thi on 6/10/2023. +// Copyright © 2023 Rajdeep Kwatra. All rights reserved. +// +// 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 UIKit + +@available(iOS 13.4, *) +class MockUIPress: UIPress { + private let _characters: String + override var key: UIKey? { + MockUIKey(characters: _characters) + } + + init(characters: String) { + self._characters = characters + } +} + +@available(iOS 13.4, *) +class MockUIKey: UIKey { + private let _characters: String + override var charactersIgnoringModifiers: String { _characters } + + init(characters: String) { + self._characters = characters + super.init() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Proton/Tests/Core/RichTextViewContextTests.swift b/Proton/Tests/Core/RichTextViewContextTests.swift index 4321fdeb..b2f9bb35 100644 --- a/Proton/Tests/Core/RichTextViewContextTests.swift +++ b/Proton/Tests/Core/RichTextViewContextTests.swift @@ -158,6 +158,30 @@ class RichTextViewContextTests: XCTestCase { waitForExpectations(timeout: 1.0) } + func testReceiveRightKey() { + guard #available(iOS 13.4, *) else { return } + let testExpectation = expectation(description: #function) + let mockTextViewDelegate = MockRichTextViewDelegate() + + let context = RichTextEditorContext.default + let textView = RichTextView(context: context) + textView.richTextViewDelegate = mockTextViewDelegate + context.textViewDidBeginEditing(textView) + textView.text = "Sample text" + + let selectedRange = NSRange.zero + textView.selectedRange = selectedRange + + mockTextViewDelegate.onShouldHandleKey = { _, key, _, _, _ in + XCTAssertEqual(key, EditorKey.right) + testExpectation.fulfill() + } + + let uiPresses: Set = [MockUIPress(characters: UIKeyCommand.inputRightArrow)] + textView.pressesBegan(uiPresses, with: nil) + waitForExpectations(timeout: 1.0) + } + func testInvokesTextDidChange() { let testExpectation = expectation(description: #function) let mockTextViewDelegate = MockRichTextViewDelegate()