diff --git a/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png b/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png index 84feb7443..64635d046 100644 Binary files a/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png and b/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png differ diff --git a/src/locales/en/main.json b/src/locales/en/main.json index f000faa58..a3c499cda 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -10,6 +10,7 @@ "download": "Download", "downloadImage": "Download image", "downloadVideo": "Download video", + "downloadAudio": "Download audio", "editOrMaskImage": "Edit or mask image", "editImage": "Edit image", "decrement": "Decrement", diff --git a/src/renderer/extensions/vueNodes/widgets/components/audio/AudioPreviewPlayer.test.ts b/src/renderer/extensions/vueNodes/widgets/components/audio/AudioPreviewPlayer.test.ts new file mode 100644 index 000000000..365c738ec --- /dev/null +++ b/src/renderer/extensions/vueNodes/widgets/components/audio/AudioPreviewPlayer.test.ts @@ -0,0 +1,86 @@ +import { mount } from '@vue/test-utils' +import { describe, expect, it, vi } from 'vitest' +import { createI18n } from 'vue-i18n' + +import Button from '@/components/ui/button/Button.vue' +import AudioPreviewPlayer from '@/renderer/extensions/vueNodes/widgets/components/audio/AudioPreviewPlayer.vue' + +const mockToastAdd = vi.fn() + +vi.mock('primevue/usetoast', () => ({ + useToast: () => ({ add: mockToastAdd }) +})) + +vi.mock('@/base/common/downloadUtil', () => ({ + downloadFile: vi.fn() +})) + +const i18n = createI18n({ + legacy: false, + locale: 'en', + messages: { en: {} } +}) + +function mountPlayer(modelValue?: string) { + return mount(AudioPreviewPlayer, { + props: { + modelValue, + hideWhenEmpty: false + }, + global: { + plugins: [i18n], + components: { Button }, + stubs: { + TieredMenu: true, + Slider: true + } + } + }) +} + +function findDownloadButton(wrapper: ReturnType) { + return wrapper.find('[aria-label="g.downloadAudio"]') +} + +describe('AudioPreviewPlayer', () => { + describe('download button', () => { + it('shows download button when audio is loaded', () => { + const wrapper = mountPlayer('http://example.com/audio.mp3') + + expect(findDownloadButton(wrapper).exists()).toBe(true) + }) + + it('hides download button when no audio is loaded', () => { + const wrapper = mountPlayer() + + expect(findDownloadButton(wrapper).exists()).toBe(false) + }) + + it('calls downloadFile when download button is clicked', async () => { + const { downloadFile } = await import('@/base/common/downloadUtil') + + const wrapper = mountPlayer('http://example.com/audio.mp3') + await findDownloadButton(wrapper).trigger('click') + + expect(downloadFile).toHaveBeenCalledWith('http://example.com/audio.mp3') + }) + + it('shows toast on download failure', async () => { + const { downloadFile } = await import('@/base/common/downloadUtil') + vi.mocked(downloadFile).mockImplementation(() => { + throw new Error('download failed') + }) + + const wrapper = mountPlayer('http://example.com/audio.mp3') + await findDownloadButton(wrapper).trigger('click') + + expect(mockToastAdd).toHaveBeenCalledWith( + expect.objectContaining({ + severity: 'error' + }) + ) + + vi.mocked(downloadFile).mockReset() + }) + }) +}) diff --git a/src/renderer/extensions/vueNodes/widgets/components/audio/AudioPreviewPlayer.vue b/src/renderer/extensions/vueNodes/widgets/components/audio/AudioPreviewPlayer.vue index b351d7f25..5b4de7e30 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/audio/AudioPreviewPlayer.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/audio/AudioPreviewPlayer.vue @@ -16,11 +16,11 @@
-
-
+
@@ -57,11 +57,11 @@
-
-
+ + + + -
-
+
@@ -137,11 +150,16 @@ import { computed, ref, useTemplateRef } from 'vue' import { useI18n } from 'vue-i18n' import { whenever } from '@vueuse/core' +import { useToast } from 'primevue/usetoast' + +import { downloadFile } from '@/base/common/downloadUtil' +import Button from '@/components/ui/button/Button.vue' import { cn } from '@/utils/tailwindUtil' import { formatTime } from '../../utils/audioUtils' const { t } = useI18n() +const toast = useToast() const props = withDefaults( defineProps<{ @@ -187,6 +205,20 @@ const togglePlayPause = () => { isPlaying.value = !isPlaying.value } +const handleDownload = () => { + if (!modelValue.value) return + try { + downloadFile(modelValue.value) + } catch { + toast.add({ + severity: 'error', + summary: t('g.error'), + detail: t('g.failedToDownloadFile'), + life: 3000 + }) + } +} + const toggleMute = () => { if (audioRef.value) { isMuted.value = !isMuted.value