mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
fix(hub,web): harden notification preference updates
This commit is contained in:
@@ -63,6 +63,16 @@ describe('PUT /api/notification-preferences', () => {
|
||||
expect(res.status).toBe(400)
|
||||
})
|
||||
|
||||
it('rejects fractional preference values', async () => {
|
||||
const app = createApp()
|
||||
const res = await app.request('/api/notification-preferences', {
|
||||
method: 'PUT',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ permissionRequests: 0.5 })
|
||||
})
|
||||
expect(res.status).toBe(400)
|
||||
})
|
||||
|
||||
it('rejects invalid bodies', async () => {
|
||||
const app = createApp()
|
||||
const res = await app.request('/api/notification-preferences', {
|
||||
|
||||
@@ -4,10 +4,10 @@ import type { Store } from '../../store'
|
||||
import type { WebAppEnv } from '../middleware/auth'
|
||||
|
||||
const updateSchema = z.object({
|
||||
permissionRequests: z.number().min(0).max(1).optional(),
|
||||
sessionReady: z.number().min(0).max(1).optional(),
|
||||
taskNotifications: z.number().min(0).max(1).optional(),
|
||||
sessionCompletion: z.number().min(0).max(1).optional()
|
||||
permissionRequests: z.number().int().min(0).max(1).optional(),
|
||||
sessionReady: z.number().int().min(0).max(1).optional(),
|
||||
taskNotifications: z.number().int().min(0).max(1).optional(),
|
||||
sessionCompletion: z.number().int().min(0).max(1).optional()
|
||||
})
|
||||
|
||||
export function createNotificationPreferencesRoutes(store: Store): Hono<WebAppEnv> {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { I18nProvider } from '@/lib/i18n-context'
|
||||
import { queryKeys } from '@/lib/query-keys'
|
||||
import SettingsNotificationsPage from './notifications'
|
||||
|
||||
const defaultPrefs = {
|
||||
@@ -66,6 +67,7 @@ describe('SettingsNotificationsPage', () => {
|
||||
</I18nProvider>
|
||||
</QueryClientProvider>,
|
||||
)
|
||||
return queryClient
|
||||
}
|
||||
|
||||
it('renders all four toggles from server preferences', async () => {
|
||||
@@ -105,6 +107,16 @@ describe('SettingsNotificationsPage', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps the confirmation open when disabling permission notifications fails', async () => {
|
||||
updateNotificationPreferences.mockRejectedValueOnce(new Error('save failed'))
|
||||
renderPage()
|
||||
fireEvent.click(await screen.findByLabelText('Permission requests'))
|
||||
fireEvent.click(screen.getByText('Turn off anyway'))
|
||||
|
||||
expect(await screen.findByText('save failed')).toBeTruthy()
|
||||
expect(screen.getByText('Turn off permission request notifications?')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('sends a test push and reports success', async () => {
|
||||
renderPage()
|
||||
const button = await screen.findByRole('button', { name: 'Send test push' })
|
||||
@@ -165,6 +177,29 @@ describe('SettingsNotificationsPage', () => {
|
||||
expect(await screen.findByText('Failed to save notification copy')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('accepts refreshed copy after a successful save', async () => {
|
||||
updateNotificationCopy.mockResolvedValueOnce({
|
||||
...defaultCopyResponse,
|
||||
copy: { ready: { title: 'Saved title', body: 'Saved body' } }
|
||||
})
|
||||
const queryClient = renderPage()
|
||||
const readyRow = await screen.findByRole('button', { name: /Session ready.*Ready for input/ })
|
||||
fireEvent.click(readyRow)
|
||||
fireEvent.change(screen.getByLabelText('Title'), { target: { value: 'Saved title' } })
|
||||
fireEvent.change(screen.getByLabelText('Body'), { target: { value: 'Saved body' } })
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Save copy' }))
|
||||
expect(await screen.findByText('Copy saved')).toBeTruthy()
|
||||
|
||||
act(() => {
|
||||
queryClient.setQueryData(queryKeys.notificationCopy, {
|
||||
...defaultCopyResponse,
|
||||
copy: { ready: { title: 'Remote title', body: 'Remote body' } }
|
||||
})
|
||||
})
|
||||
|
||||
expect(await screen.findByRole('button', { name: /Session ready.*Remote title.*Remote body/ })).toBeTruthy()
|
||||
})
|
||||
|
||||
it('keeps copy editors collapsed and prefills defaults when opened', async () => {
|
||||
renderPage()
|
||||
const readyRow = await screen.findByRole('button', { name: /Session ready.*Ready for input/ })
|
||||
|
||||
@@ -147,6 +147,7 @@ export default function SettingsNotificationsPage() {
|
||||
setCopySaveError(null)
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
userEditedRef.current = false
|
||||
queryClient.setQueryData(queryKeys.notificationCopy, data)
|
||||
setDraft(resolveEffectiveCopy(data.copy, data.defaults))
|
||||
setCopySaveError(null)
|
||||
@@ -336,7 +337,7 @@ export default function SettingsNotificationsPage() {
|
||||
confirmLabel={t('settings.notifications.disablePermissionConfirm')}
|
||||
confirmingLabel={t('settings.notifications.disablePermissionConfirm')}
|
||||
onConfirm={async () => {
|
||||
mutation.mutate({ permissionRequests: 0 })
|
||||
await mutation.mutateAsync({ permissionRequests: 0 })
|
||||
}}
|
||||
isPending={mutation.isPending}
|
||||
destructive
|
||||
|
||||
Reference in New Issue
Block a user