= {}): VueWrapper => {
+ const i18n = createI18n({
+ legacy: false,
+ locale: 'en',
+ messages: { en: enMessages }
+ })
+
+ return mount(PackVersionSelectorPopover, {
+ props: {
+ nodePack: mockNodePack,
+ selectedVersion: SelectedVersion.NIGHTLY,
+ ...props
+ },
+ global: {
+ plugins: [PrimeVue, createPinia(), i18n],
+ components: {
+ Listbox
+ }
+ }
+ })
+ }
+
+ it('fetches versions on mount', async () => {
+ mountComponent()
+ await waitForPromises()
+
+ expect(mockGetPackVersions).toHaveBeenCalledWith(mockNodePack.id)
+ })
+
+ it('shows loading state while fetching versions', async () => {
+ // Delay the promise resolution
+ mockGetPackVersions.mockImplementationOnce(
+ () =>
+ new Promise((resolve) => setTimeout(() => resolve(mockVersions), 1000))
+ )
+
+ const wrapper = mountComponent()
+
+ expect(wrapper.text()).toContain('Loading versions...')
+ })
+
+ it('displays special options and version options in the listbox', async () => {
+ const wrapper = mountComponent()
+ await waitForPromises()
+
+ const listbox = wrapper.findComponent(Listbox)
+ expect(listbox.exists()).toBe(true)
+
+ const options = listbox.props('options')!
+ expect(options.length).toBe(5) // 2 special options + 3 version options
+
+ // Check special options
+ expect(options[0].value).toBe(SelectedVersion.NIGHTLY)
+ expect(options[1].value).toBe(SelectedVersion.LATEST)
+
+ // Check version options
+ expect(options[2].value).toBe('1.0.0')
+ expect(options[3].value).toBe('0.9.0')
+ expect(options[4].value).toBe('0.8.0')
+ })
+
+ it('initializes with the provided selectedVersion prop', async () => {
+ const selectedVersion = '0.9.0'
+ const wrapper = mountComponent({ props: { selectedVersion } })
+ await waitForPromises()
+
+ // Check that the listbox has the correct model value
+ const listbox = wrapper.findComponent(Listbox)
+ expect(listbox.props('modelValue')).toBe(selectedVersion)
+ })
+
+ it('emits cancel event when cancel button is clicked', async () => {
+ const wrapper = mountComponent()
+ await waitForPromises()
+
+ const cancelButton = wrapper.findAllComponents(Button)[0]
+ await cancelButton.trigger('click')
+
+ expect(wrapper.emitted('cancel')).toBeTruthy()
+ })
+
+ it('emits apply event with current selection when apply button is clicked', async () => {
+ const selectedVersion = '0.9.0'
+ const wrapper = mountComponent({ props: { selectedVersion } })
+ await waitForPromises()
+
+ const applyButton = wrapper.findAllComponents(Button)[1]
+ await applyButton.trigger('click')
+
+ expect(wrapper.emitted('apply')).toBeTruthy()
+ expect(wrapper.emitted('apply')![0]).toEqual([selectedVersion])
+ })
+
+ it('emits apply event with LATEST when no selection and apply button is clicked', async () => {
+ const wrapper = mountComponent({ props: { selectedVersion: null } })
+ await waitForPromises()
+
+ const applyButton = wrapper.findAllComponents(Button)[1]
+ await applyButton.trigger('click')
+
+ expect(wrapper.emitted('apply')).toBeTruthy()
+ expect(wrapper.emitted('apply')![0]).toEqual([SelectedVersion.LATEST])
+ })
+
+ it('is reactive to nodePack prop changes', async () => {
+ const wrapper = mountComponent()
+ await waitForPromises()
+
+ // Clear mock calls to check if getPackVersions is called again
+ mockGetPackVersions.mockClear()
+
+ // Update the nodePack prop
+ const newNodePack = { ...mockNodePack, id: 'new-test-pack' }
+ await wrapper.setProps({ nodePack: newNodePack })
+ await waitForPromises()
+
+ // Should fetch versions for the new nodePack
+ expect(mockGetPackVersions).toHaveBeenCalledWith(newNodePack.id)
+ })
+
+ describe('Unclaimed GitHub packs handling', () => {
+ it('falls back to nightly when comfy-api returns null when fetching versions', async () => {
+ mockGetPackVersions.mockResolvedValueOnce(null)
+
+ const wrapper = mountComponent()
+ await waitForPromises()
+
+ const listbox = wrapper.findComponent(Listbox)
+ expect(listbox.props('modelValue')).toBe(SelectedVersion.NIGHTLY)
+ })
+
+ it('falls back to nightly when component mounts with no versions (unclaimed pack)', async () => {
+ mockGetPackVersions.mockResolvedValueOnce([])
+
+ const wrapper = mountComponent()
+ await waitForPromises()
+
+ const listbox = wrapper.findComponent(Listbox)
+ expect(listbox.props('modelValue')).toBe(SelectedVersion.NIGHTLY)
+ })
+ })
+})
diff --git a/src/components/dialog/content/manager/infoPanel/InfoPanel.vue b/src/components/dialog/content/manager/infoPanel/InfoPanel.vue
index 26e7f66de..2c17f81e4 100644
--- a/src/components/dialog/content/manager/infoPanel/InfoPanel.vue
+++ b/src/components/dialog/content/manager/infoPanel/InfoPanel.vue
@@ -21,7 +21,11 @@
/>
-
+
@@ -32,7 +36,7 @@
diff --git a/src/locales/en/main.json b/src/locales/en/main.json
index 8a7be2404..5df7de8f6 100644
--- a/src/locales/en/main.json
+++ b/src/locales/en/main.json
@@ -89,13 +89,18 @@
"name": "Name",
"category": "Category",
"sort": "Sort",
- "filter": "Filter"
+ "filter": "Filter",
+ "apply": "Apply"
},
"manager": {
"title": "Custom Nodes Manager",
+ "loadingVersions": "Loading versions...",
+ "selectVersion": "Select Version",
"downloads": "Downloads",
"repository": "Repository",
"license": "License",
+ "nightlyVersion": "Nightly",
+ "latestVersion": "Latest",
"createdBy": "Created By",
"totalNodes": "Total Nodes",
"discoverCommunityContent": "Discover community-made Node Packs, Extensions, and more...",
diff --git a/src/locales/fr/main.json b/src/locales/fr/main.json
index b3aa3a544..dce1e56c5 100644
--- a/src/locales/fr/main.json
+++ b/src/locales/fr/main.json
@@ -117,6 +117,7 @@
"about": "À propos",
"add": "Ajouter",
"all": "Tout",
+ "apply": "Appliquer",
"back": "Retour",
"cancel": "Annuler",
"capture": "capture",
@@ -383,13 +384,17 @@
},
"installSelected": "Installer sélectionné",
"lastUpdated": "Dernière mise à jour",
+ "latestVersion": "Dernière",
"license": "Licence",
+ "loadingVersions": "Chargement des versions...",
+ "nightlyVersion": "Nocturne",
"noDescription": "Aucune description disponible",
"noResultsFound": "Aucun résultat trouvé correspondant à votre recherche.",
"nodePack": "Pack de Nœuds",
"packsSelected": "Packs sélectionnés",
"repository": "Référentiel",
"searchPlaceholder": "Recherche",
+ "selectVersion": "Sélectionner la version",
"sort": {
"downloads": "Le plus populaire",
"rating": "Évaluation"
diff --git a/src/locales/ja/main.json b/src/locales/ja/main.json
index b12f8c61d..1fdc1031c 100644
--- a/src/locales/ja/main.json
+++ b/src/locales/ja/main.json
@@ -117,6 +117,7 @@
"about": "情報",
"add": "追加",
"all": "すべて",
+ "apply": "適用する",
"back": "戻る",
"cancel": "キャンセル",
"capture": "キャプチャ",
@@ -383,13 +384,17 @@
},
"installSelected": "選択したものをインストール",
"lastUpdated": "最終更新日",
+ "latestVersion": "最新",
"license": "ライセンス",
+ "loadingVersions": "バージョンを読み込んでいます...",
+ "nightlyVersion": "ナイトリー",
"noDescription": "説明はありません",
"noResultsFound": "検索に一致する結果が見つかりませんでした。",
"nodePack": "ノードパック",
"packsSelected": "選択したパック",
"repository": "リポジトリ",
"searchPlaceholder": "検索",
+ "selectVersion": "バージョンを選択",
"sort": {
"downloads": "最も人気",
"rating": "評価"
diff --git a/src/locales/ko/main.json b/src/locales/ko/main.json
index 95447e40a..eca9ff39c 100644
--- a/src/locales/ko/main.json
+++ b/src/locales/ko/main.json
@@ -117,6 +117,7 @@
"about": "정보",
"add": "추가",
"all": "모두",
+ "apply": "적용",
"back": "뒤로",
"cancel": "취소",
"capture": "캡처",
@@ -383,13 +384,17 @@
},
"installSelected": "선택한 항목 설치",
"lastUpdated": "마지막 업데이트",
+ "latestVersion": "최신",
"license": "라이선스",
+ "loadingVersions": "버전 로딩 중...",
+ "nightlyVersion": "야간",
"noDescription": "설명이 없습니다",
"noResultsFound": "검색과 일치하는 결과가 없습니다.",
"nodePack": "노드 팩",
"packsSelected": "선택한 팩",
"repository": "저장소",
"searchPlaceholder": "검색",
+ "selectVersion": "버전 선택",
"sort": {
"downloads": "가장 인기 있는",
"rating": "평점"
diff --git a/src/locales/ru/main.json b/src/locales/ru/main.json
index a01a106b7..fab48d5d1 100644
--- a/src/locales/ru/main.json
+++ b/src/locales/ru/main.json
@@ -117,6 +117,7 @@
"about": "О программе",
"add": "Добавить",
"all": "Все",
+ "apply": "Применить",
"back": "Назад",
"cancel": "Отмена",
"capture": "захват",
@@ -383,13 +384,17 @@
},
"installSelected": "Установить выбранное",
"lastUpdated": "Последнее обновление",
+ "latestVersion": "Последняя",
"license": "Лицензия",
+ "loadingVersions": "Загрузка версий...",
+ "nightlyVersion": "Ночная",
"noDescription": "Описание отсутствует",
"noResultsFound": "По вашему запросу ничего не найдено.",
"nodePack": "Пакет Узлов",
"packsSelected": "Выбрано пакетов",
"repository": "Репозиторий",
"searchPlaceholder": "Поиск",
+ "selectVersion": "Выберите версию",
"sort": {
"downloads": "Самые популярные",
"rating": "Рейтинг"
diff --git a/src/locales/zh/main.json b/src/locales/zh/main.json
index 48ee1a33e..36ac035b1 100644
--- a/src/locales/zh/main.json
+++ b/src/locales/zh/main.json
@@ -117,6 +117,7 @@
"about": "关于",
"add": "添加",
"all": "全部",
+ "apply": "应用",
"back": "返回",
"cancel": "取消",
"capture": "捕获",
@@ -383,13 +384,17 @@
},
"installSelected": "安装选定",
"lastUpdated": "最后更新",
+ "latestVersion": "最新",
"license": "许可证",
+ "loadingVersions": "正在加载版本...",
+ "nightlyVersion": "每夜",
"noDescription": "无可用描述",
"noResultsFound": "未找到符合您搜索的结果。",
"nodePack": "节点包",
"packsSelected": "选定的包",
"repository": "仓库",
"searchPlaceholder": "搜索",
+ "selectVersion": "选择版本",
"sort": {
"downloads": "最受欢迎",
"rating": "评级"