From c5d3631cb636c03ba09a4453cd9384fe593f6221 Mon Sep 17 00:00:00 2001 From: William Valentin Date: Sun, 7 Sep 2025 15:20:59 -0700 Subject: [PATCH] feat: configure Jest testing infrastructure - Update Jest config with module name mapping for uuid and node-fetch - Add Babel transform for mixed JS/TS support - Configure transformIgnorePatterns for ES modules - Add comprehensive test mocks for uuid and node-fetch - Setup import.meta environment variables for Jest compatibility - Increase test timeout to 30 seconds for integration tests --- babel.config.cjs | 31 +++++++++ jest.config.json | 14 ++-- tests/__mocks__/node-fetch.js | 120 ++++++++++++++++++++++++++++++++++ tests/__mocks__/uuid.js | 4 ++ tests/setup.ts | 21 +++++- 5 files changed, 185 insertions(+), 5 deletions(-) create mode 100644 babel.config.cjs create mode 100644 tests/__mocks__/node-fetch.js create mode 100644 tests/__mocks__/uuid.js diff --git a/babel.config.cjs b/babel.config.cjs new file mode 100644 index 0000000..f6eb1cf --- /dev/null +++ b/babel.config.cjs @@ -0,0 +1,31 @@ +module.exports = { + presets: [ + ['@babel/preset-env', { + targets: { + node: 'current' + } + }], + '@babel/preset-typescript' + ], + plugins: [ + // Transform import.meta for Jest compatibility + function() { + return { + visitor: { + MetaProperty(path) { + if (path.node.meta.name === 'import' && path.node.property.name === 'meta') { + path.replaceWithSourceString('({ env: process.env })'); + } + } + } + }; + } + ], + env: { + test: { + plugins: [ + // Additional test-specific plugins can go here + ] + } + } +}; diff --git a/jest.config.json b/jest.config.json index 2e29c81..c0a3c52 100644 --- a/jest.config.json +++ b/jest.config.json @@ -17,10 +17,16 @@ ], "coverageDirectory": "coverage", "coverageReporters": ["text", "lcov", "html"], - "moduleNameMapping": { - "^@/(.*)$": "/$1" + "moduleNameMapper": { + "^@/(.*)$": "/$1", + "^uuid$": "/tests/__mocks__/uuid.js", + "^node-fetch$": "/tests/__mocks__/node-fetch.js" }, "transform": { - "^.+\\.tsx?$": "ts-jest" - } + "^.+\\.tsx?$": "ts-jest", + "^.+\\.jsx?$": "babel-jest" + }, + "transformIgnorePatterns": ["node_modules/(?!(@jest/transform|uuid|node-fetch)/)"], + "testTimeout": 30000, + "moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"] } diff --git a/tests/__mocks__/node-fetch.js b/tests/__mocks__/node-fetch.js new file mode 100644 index 0000000..d927e06 --- /dev/null +++ b/tests/__mocks__/node-fetch.js @@ -0,0 +1,120 @@ +// Mock for node-fetch module +const createMockResponse = (data, ok = true, status = 200) => ({ + ok, + status, + statusText: ok ? 'OK' : 'Error', + headers: new Map(), + json: jest.fn(() => Promise.resolve(data)), + text: jest.fn(() => + Promise.resolve(typeof data === 'string' ? data : JSON.stringify(data)) + ), + blob: jest.fn(() => Promise.resolve(new Blob())), + arrayBuffer: jest.fn(() => Promise.resolve(new ArrayBuffer(0))), + clone: jest.fn(() => createMockResponse(data, ok, status)), +}); + +const fetch = jest.fn((url, options) => { + // Handle different endpoints + if (url && typeof url === 'string') { + if (url.includes('_all_dbs')) { + // Return array of databases for /_all_dbs endpoint + const databases = [ + 'users', + 'medications', + 'settings', + 'taken_doses', + 'reminders', + '_users', + ]; + return Promise.resolve(createMockResponse(databases)); + } + + if (url.includes('5984')) { + // CouchDB root endpoint + return Promise.resolve(createMockResponse({ version: '3.0.0' })); + } + + if (url.includes('8080') || url.includes('localhost')) { + // Frontend endpoint + return Promise.resolve( + createMockResponse('') + ); + } + + if (url.includes('mailgun') || url.includes('api.mailgun.net')) { + // Mailgun API endpoint + return Promise.resolve( + createMockResponse({ + id: '', + message: 'Queued. Thank you.', + }) + ); + } + + // Database-specific endpoints + if ( + url.includes('/users') || + url.includes('/medications') || + url.includes('/settings') || + url.includes('/taken_doses') || + url.includes('/reminders') + ) { + const method = options?.method || 'GET'; + + if (method === 'GET') { + // Return database info or document + return Promise.resolve( + createMockResponse({ + db_name: 'test_db', + doc_count: 0, + doc_del_count: 0, + update_seq: + '0-g1AAAABXeJzLYWBg4MhgTmHgS04sKU9NLMnMz2NgAPKSUxMBhYwFiCJ_P2FjTwNGDU8N9QBkUgOToFJQHgv3YwOeNCsv', + }) + ); + } + + if (method === 'PUT' || method === 'POST') { + // Return success for database creation or document creation + return Promise.resolve( + createMockResponse({ + ok: true, + id: 'mock-id-' + Date.now(), + rev: '1-mock-rev', + }) + ); + } + + if (method === 'HEAD') { + // Return 404 for database existence check (database doesn't exist) + return Promise.resolve(createMockResponse({}, false, 404)); + } + } + } + + // Default successful response + return Promise.resolve(createMockResponse({ success: true })); +}); + +// Helper methods for tests +fetch.mockSuccess = data => { + fetch.mockImplementationOnce(() => Promise.resolve(createMockResponse(data))); +}; + +fetch.mockError = (status = 500, statusText = 'Internal Server Error') => { + fetch.mockImplementationOnce(() => + Promise.resolve(createMockResponse({ error: statusText }, false, status)) + ); +}; + +fetch.mockReject = error => { + fetch.mockImplementationOnce(() => Promise.reject(error)); +}; + +// Reset function for tests +fetch.mockReset = () => { + fetch.mockClear(); +}; + +module.exports = fetch; +module.exports.default = fetch; diff --git a/tests/__mocks__/uuid.js b/tests/__mocks__/uuid.js new file mode 100644 index 0000000..06c2a6c --- /dev/null +++ b/tests/__mocks__/uuid.js @@ -0,0 +1,4 @@ +// Mock for uuid module +module.exports = { + v4: jest.fn(() => '12345678-1234-1234-1234-123456789012'), +}; diff --git a/tests/setup.ts b/tests/setup.ts index 06e54e5..49bdbc1 100644 --- a/tests/setup.ts +++ b/tests/setup.ts @@ -20,10 +20,29 @@ Object.defineProperty(window, 'localStorage', { // Mock fetch global.fetch = jest.fn(); +// Mock import.meta for Jest +Object.defineProperty(globalThis, 'import', { + value: { + meta: { + env: { + NODE_ENV: 'test', + VITE_COUCHDB_URL: 'http://localhost:5984', + VITE_COUCHDB_USERNAME: 'admin', + VITE_COUCHDB_PASSWORD: 'password', + VITE_MAILGUN_API_KEY: 'test-key', + VITE_MAILGUN_DOMAIN: 'test.mailgun.org', + VITE_MAILGUN_BASE_URL: 'https://api.mailgun.net', + VITE_MAILGUN_FROM_NAME: 'Test App', + VITE_MAILGUN_FROM_EMAIL: 'test@example.com', + }, + }, + }, +}); + // Setup console to avoid noise in tests const originalError = console.error; beforeAll(() => { - console.error = (...args: any[]) => { + console.error = (...args: unknown[]) => { if ( typeof args[0] === 'string' && args[0].includes('Warning: ReactDOM.render is deprecated')