diff --git a/lib/roman_numerals.dart b/lib/roman_numerals.dart index 0cbc072..1309230 100644 --- a/lib/roman_numerals.dart +++ b/lib/roman_numerals.dart @@ -1,3 +1,46 @@ -String integerToRoman(int number) { - throw UnimplementedError(); +class RomanNumeralInput { + static const _minValue = 1; + static const _maxValue = 3999; + + final int value; + + RomanNumeralInput(this.value) { + if (value < _minValue || value > _maxValue) { + throw ArgumentError( + 'Roman numeral input must be between $_minValue and $_maxValue', + ); + } + } +} + +String integerToRoman(int number) { + final input = RomanNumeralInput(number); + + const conversionRules = [ + (1000, 'M'), + (900, 'CM'), + (500, 'D'), + (400, 'CD'), + (100, 'C'), + (90, 'XC'), + (50, 'L'), + (40, 'XL'), + (10, 'X'), + (9, 'IX'), + (5, 'V'), + (4, 'IV'), + (1, 'I'), + ]; + + final result = StringBuffer(); + var remaining = input.value; + + for (final (value, symbol) in conversionRules) { + while (remaining >= value) { + result.write(symbol); + remaining -= value; + } + } + + return result.toString(); } diff --git a/test/roman_numerals_test.dart b/test/roman_numerals_test.dart index ab73b3a..831aa9c 100644 --- a/test/roman_numerals_test.dart +++ b/test/roman_numerals_test.dart @@ -1 +1,84 @@ -void main() {} +import 'package:tdd_katas/roman_numerals.dart'; +import 'package:test/test.dart'; + +void main() { + group('Roman Numerals Conversion', () { + group('Basic Symbols', () { + test('smallest unit', () => expect(integerToRoman(1), 'I')); + test('five', () => expect(integerToRoman(5), 'V')); + test('ten', () => expect(integerToRoman(10), 'X')); + test('fifty', () => expect(integerToRoman(50), 'L')); + test('hundred', () => expect(integerToRoman(100), 'C')); + test('five hundred', () => expect(integerToRoman(500), 'D')); + test('thousand', () => expect(integerToRoman(1000), 'M')); + }); + + group('Subtractive Notation', () { + test('four (one before five)', () => expect(integerToRoman(4), 'IV')); + test('nine (one before ten)', () => expect(integerToRoman(9), 'IX')); + test('forty (ten before fifty)', () => expect(integerToRoman(40), 'XL')); + test( + 'ninety (ten before hundred)', + () => expect(integerToRoman(90), 'XC'), + ); + test( + 'four hundred (hundred before five hundred)', + () => expect(integerToRoman(400), 'CD'), + ); + test( + 'nine hundred (hundred before thousand)', + () => expect(integerToRoman(900), 'CM'), + ); + }); + + group('Additive Combinations', () { + test('two (repeated symbol)', () => expect(integerToRoman(2), 'II')); + test( + 'three (maximum repetition)', + () => expect(integerToRoman(3), 'III'), + ); + test('six (additive after five)', () => expect(integerToRoman(6), 'VI')); + test( + 'twenty-seven (multiple symbols)', + () => expect(integerToRoman(27), 'XXVII'), + ); + }); + + group('Complex Edge Cases', () { + test( + 'forty-nine (combines subtractive symbols)', + () => expect(integerToRoman(49), 'XLIX'), + ); + test( + 'ninety-nine (maximum two-digit complexity)', + () => expect(integerToRoman(99), 'XCIX'), + ); + test( + 'four hundred forty-four (all subtractive positions)', + () => expect(integerToRoman(444), 'CDXLIV'), + ); + test( + '1994 (year notation stress test)', + () => expect(integerToRoman(1994), 'MCMXCIV'), + ); + test( + '3999 (maximum valid Roman numeral)', + () => expect(integerToRoman(3999), 'MMMCMXCIX'), + ); + }); + + group('Error Handling', () { + test('rejects zero', () { + expect(() => integerToRoman(0), throwsArgumentError); + }); + + test('rejects negative numbers', () { + expect(() => integerToRoman(-5), throwsArgumentError); + }); + + test('rejects numbers above 3999', () { + expect(() => integerToRoman(4000), throwsArgumentError); + }); + }); + }); +}