UNPKG

leetkick

Version:

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

305 lines (268 loc) 10.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: Record<string, string> = { 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: Record<string, string> = { 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: Record<string, string> = { 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: Record<string, string> = { 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}); });