mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-10-04 16:57:48 +08:00
fix(model-config): keep model selection valid and fix admin panel edge cases
- Fall back to the default server model when a saved one disappears - Sync model config across tabs - Validation uses the base path and sends the access code - Model ids edited as drafts (no empty, duplicate or padded ids) - Credential changes reset validation; stale validation results are dropped - Admin: generateId over HTTP, env-locked group switches, discard and toggle fixes, clearing a secret field keeps the saved key, first provider not auto-default when .env sets AI_MODEL - Model selector items use unique values
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
import { cleanup, fireEvent, render, screen } from "@testing-library/react"
|
||||
import type { ReactNode } from "react"
|
||||
import { afterEach, describe, expect, it, vi } from "vitest"
|
||||
import { SecretInput, SettingField } from "@/app/[lang]/admin/setting-field"
|
||||
import { DictionaryProvider } from "@/hooks/use-dictionary"
|
||||
import type { SettingDef } from "@/lib/admin/settings-registry"
|
||||
import type { Dictionary } from "@/lib/i18n/dictionaries"
|
||||
import en from "@/lib/i18n/dictionaries/en.json"
|
||||
|
||||
const STORED = { isSet: true as const, hint: "…abcd" }
|
||||
|
||||
function withDict(node: ReactNode) {
|
||||
return (
|
||||
<DictionaryProvider dictionary={en as unknown as Dictionary}>
|
||||
{node}
|
||||
</DictionaryProvider>
|
||||
)
|
||||
}
|
||||
|
||||
function typeInto(label: string, text: string) {
|
||||
fireEvent.change(screen.getByLabelText(label, { selector: "input" }), {
|
||||
target: { value: text },
|
||||
})
|
||||
}
|
||||
|
||||
afterEach(cleanup)
|
||||
|
||||
describe("SecretInput", () => {
|
||||
it("reverts to a key saved after mount instead of deleting it", () => {
|
||||
const onChange = vi.fn()
|
||||
const props = { id: "secret", keepOnEmpty: true, onChange }
|
||||
// New provider: nothing stored at mount, then saved
|
||||
const { rerender } = render(
|
||||
withDict(
|
||||
<>
|
||||
<label htmlFor="secret">secret</label>
|
||||
<SecretInput {...props} value={undefined} />
|
||||
</>,
|
||||
),
|
||||
)
|
||||
rerender(
|
||||
withDict(
|
||||
<>
|
||||
<label htmlFor="secret">secret</label>
|
||||
<SecretInput {...props} value={STORED} />
|
||||
</>,
|
||||
),
|
||||
)
|
||||
rerender(
|
||||
withDict(
|
||||
<>
|
||||
<label htmlFor="secret">secret</label>
|
||||
<SecretInput {...props} value="abc" />
|
||||
</>,
|
||||
),
|
||||
)
|
||||
|
||||
typeInto("secret", "")
|
||||
expect(onChange).toHaveBeenLastCalledWith(STORED)
|
||||
})
|
||||
})
|
||||
|
||||
describe("SettingField secret", () => {
|
||||
const def: SettingDef = {
|
||||
key: "LANGFUSE_SECRET_KEY",
|
||||
group: "observability",
|
||||
type: "secret",
|
||||
label: "Langfuse Secret Key",
|
||||
}
|
||||
|
||||
it("drops the pending change when a saved secret is typed over and cleared", () => {
|
||||
const onChange = vi.fn()
|
||||
const props = {
|
||||
def,
|
||||
state: { key: def.key, source: "file" as const, value: STORED },
|
||||
disabled: false,
|
||||
onChange,
|
||||
}
|
||||
const { rerender } = render(
|
||||
withDict(<SettingField {...props} pendingValue={undefined} />),
|
||||
)
|
||||
rerender(withDict(<SettingField {...props} pendingValue="a" />))
|
||||
|
||||
typeInto(en.admin.settings.LANGFUSE_SECRET_KEY.label, "")
|
||||
expect(onChange).toHaveBeenLastCalledWith(undefined)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user