mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-10-05 01:07:56 +08:00
fix(chat-input): stop template dialogs from sending and fix attachment races
- Template dialogs no longer submit the outer chat form - Sending is blocked while files or URLs are still extracting - File and URL extraction no longer drop or resurrect entries - IME composition Enter no longer sends - Tool call cards show the error text; keyboard handling on cards fixed - Template import available when empty, edit dialog resets, saved templates refresh - Only png/jpeg/gif/webp images accepted, SVG sent as text; PDF objects released - parse-url request sends the access code
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
import { describe, expect, it } from "vitest"
|
||||
import { isTextFile } from "@/lib/pdf-utils"
|
||||
|
||||
describe("isTextFile", () => {
|
||||
it("treats SVG files as text so their markup is sent to the model", () => {
|
||||
const svg = new File(["<svg/>"], "diagram.svg", {
|
||||
type: "image/svg+xml",
|
||||
})
|
||||
expect(isTextFile(svg)).toBe(true)
|
||||
})
|
||||
|
||||
it("does not treat raster images as text", () => {
|
||||
const png = new File(["x"], "photo.png", { type: "image/png" })
|
||||
expect(isTextFile(png)).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,25 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest"
|
||||
import { STORAGE_KEYS } from "@/lib/storage"
|
||||
import { extractUrlContent } from "@/lib/url-utils"
|
||||
|
||||
describe("extractUrlContent", () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals()
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
it("sends the saved access code with the request", async () => {
|
||||
localStorage.setItem(STORAGE_KEYS.accessCode, "secret")
|
||||
const body = { title: "T", content: "body", charCount: 4 }
|
||||
const fetchMock = vi
|
||||
.fn()
|
||||
.mockResolvedValue(new Response(JSON.stringify(body)))
|
||||
vi.stubGlobal("fetch", fetchMock)
|
||||
|
||||
const data = await extractUrlContent("https://example.com")
|
||||
|
||||
expect(data.content).toBe("body")
|
||||
const headers = fetchMock.mock.calls[0][1].headers
|
||||
expect(headers["x-access-code"]).toBe("secret")
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,102 @@
|
||||
import { act, renderHook } from "@testing-library/react"
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest"
|
||||
import { extractPdfText, extractTextFileContent } from "@/lib/pdf-utils"
|
||||
import { useFileProcessor } from "@/lib/use-file-processor"
|
||||
|
||||
vi.mock("sonner", () => ({ toast: { error: vi.fn() } }))
|
||||
vi.mock("@/lib/pdf-utils", async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import("@/lib/pdf-utils")>()),
|
||||
extractPdfText: vi.fn(),
|
||||
extractTextFileContent: vi.fn(),
|
||||
}))
|
||||
|
||||
// A promise we can resolve from the test, to control extraction timing
|
||||
function deferred<T>() {
|
||||
let resolve!: (value: T) => void
|
||||
const promise = new Promise<T>((r) => {
|
||||
resolve = r
|
||||
})
|
||||
return { promise, resolve }
|
||||
}
|
||||
|
||||
const pdfFile = () =>
|
||||
new File(["%PDF"], "slow.pdf", { type: "application/pdf" })
|
||||
const textFile = () => new File(["notes"], "notes.txt", { type: "text/plain" })
|
||||
|
||||
describe("useFileProcessor", () => {
|
||||
beforeEach(() => {
|
||||
vi.mocked(extractPdfText).mockReset()
|
||||
vi.mocked(extractTextFileContent).mockReset()
|
||||
})
|
||||
|
||||
it("marks queued files as extracting before the first one finishes", async () => {
|
||||
const pdf = deferred<string>()
|
||||
vi.mocked(extractPdfText).mockReturnValue(pdf.promise)
|
||||
vi.mocked(extractTextFileContent).mockResolvedValue("notes")
|
||||
const a = pdfFile()
|
||||
const b = textFile()
|
||||
const { result } = renderHook(() => useFileProcessor())
|
||||
|
||||
let done!: Promise<void>
|
||||
act(() => {
|
||||
done = result.current.handleFileChange([a, b])
|
||||
})
|
||||
|
||||
expect(result.current.pdfData.get(a)?.isExtracting).toBe(true)
|
||||
expect(result.current.pdfData.get(b)?.isExtracting).toBe(true)
|
||||
|
||||
await act(async () => {
|
||||
pdf.resolve("pdf text")
|
||||
await done
|
||||
})
|
||||
expect(result.current.pdfData.get(a)?.text).toBe("pdf text")
|
||||
expect(result.current.pdfData.get(b)?.text).toBe("notes")
|
||||
})
|
||||
|
||||
it("keeps text of a file added while an earlier file is extracting", async () => {
|
||||
const pdf = deferred<string>()
|
||||
vi.mocked(extractPdfText).mockReturnValue(pdf.promise)
|
||||
vi.mocked(extractTextFileContent).mockResolvedValue("notes")
|
||||
const a = pdfFile()
|
||||
const b = textFile()
|
||||
const { result } = renderHook(() => useFileProcessor())
|
||||
|
||||
let first!: Promise<void>
|
||||
act(() => {
|
||||
first = result.current.handleFileChange([a])
|
||||
})
|
||||
await act(async () => {
|
||||
await result.current.handleFileChange([a, b])
|
||||
})
|
||||
expect(result.current.pdfData.get(b)?.text).toBe("notes")
|
||||
|
||||
await act(async () => {
|
||||
pdf.resolve("pdf text")
|
||||
await first
|
||||
})
|
||||
expect(result.current.pdfData.get(a)?.text).toBe("pdf text")
|
||||
expect(result.current.pdfData.get(b)?.text).toBe("notes")
|
||||
})
|
||||
|
||||
it("does not bring back a file removed while extracting", async () => {
|
||||
const pdf = deferred<string>()
|
||||
vi.mocked(extractPdfText).mockReturnValue(pdf.promise)
|
||||
const a = pdfFile()
|
||||
const { result } = renderHook(() => useFileProcessor())
|
||||
|
||||
let first!: Promise<void>
|
||||
act(() => {
|
||||
first = result.current.handleFileChange([a])
|
||||
})
|
||||
await act(async () => {
|
||||
await result.current.handleFileChange([])
|
||||
})
|
||||
|
||||
await act(async () => {
|
||||
pdf.resolve("pdf text")
|
||||
await first
|
||||
})
|
||||
expect(result.current.pdfData.has(a)).toBe(false)
|
||||
expect(result.current.files).toEqual([])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user