tests: add attrs

This commit is contained in:
Nikolai Giman
2026-07-25 23:09:08 +02:00
parent b7a50049bb
commit 64089fd6e7
30 changed files with 89 additions and 13 deletions
+2 -2
View File
@@ -131,7 +131,7 @@ function applyExtensionMessages(extensions: TvWebExtension[]) {
}
}
export function createTaskviewApp(options: CreateTaskviewAppOptions = {}) {
export async function createTaskviewApp(options: CreateTaskviewAppOptions = {}) {
const extensions = options.extensions ?? []
addCollection(lucide)
@@ -143,7 +143,7 @@ export function createTaskviewApp(options: CreateTaskviewAppOptions = {}) {
const pinia = createPinia()
pinia.use(storeResetPlugin)
app.use(pinia)
app.use(api)
await api.install(app)
app.use(i18n as unknown as Plugin)
applyExtensionMessages(extensions)
@@ -13,6 +13,7 @@
icon="i-lucide-calendar"
:trailing-icon="open ? 'i-lucide-chevron-up' : 'i-lucide-chevron-down'"
:ui="activatorUi('text-muted')"
data-testid="date-range-trigger"
>
<span class="flex-1 flex items-center gap-2 text-left font-medium">
<template v-if="isRange">
@@ -50,6 +50,7 @@ import { computed, watch } from 'vue'
import { storeToRefs } from 'pinia'
import { useRoute, useRouter } from 'vue-router'
import { useGoalListsStore } from '@/stores/goal-lists.store'
import TvCollapseSidebarDesktop from '@/components/features/base/TvCollapseSidebarDesktop.vue'
import ListAddInput from './parts/ListAddInput.vue'
import ListsList from './ListsList.vue'
import ListsArchive from './ListsArchive.vue'
@@ -7,8 +7,12 @@
:to="{ name: 'user', params: { projectId, listId: list.id } }"
:active="currentListId === list.id"
variant="taskview"
:data-testid="`list-card-${list.name}`"
>
<div class="flex items-center justify-between w-full">
<div
class="flex items-center justify-between w-full"
:data-testid="`list-row-${list.name}`"
>
<span>{{ list.name }}</span>
<UButton
icon="i-lucide-ellipsis"
@@ -16,6 +20,7 @@
variant="ghost"
size="xs"
class="z-10 cursor-pointer"
data-testid="list-menu-trigger"
@click.prevent.stop="openContextMenu($event, list)"
/>
</div>
@@ -32,6 +37,7 @@
variant="ghost"
color="neutral"
class="w-full justify-start"
data-testid="list-menu-edit"
@click="openEditModal"
/>
@@ -54,6 +60,7 @@
variant="ghost"
color="error"
class="w-full justify-start"
data-testid="list-menu-delete"
@click="openDeleteDialog"
/>
</div>
@@ -77,6 +84,7 @@ import { ref, computed } from 'vue'
import { useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
import TvContextMenu from '@/components/features/base/TvContextMenu.vue'
import TvGoalLikeItem from '@/components/features/base/TvGoalLikeItem.vue'
import ListEditModal from '@/components/features/lists/parts/ListEditModal.vue'
import ListDeleteDialog from '@/components/features/lists/parts/ListDeleteDialog.vue'
import type { List } from '@/components/features/lists/types'
@@ -8,6 +8,7 @@
:ui="{
base: 'bg-tv-ui-bg-elevated',
}"
data-testid="list-add-input"
@keydown.enter="addList"
>
<template #trailing>
@@ -21,6 +21,7 @@
<UButton
:label="t('contextMenu.delete')"
color="error"
data-testid="list-delete-confirm"
@click="confirmDelete"
/>
</div>
@@ -13,6 +13,7 @@
variant="soft"
class="w-full"
:ui="{ base: 'rounded-xl' }"
data-testid="list-edit-name"
/>
</UFormField>
</div>
@@ -29,6 +30,7 @@
:label="t('common.save')"
color="primary"
variant="outline"
data-testid="list-edit-save"
@click="save"
/>
</template>
@@ -24,7 +24,10 @@
:to="{ name: 'user', params: { projectId, listId: list.id } }"
:active="currentListId === list.id"
>
<div class="flex items-center justify-between w-full">
<div
class="flex items-center justify-between w-full"
:data-testid="`list-row-${list.name}`"
>
<span>{{ list.name }}</span>
<UButton
icon="i-lucide-ellipsis"
@@ -32,6 +35,7 @@
variant="ghost"
size="xs"
class="z-10 cursor-pointer"
data-testid="list-menu-trigger"
@click.prevent.stop="openContextMenu($event, list)"
/>
</div>
@@ -49,6 +53,7 @@
variant="ghost"
color="neutral"
class="w-full justify-start"
data-testid="list-menu-edit"
@click="openEditModal"
/>
@@ -81,6 +86,7 @@
variant="ghost"
color="error"
class="w-full justify-start"
data-testid="list-menu-delete"
@click="openDeleteDialog"
/>
</div>
@@ -106,6 +112,7 @@ import { ref, computed } from 'vue'
import { useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
import TvContextMenu from '@/components/features/base/TvContextMenu.vue'
import TvGoalLikeItem from '@/components/features/base/TvGoalLikeItem.vue'
import ListEditModal from '@/components/features/lists/parts/ListEditModal.vue'
import ListDeleteDialog from '@/components/features/lists/parts/ListDeleteDialog.vue'
import ListAddInput from '@/components/features/lists/parts/ListAddInput.vue'
@@ -203,6 +203,7 @@
import { ref, computed } from 'vue'
import { useRoute } from 'vue-router'
import TvContextMenu from '@/components/features/base/TvContextMenu.vue'
import TvGoalLikeItem from '@/components/features/base/TvGoalLikeItem.vue'
import ProjectEditModal from '@/components/features/projects/parts/ProjectEditModal.vue'
import ProjectDeleteDialog from '@/components/features/projects/parts/ProjectDeleteDialog.vue'
import ProjectAddInput from '@/components/features/projects/parts/ProjectAddInput.vue'
@@ -26,11 +26,14 @@
<SettingsVersionRow v-if="section.key === 'about'" />
</SettingsSection>
<ExtensionOutlet name="settings-sections" />
<SettingsLogoutRow />
</div>
</template>
<script setup lang="ts">
import ExtensionOutlet from '@/components/ExtensionOutlet.vue'
import { useSettingsHub } from './composables/useSettingsHub'
import SettingsProfileHeader from './parts/SettingsProfileHeader.vue'
import SettingsSection from './parts/SettingsSection.vue'
@@ -1,5 +1,8 @@
<template>
<div class="h-full relative flex flex-col gap-4 @container">
<div
class="h-full relative flex flex-col gap-4 @container"
data-testid="task-detail-panel"
>
<template v-if="task">
<!-- Title & Checkbox -->
<div class="flex items-start gap-3 shadow-sm rounded-2xl dark:bg-tv-ui-bg-elevated">
@@ -12,6 +15,7 @@
class="w-full"
:class="{ 'text-muted': task.complete }"
:ui="{ base: 'rounded-2xl' }"
data-testid="task-detail-title"
>
<template #leading>
<div class="h-full">
@@ -19,6 +23,7 @@
:disabled="!canEditTaskStatus"
:model-value="!!task.complete"
class="mt-0.5"
data-testid="task-detail-complete"
@update:model-value="toggleComplete"
/>
</div>
@@ -29,12 +29,14 @@
:label="t('common.delete')"
color="error"
variant="soft"
data-testid="task-detail-delete-button"
@click="openDeleteDialog"
/>
<UButton
:label="t('common.close')"
color="neutral"
variant="soft"
data-testid="task-detail-close-button"
@click="closeTask"
/>
</div>
@@ -77,12 +79,14 @@
:label="t('common.delete')"
color="error"
variant="soft"
data-testid="task-detail-delete-button"
@click="openDeleteDialog"
/>
<UButton
:label="t('common.close')"
color="neutral"
variant="soft"
data-testid="task-detail-close-button"
@click="closeTask"
/>
</div>
@@ -1,5 +1,8 @@
<template>
<div class="note-editor border border-default rounded-2xl overflow-hidden dark:bg-tv-ui-bg-elevated!">
<div
class="note-editor border border-default rounded-2xl overflow-hidden dark:bg-tv-ui-bg-elevated!"
data-testid="task-note-editor"
>
<UEditor
v-if="canViewTaskNote"
#default="{ editor }"
@@ -22,6 +22,7 @@
:label="t('common.delete')"
color="error"
variant="soft"
data-testid="task-delete-confirm"
@click="confirmDelete"
/>
</div>
@@ -10,6 +10,7 @@
variant="soft"
class="w-full"
:ui="activatorUi('text-muted')"
data-testid="task-estimate-input"
@update:model-value="onInput"
@blur="save"
/>
@@ -50,6 +50,7 @@
color="neutral"
variant="ghost"
size="xs"
data-testid="history-info"
@click="openDetailModal(item)"
/>
<UButton
@@ -60,6 +61,7 @@
size="xs"
:loading="restoringId === item.historyId"
:disabled="restoringId !== null"
data-testid="history-restore"
@click="handleRestore(item.historyId)"
/>
</div>
@@ -194,7 +196,7 @@ async function fetchHistory() {
function toggleExpand() {
isExpanded.value = !isExpanded.value
if (isExpanded.value && history.value.length === 0) {
if (isExpanded.value) {
fetchHistory()
}
}
@@ -6,6 +6,7 @@
:icon="copied ? 'i-lucide-check' : 'i-lucide-hash'"
class="text-muted hover:text-default"
:title="t('tasks.copyId')"
data-testid="task-id-copy"
@click="copyId"
>
{{ taskId }}
@@ -54,6 +54,7 @@
:ui="{ base: 'rounded-md', leadingIcon: 'size-3' }"
variant="subtle"
size="md"
data-testid="task-recurrence-badge"
/>
<!-- Project -->
@@ -9,6 +9,7 @@
class="flex-1"
trailing-icon="i-lucide-chevron-down"
:ui="activatorUi('text-muted')"
data-testid="task-recurrence-trigger"
@click="dialogOpen = true"
>
<span
@@ -57,6 +57,7 @@
:label="t('common.save')"
class="ml-auto"
:loading="actionLoading === 'save'"
data-testid="recurrence-save"
@click="save"
/>
</div>
@@ -2,6 +2,7 @@
<div
v-if="canViewTimeTracking || canLogTime"
class="flex flex-col gap-3 rounded-2xl bg-accented/20 p-3.5"
data-testid="time-tracking-section"
>
<div class="flex items-center justify-between gap-2">
<div class="flex items-center gap-2">
@@ -17,6 +18,7 @@
variant="subtle"
size="sm"
:ui="{ base: 'rounded-full' }"
data-testid="time-total-badge"
/>
</div>
@@ -37,6 +39,7 @@
size="lg"
block
:ui="{ base: 'rounded-xl justify-center' }"
data-testid="time-start-button"
@click="onStart"
/>
<UButton
@@ -48,6 +51,7 @@
size="lg"
block
:ui="{ base: 'rounded-xl justify-center' }"
data-testid="time-stop-button"
@click="store.stop()"
/>
@@ -66,6 +70,7 @@
<div
v-if="entries.length > 0"
class="flex flex-col gap-1 rounded-2xl bg-elevated p-2 max-h-80 overflow-y-auto"
data-testid="time-entries-list"
>
<TaskTimeTrackingEntry
v-for="entry in entries"
@@ -97,6 +102,7 @@
size="lg"
block
:ui="{ base: 'rounded-xl justify-start' }"
data-testid="time-add-manual-button"
@click="manualOpen = true"
/>
<TaskTimeTrackingForm
@@ -26,6 +26,7 @@
:label="t('timeTracking.delete')"
icon="i-lucide-trash-2"
color="error"
data-testid="time-delete-confirm"
@click="onConfirm"
/>
</div>
@@ -1,5 +1,8 @@
<template>
<div class="flex flex-col gap-1 px-3 py-2 rounded-xl bg-default shadow-xs text-sm">
<div
class="flex flex-col gap-1 px-3 py-2 rounded-xl bg-default shadow-xs text-sm"
data-testid="time-entry"
>
<div class="flex lg:items-center flex-col lg:flex-row justify-between gap-2">
<div class="flex flex-col min-w-0 flex-1">
<div class="flex items-center gap-2 text-xs text-muted">
@@ -18,6 +21,7 @@
color="neutral"
variant="subtle"
size="sm"
data-testid="time-entry-duration"
/>
<UBadge
v-else
@@ -25,6 +29,7 @@
color="primary"
variant="subtle"
size="xs"
data-testid="time-entry-running"
/>
<div class="w-full flex justify-end gap-2">
<UButton
@@ -33,6 +38,7 @@
color="neutral"
variant="ghost"
size="xs"
data-testid="time-entry-edit"
@click="editing = true"
/>
<UButton
@@ -41,6 +47,7 @@
color="error"
variant="ghost"
size="xs"
data-testid="time-entry-delete"
@click="emit('delete', entry.id)"
/>
</div>
@@ -1,9 +1,13 @@
<template>
<div class="flex flex-col gap-3">
<div class="flex gap-2">
<UPopover v-model:open="startOpen" :ui="{ content: 'rounded-16' }">
<UPopover
v-model:open="startOpen"
:ui="{ content: 'rounded-16' }"
>
<UButton
:label="formattedStart"
data-testid="time-form-start"
icon="i-lucide-calendar"
color="neutral"
variant="outline"
@@ -30,6 +34,7 @@
<UPopover v-model:open="endOpen">
<UButton
:label="formattedEnd"
data-testid="time-form-end"
icon="i-lucide-calendar-check"
color="neutral"
variant="outline"
@@ -61,6 +66,7 @@
variant="outline"
class="w-full"
:ui="{ base: 'rounded-xl' }"
data-testid="time-form-description"
/>
</UFormField>
@@ -81,6 +87,7 @@
:label="submitLabel ?? t('timeTracking.save')"
color="primary"
:disabled="!isValid"
data-testid="time-form-save"
@click="onSubmit"
/>
</div>
@@ -21,6 +21,7 @@
size="sm"
:loading="loading"
:disabled="loading"
data-testid="tasks-toggle-completed"
@click="toggleCompleted"
/>
</UTooltip>
@@ -13,6 +13,7 @@
:icon="currentProject?.isInbox ? 'i-lucide-inbox' : 'i-lucide-folder'"
trailing-icon="i-lucide-chevron-down"
:ui="{ base: 'rounded-xl', trailingIcon: 'ms-auto' }"
data-testid="project-select-trigger"
>
<span
class="flex-1 text-left truncate"
@@ -33,6 +34,7 @@
:variant="project.id === currentProjectId ? 'soft' : 'ghost'"
block
class="justify-start"
:data-testid="`project-option-${project.name}`"
@click="selectProject(project)"
/>
<p
@@ -115,6 +117,7 @@
variant="soft"
size="sm"
:ui="{ base: 'rounded-lg' }"
data-testid="project-edit-button"
@click="isEditOpen = true"
/>
<UButton
@@ -124,6 +127,7 @@
variant="soft"
size="sm"
:ui="{ base: 'rounded-lg' }"
data-testid="project-delete-button"
@click="isDeleteOpen = true"
/>
</div>
+3 -2
View File
@@ -21,7 +21,7 @@ export default class LocalStorage {
this.axios = options.axios
this.getToken().then(async (token) => {
this.isLoggedIn = !!token
this.userStore = useUserStore()
this.userStore ??= useUserStore()
this.userStore.loading = false
await this.updateUserStoreByToken()
})
@@ -97,7 +97,8 @@ export default class LocalStorage {
async updateUserStoreByToken() {
const accessToken = await this.getToken()
if (accessToken && this.userStore) {
this.userStore ??= useUserStore()
if (accessToken) {
this.userStore.setAccessToken(accessToken)
const refreshToken = await this.getRefreshToken()
if (refreshToken) {
+1
View File
@@ -31,6 +31,7 @@ import { useI18n } from 'vue-i18n'
import ConnectionStatusBanner from '@/components/ConnectionStatusBanner.vue'
import DashboardSidebarFirst from '@/components/sidebars/DashboardSidebarFirst.vue'
import DashboardSidebarSecond from '@/components/sidebars/DashboardSidebarSecond.vue'
import TvMobileBottomNav from '@/components/features/base/TvMobileBottomNav.vue'
import SearchAll from '@/components/features/main/screen-main/parts/SearchAll.vue'
import { useCentrifugo } from '@/composables/useCentrifugo'
import { usePushNotifications } from '@/composables/usePushNotifications'
+2
View File
@@ -11,6 +11,8 @@
import { watch } from 'vue'
import { useRoute } from 'vue-router'
import MainScreen from '@/components/features/main/MainScreen.vue'
import ListsContainer from '@/components/features/lists/ListsContainer.vue'
import TasksContainer from '@/components/features/tasks/TasksContainer.vue'
import { useAppRouteInfo } from '@/composables/useAppRouteInfo'
import { useProjectDataLoader } from '@/composables/useProjectDataLoader'
import TvTaskDetailOverlay from '@/components/features/tasks/TvTaskDetailOverlay.vue'
+3 -2
View File
@@ -132,14 +132,15 @@ export const useTasksStore = defineStore('tasks', {
if (this.selectedTask && this.selectedTask !== mainTask && this.selectedTask.id === task.parentId) {
this.selectedTask.subtasks = mainTask.subtasks
}
} else {
} else if (!this.tasks.some((existing) => existing.id === task.id)) {
this.tasks.unshift(task)
}
return task
},
appendTasks(items: Task[]) {
this.tasks = [...this.tasks, ...items]
const existingIds = new Set(this.tasks.map((task) => task.id))
this.tasks = [...this.tasks, ...items.filter((task) => !existingIds.has(task.id))]
},
async fetchAllTasks(goalId: number, showCompleted: 0 | 1 = 1, firstNew: 0 | 1 = 1, ignoreCompleted: boolean = false) {