refac: stale test entries, failing web tests, Postgre backup bug, Hardcoded MySQL table count, Stale Postgre connection in test, own-scope visibility filter, GetAll ignoring its error, JSON tags in use-case types, 31 copies of DESKTOP_QUERY, ProfileView endpoint paths

This commit is contained in:
Florian Sylvain committed 2026-10-28 17:54:10 +01:00
1 parent efecdee200
commit 5d6f378fce
87 files changed
+696 -373

No files matched your search

+2 -4
View File
@@ -3,7 +3,6 @@ import { t } from '@/i18n'
import { computed, nextTick, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import { useMediaQuery } from '@vueuse/core'
import ActivityCardList from '@/components/ActivityCardList.vue'
import ActivityChips from '@/components/ActivityChips.vue'
import ActivityDetailsPanel from '@/components/ActivityDetailsPanel.vue'
@@ -21,12 +20,11 @@ import { useActivityPage } from '@/composables/useActivityPage'
import { useSessionStore } from '@/stores/session'
import type { ActivityFilters } from '@/lib/activity'
import type { StripKey } from '@/lib/activitySummary'
const DESKTOP_QUERY = '(min-width: 48rem)'
import { useIsDesktop } from '@/composables/useIsDesktop'
const router = useRouter()
const session = useSessionStore()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const activity = useActivityPage()
const {
filters,
+2 -3
View File
@@ -2,7 +2,6 @@
import { t } from '@/i18n'
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { useMediaQuery } from '@vueuse/core'
import { useLocalizedOverlay, useLocalizedToast } from '@/i18n/presentation'
import ApiKeyCardList from '@/components/ApiKeyCardList.vue'
import ApiKeyReveal from '@/components/ApiKeyReveal.vue'
@@ -16,8 +15,8 @@ import { toastsAboveBulkBar } from '@/composables/useToastOffset'
import { displayPrefix } from '@/lib/apiKeys'
import { useSessionStore } from '@/stores/session'
import { usePermissionsStore } from '@/stores/permissions'
import { useIsDesktop } from '@/composables/useIsDesktop'
const DESKTOP_QUERY = '(min-width: 48rem)'
const AUTH_HANDLED_STATUSES = [401, 403]
const NOT_FOUND_STATUS = 404
const REVOKE_FAILED_COPY = "Couldn't revoke the key. Try again in a moment."
@@ -30,7 +29,7 @@ const overlay = useLocalizedOverlay()
const session = useSessionStore()
const permissions = usePermissionsStore()
const { listApiKeys, revokeApiKey } = useApiKeys()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const loadState = ref<LoadState>('loading')
const keys = ref<ApiKey[]>([])
+2 -3
View File
@@ -3,7 +3,6 @@ import { t, formatNumber } from '@/i18n'
import { computed, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { useMediaQuery } from '@vueuse/core'
import { useLocalizedOverlay } from '@/i18n/presentation'
import AuthorAvatar from '@/components/AuthorAvatar.vue'
import AuthorFormPanel from '@/components/AuthorFormPanel.vue'
@@ -31,8 +30,8 @@ import { classifyFailure } from '@/lib/directoryErrors'
import type { DirectoryMenuItem } from '@/lib/directoryMenu'
import { useSessionStore } from '@/stores/session'
import { usePermissionsStore } from '@/stores/permissions'
import { useIsDesktop } from '@/composables/useIsDesktop'
const DESKTOP_QUERY = '(min-width: 48rem)'
const LIST_COLUMNS = 'minmax(0,1.4fr) minmax(0,1fr) 110px 100px 44px'
const INITIAL_SKELETON_ROWS = 6
const NOBODY = 'none'
@@ -49,7 +48,7 @@ const permissions = usePermissionsStore()
const overlay = useLocalizedOverlay()
const toasts = useDirectoryToasts()
const { rememberOpener, restoreFocus, leavePanel, followEditLink } = useDirectoryView()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const { listAuthors, listAuthorPage, listAllAuthors, deleteAuthor } = useAuthors()
const removal = useDirectoryDelete<Author>()
+2 -3
View File
@@ -2,7 +2,6 @@
import { formatNumber, t } from '@/i18n'
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
import { useMediaQuery } from '@vueuse/core'
import { useLocalizedOverlay, useLocalizedToast } from '@/i18n/presentation'
import ConfirmDialog from '@/components/ConfirmDialog.vue'
import DashboardShell from '@/components/DashboardShell.vue'
@@ -27,8 +26,8 @@ import { formatVariantSize } from '@/lib/variants/variantFormat'
import { useSessionStore } from '@/stores/session'
import { usePermissionsStore } from '@/stores/permissions'
import { useVariantCacheStore } from '@/stores/variantCache'
import { useIsDesktop } from '@/composables/useIsDesktop'
const DESKTOP_QUERY = '(min-width: 40rem)'
const SKELETON_DELAY_MS = 150
const NOT_FOUND = 404
const CONFIRM_WORD = 'purge'
@@ -39,7 +38,7 @@ const store = useVariantCacheStore()
const overlay = useLocalizedOverlay()
const toast = useLocalizedToast()
const { announceFailure, announceSuccess } = useVariantToasts()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop('40rem')
const showSkeleton = ref(false)
const purgeUnusedButton = ref<{ $el: HTMLElement } | null>(null)
+3 -3
View File
@@ -3,7 +3,7 @@ import { t } from '@/i18n'
import { computed, nextTick, onBeforeUnmount, provide, ref, watch } from 'vue'
import { useRoute, type LocationQuery } from 'vue-router'
import { useEventListener, useMediaQuery, useOnline } from '@vueuse/core'
import { useEventListener, useOnline } from '@vueuse/core'
import { useLocalizedToast } from '@/i18n/presentation'
import AppPaginator from '@/components/AppPaginator.vue'
import BulkDeleteMediaDialog from '@/components/BulkDeleteMediaDialog.vue'
@@ -51,8 +51,8 @@ import { pageCountFor } from '@/lib/pagination'
import { purgedToast } from '@/lib/mediaPurge'
import { RATE_LIMITED_TEXT, RATE_LIMITED_TITLE, UPLOAD_ACCEPT } from '@/lib/mediaUploadRows'
import { usePermissionsStore } from '@/stores/permissions'
import { useIsDesktop } from '@/composables/useIsDesktop'
const DESKTOP_QUERY = '(min-width: 48rem)'
const MEDIA_PATH = '/media'
const TOAST_DURATION_MS = 5000
const PERSISTENT_TOAST = 0
@@ -84,7 +84,7 @@ const permissions = usePermissionsStore()
const media = useMedia()
const selection = useMediaSelection(media.items)
const upload = useMediaUpload({ onAdded: refreshInBackground })
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const online = useOnline()
function differsOnlyByPage(query: MediaControls, current: MediaControls) {
+5 -12
View File
@@ -10,7 +10,8 @@ import ConfirmDialog from '@/components/ConfirmDialog.vue'
import DashboardShell from '@/components/DashboardShell.vue'
import ProfileCard from '@/components/ProfileCard.vue'
import YourRoleCard from '@/components/YourRoleCard.vue'
import { ApiError, authRedirectPending, useApi } from '@/composables/useApi'
import { ApiError, authRedirectPending } from '@/composables/useApi'
import { useProfile } from '@/composables/useProfile'
import { useRetryAfter } from '@/composables/useRetryAfter'
import { usePermissionsStore } from '@/stores/permissions'
import { useSessionStore, type Session } from '@/stores/session'
@@ -36,7 +37,7 @@ const session = useSessionStore()
const permissions = usePermissionsStore()
const toast = useLocalizedToast()
const overlay = useLocalizedOverlay()
const { request } = useApi()
const { updateDisplayName, sendPasswordReset } = useProfile()
const retryAfter = useRetryAfter()
const account = ref<Session | null>(session.session)
@@ -189,11 +190,7 @@ async function onSave() {
saveFailed.value = false
serverLimitError.value = false
try {
const updated = await request<{ display_name?: string | null }>('/v1/user/me', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ display_name: draft.value }),
})
const updated = await updateDisplayName(draft.value)
const displayName = updated.display_name ?? ''
session.setDisplayName(displayName)
field.value = displayName
@@ -223,11 +220,7 @@ async function onSendReset() {
}
resetState.value = 'sending'
try {
await request('/v1/auth/password-reset', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: email.value }),
})
await sendPasswordReset(email.value)
resetState.value = 'sent'
} catch (error) {
if (error instanceof ApiError && error.status === 429) {
+2 -3
View File
@@ -1,7 +1,6 @@
<script setup lang="ts">
import { t } from '@/i18n'
import { computed, onMounted, ref } from 'vue'
import { useMediaQuery } from '@vueuse/core'
import DashboardShell from '@/components/DashboardShell.vue'
import PermissionGrid from '@/components/PermissionGrid.vue'
import PermissionGroup from '@/components/PermissionGroup.vue'
@@ -14,12 +13,12 @@ import { useLocalizedOverlay } from '@/i18n/presentation'
import { useRoleEditor } from '@/composables/useRoleEditor'
import type { CallerScope } from '@/lib/grantLocks'
import { usePermissionsStore } from '@/stores/permissions'
import { useIsDesktop } from '@/composables/useIsDesktop'
const DESKTOP_QUERY = '(min-width: 64rem)'
const CONTENT_GROUP = 'content'
const permissions = usePermissionsStore()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop('64rem')
const editor = useRoleEditor()
const overlay = useLocalizedOverlay()
+2 -4
View File
@@ -2,7 +2,6 @@
import { t } from '@/i18n'
import { computed, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { useMediaQuery } from '@vueuse/core'
import { useLocalizedToast } from '@/i18n/presentation'
import DashboardShell from '@/components/DashboardShell.vue'
import DeleteRoleDialog from '@/components/DeleteRoleDialog.vue'
@@ -14,14 +13,13 @@ import { useRoles } from '@/composables/useRoles'
import { canCover } from '@/lib/grantLocks'
import type { RoleItem } from '@/lib/roles'
import { usePermissionsStore } from '@/stores/permissions'
const DESKTOP_QUERY = '(min-width: 48rem)'
import { useIsDesktop } from '@/composables/useIsDesktop'
const router = useRouter()
const toast = useLocalizedToast()
const permissions = usePermissionsStore()
const { listRoles } = useRoles()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const skeleton = useDelayedSkeleton()
const loadState = ref<LoadState>('loading')
+2 -3
View File
@@ -4,7 +4,6 @@ import { t } from '@/i18n'
import { computed } from 'vue'
import { useRouter } from 'vue-router'
import { usePermissionsStore } from '@/stores/permissions'
import { useMediaQuery } from '@vueuse/core'
import DashboardShell from '@/components/DashboardShell.vue'
import DirectoryAddButton from '@/components/DirectoryAddButton.vue'
import DirectoryLoadError from '@/components/DirectoryLoadError.vue'
@@ -13,13 +12,13 @@ import NoAccessState from '@/components/NoAccessState.vue'
import SchemaTable from '@/components/SchemaTable.vue'
import { useSchemaGate } from '@/composables/useSchemaGate'
import { builtinRow, customRow, schemaSummary } from '@/lib/schemaRows'
import { useIsDesktop } from '@/composables/useIsDesktop'
const DESKTOP_QUERY = '(min-width: 48rem)'
const NEW_TYPE_PATH = '/schema/new'
const router = useRouter()
const permissions = usePermissionsStore()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const {
sessionReady,
+2 -3
View File
@@ -3,7 +3,6 @@ import { t, formatNumber } from '@/i18n'
import { computed, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { useMediaQuery } from '@vueuse/core'
import { useLocalizedOverlay } from '@/i18n/presentation'
import ConfirmDialog from '@/components/ConfirmDialog.vue'
import DashboardShell from '@/components/DashboardShell.vue'
@@ -29,8 +28,8 @@ import { classifyFailure } from '@/lib/directoryErrors'
import type { DirectoryMenuItem } from '@/lib/directoryMenu'
import { useSessionStore } from '@/stores/session'
import { usePermissionsStore } from '@/stores/permissions'
import { useIsDesktop } from '@/composables/useIsDesktop'
const DESKTOP_QUERY = '(min-width: 48rem)'
const LIST_COLUMNS = 'minmax(0,1.1fr) minmax(0,1.5fr) 110px 44px'
const INITIAL_SKELETON_ROWS = 3
@@ -42,7 +41,7 @@ const permissions = usePermissionsStore()
const overlay = useLocalizedOverlay()
const toasts = useDirectoryToasts()
const { rememberOpener, restoreFocus, leavePanel, followEditLink } = useDirectoryView()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const { listAllSites, listSitePage, setDefaultSite, deleteSite } = useSites()
const removal = useDirectoryDelete<Site>()
+2 -3
View File
@@ -2,7 +2,6 @@
import { t } from '@/i18n'
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
import { useMediaQuery } from '@vueuse/core'
import ChangeRoleDialog from '@/components/ChangeRoleDialog.vue'
import DashboardShell from '@/components/DashboardShell.vue'
import InviteUserModal from '@/components/InviteUserModal.vue'
@@ -15,16 +14,16 @@ import type { AcceptedUser, UserListItem } from '@/lib/users'
import type { UserMenuContext } from '@/lib/userMenu'
import { useSessionStore } from '@/stores/session'
import { usePermissionsStore } from '@/stores/permissions'
import { useIsDesktop } from '@/composables/useIsDesktop'
const SKELETON_DELAY_MS = 150
const DESKTOP_QUERY = '(min-width: 48rem)'
type LoadState = 'loading' | 'ready' | 'error'
const session = useSessionStore()
const permissions = usePermissionsStore()
const { listUsers } = useUsers()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const loadState = ref<LoadState>('loading')
const items = ref<UserListItem[]>([])
+2 -3
View File
@@ -2,7 +2,6 @@
import { formatNumber, t } from '@/i18n'
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
import { useMediaQuery } from '@vueuse/core'
import { useLocalizedOverlay } from '@/i18n/presentation'
import ConfirmDialog from '@/components/ConfirmDialog.vue'
import DashboardShell from '@/components/DashboardShell.vue'
@@ -30,8 +29,8 @@ import { formatVariantSize } from '@/lib/variants/variantFormat'
import { useImageVariantsStore } from '@/stores/imageVariants'
import { useSessionStore } from '@/stores/session'
import { usePermissionsStore } from '@/stores/permissions'
import { useIsDesktop } from '@/composables/useIsDesktop'
const DESKTOP_QUERY = '(min-width: 40rem)'
const SKELETON_DELAY_MS = 150
const NOT_FOUND = 404
@@ -46,7 +45,7 @@ const permissions = usePermissionsStore()
const store = useImageVariantsStore()
const overlay = useLocalizedOverlay()
const { announceFailure, announceSuccess } = useVariantToasts()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop('40rem')
const showSkeleton = ref(false)
const formTarget = ref<FormTarget | null>(null)
@@ -85,7 +85,7 @@ describe('Single relation, empty, focused search', () => {
await typeSearch('venue', 'rue ')
await settleSearch(vi)
expect(searches()).toEqual(['/v1/content-type/venues/entry?q=rue&page=1&size=10'])
expect(searches()).toEqual(['/v1/content-type/venues/entry?q=rue&page=1&size=25'])
expect(optionLabels()).toEqual([
'Rue Verte hall Lyon · 120 seats',
'Rue Verte yard Lyon · outdoor',
@@ -103,7 +103,7 @@ describe('Single relation, empty, focused search', () => {
await openList('venue', vi)
expect(searches()).toEqual(['/v1/content-type/venues/entry?page=1&size=10'])
expect(searches()).toEqual(['/v1/content-type/venues/entry?page=1&size=25'])
expect(optionLabels()).toHaveLength(4)
})
@@ -82,7 +82,7 @@ describe('No match', () => {
await flushPromises()
await settleSearch(vi)
expect(relationInput('venue').value).toBe('')
expect(searches().at(-1)).toBe('/v1/content-type/venues/entry?page=1&size=10')
expect(searches().at(-1)).toBe('/v1/content-type/venues/entry?page=1&size=25')
})
it('says the type has no entries at all', async () => {
@@ -123,7 +123,7 @@ describe('Search failures', () => {
await flushPromises()
expect(calls.slice(before)).toEqual([
'/v1/content-type/venues/entry?q=rue&page=1&size=10',
'/v1/content-type/venues/entry?q=rue&page=1&size=25',
])
expect(optionLabels()).toHaveLength(3)
},
@@ -175,7 +175,7 @@ describe('Load more', () => {
button('Load more')!.click()
await flushPromises()
expect(searches().at(-1)).toBe('/v1/content-type/venues/entry?page=2&size=10')
expect(searches().at(-1)).toBe('/v1/content-type/venues/entry?page=2&size=25')
expect(optionLabels()).toHaveLength(12)
expect(button('Load more')).toBeUndefined()
})
@@ -652,8 +652,7 @@ describe('Errors', () => {
'The expiry must be a date in the future.',
'You must be logged in to access this resource.',
'This action needs a real session.',
'Only owners and admins can do this.',
'Only the owner can do this.',
'missing_permission',
'You must verify your account to access this resource.',
'Article not found',
'Image not found',
@@ -697,8 +696,7 @@ describe('Errors table rows of the custom content types round', () => {
const order = [
'This action needs a real session.',
'Only owners and admins can do this.',
'Only the owner can do this.',
'missing_permission',
'You must verify your account to access this resource.',
].map((message) => text.indexOf(message))
expect(order.every((position) => position > -1)).toBe(true)
@@ -909,7 +907,7 @@ describe('Expanded cards of the custom content types round', () => {
[
'POST /v1/content-type/',
['key', 'name', 'icon', 'fields'],
['400', '401', 'Only owners and admins can do this.', 'content_type_key_taken'],
['400', '401', '403', 'content_type_key_taken'],
],
[
'PUT /v1/content-type/{key}',
@@ -1134,7 +1132,7 @@ describe('Authentication copy of the custom content types round', () => {
"Requests act as the user who created the key, with that user's role (owner or admin).",
)
expect(auth).toContain(
'Owner and admin routes answer 403 to a key whose creator is neither.',
'Routes that need a permission answer 403 "missing_permission" to a key when the role of its creator does not hold it.',
)
})
})
@@ -187,7 +187,7 @@ describe('Search and sharing group', () => {
const wrapper = await mountIntegration()
const forbidden = ERROR_ROWS.find((row) => row.status === 403)!
expect(forbidden.messages).toHaveLength(4)
expect(forbidden.messages).toHaveLength(3)
expect(forbidden.note).toBe(NOTE)
const notes = wrapper.findAll('[data-testid="error-note"]').map((item) => item.text())
expect(notes.filter((item) => item === NOTE)).toHaveLength(2)