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
37 changes: 10 additions & 27 deletions .github/workflows/frontend-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,61 +18,44 @@ jobs:

strategy:
matrix:
node-version: [20]
node-version: [ 20 ]

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 9

- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
cache-dependency-path: 'frontend/pnpm-lock.yaml'

- name: Install dependencies
working-directory: ./frontend
run: pnpm install --frozen-lockfile

- name: Run type checking
working-directory: ./frontend
run: pnpm check

- name: Run linting
working-directory: ./frontend
run: pnpm lint

- name: Run tests
working-directory: ./frontend
run: pnpm test:run

- name: Run tests with coverage
working-directory: ./frontend
run: pnpm test:coverage

- name: Upload coverage reports
if: matrix.node-version == 20
uses: codecov/codecov-action@v3
with:
directory: ./frontend/coverage
flags: frontend
name: frontend-coverage
fail_ci_if_error: false


- name: Build application
working-directory: ./frontend
run: pnpm build

- name: Upload build artifacts
if: matrix.node-version == 20
uses: actions/upload-artifact@v4
with:
name: frontend-build
path: frontend/build/
retention-days: 7
run: pnpm build
4 changes: 4 additions & 0 deletions frontend/.prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ pnpm-lock.yaml
yarn.lock
bun.lock
bun.lockb
static
node_modules
.svelte-kit
coverage
1 change: 0 additions & 1 deletion frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,3 @@ pnpm run build
```

You can preview the production build with `pnpm run preview`.

9 changes: 8 additions & 1 deletion frontend/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## What Was Implemented

### 1. Testing Infrastructure

- **Vitest** configuration with jsdom environment
- **Global test setup** with mocked localStorage and environment variables
- **Test scripts** in package.json for running tests in different modes
Expand All @@ -11,12 +12,14 @@
### 2. Test Categories

#### Utility Function Tests (10 tests)

- `src/lib/utils.test.ts`
- Tests for `cn()` class name utility (Tailwind CSS class merging)
- Tests for `trim()` string utility function
- Covers edge cases, conditional logic, and array/object inputs

#### Validation Function Tests (22 tests)

- `src/lib/validation.test.ts`
- Comprehensive validation for:
- Email format validation
Expand All @@ -27,12 +30,14 @@
- Covers both valid and invalid inputs with detailed error messages

#### API Client Tests (4 tests)

- `src/lib/api/client.test.ts`
- Tests API client configuration
- Tests authorization middleware functionality
- Mocks localStorage interactions for token management

#### Data Loader Tests (20 tests)

- `src/routes/app/(components)/dataLoaders.test.ts` - Project creation API
- `src/routes/auth/login/dataLoaders.test.ts` - User login API
- `src/routes/auth/register/dataLoaders.test.ts` - User registration API
Expand All @@ -42,11 +47,13 @@
### 3. Test Utilities Created

#### Validation Module

- `src/lib/validation.ts` - New utility module for form validation
- Reusable validation functions with consistent error handling
- Type-safe validation results with `ValidationResult` interface

### 4. Mock Strategy

- **localStorage**: Fully mocked for browser storage simulation
- **API calls**: Mocked using Vitest's `vi.mock()` for predictable testing
- **Environment variables**: Stubbed for consistent test environment
Expand Down Expand Up @@ -81,4 +88,4 @@ pnpm test:ui

# Run tests with coverage
pnpm test:coverage
```
```
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"check": "svelte-kit sync && svelte-check --no-tsconfig --ignore \"**.test.ts\"",
"check:watch": "svelte-kit sync && svelte-check --no-tsconfig --ignore \"**.test.ts\" --watch",
"format": "prettier --write .",
"lint": "prettier --check . && eslint .",
"lint": "prettier --check . && eslint src --ignore-pattern \"src/**/*.test.ts\"",
"test": "vitest",
"test:run": "vitest run",
"test:ui": "vitest --ui",
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/lib/api/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ vi.mock('openapi-fetch', () => ({
GET: vi.fn(),
POST: vi.fn(),
PUT: vi.fn(),
DELETE: vi.fn(),
DELETE: vi.fn()
}))
}));

Expand Down
1 change: 0 additions & 1 deletion frontend/src/lib/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,4 @@ export const client = createClient<paths>({
baseUrl: API_BASE_URL
});


client.use(authWithHeadersMiddleware);
3 changes: 1 addition & 2 deletions frontend/src/lib/api/user.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ interface JWTPayload {
sub: string; // The subject of the JWT, typically username
}


class UserState {
user: UserSvelte | null = $state(null);
token: string | null = $state(null);
Expand Down Expand Up @@ -62,4 +61,4 @@ class UserState {
}
}

export const userState = new UserState();
export const userState = new UserState();
7 changes: 2 additions & 5 deletions frontend/src/lib/components/ErrorAlert.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,5 @@
<Alert.Root variant="destructive">
<CircleAlertIcon class="size-4" />
<Alert.Title>Error</Alert.Title>
<Alert.Description
>{trim(text, '"')}
</Alert.Description
>
</Alert.Root>
<Alert.Description>{trim(text, '"')}</Alert.Description>
</Alert.Root>
6 changes: 3 additions & 3 deletions frontend/src/lib/components/ui/alert/alert-description.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from 'svelte/elements';
import { cn, type WithElementRef } from '$lib/utils.js';

let {
ref = $bindable(null),
Expand All @@ -14,7 +14,7 @@
bind:this={ref}
data-slot="alert-description"
class={cn(
"text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
'text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed',
className
)}
{...restProps}
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/lib/components/ui/alert/alert-title.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from 'svelte/elements';
import { cn, type WithElementRef } from '$lib/utils.js';

let {
ref = $bindable(null),
Expand All @@ -13,7 +13,7 @@
<div
bind:this={ref}
data-slot="alert-title"
class={cn("col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight", className)}
class={cn('col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight', className)}
{...restProps}
>
{@render children?.()}
Expand Down
22 changes: 11 additions & 11 deletions frontend/src/lib/components/ui/alert/alert.svelte
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
<script lang="ts" module>
import { type VariantProps, tv } from "tailwind-variants";
import { type VariantProps, tv } from 'tailwind-variants';

export const alertVariants = tv({
base: "relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg border px-4 py-3 text-sm has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
base: 'relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg border px-4 py-3 text-sm has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current',
variants: {
variant: {
default: "bg-card text-card-foreground",
default: 'bg-card text-card-foreground',
destructive:
"text-destructive bg-card *:data-[slot=alert-description]:text-destructive/90 [&>svg]:text-current",
},
'text-destructive bg-card *:data-[slot=alert-description]:text-destructive/90 [&>svg]:text-current'
}
},
defaultVariants: {
variant: "default",
},
variant: 'default'
}
});

export type AlertVariant = VariantProps<typeof alertVariants>["variant"];
export type AlertVariant = VariantProps<typeof alertVariants>['variant'];
</script>

<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from 'svelte/elements';
import { cn, type WithElementRef } from '$lib/utils.js';

let {
ref = $bindable(null),
class: className,
variant = "default",
variant = 'default',
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/lib/components/ui/alert/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Root from "./alert.svelte";
import Description from "./alert-description.svelte";
import Title from "./alert-title.svelte";
export { alertVariants, type AlertVariant } from "./alert.svelte";
import Root from './alert.svelte';
import Description from './alert-description.svelte';
import Title from './alert-title.svelte';
export { alertVariants, type AlertVariant } from './alert.svelte';

export {
Root,
Expand All @@ -10,5 +10,5 @@ export {
//
Root as Alert,
Description as AlertDescription,
Title as AlertTitle,
Title as AlertTitle
};
2 changes: 1 addition & 1 deletion frontend/src/lib/components/ui/dialog/dialog-close.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
import { Dialog as DialogPrimitive } from 'bits-ui';

let { ref = $bindable(null), ...restProps }: DialogPrimitive.CloseProps = $props();
</script>
Expand Down
14 changes: 7 additions & 7 deletions frontend/src/lib/components/ui/dialog/dialog-content.svelte
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
import XIcon from "@lucide/svelte/icons/x";
import type { Snippet } from "svelte";
import * as Dialog from "./index.js";
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
import { Dialog as DialogPrimitive } from 'bits-ui';
import XIcon from '@lucide/svelte/icons/x';
import type { Snippet } from 'svelte';
import * as Dialog from './index.js';
import { cn, type WithoutChildrenOrChild } from '$lib/utils.js';

let {
ref = $bindable(null),
Expand All @@ -25,15 +25,15 @@
bind:ref
data-slot="dialog-content"
class={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed left-[50%] top-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
className
)}
{...restProps}
>
{@render children?.()}
{#if showCloseButton}
<DialogPrimitive.Close
class="ring-offset-background focus:ring-ring rounded-xs focus:outline-hidden absolute right-4 top-4 opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0"
class="ring-offset-background focus:ring-ring absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
>
<XIcon />
<span class="sr-only">Close</span>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
import { Dialog as DialogPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';

let {
ref = $bindable(null),
Expand All @@ -12,6 +12,6 @@
<DialogPrimitive.Description
bind:ref
data-slot="dialog-description"
class={cn("text-muted-foreground text-sm", className)}
class={cn('text-muted-foreground text-sm', className)}
{...restProps}
/>
6 changes: 3 additions & 3 deletions frontend/src/lib/components/ui/dialog/dialog-footer.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts">
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
import { cn, type WithElementRef } from '$lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements';

let {
ref = $bindable(null),
Expand All @@ -13,7 +13,7 @@
<div
bind:this={ref}
data-slot="dialog-footer"
class={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
class={cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)}
{...restProps}
>
{@render children?.()}
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/lib/components/ui/dialog/dialog-header.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from 'svelte/elements';
import { cn, type WithElementRef } from '$lib/utils.js';

let {
ref = $bindable(null),
Expand All @@ -13,7 +13,7 @@
<div
bind:this={ref}
data-slot="dialog-header"
class={cn("flex flex-col gap-2 text-center sm:text-left", className)}
class={cn('flex flex-col gap-2 text-center sm:text-left', className)}
{...restProps}
>
{@render children?.()}
Expand Down
Loading
Loading