init
This commit is contained in:
41
tests/base-button.test.js
Normal file
41
tests/base-button.test.js
Normal 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
175
tests/request.test.js
Normal 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
87
tests/session.test.js
Normal 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
54
tests/user-api.test.js
Normal 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'
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user