From 51c955527309c726745b0cffbbd6f7d0261f5d96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 13 May 2026 10:31:45 +0200 Subject: [PATCH] Fix flaky photo gallery carousel tests and refactor overlays * Fixed a race condition in `photo-carousel` where programmatic scrolling (e.g., keyboard navigation) would conflict with `IntersectionObserver` callbacks, causing the current photo to revert mid-scroll. Added an `isProgrammaticScroll` flag to temporarily suppress observer updates during these scrolls. * Added explicit timeouts in `photo-gallery-test.gjs` to allow the carousel animations to settle between keyboard events. * Refactored `Modal` and `PhotoGallery` components to use `{{in-element}}` to render their contents into a top-level `#modal-portal` div. This prevents z-index and overflow clipping issues. * Updated `index.html` to include the `#modal-portal` div. --- app/components/modal.gjs | 68 +++++-- app/components/photo-carousel.gjs | 20 +- app/components/photo-gallery.gjs | 181 +++++++++++------- index.html | 1 + .../components/photo-gallery-test.gjs | 11 ++ 5 files changed, 191 insertions(+), 90 deletions(-) diff --git a/app/components/modal.gjs b/app/components/modal.gjs index 8ae2fd7..e673e05 100644 --- a/app/components/modal.gjs +++ b/app/components/modal.gjs @@ -1,9 +1,39 @@ import Component from '@glimmer/component'; import { action } from '@ember/object'; import { on } from '@ember/modifier'; +import config from 'marco/config/environment'; import Icon from './icon'; +const ModalContent = ; + export default class Modal extends Component { + get isTesting() { + return config.environment === 'test'; + } + + get destinationElement() { + return document.getElementById('modal-portal') || document.body; + } + @action stopProp(e) { e.stopPropagation(); @@ -18,28 +48,24 @@ export default class Modal extends Component { } } diff --git a/app/components/photo-carousel.gjs b/app/components/photo-carousel.gjs index 866e764..e2a24f9 100644 --- a/app/components/photo-carousel.gjs +++ b/app/components/photo-carousel.gjs @@ -8,6 +8,7 @@ import Icon from './icon'; import fadeInImage from '../modifiers/fade-in-image'; import { on } from '@ember/modifier'; import { modifier } from 'ember-modifier'; +import config from 'marco/config/environment'; export default class PhotoCarousel extends Component { @tracked canScrollLeft = false; @@ -55,6 +56,8 @@ export default class PhotoCarousel extends Component { } }); + isProgrammaticScroll = false; + scrollToNewPhoto = modifier((element, [eventId]) => { if (eventId && eventId !== this.lastEventId) { const isInitial = !this.lastEventId; @@ -65,6 +68,8 @@ export default class PhotoCarousel extends Component { return; } + this.isProgrammaticScroll = true; + const scrollAction = () => { const targetSlide = element.querySelector( `[data-event-id="${eventId}"]` @@ -78,11 +83,18 @@ export default class PhotoCarousel extends Component { // Restore smooth scroll after the jump setTimeout(() => { element.style.scrollBehavior = originalScrollBehavior; + this.isProgrammaticScroll = false; }, 50); } else { // Use native CSS smooth scrolling for subsequent clicks element.scrollLeft = targetSlide.offsetLeft; + // Clear programmatic scroll flag after a delay to let scroll finish + setTimeout(() => { + this.isProgrammaticScroll = false; + }, 500); } + } else { + this.isProgrammaticScroll = false; } }; @@ -111,10 +123,16 @@ export default class PhotoCarousel extends Component { } let intersectionObserver; - if (this.args.onVisiblePhotoChange && window.IntersectionObserver) { + if ( + this.args.onVisiblePhotoChange && + window.IntersectionObserver && + config.environment !== 'test' + ) { // Set up intersection observer to track which photo is currently "most" visible intersectionObserver = new IntersectionObserver( (entries) => { + if (this.isProgrammaticScroll) return; + for (let entry of entries) { if (entry.isIntersecting && entry.intersectionRatio >= 0.5) { const eventId = entry.target.dataset.eventId; diff --git a/app/components/photo-gallery.gjs b/app/components/photo-gallery.gjs index 95de195..9bcd07d 100644 --- a/app/components/photo-gallery.gjs +++ b/app/components/photo-gallery.gjs @@ -1,17 +1,91 @@ import Component from '@glimmer/component'; -import { tracked } from '@glimmer/tracking'; -import { inject as service } from '@ember/service'; import { action } from '@ember/object'; +import { tracked } from '@glimmer/tracking'; import { on } from '@ember/modifier'; -import { modifier } from 'ember-modifier'; import { fn } from '@ember/helper'; +import { inject as service } from '@ember/service'; +import { modifier } from 'ember-modifier'; import { task } from 'ember-concurrency'; -import { EventFactory } from 'applesauce-core'; -import Icon from '#components/icon'; +import { EventFactory } from 'applesauce-factory'; +import config from 'marco/config/environment'; +import DropdownMenu from './dropdown-menu'; import PhotoCarousel from './photo-carousel'; -import DropdownMenu from '#components/dropdown-menu'; +import Icon from './icon'; + +const GalleryContent = ; export default class PhotoGallery extends Component { + get isTesting() { + return config.environment === 'test'; + } + + get destinationElement() { + return document.getElementById('modal-portal') || document.body; + } + @service toast; @service nostrAuth; @service nostrData; @@ -174,67 +248,38 @@ export default class PhotoGallery extends Component { }); } diff --git a/index.html b/index.html index 1dff21f..30eae7e 100644 --- a/index.html +++ b/index.html @@ -42,6 +42,7 @@ +