Merge 150940291c into 076cd214fe
This commit is contained in:
commit
5b4eb91acc
|
|
@ -4,16 +4,17 @@
|
|||
aria-label="main navigation"
|
||||
class="navbar d-print-none"
|
||||
>
|
||||
<RouterLink
|
||||
:to="{ name: 'home' }"
|
||||
<a
|
||||
href="/"
|
||||
class="logo-link"
|
||||
:aria-label="$t('navigation.home')"
|
||||
@click.prevent="openLandingPage"
|
||||
>
|
||||
<Logo
|
||||
width="164"
|
||||
height="48"
|
||||
/>
|
||||
</RouterLink>
|
||||
</a>
|
||||
|
||||
<MenuButton class="menu-button" />
|
||||
|
||||
|
|
@ -129,7 +130,7 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { PERMISSIONS as Permissions } from '@/constants/permissions'
|
||||
|
|
@ -147,6 +148,7 @@ import OpenQuickActions from '@/components/misc/OpenQuickActions.vue'
|
|||
|
||||
import { getProjectTitle } from '@/helpers/getProjectTitle'
|
||||
import { isEditorContentEmpty } from '@/helpers/editorContentEmpty'
|
||||
import { getDefaultPageRoute } from '@/helpers/getDefaultPageRoute'
|
||||
|
||||
import { useBaseStore } from '@/stores/base'
|
||||
import { useConfigStore } from '@/stores/config'
|
||||
|
|
@ -165,6 +167,7 @@ const menuActive = computed(() => baseStore.menuActive)
|
|||
|
||||
// Standalone pages (no project) surface their route's title in the header.
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { t } = useI18n()
|
||||
const pageTitle = computed(() => {
|
||||
const title = route.meta.title as string | undefined
|
||||
|
|
@ -177,6 +180,10 @@ const configStore = useConfigStore()
|
|||
const imprintUrl = computed(() => configStore.legal.imprintUrl)
|
||||
const privacyPolicyUrl = computed(() => configStore.legal.privacyPolicyUrl)
|
||||
const adminPanelEnabled = computed(() => configStore.isProFeatureEnabled(PRO_FEATURE.ADMIN_PANEL))
|
||||
|
||||
async function openLandingPage() {
|
||||
await router.push(await getDefaultPageRoute() ?? {name: 'home'})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
|||
|
|
@ -5,16 +5,17 @@
|
|||
:style="{'--sidebar-width': sidebarWidth}"
|
||||
>
|
||||
<nav class="menu top-menu">
|
||||
<RouterLink
|
||||
:to="{name: 'home'}"
|
||||
<a
|
||||
href="/"
|
||||
class="logo"
|
||||
:aria-label="$t('navigation.home')"
|
||||
@click.prevent="openLandingPage"
|
||||
>
|
||||
<Logo
|
||||
width="164"
|
||||
height="48"
|
||||
/>
|
||||
</RouterLink>
|
||||
</a>
|
||||
<menu class="menu-list other-menu-items">
|
||||
<li>
|
||||
<RouterLink
|
||||
|
|
@ -134,6 +135,7 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import {computed} from 'vue'
|
||||
import {useRouter} from 'vue-router'
|
||||
|
||||
import PoweredByLink from '@/components/home/PoweredByLink.vue'
|
||||
import Logo from '@/components/home/Logo.vue'
|
||||
|
|
@ -146,10 +148,12 @@ import {PRO_FEATURE} from '@/constants/proFeatures'
|
|||
import ProjectsNavigation from '@/components/home/ProjectsNavigation.vue'
|
||||
import type {IProject} from '@/modelTypes/IProject'
|
||||
import {useSidebarResize} from '@/composables/useSidebarResize'
|
||||
import {getDefaultPageRoute} from '@/helpers/getDefaultPageRoute'
|
||||
|
||||
const baseStore = useBaseStore()
|
||||
const projectStore = useProjectStore()
|
||||
const configStore = useConfigStore()
|
||||
const router = useRouter()
|
||||
|
||||
const timeTrackingEnabled = computed(() => configStore.isProFeatureEnabled(PRO_FEATURE.TIME_TRACKING))
|
||||
|
||||
|
|
@ -159,6 +163,10 @@ const {sidebarWidth, isResizing, startResize, isMobile} = useSidebarResize()
|
|||
const projects = computed(() => projectStore.notArchivedRootProjects as IProject[])
|
||||
const favoriteProjects = computed(() => projectStore.favoriteProjects as IProject[])
|
||||
const savedFilterProjects = computed(() => projectStore.savedFilterProjects as IProject[])
|
||||
|
||||
async function openLandingPage() {
|
||||
await router.push(await getDefaultPageRoute() ?? {name: 'home'})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import {useRouter} from 'vue-router'
|
||||
|
||||
import {getDefaultPageRoute} from '@/helpers/getDefaultPageRoute'
|
||||
import {getLastVisited, clearLastVisited} from '@/helpers/saveLastVisited'
|
||||
|
||||
export function useRedirectToLastVisited() {
|
||||
|
|
@ -19,10 +21,10 @@ export function useRedirectToLastVisited() {
|
|||
}
|
||||
}
|
||||
|
||||
function redirectIfSaved() {
|
||||
async function redirectIfSaved() {
|
||||
const lastRoute = getLastVisitedRoute()
|
||||
if (!lastRoute) {
|
||||
return router.push({name: 'home'})
|
||||
return router.push(await getDefaultPageRoute() ?? {name: 'home'})
|
||||
}
|
||||
|
||||
return router.push(lastRoute)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
export const DEFAULT_PAGE = {
|
||||
OVERVIEW: 'overview',
|
||||
UPCOMING: 'upcoming',
|
||||
DEFAULT_PROJECT: 'defaultProject',
|
||||
LAST_VISITED: 'lastVisited',
|
||||
} as const
|
||||
|
||||
export type DefaultPage = typeof DEFAULT_PAGE[keyof typeof DEFAULT_PAGE]
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import type {RouteLocationRaw} from 'vue-router'
|
||||
|
||||
import {DEFAULT_PAGE} from '@/constants/defaultPage'
|
||||
import {getLastVisitedPage} from '@/helpers/saveLastVisited'
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
import {useProjectStore} from '@/stores/projects'
|
||||
|
||||
export async function getDefaultPageRoute(): Promise<RouteLocationRaw | undefined> {
|
||||
const authStore = useAuthStore()
|
||||
const projectStore = useProjectStore()
|
||||
const defaultPage = authStore.settings?.frontendSettings?.defaultPage
|
||||
|
||||
switch (defaultPage) {
|
||||
case DEFAULT_PAGE.UPCOMING:
|
||||
return {name: 'tasks.range'}
|
||||
case DEFAULT_PAGE.DEFAULT_PROJECT: {
|
||||
const projectId = authStore.settings?.defaultProjectId
|
||||
if (projectId) {
|
||||
try {
|
||||
await projectStore.loadProject(projectId)
|
||||
return {name: 'project.index', params: {projectId}}
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
case DEFAULT_PAGE.LAST_VISITED: {
|
||||
const last = getLastVisitedPage()
|
||||
if (last) {
|
||||
return {name: last.name, params: last.params, query: last.query}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
const LAST_VISITED_KEY = 'lastVisited'
|
||||
const LAST_VISITED_PAGE_KEY = 'lastVisitedPage'
|
||||
|
||||
export const saveLastVisited = (name: string | undefined, params: object, query: object) => {
|
||||
if (typeof name === 'undefined') {
|
||||
|
|
@ -20,3 +21,20 @@ export const getLastVisited = () => {
|
|||
export const clearLastVisited = () => {
|
||||
return localStorage.removeItem(LAST_VISITED_KEY)
|
||||
}
|
||||
|
||||
export const saveLastVisitedPage = (name: string | undefined, params: object, query: object) => {
|
||||
if (typeof name === 'undefined') {
|
||||
return
|
||||
}
|
||||
|
||||
localStorage.setItem(LAST_VISITED_PAGE_KEY, JSON.stringify({name, params, query}))
|
||||
}
|
||||
|
||||
export const getLastVisitedPage = () => {
|
||||
const lastVisited = localStorage.getItem(LAST_VISITED_PAGE_KEY)
|
||||
if (lastVisited === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return JSON.parse(lastVisited)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -177,6 +177,13 @@
|
|||
"12h": "12-hour (AM/PM)",
|
||||
"24h": "24-hour (HH:mm)"
|
||||
},
|
||||
"defaultPage": "Default page",
|
||||
"defaultPageOptions": {
|
||||
"overview": "Overview",
|
||||
"upcoming": "Upcoming",
|
||||
"defaultProject": "Default project",
|
||||
"lastVisited": "Last visited"
|
||||
},
|
||||
"externalUserNameChange": "Your name is managed by your login provider ({provider}). To change it, please update it there instead."
|
||||
},
|
||||
"sections": {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import type {Priority} from '@/constants/priorities'
|
|||
import type {DateDisplay} from '@/constants/dateDisplay'
|
||||
import type {TimeFormat} from '@/constants/timeFormat'
|
||||
import type {IRelationKind} from '@/types/IRelationKind'
|
||||
import type {DefaultPage} from '@/constants/defaultPage'
|
||||
|
||||
export interface IFrontendSettings {
|
||||
playSoundWhenDone: boolean
|
||||
|
|
@ -26,6 +27,7 @@ export interface IFrontendSettings {
|
|||
showLastViewed: boolean
|
||||
sidebarWidth: number | null
|
||||
commentSortOrder: 'asc' | 'desc'
|
||||
defaultPage: DefaultPage
|
||||
desktopQuickEntryShortcut: string
|
||||
quickAddDefaultReminders: ITaskReminder[]
|
||||
timeTrackingDefaultStart?: string
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {PRIORITIES} from '@/constants/priorities'
|
|||
import {DATE_DISPLAY} from '@/constants/dateDisplay'
|
||||
import {TIME_FORMAT} from '@/constants/timeFormat'
|
||||
import {RELATION_KIND} from '@/types/IRelationKind'
|
||||
import {DEFAULT_PAGE} from '@/constants/defaultPage'
|
||||
|
||||
export default class UserSettingsModel extends AbstractModel<IUserSettings> implements IUserSettings {
|
||||
name = ''
|
||||
|
|
@ -36,6 +37,7 @@ export default class UserSettingsModel extends AbstractModel<IUserSettings> impl
|
|||
showLastViewed: true,
|
||||
sidebarWidth: null,
|
||||
commentSortOrder: 'asc',
|
||||
defaultPage: DEFAULT_PAGE.LAST_VISITED,
|
||||
desktopQuickEntryShortcut: 'CmdOrCtrl+Shift+A',
|
||||
quickAddDefaultReminders: [],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import type { RouteLocation } from 'vue-router'
|
||||
import {saveLastVisited} from '@/helpers/saveLastVisited'
|
||||
import {saveLastVisited, saveLastVisitedPage} from '@/helpers/saveLastVisited'
|
||||
import {getDefaultPageRoute} from '@/helpers/getDefaultPageRoute'
|
||||
|
||||
import {getProjectViewId} from '@/helpers/projectView'
|
||||
import {parseDateOrString} from '@/helpers/time/parseDateOrString'
|
||||
|
|
@ -46,6 +47,13 @@ const router = createRouter({
|
|||
path: '/',
|
||||
name: 'home',
|
||||
component: () => import('@/views/Home.vue'),
|
||||
async beforeEnter(_to, from) {
|
||||
if (from.name !== undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
return getDefaultPageRoute()
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
|
|
@ -537,7 +545,7 @@ export async function getAuthForRoute(to: RouteLocation, authStore) {
|
|||
const isValidUserAppRoute = !AUTH_ROUTE_NAMES.has(to.name as string) &&
|
||||
localStorage.getItem('emailConfirmToken') === null
|
||||
|
||||
if (isValidUserAppRoute) {
|
||||
if (isValidUserAppRoute && to.name !== 'home') {
|
||||
saveLastVisited(to.name as string, to.params, to.query)
|
||||
}
|
||||
|
||||
|
|
@ -622,4 +630,10 @@ router.beforeEach(async (to, from) => {
|
|||
}
|
||||
})
|
||||
|
||||
router.afterEach((to) => {
|
||||
if (!AUTH_ROUTE_NAMES.has(to.name as string) && to.name !== 'home') {
|
||||
saveLastVisitedPage(to.name as string, to.params, to.query)
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import {PrefixMode} from '@/modules/quickAddMagic'
|
|||
import {DATE_DISPLAY} from '@/constants/dateDisplay'
|
||||
import {TIME_FORMAT} from '@/constants/timeFormat'
|
||||
import {RELATION_KIND} from '@/types/IRelationKind'
|
||||
import {DEFAULT_PAGE} from '@/constants/defaultPage'
|
||||
import type {IProvider} from '@/types/IProvider'
|
||||
|
||||
// Set on explicit logout so the login page won't immediately bounce the user
|
||||
|
|
@ -159,6 +160,7 @@ export const useAuthStore = defineStore('auth', () => {
|
|||
showLastViewed: true,
|
||||
sidebarWidth: null,
|
||||
commentSortOrder: 'asc',
|
||||
defaultPage: DEFAULT_PAGE.LAST_VISITED,
|
||||
desktopQuickEntryShortcut: 'CmdOrCtrl+Shift+A',
|
||||
...newSettings.frontendSettings,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -38,6 +38,15 @@
|
|||
:loading="loading"
|
||||
>
|
||||
<div class="field-group">
|
||||
<FormField
|
||||
:label="$t('user.settings.general.defaultPage')"
|
||||
layout="two-col"
|
||||
>
|
||||
<FormSelect
|
||||
v-model="settings.frontendSettings.defaultPage"
|
||||
:options="defaultPageOptions"
|
||||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
:label="$t('user.settings.general.defaultView')"
|
||||
layout="two-col"
|
||||
|
|
@ -325,6 +334,7 @@ import {DATE_DISPLAY} from '@/constants/dateDisplay'
|
|||
import {TIME_FORMAT} from '@/constants/timeFormat'
|
||||
import {PRO_FEATURE} from '@/constants/proFeatures'
|
||||
import {RELATION_KINDS} from '@/types/IRelationKind'
|
||||
import {DEFAULT_PAGE} from '@/constants/defaultPage'
|
||||
import {isDesktopApp} from '@/helpers/desktopAuth'
|
||||
import ShortcutRecorder from '@/components/misc/ShortcutRecorder.vue'
|
||||
import Reminders from '@/components/tasks/partials/Reminders.vue'
|
||||
|
|
@ -339,6 +349,13 @@ useTitle(() => `${t('user.settings.general.title')} - ${t('user.settings.title')
|
|||
|
||||
const DEFAULT_PROJECT_ID = 0
|
||||
|
||||
const defaultPageOptions = computed(() => [
|
||||
{value: DEFAULT_PAGE.OVERVIEW, label: t('user.settings.general.defaultPageOptions.overview')},
|
||||
{value: DEFAULT_PAGE.UPCOMING, label: t('user.settings.general.defaultPageOptions.upcoming')},
|
||||
{value: DEFAULT_PAGE.DEFAULT_PROJECT, label: t('user.settings.general.defaultPageOptions.defaultProject')},
|
||||
{value: DEFAULT_PAGE.LAST_VISITED, label: t('user.settings.general.defaultPageOptions.lastVisited')},
|
||||
])
|
||||
|
||||
const defaultViewOptions = computed(() =>
|
||||
Object.values(DEFAULT_PROJECT_VIEW_SETTINGS).map(view => ({
|
||||
value: view,
|
||||
|
|
@ -428,7 +445,7 @@ const settings = ref<IUserSettings>({
|
|||
timeFormat: authStore.settings.frontendSettings.timeFormat ?? TIME_FORMAT.HOURS_12,
|
||||
// Add fallback for old settings that don't have the default task relation type set
|
||||
defaultTaskRelationType: authStore.settings.frontendSettings.defaultTaskRelationType ?? 'related',
|
||||
// Clone to escape the store's readonly array type.
|
||||
defaultPage: authStore.settings.frontendSettings.defaultPage ?? DEFAULT_PAGE.LAST_VISITED,
|
||||
quickAddDefaultReminders: [...(authStore.settings.frontendSettings.quickAddDefaultReminders ?? [])],
|
||||
timeTrackingDefaultStart: authStore.settings.frontendSettings.timeTrackingDefaultStart ?? '09:00',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,129 @@
|
|||
import {test, expect} from '../../support/fixtures'
|
||||
import {ProjectFactory} from '../../factories/project'
|
||||
import {UserFactory} from '../../factories/user'
|
||||
import {TaskFactory} from '../../factories/task'
|
||||
import {login as apiLogin, setupApiUrl} from '../../support/authenticateUser'
|
||||
import {updateUserSettings} from '../../support/updateUserSettings'
|
||||
import {createDefaultViews} from '../project/prepareProjects'
|
||||
import {TEST_PASSWORD} from '../../support/constants'
|
||||
|
||||
test.describe('Default Landing Page', () => {
|
||||
test('shows overview page with default settings when no last visited page exists', async ({authenticatedPage: page}) => {
|
||||
await page.goto('/')
|
||||
await page.waitForLoadState('networkidle')
|
||||
await expect(page).toHaveURL('/')
|
||||
await expect(page.locator('.home.app-content')).toBeVisible()
|
||||
})
|
||||
|
||||
test('redirects to upcoming when set as default page', async ({page, apiContext}) => {
|
||||
const user = (await UserFactory.create(1, {
|
||||
frontend_settings: JSON.stringify({defaultPage: 'upcoming'}),
|
||||
}))[0]
|
||||
await ProjectFactory.create(1, {owner_id: user.id})
|
||||
await apiLogin(page, apiContext, user)
|
||||
|
||||
await page.goto('/')
|
||||
await page.waitForURL('**/tasks/by/upcoming**')
|
||||
})
|
||||
|
||||
test('redirects to default project when set as default page', async ({page, apiContext}) => {
|
||||
const user = (await UserFactory.create(1, {
|
||||
frontend_settings: JSON.stringify({defaultPage: 'defaultProject'}),
|
||||
}))[0]
|
||||
const project = (await ProjectFactory.create(1, {owner_id: user.id}))[0]
|
||||
await createDefaultViews(project.id)
|
||||
|
||||
const {token} = await apiLogin(page, apiContext, user)
|
||||
|
||||
await updateUserSettings(apiContext, token, {
|
||||
default_project_id: project.id,
|
||||
overdue_tasks_reminders_time: '9:00',
|
||||
})
|
||||
|
||||
await page.goto('/')
|
||||
await page.waitForURL(`**/projects/${project.id}/**`)
|
||||
})
|
||||
|
||||
test('redirects to default project after logging in from root', async ({page, apiContext}) => {
|
||||
const user = (await UserFactory.create(1, {
|
||||
frontend_settings: JSON.stringify({defaultPage: 'defaultProject'}),
|
||||
}))[0]
|
||||
const project = (await ProjectFactory.create(1, {owner_id: user.id}))[0]
|
||||
await createDefaultViews(project.id)
|
||||
|
||||
const {token} = await apiLogin(null, apiContext, user)
|
||||
await updateUserSettings(apiContext, token, {
|
||||
default_project_id: project.id,
|
||||
overdue_tasks_reminders_time: '9:00',
|
||||
})
|
||||
|
||||
await setupApiUrl(page)
|
||||
await page.goto('/')
|
||||
await expect(page).toHaveURL(/\/login/)
|
||||
|
||||
await page.locator('input[id=username]').fill(user.username)
|
||||
await page.locator('input[id=password]').fill(TEST_PASSWORD)
|
||||
await page.locator('.button').filter({hasText: 'Login'}).click()
|
||||
|
||||
await page.waitForURL(`**/projects/${project.id}/**`)
|
||||
|
||||
await page.locator('nav.menu.top-menu a').filter({hasText: 'Overview'}).click()
|
||||
await page.waitForLoadState('networkidle')
|
||||
await expect(page).toHaveURL('/')
|
||||
|
||||
await page.locator('.logo-link').click()
|
||||
await page.waitForURL(`**/projects/${project.id}/**`)
|
||||
})
|
||||
|
||||
test('falls back to overview when default project does not exist', async ({page, apiContext}) => {
|
||||
const user = (await UserFactory.create(1, {
|
||||
frontend_settings: JSON.stringify({defaultPage: 'defaultProject'}),
|
||||
}))[0]
|
||||
await ProjectFactory.create(1, {owner_id: user.id})
|
||||
|
||||
const {token} = await apiLogin(page, apiContext, user)
|
||||
|
||||
await updateUserSettings(apiContext, token, {
|
||||
default_project_id: 999999,
|
||||
overdue_tasks_reminders_time: '9:00',
|
||||
})
|
||||
|
||||
await page.goto('/')
|
||||
await page.waitForLoadState('networkidle')
|
||||
await expect(page).toHaveURL('/')
|
||||
})
|
||||
|
||||
test('redirects to last visited page when set as default page', async ({page, apiContext}) => {
|
||||
const user = (await UserFactory.create(1, {
|
||||
frontend_settings: JSON.stringify({defaultPage: 'lastVisited'}),
|
||||
}))[0]
|
||||
const project = (await ProjectFactory.create(1, {owner_id: user.id}))[0]
|
||||
const views = await createDefaultViews(project.id)
|
||||
await TaskFactory.create(1, {project_id: project.id})
|
||||
|
||||
await apiLogin(page, apiContext, user)
|
||||
|
||||
await page.goto(`/projects/${project.id}/${views[0].id}`)
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
await page.goto('/')
|
||||
await page.waitForURL(`**/projects/${project.id}/${views[0].id}`)
|
||||
})
|
||||
|
||||
test('redirects to default page when clicking the logo', async ({page, apiContext}) => {
|
||||
const user = (await UserFactory.create(1, {
|
||||
frontend_settings: JSON.stringify({defaultPage: 'upcoming'}),
|
||||
}))[0]
|
||||
const project = (await ProjectFactory.create(1, {owner_id: user.id}))[0]
|
||||
await createDefaultViews(project.id)
|
||||
await TaskFactory.create(1, {project_id: project.id})
|
||||
|
||||
await apiLogin(page, apiContext, user)
|
||||
|
||||
await page.goto(`/projects/${project.id}/1`)
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
await page.locator('.logo-link').click()
|
||||
await page.waitForURL('**/tasks/by/upcoming**')
|
||||
})
|
||||
})
|
||||
|
|
@ -4,7 +4,16 @@ import {ProjectViewFactory} from '../../factories/project_view'
|
|||
import {updateUserSettings} from '../../support/updateUserSettings'
|
||||
import type {Page} from '@playwright/test'
|
||||
|
||||
async function visitProjectsToBuildHistory(page: Page, projects: any[]) {
|
||||
interface ProjectHistoryEntry {
|
||||
id: number
|
||||
}
|
||||
|
||||
interface ProjectSeed {
|
||||
id: number
|
||||
title: string
|
||||
}
|
||||
|
||||
async function visitProjectsToBuildHistory(page: Page, projects: ProjectSeed[]) {
|
||||
for (const project of projects) {
|
||||
const loadProjectPromise = page.waitForResponse(response =>
|
||||
response.url().includes(`/projects/${project.id}`) && response.request().method() === 'GET',
|
||||
|
|
@ -14,7 +23,7 @@ async function visitProjectsToBuildHistory(page: Page, projects: any[]) {
|
|||
await page.waitForFunction(
|
||||
(projectId) => {
|
||||
const history = JSON.parse(localStorage.getItem('projectHistory') || '[]')
|
||||
return history.some((h: any) => h.id === projectId)
|
||||
return history.some((h: ProjectHistoryEntry) => h.id === projectId)
|
||||
},
|
||||
project.id,
|
||||
)
|
||||
|
|
@ -47,7 +56,7 @@ test.describe('Project History', () => {
|
|||
await page.waitForFunction(
|
||||
(projectId) => {
|
||||
const history = JSON.parse(localStorage.getItem('projectHistory') || '[]')
|
||||
return history.some((h: any) => h.id === projectId)
|
||||
return history.some((h: ProjectHistoryEntry) => h.id === projectId)
|
||||
},
|
||||
projects[i].id,
|
||||
)
|
||||
|
|
@ -76,15 +85,25 @@ test.describe('Project History', () => {
|
|||
}, false)
|
||||
}
|
||||
|
||||
// Keep reloads on the overview page while this test focuses on the
|
||||
// last-viewed section visibility.
|
||||
await page.goto('/')
|
||||
await page.waitForLoadState('networkidle')
|
||||
const token = await page.evaluate(() => localStorage.getItem('token'))
|
||||
await updateUserSettings(apiContext, token!, {
|
||||
frontendSettings: {
|
||||
defaultPage: 'overview',
|
||||
},
|
||||
})
|
||||
|
||||
// Visit projects to build up history
|
||||
await visitProjectsToBuildHistory(page, projects)
|
||||
|
||||
// Go to overview and verify section is visible
|
||||
await page.goto('/')
|
||||
await page.locator('nav.menu.top-menu a').filter({hasText: 'Overview'}).click()
|
||||
await expect(page.locator('body')).toContainText('Last viewed')
|
||||
|
||||
// Disable the setting via API
|
||||
const token = await page.evaluate(() => localStorage.getItem('token'))
|
||||
await updateUserSettings(apiContext, token!, {
|
||||
frontendSettings: {
|
||||
showLastViewed: false,
|
||||
|
|
@ -116,6 +135,7 @@ test.describe('Project History', () => {
|
|||
const token = await page.evaluate(() => localStorage.getItem('token'))
|
||||
await updateUserSettings(apiContext, token!, {
|
||||
frontendSettings: {
|
||||
defaultPage: 'overview',
|
||||
showLastViewed: false,
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -3,14 +3,21 @@ import {TaskFactory} from '../../factories/task'
|
|||
import {ProjectFactory} from '../../factories/project'
|
||||
import {createProjects} from './prepareProjects'
|
||||
|
||||
interface ProjectWithViews {
|
||||
id: number
|
||||
title: string
|
||||
views: Array<{id: number}>
|
||||
}
|
||||
|
||||
test.describe('Projects', () => {
|
||||
test.use({
|
||||
// Use authenticated page for all tests
|
||||
})
|
||||
|
||||
let projects: any[]
|
||||
let projects: ProjectWithViews[]
|
||||
|
||||
test.beforeEach(async ({authenticatedPage}) => {
|
||||
test.beforeEach(async ({authenticatedPage: page}) => {
|
||||
void page
|
||||
projects = await createProjects()
|
||||
})
|
||||
|
||||
|
|
@ -72,7 +79,8 @@ test.describe('Projects', () => {
|
|||
await expect(page.locator('.project-title')).toContainText(newProjectName)
|
||||
await expect(page.locator('.project-title')).not.toContainText(projects[0].title)
|
||||
await expect(page.locator('.menu-container .menu-list').getByRole('listitem').filter({hasText: newProjectName})).toBeVisible()
|
||||
await page.goto('/')
|
||||
await page.locator('nav.menu.top-menu a').filter({hasText: 'Overview'}).click()
|
||||
await page.waitForLoadState('networkidle')
|
||||
await expect(page.locator('.project-grid')).toContainText(newProjectName)
|
||||
await expect(page.locator('.project-grid')).not.toContainText(projects[0].title)
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue