This commit is contained in:
2026-04-22 18:54:52 +08:00
commit bc8986e3b2
49 changed files with 20987 additions and 0 deletions

41
tests/base-button.test.js Normal file
View File

@@ -0,0 +1,41 @@
const path = require('path')
const simulate = require('miniprogram-simulate')
describe('app-button', () => {
test('passes core props to TDesign and re-emits tap events', async () => {
const id = simulate.load(path.join(process.cwd(), 'components/base/app-button/index'), {
usingComponents: {
't-button': path.join(
process.cwd(),
'node_modules/tdesign-miniprogram/miniprogram_dist/button/button'
)
}
})
const comp = simulate.render(id, {
text: '进入工作台',
theme: 'danger',
variant: 'outline'
})
const parent = document.createElement('div')
const tapHandler = jest.fn()
comp.attach(parent)
comp.addEventListener('tap', tapHandler)
const tree = comp.toJSON()
const innerButton = tree.children[0]
expect(innerButton.tagName).toBe('t-button')
expect(innerButton.attrs).toEqual(
expect.arrayContaining([
expect.objectContaining({ name: 'theme', value: 'danger' }),
expect.objectContaining({ name: 'variant', value: 'outline' })
])
)
expect(innerButton.children).toContain('进入工作台')
comp.instance.handleTap({ detail: { source: 'unit-test' } })
await Promise.resolve()
expect(tapHandler).toHaveBeenCalledTimes(1)
})
})

175
tests/request.test.js Normal file
View File

@@ -0,0 +1,175 @@
const { createRequester, RequestError } = require('../services/request')
describe('createRequester', () => {
test('normalizes successful responses', async () => {
const requester = createRequester({
requestAdapter: jest.fn().mockResolvedValue({
statusCode: 200,
data: {
code: 0,
data: { id: 1, name: 'Xuanzhi' },
message: 'ok'
}
})
})
await expect(
requester({
url: '/users/me',
method: 'GET'
})
).resolves.toEqual({
code: 0,
data: { id: 1, name: 'Xuanzhi' },
message: 'ok'
})
})
test('retries retryable transport failures once', async () => {
const requestAdapter = jest
.fn()
.mockRejectedValueOnce(new Error('timeout'))
.mockResolvedValueOnce({
statusCode: 200,
data: {
code: 0,
data: { ok: true },
message: 'ok'
}
})
const requester = createRequester({
requestAdapter,
shouldRetry: error => error.message === 'timeout'
})
await expect(
requester({
url: '/reports/slow-task',
retry: 1
})
).resolves.toEqual({
code: 0,
data: { ok: true },
message: 'ok'
})
expect(requestAdapter).toHaveBeenCalledTimes(2)
})
test('rejects business errors with a normalized error instance', async () => {
const requester = createRequester({
requestAdapter: jest.fn().mockResolvedValue({
statusCode: 200,
data: {
code: 50001,
data: null,
message: 'invalid payload'
}
})
})
await expect(
requester({
url: '/orders',
method: 'POST',
data: { name: '' }
})
).rejects.toEqual(
expect.objectContaining({
name: 'RequestError',
code: 50001,
message: 'invalid payload'
})
)
expect(RequestError).toBeDefined()
})
test('clears auth state and notifies unauthorized handler when token expires', async () => {
const onUnauthorized = jest.fn()
const sessionStore = {
clearSession: jest.fn()
}
const requester = createRequester({
requestAdapter: jest.fn().mockResolvedValue({
statusCode: 200,
data: {
code: 401,
data: null,
message: 'token expired'
}
}),
authExpiredCodes: [401],
onUnauthorized,
sessionStore
})
await expect(requester({ url: '/users/me' })).rejects.toEqual(
expect.objectContaining({
code: 401,
message: 'token expired'
})
)
expect(sessionStore.clearSession).toHaveBeenCalledTimes(1)
expect(onUnauthorized).toHaveBeenCalledWith(
expect.objectContaining({
code: 401,
message: 'token expired'
})
)
})
test('deduplicates in-flight requests when dedupe is enabled', async () => {
let resolveRequest
const requestAdapter = jest.fn(
() =>
new Promise(resolve => {
resolveRequest = resolve
})
)
const requester = createRequester({
requestAdapter
})
const firstPromise = requester({
url: '/orders/submit',
method: 'POST',
data: { skuId: 1 },
dedupe: true
})
const secondPromise = requester({
url: '/orders/submit',
method: 'POST',
data: { skuId: 1 },
dedupe: true
})
resolveRequest({
statusCode: 200,
data: {
code: 0,
data: { orderId: 'A1001' },
message: 'ok'
}
})
await expect(Promise.all([firstPromise, secondPromise])).resolves.toEqual([
{
code: 0,
data: { orderId: 'A1001' },
message: 'ok'
},
{
code: 0,
data: { orderId: 'A1001' },
message: 'ok'
}
])
expect(requestAdapter).toHaveBeenCalledTimes(1)
})
})

