Testing & QA

Edge Case Generator

Analyze function signatures and generate boundary value test cases: empty strings, zero, negative numbers, unicode, huge inputs, and more.

Get the CLI Tool

Generate edge case tests locally as an MCP server, or try it online below.

npx @clinetools/edge-cases
Requires Node.js 18+
  • Analyzes parameter types to generate type-specific boundary values
  • Strings: empty, whitespace, unicode, emoji, very long, SQL injection
  • Numbers: 0, -1, MAX_SAFE_INTEGER, NaN, Infinity, floats
  • Arrays: empty, single item, duplicates, huge length, nested
  • Preference conversation on first run — choose thoroughness level

How to Use It

Three ways to generate edge cases — pick the one that fits your workflow.

1

Try Online

Paste a function below to get edge case inputs — no install needed.

2

Use via CLI

Run as a local MCP server. On first run it asks about your thoroughness level.

npx @clinetools/edge-cases
3

Add to Cline / Claude Code

Add to your MCP settings so your agent generates edge cases automatically.

"edge-cases": { "command": "npx", "args": ["@clinetools/edge-cases"] }

MCP Client Configuration

{
  "mcpServers": {
    "edge-cases": {
      "command": "npx",
      "args": ["@clinetools/edge-cases"]
    }
  }
}
Live Demo

Try It Online

Paste a function and get boundary value test inputs automatically.

Paste Function Signature

We analyze parameter names and types to generate relevant edge cases

Try a demo:

Paste a function and click Generate Edge Cases to get boundary values.

Edge Cases

Why Edge Cases Matter

Most bugs live at the boundaries. Systematic edge case testing finds them before your users do.

The Off-By-One Bug

The most common bug in software. Does your loop handle the last element? Does your slice include the boundary? Edge case testing systematically checks every fence post.

Unicode Chaos

Users type emoji, accented characters, and right-to-left text. Your string functions need to handle \u0000, \uFFFF, surrogate pairs, zero-width joiners, and combining characters.

Number Boundaries

NaN, Infinity, -0, MAX_SAFE_INTEGER+1, floating point precision loss (0.1+0.2). JavaScript numbers are full of traps. Edge case generation covers them all systematically.

Empty Everything

Empty strings, empty arrays, null, undefined, empty objects. What happens when there's nothing? Most crashes come from code that never considered the empty case.

Find Bugs at the Boundaries

Add the Edge Case Generator to your agent's toolkit and test every corner systematically.

