diff --git a/src/services/config-service.js b/src/services/config-service.js index 77c6c873..fad122e8 100644 --- a/src/services/config-service.js +++ b/src/services/config-service.js @@ -21,6 +21,7 @@ import * as output from '../utils/output.js'; let DEFAULT_CONFIG = { comparison: { threshold: 2.0, + minClusterSize: 2, }, server: { port: 47392, @@ -104,6 +105,13 @@ export function createConfigService({ workingDir }) { config.comparison.threshold = parseFloat(process.env.VIZZLY_THRESHOLD); sources.comparison = 'env'; } + if (process.env.VIZZLY_MIN_CLUSTER_SIZE) { + config.comparison.minClusterSize = parseInt( + process.env.VIZZLY_MIN_CLUSTER_SIZE, + 10 + ); + sources.comparison = 'env'; + } if (process.env.VIZZLY_PORT) { config.server.port = parseInt(process.env.VIZZLY_PORT, 10); sources.server = 'env'; @@ -241,6 +249,20 @@ export default defineConfig(${JSON.stringify(newConfig, null, 2)}); } } + // Validate minClusterSize + if (config.comparison?.minClusterSize !== undefined) { + let minClusterSize = config.comparison.minClusterSize; + if (!Number.isInteger(minClusterSize) || minClusterSize < 1) { + errors.push( + 'comparison.minClusterSize must be a positive integer (1 or greater)' + ); + } else if (minClusterSize > 100) { + warnings.push( + 'comparison.minClusterSize above 100 may filter out most differences' + ); + } + } + // Validate port if (config.server?.port !== undefined) { let port = config.server.port; diff --git a/tests/services/config-service.test.js b/tests/services/config-service.test.js index dca2f45b..e52c4e26 100644 --- a/tests/services/config-service.test.js +++ b/tests/services/config-service.test.js @@ -43,6 +43,7 @@ describe('services/config-service', () => { tempDir = createTempDir(); // Clear any environment variables that might affect tests delete process.env.VIZZLY_THRESHOLD; + delete process.env.VIZZLY_MIN_CLUSTER_SIZE; delete process.env.VIZZLY_PORT; }); @@ -50,6 +51,7 @@ describe('services/config-service', () => { cleanupTempDir(tempDir); // Clean up environment variables after each test delete process.env.VIZZLY_THRESHOLD; + delete process.env.VIZZLY_MIN_CLUSTER_SIZE; delete process.env.VIZZLY_PORT; delete process.env.VIZZLY_HOME; }); @@ -88,6 +90,32 @@ describe('services/config-service', () => { assert.strictEqual(config.comparison.threshold, 5.0); assert.strictEqual(sources.threshold, 'project'); }); + + it('applies VIZZLY_MIN_CLUSTER_SIZE env var override', async () => { + process.env.VIZZLY_MIN_CLUSTER_SIZE = '10'; + + let service = createConfigService({ workingDir: tempDir }); + let { config, sources } = await service.getConfig('merged'); + + assert.strictEqual(config.comparison.minClusterSize, 10); + assert.strictEqual(sources.comparison, 'env'); + }); + + it('env var overrides project config for minClusterSize', async () => { + // Create project config with minClusterSize + writeFileSync( + join(tempDir, 'vizzly.config.js'), + `export default { comparison: { minClusterSize: 3 } };` + ); + + // Env var should win + process.env.VIZZLY_MIN_CLUSTER_SIZE = '8'; + + let service = createConfigService({ workingDir: tempDir }); + let { config } = await service.getConfig('merged'); + + assert.strictEqual(config.comparison.minClusterSize, 8); + }); }); describe('getConfig - project', () => { @@ -334,6 +362,50 @@ describe('services/config-service', () => { assert.ok(result.errors.some(e => e.includes('timeout'))); }); + it('returns error for non-integer minClusterSize', async () => { + let service = createConfigService({ workingDir: tempDir }); + + let result = await service.validateConfig({ + comparison: { minClusterSize: 3.5 }, + }); + + assert.strictEqual(result.valid, false); + assert.ok(result.errors.some(e => e.includes('minClusterSize'))); + }); + + it('returns error for zero or negative minClusterSize', async () => { + let service = createConfigService({ workingDir: tempDir }); + + let result = await service.validateConfig({ + comparison: { minClusterSize: 0 }, + }); + + assert.strictEqual(result.valid, false); + assert.ok(result.errors.some(e => e.includes('minClusterSize'))); + }); + + it('returns warning for very high minClusterSize', async () => { + let service = createConfigService({ workingDir: tempDir }); + + let result = await service.validateConfig({ + comparison: { minClusterSize: 150 }, + }); + + assert.strictEqual(result.valid, true); + assert.ok(result.warnings.some(w => w.includes('100'))); + }); + + it('validates valid minClusterSize', async () => { + let service = createConfigService({ workingDir: tempDir }); + + let result = await service.validateConfig({ + comparison: { minClusterSize: 5 }, + }); + + assert.strictEqual(result.valid, true); + assert.strictEqual(result.errors.length, 0); + }); + it('validates empty config as valid', async () => { let service = createConfigService({ workingDir: tempDir });