UNPKG

leetkick

Version:

A CLI tool for scaffolding LeetCode exercises with language-specific testing setups

249 lines 11.8 kB
import test from 'node:test'; import assert from 'node:assert'; import { promises as fs } from 'fs'; import { join } from 'path'; import { tmpdir } from 'os'; // Mock the file operations module const mockTemplateDir = join(tmpdir(), 'leetcode-cli-test-file-ops-templates'); const mockWorkingDir = join(tmpdir(), 'leetcode-cli-test-file-ops-workspace'); // Setup test fixtures void test('file operations test suite', async (t) => { // Setup test environment await fs.mkdir(mockTemplateDir, { recursive: true }); await fs.mkdir(mockWorkingDir, { recursive: true }); await t.test('should extract function name from TypeScript code', () => { const code = 'function twoSum(nums: number[], target: number): number[] {\n \n};'; // We would need to import the extractFunctionName function // For now, test the regex pattern const functionMatch = code.match(/function\s+(\w+)\s*\(/); assert.strictEqual(functionMatch?.[1], 'twoSum'); }); await t.test('should extract function name from C++ code', () => { const code = 'vector<int> twoSum(vector<int>& nums, int target) {\n return {};\n}'; // Test the C++ method regex pattern - need to handle templates like vector<int> const cppMethodMatch = code.match(/[\w<>]+\s+(\w+)\s*\([^)]*\)\s*\{/); assert.strictEqual(cppMethodMatch?.[1], 'twoSum'); }); await t.test('should extract function name from Kotlin code', () => { const code = 'fun twoSum(nums: IntArray, target: Int): IntArray {\n return intArrayOf()\n}'; // Test the Kotlin function regex pattern const kotlinFunMatch = code.match(/fun\s+(\w+)\s*\(/); assert.strictEqual(kotlinFunMatch?.[1], 'twoSum'); }); await t.test('should extract function name from Java code', () => { const code = 'public int[] twoSum(int[] nums, int target) {\n return new int[0];\n}'; // Test the Java method regex pattern const javaMethodMatch = code.match(/public\s+[\w<>[\]]+\s+(\w+)\s*\([^)]*\)\s*\{/); assert.strictEqual(javaMethodMatch?.[1], 'twoSum'); }); await t.test('should extract function name from Rust code', () => { const code = 'pub fn two_sum(nums: Vec<i32>, target: i32) -> Vec<i32> {\n vec![]\n}'; // Test the Rust function regex pattern const rustFnMatch = code.match(/pub\s+fn\s+(\w+)\s*\(/); assert.strictEqual(rustFnMatch?.[1], 'two_sum'); }); await t.test('should extract function name from Go code', () => { const code = 'func twoSum(nums []int, target int) []int {\n return []int{}\n}'; // Test the Go function regex pattern const goFuncMatch = code.match(/func\s+(\w+)\s*\(/); assert.strictEqual(goFuncMatch?.[1], 'twoSum'); }); await t.test('should extract function name from Python code', () => { const code = 'def twoSum(self, nums: List[int], target: int) -> List[int]:\n pass'; // Test the Python function regex pattern const pythonFuncMatch = code.match(/def\s+(\w+)\s*\(/); assert.strictEqual(pythonFuncMatch?.[1], 'twoSum'); }); await t.test('should extract function name from Python class method', () => { const code = 'class Solution:\n def twoSum(self, nums: List[int], target: int) -> List[int]:\n pass'; // Test the Python method extraction const pythonFuncMatch = code.match(/def\s+(\w+)\s*\(/); assert.strictEqual(pythonFuncMatch?.[1], 'twoSum'); }); await t.test('should format problem name to camelCase', () => { const title = 'Two Sum'; const formatted = title .replace(/[^a-zA-Z0-9\s]/g, '') .split(' ') .map((word, index) => index === 0 ? word.toLowerCase() : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) .join(''); assert.strictEqual(formatted, 'twoSum'); }); await t.test('should clean HTML description', () => { const htmlContent = '<p>Given an array of integers <code>nums</code> and an integer <code>target</code>, return indices.</p>'; const cleaned = htmlContent .replace(/<[^>]*>/g, '') .replace(/&nbsp;/g, ' ') .replace(/&lt;/g, '<') .replace(/&gt;/g, '>') .replace(/&amp;/g, '&') .trim(); assert.strictEqual(cleaned, 'Given an array of integers nums and an integer target, return indices.'); }); await t.test('should decode HTML entities in descriptions', () => { // Test the specific entities mentioned in the bug report const htmlWithEntities = 'Given an m x n 2D binary grid which represents a map of &#39;1&#39;s (land) and &#39;0&#39;s (water). ' + 'Input: grid = [&quot;1&quot;,&quot;1&quot;,&quot;0&quot;] and grid[i][j] is &#39;0&#39; or &#39;1&#39;.'; const cleaned = htmlWithEntities .replace(/<[^>]*>/g, '') .replace(/&nbsp;/g, ' ') .replace(/&lt;/g, '<') .replace(/&gt;/g, '>') .replace(/&quot;/g, '"') .replace(/&#39;/g, "'") .replace(/&apos;/g, "'") .replace(/&ldquo;/g, '"') .replace(/&rdquo;/g, '"') .replace(/&lsquo;/g, "'") .replace(/&rsquo;/g, "'") .replace(/&amp;/g, '&') .trim(); assert.strictEqual(cleaned, "Given an m x n 2D binary grid which represents a map of '1's (land) and '0's (water). " + 'Input: grid = ["1","1","0"] and grid[i][j] is \'0\' or \'1\'.'); }); await t.test('should handle various quote entities', () => { const testCases = [ { input: '&quot;hello&quot;', expected: '"hello"' }, { input: '&#39;world&#39;', expected: "'world'" }, { input: '&apos;test&apos;', expected: "'test'" }, { input: '&ldquo;fancy&rdquo;', expected: '"fancy"' }, { input: '&lsquo;quote&rsquo;', expected: "'quote'" }, ]; testCases.forEach(({ input, expected }) => { const cleaned = input .replace(/&quot;/g, '"') .replace(/&#39;/g, "'") .replace(/&apos;/g, "'") .replace(/&ldquo;/g, '"') .replace(/&rdquo;/g, '"') .replace(/&lsquo;/g, "'") .replace(/&rsquo;/g, "'") .replace(/&amp;/g, '&'); assert.strictEqual(cleaned, expected); }); }); await t.test('should create clean problem directory name', () => { const problemId = '1'; const paddedId = problemId.padStart(4, '0'); const problemDirName = `problem_${paddedId}`; assert.strictEqual(problemDirName, 'problem_0001'); }); await t.test('should format class names correctly', () => { const testCases = [ { input: 'Two Sum', expected: 'TwoSum' }, { input: 'Roman to Integer', expected: 'RomanToInteger' }, { input: 'Longest Substring Without Repeating Characters', expected: 'LongestSubstringWithoutRepeatingCharacters', }, ]; testCases.forEach(({ input, expected }) => { const formatted = input .replace(/[^a-zA-Z0-9\s]/g, '') .split(' ') .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) .join(''); assert.strictEqual(formatted, expected); }); }); await t.test('should format snake case names correctly', () => { const testCases = [ { input: 'Two Sum', expected: 'two_sum' }, { input: 'Roman to Integer', expected: 'roman_to_integer' }, { input: 'Longest Substring Without Repeating Characters', expected: 'longest_substring_without_repeating_characters', }, ]; testCases.forEach(({ input, expected }) => { const formatted = input .replace(/[^a-zA-Z0-9\s]/g, '') .toLowerCase() .split(/\s+/) .join('_'); assert.strictEqual(formatted, expected); }); }); await t.test('should get correct file extension for language', () => { const extMap = { typescript: 'ts', javascript: 'js', python: 'py', java: 'java', cpp: 'cpp', go: 'go', rust: 'rs', kotlin: 'kt', }; assert.strictEqual(extMap.typescript, 'ts'); assert.strictEqual(extMap.javascript, 'js'); assert.strictEqual(extMap.python, 'py'); assert.strictEqual(extMap.java, 'java'); assert.strictEqual(extMap.kotlin, 'kt'); }); await t.test('should get correct language slug for LeetCode API', () => { const slugMap = { typescript: 'typescript', javascript: 'javascript', python: 'python3', java: 'java', cpp: 'cpp', go: 'golang', rust: 'rust', kotlin: 'kotlin', }; assert.strictEqual(slugMap.typescript, 'typescript'); assert.strictEqual(slugMap.javascript, 'javascript'); assert.strictEqual(slugMap.python, 'python3'); assert.strictEqual(slugMap.go, 'golang'); assert.strictEqual(slugMap.kotlin, 'kotlin'); }); await t.test('should generate correct test file names', () => { const className = 'TwoSum'; const snakeCaseName = 'two_sum'; const testNameMap = { typescript: `${className}.test.ts`, javascript: `${className}.test.js`, python: `test_${snakeCaseName}.py`, java: `${className}Test.java`, cpp: `${snakeCaseName}.test.cpp`, kotlin: `${className}Test.kt`, go: `${snakeCaseName}_test.go`, rust: 'lib.rs', // Rust tests are in the same file }; assert.strictEqual(testNameMap.typescript, 'TwoSum.test.ts'); assert.strictEqual(testNameMap.javascript, 'TwoSum.test.js'); assert.strictEqual(testNameMap.python, 'test_two_sum.py'); assert.strictEqual(testNameMap.java, 'TwoSumTest.java'); assert.strictEqual(testNameMap.cpp, 'two_sum.test.cpp'); assert.strictEqual(testNameMap.kotlin, 'TwoSumTest.kt'); assert.strictEqual(testNameMap.go, 'two_sum_test.go'); assert.strictEqual(testNameMap.rust, 'lib.rs'); }); await t.test('should generate correct exercise file names', () => { const className = 'TwoSum'; const snakeCaseName = 'two_sum'; const exerciseNameMap = { typescript: `${className}.ts`, javascript: `${className}.js`, python: `${snakeCaseName}.py`, cpp: `${snakeCaseName}.cpp`, kotlin: `${className}.kt`, java: `${className}.java`, go: `${snakeCaseName}.go`, rust: 'lib.rs', // Rust always uses lib.rs }; assert.strictEqual(exerciseNameMap.typescript, 'TwoSum.ts'); assert.strictEqual(exerciseNameMap.javascript, 'TwoSum.js'); assert.strictEqual(exerciseNameMap.python, 'two_sum.py'); assert.strictEqual(exerciseNameMap.cpp, 'two_sum.cpp'); assert.strictEqual(exerciseNameMap.go, 'two_sum.go'); assert.strictEqual(exerciseNameMap.rust, 'lib.rs'); }); // Cleanup await fs.rm(mockTemplateDir, { recursive: true, force: true }); await fs.rm(mockWorkingDir, { recursive: true, force: true }); }); //# sourceMappingURL=file-operations.test.js.map