View Plans
"',why:'XSS via string output'}, {name:'SQL injection',value:'"\'; DROP TABLE users; --"',why:'SQL injection via string params'}, {name:'Path traversal',value:'"../../etc/passwd"',why:'Directory traversal attack'}, {name:'null',value:'null',why:'Null reference'}, {name:'undefined',value:'undefined',why:'Missing parameter'}, ]; var NUMBER_EDGES=[ {name:'Zero',value:'0',why:'Division by zero, falsy value'}, {name:'Negative zero',value:'-0',why:'0 === -0 is true but 1/-0 is -Infinity'}, {name:'One',value:'1',why:'Minimum positive integer, off-by-one'}, {name:'Negative one',value:'-1',why:'Common sentinel value, sign flip'}, {name:'MAX_SAFE_INTEGER',value:'Number.MAX_SAFE_INTEGER (9007199254740991)',why:'Beyond this, integer math breaks'}, {name:'MIN_SAFE_INTEGER',value:'Number.MIN_SAFE_INTEGER (-9007199254740991)',why:'Negative boundary'}, {name:'MAX_VALUE',value:'Number.MAX_VALUE (1.7976931348623157e+308)',why:'Largest representable number'}, {name:'MIN_VALUE',value:'Number.MIN_VALUE (5e-324)',why:'Smallest positive number (not zero)'}, {name:'NaN',value:'NaN',why:'Not a Number propagates through math'}, {name:'Infinity',value:'Infinity',why:'Result of overflow or 1/0'}, {name:'-Infinity',value:'-Infinity',why:'Negative infinity'}, {name:'Float precision',value:'0.1 + 0.2 (= 0.30000000000000004)',why:'IEEE 754 floating point imprecision'}, {name:'Very small float',value:'0.000000001',why:'Near-zero but not zero'}, {name:'null',value:'null',why:'null coerces to 0 in math'}, {name:'undefined',value:'undefined',why:'undefined coerces to NaN'}, ]; var ARRAY_EDGES=[ {name:'Empty array',value:'[]',why:'No elements to process'}, {name:'Single element',value:'[1]',why:'Minimum non-empty case'}, {name:'Two elements',value:'[1, 2]',why:'Minimum for comparison/sorting'}, {name:'Duplicate values',value:'[1, 1, 1, 1, 1]',why:'All same value'}, {name:'Already sorted',value:'[1, 2, 3, 4, 5]',why:'Best case for some algorithms'}, {name:'Reverse sorted',value:'[5, 4, 3, 2, 1]',why:'Worst case for some algorithms'}, {name:'Mixed types',value:'[1, "2", true, null, undefined]',why:'Type coercion chaos'}, {name:'Nested arrays',value:'[[1, 2], [3, [4, 5]]]',why:'Recursive structure'}, {name:'Sparse array',value:'[1, , , 4]',why:'Holes in arrays (not undefined)'}, {name:'Very large array',value:'Array(100000).fill(0)',why:'Performance with large datasets'}, {name:'Array with NaN',value:'[1, NaN, 3]',why:'NaN !== NaN breaks indexOf/includes'}, {name:'null',value:'null',why:'Not iterable'}, {name:'undefined',value:'undefined',why:'Not iterable'}, {name:'Array-like object',value:'{length: 3, 0: "a", 1: "b", 2: "c"}',why:'Looks like array but is not'}, ]; var GENERAL_EDGES=[ {name:'null',value:'null',why:'The billion dollar mistake'}, {name:'undefined',value:'undefined',why:'Missing property or parameter'}, {name:'Empty object',value:'{}',why:'No properties to access'}, {name:'Prototype pollution',value:'{"__proto__": {"isAdmin": true}}',why:'Prototype pollution attack'}, {name:'Circular reference',value:'const a = {}; a.self = a;',why:'Infinite loop in JSON.stringify'}, {name:'Frozen object',value:'Object.freeze({x: 1})',why:'Mutations silently fail'}, {name:'Date edge cases',value:'new Date("Invalid"), new Date(0), new Date(8640000000000000)',why:'Invalid date, epoch, max date'}, {name:'RegExp special chars',value:'".*+?^${}()|[]\\\\/"',why:'Unescaped regex metacharacters'}, {name:'Promise rejection',value:'Promise.reject(new Error("fail"))',why:'Unhandled rejection'}, {name:'Symbol',value:'Symbol("test")',why:'Cannot convert to string/number implicitly'}, ]; var demos={ string:'/**\n * Truncates a string to maxLength and adds ellipsis\n * @param {string} text - The input text\n * @param {number} maxLength - Maximum length before truncation\n * @returns {string} Truncated string with "..." if needed\n */\nfunction truncate(text: string, maxLength: number): string {\n if (text.length <= maxLength) return text;\n return text.slice(0, maxLength - 3) + "...";\n}\n\n/**\n * Converts a string to URL-friendly slug\n */\nfunction slugify(title: string): string {\n return title\n .toLowerCase()\n .trim()\n .replace(/[^\\w\\s-]/g, "")\n .replace(/[\\s_-]+/g, "-")\n .replace(/^-+|-+$/g, "");\n}', number:'/**\n * Calculates the average of an array of numbers\n */\nfunction average(numbers: number[]): number {\n if (numbers.length === 0) return 0;\n return numbers.reduce((a, b) => a + b, 0) / numbers.length;\n}\n\n/**\n * Clamps a value between min and max bounds\n */\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n\n/**\n * Formats bytes to human-readable string\n */\nfunction formatBytes(bytes: number, decimals: number = 2): string {\n if (bytes === 0) return "0 B";\n const k = 1024;\n const sizes = ["B", "KB", "MB", "GB", "TB"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return parseFloat((bytes / Math.pow(k, i)).toFixed(decimals)) + " " + sizes[i];\n}', array:'/**\n * Groups array items by a key function\n */\nfunction groupBy(items: T[], keyFn: (item: T) => string): Record {\n return items.reduce((acc, item) => {\n const key = keyFn(item);\n (acc[key] = acc[key] || []).push(item);\n return acc;\n }, {} as Record);\n}\n\n/**\n * Returns the intersection of two arrays\n */\nfunction intersect(a: T[], b: T[]): T[] {\n const setB = new Set(b);\n return a.filter(x => setB.has(x));\n}\n\n/**\n * Chunks an array into groups of given size\n */\nfunction chunk(arr: T[], size: number): T[][] {\n const result: T[][] = [];\n for (let i = 0; i < arr.length; i += size) {\n result.push(arr.slice(i, i + size));\n }\n return result;\n}' }; function loadDemo(type){ document.getElementById('code-input').value=demos[type]; setTimeout(function(){document.getElementById('scan-btn').click()},100); } function detectParamTypes(code){ var types={string:0,number:0,array:0,general:0}; var cl=code.toLowerCase(); // Detect from type annotations if(cl.indexOf(':string')!==-1||cl.indexOf('string')!==-1) types.string++; if(cl.indexOf(':number')!==-1||cl.indexOf('number')!==-1) types.number++; if(cl.indexOf('[]')!==-1||cl.indexOf('array')!==-1) types.array++; // Detect from parameter names if(/\b(text|str|name|title|label|slug|url|path|email|message)\b/i.test(cl)) types.string++; if(/\b(count|total|sum|value|min|max|num|amount|price|bytes|size|decimals|length)\b/i.test(cl)) types.number++; if(/\b(items|list|arr|elements|numbers|values|results)\b/i.test(cl)) types.array++; // Detect from function body if(cl.indexOf('.length')!==-1||cl.indexOf('.slice')!==-1||cl.indexOf('.replace')!==-1) types.string++; if(cl.indexOf('math.')!==-1||cl.indexOf('reduce')!==-1||cl.indexOf('parseFloat')!==-1) types.number++; if(cl.indexOf('.filter')!==-1||cl.indexOf('.map')!==-1||cl.indexOf('.push')!==-1) types.array++; return types; } function generateEdgeCases(code){ var types=detectParamTypes(code); var cases=[]; var counts={string:0,number:0,array:0,general:0}; if(types.string>0){ STRING_EDGES.forEach(function(e){cases.push({type:'string',name:e.name,value:e.value,why:e.why});counts.string++}); } if(types.number>0){ NUMBER_EDGES.forEach(function(e){cases.push({type:'number',name:e.name,value:e.value,why:e.why});counts.number++}); } if(types.array>0){ ARRAY_EDGES.forEach(function(e){cases.push({type:'array',name:e.name,value:e.value,why:e.why});counts.array++}); } // Always include general GENERAL_EDGES.forEach(function(e){cases.push({type:'general',name:e.name,value:e.value,why:e.why});counts.general++}); return {cases:cases,counts:counts,total:cases.length}; } function esc(s){var d=document.createElement('div');d.textContent=s;return d.innerHTML;} var scanBtn=document.getElementById('scan-btn'); scanBtn.addEventListener('click',function(){ var code=document.getElementById('code-input').value.trim(); if(!code)return; scanBtn.classList.add('loading');scanBtn.disabled=true; setTimeout(function(){ var result=generateEdgeCases(code); displayResults(result); scanBtn.classList.remove('loading');scanBtn.disabled=false; },400); }); function displayResults(r){ document.getElementById('results-placeholder').style.display='none'; document.getElementById('results-content').classList.add('active'); document.getElementById('sum-row').innerHTML= '
'+r.total+'
Total
'+ '
'+r.counts.string+'
String
'+ '
'+r.counts.number+'
Number
'+ '
'+(r.counts.array+r.counts.general)+'
Array/Other
'; document.getElementById('case-heading').textContent='Edge Cases ('+r.total+')'; var list=document.getElementById('edge-case-list');list.innerHTML=''; for(var i=0;i
'+c.type+''+esc(c.name)+'
'+esc(c.value)+'
'+esc(c.why)+'
'; } }