From 178c79e55981e47dc8bfbbfb7a857e09ed4b52ed Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Wed, 21 May 2025 18:34:13 -0700 Subject: [PATCH] [fix] Make gallery navigation buttons visible on mobile devices (#3953) --- .../sidebar/tabs/queue/ResultGallery.spec.ts | 177 ++++++++++++++++++ .../sidebar/tabs/queue/ResultGallery.vue | 8 + 2 files changed, 185 insertions(+) create mode 100644 src/components/sidebar/tabs/queue/ResultGallery.spec.ts diff --git a/src/components/sidebar/tabs/queue/ResultGallery.spec.ts b/src/components/sidebar/tabs/queue/ResultGallery.spec.ts new file mode 100644 index 000000000..bf96984be --- /dev/null +++ b/src/components/sidebar/tabs/queue/ResultGallery.spec.ts @@ -0,0 +1,177 @@ +import { mount } from '@vue/test-utils' +import PrimeVue from 'primevue/config' +import Galleria from 'primevue/galleria' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { createApp, nextTick } from 'vue' + +import type { NodeId } from '@/schemas/comfyWorkflowSchema' +import type { ResultItemImpl } from '@/stores/queueStore' + +import ResultGallery from './ResultGallery.vue' + +type MockResultItem = Partial & { + filename: string + subfolder: string + type: string + nodeId: NodeId + mediaType: string + id?: string + url?: string + isImage?: boolean + isVideo?: boolean +} + +describe('ResultGallery', () => { + // Mock ComfyImage and ResultVideo components + const mockComfyImage = { + name: 'ComfyImage', + template: '
', + props: ['src', 'contain', 'alt'] + } + + const mockResultVideo = { + name: 'ResultVideo', + template: + '
', + props: ['result'] + } + + // Sample gallery items - using mock instances with only required properties + const mockGalleryItems: MockResultItem[] = [ + { + filename: 'image1.jpg', + subfolder: 'outputs', + type: 'output', + nodeId: '123' as NodeId, + mediaType: 'images', + isImage: true, + isVideo: false, + url: 'image1.jpg', + id: '1' + }, + { + filename: 'image2.jpg', + subfolder: 'outputs', + type: 'output', + nodeId: '456' as NodeId, + mediaType: 'images', + isImage: true, + isVideo: false, + url: 'image2.jpg', + id: '2' + } + ] + + beforeEach(() => { + const app = createApp({}) + app.use(PrimeVue) + + // Create mock elements for Galleria to find + document.body.innerHTML = ` +
+ ` + }) + + afterEach(() => { + // Clean up any elements added to body + document.body.innerHTML = '' + vi.restoreAllMocks() + }) + + const mountGallery = (props = {}) => { + return mount(ResultGallery, { + global: { + plugins: [PrimeVue], + components: { + Galleria, + ComfyImage: mockComfyImage, + ResultVideo: mockResultVideo + }, + stubs: { + teleport: true + } + }, + props: { + allGalleryItems: mockGalleryItems as unknown as ResultItemImpl[], + activeIndex: 0, + ...props + }, + attachTo: document.getElementById('app') || undefined + }) + } + + it('renders Galleria component with correct props', async () => { + const wrapper = mountGallery() + + await nextTick() // Wait for component to mount + + const galleria = wrapper.findComponent(Galleria) + expect(galleria.exists()).toBe(true) + expect(galleria.props('value')).toEqual(mockGalleryItems) + expect(galleria.props('showIndicators')).toBe(false) + expect(galleria.props('showItemNavigators')).toBe(true) + expect(galleria.props('fullScreen')).toBe(true) + }) + + it('shows gallery when activeIndex changes from -1', async () => { + const wrapper = mountGallery({ activeIndex: -1 }) + + // Initially galleryVisible should be false + const vm: any = wrapper.vm + expect(vm.galleryVisible).toBe(false) + + // Change activeIndex + await wrapper.setProps({ activeIndex: 0 }) + await nextTick() + + // galleryVisible should become true + expect(vm.galleryVisible).toBe(true) + }) + + it('should render the component properly', () => { + // This is a meta-test to confirm the component mounts properly + const wrapper = mountGallery() + + // We can't directly test the compiled CSS, but we can verify the component renders + expect(wrapper.exists()).toBe(true) + + // Verify that the Galleria component exists and is properly mounted + const galleria = wrapper.findComponent(Galleria) + expect(galleria.exists()).toBe(true) + }) + + it('ensures correct configuration for mobile viewport', async () => { + // Mock window.matchMedia to simulate mobile viewport + Object.defineProperty(window, 'matchMedia', { + writable: true, + value: vi.fn().mockImplementation((query) => ({ + matches: query.includes('max-width: 768px'), + media: query, + onchange: null, + addListener: vi.fn(), + removeListener: vi.fn(), + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn() + })) + }) + + const wrapper = mountGallery() + await nextTick() + + // Verify mobile media query is working + expect(window.matchMedia('(max-width: 768px)').matches).toBe(true) + + // Check if the component renders with Galleria + const galleria = wrapper.findComponent(Galleria) + expect(galleria.exists()).toBe(true) + + // Check that our PT props for positioning work correctly + const pt = galleria.props('pt') as any + expect(pt?.prevButton?.style).toContain('position: fixed') + expect(pt?.nextButton?.style).toContain('position: fixed') + }) + + // Additional tests for interaction could be added once we can reliably + // test Galleria component in fullscreen mode +}) diff --git a/src/components/sidebar/tabs/queue/ResultGallery.vue b/src/components/sidebar/tabs/queue/ResultGallery.vue index 7309c901e..046e09a56 100644 --- a/src/components/sidebar/tabs/queue/ResultGallery.vue +++ b/src/components/sidebar/tabs/queue/ResultGallery.vue @@ -144,4 +144,12 @@ img.galleria-image { /* Set z-index so the close button doesn't get hidden behind the image when image is large */ z-index: 1; } + +/* Mobile/tablet specific fixes */ +@media screen and (max-width: 768px) { + .p-galleria-prev-button, + .p-galleria-next-button { + z-index: 2; + } +}