Files
xiaomihome/miot/miot_i18n_test.go
T
4566704 35efed7d15 chore: 修复 import 路径为 xiaomihome/miot,补充 README 项目文档
go.mod 模块名为 xiaomihome,将所有内部 import 从 "miot" 更新为
"xiaomihome/miot",涉及所有测试文件和示例文件。新增 README.md 项目
介绍文档(架构概览、模块说明、移植进度等)。新增 ARCH_PLAN.md 架构
设计文档。
2026-06-28 23:21:18 +08:00

361 lines
10 KiB
Go

// Package miot_test provides black-box tests for miot_i18n.go internationalization.
package miot_test
import (
"os"
"path/filepath"
"testing"
miot "xiaomihome/miot"
)
// ============================================================================
// NewMIoTI18n
// ============================================================================
func TestI18n_New(t *testing.T) {
i18n := miot.NewMIoTI18n("zh-Hans")
if i18n == nil {
t.Fatal("NewMIoTI18n returned nil")
}
// Should not panic on any operation before Init
s := i18n.Translate("any.key", nil)
if s != "" {
t.Errorf("expected empty string before Init, got %q", s)
}
}
func TestI18n_NewEmptyLang(t *testing.T) {
// Empty language should still create valid instance
i18n := miot.NewMIoTI18n("")
if i18n == nil {
t.Fatal("NewMIoTI18n with empty lang returned nil")
}
}
// ============================================================================
// Init / Deinit
// ============================================================================
func TestI18n_InitWithValidJSON(t *testing.T) {
// Create a temporary i18n JSON file
i18nDir := filepath.Join(t.TempDir(), "i18n")
os.MkdirAll(i18nDir, 0755)
jsonContent := `{
"device": {
"light": {
"name": "Light",
"description": "Smart Light"
},
"sensor": {
"temperature": "Temperature Sensor"
}
},
"common": {
"on": "On",
"off": "Off"
}
}`
os.WriteFile(filepath.Join(i18nDir, "en.json"), []byte(jsonContent), 0644)
// Override RootPath for the test
origRoot := miot.RootPath
miot.RootPath = filepath.Dir(i18nDir)
defer func() { miot.RootPath = origRoot }()
i18n := miot.NewMIoTI18n("en")
i18n.Init()
// Verify translation
result := i18n.Translate("device.light.name", nil)
if result != "Light" {
t.Errorf("Translate(device.light.name) = %q, want %q", result, "Light")
}
result = i18n.Translate("device.sensor.temperature", nil)
if result != "Temperature Sensor" {
t.Errorf("Translate = %q, want %q", result, "Temperature Sensor")
}
}
func TestI18n_InitTwice(t *testing.T) {
// Should not reload if already initialized
i18n := miot.NewMIoTI18n("en")
i18n.Init() // first init
i18n.Init() // second init should be no-op
// Just verify no panic
}
// ============================================================================
// Deinit
// ============================================================================
func TestI18n_Deinit(t *testing.T) {
i18nDir := filepath.Join(t.TempDir(), "i18n")
os.MkdirAll(i18nDir, 0755)
os.WriteFile(filepath.Join(i18nDir, "en.json"), []byte(`{"greeting": "Hello"}`), 0644)
origRoot := miot.RootPath
miot.RootPath = filepath.Dir(i18nDir)
defer func() { miot.RootPath = origRoot }()
i18n := miot.NewMIoTI18n("en")
i18n.Init()
// Before Deinit
result := i18n.Translate("greeting", nil)
if result != "Hello" {
t.Fatalf("Translate before Deinit should work, got %q", result)
}
// After Deinit
i18n.Deinit()
result = i18n.Translate("greeting", nil)
if result != "" {
t.Errorf("Translate after Deinit should return empty, got %q", result)
}
}
// ============================================================================
// Init with missing file
// ============================================================================
func TestI18n_InitMissingFile(t *testing.T) {
origRoot := miot.RootPath
miot.RootPath = t.TempDir()
defer func() { miot.RootPath = origRoot }()
i18n := miot.NewMIoTI18n("fantasy-lang")
// Should not panic
i18n.Init()
// Translate should return empty
result := i18n.Translate("any.key", nil)
if result != "" {
t.Errorf("translate with missing file should return empty, got %q", result)
}
}
// ============================================================================
// Translate — nested keys
// ============================================================================
func TestI18n_TranslateNestedKey(t *testing.T) {
i18nDir := filepath.Join(t.TempDir(), "i18n")
os.MkdirAll(i18nDir, 0755)
jsonContent := `{
"a": {
"b": {
"c": "deep_value"
}
}
}`
os.WriteFile(filepath.Join(i18nDir, "en.json"), []byte(jsonContent), 0644)
origRoot := miot.RootPath
miot.RootPath = filepath.Dir(i18nDir)
defer func() { miot.RootPath = origRoot }()
i18n := miot.NewMIoTI18n("en")
i18n.Init()
result := i18n.Translate("a.b.c", nil)
if result != "deep_value" {
t.Errorf("nested translate = %q, want 'deep_value'", result)
}
}
// ============================================================================
// Translate — missing key
// ============================================================================
func TestI18n_TranslateMissingKey(t *testing.T) {
i18nDir := filepath.Join(t.TempDir(), "i18n")
os.MkdirAll(i18nDir, 0755)
os.WriteFile(filepath.Join(i18nDir, "en.json"), []byte(`{"hello": "world"}`), 0644)
origRoot := miot.RootPath
miot.RootPath = filepath.Dir(i18nDir)
defer func() { miot.RootPath = origRoot }()
i18n := miot.NewMIoTI18n("en")
i18n.Init()
// Missing key
result := i18n.Translate("missing.key", nil)
if result != "" {
t.Errorf("missing key translate = %q, want empty", result)
}
// Partial match
result = i18n.Translate("hello.world", nil)
if result != "" {
t.Errorf("partial match translate = %q, want empty (hello is not a map)", result)
}
}
// ============================================================================
// Translate — placeholders
// ============================================================================
func TestI18n_TranslateWithReplace(t *testing.T) {
i18nDir := filepath.Join(t.TempDir(), "i18n")
os.MkdirAll(i18nDir, 0755)
jsonContent := `{
"message": "Hello {name}, you have {count} messages"
}`
os.WriteFile(filepath.Join(i18nDir, "en.json"), []byte(jsonContent), 0644)
origRoot := miot.RootPath
miot.RootPath = filepath.Dir(i18nDir)
defer func() { miot.RootPath = origRoot }()
i18n := miot.NewMIoTI18n("en")
i18n.Init()
result := i18n.Translate("message", map[string]string{
"name": "Alice",
"count": "5",
})
want := "Hello Alice, you have 5 messages"
if result != want {
t.Errorf("Translate with replace = %q, want %q", result, want)
}
}
func TestI18n_TranslateWithReplaceNil(t *testing.T) {
i18nDir := filepath.Join(t.TempDir(), "i18n")
os.MkdirAll(i18nDir, 0755)
os.WriteFile(filepath.Join(i18nDir, "en.json"), []byte(`{"greet": "Hello {name}"}`), 0644)
origRoot := miot.RootPath
miot.RootPath = filepath.Dir(i18nDir)
defer func() { miot.RootPath = origRoot }()
i18n := miot.NewMIoTI18n("en")
i18n.Init()
// replace = nil → no substitution
result := i18n.Translate("greet", nil)
if result != "Hello {name}" {
t.Errorf("Translate with nil replace = %q, want 'Hello {name}'", result)
}
}
func TestI18n_TranslateWithPartialReplace(t *testing.T) {
i18nDir := filepath.Join(t.TempDir(), "i18n")
os.MkdirAll(i18nDir, 0755)
os.WriteFile(filepath.Join(i18nDir, "en.json"), []byte(`{"msg": "Hello {name} from {city}"}`), 0644)
origRoot := miot.RootPath
miot.RootPath = filepath.Dir(i18nDir)
defer func() { miot.RootPath = origRoot }()
i18n := miot.NewMIoTI18n("en")
i18n.Init()
// Only replace "name", leave "{city}" as-is
result := i18n.Translate("msg", map[string]string{"name": "Bob"})
if result != "Hello Bob from {city}" {
t.Errorf("partial replace = %q, want 'Hello Bob from {city}'", result)
}
}
// ============================================================================
// Translate — non-string value
// ============================================================================
func TestI18n_TranslateNonStringValue(t *testing.T) {
i18nDir := filepath.Join(t.TempDir(), "i18n")
os.MkdirAll(i18nDir, 0755)
// Value is a nested object (map), not a string
jsonContent := `{
"config": {
"settings": {
"timeout": 30
}
}
}`
os.WriteFile(filepath.Join(i18nDir, "en.json"), []byte(jsonContent), 0644)
origRoot := miot.RootPath
miot.RootPath = filepath.Dir(i18nDir)
defer func() { miot.RootPath = origRoot }()
i18n := miot.NewMIoTI18n("en")
i18n.Init()
// config.settings is an object, not string → return ""
result := i18n.Translate("config.settings", nil)
if result != "" {
t.Errorf("non-string value should return empty, got %q", result)
}
}
// ============================================================================
// Translate — empty key
// ============================================================================
func TestI18n_TranslateEmptyKey(t *testing.T) {
i18n := miot.NewMIoTI18n("en")
result := i18n.Translate("", nil)
if result != "" {
t.Errorf("empty key should return empty, got %q", result)
}
result = i18n.Translate("", map[string]string{"x": "y"})
if result != "" {
t.Errorf("empty key with replace should return empty, got %q", result)
}
}
// ============================================================================
// Init to verify i18n JSON files exist on disk
// ============================================================================
func TestI18n_InitFromProjectFiles(t *testing.T) {
// This test verifies that actual i18n JSON files exist in the project
// and can be loaded. Relies on RootPath being set to workspace.
// Find the i18n directory relative to test file
// The i18n directory is at go-miot/i18n/
i18nDir := filepath.Join(miot.RootPath, "i18n")
entries, err := os.ReadDir(i18nDir)
if err != nil {
t.Skipf("i18n directory not found at %s, skipping: %v", i18nDir, err)
}
if len(entries) == 0 {
t.Skip("i18n directory is empty, skipping")
}
// Pick first JSON file
var firstLang string
for _, e := range entries {
if filepath.Ext(e.Name()) == ".json" {
firstLang = e.Name()[:len(e.Name())-5] // strip .json
break
}
}
if firstLang == "" {
t.Skip("no JSON files in i18n directory, skipping")
}
i18n := miot.NewMIoTI18n(firstLang)
i18n.Init()
// Should have data loaded
// Test a few common translation keys
commonKeys := []string{
"device.switch.on",
"device.switch.off",
}
for _, key := range commonKeys {
result := i18n.Translate(key, nil)
if result == "" {
t.Logf("note: key %q has no translation for %s", key, firstLang)
}
}
}