87
tests/session.test.js Normal file
View File

@@ -0,0 +1,87 @@
const { createSessionStore } = require('../stores/modules/session')
describe('createSessionStore', () => {
test('hydrates persisted session and notifies subscribers on updates', () => {
const storage = new Map([
[
'session',
{
token: 'cached-token',
userInfo: { id: 1, name: 'Xuanzhi' },
permissions: ['dashboard:view']
}
]
])
const sessionStore = createSessionStore({
storageKey: 'session',
storage: {
get(key) {
return storage.get(key)
},
set(key, value) {
storage.set(key, value)
},
remove(key) {
storage.delete(key)
}
}
})
const subscriber = jest.fn()
sessionStore.subscribe(subscriber)
sessionStore.hydrate()
sessionStore.setSession({
token: 'next-token',
userInfo: { id: 2, name: 'Architect' },
permissions: ['dashboard:view', 'dashboard:edit']
})
expect(sessionStore.getState()).toEqual({
isLoggedIn: true,
token: 'next-token',
userInfo: { id: 2, name: 'Architect' },
permissions: ['dashboard:view', 'dashboard:edit']
})
expect(storage.get('session')).toEqual({
token: 'next-token',
userInfo: { id: 2, name: 'Architect' },
permissions: ['dashboard:view', 'dashboard:edit']
})
expect(subscriber).toHaveBeenCalled()
})
test('clears persisted state on logout', () => {
const storage = new Map()
const sessionStore = createSessionStore({
storageKey: 'session',
storage: {
get(key) {
return storage.get(key)
},
set(key, value) {
storage.set(key, value)
},
remove(key) {
storage.delete(key)
}
}
})
sessionStore.setSession({
token: 'next-token',
userInfo: { id: 2, name: 'Architect' },
permissions: ['dashboard:view', 'dashboard:edit']
})
sessionStore.clearSession()
expect(sessionStore.getState()).toEqual({
isLoggedIn: false,
token: '',
userInfo: null,
permissions: []
})
expect(storage.has('session')).toBe(false)
})
})

54
tests/user-api.test.js Normal file
View File

@@ -0,0 +1,54 @@
const { createUserApi } = require('../services/api/user')
describe('createUserApi', () => {
test('calls the profile endpoint through the shared request layer', async () => {
const request = jest.fn().mockResolvedValue({
code: 0,
data: { id: 1, name: 'Xuanzhi' },
message: 'ok'
})
const userApi = createUserApi({ request })
await expect(userApi.getProfile()).resolves.toEqual({
code: 0,
data: { id: 1, name: 'Xuanzhi' },
message: 'ok'
})
expect(request).toHaveBeenCalledWith({
url: '/users/me',
method: 'GET'
})
})
test('submits profile updates through the shared request layer', async () => {
const request = jest.fn().mockResolvedValue({
code: 0,
data: { saved: true },
message: 'ok'
})
const userApi = createUserApi({ request })
await expect(
userApi.updateProfile({
nickname: 'Architect',
mobile: '13800000000'
})
).resolves.toEqual({
code: 0,
data: { saved: true },
message: 'ok'
})
expect(request).toHaveBeenCalledWith({
url: '/users/me',
method: 'PUT',
data: {
nickname: 'Architect',
mobile: '13800000000'
}
})
})
})