mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-04 08:27:46 +08:00
Show already checked / already associated models first in the 关联模型 list so they are easy to find and uncheck. Keep that order inside the current search results.
206 lines
6.1 KiB
TypeScript
206 lines
6.1 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { createApp, defineComponent, h, nextTick, type App } from 'vue'
|
|
|
|
import BatchAssignModelsDialog from '../BatchAssignModelsDialog.vue'
|
|
|
|
const globalModelMocks = vi.hoisted(() => ({
|
|
getGlobalModels: vi.fn(),
|
|
}))
|
|
|
|
const endpointMocks = vi.hoisted(() => ({
|
|
getProviderModels: vi.fn(),
|
|
getProviderKeys: vi.fn(),
|
|
batchAssignModelsToProvider: vi.fn(),
|
|
deleteModel: vi.fn(),
|
|
}))
|
|
|
|
vi.mock('@/api/endpoints/global-models', () => globalModelMocks)
|
|
vi.mock('@/api/endpoints', () => endpointMocks)
|
|
vi.mock('@/composables/useToast', () => ({
|
|
useToast: () => ({
|
|
error: vi.fn(),
|
|
success: vi.fn(),
|
|
warning: vi.fn(),
|
|
}),
|
|
}))
|
|
vi.mock('@/composables/useConfirm', () => ({
|
|
useConfirm: () => ({
|
|
confirmWarning: vi.fn().mockResolvedValue(true),
|
|
}),
|
|
}))
|
|
vi.mock('@/features/providers/composables/useUpstreamModelsCache', () => ({
|
|
useUpstreamModelsCache: () => ({
|
|
fetchModels: vi.fn(),
|
|
}),
|
|
}))
|
|
vi.mock('@/components/ui/dialog/Dialog.vue', async () => {
|
|
const { defineComponent, h } = await import('vue')
|
|
return {
|
|
default: defineComponent({
|
|
name: 'DialogStub',
|
|
setup: (_props, { slots }) => () => h('section', [slots.default?.(), slots.footer?.()]),
|
|
}),
|
|
}
|
|
})
|
|
vi.mock('@/components/ui', async () => {
|
|
const { defineComponent } = await import('vue')
|
|
const passthrough = (name: string) => defineComponent({
|
|
name,
|
|
inheritAttrs: false,
|
|
setup: (_props, { slots }) => () => slots.default?.(),
|
|
})
|
|
return {
|
|
DropdownMenu: passthrough('DropdownMenuStub'),
|
|
DropdownMenuTrigger: passthrough('DropdownMenuTriggerStub'),
|
|
DropdownMenuContent: passthrough('DropdownMenuContentStub'),
|
|
DropdownMenuItem: passthrough('DropdownMenuItemStub'),
|
|
}
|
|
})
|
|
|
|
const mountedApps: Array<{ app: App, root: HTMLElement }> = []
|
|
|
|
async function settle() {
|
|
for (let index = 0; index < 5; index += 1) {
|
|
await Promise.resolve()
|
|
await nextTick()
|
|
}
|
|
}
|
|
|
|
beforeEach(() => {
|
|
globalModelMocks.getGlobalModels.mockReset()
|
|
globalModelMocks.getGlobalModels.mockResolvedValue({ models: [], total: 0 })
|
|
endpointMocks.getProviderModels.mockReset()
|
|
endpointMocks.getProviderModels.mockResolvedValue([])
|
|
endpointMocks.getProviderKeys.mockReset()
|
|
endpointMocks.getProviderKeys.mockResolvedValue([])
|
|
endpointMocks.batchAssignModelsToProvider.mockReset()
|
|
endpointMocks.deleteModel.mockReset()
|
|
})
|
|
|
|
afterEach(() => {
|
|
for (const { app, root } of mountedApps.splice(0)) {
|
|
app.unmount()
|
|
root.remove()
|
|
}
|
|
})
|
|
|
|
function createGlobalModel(id: string, name: string, displayName = name) {
|
|
return {
|
|
id,
|
|
name,
|
|
display_name: displayName,
|
|
is_active: true,
|
|
default_tiered_pricing: { tiers: [] },
|
|
created_at: '2026-01-01T00:00:00Z',
|
|
}
|
|
}
|
|
|
|
function createProviderModel(id: string, globalModelId: string) {
|
|
return {
|
|
id,
|
|
provider_id: 'provider-1',
|
|
global_model_id: globalModelId,
|
|
provider_model_name: globalModelId,
|
|
is_active: true,
|
|
is_available: true,
|
|
created_at: '2026-01-01T00:00:00Z',
|
|
updated_at: '2026-01-01T00:00:00Z',
|
|
}
|
|
}
|
|
|
|
function visibleModelIds(root: HTMLElement): string[] {
|
|
return Array.from(root.querySelectorAll('[data-testid^="batch-assign-model-"]'))
|
|
.map(node => node.getAttribute('data-testid')?.replace('batch-assign-model-', '') ?? '')
|
|
.filter(Boolean)
|
|
}
|
|
|
|
describe('BatchAssignModelsDialog loading', () => {
|
|
it('loads model choices when lazily mounted in the open state', async () => {
|
|
const root = document.createElement('div')
|
|
document.body.appendChild(root)
|
|
const app = createApp(defineComponent({
|
|
setup() {
|
|
return () => h(BatchAssignModelsDialog, {
|
|
open: true,
|
|
providerId: 'provider-1',
|
|
providerName: 'Provider One',
|
|
})
|
|
},
|
|
}))
|
|
app.mount(root)
|
|
mountedApps.push({ app, root })
|
|
|
|
await settle()
|
|
|
|
expect(globalModelMocks.getGlobalModels).toHaveBeenCalledOnce()
|
|
expect(globalModelMocks.getGlobalModels).toHaveBeenCalledWith({ limit: 1000 })
|
|
expect(endpointMocks.getProviderModels).toHaveBeenCalledWith('provider-1')
|
|
expect(endpointMocks.getProviderKeys).toHaveBeenCalledWith('provider-1')
|
|
})
|
|
|
|
it('pins already associated models to the top of the list', async () => {
|
|
globalModelMocks.getGlobalModels.mockResolvedValue({
|
|
models: [
|
|
createGlobalModel('gm-zeta', 'zeta-model', 'Zeta'),
|
|
createGlobalModel('gm-alpha', 'alpha-model', 'Alpha'),
|
|
createGlobalModel('gm-mu', 'mu-model', 'Mu'),
|
|
],
|
|
total: 3,
|
|
})
|
|
endpointMocks.getProviderModels.mockResolvedValue([
|
|
createProviderModel('pm-mu', 'gm-mu'),
|
|
])
|
|
|
|
const root = document.createElement('div')
|
|
document.body.appendChild(root)
|
|
const app = createApp(defineComponent({
|
|
setup() {
|
|
return () => h(BatchAssignModelsDialog, {
|
|
open: true,
|
|
providerId: 'provider-1',
|
|
})
|
|
},
|
|
}))
|
|
app.mount(root)
|
|
mountedApps.push({ app, root })
|
|
await settle()
|
|
|
|
expect(visibleModelIds(root)).toEqual(['gm-mu', 'gm-alpha', 'gm-zeta'])
|
|
})
|
|
|
|
it('keeps selected matches pinned above other search results', async () => {
|
|
globalModelMocks.getGlobalModels.mockResolvedValue({
|
|
models: [
|
|
createGlobalModel('gm-beta', 'beta-flash', 'Beta Flash'),
|
|
createGlobalModel('gm-alpha', 'alpha-flash', 'Alpha Flash'),
|
|
createGlobalModel('gm-other', 'other-model', 'Other'),
|
|
],
|
|
total: 3,
|
|
})
|
|
endpointMocks.getProviderModels.mockResolvedValue([
|
|
createProviderModel('pm-beta', 'gm-beta'),
|
|
])
|
|
|
|
const root = document.createElement('div')
|
|
document.body.appendChild(root)
|
|
const app = createApp(defineComponent({
|
|
setup() {
|
|
return () => h(BatchAssignModelsDialog, {
|
|
open: true,
|
|
providerId: 'provider-1',
|
|
})
|
|
},
|
|
}))
|
|
app.mount(root)
|
|
mountedApps.push({ app, root })
|
|
await settle()
|
|
|
|
const search = root.querySelector('input') as HTMLInputElement
|
|
search.value = 'flash'
|
|
search.dispatchEvent(new Event('input', { bubbles: true }))
|
|
await settle()
|
|
|
|
expect(visibleModelIds(root)).toEqual(['gm-beta', 'gm-alpha'])
|
|
})
|
|
})
|