Initial commit: Brachnha Insight project setup

- Next.js 14+ with App Router and TypeScript
- Tailwind CSS and ShadCN UI styling
- Zustand state management
- Dexie.js for IndexedDB (local-first data)
- Auth.js v5 for authentication
- BMAD framework integration

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Max
2026-01-26 12:28:43 +07:00
commit 3fbbb1a93b
812 changed files with 150531 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
import { test as base } from '@playwright/test';
type DbFixture = {
resetDb: () => Promise<void>;
seedHistory: (data: any[]) => Promise<void>;
getHistory: () => Promise<any[]>;
getSyncQueue: () => Promise<any[]>;
};
export const test = base.extend<{ db: DbFixture }>({
db: async ({ page }, use) => {
const dbFixture = {
resetDb: async () => {
await page.evaluate(async () => {
// Assuming 'Dexie' is globally available or we use indexedDB directly
// Using indexedDB directly to be safe as Dexie might be bundled
const req = indexedDB.deleteDatabase('Test01DB'); // Match your DB name
return new Promise((resolve, reject) => {
req.onsuccess = () => resolve(true);
req.onerror = () => reject(req.error);
req.onblocked = () => console.warn('Delete DB blocked');
});
});
},
seedHistory: async (data: any[]) => {
// This is tricky if logic is encapsulated.
// We might need to expose a hidden window helper or just rely on UI for seeding in E2E
// OR use a specialized seeding script.
// For Integration/Component, we might assume we can import the DB client directly if running in same context.
// Since playwight runs in browser context, we'd need to expose it.
// For now, let's assume we can invoke a window helper if useful, or just implement specific logic
await page.evaluate(async (items) => {
// We'll need the app to expose a way to seed, or we assume specific DB structure
// This requires the app to have the DB initialized.
// Implementation deferred to actual test via page.evaluate if needed
}, data);
},
getHistory: async () => {
// Placeholder for getting history from DB
return [];
},
getSyncQueue: async () => {
// Placeholder
return [];
}
};
await use(dbFixture);
},
});

View File

@@ -0,0 +1,37 @@
import { faker } from '@faker-js/faker';
/**
* UserFactory
*
* Handles creation and cleanup of test users.
* Note: Since this is a local-first app without a real backend API for user creation yet,
* this factory currently generates mock data. adapting to real API calls later.
*/
export class UserFactory {
// In a real app, we would track IDs here for cleanup
// private createdUserIds: string[] = [];
async createUser(overrides = {}) {
const user = {
id: faker.string.uuid(),
email: faker.internet.email(),
name: faker.person.fullName(),
password: faker.internet.password(),
createdAt: new Date().toISOString(),
...overrides,
};
// Placeholder: In a real app, you would POST to API here
// const response = await fetch(\`\${process.env.API_URL}/users\`, ...);
return user;
}
async cleanup() {
// Placeholder: In a real app, you would DELETE users here
// for (const id of this.createdUserIds) { ... }
// For now, no cleanup needed for transient mock data
return Promise.resolve();
}
}

View File

@@ -0,0 +1,16 @@
import { test as base } from '@playwright/test';
import { UserFactory } from './factories/user-factory';
type TestFixtures = {
userFactory: UserFactory;
};
export const test = base.extend<TestFixtures>({
userFactory: async ({ }, use) => {
const factory = new UserFactory();
await use(factory);
await factory.cleanup();
},
});
export { expect } from '@playwright/test';

View File

@@ -0,0 +1,27 @@
import { test as base, BrowserContext } from '@playwright/test';
type OfflineFixture = {
goOffline: (context: BrowserContext) => Promise<void>;
goOnline: (context: BrowserContext) => Promise<void>;
};
export const test = base.extend<{ offlineControl: OfflineFixture }>({
offlineControl: async ({ }, use) => {
const offlineFixture = {
goOffline: async (context: BrowserContext) => {
await context.setOffline(true);
for (const page of context.pages()) {
await page.evaluate(() => window.dispatchEvent(new Event('offline'))).catch(() => { });
}
},
goOnline: async (context: BrowserContext) => {
await context.setOffline(false);
for (const page of context.pages()) {
await page.evaluate(() => window.dispatchEvent(new Event('online'))).catch(() => { });
}
},
};
await use(offlineFixture);
},
});