fix(roman-numeral-converter): input validation and feedback (#332)
* fix(roman-numeral-converter): checks for valid input and conversion enhancements Validates if numeral values are between 1 and 3999999. Validates if a roman number is valid. * fix(roman-numeral-converter): optimize logic for copy button * fix(roman-numeral-converter): changes due to review
This commit is contained in:
		
							parent
							
								
									076df11024
								
							
						
					
					
						commit
						8930e139b2
					
				| @ -13,14 +13,17 @@ describe('roman-numeral-converter', () => { | |||||||
|       expect(arabicToRoman(0.9)).toEqual(''); |       expect(arabicToRoman(0.9)).toEqual(''); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  |     it('should convert numbers greater than 3999 to empty string', () => { | ||||||
|  |       expect(arabicToRoman(3999.1)).toEqual(''); | ||||||
|  |       expect(arabicToRoman(4000)).toEqual(''); | ||||||
|  |       expect(arabicToRoman(10000)).toEqual(''); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|     it('should convert floating points number to the lower integer in roman version', () => { |     it('should convert floating points number to the lower integer in roman version', () => { | ||||||
|       expect(arabicToRoman(-100)).toEqual(''); |       expect(arabicToRoman(1.1)).toEqual('I'); | ||||||
|       expect(arabicToRoman(-42)).toEqual(''); |       expect(arabicToRoman(1.9)).toEqual('I'); | ||||||
|       expect(arabicToRoman(-26)).toEqual(''); |       expect(arabicToRoman(17.6)).toEqual('XVII'); | ||||||
|       expect(arabicToRoman(-10)).toEqual(''); |       expect(arabicToRoman(29.999)).toEqual('XXIX'); | ||||||
|       expect(arabicToRoman(0)).toEqual(''); |  | ||||||
|       expect(arabicToRoman(0.5)).toEqual(''); |  | ||||||
|       expect(arabicToRoman(0.9)).toEqual(''); |  | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('should convert positive integers to roman numbers', () => { |     it('should convert positive integers to roman numbers', () => { | ||||||
| @ -67,7 +70,6 @@ describe('roman-numeral-converter', () => { | |||||||
|       expect(arabicToRoman(999)).toEqual('CMXCIX'); |       expect(arabicToRoman(999)).toEqual('CMXCIX'); | ||||||
|       expect(arabicToRoman(1000)).toEqual('M'); |       expect(arabicToRoman(1000)).toEqual('M'); | ||||||
|       expect(arabicToRoman(2000)).toEqual('MM'); |       expect(arabicToRoman(2000)).toEqual('MM'); | ||||||
|       expect(arabicToRoman(9000)).toEqual('MMMMMMMMM'); |  | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
|  | |||||||
| @ -1,5 +1,7 @@ | |||||||
|  | export const MIN_ARABIC_TO_ROMAN = 1; | ||||||
|  | export const MAX_ARABIC_TO_ROMAN = 3999; | ||||||
| export function arabicToRoman(num: number) { | export function arabicToRoman(num: number) { | ||||||
|   if (num < 1) return ''; |   if (num < MIN_ARABIC_TO_ROMAN || num > MAX_ARABIC_TO_ROMAN) return ''; | ||||||
| 
 | 
 | ||||||
|   const lookup: { [key: string]: number } = { |   const lookup: { [key: string]: number } = { | ||||||
|     M: 1000, |     M: 1000, | ||||||
| @ -26,7 +28,16 @@ export function arabicToRoman(num: number) { | |||||||
|   return roman; |   return roman; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | const ROMAN_NUMBER_REGEX = new RegExp(/^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$/); | ||||||
|  | 
 | ||||||
|  | export function isValidRomanNumber(romanNumber: string) { | ||||||
|  |   return ROMAN_NUMBER_REGEX.test(romanNumber); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export function romanToArabic(s: string) { | export function romanToArabic(s: string) { | ||||||
|  |   if (!isValidRomanNumber(s)) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|   const map: { [key: string]: number } = { I: 1, V: 5, X: 10, L: 50, C: 100, D: 500, M: 1000 }; |   const map: { [key: string]: number } = { I: 1, V: 5, X: 10, L: 50, C: 100, D: 500, M: 1000 }; | ||||||
|   return [...s].reduce((r, c, i, s) => (map[s[i + 1]] > map[c] ? r - map[c] : r + map[c]), 0); |   return [...s].reduce((r, c, i, s) => (map[s[i + 1]] > map[c] ? r - map[c] : r + map[c]), 0); | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,21 +2,29 @@ | |||||||
|   <div> |   <div> | ||||||
|     <n-card title="Arabic to roman"> |     <n-card title="Arabic to roman"> | ||||||
|       <n-space align="center" justify="space-between"> |       <n-space align="center" justify="space-between"> | ||||||
|  |         <n-form-item v-bind="validationNumeral"> | ||||||
|           <n-input-number v-model:value="inputNumeral" :min="1" style="width: 200px" :show-button="false" /> |           <n-input-number v-model:value="inputNumeral" :min="1" style="width: 200px" :show-button="false" /> | ||||||
|  |         </n-form-item> | ||||||
|         <div class="result"> |         <div class="result"> | ||||||
|           {{ outputRoman }} |           {{ outputRoman }} | ||||||
|         </div> |         </div> | ||||||
|         <n-button secondary autofocus @click="copyRoman"> Copy </n-button> |         <n-button secondary autofocus :disabled="validationNumeral.validationStatus === 'error'" @click="copyRoman"> | ||||||
|  |           Copy | ||||||
|  |         </n-button> | ||||||
|       </n-space> |       </n-space> | ||||||
|     </n-card> |     </n-card> | ||||||
|     <br /> |     <br /> | ||||||
|     <n-card title="Roman to arabic"> |     <n-card title="Roman to arabic"> | ||||||
|       <n-space align="center" justify="space-between"> |       <n-space align="center" justify="space-between"> | ||||||
|  |         <n-form-item v-bind="validationRoman"> | ||||||
|           <n-input v-model:value="inputRoman" style="width: 200px" /> |           <n-input v-model:value="inputRoman" style="width: 200px" /> | ||||||
|  |         </n-form-item> | ||||||
|         <div class="result"> |         <div class="result"> | ||||||
|           {{ outputNumeral }} |           {{ outputNumeral }} | ||||||
|         </div> |         </div> | ||||||
|         <n-button secondary autofocus @click="copyArabic"> Copy </n-button> |         <n-button secondary autofocus :disabled="validationRoman.validationStatus === 'error'" @click="copyArabic"> | ||||||
|  |           Copy | ||||||
|  |         </n-button> | ||||||
|       </n-space> |       </n-space> | ||||||
|     </n-card> |     </n-card> | ||||||
|   </div> |   </div> | ||||||
| @ -25,14 +33,41 @@ | |||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import { useCopy } from '@/composable/copy'; | import { useCopy } from '@/composable/copy'; | ||||||
| import { ref, computed } from 'vue'; | import { ref, computed } from 'vue'; | ||||||
| import { arabicToRoman, romanToArabic } from './roman-numeral-converter.service'; | import { useValidation } from '@/composable/validation'; | ||||||
|  | import { | ||||||
|  |   arabicToRoman, | ||||||
|  |   romanToArabic, | ||||||
|  |   MAX_ARABIC_TO_ROMAN, | ||||||
|  |   MIN_ARABIC_TO_ROMAN, | ||||||
|  |   isValidRomanNumber, | ||||||
|  | } from './roman-numeral-converter.service'; | ||||||
| 
 | 
 | ||||||
| const inputNumeral = ref(42); | const inputNumeral = ref(42); | ||||||
| const outputRoman = computed(() => arabicToRoman(inputNumeral.value)); | const outputRoman = computed(() => arabicToRoman(inputNumeral.value)); | ||||||
| 
 | 
 | ||||||
|  | const { attrs: validationNumeral } = useValidation({ | ||||||
|  |   source: inputNumeral, | ||||||
|  |   rules: [ | ||||||
|  |     { | ||||||
|  |       validator: (value) => value >= MIN_ARABIC_TO_ROMAN && value <= MAX_ARABIC_TO_ROMAN, | ||||||
|  |       message: `We can only convert numbers between ${MIN_ARABIC_TO_ROMAN.toLocaleString()} and ${MAX_ARABIC_TO_ROMAN.toLocaleString()}`, | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  | }); | ||||||
|  | 
 | ||||||
| const inputRoman = ref('XLII'); | const inputRoman = ref('XLII'); | ||||||
| const outputNumeral = computed(() => romanToArabic(inputRoman.value)); | const outputNumeral = computed(() => romanToArabic(inputRoman.value)); | ||||||
| 
 | 
 | ||||||
|  | const { attrs: validationRoman } = useValidation({ | ||||||
|  |   source: inputRoman, | ||||||
|  |   rules: [ | ||||||
|  |     { | ||||||
|  |       validator: (value) => isValidRomanNumber(value), | ||||||
|  |       message: `The input you entered is not a valid roman number`, | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  | }); | ||||||
|  | 
 | ||||||
| const { copy: copyRoman } = useCopy({ source: outputRoman, text: 'Roman number copied to the clipboard' }); | const { copy: copyRoman } = useCopy({ source: outputRoman, text: 'Roman number copied to the clipboard' }); | ||||||
| const { copy: copyArabic } = useCopy({ source: outputNumeral, text: 'Arabic number copied to the clipboard' }); | const { copy: copyArabic } = useCopy({ source: outputNumeral, text: 'Arabic number copied to the clipboard' }); | ||||||
| </script> | </script> | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user