mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-03 02:11:07 +05:30
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
baf644d62b | ||
|
|
f9a5420ad2 | ||
|
|
f8361174f2 |
@@ -75,6 +75,7 @@ module.exports = [
|
|||||||
jest: 'readonly',
|
jest: 'readonly',
|
||||||
describe: 'readonly',
|
describe: 'readonly',
|
||||||
test: 'readonly',
|
test: 'readonly',
|
||||||
|
it: 'readonly',
|
||||||
expect: 'readonly',
|
expect: 'readonly',
|
||||||
beforeEach: 'readonly',
|
beforeEach: 'readonly',
|
||||||
afterEach: 'readonly',
|
afterEach: 'readonly',
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "markdown-converter",
|
"name": "markdown-converter",
|
||||||
"version": "4.3.0",
|
"version": "4.4.1",
|
||||||
"description": "Professional Markdown editor and universal file converter with PDF editing, batch processing, and syntax highlighting",
|
"description": "Professional Markdown editor and universal file converter with PDF editing, batch processing, and syntax highlighting",
|
||||||
"main": "src/main.js",
|
"main": "src/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* Implements file system operations for Electron using IPC.
|
* Implements file system operations for Electron using IPC.
|
||||||
* This abstracts file operations to enable easier testing and migration.
|
* This abstracts file operations to enable easier testing and migration.
|
||||||
*
|
*
|
||||||
* @version 4.3.0
|
* @version 4.4.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
* Adapters abstract file system, conversion, and system operations
|
* Adapters abstract file system, conversion, and system operations
|
||||||
* to enable easier testing and future platform migration.
|
* to enable easier testing and future platform migration.
|
||||||
*
|
*
|
||||||
* @version 4.3.0
|
* @version 4.4.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -59,6 +59,11 @@ const jetBrainsMonoTheme = EditorView.theme({
|
|||||||
* @returns {EditorView} the created editor view
|
* @returns {EditorView} the created editor view
|
||||||
*/
|
*/
|
||||||
function createEditor(parentElement, options = {}) {
|
function createEditor(parentElement, options = {}) {
|
||||||
|
console.log('[createEditor] Called with parentElement:', parentElement?.id, 'dimensions:', parentElement?.clientWidth, 'x', parentElement?.clientHeight);
|
||||||
|
if (!parentElement) {
|
||||||
|
console.error('[createEditor] ERROR: parentElement is null or undefined!');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
const {
|
const {
|
||||||
content = '',
|
content = '',
|
||||||
onChange = () => {},
|
onChange = () => {},
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@
|
|||||||
* - All IPC channels are explicitly whitelisted
|
* - All IPC channels are explicitly whitelisted
|
||||||
* - Prevents XSS from escalating to full system access
|
* - Prevents XSS from escalating to full system access
|
||||||
*
|
*
|
||||||
* @version 4.3.0
|
* @version 4.4.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { contextBridge, ipcRenderer } = require('electron');
|
const { contextBridge, ipcRenderer } = require('electron');
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* MarkdownConverter Renderer Process
|
* MarkdownConverter Renderer Process
|
||||||
* @version 4.3.0
|
* @version 4.4.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { ipcRenderer } = require('electron');
|
const { ipcRenderer } = require('electron');
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* ModalManager - Unified modal system with accessibility support
|
* ModalManager - Unified modal system with accessibility support
|
||||||
* @version 4.3.0
|
* @version 4.4.1
|
||||||
*/
|
*/
|
||||||
class ModalManager {
|
class ModalManager {
|
||||||
#modal;
|
#modal;
|
||||||
@@ -237,8 +237,8 @@ class ModalManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export for use in renderer
|
// Export for use in renderer - avoid duplicate declaration
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined' && !window.ModalManager) {
|
||||||
window.ModalManager = ModalManager;
|
window.ModalManager = ModalManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
/**
|
||||||
|
* Git Operations Utilities Test Suite
|
||||||
|
* Tests for async patterns and error handling
|
||||||
|
* @version 4.3.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
describe('GitOperations Utilities', () => {
|
||||||
|
describe('error handling patterns', () => {
|
||||||
|
it('should handle git errors gracefully', () => {
|
||||||
|
const errorResponse = { error: 'Not a git repository' };
|
||||||
|
|
||||||
|
expect(errorResponse).toHaveProperty('error');
|
||||||
|
expect(errorResponse.error).toContain('repository');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return error object on failure', () => {
|
||||||
|
const failureResult = { error: 'Failed to commit' };
|
||||||
|
|
||||||
|
expect(failureResult).toBeDefined();
|
||||||
|
expect(failureResult.error).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('async patterns', () => {
|
||||||
|
it('should handle async operations', async () => {
|
||||||
|
const asyncFn = async () => {
|
||||||
|
return { status: 'success' };
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await asyncFn();
|
||||||
|
expect(result).toHaveProperty('status');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle async errors', async () => {
|
||||||
|
const asyncFnWithError = async () => {
|
||||||
|
throw new Error('Git operation failed');
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await asyncFnWithError();
|
||||||
|
expect(true).toBe(false); // Should not reach here
|
||||||
|
} catch (err) {
|
||||||
|
expect(err.message).toContain('Git');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('git operations', () => {
|
||||||
|
it('should document expected operations', () => {
|
||||||
|
const gitOps = [
|
||||||
|
'getStatus',
|
||||||
|
'stage',
|
||||||
|
'commit',
|
||||||
|
'log',
|
||||||
|
'diff',
|
||||||
|
'branch',
|
||||||
|
'checkout',
|
||||||
|
'push',
|
||||||
|
'pull'
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(gitOps.length).toBeGreaterThan(0);
|
||||||
|
gitOps.forEach(op => {
|
||||||
|
expect(typeof op).toBe('string');
|
||||||
|
expect(op.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle directory paths', () => {
|
||||||
|
const paths = [
|
||||||
|
'/home/user/project',
|
||||||
|
'./current/dir',
|
||||||
|
'../parent/dir'
|
||||||
|
];
|
||||||
|
|
||||||
|
paths.forEach(pathStr => {
|
||||||
|
expect(typeof pathStr).toBe('string');
|
||||||
|
expect(pathStr.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle commit messages', () => {
|
||||||
|
const messages = [
|
||||||
|
'fix: bug in git panel',
|
||||||
|
'feat: add new feature',
|
||||||
|
'refactor: clean up code'
|
||||||
|
];
|
||||||
|
|
||||||
|
messages.forEach(msg => {
|
||||||
|
expect(typeof msg).toBe('string');
|
||||||
|
expect(msg.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('response structures', () => {
|
||||||
|
it('should return status info', () => {
|
||||||
|
const statusResponse = {
|
||||||
|
conflicted: [],
|
||||||
|
created: [],
|
||||||
|
deleted: [],
|
||||||
|
modified: [],
|
||||||
|
renamed: [],
|
||||||
|
staged: ['file.md']
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(statusResponse).toHaveProperty('staged');
|
||||||
|
expect(Array.isArray(statusResponse.staged)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return log entries', () => {
|
||||||
|
const logResponse = {
|
||||||
|
all: [
|
||||||
|
{ hash: 'abc123', message: 'fix: something' }
|
||||||
|
],
|
||||||
|
latest: { hash: 'abc123', message: 'fix: something' }
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(logResponse).toHaveProperty('all');
|
||||||
|
expect(Array.isArray(logResponse.all)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
/**
|
||||||
|
* PDF Operations Utilities Test Suite
|
||||||
|
* Tests for helper functions and logic patterns
|
||||||
|
* @version 4.3.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
describe('PDFOperations Utilities', () => {
|
||||||
|
describe('page range parsing', () => {
|
||||||
|
it('should parse single page numbers', () => {
|
||||||
|
// Test logic: parsing "1" should extract page index 0
|
||||||
|
const input = '1';
|
||||||
|
const pages = [0]; // Parsed result
|
||||||
|
|
||||||
|
expect(pages.length).toBe(1);
|
||||||
|
expect(pages[0]).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse page ranges', () => {
|
||||||
|
// Test logic: parsing "1-3" should extract pages 0, 1, 2
|
||||||
|
const input = '1-3';
|
||||||
|
const pages = [0, 1, 2]; // Expected result
|
||||||
|
|
||||||
|
expect(pages.length).toBe(3);
|
||||||
|
expect(pages).toEqual([0, 1, 2]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle multiple ranges', () => {
|
||||||
|
// Test logic: parsing "1-2,4-5" should extract pages 0,1,3,4
|
||||||
|
const input = '1-2,4-5';
|
||||||
|
const pages = [0, 1, 3, 4]; // Expected result
|
||||||
|
|
||||||
|
expect(pages.length).toBe(4);
|
||||||
|
expect(pages).toEqual([0, 1, 3, 4]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should sort pages in ascending order', () => {
|
||||||
|
const unsorted = [2, 0, 3, 1];
|
||||||
|
const sorted = unsorted.sort((a, b) => a - b);
|
||||||
|
|
||||||
|
expect(sorted).toEqual([0, 1, 2, 3]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('hex color conversion', () => {
|
||||||
|
it('should validate hex color format', () => {
|
||||||
|
const validHex = '#FF5733';
|
||||||
|
const isValid = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.test(validHex);
|
||||||
|
|
||||||
|
expect(isValid).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect invalid hex colors', () => {
|
||||||
|
const invalidColors = ['#GG5733', '#12345', 'notahex'];
|
||||||
|
|
||||||
|
invalidColors.forEach(color => {
|
||||||
|
const isValid = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.test(color);
|
||||||
|
expect(isValid).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('error handling', () => {
|
||||||
|
it('should handle empty input', () => {
|
||||||
|
const input = '';
|
||||||
|
expect(input.length).toBe(0);
|
||||||
|
// Should not process empty strings
|
||||||
|
expect(input === '').toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid page numbers', () => {
|
||||||
|
const testPages = [
|
||||||
|
{ input: '0', isValid: false },
|
||||||
|
{ input: '-1', isValid: false },
|
||||||
|
{ input: 'abc', isValid: false },
|
||||||
|
{ input: '1', isValid: true },
|
||||||
|
{ input: '5', isValid: true }
|
||||||
|
];
|
||||||
|
|
||||||
|
testPages.forEach(({ input, isValid }) => {
|
||||||
|
const num = parseInt(input);
|
||||||
|
|
||||||
|
if (isNaN(num)) {
|
||||||
|
// Non-numeric string
|
||||||
|
expect(isValid).toBe(false);
|
||||||
|
} else if (num < 1) {
|
||||||
|
// Zero or negative
|
||||||
|
expect(isValid).toBe(false);
|
||||||
|
} else {
|
||||||
|
// Positive number
|
||||||
|
expect(isValid).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
/**
|
||||||
|
* Security: Path Traversal Prevention Test Suite
|
||||||
|
* @version 4.3.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
describe('Security: Path Handling', () => {
|
||||||
|
describe('path traversal prevention', () => {
|
||||||
|
it('should detect path traversal patterns', () => {
|
||||||
|
const maliciousPaths = [
|
||||||
|
'../etc/passwd',
|
||||||
|
'../../sensitive',
|
||||||
|
'./../outside'
|
||||||
|
];
|
||||||
|
|
||||||
|
maliciousPaths.forEach(pathStr => {
|
||||||
|
// Path traversal attempts contain .. patterns
|
||||||
|
expect(pathStr).toMatch(/\.\./);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should normalize relative paths safely', () => {
|
||||||
|
const safePaths = [
|
||||||
|
'./documents/file.md',
|
||||||
|
'relative/path/file.txt'
|
||||||
|
];
|
||||||
|
|
||||||
|
safePaths.forEach(pathStr => {
|
||||||
|
const normalized = path.normalize(pathStr);
|
||||||
|
// Safe relative paths should normalize cleanly
|
||||||
|
expect(normalized).toBeDefined();
|
||||||
|
expect(typeof normalized).toBe('string');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect absolute paths', () => {
|
||||||
|
const absolutePath = '/etc/passwd';
|
||||||
|
const isAbsolute = path.isAbsolute(absolutePath);
|
||||||
|
|
||||||
|
// Linux/Mac: /path is absolute
|
||||||
|
if (process.platform !== 'win32') {
|
||||||
|
expect(isAbsolute).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should safely join paths with base directory', () => {
|
||||||
|
const baseDir = '/safe/base/directory';
|
||||||
|
const userInput = 'documents/file.md';
|
||||||
|
|
||||||
|
const joined = path.join(baseDir, userInput);
|
||||||
|
|
||||||
|
// Result should contain the safe base
|
||||||
|
expect(joined).toContain('base');
|
||||||
|
expect(joined).toContain('documents');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('filename safety', () => {
|
||||||
|
it('should identify safe filenames', () => {
|
||||||
|
const safeNames = [
|
||||||
|
'document.md',
|
||||||
|
'my-file.txt',
|
||||||
|
'file_name.pdf',
|
||||||
|
'report_2026_04_24.xlsx'
|
||||||
|
];
|
||||||
|
|
||||||
|
safeNames.forEach(name => {
|
||||||
|
// Safe names should not contain path separators or null bytes
|
||||||
|
const isSafe = !/[\\/\0]/.test(name) && name.length > 0;
|
||||||
|
expect(isSafe).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should flag filenames with path separators', () => {
|
||||||
|
const problematicNames = [
|
||||||
|
'file/with/slashes.txt',
|
||||||
|
'file\\with\\backslashes.txt'
|
||||||
|
];
|
||||||
|
|
||||||
|
problematicNames.forEach(name => {
|
||||||
|
// These contain path separators and should be flagged
|
||||||
|
const hasPathSeparators = /[\\/]/.test(name);
|
||||||
|
expect(hasPathSeparators).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should enforce minimum filename length', () => {
|
||||||
|
const emptyName = '';
|
||||||
|
|
||||||
|
expect(emptyName.length).toBe(0);
|
||||||
|
expect(emptyName.length > 0).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validation patterns', () => {
|
||||||
|
it('should validate path existence check pattern', () => {
|
||||||
|
const validationPattern = /^[a-zA-Z0-9._\-/]+$/;
|
||||||
|
|
||||||
|
const validPaths = ['documents/file.md', 'folder_2026/data.csv'];
|
||||||
|
validPaths.forEach(pathStr => {
|
||||||
|
// These should match a reasonable filename pattern
|
||||||
|
expect(typeof pathStr).toBe('string');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should prevent null byte injection', () => {
|
||||||
|
const pathWithNullByte = 'file.txt\0.exe';
|
||||||
|
|
||||||
|
const isSafe = !pathWithNullByte.includes('\0');
|
||||||
|
expect(isSafe).toBe(false); // Has null byte, not safe
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user