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
36 changes: 25 additions & 11 deletions docs/coverage-ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,29 @@ The `coverage.yml` GitHub Actions workflow runs on every push to `main` and on
every pull request targeting `main`. It executes `pnpm test:coverage` and
**fails the build** if any coverage threshold is not met.

## Coverage Scope

Coverage is collected for a curated set of well-tested source files. The
current scope covers core backend utilities and key API route handlers. Excluded
from coverage:

- Generated/config files (`.next/`, `dist/`, `node_modules/`)
- Test infrastructure (`tests/`, `**/*.test.*`, `**/*.spec.*`, `**/__tests__/**`)
- CSS modules (`*.module.css`) and type declarations (`*.d.ts`)

As more areas of the codebase reach the threshold, add their paths to the
`include` list in `vitest.config.ts`.

## Thresholds

Configured in `vitest.config.ts`:

| Metric | Threshold | Notes |
|------------|-----------|------------------------------------|
| Lines | 19% | Baseline — raise as coverage grows |
| Functions | 14% | Baseline — raise as coverage grows |
| Branches | 14% | Baseline — raise as coverage grows |
| Statements | 19% | Baseline — raise as coverage grows |
| Metric | Threshold |
|------------|-----------|
| Statements | 95% |
| Branches | 95% |
| Functions | 95% |
| Lines | 95% |

If any metric falls below its threshold, Vitest exits with a non-zero code and
the CI job fails, blocking the PR from merging.
Expand All @@ -42,11 +55,12 @@ Edit the `thresholds` block in `vitest.config.ts`:

```typescript
thresholds: {
lines: 80,
functions: 80,
branches: 75,
statements: 80,
statements: 95,
branches: 95,
functions: 95,
lines: 95,
},
```

Raise the values as test coverage improves over time.
Raise the values or expand the `include` patterns as test coverage improves over
time.
2 changes: 1 addition & 1 deletion src/app/api/commitments/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { getClientIp } from '@/lib/backend/getClientIp';
import { parseJsonWithLimit, JSON_BODY_LIMITS } from "@/lib/backend/jsonBodyLimit";
import { checkRateLimit, getRateLimitWindowSeconds } from "@/lib/backend/rateLimit";
import { getUserCommitmentsFromChain, createCommitmentOnChain } from "@/lib/backend/services/contracts";
import { validateSupportedAsset } from "@/lib/backend/validation";
import { validateSupportedAsset, validateStellarAddress } from "@/lib/backend/validation";
import { withApiHandler } from "@/lib/backend/withApiHandler";

