mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-08-05 12:07:22 -04:00

- Adds a new test suite with Docker-based smoke tests for all locales. Can be run using the ./smoketest.sh script. - Replaces all calls to Math.random() with a new helper that returns 0.5 in snapshot testing mode, ensuring deterministic snapshots. - Similarly replaces all calls to new Date() and Date.now() with new helpers that return a fixed date in snapshot testing mode. - Replaces checks against NODE_ENV with APP_ENV, to ensure that the bundles can be built with Nuxt for testing without losing code that would otherwise be stripped out by production optimizations. - Adds a database init script that can be used to initialize the database with a single admin user and a long-lived JWT token for use in automation tests. - Adds a JWT decoding/encoding CLI tool for debugging JWTs. Note: Snapshots are not checked in, and must be generated manually. See test/__snapshots__/.gitignore for more information.
138 lines
3.8 KiB
Vue
138 lines
3.8 KiB
Vue
<script setup lang="ts">
|
|
import datepicker from '@vuepic/vue-datepicker';
|
|
|
|
import type { CustomEvent } from '~/src/calendar/helpers.ts';
|
|
import { newDate } from '~/src/helpers.ts';
|
|
|
|
defineProps<{
|
|
maxitems: number;
|
|
}>();
|
|
|
|
const { $translator: translator } = useNuxtApp();
|
|
const config = useConfig();
|
|
const { isDark } = useDark();
|
|
|
|
const today = newDate();
|
|
|
|
const modelValue = defineModel<CustomEvent[]>();
|
|
|
|
const showIconSelector = ref<number | false>(false);
|
|
|
|
const maxDaysInMonths: Record<number, number> = {
|
|
1: 31,
|
|
2: 29,
|
|
3: 31,
|
|
4: 30,
|
|
5: 31,
|
|
6: 30,
|
|
7: 31,
|
|
8: 31,
|
|
9: 30,
|
|
10: 31,
|
|
11: 30,
|
|
12: 31,
|
|
};
|
|
|
|
const formatDate = (date: Date): string => {
|
|
return translator.translate(`calendar.dates.${date.getMonth() + 1}`, { day: date.getDate().toString() });
|
|
};
|
|
|
|
const prototype = () => {
|
|
return { name: '', month: '', day: '', comment: '' };
|
|
};
|
|
|
|
const validation = (v: CustomEvent): string | null => {
|
|
if (JSON.stringify(v) === JSON.stringify(prototype())) {
|
|
return null;
|
|
}
|
|
|
|
if (!v.name) {
|
|
return 'profile.calendar.customEvents.validation.missingName';
|
|
}
|
|
if (!v.month || !v.day) {
|
|
return 'profile.calendar.customEvents.validation.missingDate';
|
|
}
|
|
if (v.day < 1 || v.day > maxDaysInMonths[v.month]) {
|
|
return 'profile.calendar.customEvents.validation.invalidDate';
|
|
}
|
|
|
|
return null;
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<ListInput
|
|
v-model="modelValue"
|
|
:prototype="prototype()"
|
|
:maxitems="maxitems"
|
|
disable-sorting
|
|
>
|
|
<template #default="s">
|
|
<datepicker
|
|
:model-value="new Date(today.getFullYear(), s.val.month - 1, s.val.day)"
|
|
:format="formatDate"
|
|
auto-apply
|
|
:enable-time-picker="false"
|
|
:clearable="false"
|
|
no-today
|
|
vertical
|
|
:dark="isDark"
|
|
:locale="config.locale"
|
|
@update:model-value="(date: Date) => {
|
|
s.val.month = date.getMonth() + 1;
|
|
s.val.day = date.getDate();
|
|
}"
|
|
/>
|
|
<button
|
|
type="button"
|
|
:class="['btn', 'btn-outline-secondary', showIconSelector === s.i ? 'btn-secondary text-white border' : '']"
|
|
@click="showIconSelector = showIconSelector === s.i ? false : s.i"
|
|
>
|
|
<Icon :v="s.val.icon" />
|
|
</button>
|
|
<input
|
|
v-model="s.val.name"
|
|
class="form-control"
|
|
required
|
|
maxlength="24"
|
|
:placeholder="$t('profile.calendar.customEvents.name')"
|
|
@keyup="s.update(s.val)"
|
|
@paste="$nextTick(() => s.update(s.val))"
|
|
@change="s.update(s.val)"
|
|
>
|
|
<input
|
|
v-model="s.val.comment"
|
|
class="form-control"
|
|
maxlength="128"
|
|
:placeholder="$t('profile.calendar.customEvents.comment')"
|
|
@keyup="s.update(s.val)"
|
|
@paste="$nextTick(() => s.update(s.val))"
|
|
@change="s.update(s.val)"
|
|
>
|
|
|
|
<IconSelector
|
|
v-if="showIconSelector === s.i"
|
|
class="hanging shadow shadow-lg border"
|
|
@change="s.update({ ...s.val, icon: $event }); showIconSelector = false"
|
|
/>
|
|
</template>
|
|
<template #validation="s">
|
|
<p v-if="validation(s.val)" class="small text-danger">
|
|
<Icon v="exclamation-triangle" />
|
|
<span class="ml-1">{{ $t(validation(s.val)!) }}</span>
|
|
</p>
|
|
</template>
|
|
</ListInput>
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
.hanging {
|
|
position: absolute;
|
|
top: 100%;
|
|
left: 0;
|
|
width: 100%;
|
|
max-width: 500px;
|
|
z-index: 5000;
|
|
}
|
|
</style>
|