mirror of
https://github.com/Gimanh/taskview-community.git
synced 2026-09-11 13:29:17 +00:00
tests: add attrs
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user