oficina / docs Public

Clone
git clone https://amadan.net/oficina/docs

Plain git — no account needed to clone.

Download

Download this file

1import Foundation
2import RastrilloApple
3import XCTest
4@testable import DocsKit
5
6final class DocumentSyncIntegrationTests: XCTestCase {
7 func testFailedMergeCannotPoisonEditorOrNextMerge() async throws {
8 let replica = CRDTReplica(guid: "merge-isolation")
9 _ = try await replica.open(update: Data())
10 _ = try await replica.command(["op": .string("insert_element"), "at": .number(0), "type": .string("paragraph")])
11 do {
12 _ = try await replica.mergeUpdates([Data([255]), Data([255])])
13 XCTFail("Malformed updates must fail")
14 } catch { }
15 _ = try await replica.projection()
16 let (first, _) = try await replica.command(["op": .string("display_edit"), "at": .number(0), "length": .number(0), "text": .string("a")])
17 let (second, projection) = try await replica.command(["op": .string("display_edit"), "at": .number(1), "length": .number(0), "text": .string("b")])
18 let merged = try await replica.mergeUpdates([first, second])
19 XCTAssertFalse(merged.isEmpty)
20 let after = try await replica.projection()
21 XCTAssertEqual(after.children.map(\.plainText), projection.children.map(\.plainText))
22 await replica.close()
23 }
24 @MainActor func testTypingDoesNotAccumulateOneRoundTripPerKeystroke() async throws {
25 guard let origin = ProcessInfo.processInfo.environment["DOCS_SYNC_TEST_ORIGIN"] else {
26 throw XCTSkip("Run native/scripts/test-sync-latency.py for the delayed HTTP fixture")
27 }
28 let client = NativeHTTP(origin: URL(string: origin)!)
29 let model = DocsModel(client: client)
30 await model.open("fixture")
31 XCTAssertNotNil(model.projection)
32 model.didRender(model.projectionGeneration)
33 for _ in 0..<20 {
34 XCTAssertTrue(model.enqueue(["op": .string("display_edit"), "at": .number(0), "length": .number(0), "text": .string("X")]))
35 while model.hasPendingInput { try await Task.sleep(for: .milliseconds(1)) }
36 model.didRender(model.projectionGeneration)
37 try await Task.sleep(for: .milliseconds(50))
38 }
39 let finishedTyping = ContinuousClock.now
40 let deadline = finishedTyping.advanced(by: .seconds(15))
41 while model.hasUnsavedChanges && ContinuousClock.now < deadline {
42 await model.flush()
43 try await Task.sleep(for: .milliseconds(10))
44 }
45 XCTAssertFalse(model.hasUnsavedChanges)
46 let lag = finishedTyping.duration(to: .now)
47 struct Stats: Decodable { let updates: [Data] }
48 let stats: Stats = try await client.request("/stats")
49 print("SYNC: 20 edits, 250ms/request, uploads=\(stats.updates.count), catch-up after typing=\(lag)")
50 XCTAssertLessThanOrEqual(stats.updates.count, 10, "Unsent edits must share a request")
51 XCTAssertLessThan(lag, .seconds(1), "Typing must not leave a growing serial-request backlog")
52 let (initial, _) = try await client.response("/d/fixture/updates")
53 let peer = CRDTReplica(guid: "sync-fixture")
54 _ = try await peer.open(update: initial)
55 for update in stats.updates { _ = try await peer.apply(update) }
56 let projection = try await peer.projection()
57 XCTAssertEqual(projection.children.map(\.plainText), model.projection?.children.map(\.plainText), "A receiver must get every edit")
58 await peer.close()
59 await model.stop()
60 }
61}
62