diff --git a/client/src/App.spec.js b/client/src/App.spec.js
index 1758ed3..af9a26c 100644
--- a/client/src/App.spec.js
+++ b/client/src/App.spec.js
@@ -1,6 +1,6 @@
import { flushPromises, mount } from '@vue/test-utils';
import { createMemoryHistory, createRouter } from 'vue-router';
-import { beforeEach, describe, expect, it, vi } from 'vitest';
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import App from './App.vue';
@@ -47,6 +47,8 @@ function createRouterForTest() {
}
describe('App', () => {
+ let openSpy;
+
beforeEach(() => {
apiMocks.fetchCategories.mockResolvedValue([]);
apiMocks.fetchHotKeywords.mockResolvedValue([]);
@@ -65,6 +67,7 @@ describe('App', () => {
description: 'Simple tool',
category: { id: 'cat_demo', name: 'Developer Tools' },
tags: ['cli'],
+ displayVersion: '2026.04',
latestVersion: '1.0.0',
updatedAt: '2026-04-11',
accessMode: 'web',
@@ -95,10 +98,17 @@ describe('App', () => {
features: [],
updatedAt: '2026-04-11',
openUrl: 'https://example.com/tool',
+ displayVersion: '2026.04',
latestVersion: null,
fileSize: null,
downloadReady: true,
});
+
+ openSpy = vi.spyOn(window, 'open').mockReturnValue({});
+ });
+
+ afterEach(() => {
+ vi.restoreAllMocks();
});
it('navigates detail actions to the slug route and does not render the old detail modal', async () => {
@@ -143,4 +153,123 @@ describe('App', () => {
expect(wrapper.findAll('.modal-backdrop.open')).toHaveLength(1);
expect(wrapper.get('button.nav-btn').attributes('aria-expanded')).toBe('true');
});
+
+ it('renders displayVersion from the API payload', async () => {
+ const router = createRouterForTest();
+ await router.push('/');
+ await router.isReady();
+
+ const wrapper = mount(App, {
+ global: {
+ plugins: [router],
+ },
+ });
+
+ await flushPromises();
+
+ expect(wrapper.text()).toContain('2026.04');
+ expect(wrapper.text()).not.toContain('1.0.0');
+ });
+
+ it('keeps the current route unchanged when launching a download tool', async () => {
+ apiMocks.fetchTools.mockResolvedValue({
+ list: [
+ {
+ id: 'tool_download',
+ slug: 'download-tool',
+ name: 'Download Tool',
+ description: 'Download tool',
+ category: { id: 'cat_demo', name: 'Developer Tools' },
+ tags: ['installer'],
+ displayVersion: '2.0.0',
+ latestVersion: '2.0.0',
+ updatedAt: '2026-04-11',
+ accessMode: 'download',
+ openUrl: 'https://example.com/download',
+ downloadReady: true,
+ downloadCount: 12,
+ openCount: 0,
+ },
+ ],
+ pagination: {
+ page: 1,
+ pageSize: 6,
+ total: 1,
+ totalPages: 1,
+ },
+ });
+ apiMocks.launchTool.mockResolvedValue({
+ mode: 'download',
+ actionUrl: 'https://example.com/download',
+ openIn: 'same_tab',
+ });
+
+ const router = createRouterForTest();
+ await router.push('/');
+ await router.isReady();
+
+ const wrapper = mount(App, {
+ global: {
+ plugins: [router],
+ },
+ });
+
+ await flushPromises();
+
+ await wrapper.findAll('button.btn-small.btn-with-icon')[0].trigger('click');
+ await flushPromises();
+
+ expect(openSpy).toHaveBeenCalledWith('https://example.com/download', '_blank', 'noopener,noreferrer');
+ expect(router.currentRoute.value.fullPath).toBe('/');
+ });
+
+ it('uses the primary action as a detail shortcut for none mode tools', async () => {
+ apiMocks.fetchTools.mockResolvedValue({
+ list: [
+ {
+ id: 'tool_none',
+ slug: 'preview-tool',
+ name: 'Preview Tool',
+ description: 'Preview only',
+ category: { id: 'cat_demo', name: 'Developer Tools' },
+ tags: ['preview'],
+ displayVersion: 'preview-1',
+ latestVersion: null,
+ updatedAt: '2026-04-11',
+ accessMode: 'none',
+ openUrl: null,
+ downloadReady: false,
+ downloadCount: 0,
+ openCount: 0,
+ },
+ ],
+ pagination: {
+ page: 1,
+ pageSize: 6,
+ total: 1,
+ totalPages: 1,
+ },
+ });
+
+ const router = createRouterForTest();
+ await router.push('/');
+ await router.isReady();
+
+ const wrapper = mount(App, {
+ global: {
+ plugins: [router],
+ },
+ });
+
+ await flushPromises();
+
+ expect(wrapper.text()).toContain('查看详情');
+ expect(wrapper.text()).toContain('preview-1');
+
+ await wrapper.get('button.btn-small.btn-with-icon').trigger('click');
+ await flushPromises();
+
+ expect(apiMocks.launchTool).not.toHaveBeenCalled();
+ expect(router.currentRoute.value.fullPath).toBe('/tools/preview-tool');
+ });
});
diff --git a/client/src/App.vue b/client/src/App.vue
index d7ac2f2..e9a864d 100644
--- a/client/src/App.vue
+++ b/client/src/App.vue
@@ -152,7 +152,7 @@
版本
- {{ tool.latestVersion || '暂无版本' }}
+ {{ tool.displayVersion || '暂无版本' }}
@@ -175,7 +175,7 @@