chore: archive inactive projects to apps-archived/

Move inactive projects out of active workspace:
- bauntown (community website)
- maerchenzauber (AI story generation)
- memoro (voice memo app)
- news (news aggregation)
- nutriphi (nutrition tracking)
- reader (reading app)
- uload (URL shortener)
- wisekeep (AI wisdom extraction)

Update CLAUDE.md documentation:
- Add presi to active projects
- Document archived projects section
- Update workspace configuration

Archived apps can be re-activated by moving back to apps/

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Till-JS 2025-11-29 07:03:59 +01:00
parent b97149ac12
commit 61d181fbc2
3148 changed files with 437 additions and 46640 deletions

View file

@ -0,0 +1,165 @@
import type { User, Tag, Folder, Link, Click } from '$lib/pocketbase';
let idCounter = 0;
function generateId(): string {
return `test_${Date.now()}_${++idCounter}`;
}
export function createTestUser(overrides: Partial<User> = {}): User {
return {
id: generateId(),
email: 'test@example.com',
username: 'testuser',
name: 'Test User',
avatar: '',
bio: 'Test bio',
location: 'Test Location',
website: 'https://example.com',
github: 'testuser',
twitter: 'testuser',
linkedin: 'testuser',
instagram: 'testuser',
publicProfile: true,
showClickStats: true,
created: new Date().toISOString(),
updated: new Date().toISOString(),
...overrides,
};
}
export function createTestTag(overrides: Partial<Tag> = {}): Tag {
return {
id: generateId(),
user_id: 'test_user_123',
name: 'Test Tag',
slug: 'test-tag',
color: '#3B82F6',
icon: '🏷️',
is_public: false,
usage_count: 0,
created: new Date().toISOString(),
updated: new Date().toISOString(),
...overrides,
};
}
export function createTestFolder(overrides: Partial<Folder> = {}): Folder {
return {
id: generateId(),
user_id: 'test_user_123',
name: 'test-folder',
display_name: 'Test Folder',
description: 'Test folder description',
icon: '📁',
color: '#3B82F6',
is_public: true,
order: 0,
created: new Date().toISOString(),
updated: new Date().toISOString(),
...overrides,
};
}
export function createTestLink(overrides: Partial<Link> = {}): Link {
return {
id: generateId(),
user_id: 'test_user_123',
original_url: 'https://example.com',
short_code: 'abc123',
title: 'Test Link',
description: 'Test link description',
is_active: true,
expires_at: undefined,
password: undefined,
max_clicks: undefined,
use_username: false,
folder_id: undefined,
created: new Date().toISOString(),
updated: new Date().toISOString(),
...overrides,
};
}
export function createTestClick(overrides: Partial<Click> = {}): Click {
return {
id: generateId(),
link_id: 'test_link_123',
ip_address: '127.0.0.1',
user_agent: 'Mozilla/5.0 Test Browser',
referer: 'https://google.com',
country: 'US',
device_type: 'desktop',
browser: 'Chrome',
clicked_at: new Date().toISOString(),
created: new Date().toISOString(),
...overrides,
};
}
export function createBatchTestTags(count: number, userId: string): Tag[] {
const tags: Tag[] = [];
const colors = ['#3B82F6', '#EF4444', '#10B981', '#F59E0B', '#8B5CF6'];
const icons = ['🏷️', '📌', '⭐', '💡', '🔥'];
for (let i = 0; i < count; i++) {
tags.push(
createTestTag({
name: `Tag ${i + 1}`,
slug: `tag-${i + 1}`,
user_id: userId,
color: colors[i % colors.length],
icon: icons[i % icons.length],
usage_count: Math.floor(Math.random() * 10),
})
);
}
return tags;
}
export function createBatchTestFolders(count: number, userId: string): Folder[] {
const folders: Folder[] = [];
const colors = ['#3B82F6', '#EF4444', '#10B981', '#F59E0B', '#8B5CF6'];
const icons = ['📁', '📂', '🗂️', '📚', '💼'];
for (let i = 0; i < count; i++) {
folders.push(
createTestFolder({
name: `folder-${i + 1}`,
display_name: `Folder ${i + 1}`,
user_id: userId,
color: colors[i % colors.length],
icon: icons[i % icons.length],
order: i,
})
);
}
return folders;
}
export function createAuthError(message: string = 'Invalid credentials') {
return {
response: {
data: {
message,
code: 401,
},
},
};
}
export function createValidationError(field: string, message: string) {
return {
response: {
data: {
data: {
[field]: {
message,
},
},
},
},
};
}

