diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index b2b78bb..62b82c8 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -5,7 +5,7 @@ on: pull_request: branches: [ main, master ] jobs: - test: + e2e-testing: timeout-minutes: 60 runs-on: ubuntu-latest steps: diff --git a/src/components/PDFViewer.tsx b/src/components/PDFViewer.tsx index c160cf2..358bbb8 100644 --- a/src/components/PDFViewer.tsx +++ b/src/components/PDFViewer.tsx @@ -36,7 +36,7 @@ export function PDFViewer({ zoomLevel }: PDFViewerProps) { clearHighlights, handleTextClick, onDocumentLoadSuccess, - currDocURL, + currDocData, currDocPages, currDocText, currDocPage, @@ -163,7 +163,7 @@ export function PDFViewer({ zoomLevel }: PDFViewerProps) { } noData={} - file={currDocURL} + file={currDocData} onLoadSuccess={(pdf) => { onDocumentLoadSuccess(pdf); }} diff --git a/src/contexts/PDFContext.tsx b/src/contexts/PDFContext.tsx index e789024..9d7f910 100644 --- a/src/contexts/PDFContext.tsx +++ b/src/contexts/PDFContext.tsx @@ -29,7 +29,6 @@ import { useTTS } from '@/contexts/TTSContext'; import { useConfig } from '@/contexts/ConfigContext'; import { extractTextFromPDF, - convertPDFDataToURL, highlightPattern, clearHighlights, handleTextClick, @@ -43,7 +42,7 @@ import { combineAudioChunks } from '@/utils/audio'; */ interface PDFContextType { // Current document state - currDocURL: string | undefined; + currDocData: ArrayBuffer | undefined; currDocName: string | undefined; currDocPages: number | undefined; currDocPage: number; @@ -99,7 +98,7 @@ export function PDFProvider({ children }: { children: ReactNode }) { } = useConfig(); // Current document state - const [currDocURL, setCurrDocURL] = useState(); + const [currDocData, setCurrDocData] = useState(); const [currDocName, setCurrDocName] = useState(); const [currDocText, setCurrDocText] = useState(); const [pdfDocument, setPdfDocument] = useState(); @@ -147,14 +146,14 @@ export function PDFProvider({ children }: { children: ReactNode }) { * Triggers text extraction and processing when either the document URL or page changes */ useEffect(() => { - if (currDocURL) { + if (currDocData) { loadCurrDocText(); } - }, [currDocPage, currDocURL, loadCurrDocText]); + }, [currDocPage, currDocData, loadCurrDocText]); /** * Sets the current document based on its ID - * Retrieves document from IndexedDB and converts it to a viewable URL + * Retrieves document from IndexedDB * * @param {string} id - The unique identifier of the document to set * @returns {Promise} @@ -163,12 +162,11 @@ export function PDFProvider({ children }: { children: ReactNode }) { try { const doc = await indexedDBService.getDocument(id); if (doc) { - const url = await convertPDFDataToURL(doc.data); setCurrDocName(doc.name); - setCurrDocURL(url); + setCurrDocData(doc.data); } } catch (error) { - console.error('Failed to get document URL:', error); + console.error('Failed to get document:', error); } }, []); @@ -178,7 +176,7 @@ export function PDFProvider({ children }: { children: ReactNode }) { */ const clearCurrDoc = useCallback(() => { setCurrDocName(undefined); - setCurrDocURL(undefined); + setCurrDocData(undefined); setCurrDocText(undefined); setCurrDocPages(undefined); setPdfDocument(undefined); @@ -306,7 +304,7 @@ export function PDFProvider({ children }: { children: ReactNode }) { () => ({ onDocumentLoadSuccess, setCurrentDocument, - currDocURL, + currDocData, currDocName, currDocPages, currDocPage, @@ -322,7 +320,7 @@ export function PDFProvider({ children }: { children: ReactNode }) { [ onDocumentLoadSuccess, setCurrentDocument, - currDocURL, + currDocData, currDocName, currDocPages, currDocPage, diff --git a/src/hooks/pdf/usePDFDocuments.ts b/src/hooks/pdf/usePDFDocuments.ts index 45d6ec3..e2fa4ea 100644 --- a/src/hooks/pdf/usePDFDocuments.ts +++ b/src/hooks/pdf/usePDFDocuments.ts @@ -39,13 +39,15 @@ export function usePDFDocuments() { */ const addDocument = useCallback(async (file: File): Promise => { const id = uuidv4(); + const arrayBuffer = await file.arrayBuffer(); + const newDoc: PDFDocument = { id, type: 'pdf', name: file.name, size: file.size, lastModified: file.lastModified, - data: new Blob([file], { type: file.type }), + data: arrayBuffer, }; try { diff --git a/src/types/documents.ts b/src/types/documents.ts index 5097115..07cbd41 100644 --- a/src/types/documents.ts +++ b/src/types/documents.ts @@ -11,7 +11,7 @@ export interface BaseDocument { export interface PDFDocument extends BaseDocument { type: 'pdf'; - data: Blob; + data: ArrayBuffer; } export interface EPUBDocument extends BaseDocument { diff --git a/src/utils/indexedDB.ts b/src/utils/indexedDB.ts index 6df16b9..e60aa5a 100644 --- a/src/utils/indexedDB.ts +++ b/src/utils/indexedDB.ts @@ -43,7 +43,7 @@ class IndexedDBService { request.onupgradeneeded = (event) => { console.log('Upgrading IndexedDB schema...'); const db = (event.target as IDBOpenDBRequest).result; - + if (!db.objectStoreNames.contains(PDF_STORE_NAME)) { console.log('Creating PDF documents store...'); db.createObjectStore(PDF_STORE_NAME, { keyPath: 'id' }); @@ -75,7 +75,12 @@ class IndexedDBService { console.log('Adding document to IndexedDB:', document.name); const transaction = this.db!.transaction([PDF_STORE_NAME], 'readwrite'); const store = transaction.objectStore(PDF_STORE_NAME); - const request = store.put(document); + + // Create a structured clone of the document to ensure proper storage + const request = store.put({ + ...document, + data: document.data // Store ArrayBuffer directly + }); request.onerror = (event) => { const error = (event.target as IDBRequest).error; @@ -165,7 +170,7 @@ class IndexedDBService { // Create two transactions - one for document deletion, one for location cleanup const docTransaction = this.db!.transaction([PDF_STORE_NAME], 'readwrite'); const configTransaction = this.db!.transaction([CONFIG_STORE_NAME], 'readwrite'); - + const docStore = docTransaction.objectStore(PDF_STORE_NAME); const configStore = configTransaction.objectStore(CONFIG_STORE_NAME); @@ -221,7 +226,7 @@ class IndexedDBService { const transaction = this.db!.transaction([EPUB_STORE_NAME], 'readwrite'); const store = transaction.objectStore(EPUB_STORE_NAME); - + // Create a structured clone of the document to ensure proper storage const request = store.put({ ...document, @@ -315,7 +320,7 @@ class IndexedDBService { // Create two transactions - one for document deletion, one for location cleanup const docTransaction = this.db!.transaction([EPUB_STORE_NAME], 'readwrite'); const configTransaction = this.db!.transaction([CONFIG_STORE_NAME], 'readwrite'); - + const docStore = docTransaction.objectStore(EPUB_STORE_NAME); const configStore = configTransaction.objectStore(CONFIG_STORE_NAME); @@ -477,17 +482,15 @@ class IndexedDBService { async syncToServer(): Promise<{ lastSync: number }> { const pdfDocs = await this.getAllDocuments(); const epubDocs = await this.getAllEPUBDocuments(); - + const documents = []; - - // Process PDF documents - store the raw PDF data + + // Process PDF documents - convert ArrayBuffer to array for JSON serialization for (const doc of pdfDocs) { - const arrayBuffer = await doc.data.arrayBuffer(); - const uint8Array = new Uint8Array(arrayBuffer); documents.push({ ...doc, type: 'pdf', - data: Array.from(uint8Array) // Convert to regular array for JSON serialization + data: Array.from(new Uint8Array(doc.data)) }); } @@ -496,7 +499,7 @@ class IndexedDBService { documents.push({ ...doc, type: 'epub', - data: Array.from(new Uint8Array(doc.data)) // Convert to regular array for JSON serialization + data: Array.from(new Uint8Array(doc.data)) }); } @@ -520,31 +523,26 @@ class IndexedDBService { } const { documents } = await response.json(); - + // Process each document for (const doc of documents) { + // Convert the numeric array back to ArrayBuffer + const uint8Array = new Uint8Array(doc.data); + const documentData = { + id: doc.id, + type: doc.type, + name: doc.name, + size: doc.size, + lastModified: doc.lastModified, + data: uint8Array.buffer + }; + if (doc.type === 'pdf') { - // Create a Blob from the raw binary data - const blob = new Blob([new Uint8Array(doc.data)], { type: 'application/pdf' }); - await this.addDocument({ - id: doc.id, - type: doc.type, - name: doc.name, - size: doc.size, - lastModified: doc.lastModified, - data: blob - }); + await this.addDocument(documentData); } else if (doc.type === 'epub') { - // Convert the numeric array back to ArrayBuffer for EPUB - const uint8Array = new Uint8Array(doc.data); - await this.addEPUBDocument({ - id: doc.id, - type: doc.type, - name: doc.name, - size: doc.size, - lastModified: doc.lastModified, - data: uint8Array.buffer - }); + await this.addEPUBDocument(documentData); + } else { + console.warn(`Unknown document type: ${doc.type}`); } } diff --git a/src/utils/pdf.ts b/src/utils/pdf.ts index 24eac9a..1d1515d 100644 --- a/src/utils/pdf.ts +++ b/src/utils/pdf.ts @@ -15,16 +15,6 @@ interface TextMatch { lengthDiff: number; } -// URL Conversion functions -export function convertPDFDataToURL(pdfData: Blob): Promise { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onload = () => resolve(reader.result as string); - reader.onerror = () => reject(reader.error); - reader.readAsDataURL(pdfData); - }); -} - // Text Processing functions export async function extractTextFromPDF( pdf: PDFDocumentProxy, diff --git a/tests/e2e.spec.ts b/tests/e2e.spec.ts index baa0b28..92ee4b7 100644 --- a/tests/e2e.spec.ts +++ b/tests/e2e.spec.ts @@ -11,7 +11,7 @@ async function uploadFile(page: Page, filePath: string) { test.describe('Document Upload and Display', () => { test.beforeEach(async ({ page }) => { // Navigate to the home page before each test - await page.goto('http://localhost:3003'); + await page.goto('/'); // Click the "done" button to dismiss the welcome message await page.getByText('Done').click(); @@ -23,7 +23,7 @@ test.describe('Document Upload and Display', () => { await uploadFile(page, './public/sample.pdf'); // Verify upload success - await expect(page.getByText('sample.pdf')).toBeVisible(); + await expect(page.getByText('sample.pdf')).toBeVisible({ timeout: 10000 }); }); test('should display a PDF document', async ({ page }) => { @@ -31,11 +31,11 @@ test.describe('Document Upload and Display', () => { await uploadFile(page, './public/sample.pdf'); // Click on the uploaded document - await page.getByText('sample.pdf').click(); + await page.getByText('sample.pdf').click({ timeout: 10000 }); // Verify PDF viewer is displayed - await expect(page.locator('.react-pdf__Document')).toBeVisible(); - await expect(page.locator('.react-pdf__Page')).toBeVisible(); + await expect(page.locator('.react-pdf__Document')).toBeVisible({ timeout: 10000 }); + await expect(page.locator('.react-pdf__Page')).toBeVisible({ timeout: 10000 }); }); }); @@ -45,7 +45,7 @@ test.describe('Document Upload and Display', () => { await uploadFile(page, './public/sample.epub'); // Verify upload success - await expect(page.getByText('sample.epub')).toBeVisible(); + await expect(page.getByText('sample.epub')).toBeVisible({ timeout: 10000 }); }); test('should display an EPUB document', async ({ page }) => { @@ -53,10 +53,10 @@ test.describe('Document Upload and Display', () => { await uploadFile(page, './public/sample.epub'); // Click on the uploaded document - await page.getByText('sample.epub').click(); + await page.getByText('sample.epub').click({ timeout: 10000 }); // Verify EPUB viewer is displayed - await expect(page.locator('.epub-container')).toBeVisible(); + await expect(page.locator('.epub-container')).toBeVisible({ timeout: 10000 }); }); }); });