修改页面
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
|
||||
// Mock fetch
|
||||
const mockFetch = vi.fn()
|
||||
global.fetch = mockFetch
|
||||
|
||||
// Mock localStorage
|
||||
const store: Record<string, string> = {}
|
||||
vi.stubGlobal('localStorage', {
|
||||
getItem: (key: string) => store[key] || null,
|
||||
setItem: (key: string, value: string) => { store[key] = value },
|
||||
removeItem: (key: string) => { delete store[key] },
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
mockFetch.mockReset()
|
||||
Object.keys(store).forEach((k) => delete store[k])
|
||||
})
|
||||
|
||||
function mockResponse(data: any) {
|
||||
return { json: () => Promise.resolve(data) }
|
||||
}
|
||||
|
||||
describe('Api Service', () => {
|
||||
it('should send GET request with token', async () => {
|
||||
store['token'] = 'test-token'
|
||||
mockFetch.mockResolvedValue(mockResponse({ code: 0, data: { id: '1' } }))
|
||||
|
||||
const { request } = await import('../services/api')
|
||||
const result = await request('/api/test')
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
'http://localhost:8080/api/test',
|
||||
expect.objectContaining({
|
||||
headers: expect.objectContaining({ Authorization: 'Bearer test-token' }),
|
||||
}),
|
||||
)
|
||||
expect(result).toEqual({ id: '1' })
|
||||
})
|
||||
|
||||
it('should throw on non-zero code', async () => {
|
||||
mockFetch.mockResolvedValue(mockResponse({ code: 400, message: 'Bad Request' }))
|
||||
|
||||
const { request } = await import('../services/api')
|
||||
await expect(request('/api/test')).rejects.toThrow('Bad Request')
|
||||
})
|
||||
|
||||
it('should support POST method', async () => {
|
||||
mockFetch.mockResolvedValue(mockResponse({ code: 0, data: { created: true } }))
|
||||
|
||||
const { api } = await import('../services/api')
|
||||
const result = await api.post('/api/items', { name: 'test' })
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
'http://localhost:8080/api/items',
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ name: 'test' }),
|
||||
}),
|
||||
)
|
||||
expect(result).toEqual({ created: true })
|
||||
})
|
||||
|
||||
it('should support PUT and DELETE methods', async () => {
|
||||
mockFetch.mockResolvedValue(mockResponse({ code: 0, data: null }))
|
||||
|
||||
const { api } = await import('../services/api')
|
||||
await api.put('/api/items/1', { name: 'updated' })
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
'http://localhost:8080/api/items/1',
|
||||
expect.objectContaining({ method: 'PUT' }),
|
||||
)
|
||||
|
||||
await api.delete('/api/items/1')
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
'http://localhost:8080/api/items/1',
|
||||
expect.objectContaining({ method: 'DELETE' }),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Auth Service', () => {
|
||||
it('should store and clear token', async () => {
|
||||
const { setToken, clearToken, getStoredUser } = await import('../services/api')
|
||||
|
||||
setToken('abc')
|
||||
expect(store['token']).toBe('abc')
|
||||
|
||||
setStoredUser({ nickname: 'Test' })
|
||||
expect(getStoredUser()).toEqual({ nickname: 'Test' })
|
||||
|
||||
clearToken()
|
||||
expect(store['token']).toBeUndefined()
|
||||
})
|
||||
|
||||
// Helper to test the import below
|
||||
const { setStoredUser } = await import('../services/api')
|
||||
void setStoredUser
|
||||
})
|
||||
|
||||
describe('CheckIn Service', () => {
|
||||
it('should call createItem with proper payload', async () => {
|
||||
mockFetch.mockResolvedValue(mockResponse({ code: 0, data: { id: '1', name: 'Read' } }))
|
||||
|
||||
const { createItem } = await import('../services/checkin')
|
||||
const result = await createItem({ name: 'Read', category: 'study' })
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
'http://localhost:8080/api/checkin/items',
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
name: 'Read', category: 'study', icon: undefined,
|
||||
dailyGoal: undefined, cycleType: 'daily', privacy: 2,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
expect(result.name).toBe('Read')
|
||||
})
|
||||
|
||||
it('should list items', async () => {
|
||||
mockFetch.mockResolvedValue(mockResponse({ code: 0, data: [{ id: '1' }, { id: '2' }] }))
|
||||
|
||||
const { listItems } = await import('../services/checkin')
|
||||
const items = await listItems()
|
||||
expect(items).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('should get streak', async () => {
|
||||
mockFetch.mockResolvedValue(mockResponse({ code: 0, data: { itemId: '1', streak: 7 } }))
|
||||
|
||||
const { getStreak } = await import('../services/checkin')
|
||||
const result = await getStreak('1')
|
||||
expect(result.streak).toBe(7)
|
||||
})
|
||||
|
||||
it('should get today status', async () => {
|
||||
mockFetch.mockResolvedValue(mockResponse({ code: 0, data: { 'item-1': true, 'item-2': false } }))
|
||||
|
||||
const { getTodayStatus } = await import('../services/checkin')
|
||||
const status = await getTodayStatus()
|
||||
expect(status['item-1']).toBe(true)
|
||||
expect(status['item-2']).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Social Service', () => {
|
||||
it('should create and fetch feed', async () => {
|
||||
mockFetch.mockResolvedValue(mockResponse({ code: 0, data: { list: [{ id: 'p1' }], total: 1 } }))
|
||||
|
||||
const { getFeed } = await import('../services/social')
|
||||
const feed = await getFeed()
|
||||
expect(feed.list).toHaveLength(1)
|
||||
expect(feed.total).toBe(1)
|
||||
})
|
||||
|
||||
it('should send friend request', async () => {
|
||||
mockFetch.mockResolvedValue(mockResponse({ code: 0, data: null }))
|
||||
|
||||
const { sendFriendRequest } = await import('../services/social')
|
||||
await sendFriendRequest('user-2')
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
'http://localhost:8080/api/social/friends/request',
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ friendId: 'user-2' }),
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Shared Utils', () => {
|
||||
it('formatDate should return YYYY-MM-DD', async () => {
|
||||
const { formatDate } = await import('../utils/index')
|
||||
expect(formatDate('2026-06-25')).toBe('2026-06-25')
|
||||
expect(formatDate(new Date('2026-01-05'))).toBe('2026-01-05')
|
||||
})
|
||||
|
||||
it('timeAgo should return relative time', async () => {
|
||||
const { timeAgo } = await import('../utils/index')
|
||||
const now = new Date().toISOString()
|
||||
expect(timeAgo(now)).toBe('刚刚')
|
||||
})
|
||||
|
||||
it('getCategoryLabel should return Chinese label', async () => {
|
||||
const { getCategoryLabel } = await import('../utils/index')
|
||||
expect(getCategoryLabel('study')).toBe('学习成长')
|
||||
expect(getCategoryLabel('health')).toBe('健康生活')
|
||||
expect(getCategoryLabel('unknown')).toBe('unknown')
|
||||
})
|
||||
|
||||
it('formatPoints should format with + sign', async () => {
|
||||
const { formatPoints } = await import('../utils/index')
|
||||
expect(formatPoints(10)).toBe('+10')
|
||||
expect(formatPoints(0)).toBe('0')
|
||||
expect(formatPoints(null)).toBe('--')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,64 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
|
||||
const mockRequest = vi.fn()
|
||||
|
||||
vi.mock('../services/api', () => ({
|
||||
request: mockRequest,
|
||||
api: {
|
||||
get: (path: string) => mockRequest('get', path),
|
||||
post: (path: string, data?: unknown) => mockRequest('post', path, data),
|
||||
put: (path: string, data?: unknown) => mockRequest('put', path, data),
|
||||
delete: (path: string) => mockRequest('delete', path),
|
||||
},
|
||||
}))
|
||||
|
||||
describe('CheckIn Service', () => {
|
||||
beforeEach(() => {
|
||||
mockRequest.mockReset()
|
||||
})
|
||||
|
||||
it('should call createItem API', async () => {
|
||||
mockRequest.mockResolvedValue({ id: '1', name: 'Daily Read' })
|
||||
const { createItem } = await import('../services/checkin')
|
||||
const result = await createItem({ name: 'Daily Read', category: 'study' })
|
||||
expect(mockRequest).toHaveBeenCalledWith('post', '/api/checkin/items', {
|
||||
name: 'Daily Read',
|
||||
category: 'study',
|
||||
icon: undefined,
|
||||
dailyGoal: undefined,
|
||||
cycleType: 'daily',
|
||||
privacy: 2,
|
||||
})
|
||||
expect(result).toEqual({ id: '1', name: 'Daily Read' })
|
||||
})
|
||||
|
||||
it('should call listItems API', async () => {
|
||||
mockRequest.mockResolvedValue([{ id: '1' }])
|
||||
const { listItems } = await import('../services/checkin')
|
||||
const result = await listItems()
|
||||
expect(mockRequest).toHaveBeenCalledWith('get', '/api/checkin/items')
|
||||
expect(result).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('should call getStreak API', async () => {
|
||||
mockRequest.mockResolvedValue({ itemId: '1', streak: 7 })
|
||||
const { getStreak } = await import('../services/checkin')
|
||||
const result = await getStreak('1')
|
||||
expect(mockRequest).toHaveBeenCalledWith('get', '/api/checkin/streak/1')
|
||||
expect(result.streak).toBe(7)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Social Service', () => {
|
||||
beforeEach(() => {
|
||||
mockRequest.mockReset()
|
||||
})
|
||||
|
||||
it('should call getFeed API', async () => {
|
||||
mockRequest.mockResolvedValue({ list: [{ id: 'p1' }], total: 1, page: 1 })
|
||||
const { getFeed } = await import('../services/social')
|
||||
const result = await getFeed(1, 20)
|
||||
expect(mockRequest).toHaveBeenCalledWith('get', '/api/social/feed?page=1&pageSize=20')
|
||||
expect(result.list).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,14 @@
|
||||
export {
|
||||
formatDate,
|
||||
formatMonth,
|
||||
timeAgo,
|
||||
CHECKIN_CATEGORY,
|
||||
getCategoryLabel,
|
||||
getCycleLabel,
|
||||
getPrivacyLabel,
|
||||
formatPoints,
|
||||
truncate,
|
||||
shortId,
|
||||
CYCLE_LABEL,
|
||||
} from '../../shared/utils'
|
||||
export type { CheckinCategory } from '../../shared/utils'
|
||||
@@ -0,0 +1,16 @@
|
||||
import { defineConfig } from 'vitest/config'
|
||||
import path from 'path'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
environment: 'node',
|
||||
globals: true,
|
||||
include: ['src/**/*.test.ts', 'src/**/*.test.tsx'],
|
||||
setupFiles: [],
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, 'src'),
|
||||
},
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user