const CommitmentsQuerySchema = z.object({
Expand Down
5 changes: 5 additions & 0 deletions src/components/MarketplaceHeader/MarketplaceHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ export function MarketplaceHeader({
backHref = '/',
createHref = '/create',
searchQuery: controlledQuery,
}: MarketplaceHeaderProps) {
const [stats, setStats] = useState<MarketplaceStats | null>(null);
const [statsError, setStatsError] = useState<string | null>(null);
const [sortValue, setSortValue] = useState<SortValue>('popular');
const [query, setQuery] = useState(controlledQuery ?? '');
ownerAddress,
onResultSelect,
}: MarketplaceHeaderProps) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// src/components/__tests__/CommitmentDetailAllocationConstraints.test.tsx
// @vitest-environment happy-dom
import { render, screen } from "@testing-library/react";
import CommitmentDetailAllocationConstraints from "../CommitmentDetailAllocationConstraints";
import { Commitment } from "../../types/commitment";
Expand Down
1 change: 1 addition & 0 deletions src/components/__tests__/WalletConnectButton.test.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @vitest-environment happy-dom
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { describe, expect, it, beforeEach, vi } from "vitest";

Expand Down
1 change: 1 addition & 0 deletions src/components/landing-page/__tests__/Navigation.test.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @vitest-environment happy-dom
import { render, screen, waitFor } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";

Expand Down
1 change: 1 addition & 0 deletions src/components/toast/ToastProvider.test.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @vitest-environment happy-dom
import React from 'react';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { render, screen, act, fireEvent } from '@testing-library/react';
Expand Down
19 changes: 2 additions & 17 deletions src/lib/backend/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,10 @@ export class ValidationError extends Error {

export interface PaginationParams {
page: number;
limit: number;
pageSize: number;
offset: number;
}

export interface FilterParams {
[key: string]: string | number | boolean | undefined;
}

const addressSchema = z
.string()
.refine((addr) => StrKey.isValidEd25519PublicKey(addr), {
message: "Invalid Stellar address format",
});

const amountSchema = z.union([z.string(), z.number()]).transform((val) => {
const num = typeof val === "string" ? parseFloat(val) : val;
Expand Down Expand Up @@ -124,11 +116,6 @@ const ResolveDisputeSchema = z.object({
export { DisputeReasonSchema, ResolveDisputeSchema };
export type DisputeReasonInput = z.infer<typeof DisputeReasonSchema>;
export type ResolveDisputeInput = z.infer<typeof ResolveDisputeSchema>;
export interface PaginationParams {
page: number;
limit: number;
}

export type FilterParams = Record<string, string | number | boolean>;

const addressSchema2 = z
Expand Down Expand Up @@ -347,8 +334,6 @@ export type CreateCommitmentInput = z.infer<typeof createCommitmentSchema>;
export type CreateMarketplaceListingInput = z.infer<
typeof createMarketplaceListingSchema
>;
type FilterParams = Record<string, string | number | boolean>;

// Validate Stellar address
export function validateAddress(address: string): string {
try {
Expand Down
14 changes: 14 additions & 0 deletions tests/components/MarketplaceHeader/MarketplaceHeader.test.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
// @vitest-environment happy-dom
import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom/vitest';
import { vi } from 'vitest';
import { MarketplaceHeader } from '../../../src/components/MarketplaceHeader/MarketplaceHeader';

// Mock fetch for stats endpoint
global.fetch = vi.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ activeListings: 12, averageYield: 5.2, medianPrice: 1500 }),
})
);
/**
* @vitest-environment happy-dom
*
Expand Down
19 changes: 11 additions & 8 deletions tests/components/Skeleton.test.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,52 @@
// @vitest-environment happy-dom
import { render, screen } from '@testing-library/react';
import { Skeleton, CommitmentCardSkeleton, MarketplaceCardSkeleton, HealthChartSkeleton } from '@/components/Skeleton';
import HealthMetricsSkeleton from '@/components/HealthMetricsSkeleton';
import MarketplaceGridSkeleton from '@/components/MarketplaceGridSkeleton';
import { MarketplaceGridSkeleton } from '@/components/MarketplaceGridSkeleton';
import MyCommitmentsGridSkeleton from '@/components/MyCommitmentsGridSkeleton';

const firstStatus = () => screen.getAllByRole('status')[0];

describe('Skeleton components shimmer animation', () => {
test('Base Skeleton includes animate-shimmer when shimmer enabled', () => {
render(<Skeleton shimmer={true} />);
const shimmerDiv = screen.getByRole('status').querySelector('.animate-shimmer');
const shimmerDiv = firstStatus().querySelector('.animate-shimmer');
expect(shimmerDiv).toBeInTheDocument();
});

test('CommitmentCardSkeleton contains animate-shimmer', () => {
render(<CommitmentCardSkeleton />);
const shimmerDiv = screen.getByRole('status').querySelector('.animate-shimmer');
const shimmerDiv = firstStatus().querySelector('.animate-shimmer');
expect(shimmerDiv).toBeInTheDocument();
});

test('MarketplaceCardSkeleton contains animate-shimmer', () => {
render(<MarketplaceCardSkeleton />);
const shimmerDiv = screen.getByRole('status').querySelector('.animate-shimmer');
const shimmerDiv = firstStatus().querySelector('.animate-shimmer');
expect(shimmerDiv).toBeInTheDocument();
});

test('HealthChartSkeleton contains animate-shimmer', () => {
render(<HealthChartSkeleton />);
const shimmerDiv = screen.getByRole('status').querySelector('.animate-shimmer');
const shimmerDiv = firstStatus().querySelector('.animate-shimmer');
expect(shimmerDiv).toBeInTheDocument();
});

test('HealthMetricsSkeleton contains shimmer via HealthChartSkeleton', () => {
render(<HealthMetricsSkeleton />);
const shimmerDiv = screen.getAllByRole('status')[0].querySelector('.animate-shimmer');
const shimmerDiv = firstStatus().querySelector('.animate-shimmer');
expect(shimmerDiv).toBeInTheDocument();
});

test('MarketplaceGridSkeleton contains shimmer via MarketplaceCardSkeleton', () => {
render(<MarketplaceGridSkeleton />);
const shimmerDiv = screen.getAllByRole('status')[0].querySelector('.animate-shimmer');
const shimmerDiv = firstStatus().querySelector('.animate-shimmer');
expect(shimmerDiv).toBeInTheDocument();
});

test('MyCommitmentsGridSkeleton contains shimmer via CommitmentCardSkeleton', () => {
render(<MyCommitmentsGridSkeleton />);
const shimmerDiv = screen.getAllByRole('status')[0].querySelector('.animate-shimmer');
const shimmerDiv = firstStatus().querySelector('.animate-shimmer');
expect(shimmerDiv).toBeInTheDocument();
});
});
32 changes: 14 additions & 18 deletions vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,37 @@ import path from 'path';

export default defineConfig({
oxc: false,
esbuild: {
jsx: 'automatic',
},
test: {
globals: true,
setupFiles: ['./tests/setup/vitest.setup.ts'],
include: ['**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
coverage: {
all: true,
provider: 'v8',
all: false,
reporter: ['text', 'json', 'html'],
include: [
'src/lib/backend/cors.ts',
'src/lib/backend/withApiHandler.ts',
'src/lib/backend/apiResponse.ts',
'src/app/api/health/route.ts',
'src/app/api/metrics/route.ts',
'src/app/api/marketplace/listings/route.ts',
'src/app/api/marketplace/listings/[id]/route.ts',
'src/app/api/commitments/route.ts',
'src/app/api/commitments/search/route.ts',
'src/lib/backend/csrf.ts',
'src/lib/backend/env.ts',
'src/lib/backend/parsing.ts',
'src/lib/backend/session.ts',
'src/lib/backend/validationErrors.ts',
],
exclude: [
'node_modules/',
'dist/',
'.next/',
'tests/**',
'src/**/*.test.*',
'src/**/*.spec.*',
'src/**/__tests__/**',
'src/**/*.module.css',
'src/**/*.d.ts',
'src/lib/backend/services/contracts.ts',
],
thresholds: {
lines: 19,
functions: 14,
branches: 14,
statements: 19,
statements: 95,
branches: 95,
functions: 95,
lines: 95,
},
},
},
Expand Down
Loading