feat(i18n): add multi-language support (ja/ko/vi/ru/zh-TW) with system auto-detection
- Extend AppLanguage to 7 locales: zh-CN, zh-TW, en, ja, ko, vi, ru - Auto-detect system language via navigator.languages; fallback to English - Add BCP47 locale mapping for date formatting and sorting - Create 50 translation JSON files (5 languages × 10 namespaces, 1137 keys each) - Update language selector with native language names - Extend locale key parity test to cover all 7 languages (70 assertions) - Add VNC/RDP desktop, serial terminal, AI agent, port forwarding, and SFTP features - Add credential encryption, host metrics, session backup crypto, and zmodem bridge - Update Rust backend: desktop services, host metrics, SSH/SFTP enhancements
This commit is contained in:
1 parent
fea6d8db04
commit
b0d0e3a5f3
301 files changed
+28002
-2705
No files matched your search
@@ -4,6 +4,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import Sidebar from '../../src/components/layout/Sidebar'
|
||||
import { useSessionStore, type Session } from '../../src/stores/sessionStore'
|
||||
import { useTabStore } from '../../src/stores/tabStore'
|
||||
import { unlockProForTests } from '../helpers/entitlement'
|
||||
import { mockDesktopApi } from '../setup'
|
||||
|
||||
const sessions: Session[] = [
|
||||
@@ -14,6 +15,7 @@ const sessions: Session[] = [
|
||||
describe('batch session operations', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
unlockProForTests()
|
||||
window.opsxApi = mockDesktopApi
|
||||
vi.mocked(mockDesktopApi.sessionGetAll).mockResolvedValueOnce({ success: true, sessions })
|
||||
useSessionStore.setState({ sessions: [] })
|
||||
@@ -23,7 +25,7 @@ describe('batch session operations', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('selects multiple saved sessions and opens connected terminal tabs', async () => {
|
||||
it('selects multiple saved sessions and opens batch exec', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(
|
||||
<Sidebar
|
||||
@@ -38,13 +40,20 @@ describe('batch session operations', () => {
|
||||
await user.click(screen.getByText('Prod A'))
|
||||
await user.click(screen.getByText('Prod B'))
|
||||
|
||||
expect(screen.getByText('已选 2 个会话')).toBeVisible()
|
||||
await user.click(screen.getByRole('button', { name: /打开终端/ }))
|
||||
expect(screen.getByText(/已选 2 个 SSH/)).toBeVisible()
|
||||
expect(screen.queryByRole('button', { name: /打开终端/ })).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: /打开 SFTP/ })).not.toBeInTheDocument()
|
||||
|
||||
expect(useTabStore.getState().tabs).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ sessionId: 'prod-a', type: 'terminal', connectOnMount: true }),
|
||||
expect.objectContaining({ sessionId: 'prod-b', type: 'terminal', connectOnMount: true }),
|
||||
]))
|
||||
await user.click(screen.getByRole('button', { name: '批量执行' }))
|
||||
|
||||
expect(useTabStore.getState().tabs).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: 'batch',
|
||||
batchSessionIds: ['prod-a', 'prod-b'],
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it('opens SFTP only from the saved session context menu', async () => {
|
||||
@@ -59,10 +68,10 @@ describe('batch session operations', () => {
|
||||
|
||||
const sessionName = await screen.findByText('Prod A')
|
||||
expect(screen.queryByTitle('SFTP')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('SFTP 文件')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('SFTP')).not.toBeInTheDocument()
|
||||
|
||||
fireEvent.contextMenu(sessionName)
|
||||
await user.click(screen.getByRole('button', { name: 'SFTP 文件' }))
|
||||
await user.click(screen.getByRole('button', { name: 'SFTP' }))
|
||||
|
||||
expect(useTabStore.getState().tabs).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ sessionId: 'prod-a', type: 'sftp' }),
|
||||
|
||||
Reference in new issue
Block a user