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", |
"extends": "../.nuxt/tsconfig.server.json", |
||||
"compilerOptions": { |
"compilerOptions": { |
||||
"types": ["vitest/globals"] |
"paths": { |
||||
|
"#app/*": ["../app/*"] |
||||
|
} |
||||
}, |
}, |
||||
"include": ["./**/*.ts"] |
"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