feat(new-tool): percentage calculator (#456)
* feat(new tool): percentage calculator * Apply suggestions from code review Co-authored-by: Corentin THOMASSET <corentin.thomasset74@gmail.com> --------- Co-authored-by: Corentin THOMASSET <corentin.thomasset74@gmail.com>
This commit is contained in:
		
							parent
							
								
									69f0bd079f
								
							
						
					
					
						commit
						b9406a492d
					
				
							
								
								
									
										1
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -135,6 +135,7 @@ declare module '@vue/runtime-core' { | |||||||
|     NUpload: typeof import('naive-ui')['NUpload'] |     NUpload: typeof import('naive-ui')['NUpload'] | ||||||
|     NUploadDragger: typeof import('naive-ui')['NUploadDragger'] |     NUploadDragger: typeof import('naive-ui')['NUploadDragger'] | ||||||
|     OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default'] |     OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default'] | ||||||
|  |     PercentageCalculator: typeof import('./src/tools/percentage-calculator/percentage-calculator.vue')['default'] | ||||||
|     PhoneParserAndFormatter: typeof import('./src/tools/phone-parser-and-formatter/phone-parser-and-formatter.vue')['default'] |     PhoneParserAndFormatter: typeof import('./src/tools/phone-parser-and-formatter/phone-parser-and-formatter.vue')['default'] | ||||||
|     QrCodeGenerator: typeof import('./src/tools/qr-code-generator/qr-code-generator.vue')['default'] |     QrCodeGenerator: typeof import('./src/tools/qr-code-generator/qr-code-generator.vue')['default'] | ||||||
|     RandomPortGenerator: typeof import('./src/tools/random-port-generator/random-port-generator.vue')['default'] |     RandomPortGenerator: typeof import('./src/tools/random-port-generator/random-port-generator.vue')['default'] | ||||||
|  | |||||||
| @ -33,6 +33,7 @@ import { tool as dateTimeConverter } from './date-time-converter'; | |||||||
| import { tool as deviceInformation } from './device-information'; | import { tool as deviceInformation } from './device-information'; | ||||||
| import { tool as cypher } from './encryption'; | import { tool as cypher } from './encryption'; | ||||||
| import { tool as etaCalculator } from './eta-calculator'; | import { tool as etaCalculator } from './eta-calculator'; | ||||||
|  | import { tool as percentageCalculator } from './percentage-calculator'; | ||||||
| import { tool as gitMemo } from './git-memo'; | import { tool as gitMemo } from './git-memo'; | ||||||
| import { tool as hashText } from './hash-text'; | import { tool as hashText } from './hash-text'; | ||||||
| import { tool as hmacGenerator } from './hmac-generator'; | import { tool as hmacGenerator } from './hmac-generator'; | ||||||
| @ -126,7 +127,7 @@ export const toolsByCategory: ToolCategory[] = [ | |||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: 'Math', |     name: 'Math', | ||||||
|     components: [mathEvaluator, etaCalculator], |     components: [mathEvaluator, etaCalculator, percentageCalculator], | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: 'Measurement', |     name: 'Measurement', | ||||||
|  | |||||||
							
								
								
									
										12
									
								
								src/tools/percentage-calculator/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/tools/percentage-calculator/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | |||||||
|  | import { Percentage } from '@vicons/tabler'; | ||||||
|  | import { defineTool } from '../tool'; | ||||||
|  | 
 | ||||||
|  | export const tool = defineTool({ | ||||||
|  |   name: 'Percentage calculator', | ||||||
|  |   path: '/percentage-calculator', | ||||||
|  |   description: 'Easily calculate percentages from a value to another value, or from a percentage to a value.', | ||||||
|  |   keywords: ['percentage', 'calculator', 'calculate', 'value', 'number', '%'], | ||||||
|  |   component: () => import('./percentage-calculator.vue'), | ||||||
|  |   icon: Percentage, | ||||||
|  |   createdAt: new Date('2023-06-18'), | ||||||
|  | }); | ||||||
| @ -0,0 +1,36 @@ | |||||||
|  | import { expect, test } from '@playwright/test'; | ||||||
|  | 
 | ||||||
|  | test.describe('Tool - Percentage calculator', () => { | ||||||
|  |   test.beforeEach(async ({ page }) => { | ||||||
|  |     await page.goto('/percentage-calculator'); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('Has correct title', async ({ page }) => { | ||||||
|  |     await expect(page).toHaveTitle('Percentage calculator - IT Tools'); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('Correctly works out percentages', async ({ page }) => { | ||||||
|  |     await page.getByTestId('percentageX').locator('input').fill('123'); | ||||||
|  |     await page.getByTestId('percentageY').locator('input').fill('456'); | ||||||
|  |     await expect(page.getByTestId('percentageResult').locator('input')).toHaveValue('560.88'); | ||||||
|  | 
 | ||||||
|  |     await page.getByTestId('numberX').locator('input').fill('123'); | ||||||
|  |     await page.getByTestId('numberY').locator('input').fill('456'); | ||||||
|  |     await expect(page.getByTestId('numberResult').locator('input')).toHaveValue('26.973684210526315'); | ||||||
|  | 
 | ||||||
|  |     await page.getByTestId('numberFrom').locator('input').fill('123'); | ||||||
|  |     await page.getByTestId('numberTo').locator('input').fill('456'); | ||||||
|  |     await expect(page.getByTestId('percentageIncreaseDecrease').locator('input')).toHaveValue('270.7317073170732'); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('Displays empty results for incomplete input', async ({ page }) => { | ||||||
|  |     await page.getByTestId('percentageX').locator('input').fill('123'); | ||||||
|  |     await expect(page.getByTestId('percentageResult').locator('input')).toHaveValue(''); | ||||||
|  | 
 | ||||||
|  |     await page.getByTestId('numberY').locator('input').fill('456'); | ||||||
|  |     await expect(page.getByTestId('numberResult').locator('input')).toHaveValue(''); | ||||||
|  | 
 | ||||||
|  |     await page.getByTestId('numberFrom').locator('input').fill('123'); | ||||||
|  |     await expect(page.getByTestId('percentageIncreaseDecrease').locator('input')).toHaveValue(''); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
							
								
								
									
										78
									
								
								src/tools/percentage-calculator/percentage-calculator.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/tools/percentage-calculator/percentage-calculator.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,78 @@ | |||||||
|  | <script setup lang="ts"> | ||||||
|  | const percentageX = ref(); | ||||||
|  | const percentageY = ref(); | ||||||
|  | const percentageResult = computed(() => { | ||||||
|  |   if (percentageX.value === undefined || percentageY.value === undefined) { | ||||||
|  |     return ''; | ||||||
|  |   } | ||||||
|  |   return (percentageX.value / 100 * percentageY.value).toString(); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const numberX = ref(); | ||||||
|  | const numberY = ref(); | ||||||
|  | const numberResult = computed(() => { | ||||||
|  |   if (numberX.value === undefined || numberY.value === undefined) { | ||||||
|  |     return ''; | ||||||
|  |   } | ||||||
|  |   const result = 100 * numberX.value / numberY.value; | ||||||
|  |   return (!Number.isFinite(result) || Number.isNaN(result)) ? '' : result.toString(); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const numberFrom = ref(); | ||||||
|  | const numberTo = ref(); | ||||||
|  | const percentageIncreaseDecrease = computed(() => { | ||||||
|  |   if (numberFrom.value === undefined || numberTo.value === undefined) { | ||||||
|  |     return ''; | ||||||
|  |   } | ||||||
|  |   const result = (numberTo.value - numberFrom.value) / numberFrom.value * 100; | ||||||
|  |   return (!Number.isFinite(result) || Number.isNaN(result)) ? '' : result.toString(); | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <div style="flex: 0 0 100%"> | ||||||
|  |     <div style="margin: 0 auto; max-width: 600px"> | ||||||
|  |       <c-card mb-3> | ||||||
|  |         <div mb-3 sm:hidden> | ||||||
|  |           What is | ||||||
|  |         </div> | ||||||
|  |         <div flex gap-2> | ||||||
|  |           <div hidden pt-1 sm:block style="min-width: 48px;"> | ||||||
|  |             What is | ||||||
|  |           </div> | ||||||
|  |           <n-input-number v-model:value="percentageX" data-test-id="percentageX" placeholder="X" /> | ||||||
|  |           <div min-w-fit pt-1> | ||||||
|  |             % of | ||||||
|  |           </div> | ||||||
|  |           <n-input-number v-model:value="percentageY" data-test-id="percentageY" placeholder="Y" /> | ||||||
|  |           <input-copyable v-model:value="percentageResult" data-test-id="percentageResult" readonly placeholder="Result" style="max-width: 150px;" /> | ||||||
|  |         </div> | ||||||
|  |       </c-card> | ||||||
|  | 
 | ||||||
|  |       <c-card mb-3> | ||||||
|  |         <div mb-3 sm:hidden> | ||||||
|  |           X is what percent of Y | ||||||
|  |         </div> | ||||||
|  |         <div flex gap-2> | ||||||
|  |           <n-input-number v-model:value="numberX" data-test-id="numberX" placeholder="X" /> | ||||||
|  |           <div hidden min-w-fit pt-1 sm:block> | ||||||
|  |             is what percent of | ||||||
|  |           </div> | ||||||
|  |           <n-input-number v-model:value="numberY" data-test-id="numberY" placeholder="Y" /> | ||||||
|  |           <input-copyable v-model:value="numberResult" data-test-id="numberResult" readonly placeholder="Result" style="max-width: 150px;" /> | ||||||
|  |         </div> | ||||||
|  |       </c-card> | ||||||
|  | 
 | ||||||
|  |       <c-card mb-3> | ||||||
|  |         <div mb-3> | ||||||
|  |           What is the percentage increase/decrease | ||||||
|  |         </div> | ||||||
|  |         <div flex gap-2> | ||||||
|  |           <n-input-number v-model:value="numberFrom" data-test-id="numberFrom" placeholder="From" /> | ||||||
|  |           <n-input-number v-model:value="numberTo" data-test-id="numberTo" placeholder="To" /> | ||||||
|  |           <input-copyable v-model:value="percentageIncreaseDecrease" data-test-id="percentageIncreaseDecrease" readonly placeholder="Result" style="max-width: 150px;" /> | ||||||
|  |         </div> | ||||||
|  |       </c-card> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user