/** * Property-based tests for chat state management * **Feature: miniapp-frontend, Property 22: Unread Badge Display** * **Validates: Requirements 13.5** */ import { describe, it, expect, beforeEach } from 'vitest' import * as fc from 'fast-check' import { setActivePinia, createPinia } from 'pinia' // Import mock before other modules import '../mocks/uni.js' import { useChatStore, MessageType, MessageStatus } from '../../store/chat.js' describe('Chat Store Property Tests', () => { beforeEach(() => { // Create a fresh pinia instance for each test setActivePinia(createPinia()) }) /** * Property 22: Unread Badge Display * *For any* unread message count > 0, the message tab SHALL display a red badge. * **Validates: Requirements 13.5** */ it('Property 22: Unread Badge Display - badge should show when unread count > 0', () => { fc.assert( fc.property( // Generate positive unread counts fc.integer({ min: 1, max: 1000 }), (unreadCount) => { const chatStore = useChatStore() // Use the pure function to test const shouldShow = chatStore.shouldShowUnreadBadge(unreadCount) // Badge should show for any positive count return shouldShow === true } ), { numRuns: 100 } ) }) /** * Property 22 (continued): Badge should NOT show when unread count is 0 */ it('Property 22: Unread Badge Display - badge should not show when unread count is 0', () => { const chatStore = useChatStore() // Use the pure function to test const shouldShow = chatStore.shouldShowUnreadBadge(0) // Badge should not show for zero count expect(shouldShow).toBe(false) }) /** * Property 22 (continued): Badge should NOT show for negative counts */ it('Property 22: Unread Badge Display - badge should not show for negative counts', () => { fc.assert( fc.property( // Generate negative counts (edge case) fc.integer({ min: -1000, max: -1 }), (negativeCount) => { const chatStore = useChatStore() // Use the pure function to test const shouldShow = chatStore.shouldShowUnreadBadge(negativeCount) // Badge should not show for negative counts return shouldShow === false } ), { numRuns: 100 } ) }) /** * Property: Total unread count should equal sum of session unread counts */ it('Total unread count should equal sum of session unread counts', () => { fc.assert( fc.property( // Generate array of sessions with unread counts fc.array( fc.record({ sessionId: fc.integer({ min: 1 }), targetUserId: fc.integer({ min: 1 }), targetNickname: fc.string({ minLength: 1 }), targetAvatar: fc.string(), lastMessage: fc.string(), lastMessageTime: fc.string(), unreadCount: fc.integer({ min: 0, max: 100 }) }), { minLength: 0, maxLength: 10 } ), (sessions) => { const chatStore = useChatStore() // Set sessions chatStore.setSessions(sessions) // Calculate expected total const expectedTotal = sessions.reduce( (sum, s) => sum + (s.unreadCount || 0), 0 ) // Total should match return chatStore.totalUnreadCount === expectedTotal } ), { numRuns: 100 } ) }) /** * Property: hasUnreadMessages getter should match totalUnreadCount > 0 */ it('hasUnreadMessages getter should match totalUnreadCount > 0', () => { fc.assert( fc.property( fc.array( fc.record({ sessionId: fc.integer({ min: 1 }), targetUserId: fc.integer({ min: 1 }), targetNickname: fc.string({ minLength: 1 }), targetAvatar: fc.string(), lastMessage: fc.string(), lastMessageTime: fc.string(), unreadCount: fc.integer({ min: 0, max: 100 }) }), { minLength: 0, maxLength: 10 } ), (sessions) => { const chatStore = useChatStore() // Set sessions chatStore.setSessions(sessions) // hasUnreadMessages should match whether total > 0 const expectedHasUnread = chatStore.totalUnreadCount > 0 return chatStore.hasUnreadMessages === expectedHasUnread } ), { numRuns: 100 } ) }) /** * Property: Marking session as read should decrease total unread count */ it('Marking session as read should decrease total unread count', () => { fc.assert( fc.property( // Generate sessions with at least one having unread messages fc.array( fc.record({ sessionId: fc.integer({ min: 1, max: 100 }), targetUserId: fc.integer({ min: 1 }), targetNickname: fc.string({ minLength: 1 }), targetAvatar: fc.string(), lastMessage: fc.string(), lastMessageTime: fc.string(), unreadCount: fc.integer({ min: 0, max: 50 }) }), { minLength: 1, maxLength: 5 } ).filter(sessions => sessions.some(s => s.unreadCount > 0)), (sessions) => { const chatStore = useChatStore() // Ensure unique session IDs const uniqueSessions = sessions.map((s, i) => ({ ...s, sessionId: i + 1 })) // Set sessions chatStore.setSessions(uniqueSessions) // Find a session with unread messages const sessionWithUnread = uniqueSessions.find(s => s.unreadCount > 0) if (!sessionWithUnread) return true // Skip if no unread const totalBefore = chatStore.totalUnreadCount const sessionUnread = sessionWithUnread.unreadCount // Mark as read chatStore.markSessionAsRead(sessionWithUnread.sessionId) const totalAfter = chatStore.totalUnreadCount // Total should decrease by the session's unread count return totalAfter === totalBefore - sessionUnread } ), { numRuns: 100 } ) }) /** * Property: Receiving message in non-current session should increment unread */ it('Receiving message in non-current session should increment unread count', () => { fc.assert( fc.property( fc.integer({ min: 1, max: 100 }), // sessionId fc.integer({ min: 101, max: 200 }), // different currentSessionId fc.string({ minLength: 1 }), // message content (sessionId, currentSessionId, content) => { const chatStore = useChatStore() // Set up session chatStore.setSessions([{ sessionId, targetUserId: 1, targetNickname: 'Test', targetAvatar: '', lastMessage: '', lastMessageTime: '', unreadCount: 0 }]) // Set current session to a different one chatStore.setCurrentSession(currentSessionId) const unreadBefore = chatStore.totalUnreadCount // Receive message in the non-current session chatStore.receiveMessage({ id: 1, sessionId, senderId: 2, receiverId: 1, messageType: MessageType.TEXT, content, status: MessageStatus.DELIVERED, createTime: new Date().toISOString(), isMine: false }) const unreadAfter = chatStore.totalUnreadCount // Unread count should increase by 1 return unreadAfter === unreadBefore + 1 } ), { numRuns: 100 } ) }) })