diff --git a/frontend/src/views/admin/AccountsView.vue b/frontend/src/views/admin/AccountsView.vue index 982c21b6f5..cbefbc5a03 100644 --- a/frontend/src/views/admin/AccountsView.vue +++ b/frontend/src/views/admin/AccountsView.vue @@ -634,7 +634,7 @@ const accountToolsDropdownStyle = computed(() => ({ width: `${accountToolsDropdownPosition.width}px` })) const hiddenColumns = reactive>(new Set()) -const DEFAULT_HIDDEN_COLUMNS = ['today_stats', 'proxy', 'notes', 'priority', 'scheduler_score', 'rate_multiplier'] +const DEFAULT_HIDDEN_COLUMNS = ['today_stats', 'proxy', 'notes', 'scheduler_score', 'rate_multiplier'] const HIDDEN_COLUMNS_KEY = 'account-hidden-columns' // One-time migration: hide scheduler score for existing admins too, because showing it opt-ins to heavy backend scoring. const HIDDEN_COLUMNS_VERSION_KEY = 'account-hidden-columns-version' diff --git a/frontend/src/views/admin/__tests__/AccountsView.priorityColumn.spec.ts b/frontend/src/views/admin/__tests__/AccountsView.priorityColumn.spec.ts new file mode 100644 index 0000000000..42c1071e87 --- /dev/null +++ b/frontend/src/views/admin/__tests__/AccountsView.priorityColumn.spec.ts @@ -0,0 +1,152 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { flushPromises, mount } from '@vue/test-utils' + +import AccountsView from '../AccountsView.vue' + +const { listAccounts } = vi.hoisted(() => ({ + listAccounts: vi.fn() +})) + +vi.mock('@/api/admin', () => ({ + adminAPI: { + accounts: { + list: listAccounts, + listWithEtag: vi.fn(), + getBatchTodayStats: vi.fn().mockResolvedValue({ stats: {} }), + getUpstreamBillingProbeSettings: vi.fn().mockResolvedValue({ enabled: true, interval_minutes: 30 }), + delete: vi.fn(), + batchClearError: vi.fn(), + batchRefresh: vi.fn(), + toggleSchedulable: vi.fn() + }, + proxies: { getAll: vi.fn().mockResolvedValue([]) }, + groups: { getAll: vi.fn().mockResolvedValue([]) } + } +})) + +vi.mock('@/stores/app', () => ({ + useAppStore: () => ({ showError: vi.fn(), showSuccess: vi.fn(), showInfo: vi.fn() }) +})) + +vi.mock('@/stores/auth', () => ({ + useAuthStore: () => ({ token: 'test-token', isSimpleMode: false }) +})) + +vi.mock('vue-i18n', async () => { + const actual = await vi.importActual('vue-i18n') + return { + ...actual, + useI18n: () => ({ t: (key: string) => key }) + } +}) + +const DataTableStub = { + props: ['columns'], + emits: ['sort'], + template: ` +
+ + {{ column.sortable ? 'sortable' : 'fixed' }} + +
+ ` +} + +function mountView() { + return mount(AccountsView, { + global: { + stubs: { + AppLayout: { template: '
' }, + TablePageLayout: { + template: '
' + }, + DataTable: DataTableStub, + AccountTableActions: { template: '
' }, + AccountTableFilters: true, + AccountBulkActionsBar: true, + Pagination: true, + ConfirmDialog: true, + AccountActionMenu: true, + ImportDataModal: true, + ReAuthAccountModal: true, + AccountTestModal: true, + AccountStatsModal: true, + ScheduledTestsPanel: true, + SyncFromCrsModal: true, + TempUnschedStatusModal: true, + ErrorPassthroughRulesModal: true, + TLSFingerprintProfilesModal: true, + CreateAccountModal: true, + EditAccountModal: true, + BulkEditAccountModal: true, + PlatformTypeBadge: true, + AccountCapacityCell: true, + AccountStatusIndicator: true, + AccountTodayStatsCell: true, + AccountGroupsCell: true, + AccountUsageCell: true, + HelpTooltip: true, + Icon: true, + Teleport: true + } + } + }) +} + +describe('admin AccountsView priority column preferences', () => { + beforeEach(() => { + localStorage.clear() + listAccounts.mockReset().mockResolvedValue({ + items: [], + total: 0, + page: 1, + page_size: 20, + pages: 0 + }) + }) + + it('shows priority as a sortable column for fresh preferences', async () => { + const wrapper = mountView() + await flushPromises() + + expect(wrapper.get('[data-column="priority"]').text()).toBe('sortable') + + await wrapper.get('[data-test="sort-priority"]').trigger('click') + await flushPromises() + + expect(listAccounts).toHaveBeenLastCalledWith( + 1, + 20, + expect.objectContaining({ sort_by: 'priority', sort_order: 'desc' }), + expect.objectContaining({ signal: expect.any(AbortSignal) }) + ) + }) + + it('preserves an existing preference that explicitly hides priority', async () => { + localStorage.setItem('account-hidden-columns', JSON.stringify(['priority', 'today_stats'])) + localStorage.setItem('account-hidden-columns-version', 'scheduler-score-hidden-by-default') + + const wrapper = mountView() + await flushPromises() + + expect(wrapper.find('[data-column="priority"]').exists()).toBe(false) + expect(JSON.parse(localStorage.getItem('account-hidden-columns') || '[]')).toEqual([ + 'priority', + 'today_stats' + ]) + }) + + it('keeps priority visible while migrating older saved preferences', async () => { + localStorage.setItem('account-hidden-columns', JSON.stringify(['today_stats'])) + + const wrapper = mountView() + await flushPromises() + + expect(wrapper.get('[data-column="priority"]').text()).toBe('sortable') + expect(JSON.parse(localStorage.getItem('account-hidden-columns') || '[]')).toEqual( + expect.arrayContaining(['today_stats', 'scheduler_score']) + ) + expect(JSON.parse(localStorage.getItem('account-hidden-columns') || '[]')).not.toContain('priority') + }) +})