initialize project with Vue 3, Vite, and Element Plus; set up basic routing, components, configuration, and project structure

This commit is contained in:
2025-12-14 18:55:20 +08:00
commit 0959754d1e
24 changed files with 5575 additions and 0 deletions

163
tests/users-view.spec.js Normal file
View 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')
})
})