Files
calctext/calcpad-macos/Tests/CalcPadTests/FFIModelTests.swift
C. Cassel 806e2f1ec6 feat: add platform shells, CLI, formatting, plugins, tests, and benchmarks
Phase 4 — Platform shells:
- calcpad-macos/: SwiftUI two-column editor with Rust FFI bridge (16 files)
- calcpad-windows/: iced GUI with Windows 11 Fluent theme (7 files, 13 tests)
- calcpad-web/: React 18 + CodeMirror 6 + WASM Worker + PWA (20 files)
- calcpad-cli/: clap-based CLI with expression eval, pipe/stdin, JSON/CSV
  output, and interactive REPL with rustyline history

Phase 5 — Engine modules:
- formatting/: answer formatting (decimal/scientific/SI notation, thousands
  separators, currency), line type classification, clipboard values (93 tests)
- plugins/: CalcPadPlugin trait, PluginRegistry, Rhai scripting stub (43 tests)
- benches/: criterion benchmarks (single-line, 100/500-line sheets, DAG, incremental)
- tests/sheet_scenarios.rs: 20 real-world integration tests
- tests/proptest_fuzz.rs: 12 property-based fuzz tests

771 tests passing across workspace, 0 failures.
2026-03-17 09:46:40 -04:00

228 lines
7.4 KiB
Swift

import Testing
import Foundation
@testable import CalcPad
@Suite("FFI JSON Model Decoding Tests")
struct FFIModelTests {
// MARK: - Single Line Response
@Test("Decode number result")
func decodeNumberResult() throws {
let json = """
{
"schema_version": "1.0",
"result": {
"value": { "kind": "Number", "value": 42.0 },
"metadata": {
"span": { "start": 0, "end": 5 },
"result_type": "Number",
"display": "42",
"raw_value": 42.0
}
}
}
"""
let response = try JSONDecoder().decode(FFIResponse.self, from: Data(json.utf8))
#expect(response.schemaVersion == "1.0")
#expect(response.result.metadata.display == "42")
#expect(response.result.metadata.resultType == "Number")
#expect(response.result.metadata.rawValue == 42.0)
if case .number(let value) = response.result.value {
#expect(value == 42.0)
} else {
Issue.record("Expected .number variant")
}
}
@Test("Decode unit value result")
func decodeUnitValueResult() throws {
let json = """
{
"schema_version": "1.0",
"result": {
"value": { "kind": "UnitValue", "value": 5.0, "unit": "kg" },
"metadata": {
"span": { "start": 0, "end": 4 },
"result_type": "UnitValue",
"display": "5 kg",
"raw_value": 5.0
}
}
}
"""
let response = try JSONDecoder().decode(FFIResponse.self, from: Data(json.utf8))
if case .unitValue(let value, let unit) = response.result.value {
#expect(value == 5.0)
#expect(unit == "kg")
} else {
Issue.record("Expected .unitValue variant")
}
}
@Test("Decode currency value result")
func decodeCurrencyValueResult() throws {
let json = """
{
"schema_version": "1.0",
"result": {
"value": { "kind": "CurrencyValue", "amount": 19.99, "currency": "USD" },
"metadata": {
"span": { "start": 0, "end": 6 },
"result_type": "CurrencyValue",
"display": "$19.99",
"raw_value": 19.99
}
}
}
"""
let response = try JSONDecoder().decode(FFIResponse.self, from: Data(json.utf8))
if case .currencyValue(let amount, let currency) = response.result.value {
#expect(amount == 19.99)
#expect(currency == "USD")
} else {
Issue.record("Expected .currencyValue variant")
}
}
@Test("Decode datetime result")
func decodeDateTimeResult() throws {
let json = """
{
"schema_version": "1.0",
"result": {
"value": { "kind": "DateTime", "date": "2024-03-17" },
"metadata": {
"span": { "start": 0, "end": 10 },
"result_type": "DateTime",
"display": "2024-03-17",
"raw_value": null
}
}
}
"""
let response = try JSONDecoder().decode(FFIResponse.self, from: Data(json.utf8))
if case .dateTime(let date) = response.result.value {
#expect(date == "2024-03-17")
} else {
Issue.record("Expected .dateTime variant")
}
#expect(response.result.metadata.rawValue == nil)
}
@Test("Decode time delta result")
func decodeTimeDeltaResult() throws {
let json = """
{
"schema_version": "1.0",
"result": {
"value": { "kind": "TimeDelta", "days": 30, "description": "30 days" },
"metadata": {
"span": { "start": 0, "end": 8 },
"result_type": "TimeDelta",
"display": "30 days",
"raw_value": 30.0
}
}
}
"""
let response = try JSONDecoder().decode(FFIResponse.self, from: Data(json.utf8))
if case .timeDelta(let days, let desc) = response.result.value {
#expect(days == 30)
#expect(desc == "30 days")
} else {
Issue.record("Expected .timeDelta variant")
}
}
@Test("Decode boolean result")
func decodeBooleanResult() throws {
let json = """
{
"schema_version": "1.0",
"result": {
"value": { "kind": "Boolean", "value": true },
"metadata": {
"span": { "start": 0, "end": 5 },
"result_type": "Boolean",
"display": "true",
"raw_value": null
}
}
}
"""
let response = try JSONDecoder().decode(FFIResponse.self, from: Data(json.utf8))
if case .boolean(let value) = response.result.value {
#expect(value == true)
} else {
Issue.record("Expected .boolean variant")
}
}
@Test("Decode error result")
func decodeErrorResult() throws {
let json = """
{
"schema_version": "1.0",
"result": {
"value": { "kind": "Error", "message": "unexpected token", "span": { "start": 0, "end": 3 } },
"metadata": {
"span": { "start": 0, "end": 3 },
"result_type": "Error",
"display": "Error: unexpected token",
"raw_value": null
}
}
}
"""
let response = try JSONDecoder().decode(FFIResponse.self, from: Data(json.utf8))
#expect(response.result.value.isError)
if case .error(let message) = response.result.value {
#expect(message == "unexpected token")
} else {
Issue.record("Expected .error variant")
}
}
// MARK: - Sheet Response
@Test("Decode sheet response with multiple results")
func decodeSheetResponse() throws {
let json = """
{
"schema_version": "1.0",
"results": [
{
"value": { "kind": "Number", "value": 4.0 },
"metadata": { "span": { "start": 0, "end": 5 }, "result_type": "Number", "display": "4", "raw_value": 4.0 }
},
{
"value": { "kind": "Error", "message": "empty expression", "span": { "start": 0, "end": 0 } },
"metadata": { "span": { "start": 0, "end": 0 }, "result_type": "Error", "display": "Error: empty expression", "raw_value": null }
},
{
"value": { "kind": "Number", "value": 30.0 },
"metadata": { "span": { "start": 0, "end": 6 }, "result_type": "Number", "display": "30", "raw_value": 30.0 }
}
]
}
"""
let response = try JSONDecoder().decode(FFISheetResponse.self, from: Data(json.utf8))
#expect(response.results.count == 3)
#expect(response.results[0].metadata.display == "4")
#expect(response.results[1].value.isError)
#expect(response.results[2].metadata.display == "30")
}
// MARK: - FFICalcValue.isError
@Test("isError returns false for non-error values")
func isErrorFalseForNonError() throws {
let json = """
{ "kind": "Number", "value": 42.0 }
"""
let value = try JSONDecoder().decode(FFICalcValue.self, from: Data(json.utf8))
#expect(value.isError == false)
}
}