diff --git a/__tests__/tests/test-improvement.test.js b/__tests__/tests/test-improvement.test.js new file mode 100644 index 0000000..ffc91a1 --- /dev/null +++ b/__tests__/tests/test-improvement.test.js @@ -0,0 +1,43 @@ +const rule = require('../../src/rules/tests/test-improvement'); + +describe('Test Improvement Rule', () => { + it('labels when both source and tests change', () => { + const files = [{ filename: 'src/app.ts' }, { filename: '__tests__/app.test.ts' }]; + const labels = rule({ files, pr: {}, enableDebug: false }); + expect(labels).toContain('test-improvement'); + }); + + it('labels for various source and test combinations', () => { + const files = [{ filename: 'lib/util.js' }, { filename: 'test/util.spec.js' }]; + const labels = rule({ files, pr: {}, enableDebug: false }); + expect(labels).toContain('test-improvement'); + }); + + it('does not label when only tests change', () => { + const files = [{ filename: '__tests__/app.test.ts' }]; + const labels = rule({ files, pr: {}, enableDebug: false }); + expect(labels).toEqual([]); + }); + + it('does not label when only source changes', () => { + const files = [{ filename: 'src/app.ts' }]; + const labels = rule({ files, pr: {}, enableDebug: false }); + expect(labels).toEqual([]); + }); + + it('does not label when only docs change', () => { + const files = [{ filename: 'README.md' }]; + const labels = rule({ files, pr: {}, enableDebug: false }); + expect(labels).toEqual([]); + }); + + it('handles empty files array', () => { + const labels = rule({ files: [], pr: {}, enableDebug: false }); + expect(labels).toEqual([]); + }); + + it('handles debug mode', () => { + const files = [{ filename: 'src/app.js' }, { filename: '__tests__/app.test.js' }]; + expect(() => rule({ files, pr: {}, enableDebug: true })).not.toThrow(); + }); +}); diff --git a/__tests__/tests/test-missing.test.js b/__tests__/tests/test-missing.test.js new file mode 100644 index 0000000..6f69750 --- /dev/null +++ b/__tests__/tests/test-missing.test.js @@ -0,0 +1,43 @@ +const rule = require('../../src/rules/tests/test-missing'); + +describe('Test Missing Rule', () => { + it('labels when source changes without tests', () => { + const files = [{ filename: 'src/app.ts' }, { filename: 'src/util.ts' }]; + const labels = rule({ files, pr: {}, enableDebug: false }); + expect(labels).toContain('test-missing'); + }); + + it('labels for various source file types', () => { + const files = [{ filename: 'lib/helper.js' }]; + const labels = rule({ files, pr: {}, enableDebug: false }); + expect(labels).toContain('test-missing'); + }); + + it('does not label when tests are present', () => { + const files = [{ filename: 'src/app.ts' }, { filename: '__tests__/app.test.ts' }]; + const labels = rule({ files, pr: {}, enableDebug: false }); + expect(labels).toEqual([]); + }); + + it('does not label when only tests change', () => { + const files = [{ filename: '__tests__/app.test.ts' }]; + const labels = rule({ files, pr: {}, enableDebug: false }); + expect(labels).toEqual([]); + }); + + it('does not label when only docs change', () => { + const files = [{ filename: 'README.md' }]; + const labels = rule({ files, pr: {}, enableDebug: false }); + expect(labels).toEqual([]); + }); + + it('handles empty files array', () => { + const labels = rule({ files: [], pr: {}, enableDebug: false }); + expect(labels).toEqual([]); + }); + + it('handles debug mode', () => { + const files = [{ filename: 'src/app.js' }]; + expect(() => rule({ files, pr: {}, enableDebug: true })).not.toThrow(); + }); +}); diff --git a/__tests__/tests/test-only-change.test.js b/__tests__/tests/test-only-change.test.js new file mode 100644 index 0000000..3c0ec9c --- /dev/null +++ b/__tests__/tests/test-only-change.test.js @@ -0,0 +1,37 @@ +const rule = require('../../src/rules/tests/test-only-change'); + +describe('Test Only Change Rule', () => { + it('labels when only tests are changed', () => { + const files = [{ filename: '__tests__/a.test.js' }, { filename: 'tests/utils.spec.ts' }]; + const labels = rule({ files, pr: {}, enableDebug: false }); + expect(labels).toContain('test-only-change'); + }); + + it('labels when test/ directory files change', () => { + const files = [{ filename: 'test/unit.js' }]; + const labels = rule({ files, pr: {}, enableDebug: false }); + expect(labels).toContain('test-only-change'); + }); + + it('does not label when source files also change', () => { + const files = [{ filename: '__tests__/a.test.js' }, { filename: 'src/app.ts' }]; + const labels = rule({ files, pr: {}, enableDebug: false }); + expect(labels).toEqual([]); + }); + + it('does not label when only source files change', () => { + const files = [{ filename: 'src/app.js' }]; + const labels = rule({ files, pr: {}, enableDebug: false }); + expect(labels).toEqual([]); + }); + + it('handles empty files array', () => { + const labels = rule({ files: [], pr: {}, enableDebug: false }); + expect(labels).toEqual([]); + }); + + it('handles debug mode', () => { + const files = [{ filename: '__tests__/test.js' }]; + expect(() => rule({ files, pr: {}, enableDebug: true })).not.toThrow(); + }); +}); diff --git a/src/rules/tests/test-improvement.js b/src/rules/tests/test-improvement.js new file mode 100644 index 0000000..5935a08 --- /dev/null +++ b/src/rules/tests/test-improvement.js @@ -0,0 +1,39 @@ +/** + * Test Improvement Detection Rule + * + * Adds `test-improvement` when both source and test files changed. + */ + +module.exports = function testImprovementRule({ files, pr, enableDebug }) { + const labels = []; + + const isTestFile = (name) => /(^|\/).__tests__\//i.test(name) || /\.(test|spec)\.[a-z0-9]+$/i.test(name) || /(^|\/)tests?\//i.test(name); + const isSourceFile = (name) => /\.(js|jsx|ts|tsx|java|go|py|rb|rs|php|cpp|c|cs|scala|kt|swift|vue|svelte)$/i.test(name) && !isTestFile(name); + + const names = (files || []).map(f => (f && f.filename ? String(f.filename).toLowerCase() : '')); + const hasSource = names.some(n => n && isSourceFile(n)); + const hasTests = names.some(n => n && isTestFile(n)); + + if (hasSource && hasTests) { + labels.push('test-improvement'); + } + + if (enableDebug) { + console.log(`[Test Improvement Rule] hasSource=${hasSource} hasTests=${hasTests} → ${labels.join(', ') || 'none'}`); + } + + return labels; +}; + +module.exports.metadata = { + name: 'Test Improvement Detection', + description: 'Detects PRs that modify tests alongside code changes', + labels: [ + { name: 'test-improvement', color: '0E8A16', description: 'Tests improved/added with code changes' } + ], + author: 'pr-auto-labeler', + version: '1.0.0', + category: 'tests' +}; + + diff --git a/src/rules/tests/test-missing.js b/src/rules/tests/test-missing.js new file mode 100644 index 0000000..a816ac5 --- /dev/null +++ b/src/rules/tests/test-missing.js @@ -0,0 +1,39 @@ +/** + * Source Changed Without Tests Detection Rule + * + * Adds `test-missing` when source files changed but no test files changed. + */ + +module.exports = function testMissingRule({ files, pr, enableDebug }) { + const labels = []; + + const isTestFile = (name) => /(^|\/)__tests__\//i.test(name) || /\.(test|spec)\.[a-z0-9]+$/i.test(name) || /(^|\/)tests?\//i.test(name); + const isSourceFile = (name) => /\.(js|jsx|ts|tsx|java|go|py|rb|rs|php|cpp|c|cs|scala|kt|swift|vue|svelte)$/i.test(name); + + const names = (files || []).map(f => (f && f.filename ? String(f.filename).toLowerCase() : '')); + const hasSource = names.some(n => n && isSourceFile(n)); + const hasTests = names.some(n => n && isTestFile(n)); + + if (hasSource && !hasTests) { + labels.push('test-missing'); + } + + if (enableDebug) { + console.log(`[Test Missing Rule] hasSource=${hasSource} hasTests=${hasTests} → ${labels.join(', ') || 'none'}`); + } + + return labels; +}; + +module.exports.metadata = { + name: 'Source Changed Without Tests', + description: 'Detects code changes without accompanying tests', + labels: [ + { name: 'test-missing', color: 'D93F0B', description: 'Code changes without tests' } + ], + author: 'pr-auto-labeler', + version: '1.0.0', + category: 'tests' +}; + + diff --git a/src/rules/tests/test-only-change.js b/src/rules/tests/test-only-change.js new file mode 100644 index 0000000..b563f9b --- /dev/null +++ b/src/rules/tests/test-only-change.js @@ -0,0 +1,38 @@ +/** + * Test-Only Change Detection Rule + * + * Adds `test-only-change` when only test files are changed. + */ + +module.exports = function testOnlyChangeRule({ files, pr, enableDebug }) { + const labels = []; + + const isTestFile = (name) => /(^|\/)__tests__\//i.test(name) || /\.(test|spec)\.[a-z0-9]+$/i.test(name) || /(^|\/)tests?\//i.test(name); + + const names = (files || []).map(f => (f && f.filename ? String(f.filename).toLowerCase() : '')); + const hasAny = names.length > 0; + const allAreTests = hasAny && names.every(n => n && isTestFile(n)); + + if (allAreTests) { + labels.push('test-only-change'); + } + + if (enableDebug) { + console.log(`[Test Only Change Rule] allAreTests=${allAreTests} → ${labels.join(', ') || 'none'}`); + } + + return labels; +}; + +module.exports.metadata = { + name: 'Test-only Change Detection', + description: 'Detects PRs that modify only test files', + labels: [ + { name: 'test-only-change', color: '0E8A16', description: 'Only test files changed' } + ], + author: 'pr-auto-labeler', + version: '1.0.0', + category: 'tests' +}; + +