Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"lucide-react": "^1.22.0",
"mathjs": "^15.2.0",
"optional": "^0.1.4",
"radix-ui": "^1.6.0",
"radix-ui": "^1.6.1",
"react": "^19.2.7",
"react-dom": "^19.2.7",
"react-dropzone": "^15.0.0",
Expand All @@ -62,7 +62,7 @@
"prettier-plugin-tailwindcss": "^0.8.0",
"tailwindcss": "^4.3.2",
"typescript": "^6.0.3",
"vite": "^8.1.0",
"vite": "^8.1.2",
"vite-plugin-devtools-json": "^1.0.0",
"vite-plugin-pwa": "^1.3.0",
"vitest": "^4.1.9",
Expand Down
658 changes: 329 additions & 329 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

103 changes: 73 additions & 30 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
@@ -1,33 +1,76 @@
packages:
- '.'
- '.'
allowBuilds:
esbuild: true
msw: true
sharp: true
esbuild: true
msw: true
sharp: true
minimumReleaseAgeExclude:
- isbot@5.1.44
- vite@8.1.0
- prettier@3.9.4
- shadcn@4.12.0
- lucide-react@1.22.0
- '@react-router/dev@8.1.0'
- '@react-router/express@8.1.0'
- '@react-router/node@8.1.0'
- '@react-router/serve@8.1.0'
- '@tailwindcss/node@4.3.2'
- '@tailwindcss/oxide-android-arm64@4.3.2'
- '@tailwindcss/oxide-darwin-arm64@4.3.2'
- '@tailwindcss/oxide-darwin-x64@4.3.2'
- '@tailwindcss/oxide-freebsd-x64@4.3.2'
- '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.2'
- '@tailwindcss/oxide-linux-arm64-gnu@4.3.2'
- '@tailwindcss/oxide-linux-arm64-musl@4.3.2'
- '@tailwindcss/oxide-linux-x64-gnu@4.3.2'
- '@tailwindcss/oxide-linux-x64-musl@4.3.2'
- '@tailwindcss/oxide-wasm32-wasi@4.3.2'
- '@tailwindcss/oxide-win32-arm64-msvc@4.3.2'
- '@tailwindcss/oxide-win32-x64-msvc@4.3.2'
- '@tailwindcss/oxide@4.3.2'
- '@tailwindcss/vite@4.3.2'
- react-router@8.1.0
- tailwindcss@4.3.2
- isbot@5.1.44
- vite@8.1.0 || 8.1.2
- prettier@3.9.4
- shadcn@4.12.0
- lucide-react@1.22.0
- '@react-router/dev@8.1.0'
- '@react-router/express@8.1.0'
- '@react-router/node@8.1.0'
- '@react-router/serve@8.1.0'
- '@tailwindcss/node@4.3.2'
- '@tailwindcss/oxide-android-arm64@4.3.2'
- '@tailwindcss/oxide-darwin-arm64@4.3.2'
- '@tailwindcss/oxide-darwin-x64@4.3.2'
- '@tailwindcss/oxide-freebsd-x64@4.3.2'
- '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.2'
- '@tailwindcss/oxide-linux-arm64-gnu@4.3.2'
- '@tailwindcss/oxide-linux-arm64-musl@4.3.2'
- '@tailwindcss/oxide-linux-x64-gnu@4.3.2'
- '@tailwindcss/oxide-linux-x64-musl@4.3.2'
- '@tailwindcss/oxide-wasm32-wasi@4.3.2'
- '@tailwindcss/oxide-win32-arm64-msvc@4.3.2'
- '@tailwindcss/oxide-win32-x64-msvc@4.3.2'
- '@tailwindcss/oxide@4.3.2'
- '@tailwindcss/vite@4.3.2'
- react-router@8.1.0
- tailwindcss@4.3.2
- '@radix-ui/react-accessible-icon@1.1.11'
- '@radix-ui/react-accordion@1.2.15'
- '@radix-ui/react-alert-dialog@1.1.18'
- '@radix-ui/react-arrow@1.1.11'
- '@radix-ui/react-aspect-ratio@1.1.11'
- '@radix-ui/react-avatar@1.2.1'
- '@radix-ui/react-checkbox@1.3.6'
- '@radix-ui/react-collapsible@1.1.15'
- '@radix-ui/react-collection@1.1.11'
- '@radix-ui/react-context-menu@2.3.2'
- '@radix-ui/react-dialog@1.1.18'
- '@radix-ui/react-dismissable-layer@1.1.14'
- '@radix-ui/react-dropdown-menu@2.1.19'
- '@radix-ui/react-focus-scope@1.1.11'
- '@radix-ui/react-form@0.1.11'
- '@radix-ui/react-hover-card@1.1.18'
- '@radix-ui/react-label@2.1.11'
- '@radix-ui/react-menu@2.1.19'
- '@radix-ui/react-menubar@1.1.19'
- '@radix-ui/react-navigation-menu@1.2.17'
- '@radix-ui/react-one-time-password-field@0.1.11'
- '@radix-ui/react-password-toggle-field@0.1.6'
- '@radix-ui/react-popover@1.1.18'
- '@radix-ui/react-popper@1.3.2'
- '@radix-ui/react-portal@1.1.13'
- '@radix-ui/react-primitive@2.1.7'
- '@radix-ui/react-progress@1.1.11'
- '@radix-ui/react-radio-group@1.4.2'
- '@radix-ui/react-roving-focus@1.1.14'
- '@radix-ui/react-scroll-area@1.2.13'
- '@radix-ui/react-select@2.3.2'
- '@radix-ui/react-separator@1.1.11'
- '@radix-ui/react-slider@1.4.2'
- '@radix-ui/react-switch@1.3.2'
- '@radix-ui/react-tabs@1.1.16'
- '@radix-ui/react-toast@1.2.18'
- '@radix-ui/react-toggle-group@1.1.14'
- '@radix-ui/react-toggle@1.1.13'
- '@radix-ui/react-toolbar@1.1.14'
- '@radix-ui/react-tooltip@1.2.11'
- '@radix-ui/react-use-escape-keydown@1.1.3'
- '@radix-ui/react-visually-hidden@1.2.7'
- radix-ui@1.6.1
74 changes: 74 additions & 0 deletions tests/browser/components/converters/bytes.browser.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { describe, expect, test } from 'vitest';
import { render } from 'vitest-browser-react';
import Bytes from '~/components/pages/converters/bytes';

