diff --git a/easytier-web/frontend-lib/src/components/UrlListInput.vue b/easytier-web/frontend-lib/src/components/UrlListInput.vue index 8f58c5a7..1b51a094 100644 --- a/easytier-web/frontend-lib/src/components/UrlListInput.vue +++ b/easytier-web/frontend-lib/src/components/UrlListInput.vue @@ -11,8 +11,16 @@ const props = defineProps<{ const list = defineModel({ required: true }) +const fallbackUrl = () => { + const defaultProto = Object.keys(props.protos).includes('tcp') + ? 'tcp' + : (Object.keys(props.protos)[0] ?? 'tcp') + const defaultPort = props.protos[defaultProto] ?? 11010 + return `${defaultProto}://0.0.0.0:${defaultPort}` +} + const addUrl = () => { - list.value.push(props.defaultUrl || 'tcp://0.0.0.0:11010') + list.value.push(props.defaultUrl || fallbackUrl()) } const removeUrl = (index: number) => { diff --git a/easytier-web/frontend-lib/tests/url-list-input.spec.ts b/easytier-web/frontend-lib/tests/url-list-input.spec.ts new file mode 100644 index 00000000..fb5ae8af --- /dev/null +++ b/easytier-web/frontend-lib/tests/url-list-input.spec.ts @@ -0,0 +1,75 @@ +import { mount } from '@vue/test-utils' +import { describe, expect, it } from 'vitest' +import { defineComponent, h, nextTick, ref } from 'vue' +import UrlListInput from '../src/components/UrlListInput.vue' + +const ButtonStub = defineComponent({ + name: 'Button', + emits: ['click'], + setup(_, { slots, emit }) { + return () => h('button', { onClick: (event: MouseEvent) => emit('click', event) }, slots.default?.()) + }, +}) + +const UrlInputStub = defineComponent({ + name: 'UrlInput', + setup(_, { slots }) { + return () => h('div', slots.actions?.()) + }, +}) + +function mountUrlListInput(protos: Record, defaultUrl?: string) { + const urls = ref([]) + const wrapper = mount(defineComponent({ + components: { UrlListInput }, + setup() { + return { urls, protos, defaultUrl } + }, + template: ` + + `, + }), { + global: { + stubs: { + Button: ButtonStub, + UrlInput: UrlInputStub, + }, + }, + }) + + return { wrapper, urls } +} + +describe('UrlListInput.vue add fallback', () => { + it('derives the fallback URL from protos when defaultUrl is not provided', async () => { + const { wrapper, urls } = mountUrlListInput({ tcp: 11010, udp: 11010 }) + + await wrapper.find('.cursor-pointer').trigger('click') + await nextTick() + + expect(urls.value).toEqual(['tcp://0.0.0.0:11010']) + }) + + it('supports port-zero fallback from protos', async () => { + const { wrapper, urls } = mountUrlListInput({ tcp: 0, udp: 0 }) + + await wrapper.find('.cursor-pointer').trigger('click') + await nextTick() + + expect(urls.value).toEqual(['tcp://0.0.0.0:0']) + }) + + it('uses defaultUrl when provided', async () => { + const { wrapper, urls } = mountUrlListInput({ tcp: 11010 }, 'udp://0.0.0.0:22000') + + await wrapper.find('.cursor-pointer').trigger('click') + await nextTick() + + expect(urls.value).toEqual(['udp://0.0.0.0:22000']) + }) +})