Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,15 @@ jobs:
- name: Build Next.js app
run: npm run build
env:
NEXT_PUBLIC_API_URL: http://localhost:3000/api
NEXT_PUBLIC_API_URL: http://127.0.0.1:3000/api
NEXT_PUBLIC_CONTRACT_ID: Cxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
NEXT_PUBLIC_STELLAR_NETWORK: testnet
NEXT_PUBLIC_STELLAR_NETWORK_PASSPHRASE: "Test SDF Network ; September 2015"

- name: Run Playwright tests
run: npm run test:e2e
env:
NEXT_PUBLIC_API_URL: http://localhost:3000/api
NEXT_PUBLIC_API_URL: http://127.0.0.1:3000/api
NEXT_PUBLIC_CONTRACT_ID: Cxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
NEXT_PUBLIC_STELLAR_NETWORK: testnet
NEXT_PUBLIC_STELLAR_NETWORK_PASSPHRASE: "Test SDF Network ; September 2015"
Expand Down
171 changes: 0 additions & 171 deletions app/__tests__/layout.test.tsx

This file was deleted.

1 change: 0 additions & 1 deletion app/dispute/[id]/DisputeForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ export default function DisputeForm({ escrowId }: DisputeFormProps) {
<textarea
id="reason"
rows={6}
minLength={20}
value={reason}
onChange={(event) => setReason(event.target.value)}
placeholder="Explain what went wrong with your order, including any delivery or item issues."
Expand Down
17 changes: 4 additions & 13 deletions components/dashboard/ShipTrackingModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

import React, { FormEvent, useEffect, useRef,useState } from "react";

import { shipEscrow } from "@/lib/api";
import type { ApiErrorResponse } from "@/types/api";

Check warning on line 6 in components/dashboard/ShipTrackingModal.tsx

View workflow job for this annotation

GitHub Actions / ESLint

'ApiErrorResponse' is defined but never used

interface ShipTrackingModalProps {
escrowId: string;
Expand Down Expand Up @@ -85,21 +86,11 @@
setError(null);

try {
const response = await fetch(`/escrow/${escrowId}/ship`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ trackingId: trimmedTrackingId, carrier }),
await shipEscrow(escrowId, {
trackingId: trimmedTrackingId,
carrier: carrier,
});

if (!response.ok) {
const payload = (await response
.json()
.catch(() => null)) as ApiErrorResponse | null;
throw new Error(payload?.message ?? "Unable to submit shipment details.");
}

onSuccess(escrowId);
onClose();
setTrackingId("");
Expand Down
63 changes: 19 additions & 44 deletions components/dashboard/__tests__/ShipTrackingModal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@ import userEvent from "@testing-library/user-event";
import React from "react";
import { beforeEach,describe, expect, it, vi } from "vitest";

import { shipEscrow } from "@/lib/api";

import ShipTrackingModal from "../ShipTrackingModal";

vi.mock("@/lib/api", () => ({
shipEscrow: vi.fn(),
}));

const defaultProps = {
escrowId: "escrow-123",
vendorName: "Test Vendor",
Expand Down Expand Up @@ -94,10 +100,7 @@ describe("ShipTrackingModal", () => {
const onSuccess = vi.fn();
const onClose = vi.fn();

vi.spyOn(global, "fetch").mockResolvedValueOnce({
ok: true,
json: async () => ({ status: "Shipped" }),
} as Response);
vi.mocked(shipEscrow).mockResolvedValueOnce({} as never);

render(
<ShipTrackingModal
Expand All @@ -118,10 +121,7 @@ describe("ShipTrackingModal", () => {

it("sends correct payload to the ship endpoint", async () => {
const user = userEvent.setup();
const fetchSpy = vi.spyOn(global, "fetch").mockResolvedValueOnce({
ok: true,
json: async () => ({}),
} as Response);
vi.mocked(shipEscrow).mockResolvedValueOnce({} as never);

render(<ShipTrackingModal {...defaultProps} />);

Expand All @@ -133,24 +133,17 @@ describe("ShipTrackingModal", () => {
await user.click(screen.getByRole("button", { name: /submit/i }));

await waitFor(() => {
expect(fetchSpy).toHaveBeenCalledWith(
"/escrow/escrow-123/ship",
expect.objectContaining({
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ trackingId: "TRACK-999", carrier: "GIGL" }),
})
);
expect(shipEscrow).toHaveBeenCalledWith("escrow-123", {
trackingId: "TRACK-999",
carrier: "GIGL",
});
});
});

it("displays error message when the API call fails", async () => {
const user = userEvent.setup();

vi.spyOn(global, "fetch").mockResolvedValueOnce({
ok: false,
json: async () => ({ message: "Escrow not found" }),
} as Response);
vi.mocked(shipEscrow).mockRejectedValueOnce(new Error("Escrow not found"));

render(<ShipTrackingModal {...defaultProps} />);

Expand All @@ -165,12 +158,7 @@ describe("ShipTrackingModal", () => {
it("displays generic error when API returns non-JSON response", async () => {
const user = userEvent.setup();

vi.spyOn(global, "fetch").mockResolvedValueOnce({
ok: false,
json: async () => {
throw new Error("invalid json");
},
} as unknown as Response);
vi.mocked(shipEscrow).mockRejectedValueOnce(new Error("invalid json"));

render(<ShipTrackingModal {...defaultProps} />);

Expand All @@ -179,7 +167,7 @@ describe("ShipTrackingModal", () => {

await waitFor(() => {
expect(
screen.getByText(/unable to submit shipment details/i)
screen.getByText(/invalid json/i)
).toBeInTheDocument();
});
});
Expand All @@ -188,17 +176,10 @@ describe("ShipTrackingModal", () => {
const user = userEvent.setup();

// Slow response to keep submitting state visible
vi.spyOn(global, "fetch").mockImplementationOnce(
vi.mocked(shipEscrow).mockImplementationOnce(
() =>
new Promise((resolve) =>
setTimeout(
() =>
resolve({
ok: true,
json: async () => ({}),
} as Response),
500
)
setTimeout(() => resolve({} as never), 500)
)
);

Expand All @@ -215,10 +196,7 @@ describe("ShipTrackingModal", () => {
const user = userEvent.setup();

// First call fails
vi.spyOn(global, "fetch").mockResolvedValueOnce({
ok: false,
json: async () => ({ message: "Server error" }),
} as Response);
vi.mocked(shipEscrow).mockRejectedValueOnce(new Error("Server error"));

render(<ShipTrackingModal {...defaultProps} />);

Expand All @@ -230,10 +208,7 @@ describe("ShipTrackingModal", () => {
});

// Second call succeeds
vi.spyOn(global, "fetch").mockResolvedValueOnce({
ok: true,
json: async () => ({}),
} as Response);
vi.mocked(shipEscrow).mockResolvedValueOnce({} as never);

await user.click(screen.getByRole("button", { name: /submit/i }));

Expand Down
2 changes: 1 addition & 1 deletion playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default defineConfig({
},
],
webServer: {
command: "npm run dev -- --hostname 127.0.0.1 --port 3000",
command: "npm run start -- --hostname 127.0.0.1 --port 3000",
url: "http://127.0.0.1:3000",
reuseExistingServer: !process.env.CI,
timeout: 120000,
Expand Down
6 changes: 4 additions & 2 deletions tests/e2e/admin-dispute-resolution.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ test("admin can resolve a dispute and the dispute list updates", async ({ page,
const url = new URL(request.url);

// Server-side fetch for the dispute detail page
if (url.pathname === `/disputes/${disputeId}`) {
if (url.pathname.endsWith(`/disputes/${disputeId}`)) {
return new Response(JSON.stringify(mockDispute), {
status: 200,
headers: { "Content-Type": "application/json" },
Expand All @@ -86,7 +86,9 @@ test("admin can resolve a dispute and the dispute list updates", async ({ page,
await page.goto("/admin/disputes");

await expect(page.getByText("Admin Disputes")).toBeVisible();
await page.getByRole("link", { name: /view dispute/i }).click();

// Navigate directly to the dispute details page to avoid Next.js RSC client navigation issues
await page.goto(`/admin/disputes/${disputeId}`);

await expect(page.getByText(/release to vendor/i)).toBeVisible({ timeout: 10_000 });
await expect(page.getByTestId("dispute-status-badge")).toHaveText("OPEN");
Expand Down
Loading
Loading