Merge branch 'main' into chore/ponytail-cleanup

This commit is contained in:
Amruth Pillai
2026-07-04 20:40:21 +02:00
49 changed files with 236 additions and 52 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: am\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Amharic\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: ar\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Arabic\n"
"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: az\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Azerbaijani\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: bg\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Bulgarian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: bn\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Bengali\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: ca\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Catalan\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: cs\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Czech\n"
"Plural-Forms: nplurals=4; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 3;\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: da\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Danish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: de\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: German\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: el\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Greek\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: es\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Spanish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: fa\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Persian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: fi\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Finnish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: he\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Hebrew\n"
"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: hi\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Hindi\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: hu\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Hungarian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: id\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Indonesian\n"
"Plural-Forms: nplurals=1; plural=0;\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: it\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Italian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: km\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Khmer\n"
"Plural-Forms: nplurals=1; plural=0;\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: kn\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Kannada\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: ko\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Korean\n"
"Plural-Forms: nplurals=1; plural=0;\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: lt\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Lithuanian\n"
"Plural-Forms: nplurals=4; plural=(n%10==1 && (n%100>19 || n%100<11) ? 0 : (n%10>=2 && n%10<=9) && (n%100>19 || n%100<11) ? 1 : n%1!=0 ? 2: 3);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: lv\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Latvian\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2;\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: ml\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Malayalam\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: mr\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Marathi\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: ms\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Malay\n"
"Plural-Forms: nplurals=1; plural=0;\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: ne\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Nepali\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: nl\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Dutch\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: no\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Norwegian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: or\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Odia\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: pl\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Polish\n"
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: pt\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Portuguese, Brazilian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: pt\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Portuguese\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: ro\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Romanian\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100>0 && n%100<20)) ? 1 : 2);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: ru\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Russian\n"
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: sk\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Slovak\n"
"Plural-Forms: nplurals=4; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 3;\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: sq\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Albanian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: sr\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Serbian (Cyrillic)\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: sv\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Swedish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: ta\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Tamil\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: te\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Telugu\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: th\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Thai\n"
"Plural-Forms: nplurals=1; plural=0;\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: tr\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Turkish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: uz\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Uzbek\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: vi\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Vietnamese\n"
"Plural-Forms: nplurals=1; plural=0;\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: zh\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Chinese Simplified\n"
"Plural-Forms: nplurals=1; plural=0;\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: zh\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-07-04 16:06\n"
"PO-Revision-Date: 2026-07-04 17:07\n"
"Last-Translator: \n"
"Language-Team: Chinese Traditional\n"
"Plural-Forms: nplurals=1; plural=0;\n"
@@ -0,0 +1,169 @@
// @vitest-environment happy-dom
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { i18n } from "@lingui/core";
import { I18nProvider } from "@lingui/react";
type MutationName = "create" | "test" | "update" | "delete";
type MockProvider = {
id: string;
label: string;
provider: "openai";
model: string;
baseURL: string;
enabled: boolean;
testStatus: string;
testError: string | null;
apiKeyPreview: string;
apiKeyFingerprint: string;
lastTestedAt: Date | null;
lastUsedAt: Date | null;
createdAt: Date;
updatedAt: Date;
};
type MutationOptions = {
name: MutationName;
meta?: { noInvalidate?: boolean };
};
type ComboboxProps = {
id: string;
value: string;
options: { value: string; label: string }[];
onValueChange: (value: string) => void;
};
const queryClient = vi.hoisted(() => ({
invalidateQueries: vi.fn(),
setQueryData: vi.fn(),
}));
const mutations = vi.hoisted(() => ({
create: vi.fn(),
test: vi.fn(),
update: vi.fn(),
delete: vi.fn(),
}));
const mutationOptions = vi.hoisted(() => ({
create: vi.fn((options?: MutationOptions) => ({ ...options, name: "create" })),
test: vi.fn((options?: MutationOptions) => ({ ...options, name: "test" })),
update: vi.fn((options?: MutationOptions) => ({ ...options, name: "update" })),
delete: vi.fn((options?: MutationOptions) => ({ ...options, name: "delete" })),
}));
vi.mock("@tanstack/react-query", () => ({
useQuery: () => ({ data: [], isLoading: false, error: null }),
useQueryClient: () => queryClient,
useMutation: (options: MutationOptions) => ({
isPending: false,
mutate: (
input: unknown,
handlers?: { onSuccess?: (data: unknown) => void; onError?: (error: unknown) => void },
) => {
mutations[options.name](input).then(handlers?.onSuccess).catch(handlers?.onError);
},
mutateAsync: mutations[options.name],
}),
}));
vi.mock("@/libs/orpc/client", () => ({
orpc: {
aiProviders: {
list: {
queryOptions: () => ({ queryKey: ["aiProviders", "list"] }),
queryKey: () => ["aiProviders", "list"],
},
create: { mutationOptions: mutationOptions.create },
test: { mutationOptions: mutationOptions.test },
update: { mutationOptions: mutationOptions.update },
delete: { mutationOptions: mutationOptions.delete },
},
},
}));
vi.mock("@/components/ui/combobox", () => ({
Combobox: ({ id, value, options, onValueChange }: ComboboxProps) => (
<select id={id} value={value} onChange={(event) => onValueChange(event.currentTarget.value)}>
{options.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
),
}));
vi.mock("sonner", () => ({ toast: { error: vi.fn(), success: vi.fn() } }));
i18n.loadAndActivate({ locale: "en", messages: {} });
const { AISettingsSection } = await import("./ai-section");
const provider = (overrides: Partial<MockProvider>): MockProvider => ({
id: "provider-1",
label: "OpenAI",
provider: "openai",
model: "gpt-4.1",
baseURL: "https://api.openai.com/v1",
enabled: false,
testStatus: "untested",
testError: null,
apiKeyPreview: "sk-p...test",
apiKeyFingerprint: "fingerprint",
lastTestedAt: null,
lastUsedAt: null,
createdAt: new Date("2026-07-04T00:00:00Z"),
updatedAt: new Date("2026-07-04T00:00:00Z"),
...overrides,
});
const renderSection = () =>
render(
<I18nProvider i18n={i18n}>
<AISettingsSection />
</I18nProvider>,
);
describe("AISettingsSection", () => {
beforeEach(() => {
queryClient.invalidateQueries.mockReset();
queryClient.setQueryData.mockReset();
mutationOptions.create.mockClear();
mutationOptions.test.mockClear();
mutationOptions.update.mockClear();
mutationOptions.delete.mockClear();
mutations.create.mockReset();
mutations.test.mockReset();
mutations.update.mockReset();
mutations.delete.mockReset();
});
it("uses the save-and-test result as the connected provider row", async () => {
const created = provider({});
const tested = provider({ enabled: true, testStatus: "success", lastTestedAt: new Date("2026-07-04T01:00:00Z") });
mutations.create.mockResolvedValue(created);
mutations.test.mockResolvedValue(tested);
renderSection();
fireEvent.change(screen.getByLabelText("API Key"), { target: { value: "sk-test" } });
fireEvent.click(screen.getByRole("button", { name: /save & test provider/i }));
await waitFor(() => expect(queryClient.setQueryData).toHaveBeenCalled());
expect(mutationOptions.create).toHaveBeenCalledWith({ meta: { noInvalidate: true } });
expect(mutationOptions.test).toHaveBeenCalledWith({ meta: { noInvalidate: true } });
expect(mutations.update).not.toHaveBeenCalled();
const [, updater] = queryClient.setQueryData.mock.calls.at(-1) as [
unknown,
(providers: MockProvider[]) => MockProvider[],
];
expect(updater([created])).toEqual([tested]);
});
});
@@ -122,6 +122,13 @@ function providerLabel(provider: AIProvider) {
return providerOptions.find((option) => option.value === provider)?.label ?? provider;
}
function upsertProvider(providers: SavedProvider[] | undefined, provider: SavedProvider) {
if (!providers) return [provider];
if (!providers.some((entry) => entry.id === provider.id)) return [...providers, provider];
return providers.map((entry) => (entry.id === provider.id ? provider : entry));
}
function isAiProviderConfigError(error: unknown) {
if (error instanceof ORPCError && error.code === "PRECONDITION_FAILED") return true;
@@ -247,11 +254,18 @@ function CreateProviderForm() {
[form.provider],
);
const invalidate = () => queryClient.invalidateQueries({ queryKey: orpc.aiProviders.list.queryKey() });
const setProviderInCache = (provider: SavedProvider) =>
queryClient.setQueryData(orpc.aiProviders.list.queryKey(), (providers: SavedProvider[] | undefined) =>
upsertProvider(providers, provider),
);
const { mutateAsync: createProvider, isPending: isCreating } = useMutation(orpc.aiProviders.create.mutationOptions());
const { mutateAsync: testProvider, isPending: isTesting } = useMutation(orpc.aiProviders.test.mutationOptions());
const { mutateAsync: enableProvider, isPending: isEnabling } = useMutation(orpc.aiProviders.update.mutationOptions());
const isSaving = isCreating || isTesting || isEnabling;
const { mutateAsync: createProvider, isPending: isCreating } = useMutation(
orpc.aiProviders.create.mutationOptions({ meta: { noInvalidate: true } }),
);
const { mutateAsync: testProvider, isPending: isTesting } = useMutation(
orpc.aiProviders.test.mutationOptions({ meta: { noInvalidate: true } }),
);
const isSaving = isCreating || isTesting;
// Model/label are prefilled from provider defaults, so step 1 (Provider + API Key) is enough to save.
const model = form.model.trim();
@@ -271,8 +285,9 @@ function CreateProviderForm() {
// Test on save: verify the connection immediately instead of leaving it to a manual step.
const tested = await testProvider({ id: created.id });
setProviderInCache(tested);
if (tested.testStatus === "success") {
await enableProvider({ id: created.id, enabled: true });
setForm(emptyForm);
setResult({ ok: true, message: t`Connection verified — provider is ready to use.` });
} else {