返回 DeepSeek-Reasonix
tabs_locale_test.go
根目录 / desktop / tabs_locale_test.go
1 package main
2
3 import (
4 "context"
5 "os"
6 "testing"
7 "time"
8
9 "reasonix/internal/config"
10 "reasonix/internal/control"
11 "reasonix/internal/sessioncatalog"
12 )
13
14 func TestDefaultTopicTitleLocalizesAtAPIBoundary(t *testing.T) {
15 app := &App{}
16 for _, tt := range []struct {
17 locale string
18 want string
19 }{
20 {locale: "en-US", want: defaultTopicTitleEn},
21 {locale: "zh-CN", want: defaultTopicTitle},
22 {locale: "zh-TW", want: defaultTopicTitleZhTW},
23 } {
24 app.setDesktopLocale(tt.locale)
25 if got := app.localizedTopicTitle(defaultTopicTitle, topicTitleSourceAuto); got != tt.want {
26 t.Fatalf("locale %q title = %q, want %q", tt.locale, got, tt.want)
27 }
28 }
29 }
30
31 func TestManualDefaultTopicTitleIsNotLocalized(t *testing.T) {
32 app := &App{}
33 for _, tt := range []struct {
34 locale string
35 title string
36 }{
37 {locale: "zh-CN", title: defaultTopicTitleEn},
38 {locale: "en-US", title: defaultTopicTitle},
39 {locale: "zh-CN", title: defaultTopicTitleZhTW},
40 } {
41 app.setDesktopLocale(tt.locale)
42 if got := app.localizedTopicTitle(tt.title, topicTitleSourceManual); got != tt.title {
43 t.Fatalf("locale %q manual title = %q, want %q", tt.locale, got, tt.title)
44 }
45 }
46 }
47
48 func TestCreateTopicPreservesManualDefaultTitle(t *testing.T) {
49 isolateDesktopUserDirs(t)
50 app := &App{}
51 app.projectTreeChangedHook = func() {}
52 app.setDesktopLocale("zh-CN")
53
54 manual, err := app.CreateTopic("global", "", defaultTopicTitleEn)
55 if err != nil {
56 t.Fatalf("CreateTopic manual: %v", err)
57 }
58 if manual.Title != defaultTopicTitleEn {
59 t.Fatalf("manual title = %q, want %q", manual.Title, defaultTopicTitleEn)
60 }
61 if got := loadTopicTitleSource("", manual.ID); got != topicTitleSourceManual {
62 t.Fatalf("manual title source = %q, want %q", got, topicTitleSourceManual)
63 }
64 tree := mustListProjectTree(t, app)
65 if len(tree) == 0 || len(tree[0].Children) == 0 || tree[0].Children[0].Label != defaultTopicTitleEn {
66 t.Fatalf("manual project-tree title was localized: %+v", tree)
67 }
68
69 automatic, err := app.CreateTopic("global", "", "")
70 if err != nil {
71 t.Fatalf("CreateTopic automatic: %v", err)
72 }
73 if automatic.Title != defaultTopicTitle {
74 t.Fatalf("automatic title = %q, want localized %q", automatic.Title, defaultTopicTitle)
75 }
76 if err := app.RenameTopic(automatic.ID, defaultTopicTitleEn); err != nil {
77 t.Fatalf("RenameTopic manual default: %v", err)
78 }
79 if got := loadTopicTitleSource("", automatic.ID); got != topicTitleSourceManual {
80 t.Fatalf("renamed title source = %q, want %q", got, topicTitleSourceManual)
81 }
82 tree = mustListProjectTree(t, app)
83 if len(tree) == 0 || len(tree[0].Children) == 0 || tree[0].Children[0].Label != defaultTopicTitleEn {
84 t.Fatalf("renamed project-tree title was localized: %+v", tree)
85 }
86 }
87
88 func TestDefaultTopicTitleVariantsRemainPersistenceSentinels(t *testing.T) {
89 for _, title := range []string{defaultTopicTitle, defaultTopicTitleEn, defaultTopicTitleZhTW} {
90 if !isDefaultTopicTitle(title) {
91 t.Fatalf("title %q was not recognized as a default sentinel", title)
92 }
93 }
94 if got := topicTitleFromText(defaultTopicTitleEn); got != "" {
95 t.Fatalf("English default title became a generated title: %q", got)
96 }
97 }
98
99 func TestForkTopicTitleUsesHarnessNumbering(t *testing.T) {
100 app := &App{}
101 app.setDesktopLocale("en")
102 if got := app.forkTopicTitle("New session"); got != "New session (1)" {
103 t.Fatalf("English fork title = %q", got)
104 }
105 app.setDesktopLocale("zh-TW")
106 if got := app.forkTopicTitle(defaultTopicTitle); got != "新的會話 (1)" {
107 t.Fatalf("Traditional Chinese fork title = %q", got)
108 }
109 app.desktopLocale.Store(desktopLocaleUnknown)
110 if got := app.forkTopicTitle(""); got != "新的会话 (1)" {
111 t.Fatalf("legacy fallback fork title = %q", got)
112 }
113 if got := app.forkTopicTitle("Roadmap (1)"); got != "Roadmap (2)" {
114 t.Fatalf("numbered fork title = %q", got)
115 }
116 if got := app.forkTopicTitle("计划(9)"); got != "计划(10)" {
117 t.Fatalf("fullwidth numbered fork title = %q", got)
118 }
119 }
120
121 func TestSetTrayLocaleDoesNotChangeAutoCurrency(t *testing.T) {
122 isolateDesktopUserDirs(t)
123 cfg := config.Default()
124 if err := cfg.SaveTo(config.UserConfigPath()); err != nil {
125 t.Fatalf("save auto config: %v", err)
126 }
127
128 app := NewApp()
129 app.projectTreeChangedHook = func() {}
130 activeCtrl := control.New(control.Options{Label: "active"})
131 inactiveCtrl := control.New(control.Options{Label: "inactive"})
132 t.Cleanup(activeCtrl.Close)
133 t.Cleanup(inactiveCtrl.Close)
134 app.tabs = map[string]*WorkspaceTab{
135 "active": {ID: "active", Ctrl: activeCtrl},
136 "inactive": {ID: "inactive", Ctrl: inactiveCtrl},
137 }
138 app.activeTabID = "active"
139 app.setDesktopLocale("en")
140
141 if err := app.SetTrayLocale("zh-CN"); err != nil {
142 t.Fatalf("SetTrayLocale: %v", err)
143 }
144 for _, tabID := range []string{"active", "inactive"} {
145 if app.deferredRebuildPending(tabID) {
146 t.Fatalf("locale change scheduled currency refresh for %q", tabID)
147 }
148 }
149 }
150
151 func TestSetTrayLocalePublishesPresentationWithoutHistoryDiscovery(t *testing.T) {
152 isolateDesktopUserDirs(t)
153 app := NewApp()
154 scans, notifications := 0, 0
155 app.projectTreeCatalogRefreshHook = func() { scans++ }
156 app.projectTreeChangedHook = func() { notifications++ }
157 // The renderer sends this after every mount, including a restored launch
158 // whose initial background discovery may already have dispatched.
159 for _, locale := range []string{"en", "en", "zh-CN", "zh-TW"} {
160 if err := app.SetTrayLocale(locale); err != nil {
161 t.Fatal(err)
162 }
163 }
164 if scans != 0 || notifications != 4 {
165 t.Fatalf("locale must publish labels without scanning sources: scans=%d notifications=%d", scans, notifications)
166 }
167 }
168
169 func TestSetTrayLocaleKeepsExplicitCurrencyRuntime(t *testing.T) {
170 isolateDesktopUserDirs(t)
171 cfg := config.Default()
172 cfg.Desktop.Currency = "USD"
173 cfg.ApplyDeepSeekOfficialDefaultPricing()
174 if err := cfg.SaveTo(config.UserConfigPath()); err != nil {
175 t.Fatalf("save explicit currency config: %v", err)
176 }
177
178 app := NewApp()
179 app.projectTreeChangedHook = func() {}
180 ctrl := control.New(control.Options{Label: "active"})
181 t.Cleanup(ctrl.Close)
182 app.tabs = map[string]*WorkspaceTab{"active": {ID: "active", Ctrl: ctrl}}
183 app.activeTabID = "active"
184 app.setDesktopLocale("en")
185
186 if err := app.SetTrayLocale("zh-CN"); err != nil {
187 t.Fatalf("SetTrayLocale: %v", err)
188 }
189 if app.deferredRebuildPending("active") {
190 t.Fatal("explicit USD currency scheduled an automatic locale refresh")
191 }
192 }
193
194 func TestDesktopEffectivePricingCurrencyUsesLocaleOnlyForAuto(t *testing.T) {
195 app := NewApp()
196 app.setDesktopLocale("zh-CN")
197
198 cfg := config.Default()
199 if got := app.desktopEffectivePricingCurrency(cfg); got != "" {
200 t.Fatalf("auto pricing currency = %q, want unresolved auto", got)
201 }
202
203 cfg.Desktop.Language = "en"
204 cfg.ApplyDeepSeekOfficialDefaultPricing()
205 if got := app.desktopEffectivePricingCurrency(cfg); got != "" {
206 t.Fatalf("desktop language changed pricing currency = %q", got)
207 }
208
209 cfg.Desktop.Language = ""
210 cfg.Language = "en"
211 cfg.ApplyDeepSeekOfficialDefaultPricing()
212 if got := app.desktopEffectivePricingCurrency(cfg); got != "" {
213 t.Fatalf("CLI language changed pricing currency = %q", got)
214 }
215
216 cfg.Language = ""
217 cfg.Desktop.Currency = "USD"
218 cfg.ApplyDeepSeekOfficialDefaultPricing()
219 if got := app.desktopEffectivePricingCurrency(cfg); got != "USD" {
220 t.Fatalf("explicit currency pricing currency = %q, want USD", got)
221 }
222 }
223
224 func installLocaleTestCatalog(t *testing.T, app *App) *sessioncatalog.Catalog {
225 t.Helper()
226 catalog, err := sessioncatalog.Open(context.Background(), sessioncatalog.Options{InMemory: true, DisableRepair: true})
227 if err != nil {
228 t.Fatalf("open catalog: %v", err)
229 }
230 app.sessionCatalog.Store(catalog)
231 t.Cleanup(func() {
232 app.sessionCatalog.CompareAndSwap(catalog, nil)
233 _ = catalog.Close(context.Background())
234 })
235 if err := catalog.ReconcileDirectory(context.Background(), sessioncatalog.DirectoryTarget{Path: desktopSessionDir(globalWorkspaceRoot()), Scope: "global", WorkspaceRoot: ""}); err != nil {
236 t.Fatalf("reconcile catalog: %v", err)
237 }
238 if err := app.syncSessionCatalogMetadataBounded(context.Background(), catalog); err != nil {
239 t.Fatalf("sync catalog metadata: %v", err)
240 }
241 return catalog
242 }
243
244 func TestCatalogManualDefaultTitleIsNotLocalized(t *testing.T) {
245 isolateDesktopUserDirs(t)
246 app := NewApp()
247 app.projectTreeChangedHook = func() {}
248 app.setDesktopLocale("en-US")
249
250 manual, err := app.CreateTopic("global", "", defaultTopicTitle)
251 if err != nil {
252 t.Fatalf("CreateTopic manual: %v", err)
253 }
254 if manual.Title != defaultTopicTitle {
255 t.Fatalf("manual title = %q, want %q", manual.Title, defaultTopicTitle)
256 }
257 sessionDir := desktopSessionDir(globalWorkspaceRoot())
258 if err := os.MkdirAll(sessionDir, 0o755); err != nil {
259 t.Fatalf("mkdir global sessions: %v", err)
260 }
261 writeTopicSessionWithPrompt(
262 t, sessionDir, "manual-default.jsonl", manual.ID, defaultTopicTitle, "",
263 "manual default title should remain unchanged", time.Now(),
264 )
265 catalog := installLocaleTestCatalog(t, app)
266 page, err := app.catalogTopicPage(catalog, ProjectTopicPageRequest{Scope: "global", WorkspaceRoot: "", Limit: 100})
267 if err != nil {
268 t.Fatalf("catalogTopicPage: %v", err)
269 }
270 if len(page.Items) != 1 || page.Items[0].TopicID != manual.ID || page.Items[0].Label != defaultTopicTitle {
271 t.Fatalf("manual catalog topic = %+v, want topic %q with untouched label %q", page.Items, manual.ID, defaultTopicTitle)
272 }
273 }
274
274 lines GO