migrate persistence from JSON to SwiftData
iOS CI / Build and Test SwiftUI App (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build Unsigned iOS IPA (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build macOS ZIP (push) Canceled after 0s

Models:
- Convert Conversation and ChatMessage to SwiftData @Model classes with
  a
  one-to-many relationship and cascade delete.
- Rename ChatMessage.id to clientID and drop Conversation.id in favor of
  jid
  (the string id would clash with PersistentIdentifier).
- Keep MessageReaction as a Codable value stored in an array attribute.

Persistence:
- Add ArchiveStore, a per-account ModelContainer/ModelContext that
  replaces
  the JSON ChatArchive with load/save/erase.
- Add ArchiveMetadataRecord for the durable MAM checkpoints, cursor,
  locally
  deleted message IDs and roster contact JIDs.
- Add LegacyArchiveImporter to import the old JSON snapshot once and
  delete
  the file afterwards, preserving existing history.

AppModel:
- Replace ChatArchive with ArchiveStore and persist via context.save();
  insert new models and delete the replaced object on upsert merges so
  SwiftData never keeps an orphaned duplicate.

Tests:
- Replace ChatArchiveTests with ArchiveStoreTests (store round-trip and
  legacy import) and drop the now-obsolete Codable round-trip
  assertions.
This commit is contained in:
wt committed 2026-08-18 22:58:18 +07:00
1 parent 0317277d46
commit cbb820a86e
30 files changed
+726 -665

No files matched your search

+24 -47
View File
@@ -4,31 +4,32 @@ import XCTest
final class MessageCorrectionTests: XCTestCase {
func testOnlyDeliveredOrSentOutgoingTextCanBeEdited() {
let editable = ChatMessage(
conversationID: "bob@example.org",
senderJID: "alice@example.org",
body: "До исправления",
direction: .outgoing,
delivery: .sent,
security: .omemo
)
var incoming = editable
incoming.direction = .incoming
var attachment = editable
attachment.kind = .attachment
var failed = editable
failed.delivery = .failed
var retracted = editable
retracted.retractedAt = Date()
func make(
direction: ChatMessage.Direction = .outgoing,
delivery: ChatMessage.Delivery = .sent,
kind: ChatMessage.Kind = .text,
retractedAt: Date? = nil
) -> ChatMessage {
ChatMessage(
conversationID: "bob@example.org",
senderJID: "alice@example.org",
body: "До исправления",
direction: direction,
delivery: delivery,
security: .omemo,
kind: kind,
retractedAt: retractedAt
)
}
XCTAssertTrue(editable.canBeEdited)
XCTAssertFalse(incoming.canBeEdited)
XCTAssertFalse(attachment.canBeEdited)
XCTAssertFalse(failed.canBeEdited)
XCTAssertFalse(retracted.canBeEdited)
XCTAssertTrue(make().canBeEdited)
XCTAssertFalse(make(direction: .incoming).canBeEdited)
XCTAssertFalse(make(kind: .attachment).canBeEdited)
XCTAssertFalse(make(delivery: .failed).canBeEdited)
XCTAssertFalse(make(retractedAt: Date()).canBeEdited)
}
func testEditedTimestampRoundTrips() throws {
func testEditedTimestampIsStored() {
let editedAt = Date(timeIntervalSince1970: 1_700_000_000)
let message = ChatMessage(
conversationID: "bob@example.org",
@@ -40,30 +41,6 @@ final class MessageCorrectionTests: XCTestCase {
editedAt: editedAt
)
let data = try JSONEncoder().encode(message)
let decoded = try JSONDecoder().decode(ChatMessage.self, from: data)
XCTAssertEqual(decoded.editedAt, editedAt)
}
func testLegacyMessageWithoutEditedTimestampStillDecodes() throws {
let message = ChatMessage(
conversationID: "bob@example.org",
senderJID: "alice@example.org",
body: "Старое сообщение",
direction: .incoming,
delivery: .delivered,
security: .omemo
)
let encoded = try JSONEncoder().encode(message)
var object = try XCTUnwrap(JSONSerialization.jsonObject(with: encoded) as? [String: Any])
object.removeValue(forKey: "editedAt")
let decoded = try JSONDecoder().decode(
ChatMessage.self,
from: JSONSerialization.data(withJSONObject: object)
)
XCTAssertNil(decoded.editedAt)
XCTAssertEqual(message.editedAt, editedAt)
}
}