Add support for sharing URLs and text. (#3546)

* Add support for sharing URLs and text.

* Fix a bug where a stored draft would overwrite the shared text.

* Add tests.
This commit is contained in:
Doug
2024-11-21 14:48:38 +00:00
committed by GitHub
parent 3a9f54a9f6
commit c081e538b4
14 changed files with 219 additions and 83 deletions
@@ -23,17 +23,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
AppSettings.resetAllSettings()
appSettings = AppSettings()
ServiceLocator.shared.register(appSettings: appSettings)
wysiwygViewModel = WysiwygComposerViewModel()
completionSuggestionServiceMock = CompletionSuggestionServiceMock(configuration: .init())
draftServiceMock = ComposerDraftServiceMock()
viewModel = ComposerToolbarViewModel(wysiwygViewModel: wysiwygViewModel,
completionSuggestionService: completionSuggestionServiceMock,
mediaProvider: MediaProviderMock(configuration: .init()),
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
analyticsService: ServiceLocator.shared.analytics,
composerDraftService: draftServiceMock)
viewModel.context.composerFormattingEnabled = true
setUpViewModel()
}
override func tearDown() {
@@ -340,7 +330,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
return .success(nil)
}
viewModel.loadDraft()
await viewModel.loadDraft()
await fulfillment(of: [expectation], timeout: 10)
XCTAssertFalse(viewModel.context.composerFormattingEnabled)
XCTAssertTrue(viewModel.state.composerEmpty)
@@ -356,7 +346,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
htmlText: nil,
draftType: .newMessage))
}
viewModel.loadDraft()
await viewModel.loadDraft()
await fulfillment(of: [expectation], timeout: 10)
XCTAssertFalse(viewModel.context.composerFormattingEnabled)
@@ -373,7 +363,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
htmlText: "<strong>Hello</strong> world!",
draftType: .newMessage))
}
viewModel.loadDraft()
await viewModel.loadDraft()
await fulfillment(of: [expectation], timeout: 10)
XCTAssertTrue(viewModel.context.composerFormattingEnabled)
@@ -391,7 +381,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
htmlText: nil,
draftType: .edit(eventID: "testID")))
}
viewModel.loadDraft()
await viewModel.loadDraft()
await fulfillment(of: [expectation], timeout: 10)
XCTAssertFalse(viewModel.context.composerFormattingEnabled)
@@ -424,7 +414,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
return .success(.init(details: loadedReply,
isThreaded: true))
}
viewModel.loadDraft()
await viewModel.loadDraft()
await fulfillment(of: [draftExpectation], timeout: 10)
XCTAssertFalse(viewModel.context.composerFormattingEnabled)
@@ -464,7 +454,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
return .success(.init(details: loadedReply,
isThreaded: true))
}
viewModel.loadDraft()
await viewModel.loadDraft()
await fulfillment(of: [draftExpectation], timeout: 10)
XCTAssertFalse(viewModel.context.composerFormattingEnabled)
@@ -622,6 +612,45 @@ class ComposerToolbarViewModelTests: XCTestCase {
viewModel.process(viewAction: .sendMessage)
try await deferred.fulfill()
}
func testRestoreDoesntOverwriteInitialText() async {
let sharedText = "Some shared text"
let expectation = expectation(description: "Wait for draft to be restored")
expectation.isInverted = true
setUpViewModel(initialText: sharedText) {
defer { expectation.fulfill() }
return .success(.init(plainText: "Hello world!",
htmlText: nil,
draftType: .newMessage))
}
viewModel.context.composerFormattingEnabled = false
await viewModel.loadDraft()
await fulfillment(of: [expectation], timeout: 1)
XCTAssertFalse(viewModel.context.composerFormattingEnabled)
XCTAssertEqual(viewModel.state.composerMode, .default)
XCTAssertEqual(viewModel.context.plainComposerText, NSAttributedString(string: sharedText))
}
// MARK: - Helpers
private func setUpViewModel(initialText: String? = nil, loadDraftClosure: (() async -> Result<ComposerDraftProxy?, ComposerDraftServiceError>)? = nil) {
wysiwygViewModel = WysiwygComposerViewModel()
completionSuggestionServiceMock = CompletionSuggestionServiceMock(configuration: .init())
draftServiceMock = ComposerDraftServiceMock()
if let loadDraftClosure {
draftServiceMock.loadDraftClosure = loadDraftClosure
}
viewModel = ComposerToolbarViewModel(initialText: initialText,
wysiwygViewModel: wysiwygViewModel,
completionSuggestionService: completionSuggestionServiceMock,
mediaProvider: MediaProviderMock(configuration: .init()),
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
analyticsService: ServiceLocator.shared.analytics,
composerDraftService: draftServiceMock)
viewModel.context.composerFormattingEnabled = true
}
}
private extension MentionSuggestionItem {
@@ -218,7 +218,7 @@ class RoomFlowCoordinatorTests: XCTestCase {
XCTAssert(navigationStackCoordinator.stackCoordinators.first is RoomScreenCoordinator)
}
func testShareRoute() async throws {
func testShareMediaRoute() async throws {
await setupRoomFlowCoordinator()
try await process(route: .room(roomID: "1", via: []))
@@ -243,6 +243,31 @@ class RoomFlowCoordinatorTests: XCTestCase {
XCTAssertTrue((navigationStackCoordinator.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is MediaUploadPreviewScreenCoordinator)
}
func testShareTextRoute() async throws {
await setupRoomFlowCoordinator()
try await process(route: .room(roomID: "1", via: []))
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomScreenCoordinator)
XCTAssertEqual(navigationStackCoordinator.stackCoordinators.count, 0)
let sharePayload: ShareExtensionPayload = .text(roomID: "1", text: "Important text")
try await process(route: .share(sharePayload))
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomScreenCoordinator)
XCTAssertEqual(navigationStackCoordinator.stackCoordinators.count, 0)
XCTAssertNil(navigationStackCoordinator.sheetCoordinator, "The media upload sheet shouldn't be shown when sharing text.")
try await process(route: .childRoom(roomID: "2", via: []))
XCTAssertNil(navigationStackCoordinator.sheetCoordinator)
XCTAssertEqual(navigationStackCoordinator.stackCoordinators.count, 1)
try await process(route: .share(sharePayload))
XCTAssertEqual(navigationStackCoordinator.stackCoordinators.count, 0)
XCTAssertNil(navigationStackCoordinator.sheetCoordinator, "The media upload sheet shouldn't be shown when sharing text.")
}
// MARK: - Private
private func process(route: AppRoute) async throws {
@@ -242,7 +242,7 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
"A new timeline should be created for the same room ID, so that the screen isn't stale while loading.")
}
func testShareRouteWithoutRoom() async throws {
func testShareMediaRouteWithoutRoom() async throws {
try await process(route: .settings, expectedState: .settingsScreen(selectedRoomID: nil))
XCTAssertTrue((splitCoordinator?.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is SettingsScreenCoordinator)
@@ -253,7 +253,7 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
XCTAssertTrue((splitCoordinator?.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is RoomSelectionScreenCoordinator)
}
func testShareRouteWithRoom() async throws {
func testShareMediaRouteWithRoom() async throws {
try await process(route: .event(eventID: "1", roomID: "1", via: []), expectedState: .roomList(selectedRoomID: "1"))
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
@@ -265,6 +265,29 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
XCTAssertTrue((splitCoordinator?.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is MediaUploadPreviewScreenCoordinator)
}
func testShareTextRouteWithoutRoom() async throws {
try await process(route: .settings, expectedState: .settingsScreen(selectedRoomID: nil))
XCTAssertTrue((splitCoordinator?.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is SettingsScreenCoordinator)
let sharePayload: ShareExtensionPayload = .text(roomID: nil, text: "Important Text")
try await process(route: .share(sharePayload),
expectedState: .shareExtensionRoomList(sharePayload: sharePayload))
XCTAssertTrue((splitCoordinator?.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is RoomSelectionScreenCoordinator)
}
func testShareTextRouteWithRoom() async throws {
try await process(route: .event(eventID: "1", roomID: "1", via: []), expectedState: .roomList(selectedRoomID: "1"))
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
let sharePayload: ShareExtensionPayload = .text(roomID: "2", text: "Important text")
try await process(route: .share(sharePayload),
expectedState: .roomList(selectedRoomID: "2"))
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
XCTAssertNil(splitCoordinator?.sheetCoordinator, "The media upload sheet shouldn't be shown when sharing text.")
}
// MARK: - Private
private func process(route: AppRoute, expectedState: UserSessionFlowCoordinatorStateMachine.State) async throws {