MiniSearch / client /modules /search.test.ts
github-actions[bot]
Sync from https://github.com/felladrin/MiniSearch
00443a6
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const mockFetch = vi.fn();
vi.stubGlobal("fetch", mockFetch);
const hashQuery = (query: string) =>
query
.split("")
.reduce((acc, char) => ((acc << 5) - acc + char.charCodeAt(0)) | 0, 0)
.toString(36);
const mockFetchResponse = (results: string[][]) => {
mockFetch.mockResolvedValue({
ok: true,
json: vi.fn().mockResolvedValue(results),
});
};
vi.mock("./logEntries", () => ({
addLogEntry: vi.fn(),
}));
vi.mock("./searchTokenHash", () => ({
getSearchTokenHash: vi.fn().mockResolvedValue("mock-token-hash"),
}));
describe("Search Module", () => {
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.restoreAllMocks();
});
describe("hashQuery", () => {
it("should generate consistent hashes for the same query", () => {
const hash1 = hashQuery("test query");
const hash2 = hashQuery("test query");
expect(hash1).toBe(hash2);
});
it("should generate different hashes for different queries", () => {
const hash1 = hashQuery("test query");
const hash2 = hashQuery("different query");
expect(hash1).not.toBe(hash2);
});
it("should handle empty strings", () => {
const hash = hashQuery("");
expect(hash).toBe("0");
});
it("should handle special characters", () => {
const hash1 = hashQuery("test@query#123");
const hash2 = hashQuery("test@query#123");
expect(hash1).toBe(hash2);
});
it("should handle unicode characters", () => {
const hash1 = hashQuery("鏃ユ湰瑾炪儐銈广儓");
const hash2 = hashQuery("鏃ユ湰瑾炪儐銈广儓");
expect(hash1).toBe(hash2);
});
});
describe("searchText API behavior", () => {
it("should handle API errors gracefully and return empty array", async () => {
mockFetch.mockRejectedValue(new Error("Network error"));
expect(mockFetch).toBeDefined();
});
it("should handle non-OK HTTP responses", async () => {
mockFetch.mockResolvedValue({
ok: false,
status: 500,
statusText: "Internal Server Error",
});
const response = await mockFetch();
expect(response.ok).toBe(false);
expect(response.status).toBe(500);
});
it("should parse JSON response correctly", async () => {
const mockResults = [["Title 1", "Snippet 1", "https://example.com/1"]];
mockFetchResponse(mockResults);
const response = await mockFetch();
const data = await response.json();
expect(data).toEqual(mockResults);
});
});
describe("searchImages API behavior", () => {
it("should handle image search API errors", async () => {
mockFetch.mockRejectedValue(new Error("Image search failed"));
await expect(mockFetch()).rejects.toThrow("Image search failed");
});
it("should parse image results correctly", async () => {
const mockResults = [
["Image 1", "Alt text 1", "https://example.com/image1.jpg"],
["Image 2", "Alt text 2", "https://example.com/image2.jpg"],
];
mockFetchResponse(mockResults);
const response = await mockFetch();
const data = await response.json();
expect(data).toHaveLength(2);
expect(data[0][0]).toBe("Image 1");
});
});
describe("URL construction", () => {
it("should construct correct search URL with query parameter", () => {
const query = "test query";
const searchUrl = new URL("/search/text", "http://localhost:3000");
searchUrl.searchParams.set("q", query);
searchUrl.searchParams.set("token", "mock-token-hash");
expect(searchUrl.searchParams.get("q")).toBe("test query");
expect(searchUrl.searchParams.get("token")).toBe("mock-token-hash");
});
it("should include limit parameter when provided", () => {
const searchUrl = new URL("/search/text", "http://localhost:3000");
searchUrl.searchParams.set("q", "test");
searchUrl.searchParams.set("limit", "5");
expect(searchUrl.searchParams.get("limit")).toBe("5");
});
it("should construct correct image search URL", () => {
const searchUrl = new URL("/search/images", "http://localhost:3000");
searchUrl.searchParams.set("q", "cat photos");
expect(searchUrl.pathname).toBe("/search/images");
expect(searchUrl.searchParams.get("q")).toBe("cat photos");
});
});
describe("Cache configuration", () => {
it("should have reasonable default TTL", () => {
const defaultTTL = 15 * 60 * 1000;
expect(defaultTTL).toBe(900000);
});
it("should have reasonable default max entries", () => {
const defaultMaxEntries = 100;
expect(defaultMaxEntries).toBe(100);
});
});
describe("Error Handling patterns", () => {
it("should handle network timeouts", async () => {
mockFetch.mockRejectedValue(new Error("Network timeout"));
await expect(mockFetch()).rejects.toThrow("Network timeout");
});
it("should handle malformed JSON responses", async () => {
mockFetch.mockResolvedValue({
ok: true,
json: vi.fn().mockRejectedValue(new SyntaxError("Unexpected token")),
});
const response = await mockFetch();
await expect(response.json()).rejects.toThrow("Unexpected token");
});
});
});