fix: wire catalog add buttons, fix Trans bold rendering, lint cleanup
- CatalogSearchOverlay: replace handleAddStub with real openAddToCollection/openAddToThread routing based on catalogSearchMode - ConfirmDialog + __root.tsx: swap t() for Trans component on deleteItemMessage, deleteCandidateMessage, pickWinnerMessage — fixes <bold> rendering as literal text - Biome format pass: fix 23 lint/format errors across scripts, services, tests - Planning: mark all UAT and verification gaps resolved for phases 07, 11, 16, 20, 21, 22, 24, 32, 34; close debug sessions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,10 @@ import { registerThreadTools } from "../../src/server/mcp/tools/threads.ts";
|
||||
import { createSecondTestUser, createTestDb } from "../helpers/db.ts";
|
||||
|
||||
async function insertManufacturer(db: any, name: string) {
|
||||
const slug = name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
||||
const slug = name
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, "-")
|
||||
.replace(/[^a-z0-9-]/g, "");
|
||||
const [row] = await db
|
||||
.insert(manufacturers)
|
||||
.values({ name, slug, website: `https://${slug}.com` })
|
||||
|
||||
@@ -21,7 +21,10 @@ async function createTestApp() {
|
||||
}
|
||||
|
||||
async function insertManufacturer(db: TestDb["db"], name: string) {
|
||||
const slug = name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
||||
const slug = name
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, "-")
|
||||
.replace(/[^a-z0-9-]/g, "");
|
||||
const [row] = await db
|
||||
.insert(manufacturers)
|
||||
.values({ name, slug, website: `https://${slug}.com` })
|
||||
|
||||
@@ -27,7 +27,10 @@ async function createTestApp() {
|
||||
}
|
||||
|
||||
async function insertManufacturer(db: TestDb["db"], name: string) {
|
||||
const slug = name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
||||
const slug = name
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, "-")
|
||||
.replace(/[^a-z0-9-]/g, "");
|
||||
const [row] = await db
|
||||
.insert(manufacturers)
|
||||
.values({ name, slug, website: `https://${slug}.com` })
|
||||
|
||||
@@ -18,7 +18,10 @@ import { createTestDb } from "../helpers/db.ts";
|
||||
type TestDb = Awaited<ReturnType<typeof createTestDb>>;
|
||||
|
||||
async function insertManufacturer(db: TestDb["db"], name: string) {
|
||||
const slug = name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
||||
const slug = name
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, "-")
|
||||
.replace(/[^a-z0-9-]/g, "");
|
||||
const [existing] = await db
|
||||
.select()
|
||||
.from(manufacturers)
|
||||
|
||||
@@ -18,7 +18,11 @@ import { createTestDb } from "../helpers/db.ts";
|
||||
|
||||
type TestDb = Awaited<ReturnType<typeof createTestDb>>;
|
||||
|
||||
async function insertManufacturer(db: TestDb["db"], name = "Apidura", slug = "apidura") {
|
||||
async function insertManufacturer(
|
||||
db: TestDb["db"],
|
||||
name = "Apidura",
|
||||
slug = "apidura",
|
||||
) {
|
||||
const [row] = await db
|
||||
.insert(schema.manufacturers)
|
||||
.values({ name, slug, website: `https://${slug}.com` })
|
||||
@@ -87,20 +91,40 @@ describe("Global Item Service", () => {
|
||||
|
||||
describe("searchGlobalItems", () => {
|
||||
it("returns all global items when no query provided", async () => {
|
||||
const m1 = await insertManufacturer(db, "Revelate Designs", "revelate-designs");
|
||||
const m1 = await insertManufacturer(
|
||||
db,
|
||||
"Revelate Designs",
|
||||
"revelate-designs",
|
||||
);
|
||||
const m2 = await insertManufacturer(db, "Apidura", "apidura");
|
||||
await insertGlobalItem(db, { manufacturerId: m1.id, model: "Terrapin System" });
|
||||
await insertGlobalItem(db, { manufacturerId: m2.id, model: "Handlebar Pack" });
|
||||
await insertGlobalItem(db, {
|
||||
manufacturerId: m1.id,
|
||||
model: "Terrapin System",
|
||||
});
|
||||
await insertGlobalItem(db, {
|
||||
manufacturerId: m2.id,
|
||||
model: "Handlebar Pack",
|
||||
});
|
||||
|
||||
const results = await searchGlobalItems(db);
|
||||
expect(results).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("returns items matching brand (case-insensitive)", async () => {
|
||||
const m1 = await insertManufacturer(db, "Revelate Designs", "revelate-designs");
|
||||
const m1 = await insertManufacturer(
|
||||
db,
|
||||
"Revelate Designs",
|
||||
"revelate-designs",
|
||||
);
|
||||
const m2 = await insertManufacturer(db, "Apidura", "apidura");
|
||||
await insertGlobalItem(db, { manufacturerId: m1.id, model: "Terrapin System" });
|
||||
await insertGlobalItem(db, { manufacturerId: m2.id, model: "Handlebar Pack" });
|
||||
await insertGlobalItem(db, {
|
||||
manufacturerId: m1.id,
|
||||
model: "Terrapin System",
|
||||
});
|
||||
await insertGlobalItem(db, {
|
||||
manufacturerId: m2.id,
|
||||
model: "Handlebar Pack",
|
||||
});
|
||||
|
||||
const results = await searchGlobalItems(db, "revelate");
|
||||
expect(results).toHaveLength(1);
|
||||
@@ -108,10 +132,20 @@ describe("Global Item Service", () => {
|
||||
});
|
||||
|
||||
it("returns items matching model (case-insensitive)", async () => {
|
||||
const m1 = await insertManufacturer(db, "Revelate Designs", "revelate-designs");
|
||||
const m1 = await insertManufacturer(
|
||||
db,
|
||||
"Revelate Designs",
|
||||
"revelate-designs",
|
||||
);
|
||||
const m2 = await insertManufacturer(db, "Apidura", "apidura");
|
||||
await insertGlobalItem(db, { manufacturerId: m1.id, model: "Terrapin System" });
|
||||
await insertGlobalItem(db, { manufacturerId: m2.id, model: "Handlebar Pack" });
|
||||
await insertGlobalItem(db, {
|
||||
manufacturerId: m1.id,
|
||||
model: "Terrapin System",
|
||||
});
|
||||
await insertGlobalItem(db, {
|
||||
manufacturerId: m2.id,
|
||||
model: "Handlebar Pack",
|
||||
});
|
||||
|
||||
const results = await searchGlobalItems(db, "HANDLEBAR");
|
||||
expect(results).toHaveLength(1);
|
||||
@@ -119,30 +153,60 @@ describe("Global Item Service", () => {
|
||||
});
|
||||
|
||||
it("does not match everything with wildcard chars", async () => {
|
||||
const m1 = await insertManufacturer(db, "Revelate Designs", "revelate-designs");
|
||||
const m1 = await insertManufacturer(
|
||||
db,
|
||||
"Revelate Designs",
|
||||
"revelate-designs",
|
||||
);
|
||||
const m2 = await insertManufacturer(db, "Apidura", "apidura");
|
||||
await insertGlobalItem(db, { manufacturerId: m1.id, model: "Terrapin System" });
|
||||
await insertGlobalItem(db, { manufacturerId: m2.id, model: "Handlebar Pack" });
|
||||
await insertGlobalItem(db, {
|
||||
manufacturerId: m1.id,
|
||||
model: "Terrapin System",
|
||||
});
|
||||
await insertGlobalItem(db, {
|
||||
manufacturerId: m2.id,
|
||||
model: "Handlebar Pack",
|
||||
});
|
||||
|
||||
const results = await searchGlobalItems(db, "100%");
|
||||
expect(results).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("returns all items when no tags provided", async () => {
|
||||
const m1 = await insertManufacturer(db, "Revelate Designs", "revelate-designs");
|
||||
const m1 = await insertManufacturer(
|
||||
db,
|
||||
"Revelate Designs",
|
||||
"revelate-designs",
|
||||
);
|
||||
const m2 = await insertManufacturer(db, "Apidura", "apidura");
|
||||
await insertGlobalItem(db, { manufacturerId: m1.id, model: "Terrapin System" });
|
||||
await insertGlobalItem(db, { manufacturerId: m2.id, model: "Handlebar Pack" });
|
||||
await insertGlobalItem(db, {
|
||||
manufacturerId: m1.id,
|
||||
model: "Terrapin System",
|
||||
});
|
||||
await insertGlobalItem(db, {
|
||||
manufacturerId: m2.id,
|
||||
model: "Handlebar Pack",
|
||||
});
|
||||
|
||||
const results = await searchGlobalItems(db, undefined, undefined);
|
||||
expect(results).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("filters by single tag", async () => {
|
||||
const m1 = await insertManufacturer(db, "Revelate Designs", "revelate-designs");
|
||||
const m1 = await insertManufacturer(
|
||||
db,
|
||||
"Revelate Designs",
|
||||
"revelate-designs",
|
||||
);
|
||||
const m2 = await insertManufacturer(db, "Apidura", "apidura");
|
||||
const gi1 = await insertGlobalItem(db, { manufacturerId: m1.id, model: "Terrapin System" });
|
||||
const _gi2 = await insertGlobalItem(db, { manufacturerId: m2.id, model: "Handlebar Pack" });
|
||||
const gi1 = await insertGlobalItem(db, {
|
||||
manufacturerId: m1.id,
|
||||
model: "Terrapin System",
|
||||
});
|
||||
const _gi2 = await insertGlobalItem(db, {
|
||||
manufacturerId: m2.id,
|
||||
model: "Handlebar Pack",
|
||||
});
|
||||
|
||||
const tag = await insertTag(db, "ultralight");
|
||||
await tagGlobalItem(db, gi1.id, tag.id);
|
||||
@@ -153,10 +217,20 @@ describe("Global Item Service", () => {
|
||||
});
|
||||
|
||||
it("filters by multiple tags with AND logic", async () => {
|
||||
const m1 = await insertManufacturer(db, "Revelate Designs", "revelate-designs");
|
||||
const m1 = await insertManufacturer(
|
||||
db,
|
||||
"Revelate Designs",
|
||||
"revelate-designs",
|
||||
);
|
||||
const m2 = await insertManufacturer(db, "Apidura", "apidura");
|
||||
const gi1 = await insertGlobalItem(db, { manufacturerId: m1.id, model: "Terrapin System" });
|
||||
const gi2 = await insertGlobalItem(db, { manufacturerId: m2.id, model: "Handlebar Pack" });
|
||||
const gi1 = await insertGlobalItem(db, {
|
||||
manufacturerId: m1.id,
|
||||
model: "Terrapin System",
|
||||
});
|
||||
const gi2 = await insertGlobalItem(db, {
|
||||
manufacturerId: m2.id,
|
||||
model: "Handlebar Pack",
|
||||
});
|
||||
|
||||
const tagUL = await insertTag(db, "ultralight");
|
||||
const tagBP = await insertTag(db, "bikepacking");
|
||||
@@ -175,9 +249,19 @@ describe("Global Item Service", () => {
|
||||
});
|
||||
|
||||
it("combines text search and tag filtering", async () => {
|
||||
const m = await insertManufacturer(db, "Revelate Designs", "revelate-designs");
|
||||
const gi1 = await insertGlobalItem(db, { manufacturerId: m.id, model: "Terrapin System" });
|
||||
const gi2 = await insertGlobalItem(db, { manufacturerId: m.id, model: "Spinelock" });
|
||||
const m = await insertManufacturer(
|
||||
db,
|
||||
"Revelate Designs",
|
||||
"revelate-designs",
|
||||
);
|
||||
const gi1 = await insertGlobalItem(db, {
|
||||
manufacturerId: m.id,
|
||||
model: "Terrapin System",
|
||||
});
|
||||
const gi2 = await insertGlobalItem(db, {
|
||||
manufacturerId: m.id,
|
||||
model: "Spinelock",
|
||||
});
|
||||
|
||||
const tag = await insertTag(db, "bikepacking");
|
||||
await tagGlobalItem(db, gi1.id, tag.id);
|
||||
@@ -193,7 +277,10 @@ describe("Global Item Service", () => {
|
||||
describe("getGlobalItemWithOwnerCount", () => {
|
||||
it("returns item with ownerCount 0 when no items reference it", async () => {
|
||||
const m = await insertManufacturer(db, "MSR", "msr");
|
||||
const gi = await insertGlobalItem(db, { manufacturerId: m.id, model: "PocketRocket 2" });
|
||||
const gi = await insertGlobalItem(db, {
|
||||
manufacturerId: m.id,
|
||||
model: "PocketRocket 2",
|
||||
});
|
||||
|
||||
const result = await getGlobalItemWithOwnerCount(db, gi.id);
|
||||
expect(result).not.toBeNull();
|
||||
@@ -203,7 +290,10 @@ describe("Global Item Service", () => {
|
||||
|
||||
it("returns ownerCount matching number of items with globalItemId", async () => {
|
||||
const m = await insertManufacturer(db, "MSR", "msr");
|
||||
const gi = await insertGlobalItem(db, { manufacturerId: m.id, model: "PocketRocket 2" });
|
||||
const gi = await insertGlobalItem(db, {
|
||||
manufacturerId: m.id,
|
||||
model: "PocketRocket 2",
|
||||
});
|
||||
|
||||
await insertItem(db, "My Stove", userId, { globalItemId: gi.id });
|
||||
await insertItem(db, "Another Stove", userId, {
|
||||
@@ -371,8 +461,16 @@ describe("Global Item Service", () => {
|
||||
await insertManufacturer(db, "Black Diamond", "black-diamond");
|
||||
const result = await bulkUpsertGlobalItems(db, [
|
||||
{ manufacturerSlug: "petzl", model: "Actik Core", weightGrams: 87 },
|
||||
{ manufacturerSlug: "black-diamond", model: "Spot 400", weightGrams: 95 },
|
||||
{ manufacturerSlug: "black-diamond", model: "Spot 350", weightGrams: 90 },
|
||||
{
|
||||
manufacturerSlug: "black-diamond",
|
||||
model: "Spot 400",
|
||||
weightGrams: 95,
|
||||
},
|
||||
{
|
||||
manufacturerSlug: "black-diamond",
|
||||
model: "Spot 350",
|
||||
weightGrams: 90,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(result.created).toBe(3);
|
||||
@@ -392,7 +490,11 @@ describe("Global Item Service", () => {
|
||||
|
||||
const result = await bulkUpsertGlobalItems(db, [
|
||||
{ manufacturerSlug: "petzl", model: "Actik Core", weightGrams: 90 }, // existing
|
||||
{ manufacturerSlug: "black-diamond", model: "Spot 400", weightGrams: 95 }, // new
|
||||
{
|
||||
manufacturerSlug: "black-diamond",
|
||||
model: "Spot 400",
|
||||
weightGrams: 95,
|
||||
}, // new
|
||||
]);
|
||||
|
||||
expect(result.created).toBe(1);
|
||||
|
||||
@@ -171,7 +171,10 @@ describe("Item Service", () => {
|
||||
|
||||
describe("reference items (globalItemId)", () => {
|
||||
async function insertManufacturer(testDb: any, name: string) {
|
||||
const slug = name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
||||
const slug = name
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, "-")
|
||||
.replace(/[^a-z0-9-]/g, "");
|
||||
const [row] = await testDb
|
||||
.insert(manufacturers)
|
||||
.values({ name, slug, website: `https://${slug}.com` })
|
||||
@@ -190,13 +193,16 @@ describe("Item Service", () => {
|
||||
},
|
||||
) {
|
||||
const m = await insertManufacturer(testDb, data.brand);
|
||||
const [row] = await testDb.insert(globalItems).values({
|
||||
manufacturerId: m.id,
|
||||
model: data.model,
|
||||
weightGrams: data.weightGrams ?? null,
|
||||
priceCents: data.priceCents ?? null,
|
||||
imageUrl: data.imageUrl ?? null,
|
||||
}).returning();
|
||||
const [row] = await testDb
|
||||
.insert(globalItems)
|
||||
.values({
|
||||
manufacturerId: m.id,
|
||||
model: data.model,
|
||||
weightGrams: data.weightGrams ?? null,
|
||||
priceCents: data.priceCents ?? null,
|
||||
imageUrl: data.imageUrl ?? null,
|
||||
})
|
||||
.returning();
|
||||
return row;
|
||||
}
|
||||
|
||||
|
||||
@@ -63,8 +63,16 @@ describe("getManufacturerBySlug", () => {
|
||||
|
||||
describe("listManufacturers", () => {
|
||||
it("returns all manufacturers ordered by name", async () => {
|
||||
await createManufacturer(db, { name: "Ortlieb", slug: "ortlieb", website: "https://ortlieb.com" });
|
||||
await createManufacturer(db, { name: "Apidura", slug: "apidura", website: "https://apidura.com" });
|
||||
await createManufacturer(db, {
|
||||
name: "Ortlieb",
|
||||
slug: "ortlieb",
|
||||
website: "https://ortlieb.com",
|
||||
});
|
||||
await createManufacturer(db, {
|
||||
name: "Apidura",
|
||||
slug: "apidura",
|
||||
website: "https://apidura.com",
|
||||
});
|
||||
const result = await listManufacturers(db);
|
||||
expect(result[0]?.name).toBe("Apidura");
|
||||
expect(result[1]?.name).toBe("Ortlieb");
|
||||
|
||||
@@ -619,7 +619,10 @@ describe("Thread Service", () => {
|
||||
|
||||
describe("catalog-linked candidates (globalItemId)", () => {
|
||||
async function insertManufacturer(testDb: any, name: string) {
|
||||
const slug = name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
||||
const slug = name
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, "-")
|
||||
.replace(/[^a-z0-9-]/g, "");
|
||||
const [row] = await testDb
|
||||
.insert(manufacturers)
|
||||
.values({ name, slug, website: `https://${slug}.com` })
|
||||
@@ -638,13 +641,16 @@ describe("Thread Service", () => {
|
||||
},
|
||||
) {
|
||||
const m = await insertManufacturer(testDb, data.brand);
|
||||
const [row] = await testDb.insert(globalItems).values({
|
||||
manufacturerId: m.id,
|
||||
model: data.model,
|
||||
weightGrams: data.weightGrams ?? null,
|
||||
priceCents: data.priceCents ?? null,
|
||||
imageUrl: data.imageUrl ?? null,
|
||||
}).returning();
|
||||
const [row] = await testDb
|
||||
.insert(globalItems)
|
||||
.values({
|
||||
manufacturerId: m.id,
|
||||
model: data.model,
|
||||
weightGrams: data.weightGrams ?? null,
|
||||
priceCents: data.priceCents ?? null,
|
||||
imageUrl: data.imageUrl ?? null,
|
||||
})
|
||||
.returning();
|
||||
return row;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user