mirror of
				https://github.com/CorentinTh/it-tools.git
				synced 2025-11-04 05:53:25 +00:00 
			
		
		
		
	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:
		
							
								
								
									
										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>
 | 
				
			||||||
		Reference in New Issue
	
	Block a user