Files
hapi/ios/Packages/HapiKit/Tests/HapiClientTests/Files/FileViewerModelTests.swift
T

192 lines
7.1 KiB
Swift

import Foundation
import HapiClient
import HapiProtocol
import Testing
/// Transcription of the Android `FileViewerViewModelTest` suite against the
/// real `FileViewerModel` (only the gateway is faked). One adaptation: on
/// iOS the unified-diff *parser* lives in HapiUI, so `DiffState.ready`
/// carries the raw stdout judged through the injected `hasRenderableDiff`
/// probe (parse-level assertions are covered by `HapiUITests/DiffModelTests`);
/// these tests use the default non-empty probe.
@Suite("FileViewerModel")
@MainActor
struct FileViewerModelTests {
private func makeModel(
_ gateway: FakeFilesGateway,
path: String = "src/app.ts",
staged: Bool? = nil,
mode: FileViewerModel.Mode? = nil,
line: Int? = nil
) -> FileViewerModel {
FileViewerModel(
sessionId: "s1",
path: path,
initialStaged: staged,
initialMode: mode,
focusLine: line,
requester: gateway
)
}
private func b64(_ text: String) -> String {
Data(text.utf8).base64EncodedString()
}
private let sampleDiff = """
diff --git a/src/app.ts b/src/app.ts
--- a/src/app.ts
+++ b/src/app.ts
@@ -1,2 +1,2 @@
keep
-old
+new
"""
private func settled(_ model: FileViewerModel) async -> Bool {
await filesEventually { model.diff != .loading && model.content != .loading }
}
@Test func parsesTheDiffAndStaysInDiffMode() async {
let gateway = FakeFilesGateway()
await gateway.setDiffFile(GitCommandResponse(success: true, stdout: sampleDiff))
await gateway.setReadFile(FileReadResponse(success: true, content: b64("keep\nnew\n")))
let model = makeModel(gateway)
model.start()
#expect(await settled(model))
#expect(model.mode == .diff)
#expect(model.diff == .ready(unifiedDiff: sampleDiff))
#expect(await gateway.diffFileCalls == [DiffFileCall(path: "src/app.ts", staged: false)])
}
@Test func emptyDiffAutoFallsBackToFileMode() async {
let gateway = FakeFilesGateway()
await gateway.setDiffFile(GitCommandResponse(success: true, stdout: ""))
await gateway.setReadFile(FileReadResponse(success: true, content: b64("hello")))
let model = makeModel(gateway)
model.start()
#expect(await settled(model))
#expect(model.mode == .file)
#expect(model.diff == .empty)
#expect(model.content == .text("hello", language: "ts", isMarkdown: false))
}
@Test func failedDiffAutoFallsBackToFileModeWithMessageKept() async {
let gateway = FakeFilesGateway()
await gateway.setDiffFile(GitCommandResponse(success: false, error: "not a repo"))
await gateway.setReadFile(FileReadResponse(success: true, content: b64("x")))
let model = makeModel(gateway)
model.start()
#expect(await settled(model))
#expect(model.mode == .file)
#expect(model.diff == .failed("not a repo"))
}
@Test func explicitFileModeFromTheRouteDisablesAutoBehavior() async {
let gateway = FakeFilesGateway()
await gateway.setDiffFile(GitCommandResponse(success: true, stdout: sampleDiff))
await gateway.setReadFile(FileReadResponse(success: true, content: b64("body")))
let model = makeModel(gateway, mode: .file, line: 12)
model.start()
#expect(await settled(model))
#expect(model.mode == .file)
#expect(model.focusLine == 12)
// The diff still loaded, so the user can flip to it.
#expect(model.diff == .ready(unifiedDiff: sampleDiff))
}
@Test func stagedToggleReloadsTheDiffForTheOtherSide() async {
let gateway = FakeFilesGateway()
await gateway.setDiffFile(GitCommandResponse(success: true, stdout: sampleDiff))
await gateway.setReadFile(FileReadResponse(success: true, content: b64("x")))
let model = makeModel(gateway, staged: false)
model.start()
#expect(await settled(model))
model.setStaged(true)
#expect(await filesEventually { await gateway.diffFileCalls.count == 2 })
#expect(await gateway.diffFileCalls == [
DiffFileCall(path: "src/app.ts", staged: false),
DiffFileCall(path: "src/app.ts", staged: true),
])
#expect(model.staged)
// Same side again is a no-op.
model.setStaged(true)
try? await Task.sleep(for: .milliseconds(50))
#expect(await gateway.diffFileCalls.count == 2)
}
@Test func markdownFilesFlagMarkdownAndDefaultToPreview() async {
let gateway = FakeFilesGateway()
await gateway.setDiffFile(GitCommandResponse(success: true, stdout: ""))
await gateway.setReadFile(FileReadResponse(success: true, content: b64("# Title"), size: 7, modified: 123))
let model = makeModel(gateway, path: "README.md")
model.start()
#expect(await settled(model))
#expect(model.content == .text("# Title", language: "md", isMarkdown: true))
#expect(model.markdownPreview)
#expect(model.sizeBytes == 7)
#expect(model.modifiedAt == 123)
model.setMarkdownPreview(false)
#expect(model.markdownPreview == false)
}
@Test func imageExtensionsDecodeToImageContent() async {
let bytes = Data([1, 2, 3, 4])
let gateway = FakeFilesGateway()
await gateway.setDiffFile(GitCommandResponse(success: true, stdout: ""))
await gateway.setReadFile(FileReadResponse(success: true, content: bytes.base64EncodedString()))
let model = makeModel(gateway, path: "assets/logo.PNG")
model.start()
#expect(await settled(model))
#expect(model.content == .image(data: bytes, mimeType: "image/png"))
#expect(model.mode == .file)
}
@Test func nulBytesClassifyAsBinary() async {
let gateway = FakeFilesGateway()
await gateway.setDiffFile(GitCommandResponse(success: true, stdout: ""))
await gateway.setReadFile(FileReadResponse(
success: true,
content: Data([104, 105, 0, 106]).base64EncodedString()
))
let model = makeModel(gateway, path: "blob.bin")
model.start()
#expect(await settled(model))
#expect(model.content == .binary)
}
@Test func readFailureSurfacesItsMessage() async {
let gateway = FakeFilesGateway()
await gateway.setDiffFile(GitCommandResponse(success: true, stdout: ""))
await gateway.setReadFile(FileReadResponse(success: false, error: "File not found"))
let model = makeModel(gateway)
model.start()
#expect(await settled(model))
#expect(model.content == .failed("File not found"))
}
@Test func invalidBase64ClassifiesAsBinary() async {
let gateway = FakeFilesGateway()
await gateway.setDiffFile(GitCommandResponse(success: true, stdout: ""))
await gateway.setReadFile(FileReadResponse(success: true, content: "%%%not-base64%%%"))
let model = makeModel(gateway)
model.start()
#expect(await settled(model))
#expect(model.content == .binary)
}
}