Files
TUIkit/Tests/TUIkitTests/LocalizationServiceTests.swift
T
phranck db8ea40c0a Refactor: Fix SwiftLint warnings and refactor StatusBar to _StatusBarCore pattern
- Fix 80 SwiftLint warnings (159 -> 79): vertical_whitespace, prefer_self_in_static_references, modifier_order, trailing_newline, trailing_whitespace, prefer_for_where, unneeded_synthesized_initializer, redundant_type_annotation, implicit_optional_initialization, superfluous_disable_command, shorthand_optional_binding, syntactic_sugar, empty_string, vertical_whitespace_closing_braces, identifier_name in BadgeModifier
- Refactor StatusBar from direct Renderable to _StatusBarCore pattern (public View with real body wrapping private Renderable core)
2026-02-15 02:35:18 +01:00

358 lines
13 KiB
Swift
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 🖥 TUIKit Terminal UI Kit for Swift
// LocalizationServiceTests.swift
//
// Created by LAYERED.work
// License: MIT
import Foundation
import Testing
@testable import TUIkit
// MARK: - Localization Service Tests
@Suite("LocalizationService")
final class LocalizationServiceTests {
var sut: LocalizationService
init() {
sut = LocalizationService()
sut.setLanguage(.english)
}
// MARK: - Bundle Loading Tests
@Test("Loads English translations from bundle")
func loadEnglishTranslations() {
let sut = LocalizationService()
let englishStrings = sut.string(for: "button.ok")
#expect(englishStrings == "OK")
}
@Test("Loads German translations from bundle")
func loadGermanTranslations() {
let sut = LocalizationService()
sut.setLanguage(.german)
let germanStrings = sut.string(for: "button.ok")
#expect(germanStrings == "OK")
}
@Test("Loads French translations from bundle")
func loadFrenchTranslations() {
let sut = LocalizationService()
sut.setLanguage(.french)
let frenchStrings = sut.string(for: "button.cancel")
#expect(frenchStrings == "Annuler")
}
@Test("Loads Italian translations from bundle")
func loadItalianTranslations() {
let sut = LocalizationService()
sut.setLanguage(.italian)
let italianStrings = sut.string(for: "button.yes")
#expect(italianStrings == "")
}
@Test("Loads Spanish translations from bundle")
func loadSpanishTranslations() {
let sut = LocalizationService()
sut.setLanguage(.spanish)
let spanishStrings = sut.string(for: "button.no")
#expect(spanishStrings == "No")
}
// MARK: - String Resolution Tests
@Test("Resolves dot-notation keys")
func resolvesDotNotationKeys() {
let string = sut.string(for: "button.ok")
#expect(string == "OK")
}
@Test("Resolves nested keys")
func resolvesNestedKeys() {
let string = sut.string(for: "error.invalid_input")
#expect(string == "Invalid input")
}
@Test("Resolves all key categories")
func resolvesAllKeyCategories() {
#expect(sut.string(for: "button.save") == "Save")
#expect(sut.string(for: "label.name") == "Name")
#expect(sut.string(for: "error.not_found") == "Not found")
#expect(sut.string(for: "placeholder.search") == "Search...")
#expect(sut.string(for: "menu.file") == "File")
#expect(sut.string(for: "dialog.confirm") == "Confirm")
#expect(sut.string(for: "validation.email_invalid") == "Invalid email address")
}
// MARK: - Fallback Tests
@Test("Falls back to English when key missing in current language")
func fallsBackToEnglish() {
sut.setLanguage(.german)
let string = sut.string(for: "button.ok")
#expect(string != "button.ok")
}
@Test("Returns key when not found in any language")
func returnsKeyWhenNotFound() {
let unknownKey = "nonexistent.key.that.does.not.exist"
let string = sut.string(for: unknownKey)
#expect(string == unknownKey)
}
@Test("Fallback chain works correctly")
func fallbackChain() {
sut.setLanguage(.german)
let germanString = sut.string(for: "button.ok")
#expect(germanString == "OK")
sut.setLanguage(.english)
let englishString = sut.string(for: "button.ok")
#expect(englishString == "OK")
#expect(germanString == englishString)
}
// MARK: - Language Switching Tests
@Test("Switches language successfully")
func switchesLanguage() {
sut.setLanguage(.english)
#expect(sut.currentLanguage == .english)
sut.setLanguage(.german)
#expect(sut.currentLanguage == .german)
sut.setLanguage(.french)
#expect(sut.currentLanguage == .french)
}
@Test("Language property returns current language")
func languageProperty() {
sut.setLanguage(.english)
#expect(sut.currentLanguage == .english)
sut.setLanguage(.italian)
#expect(sut.currentLanguage == .italian)
}
@Test("Resolves strings after language switch")
func resolvesStringsAfterSwitch() {
sut.setLanguage(.english)
var string = sut.string(for: "button.save")
#expect(string == "Save")
sut.setLanguage(.german)
string = sut.string(for: "button.save")
#expect(string == "Speichern")
sut.setLanguage(.french)
string = sut.string(for: "button.save")
#expect(string == "Enregistrer")
}
// MARK: - Persistence Tests
@Test("Saves language preference to config file")
func savesLanguagePreference() throws {
let tempDir = NSTemporaryDirectory() + "tuikit-test-\(UUID().uuidString)"
defer { try? FileManager.default.removeItem(atPath: tempDir) }
let service = LocalizationService(configDirectoryPath: tempDir)
service.setLanguage(.german)
let path = (tempDir as NSString).appendingPathComponent("language")
let content = try String(contentsOfFile: path, encoding: .utf8).trimmingCharacters(
in: .whitespacesAndNewlines
)
#expect(content == "de")
}
@Test("Loads language preference from config file")
func loadsLanguagePreference() throws {
let tempDir = NSTemporaryDirectory() + "tuikit-test-\(UUID().uuidString)"
defer { try? FileManager.default.removeItem(atPath: tempDir) }
try FileManager.default.createDirectory(
atPath: tempDir,
withIntermediateDirectories: true,
attributes: nil
)
let path = (tempDir as NSString).appendingPathComponent("language")
try "fr".write(toFile: path, atomically: true, encoding: .utf8)
let service = LocalizationService(configDirectoryPath: tempDir)
#expect(service.currentLanguage == .french)
}
@Test("Handles missing config file gracefully")
func handlesMissingConfigFile() {
let tempDir = NSTemporaryDirectory() + "tuikit-test-\(UUID().uuidString)"
defer { try? FileManager.default.removeItem(atPath: tempDir) }
let service = LocalizationService(configDirectoryPath: tempDir)
#expect(service.currentLanguage == .english)
}
@Test("Handles invalid config file content gracefully")
func handlesInvalidConfigFile() throws {
let tempDir = NSTemporaryDirectory() + "tuikit-test-\(UUID().uuidString)"
defer { try? FileManager.default.removeItem(atPath: tempDir) }
try FileManager.default.createDirectory(
atPath: tempDir,
withIntermediateDirectories: true,
attributes: nil
)
let path = (tempDir as NSString).appendingPathComponent("language")
try "invalid_lang_code".write(toFile: path, atomically: true, encoding: .utf8)
let service = LocalizationService(configDirectoryPath: tempDir)
#expect(service.currentLanguage == .english)
}
// MARK: - Language Enum Tests
@Test("Language enum raw values are correct")
func languageRawValues() {
#expect(LocalizationService.Language.english.rawValue == "en")
#expect(LocalizationService.Language.german.rawValue == "de")
#expect(LocalizationService.Language.french.rawValue == "fr")
#expect(LocalizationService.Language.italian.rawValue == "it")
#expect(LocalizationService.Language.spanish.rawValue == "es")
}
@Test("Language enum display names are correct")
func languageDisplayNames() {
#expect(LocalizationService.Language.english.displayName == "English")
#expect(LocalizationService.Language.german.displayName == "Deutsch")
#expect(LocalizationService.Language.french.displayName == "Français")
#expect(LocalizationService.Language.italian.displayName == "Italiano")
#expect(LocalizationService.Language.spanish.displayName == "Español")
}
@Test("Language enum is codable")
func languageIsCodable() {
let encoder = JSONEncoder()
let decoder = JSONDecoder()
let english = LocalizationService.Language.english
let encodedEnglish = try? encoder.encode(english)
let decodedEnglish = try? decoder.decode(LocalizationService.Language.self, from: encodedEnglish ?? Data())
#expect(decodedEnglish == english)
let german = LocalizationService.Language.german
let encodedGerman = try? encoder.encode(german)
let decodedGerman = try? decoder.decode(LocalizationService.Language.self, from: encodedGerman ?? Data())
#expect(decodedGerman == german)
}
// MARK: - Caching Tests
@Test("Caches translation dictionaries")
func cacheTranslations() {
_ = sut.string(for: "button.ok")
let string1 = sut.string(for: "button.ok")
let string2 = sut.string(for: "button.ok")
#expect(string1 == string2)
}
@Test("Cache is per-language")
func cacheIsPerLanguage() {
sut.setLanguage(.english)
let englishString = sut.string(for: "button.save")
#expect(englishString == "Save")
sut.setLanguage(.german)
let germanString = sut.string(for: "button.save")
#expect(germanString == "Speichern")
sut.setLanguage(.english)
let englishAgain = sut.string(for: "button.save")
#expect(englishAgain == "Save")
}
}
// MARK: - Localization Key Tests
@Suite("LocalizationKey")
final class LocalizationKeyTests {
var service: LocalizationService
init() {
service = LocalizationService()
service.setLanguage(.english)
}
@Test("Button keys resolve correctly")
func buttonKeys() {
#expect(service.string(for: LocalizationKey.Button.ok) == "OK")
#expect(service.string(for: LocalizationKey.Button.cancel) == "Cancel")
#expect(service.string(for: LocalizationKey.Button.save) == "Save")
#expect(service.string(for: LocalizationKey.Button.delete) == "Delete")
}
@Test("Label keys resolve correctly")
func labelKeys() {
#expect(service.string(for: LocalizationKey.Label.name) == "Name")
#expect(service.string(for: LocalizationKey.Label.description) == "Description")
#expect(service.string(for: LocalizationKey.Label.value) == "Value")
#expect(service.string(for: LocalizationKey.Label.status) == "Status")
}
@Test("Error keys resolve correctly")
func errorKeys() {
#expect(service.string(for: LocalizationKey.Error.invalidInput) == "Invalid input")
#expect(service.string(for: LocalizationKey.Error.notFound) == "Not found")
#expect(service.string(for: LocalizationKey.Error.accessDenied) == "Access denied")
}
@Test("Placeholder keys resolve correctly")
func placeholderKeys() {
#expect(service.string(for: LocalizationKey.Placeholder.search) == "Search...")
#expect(service.string(for: LocalizationKey.Placeholder.enterText) == "Enter text...")
#expect(service.string(for: LocalizationKey.Placeholder.selectOption) == "Select an option...")
}
@Test("Menu keys resolve correctly")
func menuKeys() {
#expect(service.string(for: LocalizationKey.Menu.file) == "File")
#expect(service.string(for: LocalizationKey.Menu.edit) == "Edit")
#expect(service.string(for: LocalizationKey.Menu.view) == "View")
}
@Test("Dialog keys resolve correctly")
func dialogKeys() {
#expect(service.string(for: LocalizationKey.Dialog.confirm) == "Confirm")
#expect(service.string(for: LocalizationKey.Dialog.deleteConfirmation) == "Are you sure you want to delete this?")
#expect(service.string(for: LocalizationKey.Dialog.success) == "Operation completed successfully")
}
@Test("Validation keys resolve correctly")
func validationKeys() {
#expect(service.string(for: LocalizationKey.Validation.emailInvalid) == "Invalid email address")
#expect(service.string(for: LocalizationKey.Validation.passwordTooShort) == "Password must be at least 8 characters")
#expect(service.string(for: LocalizationKey.Validation.usernameTaken) == "Username already exists")
}
@Test("Keys work across all languages")
func keysAcrossLanguages() {
service.setLanguage(.german)
#expect(service.string(for: LocalizationKey.Button.ok) == "OK")
service.setLanguage(.french)
#expect(service.string(for: LocalizationKey.Button.cancel) == "Annuler")
service.setLanguage(.italian)
#expect(service.string(for: LocalizationKey.Error.notFound) == "Non trovato")
}
@Test("Key enum raw values match string keys")
func keyRawValues() {
#expect(LocalizationKey.Button.ok.rawValue == "button.ok")
#expect(LocalizationKey.Error.notFound.rawValue == "error.not_found")
#expect(LocalizationKey.Label.name.rawValue == "label.name")
}
}