diff --git a/web/app/components/explore/app-list/loading-skeletons.tsx b/web/app/components/explore/app-list/loading-skeletons.tsx index f63ec0e777..8ffac62cba 100644 --- a/web/app/components/explore/app-list/loading-skeletons.tsx +++ b/web/app/components/explore/app-list/loading-skeletons.tsx @@ -73,23 +73,26 @@ export function ExploreHeaderSkeleton() { function ExploreAppCardSkeleton() { return (
- - +
+
-
- - -
- -
- -
-
- - +
+ +
- +
+
+
+ + +
+
+
+
+ +
+
) } diff --git a/web/app/components/explore/banner/__tests__/banner.spec.tsx b/web/app/components/explore/banner/__tests__/banner.spec.tsx index ac8928703f..1fc0a047fc 100644 --- a/web/app/components/explore/banner/__tests__/banner.spec.tsx +++ b/web/app/components/explore/banner/__tests__/banner.spec.tsx @@ -155,11 +155,10 @@ describe('Banner', () => { render() - const loadingWrapper = document.querySelector('[style*="min-height"]') - expect(loadingWrapper).toBeInTheDocument() + expect(screen.getByRole('status', { name: 'loading' })).toBeInTheDocument() }) - it('shows loading indicator with correct minimum height', () => { + it('matches the loaded greeting card shell while loading', () => { mockUseGetBanners.mockReturnValue({ data: null, isLoading: true, @@ -168,39 +167,42 @@ describe('Banner', () => { render() - const loadingWrapper = document.querySelector('[style*="min-height: 168px"]') - expect(loadingWrapper).toBeInTheDocument() + const loadingWrapper = screen.getByRole('status', { name: 'loading' }) + expect(loadingWrapper).toHaveClass('rounded-[24px]', 'bg-background-default-dodge') + expect(loadingWrapper.querySelector('.px-8.pt-8.pb-8')).toBeInTheDocument() }) }) describe('error state', () => { - it('returns null when isError is true', () => { + it('renders the greeting shell without slider when isError is true', () => { mockUseGetBanners.mockReturnValue({ data: null, isLoading: false, isError: true, }) - const { container } = render() + render() - expect(container.firstChild).toBeNull() + expect(screen.getByText('Welcome back, Evan 👋')).toBeInTheDocument() + expect(screen.queryByTestId('carousel')).not.toBeInTheDocument() }) }) describe('empty state', () => { - it('returns null when banners array is empty', () => { + it('renders the greeting shell without slider when banners array is empty', () => { mockUseGetBanners.mockReturnValue({ data: [], isLoading: false, isError: false, }) - const { container } = render() + render() - expect(container.firstChild).toBeNull() + expect(screen.getByText('Welcome back, Evan 👋')).toBeInTheDocument() + expect(screen.queryByTestId('carousel')).not.toBeInTheDocument() }) - it('returns null when all banners are disabled', () => { + it('renders the greeting shell without slider when all banners are disabled', () => { mockUseGetBanners.mockReturnValue({ data: [ createMockBanner('1', 'disabled'), @@ -210,21 +212,23 @@ describe('Banner', () => { isError: false, }) - const { container } = render() + render() - expect(container.firstChild).toBeNull() + expect(screen.getByText('Welcome back, Evan 👋')).toBeInTheDocument() + expect(screen.queryByTestId('carousel')).not.toBeInTheDocument() }) - it('returns null when data is undefined', () => { + it('renders the greeting shell without slider when data is undefined', () => { mockUseGetBanners.mockReturnValue({ data: undefined, isLoading: false, isError: false, }) - const { container } = render() + render() - expect(container.firstChild).toBeNull() + expect(screen.getByText('Welcome back, Evan 👋')).toBeInTheDocument() + expect(screen.queryByTestId('carousel')).not.toBeInTheDocument() }) }) diff --git a/web/app/components/explore/banner/banner.tsx b/web/app/components/explore/banner/banner.tsx index a05c0e34a2..1808921699 100644 --- a/web/app/components/explore/banner/banner.tsx +++ b/web/app/components/explore/banner/banner.tsx @@ -6,24 +6,31 @@ import { useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { trackEvent } from '@/app/components/base/amplitude' import { Carousel, useCarousel } from '@/app/components/base/carousel' +import { SkeletonRectangle } from '@/app/components/base/skeleton' import { useSelector } from '@/context/app-context' import { useLocale } from '@/context/i18n' import { useGetBanners } from '@/service/use-explore' -import Loading from '../../base/loading' import { BannerItem } from './banner-item' const AUTOPLAY_DELAY = 5000 -const MIN_LOADING_HEIGHT = 168 const RESIZE_DEBOUNCE_DELAY = 50 -const LoadingState: FC = () => ( -
- -
-) +const LoadingState: FC = () => { + const { t } = useTranslation() + + return ( +
+
+ + +
+
+ ) +} type BannerImpressionTrackerProps = { banners: BannerType[]