describe('Bytes', () => {
describe('rendering', () => {
test('shows all unit labels', async () => {
const screen = await render(<Bytes />);
for (const label of [
'Bytes (B)',
'Kilobytes (KB)',
'Megabytes (MB)',
'Gigabytes (GB)',
'Terabytes (TB)',
'Petabytes (PB)',
]) {
await expect.element(screen.getByLabelText(label)).toBeInTheDocument();
}
});

test('inputs start empty', async () => {
const screen = await render(<Bytes />);
await expect.element(screen.getByLabelText('Bytes (B)')).toHaveValue('');
});
});

describe('conversion', () => {
test.each([
['1000', 'Bytes (B)', 'Kilobytes (KB)', '1'],
['1', 'Kilobytes (KB)', 'Bytes (B)', '1000'],
['1', 'Megabytes (MB)', 'Kilobytes (KB)', '1000'],
['1', 'Gigabytes (GB)', 'Megabytes (MB)', '1000'],
['1', 'Terabytes (TB)', 'Gigabytes (GB)', '1000'],
['1', 'Petabytes (PB)', 'Terabytes (TB)', '1000'],
['0.5', 'Kilobytes (KB)', 'Bytes (B)', '500'],
])('%s %s → %s %s', async (value, inputLabel, outputLabel, expected) => {
const screen = await render(<Bytes />);
await screen.getByLabelText(inputLabel).fill(value);
await expect.element(screen.getByLabelText(outputLabel)).toHaveValue(expected);
});

test('comma decimal separator converts correctly', async () => {
const screen = await render(<Bytes />);
await screen.getByLabelText('Kilobytes (KB)').fill('1,5');
await expect.element(screen.getByLabelText('Bytes (B)')).toHaveValue('1500');
});
});

describe('invalid input', () => {
test('ignores letters', async () => {
const screen = await render(<Bytes />);
const input = screen.getByLabelText('Bytes (B)');
await input.fill('abc');
await expect.element(input).toHaveValue('');
});

test('ignores special characters', async () => {
const screen = await render(<Bytes />);
const input = screen.getByLabelText('Kilobytes (KB)');
await input.fill('!@#');
await expect.element(input).toHaveValue('');
});
});

describe('clearing', () => {
test('clearing the active field clears all others', async () => {
const screen = await render(<Bytes />);
await screen.getByLabelText('Bytes (B)').fill('1000');
await screen.getByLabelText('Bytes (B)').fill('');
await expect.element(screen.getByLabelText('Kilobytes (KB)')).toHaveValue('');
await expect.element(screen.getByLabelText('Megabytes (MB)')).toHaveValue('');
});
});
});
57 changes: 57 additions & 0 deletions tests/browser/components/converters/length.browser.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { describe, expect, test } from 'vitest';
import { render } from 'vitest-browser-react';
import Length from '~/components/pages/converters/length';

