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
This commit is contained in:
William Valentin
2025-09-07 15:20:59 -07:00
parent 315303b120
commit c5d3631cb6
5 changed files with 185 additions and 5 deletions

31
babel.config.cjs Normal file
View File

@@ -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
]
}
}
};

View File

@@ -17,10 +17,16 @@
], ],
"coverageDirectory": "coverage", "coverageDirectory": "coverage",
"coverageReporters": ["text", "lcov", "html"], "coverageReporters": ["text", "lcov", "html"],
"moduleNameMapping": { "moduleNameMapper": {
"^@/(.*)$": "<rootDir>/$1" "^@/(.*)$": "<rootDir>/$1",
"^uuid$": "<rootDir>/tests/__mocks__/uuid.js",
"^node-fetch$": "<rootDir>/tests/__mocks__/node-fetch.js"
}, },
"transform": { "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"]
} }

View File

@@ -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('<!DOCTYPE html><html></html>')
);
}
if (url.includes('mailgun') || url.includes('api.mailgun.net')) {
// Mailgun API endpoint
return Promise.resolve(
createMockResponse({
id: '<mock-message-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;

4
tests/__mocks__/uuid.js Normal file
View File

@@ -0,0 +1,4 @@
// Mock for uuid module
module.exports = {
v4: jest.fn(() => '12345678-1234-1234-1234-123456789012'),
};

View File

@@ -20,10 +20,29 @@ Object.defineProperty(window, 'localStorage', {
// Mock fetch // Mock fetch
global.fetch = jest.fn(); 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 // Setup console to avoid noise in tests
const originalError = console.error; const originalError = console.error;
beforeAll(() => { beforeAll(() => {
console.error = (...args: any[]) => { console.error = (...args: unknown[]) => {
if ( if (
typeof args[0] === 'string' && typeof args[0] === 'string' &&
args[0].includes('Warning: ReactDOM.render is deprecated') args[0].includes('Warning: ReactDOM.render is deprecated')