/** * Property-Based Tests: Config Wizard Registry Invariants * * Feature: config-wizard * * Tests the structural invariants of the VARIABLE_DESCRIPTORS registry * from `configure.js`. * * Validates: Requirements 2.1, 2.4, 2.5 */ const { VARIABLE_DESCRIPTORS, GROUP_ORDER } = require('../../configure.js'); // --- Property 1: Descriptor registry uniqueness --- describe('Property 1: Descriptor registry uniqueness', () => { /** * **Validates: Requirements 2.5** * * Every variable name appears exactly once across all groups in the * VARIABLE_DESCRIPTORS registry. */ test('every variable name appears exactly once in the registry', () => { const names = VARIABLE_DESCRIPTORS.map(d => d.name); const nameSet = new Set(names); // No duplicates: set size equals array length expect(nameSet.size).toBe(names.length); // Each name appears exactly once const nameCounts = {}; for (const name of names) { nameCounts[name] = (nameCounts[name] || 0) + 1; } for (const [name, count] of Object.entries(nameCounts)) { expect(count).toBe(1); } }); test('no variable is assigned to multiple groups', () => { const nameToGroups = {}; for (const desc of VARIABLE_DESCRIPTORS) { if (!nameToGroups[desc.name]) { nameToGroups[desc.name] = []; } nameToGroups[desc.name].push(desc.group); } for (const [name, groups] of Object.entries(nameToGroups)) { expect(groups.length).toBe(1); } }); }); // --- Property 2: Group presentation order --- describe('Property 2: Group presentation order', () => { /** * **Validates: Requirements 2.1** * * Consecutive descriptors have non-decreasing group index in GROUP_ORDER, * ensuring variables are presented in group order. */ test('consecutive descriptors have non-decreasing group index', () => { for (let i = 1; i < VARIABLE_DESCRIPTORS.length; i++) { const prevGroupIndex = GROUP_ORDER.indexOf(VARIABLE_DESCRIPTORS[i - 1].group); const currGroupIndex = GROUP_ORDER.indexOf(VARIABLE_DESCRIPTORS[i].group); // Both groups must exist in GROUP_ORDER expect(prevGroupIndex).toBeGreaterThanOrEqual(0); expect(currGroupIndex).toBeGreaterThanOrEqual(0); // Current group index must be >= previous group index expect(currGroupIndex).toBeGreaterThanOrEqual(prevGroupIndex); } }); test('all descriptor groups are present in GROUP_ORDER', () => { const descriptorGroups = new Set(VARIABLE_DESCRIPTORS.map(d => d.group)); for (const group of descriptorGroups) { expect(GROUP_ORDER).toContain(group); } }); }); // --- Property 3: Required-before-optional ordering --- describe('Property 3: Required-before-optional ordering', () => { /** * **Validates: Requirements 2.4** * * Within each group, all required descriptors precede optional ones * in the registry ordering. */ test('within each group, all required descriptors precede optional ones', () => { for (const group of GROUP_ORDER) { const groupDescriptors = VARIABLE_DESCRIPTORS.filter(d => d.group === group); let seenOptional = false; for (const desc of groupDescriptors) { if (desc.required) { // Once we've seen an optional, no more required should appear expect(seenOptional).toBe(false); } else { seenOptional = true; } } } }); test('required count + optional count equals total for each group', () => { for (const group of GROUP_ORDER) { const groupDescriptors = VARIABLE_DESCRIPTORS.filter(d => d.group === group); const requiredCount = groupDescriptors.filter(d => d.required).length; const optionalCount = groupDescriptors.filter(d => !d.required).length; expect(requiredCount + optionalCount).toBe(groupDescriptors.length); } }); });