feat: initial commit — storefront, convex backend, and shared packages
Completes the first milestone of The Pet Loft ecommerce platform: - apps/storefront: full customer-facing Next.js app with HeroUI (cart, checkout, orders, wishlist, product detail, shop, search, auth) - convex/: serverless backend with schema, queries, mutations, actions, HTTP routes, Stripe/Shippo integrations, and co-located tests - packages/types, packages/utils, packages/convex: shared workspace packages Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
121
convex/carts.test.ts
Normal file
121
convex/carts.test.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import { convexTest } from "convex-test";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { api } from "./_generated/api";
|
||||
import schema from "./schema";
|
||||
|
||||
const modules = import.meta.glob("./**/*.ts");
|
||||
|
||||
async function setupUserAndVariant(t: ReturnType<typeof convexTest>) {
|
||||
const asCustomer = t.withIdentity({
|
||||
name: "Alice",
|
||||
email: "alice@example.com",
|
||||
subject: "clerk_alice_123",
|
||||
});
|
||||
await asCustomer.mutation(api.users.store, {});
|
||||
|
||||
let categoryId: any;
|
||||
let variantId: any;
|
||||
await t.run(async (ctx) => {
|
||||
categoryId = await ctx.db.insert("categories", {
|
||||
name: "Toys",
|
||||
slug: "toys",
|
||||
});
|
||||
const productId = await ctx.db.insert("products", {
|
||||
name: "Ball",
|
||||
slug: "ball",
|
||||
status: "active",
|
||||
categoryId,
|
||||
tags: [],
|
||||
});
|
||||
variantId = await ctx.db.insert("productVariants", {
|
||||
productId,
|
||||
name: "Red Ball",
|
||||
sku: "BALL-RED-001",
|
||||
price: 999,
|
||||
stockQuantity: 50,
|
||||
attributes: { color: "Red" },
|
||||
isActive: true,
|
||||
});
|
||||
});
|
||||
|
||||
return { asCustomer, variantId };
|
||||
}
|
||||
|
||||
describe("carts", () => {
|
||||
it("addItem adds a line; second addItem with same variantId increases quantity", async () => {
|
||||
const t = convexTest(schema, modules);
|
||||
const { asCustomer, variantId } = await setupUserAndVariant(t);
|
||||
|
||||
await asCustomer.mutation(api.carts.addItem, {
|
||||
variantId,
|
||||
quantity: 2,
|
||||
});
|
||||
let cart = await asCustomer.query(api.carts.get, {});
|
||||
expect(cart).not.toBeNull();
|
||||
expect(cart!.items).toHaveLength(1);
|
||||
expect(cart!.items[0].quantity).toBe(2);
|
||||
|
||||
await asCustomer.mutation(api.carts.addItem, {
|
||||
variantId,
|
||||
quantity: 3,
|
||||
});
|
||||
cart = await asCustomer.query(api.carts.get, {});
|
||||
expect(cart!.items).toHaveLength(1);
|
||||
expect(cart!.items[0].quantity).toBe(5);
|
||||
});
|
||||
|
||||
it("updateItem with quantity 0 removes the line", async () => {
|
||||
const t = convexTest(schema, modules);
|
||||
const { asCustomer, variantId } = await setupUserAndVariant(t);
|
||||
|
||||
await asCustomer.mutation(api.carts.addItem, { variantId, quantity: 1 });
|
||||
await asCustomer.mutation(api.carts.updateItem, {
|
||||
variantId,
|
||||
quantity: 0,
|
||||
});
|
||||
const cart = await asCustomer.query(api.carts.get, {});
|
||||
expect(cart!.items).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("clear empties the cart", async () => {
|
||||
const t = convexTest(schema, modules);
|
||||
const { asCustomer, variantId } = await setupUserAndVariant(t);
|
||||
|
||||
await asCustomer.mutation(api.carts.addItem, { variantId, quantity: 2 });
|
||||
await asCustomer.mutation(api.carts.clear, {});
|
||||
const cart = await asCustomer.query(api.carts.get, {});
|
||||
expect(cart!.items).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("guest cart with sessionId; merge into authenticated user cart", async () => {
|
||||
const t = convexTest(schema, modules);
|
||||
const { variantId } = await setupUserAndVariant(t);
|
||||
const sessionId = "guest-session-123";
|
||||
|
||||
await t.mutation(api.carts.addItem, {
|
||||
variantId,
|
||||
quantity: 2,
|
||||
sessionId,
|
||||
});
|
||||
const guestCart = await t.query(api.carts.get, { sessionId });
|
||||
expect(guestCart).not.toBeNull();
|
||||
expect(guestCart!.items).toHaveLength(1);
|
||||
expect(guestCart!.items[0].quantity).toBe(2);
|
||||
|
||||
const asCustomer = t.withIdentity({
|
||||
name: "Alice",
|
||||
email: "alice@example.com",
|
||||
subject: "clerk_alice_123",
|
||||
});
|
||||
await asCustomer.mutation(api.users.store, {});
|
||||
await asCustomer.mutation(api.carts.addItem, { variantId, quantity: 1 });
|
||||
await asCustomer.mutation(api.carts.merge, { sessionId });
|
||||
|
||||
const userCart = await asCustomer.query(api.carts.get, {});
|
||||
expect(userCart!.items).toHaveLength(1);
|
||||
expect(userCart!.items[0].quantity).toBe(3);
|
||||
|
||||
const guestCartAfter = await t.query(api.carts.get, { sessionId });
|
||||
expect(guestCartAfter!.items).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user