mirror of https://github.com/wg-easy/wg-easy
7 changed files with 120 additions and 1 deletions
@ -0,0 +1,45 @@ |
|||
<template> |
|||
<div class="flex items-center"> |
|||
<FormLabel :for="id"> |
|||
{{ label }} |
|||
</FormLabel> |
|||
<BaseTooltip v-if="description" :text="description"> |
|||
<IconsInfo class="size-4" /> |
|||
</BaseTooltip> |
|||
</div> |
|||
<div |
|||
class="flex overflow-hidden rounded-lg border-2 border-gray-100 dark:border-neutral-800" |
|||
> |
|||
<BaseInput |
|||
:id="id" |
|||
v-model.number="quotaGiB" |
|||
:max="maximumGiB" |
|||
:min="minimumGiB" |
|||
:name="id" |
|||
step="any" |
|||
type="number" |
|||
class="w-full !border-0" |
|||
/> |
|||
<div |
|||
class="flex h-full items-center bg-neutral-200 px-4 dark:bg-neutral-800" |
|||
> |
|||
<span>GiB</span> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
defineProps<{ id: string; label: string; description?: string }>(); |
|||
|
|||
const quotaBytes = defineModel<number | null>({ required: true }); |
|||
const minimumGiB = quotaBytesToGiB(1)!; |
|||
const maximumGiB = quotaBytesToGiB(Number.MAX_SAFE_INTEGER)!; |
|||
|
|||
const quotaGiB = computed<number | null>({ |
|||
get: () => quotaBytesToGiB(quotaBytes.value), |
|||
set: (value) => { |
|||
const normalized = (value as number | string | null) === '' ? null : value; |
|||
quotaBytes.value = quotaGiBToBytes(normalized); |
|||
}, |
|||
}); |
|||
</script> |
|||
@ -1,7 +1,9 @@ |
|||
{ |
|||
"extends": "../.nuxt/tsconfig.server.json", |
|||
"compilerOptions": { |
|||
"types": ["vitest/globals"] |
|||
"paths": { |
|||
"#app/*": ["../app/*"] |
|||
} |
|||
}, |
|||
"include": ["./**/*.ts"] |
|||
} |
|||
|
|||
@ -0,0 +1,32 @@ |
|||
import { describe, expect, test } from 'vitest'; |
|||
|
|||
import { |
|||
GIBIBYTE_IN_BYTES, |
|||
quotaBytesToGiB, |
|||
quotaGiBToBytes, |
|||
} from '#app/utils/math'; |
|||
|
|||
describe('quota conversions', () => { |
|||
test('keeps disabled quotas null', () => { |
|||
expect(quotaBytesToGiB(null)).toBeNull(); |
|||
expect(quotaGiBToBytes(null)).toBeNull(); |
|||
expect(quotaGiBToBytes(0)).toBeNull(); |
|||
}); |
|||
|
|||
test('converts whole and fractional GiB to bytes', () => { |
|||
expect(quotaGiBToBytes(1)).toBe(GIBIBYTE_IN_BYTES); |
|||
expect(quotaGiBToBytes(0.5)).toBe(GIBIBYTE_IN_BYTES / 2); |
|||
expect(quotaGiBToBytes(1 / GIBIBYTE_IN_BYTES)).toBe(1); |
|||
}); |
|||
|
|||
test('rounds edited GiB values to the nearest byte', () => { |
|||
expect(quotaGiBToBytes(1.5 / GIBIBYTE_IN_BYTES)).toBe(2); |
|||
}); |
|||
|
|||
test.each([1, 150, GIBIBYTE_IN_BYTES, Number.MAX_SAFE_INTEGER])( |
|||
'round-trips the exact byte value %s', |
|||
(bytes) => { |
|||
expect(quotaGiBToBytes(quotaBytesToGiB(bytes))).toBe(bytes); |
|||
} |
|||
); |
|||
}); |
|||
Loading…
Reference in new issue