Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Columba.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@
8A321B0938566F0D62D64562 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3BEE6E339CC852C9BA8C5D75 /* Python.xcframework */; };
922658C82CEEA53695143F9B /* MapLibre in Frameworks */ = {isa = PBXBuildFile; productRef = DBD8F3A253D413F087742BC0 /* MapLibre */; };
92DDF4AE0AB493CC2AA0BA20 /* LXSTSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 6000D5CED2C3C89F74999BC1 /* LXSTSwift */; };
9566DCEF56FEFC97BAAA47BA /* MessageFormattedTimeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4EF2D71A3D88C71D5BEDAD /* MessageFormattedTimeTests.swift */; };
98547ADE9B17DD692240E7F7 /* LXMFSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 85B9530D8CAE0E16D5371319 /* LXMFSwift */; };
9D9069A3F6302111A4727454 /* SwiftBLEBridge in Frameworks */ = {isa = PBXBuildFile; productRef = DD88CFE74E7E22427BC4D163 /* SwiftBLEBridge */; };
A0C0D9AE12F728A484F0F108 /* AudioManagerConfigChangeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE282FA3937E59BC026E072 /* AudioManagerConfigChangeTests.swift */; };
Expand Down Expand Up @@ -207,6 +208,7 @@
AD87870C760723D7E77C87E9 /* TCPClientWizardViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TCPClientWizardViewModel.swift; sourceTree = "<group>"; };
B1AB71D8977FE792BA9B176D /* JetBrainsMono-Bold.ttf */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file; path = "JetBrainsMono-Bold.ttf"; sourceTree = "<group>"; };
B68384C48BFF8F5294340EDB /* PttButton.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PttButton.swift; sourceTree = "<group>"; };
BC4EF2D71A3D88C71D5BEDAD /* MessageFormattedTimeTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = MessageFormattedTimeTests.swift; sourceTree = "<group>"; };
BF48C97880B30682DC35613C /* CeaseTelemetry.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CeaseTelemetry.swift; sourceTree = "<group>"; };
BKF002 /* BackendFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackendFactory.swift; sourceTree = "<group>"; };
CCF4DCA18506B96230721ACC /* PythonBLECallbackBridge.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PythonBLECallbackBridge.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -682,6 +684,7 @@
DE307E24C72DBA41D76C9A6C /* AudioRingBufferTests.swift */,
6FE282FA3937E59BC026E072 /* AudioManagerConfigChangeTests.swift */,
30A79B9ECDB4166F8A36A670 /* CallManagerCallKitTests.swift */,
BC4EF2D71A3D88C71D5BEDAD /* MessageFormattedTimeTests.swift */,
);
path = Tests/ColumbaAppTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -1082,6 +1085,7 @@
2B3A3E3FB59D1E22B424E109 /* AudioRingBufferTests.swift in Sources */,
A0C0D9AE12F728A484F0F108 /* AudioManagerConfigChangeTests.swift in Sources */,
E49B977D311DA1C9ACE14CAB /* CallManagerCallKitTests.swift in Sources */,
9566DCEF56FEFC97BAAA47BA /* MessageFormattedTimeTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
7 changes: 6 additions & 1 deletion Sources/ColumbaApp/Views/Messaging/MessageBubble.swift
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,12 @@ public struct Message: Identifiable, Equatable {

/// Formatted time string (e.g., "5 min ago", "Just now")
public var formattedTime: String {
Self.relativeFormatter.localizedString(for: timestamp, relativeTo: Date())
// Clamp future-dated timestamps to now: a peer (or our own past clock)
// can stamp a wire ts ahead of local time, and we must never render
// "in 5 min" on a message that has already arrived.
let now = Date()
let display = min(timestamp, now)
return Self.relativeFormatter.localizedString(for: display, relativeTo: now)
}

/// True if message has no visible content (telemetry-only messages).
Expand Down
33 changes: 33 additions & 0 deletions Tests/ColumbaAppTests/MessageFormattedTimeTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import XCTest
@testable import ColumbaApp

final class MessageFormattedTimeTests: XCTestCase {

func test_formattedTime_clampsFutureTimestampToNow() {
let future = Date().addingTimeInterval(600)
let message = Message(content: "hi", timestamp: future, isFromMe: true)

let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .abbreviated
let referenceNow = Date()
let expected = formatter.localizedString(for: referenceNow, relativeTo: referenceNow)

XCTAssertEqual(message.formattedTime, expected)
}

func test_formattedTime_pastTimestampsRenderRelativePast() {
// 2 hours is comfortably mid-bucket for the abbreviated formatter. The
// 60-min mark is a min/hr rounding boundary, so a 1-hr offset could let
// the two independent Date() reads (here vs. inside formattedTime)
// straddle it and flake. Anchor `now` once for the expected value.
let now = Date()
let twoHoursAgo = now.addingTimeInterval(-7200)
let message = Message(content: "hi", timestamp: twoHoursAgo, isFromMe: false)

let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .abbreviated
let expected = formatter.localizedString(for: twoHoursAgo, relativeTo: now)

XCTAssertEqual(message.formattedTime, expected)
}
Comment thread
torlando-tech marked this conversation as resolved.
}
Loading