Forji/Forji/ForjiUITests/NotificationsUITests.swift
Stefan Hausotte 330d2bde4f fix: remove redundant notification Dismiss swipe action (#46)
The trailing Dismiss swipe called the same setNotificationRead path as the
leading Mark Read action, since ForgejoKit's NotificationService only exposes
markAsRead (PATCH to-status=read). Dismiss therefore did nothing distinct and
left the row visible under the All/Read filters, just relabeled.

Drop the redundant action in both NotificationsOverviewView and
MergedNotificationsOverviewView. The leading Mark Read swipe (shown for unread
threads, full-swipe by default) remains the single, honest action.

Reviewed-on: https://codeberg.org/secana/Forji/pulls/64
2026-06-04 14:08:20 +02:00

105 lines
4.6 KiB
Swift

import XCTest
final class NotificationsUITests: ForgejoUITestBase {
// MARK: - Notifications (unread, filter, swipe mark-read)
@MainActor
func testNotifications() throws {
loginAndWaitForHome()
app.tabBars.buttons["Notifications"].tap()
let notificationsList = app.collectionViews.firstMatch
XCTAssertTrue(notificationsList.waitForExistence(timeout: 10))
XCTAssertTrue(app.staticTexts["testadmin/test-repo"].waitForExistence(timeout: 5))
// Filter by Read
let readButton = app.buttons["Read"]
XCTAssertTrue(readButton.waitForExistence(timeout: 5))
readButton.tap()
// Switch to All
let allButton = app.buttons["All"]
XCTAssertTrue(allButton.waitForExistence(timeout: 5))
allButton.tap()
// Switch back to Unread for swipe tests
let unreadButton = app.buttons["Unread"]
XCTAssertTrue(unreadButton.waitForExistence(timeout: 5))
unreadButton.tap()
XCTAssertTrue(notificationsList.waitForExistence(timeout: 10))
// Swipe right to mark as read
let firstCell = notificationsList.cells.firstMatch
XCTAssertTrue(firstCell.waitForExistence(timeout: 5))
firstCell.swipeRight()
let markReadButton = app.buttons["Mark Read"]
if markReadButton.waitForExistence(timeout: 3) {
markReadButton.tap()
}
}
// MARK: - Open-to-clear (#32)
/// Opening an unread notification marks it read on the server, so it leaves
/// the Unread filter on the next refresh.
///
/// Covers the in-app NotificationsOverviewView path: the tap gesture on the
/// row calls markReadOnOpen, which marks the thread read with
/// removeFromUnread: false so the row stays put during navigation and only
/// drops out of Unread on the next server fetch. The multi-instance and
/// system-notification open paths apply the same fix but aren't reachable
/// from this single-instance harness.
///
/// Sorts after testNotifications and opens whichever unread row is first
/// rather than a fixed one, so it's unaffected by the two notifications that
/// test consumes (the seed leaves several more).
@MainActor
func testOpeningNotificationMarksItRead() throws {
loginAndWaitForHome()
app.tabBars.buttons["Notifications"].tap()
let notificationsList = app.collectionViews.firstMatch
XCTAssertTrue(notificationsList.waitForExistence(timeout: 10))
// Make sure the Unread filter is active.
let unreadButton = app.buttons["Unread"]
XCTAssertTrue(unreadButton.waitForExistence(timeout: 5))
unreadButton.tap()
XCTAssertTrue(notificationsList.waitForExistence(timeout: 10))
// Remember the first unread notification's title (the row's first text)
// so we can assert that specific one leaves the Unread list.
let firstCell = notificationsList.cells.firstMatch
XCTAssertTrue(firstCell.waitForExistence(timeout: 10), "Expected at least one unread notification")
let openedTitle = firstCell.staticTexts.firstMatch.label
XCTAssertFalse(openedTitle.isEmpty, "Could not read the unread notification's title")
// Open it. Navigating into the detail proves the row was tappable; the
// nav bar title changes away from "Notifications" once it's on screen.
firstCell.tap()
let detailNavBar = app.navigationBars.element(boundBy: 0)
XCTAssertTrue(detailNavBar.waitForExistence(timeout: 10), "Detail view did not open")
XCTAssertNotEqual(detailNavBar.identifier, "Notifications", "Did not navigate into the notification detail")
// Back to the list.
app.navigationBars.buttons.element(boundBy: 0).tap()
XCTAssertTrue(notificationsList.waitForExistence(timeout: 10))
// Force a server refresh of Unread by toggling the filter. Opening marks
// the thread read but keeps the row visible until the next fetch, so this
// round-trip is what surfaces the open-to-clear behaviour.
app.buttons["Read"].tap()
XCTAssertTrue(notificationsList.waitForExistence(timeout: 10))
unreadButton.tap()
XCTAssertTrue(notificationsList.waitForExistence(timeout: 10))
// The opened notification should no longer be under Unread.
let openedRow = notificationsList.staticTexts[openedTitle]
XCTAssertFalse(
openedRow.waitForExistence(timeout: 5),
"Opened notification \"\(openedTitle)\" should have left the Unread filter after refresh",
)
}
}