feat(ios): W3 UI layer + integration CI + ntfy docs
T-iOS-11: TerminalScreen/KeyBar/TerminalViewModel + KeyByteMap (byte-for-byte keybar.ts, arrows excluded from UIKeyCommand to preserve DECCKM) T-iOS-12: PairingScreen/VM — confirm-before-network (zero-call assertions), §5.4 four-tier warnings, Host construction per contract ruling T-iOS-13: SessionListScreen/VM — Tunables-paced polling with leak-free teardown, optimistic kill+rollback, pending via overlay (LiveSessionInfo has no pending field) T-iOS-14: GateBanner/PlanGateSheet/AwayDigestView/GateViewModel — three-way mapping from SessionCore Affordance single source, tap-epoch guard, per-epoch haptics T-iOS-16: IntegrationTests vs real Node server (10 tests: origin guards, mirror, kill-close vs exit-frame differential, 16MiB+ESC/C0 replay) + ios.yml own-sources coverage gate (red-once demoed) T-iOS-17: ios/README.md ntfy chapter (read-only verification, file:line cites) Verified: 224 unit + 10 integration tests green; 5/5 semantic spot-checks; zero Owns violations
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
import Testing
|
||||
@testable import SessionCore
|
||||
|
||||
/// T-iOS-11 · KeyByteMap (plan §7). The key-bar byte table must mirror
|
||||
/// `public/keybar.ts` `KEY_MAP` byte-for-byte — every entry is compared
|
||||
/// against an independently written expectation (raw scalar values, not the
|
||||
/// implementation's own literals) so a typo in either side turns the suite red.
|
||||
@Suite("KeyByteMap")
|
||||
struct KeyByteMapTests {
|
||||
/// Expected byte strings, transcribed key-by-key from public/keybar.ts
|
||||
/// KEY_MAP (the web client's single source of truth). Values are built
|
||||
/// from Unicode scalars so this table cannot share a typo with the
|
||||
/// implementation's string literals.
|
||||
private static let expectedBytesByKey: [KeyByteMap.Key: [UInt8]] = [
|
||||
.esc: [0x1B], // '\x1b'
|
||||
.escEsc: [0x1B, 0x1B], // '\x1b\x1b'
|
||||
.shiftTab: [0x1B, 0x5B, 0x5A], // '\x1b[Z'
|
||||
.arrowUp: [0x1B, 0x5B, 0x41], // '\x1b[A'
|
||||
.arrowDown: [0x1B, 0x5B, 0x42], // '\x1b[B'
|
||||
.arrowLeft: [0x1B, 0x5B, 0x44], // '\x1b[D'
|
||||
.arrowRight: [0x1B, 0x5B, 0x43], // '\x1b[C'
|
||||
.enter: [0x0D], // '\r' — carriage return, NOT '\n'
|
||||
.ctrlC: [0x03],
|
||||
.ctrlR: [0x12],
|
||||
.ctrlO: [0x0F],
|
||||
.ctrlL: [0x0C],
|
||||
.ctrlT: [0x14],
|
||||
.ctrlB: [0x02],
|
||||
.ctrlD: [0x04],
|
||||
.tab: [0x09], // '\t'
|
||||
.slash: [0x2F], // '/'
|
||||
]
|
||||
|
||||
@Test("every key resolves to the exact bytes of public/keybar.ts KEY_MAP")
|
||||
func everyEntryMatchesWebKeyMapByteForByte() {
|
||||
for key in KeyByteMap.Key.allCases {
|
||||
// Arrange
|
||||
let expected = Self.expectedBytesByKey[key]
|
||||
|
||||
// Act
|
||||
let actual = Array(KeyByteMap.bytes(for: key).utf8)
|
||||
|
||||
// Assert
|
||||
#expect(actual == expected, "bytes mismatch for \(key.rawValue)")
|
||||
}
|
||||
}
|
||||
|
||||
@Test("the table covers exactly the 17 web KEY_MAP entries — no more, no less")
|
||||
func tableCoversExactlyTheWebKeyMapEntries() {
|
||||
#expect(KeyByteMap.Key.allCases.count == Self.expectedBytesByKey.count)
|
||||
#expect(Set(KeyByteMap.Key.allCases).count == KeyByteMap.Key.allCases.count)
|
||||
}
|
||||
|
||||
@Test("Enter sends carriage return (0x0D), never linefeed (gotcha: \\r not \\n)")
|
||||
func enterIsCarriageReturnNotLinefeed() {
|
||||
#expect(KeyByteMap.bytes(for: .enter) == "\r")
|
||||
#expect(KeyByteMap.bytes(for: .enter) != "\n")
|
||||
}
|
||||
|
||||
@Test("arrow CSI letters: up=A down=B left=D right=C (left/right are the classic swap)")
|
||||
func arrowFinalLettersAreNotSwapped() {
|
||||
#expect(KeyByteMap.bytes(for: .arrowUp).hasSuffix("A"))
|
||||
#expect(KeyByteMap.bytes(for: .arrowDown).hasSuffix("B"))
|
||||
#expect(KeyByteMap.bytes(for: .arrowLeft).hasSuffix("D"))
|
||||
#expect(KeyByteMap.bytes(for: .arrowRight).hasSuffix("C"))
|
||||
}
|
||||
|
||||
@Test("raw values mirror the web KEY_MAP property names (stable lookup identity)")
|
||||
func rawValuesMirrorWebKeyNames() {
|
||||
let expectedNames = [
|
||||
"esc", "escEsc", "shiftTab", "arrowUp", "arrowDown", "arrowLeft",
|
||||
"arrowRight", "enter", "ctrlC", "ctrlR", "ctrlO", "ctrlL", "ctrlT",
|
||||
"ctrlB", "ctrlD", "tab", "slash",
|
||||
]
|
||||
#expect(KeyByteMap.Key.allCases.map(\.rawValue) == expectedNames)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user