View file

@ -0,0 +1,107 @@
import { vi } from 'vitest';
import type { Mock } from 'vitest';
export interface MockCollection {
create: Mock;
update: Mock;
delete: Mock;
getList: Mock;
getOne: Mock;
getFirstListItem: Mock;
authWithPassword: Mock;
}
export interface MockPocketBase {
collection: Mock<[string], MockCollection>;
authStore: {
isValid: boolean;
token: string;
model: any;
clear: Mock;
};
baseUrl: string;
}
export function createMockPocketBase(): MockPocketBase {
return {
collection: vi.fn((name: string) => ({
create: vi.fn(),
update: vi.fn(),
delete: vi.fn(),
getList: vi.fn(() =>
Promise.resolve({
items: [],
totalItems: 0,
totalPages: 0,
page: 1,
perPage: 20,
})
),
getOne: vi.fn(),
getFirstListItem: vi.fn(() => Promise.reject(new Error('No items found'))),
authWithPassword: vi.fn(),
})),
authStore: {
isValid: false,
token: '',
model: null,
clear: vi.fn(),
},
baseUrl: 'http://localhost:8090',
};
}
export function mockSuccessfulAuth(pb: MockPocketBase, user: any) {
const collection = pb.collection('users') as MockCollection;
collection.authWithPassword.mockResolvedValue({
token: 'mock-token',
record: user,
});
pb.authStore.isValid = true;
pb.authStore.token = 'mock-token';
pb.authStore.model = user;
}
export function mockFailedAuth(pb: MockPocketBase, error: string = 'Invalid credentials') {
const collection = pb.collection('users') as MockCollection;
collection.authWithPassword.mockRejectedValue(new Error(error));
}
export function mockCreateSuccess(pb: MockPocketBase, collectionName: string, data: any) {
const collection = pb.collection(collectionName) as MockCollection;
collection.create.mockResolvedValue({
...data,
id: 'mock-id-' + Date.now(),
created: new Date().toISOString(),
updated: new Date().toISOString(),
});
}
export function mockCreateError(pb: MockPocketBase, collectionName: string, error: any) {
const collection = pb.collection(collectionName) as MockCollection;
collection.create.mockRejectedValue(error);
}
export function mockGetListSuccess(pb: MockPocketBase, collectionName: string, items: any[]) {
const collection = pb.collection(collectionName) as MockCollection;
collection.getList.mockResolvedValue({
items,
totalItems: items.length,
totalPages: 1,
page: 1,
perPage: 20,
});
}
export function mockUpdateSuccess(pb: MockPocketBase, collectionName: string, updatedData: any) {
const collection = pb.collection(collectionName) as MockCollection;
collection.update.mockResolvedValue({
...updatedData,
updated: new Date().toISOString(),
});
}
export function mockDeleteSuccess(pb: MockPocketBase, collectionName: string) {
const collection = pb.collection(collectionName) as MockCollection;
collection.delete.mockResolvedValue(true);
}

View file

