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:
12
tests/integration/deletion-persistence.test.ts
Normal file
12
tests/integration/deletion-persistence.test.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Deletion Persistence', () => {
|
||||
test('should permanently remove item from Dexie DB', async () => {
|
||||
// GIVEN: Item exists in DB
|
||||
|
||||
// WHEN: Delete action (via Service or UI)
|
||||
|
||||
// THEN: Item is gone from DB
|
||||
expect(true).toBe(false);
|
||||
});
|
||||
});
|
||||
27
tests/integration/ghostwriter-persistence.test.ts
Normal file
27
tests/integration/ghostwriter-persistence.test.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Ghostwriter Persistence', () => {
|
||||
test('Scenario 2.4.2 (P0): Session Saved as Completed', async ({ request }) => {
|
||||
// GIVEN: A chat session flow is completed
|
||||
// This is an API/Integration test, so we hit the endpoint directly if possible,
|
||||
// OR we use the service capability if we were in a unit test.
|
||||
// For Playwright Integration (API), we assume an endpoint exists to finalize.
|
||||
|
||||
// Setup: Create a session
|
||||
const createRes = await request.post('/api/sessions', { data: { title: 'Test Session' } });
|
||||
const { sessionId } = await createRes.json();
|
||||
|
||||
// WHEN: User saves the final draft
|
||||
const saveRes = await request.post(`/api/sessions/${sessionId}/finalize`, {
|
||||
data: { finalContent: 'My Lesson' }
|
||||
});
|
||||
expect(saveRes.status()).toBe(200);
|
||||
|
||||
// THEN: Session status is COMPLETED
|
||||
const getRes = await request.get(`/api/sessions/${sessionId}`);
|
||||
const session = await getRes.json();
|
||||
|
||||
expect(session.status).toBe('COMPLETED');
|
||||
expect(session.finalArtifact).toBe('My Lesson');
|
||||
});
|
||||
});
|
||||
79
tests/integration/offline-action-queueing.test.ts
Normal file
79
tests/integration/offline-action-queueing.test.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
|
||||
import { expect } from '@playwright/test';
|
||||
import { test } from '../support/fixtures/offline.fixture';
|
||||
|
||||
test.describe('Offline Action Queueing', () => {
|
||||
test('should enqueue SAVE_DRAFT action when offline', async ({ offlineControl, page }) => {
|
||||
// GIVEN: App is loaded
|
||||
await page.goto('/');
|
||||
await page.waitForFunction(() => (window as any).db !== undefined && (window as any).DraftService !== undefined, { timeout: 10000 });
|
||||
|
||||
// GIVEN: App goes offline
|
||||
await offlineControl.goOffline(page.context());
|
||||
|
||||
// WHEN: DraftService.saveDraft is called (Service Integration)
|
||||
await page.evaluate(async () => {
|
||||
// @ts-ignore
|
||||
await window.DraftService.saveDraft({
|
||||
title: 'Offline Draft',
|
||||
content: 'Content written offline',
|
||||
tags: [],
|
||||
createdAt: Date.now(),
|
||||
status: 'draft',
|
||||
sessionId: 'test-session'
|
||||
});
|
||||
});
|
||||
|
||||
// THEN: 'syncQueue' table has 1 item
|
||||
const queueCount = await page.evaluate(async () => {
|
||||
// @ts-ignore
|
||||
return await window.db.syncQueue.count();
|
||||
});
|
||||
|
||||
expect(queueCount).toBe(1);
|
||||
|
||||
const firstItem = await page.evaluate(async () => {
|
||||
// @ts-ignore
|
||||
return await window.db.syncQueue.orderBy('createdAt').first();
|
||||
});
|
||||
expect(firstItem.action).toBe('saveDraft');
|
||||
expect(firstItem.payload.draftData.title).toBe('Offline Draft');
|
||||
});
|
||||
|
||||
test('should enqueue DELETE_ENTRY action when offline', async ({ offlineControl, page }) => {
|
||||
// GIVEN: App is loaded and has an entry
|
||||
await page.goto('/');
|
||||
await page.waitForFunction(() => (window as any).db !== undefined && (window as any).DraftService !== undefined, { timeout: 10000 });
|
||||
|
||||
// Setup entry
|
||||
const draftId = await page.evaluate(async () => {
|
||||
// @ts-ignore
|
||||
const id = await window.db.drafts.add({
|
||||
title: 'To Delete',
|
||||
content: 'Delete me',
|
||||
tags: [],
|
||||
createdAt: Date.now(),
|
||||
status: 'draft',
|
||||
sessionId: 'test-session'
|
||||
});
|
||||
return id;
|
||||
});
|
||||
|
||||
await offlineControl.goOffline(page.context());
|
||||
|
||||
// WHEN: DraftService.deleteDraft is called
|
||||
await page.evaluate(async (id) => {
|
||||
// @ts-ignore
|
||||
await window.DraftService.deleteDraft(id);
|
||||
}, draftId);
|
||||
|
||||
// THEN: SyncQueue has delete action
|
||||
const lastItem = await page.evaluate(async () => {
|
||||
// @ts-ignore
|
||||
return await window.db.syncQueue.orderBy('createdAt').last();
|
||||
});
|
||||
|
||||
expect(lastItem.action).toBe('deleteDraft');
|
||||
expect(lastItem.payload.draftId).toBe(draftId);
|
||||
});
|
||||
});
|
||||
45
tests/integration/pwa-manifest.test.ts
Normal file
45
tests/integration/pwa-manifest.test.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('PWA Manifest', () => {
|
||||
test('should serve a valid manifest.webmanifest', async ({ request }) => {
|
||||
const response = await request.get('/manifest.webmanifest');
|
||||
expect(response.status()).toBe(200);
|
||||
expect(response.headers()['content-type']).toContain('application/manifest+json');
|
||||
|
||||
const manifest = await response.json();
|
||||
|
||||
// Core properties matching manifest.ts
|
||||
expect(manifest.name).toBe('Test01');
|
||||
expect(manifest.short_name).toBe('Test01');
|
||||
expect(manifest.display).toBe('standalone');
|
||||
expect(manifest.start_url).toBe('/');
|
||||
expect(manifest.background_color).toBe('#F8FAFC');
|
||||
expect(manifest.theme_color).toBe('#64748B');
|
||||
|
||||
// Icons
|
||||
expect(manifest.icons.length).toBeGreaterThanOrEqual(2);
|
||||
expect(manifest.icons).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
src: '/icons/icon-192x192.png',
|
||||
sizes: '192x192',
|
||||
type: 'image/png'
|
||||
}),
|
||||
expect.objectContaining({
|
||||
src: '/icons/icon-512x512.png',
|
||||
sizes: '512x512',
|
||||
type: 'image/png'
|
||||
})
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
test('should have accessible icon files', async ({ request }) => {
|
||||
const icon192 = await request.get('/icons/icon-192x192.png');
|
||||
expect(icon192.status()).toBe(200);
|
||||
|
||||
const icon512 = await request.get('/icons/icon-512x512.png');
|
||||
expect(icon512.status()).toBe(200);
|
||||
});
|
||||
});
|
||||
40
tests/integration/sync-action-replay.test.ts
Normal file
40
tests/integration/sync-action-replay.test.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
|
||||
import { expect } from '@playwright/test';
|
||||
import { test } from '../support/fixtures/offline.fixture';
|
||||
|
||||
test.describe('Sync Action Replay', () => {
|
||||
test('should process pending actions when network restores', async ({ offlineControl, page }) => {
|
||||
// GIVEN: Offline queue has actions
|
||||
await page.goto('/'); // Ensure page is loaded to initialize DB
|
||||
await page.waitForFunction(() => (window as any).db !== undefined, { timeout: 10000 });
|
||||
|
||||
await offlineControl.goOffline(page.context());
|
||||
|
||||
// Seed queue via evaluate
|
||||
await page.evaluate(async () => {
|
||||
// @ts-ignore
|
||||
await window.db.syncQueue.add({
|
||||
action: 'saveDraft',
|
||||
payload: { draftData: { title: 'Pending Sync', content: '...' } },
|
||||
status: 'pending',
|
||||
createdAt: Date.now(),
|
||||
retries: 0
|
||||
});
|
||||
});
|
||||
|
||||
// WHEN: Network restores
|
||||
await offlineControl.goOnline(page.context());
|
||||
|
||||
// Trigger sync - The SyncManager listens to 'online' event.
|
||||
// Note: The 'online' event listeners might execute async.
|
||||
|
||||
// Wait for processing
|
||||
// We can poll the DB
|
||||
await expect.poll(async () => {
|
||||
return await page.evaluate(async () => {
|
||||
// @ts-ignore
|
||||
return await window.db.syncQueue.where('status').equals('pending').count();
|
||||
});
|
||||
}, { timeout: 5000 }).toBe(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user