mirror of
https://github.com/BigMarc/grammargem.git
synced 2026-10-13 17:01:55 +02:00
Native SwiftUI menu-bar app: global hotkey → capture selected text (AX API / clipboard fallback) → grammar (Harper) / local AI (MLX) → replace in place. Free/paid entitlement gate + Lemon Squeezy device-cap licensing, $0 marginal cost per user. Heavyweight engines behind protocol stubs so it builds out of the box. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
79 lines
3.0 KiB
Swift
79 lines
3.0 KiB
Swift
import XCTest
|
|
@testable import GrammaGem
|
|
|
|
final class GrammaGemTests: XCTestCase {
|
|
|
|
// MARK: - Tiers / entitlements
|
|
|
|
func testDeviceCapsMatchSpec() {
|
|
XCTAssertEqual(Tier.solo.deviceCap, 1)
|
|
XCTAssertEqual(Tier.personal.deviceCap, 2)
|
|
XCTAssertEqual(Tier.studio.deviceCap, 4)
|
|
XCTAssertEqual(Tier.free.deviceCap, 1)
|
|
}
|
|
|
|
func testFreeEntitlementsAreLimited() {
|
|
let free = Entitlements(tier: .free)
|
|
XCTAssertFalse(free.unlimitedAIActions)
|
|
XCTAssertEqual(free.dailyAIActionCap, AppConfig.Limits.freeDailyAIActions)
|
|
XCTAssertEqual(free.dictionaryCap, AppConfig.Limits.freeDictionaryCap)
|
|
XCTAssertFalse(free.appAwareSwitching)
|
|
XCTAssertFalse(free.allModes)
|
|
XCTAssertTrue(free.unlimitedGrammar) // grammar is always free
|
|
}
|
|
|
|
func testPaidEntitlementsUnlockEverything() {
|
|
let paid = Entitlements(tier: .personal)
|
|
XCTAssertTrue(paid.unlimitedAIActions)
|
|
XCTAssertEqual(paid.dailyAIActionCap, Int.max)
|
|
XCTAssertEqual(paid.dictionaryCap, Int.max)
|
|
XCTAssertTrue(paid.appAwareSwitching)
|
|
XCTAssertTrue(paid.allModes)
|
|
}
|
|
|
|
// MARK: - Grammar (Harper stub) rules
|
|
|
|
func testHarperFixesRepeatedWordAndCasing() {
|
|
let harper = HarperEngine()
|
|
let input = "hey i wanted to checkin about the the proposal"
|
|
let fixed = harper.correct(input)
|
|
XCTAssertFalse(fixed.contains("the the"), "repeated word should be collapsed")
|
|
XCTAssertTrue(fixed.contains(" I "), "lowercase pronoun i should be capitalized")
|
|
XCTAssertTrue(fixed.contains("check in"), "checkin should split into two words")
|
|
}
|
|
|
|
func testIgnoreListSuppressesCorrection() {
|
|
let harper = HarperEngine()
|
|
harper.ignoreList = ["teh"] // pretend "teh" is an intentional brand term
|
|
let suggestions = harper.check("teh launch")
|
|
XCTAssertFalse(suggestions.contains { $0.original.lowercased() == "teh" })
|
|
}
|
|
|
|
// MARK: - Modes
|
|
|
|
func testFreeTierGetsOnlyPolishMode() {
|
|
let free = ModeRegistry.available(for: .free)
|
|
XCTAssertEqual(free.map(\.id), ["polish"])
|
|
let paid = ModeRegistry.available(for: .studio)
|
|
XCTAssertGreaterThan(paid.count, 1)
|
|
XCTAssertTrue(paid.contains { $0.id == "email" })
|
|
}
|
|
|
|
func testAppAwareModeMapping() {
|
|
XCTAssertEqual(ModeRegistry.mode(forBundleID: "com.apple.mail")?.id, "email")
|
|
XCTAssertEqual(ModeRegistry.mode(forBundleID: "com.microsoft.VSCode")?.id, "code")
|
|
XCTAssertNil(ModeRegistry.mode(forBundleID: "com.unknown.app"))
|
|
}
|
|
|
|
// MARK: - Device fingerprint
|
|
|
|
func testDeviceFingerprintIsStableAndHashed() {
|
|
let a = DeviceFingerprint.stableID()
|
|
let b = DeviceFingerprint.stableID()
|
|
XCTAssertEqual(a, b, "fingerprint must be stable across calls")
|
|
XCTAssertFalse(a.isEmpty)
|
|
// SHA-256 hex is 64 chars (unless the random fallback path is hit).
|
|
XCTAssertTrue(a.count == 64 || a.count == UUID().uuidString.count)
|
|
}
|
|
}
|