@ -0,0 +1,240 @@
// Global test setup für uLoad
import { vi } from 'vitest';
import type { Mock } from 'vitest';
// Check if we're in browser environment
const isBrowser = typeof window !== 'undefined';
const isNode = typeof process !== 'undefined' && process.versions && process.versions.node;
// Mock SvelteKit modules
vi.mock('$app/environment', () => ({
browser: false,
dev: true,
building: false,
version: '1.0.0',
}));
vi.mock('$app/stores', () => ({
page: {
subscribe: vi.fn(() => () => {}),
},
updated: {
subscribe: vi.fn(() => () => {}),
},
navigating: {
subscribe: vi.fn(() => () => {}),
},
}));
vi.mock('$app/navigation', () => ({
goto: vi.fn(),
invalidate: vi.fn(),
invalidateAll: vi.fn(),
preloadData: vi.fn(),
pushState: vi.fn(),
replaceState: vi.fn(),
}));
// Mock PocketBase für Tests
vi.mock('$lib/pocketbase', async () => {
const actual = await vi.importActual('$lib/pocketbase');
// Mock PocketBase-Instanz
const mockPb = {
authStore: {
model: null,
token: '',
isValid: false,
save: vi.fn(),
clear: vi.fn(),
onChange: vi.fn(() => () => {}),
},
collection: vi.fn(() => ({
create: vi.fn(),
getList: vi.fn(),
getOne: vi.fn(),
update: vi.fn(),
delete: vi.fn(),
authWithPassword: vi.fn(),
authRefresh: vi.fn(),
requestPasswordReset: vi.fn(),
confirmPasswordReset: vi.fn(),
requestVerification: vi.fn(),
confirmVerification: vi.fn(),
})),
send: vi.fn(),
};
return {
...actual,
pb: mockPb,
};
});
// Mock Toast Service
vi.mock('$lib/services/toast', () => ({
toast: {
success: vi.fn(),
error: vi.fn(),
info: vi.fn(),
warning: vi.fn(),
},
}));
// Mock Theme Store
vi.mock('$lib/theme.svelte', () => ({
themeMode: { value: 'light' },
themePreset: { value: 'minimal' },
applyTheme: vi.fn(),
initializeTheme: vi.fn(),
}));
// Global test utilities
export const createMockEvent = (data: any = {}) => ({
request: {
method: 'GET',
url: new URL('http://localhost:5173'),
formData: async () => new FormData(),
json: async () => data,
...data.request,
},
locals: {
pb: {
authStore: { model: null, isValid: false },
collection: vi.fn(() => ({
create: vi.fn(),
getList: vi.fn(),
getOne: vi.fn(),
update: vi.fn(),
delete: vi.fn(),
})),
},
...data.locals,
},
cookies: {
get: vi.fn(),
set: vi.fn(),
delete: vi.fn(),
},
...data,
});
// Mock User für Tests
export const createMockUser = (overrides = {}) => ({
id: 'test-user-id',
email: 'test@example.com',
username: 'testuser',
name: 'Test User',
verified: true,
created: '2024-01-01T00:00:00Z',
updated: '2024-01-01T00:00:00Z',
...overrides,
});
// Mock Link für Tests
export const createMockLink = (overrides = {}) => ({
id: 'test-link-id',
user_id: 'test-user-id',
original_url: 'https://example.com',
short_code: 'test123',
title: 'Test Link',
is_active: true,
click_count: 0,
created: '2024-01-01T00:00:00Z',
updated: '2024-01-01T00:00:00Z',
...overrides,
});
// Mock Analytics für Tests
export const createMockAnalytics = (overrides = {}) => ({
id: 'test-analytics-id',
link_id: 'test-link-id',
ip_address: '127.0.0.1',
user_agent: 'Mozilla/5.0',
country: 'Germany',
device: 'desktop',
created: '2024-01-01T00:00:00Z',
...overrides,
});
// Test Environment Setup
beforeEach(() => {
// Reset all mocks before each test
vi.clearAllMocks();
// Reset DOM (only in browser)
if (isBrowser && typeof document !== 'undefined') {
document.body.innerHTML = '';
}
// Reset localStorage/sessionStorage (only in browser)
if (isBrowser && typeof localStorage !== 'undefined') {
localStorage.clear();
sessionStorage.clear();
}
});
// Global error handler für Tests (only in Node)
if (isNode) {
process.on('unhandledRejection', (error) => {
console.error('Unhandled Promise Rejection in test:', error);
});
}
// Extend expect with custom matchers
import { expect } from 'vitest';
expect.extend({
toBeValidEmail(received: string) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const pass = emailRegex.test(received);
return {
pass,
message: () =>
pass
? `Expected "${received}" not to be a valid email`
: `Expected "${received}" to be a valid email`,
};
},
toBeValidUsername(received: string) {
const usernameRegex = /^[a-z0-9_-]+$/;
const pass = usernameRegex.test(received) && received.length >= 3;
return {
pass,
message: () =>
pass
? `Expected "${received}" not to be a valid username`
: `Expected "${received}" to be a valid username (3+ chars, lowercase, numbers, - and _ only)`,
};
},
toBeValidShortCode(received: string) {
const codeRegex = /^[a-zA-Z0-9_-]+$/;
const pass = codeRegex.test(received) && received.length >= 3;
return {
pass,
message: () =>
pass
? `Expected "${received}" not to be a valid short code`
: `Expected "${received}" to be a valid short code`,
};
},
});
// Type declarations für custom matchers
declare module 'vitest' {
interface Assertion<T = any> {
toBeValidEmail(): T;
toBeValidUsername(): T;
toBeValidShortCode(): T;
}
interface AsymmetricMatchersContaining {
toBeValidEmail(): any;
toBeValidUsername(): any;
toBeValidShortCode(): any;
}
}