Full document, spreadsheet, slideshow, and diagram tooling
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 199 lines 6.4 kB view raw
1/** 2 * Tests for data validation logic. 3 * VSDD: Red phase — these tests define the spec. 4 */ 5import { describe, it, expect } from 'vitest'; 6import { parseListItems, validateCell, getDropdownItems } from '../src/sheets/data-validation.js'; 7 8describe('parseListItems', () => { 9 it('splits comma-separated string into trimmed items', () => { 10 expect(parseListItems('Red, Green, Blue')).toEqual(['Red', 'Green', 'Blue']); 11 }); 12 13 it('handles no spaces around commas', () => { 14 expect(parseListItems('A,B,C')).toEqual(['A', 'B', 'C']); 15 }); 16 17 it('filters out empty items from trailing commas', () => { 18 expect(parseListItems('A,B,')).toEqual(['A', 'B']); 19 }); 20 21 it('returns empty array for empty string', () => { 22 expect(parseListItems('')).toEqual([]); 23 }); 24 25 it('returns empty array for null', () => { 26 expect(parseListItems(null)).toEqual([]); 27 }); 28 29 it('returns empty array for undefined', () => { 30 expect(parseListItems(undefined)).toEqual([]); 31 }); 32 33 it('handles single item', () => { 34 expect(parseListItems('Only')).toEqual(['Only']); 35 }); 36 37 it('trims whitespace from items', () => { 38 expect(parseListItems(' A , B ')).toEqual(['A', 'B']); 39 }); 40}); 41 42describe('validateCell', () => { 43 describe('list validation', () => { 44 const listRule = { type: 'list', value: 'Red, Green, Blue' }; 45 46 it('accepts valid list item', () => { 47 expect(validateCell('Red', listRule)).toEqual({ valid: true }); 48 }); 49 50 it('accepts valid list item case-insensitively', () => { 51 expect(validateCell('red', listRule)).toEqual({ valid: true }); 52 expect(validateCell('RED', listRule)).toEqual({ valid: true }); 53 }); 54 55 it('rejects invalid value', () => { 56 const result = validateCell('Yellow', listRule); 57 expect(result.valid).toBe(false); 58 expect(result.message).toContain('Red'); 59 }); 60 61 it('allows empty cell', () => { 62 expect(validateCell('', listRule)).toEqual({ valid: true }); 63 expect(validateCell(null, listRule)).toEqual({ valid: true }); 64 expect(validateCell(undefined, listRule)).toEqual({ valid: true }); 65 }); 66 67 it('works with items array instead of value string', () => { 68 const rule = { type: 'list', items: ['Yes', 'No', 'Maybe'] }; 69 expect(validateCell('Yes', rule)).toEqual({ valid: true }); 70 expect(validateCell('perhaps', rule).valid).toBe(false); 71 }); 72 73 it('valid when list is empty', () => { 74 expect(validateCell('anything', { type: 'list', value: '' })).toEqual({ valid: true }); 75 }); 76 }); 77 78 describe('numberBetween validation', () => { 79 const numRule = { type: 'numberBetween', value: '1', value2: '100' }; 80 81 it('accepts number within range', () => { 82 expect(validateCell(50, numRule)).toEqual({ valid: true }); 83 }); 84 85 it('accepts value at minimum', () => { 86 expect(validateCell(1, numRule)).toEqual({ valid: true }); 87 }); 88 89 it('accepts value at maximum', () => { 90 expect(validateCell(100, numRule)).toEqual({ valid: true }); 91 }); 92 93 it('rejects number below minimum', () => { 94 const result = validateCell(0, numRule); 95 expect(result.valid).toBe(false); 96 expect(result.message).toContain('between'); 97 }); 98 99 it('rejects number above maximum', () => { 100 const result = validateCell(101, numRule); 101 expect(result.valid).toBe(false); 102 }); 103 104 it('rejects non-numeric input', () => { 105 const result = validateCell('abc', numRule); 106 expect(result.valid).toBe(false); 107 expect(result.message).toContain('number'); 108 }); 109 110 it('allows empty cell', () => { 111 expect(validateCell('', numRule)).toEqual({ valid: true }); 112 }); 113 114 it('accepts string number', () => { 115 expect(validateCell('50', numRule)).toEqual({ valid: true }); 116 }); 117 118 it('handles reversed min/max', () => { 119 const reversed = { type: 'numberBetween', value: '100', value2: '1' }; 120 expect(validateCell(50, reversed)).toEqual({ valid: true }); 121 }); 122 }); 123 124 describe('textLength validation', () => { 125 const textRule = { type: 'textLength', value: '3', value2: '10' }; 126 127 it('accepts text within length range', () => { 128 expect(validateCell('Hello', textRule)).toEqual({ valid: true }); 129 }); 130 131 it('accepts text at minimum length', () => { 132 expect(validateCell('abc', textRule)).toEqual({ valid: true }); 133 }); 134 135 it('accepts text at maximum length', () => { 136 expect(validateCell('0123456789', textRule)).toEqual({ valid: true }); 137 }); 138 139 it('rejects text below minimum length', () => { 140 const result = validateCell('ab', textRule); 141 expect(result.valid).toBe(false); 142 expect(result.message).toContain('length'); 143 }); 144 145 it('rejects text above maximum length', () => { 146 const result = validateCell('this is too long', textRule); 147 expect(result.valid).toBe(false); 148 }); 149 150 it('allows empty cell', () => { 151 expect(validateCell('', textRule)).toEqual({ valid: true }); 152 }); 153 154 it('converts numbers to string for length check', () => { 155 expect(validateCell(12345, textRule)).toEqual({ valid: true }); 156 }); 157 }); 158 159 describe('edge cases', () => { 160 it('returns valid for null rule', () => { 161 expect(validateCell('test', null)).toEqual({ valid: true }); 162 }); 163 164 it('returns valid for undefined rule', () => { 165 expect(validateCell('test', undefined)).toEqual({ valid: true }); 166 }); 167 168 it('returns valid for rule without type', () => { 169 expect(validateCell('test', {})).toEqual({ valid: true }); 170 }); 171 172 it('returns valid for unknown rule type', () => { 173 expect(validateCell('test', { type: 'unknownType' })).toEqual({ valid: true }); 174 }); 175 }); 176}); 177 178describe('getDropdownItems', () => { 179 it('returns items from value string', () => { 180 expect(getDropdownItems({ type: 'list', value: 'A, B, C' })).toEqual(['A', 'B', 'C']); 181 }); 182 183 it('returns items array directly if provided', () => { 184 const items = ['X', 'Y', 'Z']; 185 expect(getDropdownItems({ type: 'list', items })).toEqual(items); 186 }); 187 188 it('returns empty array for non-list type', () => { 189 expect(getDropdownItems({ type: 'numberBetween', value: '1' })).toEqual([]); 190 }); 191 192 it('returns empty array for null rule', () => { 193 expect(getDropdownItems(null)).toEqual([]); 194 }); 195 196 it('returns empty array for undefined rule', () => { 197 expect(getDropdownItems(undefined)).toEqual([]); 198 }); 199});