From be3e8af96035522703b214e19774a643944cb689 Mon Sep 17 00:00:00 2001 From: Hon Thi Date: Mon, 9 Oct 2023 14:35:20 +1100 Subject: [PATCH 1/7] Support directional key commands --- Proton/Proton.xcodeproj/project.pbxproj | 8 +++ Proton/Sources/Swift/Core/RichTextView.swift | 15 ++++++ .../Swift/Core/RichTextViewDelegate.swift | 12 +++++ Proton/Sources/Swift/Editor/EditorView.swift | 4 ++ .../TextProcessors/ListTextProcessor.swift | 3 ++ Proton/Tests/Core/EditorKeyTests.swift | 53 +++++++++++++++++++ .../Tests/Core/Mocks/MockKeyboardPress.swift | 48 +++++++++++++++++ .../Tests/Core/RichTextViewContextTests.swift | 23 ++++++++ 8 files changed, 166 insertions(+) create mode 100644 Proton/Tests/Core/EditorKeyTests.swift create mode 100644 Proton/Tests/Core/Mocks/MockKeyboardPress.swift 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..b16d9bdc --- /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 { + var _characters: String + override var key: UIKey? { + MockUIKey(characters: _characters) + } + + init(characters: String) { + self._characters = characters + } +} + +@available(iOS 13.4, *) +class MockUIKey: UIKey { + var _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..bef5a5d7 100644 --- a/Proton/Tests/Core/RichTextViewContextTests.swift +++ b/Proton/Tests/Core/RichTextViewContextTests.swift @@ -158,6 +158,29 @@ 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, _, range, _ in + XCTAssertEqual(key, EditorKey.right) + testExpectation.fulfill() + } + + textView.pressesBegan(Set(arrayLiteral: MockUIPress(characters: UIKeyCommand.inputRightArrow)), with: nil) + waitForExpectations(timeout: 1.0) + } + func testInvokesTextDidChange() { let testExpectation = expectation(description: #function) let mockTextViewDelegate = MockRichTextViewDelegate() From be3fede5afe81ed6e089f6f5339dac0d129a36e0 Mon Sep 17 00:00:00 2001 From: Hon Thi Date: Mon, 9 Oct 2023 14:53:39 +1100 Subject: [PATCH 2/7] Fix build failures --- Proton/Tests/Core/Mocks/MockKeyboardPress.swift | 4 ++-- Proton/Tests/Core/RichTextViewContextTests.swift | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Proton/Tests/Core/Mocks/MockKeyboardPress.swift b/Proton/Tests/Core/Mocks/MockKeyboardPress.swift index b16d9bdc..26f031bc 100644 --- a/Proton/Tests/Core/Mocks/MockKeyboardPress.swift +++ b/Proton/Tests/Core/Mocks/MockKeyboardPress.swift @@ -22,7 +22,7 @@ import UIKit @available(iOS 13.4, *) class MockUIPress: UIPress { - var _characters: String + private let _characters: String override var key: UIKey? { MockUIKey(characters: _characters) } @@ -34,7 +34,7 @@ class MockUIPress: UIPress { @available(iOS 13.4, *) class MockUIKey: UIKey { - var _characters: String + private let _characters: String override var charactersIgnoringModifiers: String { _characters } init(characters: String) { diff --git a/Proton/Tests/Core/RichTextViewContextTests.swift b/Proton/Tests/Core/RichTextViewContextTests.swift index bef5a5d7..b2f9bb35 100644 --- a/Proton/Tests/Core/RichTextViewContextTests.swift +++ b/Proton/Tests/Core/RichTextViewContextTests.swift @@ -172,12 +172,13 @@ class RichTextViewContextTests: XCTestCase { let selectedRange = NSRange.zero textView.selectedRange = selectedRange - mockTextViewDelegate.onShouldHandleKey = { _, key, _, range, _ in + mockTextViewDelegate.onShouldHandleKey = { _, key, _, _, _ in XCTAssertEqual(key, EditorKey.right) testExpectation.fulfill() } - textView.pressesBegan(Set(arrayLiteral: MockUIPress(characters: UIKeyCommand.inputRightArrow)), with: nil) + let uiPresses: Set = [MockUIPress(characters: UIKeyCommand.inputRightArrow)] + textView.pressesBegan(uiPresses, with: nil) waitForExpectations(timeout: 1.0) } From d2bdc471b7b1c29da1a0b26500a62ddaae2ea7d4 Mon Sep 17 00:00:00 2001 From: Hon Thi Date: Mon, 9 Oct 2023 15:30:27 +1100 Subject: [PATCH 3/7] Update workflow permissions --- .github/workflows/build.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ea209248..8b295c74 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,6 +7,11 @@ on: branches: - main +permissions: + # This workflow needs write access to the checks to publish + # the results of the tests via xcresulttool. + checks: write + jobs: build: From 0ae47c9333dfed54704602d390b8147eb36a62ab Mon Sep 17 00:00:00 2001 From: Hon Thi Date: Mon, 9 Oct 2023 16:31:14 +1100 Subject: [PATCH 4/7] Disable Compile Test Results step for now --- .github/workflows/build.yml | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8b295c74..416e7536 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,11 +7,6 @@ on: branches: - main -permissions: - # This workflow needs write access to the checks to publish - # the results of the tests via xcresulttool. - checks: write - jobs: build: @@ -28,13 +23,13 @@ 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 - uses: kishikawakatsumi/xcresulttool@v1 - with: - path: TestResults.xcresult - if: success() || failure() - # ^ This is important because the action will be run - # even if the test fails in the previous step. + # - name: Compile Test results + # uses: kishikawakatsumi/xcresulttool@v1 + # with: + # path: TestResults.xcresult + # if: success() || failure() + # # ^ This is important because the action will be run + # # even if the test fails in the previous step. - name: Build and Test Proton as dependency in another package run: xcodebuild build -destination "name=iPhone 14 Pro" -scheme "TestBuildPackage" - name: Capture Proton Code Coverage From 51919c0e931412efa1102ed4067c2d6421847aa8 Mon Sep 17 00:00:00 2001 From: Rajdeep Kwatra Date: Mon, 9 Oct 2023 16:47:26 +1100 Subject: [PATCH 5/7] Updated workflow --- .github/workflows/build.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 416e7536..58ac6317 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,13 +23,15 @@ 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 - # uses: kishikawakatsumi/xcresulttool@v1 - # with: - # path: TestResults.xcresult - # if: success() || failure() - # # ^ This is important because the action will be run - # # even if the test fails in the previous step. + - name: Compile Test results + uses: kishikawakatsumi/xcresulttool@v1 + with: + path: TestResults.xcresult + permissions: + checks: write + if: success() || failure() + # ^ This is important because the action will be run + # even if the test fails in the previous step. - name: Build and Test Proton as dependency in another package run: xcodebuild build -destination "name=iPhone 14 Pro" -scheme "TestBuildPackage" - name: Capture Proton Code Coverage From 322e0e23e7e4f8f88e842ff4712b29032e2cb512 Mon Sep 17 00:00:00 2001 From: Rajdeep Kwatra Date: Mon, 9 Oct 2023 17:14:26 +1100 Subject: [PATCH 6/7] Updated workflow file --- .github/workflows/build.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 58ac6317..bc8e1d46 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,7 +3,7 @@ name: Build on: push: page_build: - pull_request: + pull_request_target: branches: - main @@ -27,8 +27,6 @@ jobs: uses: kishikawakatsumi/xcresulttool@v1 with: path: TestResults.xcresult - permissions: - checks: write if: success() || failure() # ^ This is important because the action will be run # even if the test fails in the previous step. From 838012d88b1d1369a6caa5ebc80d5f6640af9d03 Mon Sep 17 00:00:00 2001 From: Rajdeep Kwatra Date: Mon, 9 Oct 2023 17:25:27 +1100 Subject: [PATCH 7/7] Updated workflow file --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bc8e1d46..01947d3e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,7 +3,7 @@ name: Build on: push: page_build: - pull_request_target: + pull_request: branches: - main