bitchat/bitchatTests/ConnectivityStatusTests.swift
jack 5b592c8bae
Add a persistent connectivity banner and stop the radar lying (#1597)
* Add a persistent connectivity banner and stop the radar lying

Two "the app looks fine while it isn't" problems from the UX audit:

- Bluetooth-off had exactly one signal: a dismissible alert. Once
  dismissed, the app looked completely normal — empty timeline, radar
  sweeping "searching for people" — while the radio was off. The radar
  animation now requires a radio that can actually scan, and a
  persistent red banner under the header carries the state instead:
  off (tap → settings), denied (tap → settings), or unsupported
  (mesh unavailable; location channels still work).
- A Tor bootstrap stall was only ever announced inside geohash
  timelines (addGeohashOnlySystemMessage), so someone sitting in #mesh
  or a DM whose messages route over Nostr got silence and hanging
  "sent" states. The stall now also drives a chrome-level torBlocked
  flag and the same banner: "tor can't connect — internet features are
  paused. mesh still works."

The banner renders nothing when everything is healthy, and .unknown/
.resetting radio states deliberately stay quiet — a false "bluetooth is
off" flash at launch would be the same kind of lie. Priority and
quiet-start behavior are pinned by ConnectivityIssueTests. 4 new
strings, all 30 locales.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Review fixes: banner in the sheet, real fix targets, tor-clear tests

- The people/DM sheet covers the root header, so the connectivity
  banner vanished exactly where people sit longest. It now mirrors
  into the sheet via a top safe-area inset over both the people list
  and the DM view.
- "tap to fix" for powered-off Bluetooth opened the macOS privacy
  permission pane, where an already-authorized person can't fix
  anything. New SystemSettings.bluetoothPower routes to the Bluetooth
  pane; denied still goes to the privacy anchor. The one-shot alert's
  settings button routes by state the same way.
- Tests pin that torBlocked clears on tor-ready and on preference
  change (no stale "tor can't connect" after toggling tor off).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-10 08:47:53 +02:00

32 lines
1.4 KiB
Swift

//
// ConnectivityStatusTests.swift
// bitchatTests
//
// This is free and unencumbered software released into the public domain.
// For more information, see <https://unlicense.org>
//
import CoreBluetooth
import Testing
@testable import bitchat
struct ConnectivityIssueTests {
@Test("Bluetooth problems outrank a tor stall; healthy state shows nothing")
func resolvePrioritizesBluetoothOverTor() {
#expect(ConnectivityIssue.resolve(bluetoothState: .poweredOff, torBlocked: true) == .bluetoothOff)
#expect(ConnectivityIssue.resolve(bluetoothState: .unauthorized, torBlocked: false) == .bluetoothDenied)
#expect(ConnectivityIssue.resolve(bluetoothState: .unsupported, torBlocked: false) == .bluetoothUnsupported)
#expect(ConnectivityIssue.resolve(bluetoothState: .poweredOn, torBlocked: true) == .torBlocked)
#expect(ConnectivityIssue.resolve(bluetoothState: .poweredOn, torBlocked: false) == nil)
}
@Test("A starting radio must not flash a false 'bluetooth is off' banner")
func resolveStaysQuietWhileRadioIsStarting() {
#expect(ConnectivityIssue.resolve(bluetoothState: .unknown, torBlocked: false) == nil)
#expect(ConnectivityIssue.resolve(bluetoothState: .resetting, torBlocked: false) == nil)
// A tor stall still surfaces once known, even while the radio starts.
#expect(ConnectivityIssue.resolve(bluetoothState: .unknown, torBlocked: true) == .torBlocked)
}
}