describe('Length', () => {
describe('rendering', () => {
test('shows all unit labels', async () => {
const screen = await render(<Length />);
for (const label of [
'Kilometers (km)',
'Meters (m)',
'Centimeters (cm)',
'Millimeters (mm)',
'Miles (mi)',
'Yards (yd)',
'Inches (in)',
'Feet (ft)',
'Nautical Miles (nmi)',
]) {
await expect.element(screen.getByLabelText(label)).toBeInTheDocument();
}
});
});

describe('conversion', () => {
test.each([
['1', 'Kilometers (km)', 'Meters (m)', '1000'],
['1000', 'Meters (m)', 'Kilometers (km)', '1'],
['1', 'Meters (m)', 'Centimeters (cm)', '100'],
['100', 'Centimeters (cm)', 'Meters (m)', '1'],
['1', 'Meters (m)', 'Millimeters (mm)', '1000'],
['1', 'Miles (mi)', 'Meters (m)', '1609.34'],
])('%s %s → %s %s', async (value, inputLabel, outputLabel, expected) => {
const screen = await render(<Length />);
await screen.getByLabelText(inputLabel).fill(value);
await expect.element(screen.getByLabelText(outputLabel)).toHaveValue(expected);
});
});

describe('invalid input', () => {
test('ignores letters', async () => {
const screen = await render(<Length />);
const input = screen.getByLabelText('Meters (m)');
await input.fill('xyz');
await expect.element(input).toHaveValue('');
});
});

describe('clearing', () => {
test('clearing the active field clears all others', async () => {
const screen = await render(<Length />);
await screen.getByLabelText('Kilometers (km)').fill('1');
await screen.getByLabelText('Kilometers (km)').fill('');
await expect.element(screen.getByLabelText('Meters (m)')).toHaveValue('');
});
});
});
55 changes: 55 additions & 0 deletions tests/browser/components/converters/mass.browser.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { describe, expect, test } from 'vitest';
import { render } from 'vitest-browser-react';
import Mass from '~/components/pages/converters/mass';

describe('Mass', () => {
describe('rendering', () => {
test('shows all unit labels', async () => {
const screen = await render(<Mass />);
for (const label of [
'Tonnes (t)',
'Kilograms (kg)',
'Grams (g)',
'Milligrams (mg)',
'Pounds (lb)',
'Ounces (oz)',
'Stones (st)',
]) {
await expect.element(screen.getByLabelText(label)).toBeInTheDocument();
}
});
});

describe('conversion', () => {
test.each([
['1', 'Kilograms (kg)', 'Grams (g)', '1000'],
['1000', 'Grams (g)', 'Kilograms (kg)', '1'],
['1', 'Tonnes (t)', 'Kilograms (kg)', '1000'],
['1', 'Kilograms (kg)', 'Pounds (lb)', '2.204623'],
['1', 'Pounds (lb)', 'Grams (g)', '453.592'],
['1', 'Kilograms (kg)', 'Ounces (oz)', '35.27396'],
])('%s %s → %s %s', async (value, inputLabel, outputLabel, expected) => {
const screen = await render(<Mass />);
await screen.getByLabelText(inputLabel).fill(value);
await expect.element(screen.getByLabelText(outputLabel)).toHaveValue(expected);
});
});

describe('invalid input', () => {
test('ignores letters', async () => {
const screen = await render(<Mass />);
const input = screen.getByLabelText('Kilograms (kg)');
await input.fill('abc');
await expect.element(input).toHaveValue('');
});
});

describe('clearing', () => {
test('clearing the active field clears all others', async () => {
const screen = await render(<Mass />);
await screen.getByLabelText('Kilograms (kg)').fill('1');
await screen.getByLabelText('Kilograms (kg)').fill('');
await expect.element(screen.getByLabelText('Grams (g)')).toHaveValue('');
});
});
});
52 changes: 52 additions & 0 deletions tests/browser/components/converters/speed.browser.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { describe, expect, test } from 'vitest';
import { render } from 'vitest-browser-react';
import Speed from '~/components/pages/converters/speed';

describe('Speed', () => {
describe('rendering', () => {
test('shows all unit labels', async () => {
const screen = await render(<Speed />);
for (const label of [
'Kilometers per hour (km/h)',
'Miles per hour (mph)',
'Meters per second (m/s)',
'Feet per second (ft/s)',
'Knots (kn)',
'Mach (Ma)',
]) {
await expect.element(screen.getByLabelText(label)).toBeInTheDocument();
}
});
});

describe('conversion', () => {
test.each([
['3.6', 'Meters per second (m/s)', 'Kilometers per hour (km/h)', '12.96'],
['1', 'Kilometers per hour (km/h)', 'Meters per second (m/s)', '0.277778'],
['1', 'Miles per hour (mph)', 'Kilometers per hour (km/h)', '1.609'],
['1', 'Knots (kn)', 'Kilometers per hour (km/h)', '1.852'],
])('%s %s → %s %s', async (value, inputLabel, outputLabel, expected) => {
const screen = await render(<Speed />);
await screen.getByLabelText(inputLabel).fill(value);
await expect.element(screen.getByLabelText(outputLabel)).toHaveValue(expected);
});
});

describe('invalid input', () => {
test('ignores letters', async () => {
const screen = await render(<Speed />);
const input = screen.getByLabelText('Kilometers per hour (km/h)');
await input.fill('fast');
await expect.element(input).toHaveValue('');
});
});

describe('clearing', () => {
test('clearing the active field clears all others', async () => {
const screen = await render(<Speed />);
await screen.getByLabelText('Kilometers per hour (km/h)').fill('100');
await screen.getByLabelText('Kilometers per hour (km/h)').fill('');
await expect.element(screen.getByLabelText('Miles per hour (mph)')).toHaveValue('');
});
});
});
Loading
Loading