committed by
GitHub
1 changed files with 176 additions and 0 deletions
@ -0,0 +1,176 @@ |
|||
import { DOMWrapper, mount } from '@vue/test-utils'; |
|||
import { defineComponent, h, markRaw, nextTick } from 'vue'; |
|||
|
|||
import { |
|||
afterAll, |
|||
afterEach, |
|||
beforeAll, |
|||
describe, |
|||
expect, |
|||
it, |
|||
vi, |
|||
} from 'vitest'; |
|||
|
|||
import IconPicker from '../icon-picker.vue'; |
|||
|
|||
// VbenIcon renders through @iconify/vue's Icon, which lazily fetches icon data
|
|||
// from api.iconify.design at render time. Isolate it in tests: we only care
|
|||
// about which icon names are rendered into the grid, not the SVG bodies.
|
|||
vi.mock('@iconify/vue', () => ({ |
|||
Icon: defineComponent({ |
|||
name: 'IconifyIconMock', |
|||
props: { icon: { type: String, default: '' } }, |
|||
setup() { |
|||
return () => h('span', { class: 'mock-icon' }); |
|||
}, |
|||
}), |
|||
addCollection: vi.fn(), |
|||
addIcon: vi.fn(), |
|||
listIcons: vi.fn(() => []), |
|||
})); |
|||
|
|||
vi.mock('../icons', () => ({ |
|||
ICONS_MAP: {}, |
|||
fetchIconsData: vi.fn(async (prefix: string) => |
|||
prefix === 'carbon' |
|||
? [ |
|||
'carbon:home', |
|||
'carbon:search', |
|||
'carbon:settings', |
|||
'carbon:user-profile', |
|||
] |
|||
: [], |
|||
), |
|||
})); |
|||
|
|||
// Belt and braces: log + block any remaining network access.
|
|||
beforeAll(() => { |
|||
vi.stubGlobal( |
|||
'fetch', |
|||
vi.fn(async (url: unknown) => { |
|||
console.log('[icon-picker test] blocked fetch:', String(url)); |
|||
return new Response('{}', { status: 200 }); |
|||
}), |
|||
); |
|||
}); |
|||
afterEach(() => { |
|||
document.body.innerHTML = ''; |
|||
}); |
|||
|
|||
afterAll(() => { |
|||
vi.unstubAllGlobals(); |
|||
}); |
|||
|
|||
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); |
|||
|
|||
const gridIconCount = () => |
|||
document.body.querySelector('.grid-cols-6')?.childElementCount ?? 0; |
|||
|
|||
async function openIconPicker(wrapper: ReturnType<typeof mount>) { |
|||
await wrapper.find('input').trigger('click'); |
|||
await nextTick(); |
|||
await vi.waitFor(() => { |
|||
expect(document.body.querySelector('.grid-cols-6')).toBeTruthy(); |
|||
}); |
|||
} |
|||
|
|||
async function searchAndExpect(keyword: string, expectedCount: number) { |
|||
const inputs = [...document.body.querySelectorAll('input')]; |
|||
const el = inputs.at(-1); |
|||
if (!el) { |
|||
throw new Error('search input not found in popover'); |
|||
} |
|||
await new DOMWrapper(el).setValue(keyword); |
|||
// keyword debounce is 300ms; waitFor polls until the grid settles.
|
|||
await vi.waitFor(() => { |
|||
expect(gridIconCount()).toBe(expectedCount); |
|||
}); |
|||
} |
|||
|
|||
// Mimics the antdv-next adapter registration:
|
|||
// IconPicker: withDefaultPlaceholder(IconPicker, 'select', {
|
|||
// iconSlot: 'addonAfter',
|
|||
// inputComponent: Input,
|
|||
// modelValueProp: 'value',
|
|||
// })
|
|||
const AntdvNextLikeInput = defineComponent({ |
|||
name: 'AntdvNextLikeInput', |
|||
props: { |
|||
value: { type: String, default: '' }, |
|||
}, |
|||
emits: ['update:value'], |
|||
setup(props, { emit, attrs }) { |
|||
return () => |
|||
h('input', { |
|||
...attrs, |
|||
class: ['antdv-next-like-input', attrs.class as string], |
|||
value: props.value, |
|||
onInput: (event: Event) => { |
|||
const target = event.target as HTMLInputElement; |
|||
emit('update:value', target.value); |
|||
}, |
|||
}); |
|||
}, |
|||
}); |
|||
|
|||
const STATIC_ICONS = ['home', 'search', 'settings', 'user-profile']; |
|||
|
|||
describe('icon-picker.vue', () => { |
|||
it('filters icons with the search input (static icons)', async () => { |
|||
const wrapper = mount(IconPicker, { |
|||
attachTo: document.body, |
|||
props: { |
|||
icons: STATIC_ICONS, |
|||
prefix: '', |
|||
}, |
|||
}); |
|||
await openIconPicker(wrapper); |
|||
expect(gridIconCount()).toBe(4); |
|||
|
|||
// 'ho' matches only 'home'
|
|||
await searchAndExpect('ho', 1); |
|||
|
|||
await searchAndExpect('zzz', 0); // empty result
|
|||
|
|||
wrapper.unmount(); |
|||
}); |
|||
|
|||
it('filters icons with the search input (prefix + fetched icons)', async () => { |
|||
const wrapper = mount(IconPicker, { |
|||
attachTo: document.body, |
|||
props: { |
|||
prefix: 'carbon', |
|||
}, |
|||
}); |
|||
// watchDebounced: immediate + 500ms debounce before fetchIconsData
|
|||
await sleep(700); |
|||
await openIconPicker(wrapper); |
|||
expect(gridIconCount()).toBe(4); |
|||
|
|||
// 'user' matches only 'carbon:user-profile'
|
|||
await searchAndExpect('user', 1); |
|||
|
|||
wrapper.unmount(); |
|||
}); |
|||
|
|||
it('filters icons through inputComponent using the value/onUpdate:value protocol', async () => { |
|||
const wrapper = mount(IconPicker, { |
|||
attachTo: document.body, |
|||
props: { |
|||
icons: STATIC_ICONS, |
|||
inputComponent: markRaw(AntdvNextLikeInput), |
|||
modelValueProp: 'value', |
|||
prefix: '', |
|||
}, |
|||
}); |
|||
await openIconPicker(wrapper); |
|||
expect(gridIconCount()).toBe(4); |
|||
|
|||
// 'se' matches 'search', 'settings' and 'user-profile' (u-s-e-r)
|
|||
await searchAndExpect('se', 3); |
|||
|
|||
await searchAndExpect('zzz', 0); |
|||
|
|||
wrapper.unmount(); |
|||
}); |
|||
}); |
|||
Loading…
Reference in new issue