ConversationListView (advanced list, ⌘L): ⌘-click toggles a row, Shift-click selects a contiguous range, and a "Move to Folder" toolbar button/context-menu entry moves every selected conversation at once. Confirmed working live. SidebarView: same capability, adapted to the sidebar's own click model since opening a chat there previously required only a single click. Single-click now selects only (replacing the prior selection), ⌘/Shift-click work the same as the advanced list, and double-click opens a chat (clearing the selection). Selected rows get a distinct neutral tint from the existing accent highlight used for the currently-open conversation. Dragging a row that's part of a multi-selection now bundles every selected conversation's ID into the drag payload, so dropping on a folder moves the whole selection instead of just the dragged row. Range-selection math (idsInRange) is defined once on ConversationListView and reused directly by SidebarView rather than duplicated — it's `internal`, not `private`, specifically so both views can share it.
55 lines
1.9 KiB
Swift
55 lines
1.9 KiB
Swift
//
|
|
// ConversationListViewPureLogicTests.swift
|
|
// oAITests
|
|
//
|
|
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
|
|
// Copyright (C) 2026 Rune Olsen
|
|
|
|
import Testing
|
|
import Foundation
|
|
@testable import Confab
|
|
|
|
@Suite("ConversationListView.idsInRange")
|
|
struct ConversationListViewPureLogicTests {
|
|
|
|
private let ids = (0..<6).map { _ in UUID() }
|
|
|
|
@Test("Forward range includes anchor and target inclusive")
|
|
func forwardRange() {
|
|
let result = ConversationListView.idsInRange(orderedIds: ids, anchorId: ids[1], targetId: ids[4])
|
|
#expect(result == Set(ids[1...4]))
|
|
}
|
|
|
|
@Test("Backward range (target above anchor) still selects the span between them")
|
|
func backwardRange() {
|
|
let result = ConversationListView.idsInRange(orderedIds: ids, anchorId: ids[4], targetId: ids[1])
|
|
#expect(result == Set(ids[1...4]))
|
|
}
|
|
|
|
@Test("Anchor equal to target selects just that one id")
|
|
func sameAnchorAndTarget() {
|
|
let result = ConversationListView.idsInRange(orderedIds: ids, anchorId: ids[2], targetId: ids[2])
|
|
#expect(result == [ids[2]])
|
|
}
|
|
|
|
@Test("Nil anchor (first Shift-click) falls back to just the target")
|
|
func nilAnchorFallsBackToTarget() {
|
|
let result = ConversationListView.idsInRange(orderedIds: ids, anchorId: nil, targetId: ids[3])
|
|
#expect(result == [ids[3]])
|
|
}
|
|
|
|
@Test("Anchor no longer present in the ordered list falls back to just the target")
|
|
func missingAnchorFallsBackToTarget() {
|
|
let staleAnchor = UUID()
|
|
let result = ConversationListView.idsInRange(orderedIds: ids, anchorId: staleAnchor, targetId: ids[3])
|
|
#expect(result == [ids[3]])
|
|
}
|
|
|
|
@Test("Single-element list selects that element")
|
|
func singleElementList() {
|
|
let solo = [ids[0]]
|
|
let result = ConversationListView.idsInRange(orderedIds: solo, anchorId: ids[0], targetId: ids[0])
|
|
#expect(result == [ids[0]])
|
|
}
|
|
}
|