initialize project with Vue 3, Vite, and Element Plus; set up basic routing, components, configuration, and project structure
This commit is contained in:
83
tests/new-chat.spec.js
Normal file
83
tests/new-chat.spec.js
Normal file
@@ -0,0 +1,83 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { mount, flushPromises } from '@vue/test-utils'
|
||||
import { createRouter, createMemoryHistory } from 'vue-router'
|
||||
import ElementPlus from 'element-plus'
|
||||
import NewChatButton from '../src/components/NewChatButton.vue'
|
||||
import LoginView from '../src/views/LoginView.vue'
|
||||
import axios from 'axios'
|
||||
|
||||
vi.mock('axios', () => ({
|
||||
default: {
|
||||
post: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
const mockedAxios = axios
|
||||
|
||||
const makeRouter = () =>
|
||||
createRouter({
|
||||
history: createMemoryHistory(),
|
||||
routes: [
|
||||
{ path: '/', name: 'login', component: LoginView },
|
||||
{ path: '/welcome', name: 'welcome', component: LoginView },
|
||||
],
|
||||
})
|
||||
|
||||
describe('NewChatButton', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('redirects to login when no token', async () => {
|
||||
const router = makeRouter()
|
||||
router.push('/welcome')
|
||||
await router.isReady()
|
||||
|
||||
const wrapper = mount(NewChatButton, {
|
||||
global: {
|
||||
plugins: [router, ElementPlus],
|
||||
},
|
||||
})
|
||||
|
||||
await wrapper.find('[data-testid="new-chat-trigger"]').trigger('click')
|
||||
await flushPromises()
|
||||
await wrapper.find('[data-testid="create-session"]').trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(router.currentRoute.value.name).toBe('login')
|
||||
})
|
||||
|
||||
it('creates session with Authorization header', async () => {
|
||||
localStorage.setItem('ars-token', 'jwt-token')
|
||||
mockedAxios.post.mockResolvedValue({
|
||||
data: { session_id: 'session-123', session_name: 'Demo' },
|
||||
})
|
||||
|
||||
const router = makeRouter()
|
||||
router.push('/welcome')
|
||||
await router.isReady()
|
||||
|
||||
const wrapper = mount(NewChatButton, {
|
||||
global: {
|
||||
plugins: [router, ElementPlus],
|
||||
},
|
||||
})
|
||||
|
||||
// 直接调用方法以绕过弹层结构细节
|
||||
wrapper.vm.sessionName = 'Demo Session'
|
||||
await wrapper.vm.createSession()
|
||||
await flushPromises()
|
||||
|
||||
expect(mockedAxios.post).toHaveBeenCalledWith(
|
||||
'http://localhost:8000/api/sessions',
|
||||
expect.objectContaining({ session_name: 'Demo Session' }),
|
||||
expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
Authorization: 'Bearer jwt-token',
|
||||
}),
|
||||
})
|
||||
)
|
||||
expect(localStorage.getItem('ars-last-session')).toBe('session-123')
|
||||
})
|
||||
})
|
||||
86
tests/session-sidebar.spec.js
Normal file
86
tests/session-sidebar.spec.js
Normal file
@@ -0,0 +1,86 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { mount, flushPromises } from '@vue/test-utils'
|
||||
import { createRouter, createMemoryHistory } from 'vue-router'
|
||||
import ElementPlus from 'element-plus'
|
||||
import SessionSidebar from '../src/components/SessionSidebar.vue'
|
||||
import LoginView from '../src/views/LoginView.vue'
|
||||
import axios from 'axios'
|
||||
|
||||
vi.mock('axios', () => ({
|
||||
default: {
|
||||
get: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
const mockedAxios = axios
|
||||
|
||||
const makeRouter = () =>
|
||||
createRouter({
|
||||
history: createMemoryHistory(),
|
||||
routes: [
|
||||
{ path: '/', name: 'login', component: LoginView },
|
||||
{ path: '/welcome', name: 'welcome', component: LoginView },
|
||||
],
|
||||
})
|
||||
|
||||
describe('SessionSidebar', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('redirects to login without token', async () => {
|
||||
const router = makeRouter()
|
||||
router.push('/welcome')
|
||||
await router.isReady()
|
||||
|
||||
mount(SessionSidebar, {
|
||||
global: {
|
||||
plugins: [router, ElementPlus],
|
||||
},
|
||||
})
|
||||
|
||||
await flushPromises()
|
||||
expect(router.currentRoute.value.name).toBe('login')
|
||||
})
|
||||
|
||||
it('renders sessions and selects one', async () => {
|
||||
localStorage.setItem('ars-token', 'jwt-token')
|
||||
mockedAxios.get.mockResolvedValue({
|
||||
data: {
|
||||
data: [
|
||||
{
|
||||
session_id: 's1',
|
||||
session_name: 'Demo',
|
||||
status: 'OPEN',
|
||||
last_message_preview: 'Hi there',
|
||||
last_seq: 3,
|
||||
updated_at: '2025-02-14T10:00:00Z',
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
current_page: 1,
|
||||
per_page: 20,
|
||||
},
|
||||
})
|
||||
|
||||
const router = makeRouter()
|
||||
router.push('/welcome')
|
||||
await router.isReady()
|
||||
|
||||
const wrapper = mount(SessionSidebar, {
|
||||
global: {
|
||||
plugins: [router, ElementPlus],
|
||||
},
|
||||
})
|
||||
|
||||
await flushPromises()
|
||||
expect(mockedAxios.get).toHaveBeenCalledWith(
|
||||
'http://localhost:8000/api/sessions',
|
||||
expect.any(Object)
|
||||
)
|
||||
expect(wrapper.text()).toContain('Demo')
|
||||
await wrapper.find('.session-item').trigger('click')
|
||||
expect(localStorage.getItem('ars-current-session')).toBe('s1')
|
||||
})
|
||||
})
|
||||
163
tests/users-view.spec.js
Normal file
163
tests/users-view.spec.js
Normal file
@@ -0,0 +1,163 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { mount, flushPromises } from '@vue/test-utils'
|
||||
import { createRouter, createMemoryHistory } from 'vue-router'
|
||||
import ElementPlus from 'element-plus'
|
||||
import UsersView from '../src/views/UsersView.vue'
|
||||
import LoginView from '../src/views/LoginView.vue'
|
||||
import WelcomeView from '../src/views/WelcomeView.vue'
|
||||
import axios from 'axios'
|
||||
|
||||
vi.mock('axios', () => ({
|
||||
default: {
|
||||
get: vi.fn(),
|
||||
post: vi.fn(),
|
||||
put: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
const mockedAxios = axios
|
||||
|
||||
const makeRouter = () =>
|
||||
createRouter({
|
||||
history: createMemoryHistory(),
|
||||
routes: [
|
||||
{ path: '/', name: 'login', component: LoginView },
|
||||
{ path: '/welcome', name: 'welcome', component: WelcomeView },
|
||||
{ path: '/users', name: 'users', component: UsersView },
|
||||
],
|
||||
})
|
||||
|
||||
describe('UsersView', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('redirects to login when token is missing', async () => {
|
||||
const router = makeRouter()
|
||||
router.push('/users')
|
||||
await router.isReady()
|
||||
|
||||
mount(UsersView, {
|
||||
global: {
|
||||
plugins: [router, ElementPlus],
|
||||
},
|
||||
})
|
||||
|
||||
await flushPromises()
|
||||
expect(router.currentRoute.value.name).toBe('login')
|
||||
})
|
||||
|
||||
it('renders user rows when token exists and API succeeds', async () => {
|
||||
localStorage.setItem('ars-token', 'jwt-token')
|
||||
mockedAxios.get.mockResolvedValue({
|
||||
data: {
|
||||
data: [
|
||||
{ id: 1, name: 'Alice', email: 'alice@example.com', is_active: true },
|
||||
{ id: 2, name: 'Bob', email: 'bob@example.com', is_active: false },
|
||||
],
|
||||
current_page: 1,
|
||||
per_page: 10,
|
||||
total: 2,
|
||||
},
|
||||
})
|
||||
|
||||
const router = makeRouter()
|
||||
router.push('/users')
|
||||
await router.isReady()
|
||||
|
||||
const wrapper = mount(UsersView, {
|
||||
global: {
|
||||
plugins: [router, ElementPlus],
|
||||
},
|
||||
})
|
||||
|
||||
await flushPromises()
|
||||
|
||||
const rows = wrapper.findAll('[data-testid="user-row"]')
|
||||
expect(rows).toHaveLength(2)
|
||||
expect(wrapper.text()).toContain('Alice')
|
||||
expect(wrapper.text()).toContain('bob@example.com')
|
||||
})
|
||||
|
||||
it('deactivates a user via API and updates UI', async () => {
|
||||
localStorage.setItem('ars-token', 'jwt-token')
|
||||
mockedAxios.get.mockResolvedValue({
|
||||
data: {
|
||||
data: [{ id: 1, name: 'Alice', email: 'alice@example.com', is_active: true }],
|
||||
current_page: 1,
|
||||
per_page: 10,
|
||||
total: 1,
|
||||
},
|
||||
})
|
||||
mockedAxios.post.mockResolvedValue({ data: { id: 1, is_active: false } })
|
||||
|
||||
const router = makeRouter()
|
||||
router.push('/users')
|
||||
await router.isReady()
|
||||
|
||||
const wrapper = mount(UsersView, {
|
||||
global: {
|
||||
plugins: [router, ElementPlus],
|
||||
},
|
||||
})
|
||||
|
||||
await flushPromises()
|
||||
await wrapper.find('[data-testid="toggle-btn-1"]').trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(mockedAxios.post).toHaveBeenCalledWith(
|
||||
'http://localhost:8000/api/users/1/deactivate',
|
||||
{},
|
||||
expect.objectContaining({
|
||||
headers: expect.objectContaining({ Authorization: 'Bearer jwt-token' }),
|
||||
})
|
||||
)
|
||||
expect(wrapper.text()).toContain('停用')
|
||||
})
|
||||
|
||||
it('edits a user and sends PUT payload', async () => {
|
||||
localStorage.setItem('ars-token', 'jwt-token')
|
||||
mockedAxios.get.mockResolvedValue({
|
||||
data: {
|
||||
data: [{ id: 1, name: 'Alice', email: 'alice@example.com', is_active: true }],
|
||||
current_page: 1,
|
||||
per_page: 10,
|
||||
total: 1,
|
||||
},
|
||||
})
|
||||
mockedAxios.put.mockResolvedValue({
|
||||
data: { id: 1, name: 'Alice Updated', email: 'alice@new.com', is_active: true },
|
||||
})
|
||||
|
||||
const router = makeRouter()
|
||||
router.push('/users')
|
||||
await router.isReady()
|
||||
|
||||
const wrapper = mount(UsersView, {
|
||||
global: {
|
||||
plugins: [router, ElementPlus],
|
||||
},
|
||||
})
|
||||
|
||||
await flushPromises()
|
||||
await wrapper.find('[data-testid="edit-btn-1"]').trigger('click')
|
||||
await wrapper.find('[data-testid="edit-name"]').setValue('Alice Updated')
|
||||
await wrapper.find('[data-testid="edit-email"]').setValue('alice@new.com')
|
||||
await wrapper.find('[data-testid="save-user"]').trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(mockedAxios.put).toHaveBeenCalledWith(
|
||||
'http://localhost:8000/api/users/1',
|
||||
expect.objectContaining({
|
||||
name: 'Alice Updated',
|
||||
email: 'alice@new.com',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
headers: expect.objectContaining({ Authorization: 'Bearer jwt-token' }),
|
||||
})
|
||||
)
|
||||
expect(wrapper.text()).toContain('Alice Updated')
|
||||
expect(wrapper.text()).toContain('alice@new.com')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user