From c1068be22dd92d9d0a46fb4c30a4071134d00cb1 Mon Sep 17 00:00:00 2001 From: Patrick Varkoi Date: Mon, 3 Aug 2026 19:07:29 +0300 Subject: [PATCH 1/8] chore: update altLogoold.png image asset --- .../src/shared/assets/images/altLogoold.png | Bin 29397 -> 38165 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/frontend-next-migration/src/shared/assets/images/altLogoold.png b/frontend-next-migration/src/shared/assets/images/altLogoold.png index cc4540b40604e578db0d7ee20cfd5febad4f40fd..38843232035761b3ab41267cf43fe3478bceae2a 100644 GIT binary patch literal 38165 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 From d94467b4f3c6a097ef8a64591bcd9d6b64a2494f Mon Sep 17 00:00:00 2001 From: Patrick Varkoi Date: Fri, 28 Aug 2026 14:58:05 +0300 Subject: [PATCH 2/8] feat(hero): add status filter to hero API queries --- frontend-next-migration/src/entities/Hero/model/heroApi.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend-next-migration/src/entities/Hero/model/heroApi.ts b/frontend-next-migration/src/entities/Hero/model/heroApi.ts index c541e3cd4..27df1da04 100644 --- a/frontend-next-migration/src/entities/Hero/model/heroApi.ts +++ b/frontend-next-migration/src/entities/Hero/model/heroApi.ts @@ -46,6 +46,7 @@ function pickTranslationByLocale( const FIELDS = [ 'id', 'slug', + 'status', 'order', ...HERO_IMG_KEYS.flatMap((key) => [`${key}.id`, `${key}.width`, `${key}.height`]), ...HERO_GIF_KEYS.flatMap((key) => [`${key}.id`, `${key}.width`, `${key}.height`]), @@ -175,6 +176,7 @@ function mapHero(item: any, locale: Locale): HeroWithGroup { function buildParams(locale: Locale, options?: { slug?: string; limit?: string }) { const params = new URLSearchParams(); if (options?.slug) params.set('filter[slug][_eq]', options.slug); + params.set('filter[status][_eq]', 'Published'); if (options?.limit) params.set('limit', options.limit); params.set('fields', FIELDS); params.set('sort', 'order'); From 12defc6e0bcb1261c9ec0fa002052de1da5189a2 Mon Sep 17 00:00:00 2001 From: Patrick Varkoi Date: Wed, 2 Sep 2026 16:46:16 +0300 Subject: [PATCH 3/8] feat(hero): implement status gating for hero API queries and add tests --- .../Hero/model/buildHeroQueryParams.ts | 2 + .../src/entities/Hero/model/heroApi.test.ts | 82 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 frontend-next-migration/src/entities/Hero/model/heroApi.test.ts diff --git a/frontend-next-migration/src/entities/Hero/model/buildHeroQueryParams.ts b/frontend-next-migration/src/entities/Hero/model/buildHeroQueryParams.ts index c1504b3fc..f9434aea8 100644 --- a/frontend-next-migration/src/entities/Hero/model/buildHeroQueryParams.ts +++ b/frontend-next-migration/src/entities/Hero/model/buildHeroQueryParams.ts @@ -20,6 +20,7 @@ const languageCode = (locale: Locale): string => locale; export const FIELDS = [ 'id', 'slug', + 'status', 'order', ...HERO_IMG_KEYS.flatMap((key) => [`${key}.id`, `${key}.width`, `${key}.height`]), ...HERO_GIF_KEYS.flatMap((key) => [`${key}.id`, `${key}.width`, `${key}.height`]), @@ -67,6 +68,7 @@ export function buildHeroQueryParams( ): URLSearchParams { const params = new URLSearchParams(); if (options?.slug) params.set('filter[slug][_eq]', options.slug); + params.set('filter[status][_eq]', 'Published'); if (options?.limit) params.set('limit', options.limit); params.set('fields', FIELDS); params.set('sort', 'order'); diff --git a/frontend-next-migration/src/entities/Hero/model/heroApi.test.ts b/frontend-next-migration/src/entities/Hero/model/heroApi.test.ts new file mode 100644 index 000000000..23f70efce --- /dev/null +++ b/frontend-next-migration/src/entities/Hero/model/heroApi.test.ts @@ -0,0 +1,82 @@ +import { HeroGroup, HeroSlug } from '../types/hero'; +import { groupHeroesByGroup } from './groupHeroesByGroup'; +import { fetchAllHeroes, fetchHeroBySlug } from './heroApi'; + +jest.mock('@/shared/const/envHelper', () => ({ + envHelper: { directusHost: 'https://directus.test' }, +})); + +const publishedHero = { + id: 1, + slug: HeroSlug.OVEREATER, + status: 'Published', + order: 1, + translations: [ + { + languages_code: 'en-US', + title: 'Overeater', + description: 'Published hero', + alt: 'Overeater', + altGif: 'Overeater animated', + }, + ], + group: { + key: HeroGroup.RETROFLECTOR, + bgColour: '#000', + translations: [{ languages_code: 'en-US', name: 'Retroflector', description: 'Group' }], + }, + heroes_stats: [], +}; + +const response = (data: unknown) => + ({ + ok: true, + json: async () => ({ data }), + text: async () => JSON.stringify({ data }), + }) as Response; + +describe('heroApi status gating', () => { + const fetchMock = jest.fn() as jest.MockedFunction; + + beforeEach(() => { + fetchMock.mockReset(); + global.fetch = fetchMock; + }); + + it('does not return an unpublished hero by slug', async () => { + fetchMock.mockImplementation(async (input) => { + const url = new URL(String(input)); + expect(url.searchParams.get('filter[status][_eq]')).toBe('Published'); + return response([]); + }); + + await expect(fetchHeroBySlug('draft-hero' as HeroSlug)).resolves.toBeUndefined(); + }); + + it('returns only the published heroes from the all-heroes request', async () => { + fetchMock.mockImplementation(async (input) => { + const url = new URL(String(input)); + expect(url.searchParams.get('filter[status][_eq]')).toBe('Published'); + return response([publishedHero]); + }); + + const heroes = await fetchAllHeroes(); + + expect(heroes).toHaveLength(1); + expect(heroes[0].slug).toBe(HeroSlug.OVEREATER); + }); + + it('groups the published heroes returned by the filtered request', async () => { + fetchMock.mockImplementation(async (input) => { + const url = new URL(String(input)); + expect(url.searchParams.get('filter[status][_eq]')).toBe('Published'); + return response([publishedHero]); + }); + + const heroes = await fetchAllHeroes(); + const result = groupHeroesByGroup(heroes); + + expect(result[HeroGroup.RETROFLECTOR].heroes).toHaveLength(1); + expect(result[HeroGroup.RETROFLECTOR].heroes[0].slug).toBe(HeroSlug.OVEREATER); + }); +}); From c7f4be2f9e0d2c874dcd2cd3b9e65907f9fca196 Mon Sep 17 00:00:00 2001 From: Patrick Varkoi Date: Thu, 3 Sep 2026 18:25:49 +0300 Subject: [PATCH 4/8] feat(hero): enhance hero component with locale-based data fetching and loading states --- .../src/widgets/SectionHeroesBlocks/ui/index.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/frontend-next-migration/src/widgets/SectionHeroesBlocks/ui/index.tsx b/frontend-next-migration/src/widgets/SectionHeroesBlocks/ui/index.tsx index 12e81b7be..3bd97237e 100644 --- a/frontend-next-migration/src/widgets/SectionHeroesBlocks/ui/index.tsx +++ b/frontend-next-migration/src/widgets/SectionHeroesBlocks/ui/index.tsx @@ -1,11 +1,14 @@ 'use client'; +import React from 'react'; import { useInView } from 'react-intersection-observer'; import { HeroManager } from '@/entities/Hero'; +import { useGetHeroGroupsQuery } from '@/entities/Hero/model/heroApi'; import { Button, ButtonSize, ButtonTheme } from '@/shared/ui/Button'; import { classNames } from '@/shared/lib/classNames/classNames'; import { useClientTranslation } from '@/shared/i18n'; import { Container } from '@/shared/ui/Container'; import { AppLink } from '@/shared/ui/AppLink/AppLink'; +import { useParams } from 'next/navigation'; import HeroesBlocks from './heroesBlocks/HeroesBlocks'; import cls from './main.module.scss'; @@ -34,8 +37,16 @@ function Main(props: Props) { }; const { t } = useClientTranslation('heroes'); - const heroManager = new HeroManager(t); - const heroesGroups2 = heroManager.getGroupsWithHeroesAsArray(); + const params = useParams(); + const lng = (params?.lng as string) || 'en'; + const locale = (lng === 'en' ? 'en' : lng === 'fi' ? 'fi' : 'ru') as 'en' | 'fi' | 'ru'; + const { data: directusGroups, isError, isLoading } = useGetHeroGroupsQuery({ locale }); + const staticGroups = React.useMemo(() => new HeroManager(t).getGroupsWithHeroesAsArray(), [t]); + const heroesGroups2 = React.useMemo(() => { + if (isLoading) return []; + if (isError) return staticGroups; + return directusGroups ? Object.values(directusGroups) : []; + }, [directusGroups, isError, isLoading, staticGroups]); const displayedGroups = maxGroupsPerPage ? heroesGroups2.slice(0, maxGroupsPerPage) From 1934cb63f316a636f79f728ca8a2ce289d131a2b Mon Sep 17 00:00:00 2001 From: Patrick Varkoi Date: Fri, 4 Sep 2026 18:59:59 +0300 Subject: [PATCH 5/8] feat(hero): implement status gating for hero items in API responses and tests --- .../src/entities/Hero/model/heroApi.test.ts | 4 ++-- .../src/entities/Hero/model/heroApi.ts | 13 ++++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/frontend-next-migration/src/entities/Hero/model/heroApi.test.ts b/frontend-next-migration/src/entities/Hero/model/heroApi.test.ts index 23f70efce..b4bc3e3dc 100644 --- a/frontend-next-migration/src/entities/Hero/model/heroApi.test.ts +++ b/frontend-next-migration/src/entities/Hero/model/heroApi.test.ts @@ -43,11 +43,11 @@ describe('heroApi status gating', () => { global.fetch = fetchMock; }); - it('does not return an unpublished hero by slug', async () => { + it.each(['Draft', 'Archived'])('does not return a %s hero by slug', async (status) => { fetchMock.mockImplementation(async (input) => { const url = new URL(String(input)); expect(url.searchParams.get('filter[status][_eq]')).toBe('Published'); - return response([]); + return response([{ ...publishedHero, status }]); }); await expect(fetchHeroBySlug('draft-hero' as HeroSlug)).resolves.toBeUndefined(); diff --git a/frontend-next-migration/src/entities/Hero/model/heroApi.ts b/frontend-next-migration/src/entities/Hero/model/heroApi.ts index 27df1da04..68f8e9494 100644 --- a/frontend-next-migration/src/entities/Hero/model/heroApi.ts +++ b/frontend-next-migration/src/entities/Hero/model/heroApi.ts @@ -34,6 +34,7 @@ const normalizeLocale = (locale: Locale): string => { return localeMap[locale] || locale; }; const languageCode = (locale: Locale): string => normalizeLocale(locale); +const isPublished = (item: any): boolean => item?.status === 'Published'; function pickTranslationByLocale( arr: T[] | undefined, @@ -197,7 +198,9 @@ export const heroApi = directusApi.injectEndpoints({ }), transformResponse: (resp: any, _meta, args) => { const item = resp?.data?.[0]; - return item ? mapHero(item, (args?.locale ?? 'en') as Locale) : undefined; + return item && isPublished(item) + ? mapHero(item, (args?.locale ?? 'en') as Locale) + : undefined; }, providesTags: (_res, _err, args) => [{ type: 'Hero' as const, id: args.slug }], }), @@ -218,7 +221,7 @@ export const heroApi = directusApi.injectEndpoints({ url: `/items/heroes?${buildParams(locale, { limit: '-1' }).toString()}`, }), transformResponse: (resp: any, _meta, args) => { - const items = resp?.data || []; + const items = (resp?.data || []).filter(isPublished); return items.map((item: any) => mapHero(item, (args?.locale ?? 'en') as Locale)); }, providesTags: () => [{ type: 'Hero' as const, id: 'LIST' }], @@ -228,7 +231,7 @@ export const heroApi = directusApi.injectEndpoints({ url: `/items/heroes?${buildParams(locale, { limit: '-1' }).toString()}`, }), transformResponse: (resp: any, _meta, args) => { - const items = resp?.data || []; + const items = (resp?.data || []).filter(isPublished); const heroes = items.map((item: any) => mapHero(item, (args?.locale ?? 'en') as Locale), ); @@ -275,7 +278,7 @@ export async function fetchHeroBySlug( const json = await res.json(); const item = json?.data?.[0]; - if (!item) { + if (!item || !isPublished(item)) { // eslint-disable-next-line no-console console.warn( `[fetchHeroBySlug] No hero found in Directus for slug "${slug}" (response had ${json?.data?.length || 0} items)`, @@ -320,7 +323,7 @@ export async function fetchAllHeroes(locale: Locale = 'en'): Promise mapHero(item, locale)); } catch (error) { // eslint-disable-next-line no-console From 8c635d7228081dce9c910471aac2253a297054a5 Mon Sep 17 00:00:00 2001 From: Patrick Varkoi Date: Tue, 15 Sep 2026 18:35:24 +0300 Subject: [PATCH 6/8] Implemented the hero fallback enhancement. Successful empty Directus responses now remain empty, while static data is used only for actual request failures or unavailable Directus. Updated manager logic, hero detail routes, gallery/group views, navigation, and client detail loading. Added regression tests covering empty responses and failure fallback in heroApi.test.ts and HeroManager.test.ts. Validation: - Focused Jest tests: 9 passed - git diff --check: passed - TypeScript check: only existing unrelated test errors remain. --- .../hero-development/[slug]/_getPage.ts | 7 +--- .../[lng]/(helper)/heroes/[slug]/_getPage.ts | 16 ++------ .../entities/Hero/model/HeroManager.test.ts | 39 +++++++++++++++++++ .../src/entities/Hero/model/HeroManager.ts | 24 +++--------- .../src/entities/Hero/model/heroApi.test.ts | 12 ++++++ .../src/entities/Hero/model/heroApi.ts | 12 +++--- .../Hero/model/initializeHeroGroups.ts | 14 +------ .../ui/HeroGroupNavMenu/HeroGroupNavMenu.tsx | 9 ++--- .../ui/DefenseGalleryPage.tsx | 7 +--- .../ui/SingleDefensePage.tsx | 7 ++-- .../HeroesPages/ui/SingleHeroPage.tsx | 12 ++++-- 11 files changed, 85 insertions(+), 74 deletions(-) create mode 100644 frontend-next-migration/src/entities/Hero/model/HeroManager.test.ts diff --git a/frontend-next-migration/src/app/[lng]/(helper)/hero-development/[slug]/_getPage.ts b/frontend-next-migration/src/app/[lng]/(helper)/hero-development/[slug]/_getPage.ts index 6dc8f55af..60edc5f69 100644 --- a/frontend-next-migration/src/app/[lng]/(helper)/hero-development/[slug]/_getPage.ts +++ b/frontend-next-migration/src/app/[lng]/(helper)/hero-development/[slug]/_getPage.ts @@ -18,14 +18,11 @@ export async function _getPage(lng: string, slug: string) { // Initialize from Directus first await heroManager.initializeFromDirectus(lng as 'en' | 'fi' | 'ru'); - // Try to get hero from Directus, fallback to static data - let currentHero = await heroManager.getHeroBySlugAsync( + // Only a failed Directus request may use the static fallback. + const currentHero = await heroManager.getHeroBySlugAsync( slug as HeroSlug, lng as 'en' | 'fi' | 'ru', ); - if (!currentHero) { - currentHero = heroManager.getHeroBySlug(slug as HeroSlug); - } if (!currentHero) { notFound(); } diff --git a/frontend-next-migration/src/app/[lng]/(helper)/heroes/[slug]/_getPage.ts b/frontend-next-migration/src/app/[lng]/(helper)/heroes/[slug]/_getPage.ts index d56254297..58d00bddb 100644 --- a/frontend-next-migration/src/app/[lng]/(helper)/heroes/[slug]/_getPage.ts +++ b/frontend-next-migration/src/app/[lng]/(helper)/heroes/[slug]/_getPage.ts @@ -17,15 +17,8 @@ async function getCurrentHero( lng: string, ): Promise { const locale = lng as 'en' | 'fi' | 'ru'; - let currentHero = await heroManager.getHeroBySlugAsync(slug as HeroSlug, locale); + const currentHero = await heroManager.getHeroBySlugAsync(slug as HeroSlug, locale); if (!currentHero) { - // eslint-disable-next-line no-console - console.log(`[heroes/_getPage] Hero "${slug}" not found in Directus, trying static data`); - currentHero = heroManager.getHeroBySlug(slug as HeroSlug); - } - if (!currentHero) { - // eslint-disable-next-line no-console - console.error(`[heroes/_getPage] Hero "${slug}" not found in Directus or static data`); notFound(); } return currentHero; @@ -83,11 +76,8 @@ export async function _getPage(lng: string, slug: string) { // Get current hero with fallback const currentHero = await getCurrentHero(heroManager, slug, lng); - // Get all heroes for navigation (try Directus first, fallback to static) - let heroes = await heroManager.getAllHeroesFromDirectus(lng as 'en' | 'fi' | 'ru'); - if (heroes.length === 0) { - heroes = heroManager.getAllHeroes(); - } + // Get all published heroes for navigation. + const heroes = await heroManager.getAllHeroesFromDirectus(lng as 'en' | 'fi' | 'ru'); const { prevHero, nextHero } = getNavigationHeroes(heroes, currentHero); const prevHeroLink = getRouteOneHeroPage(prevHero.slug); diff --git a/frontend-next-migration/src/entities/Hero/model/HeroManager.test.ts b/frontend-next-migration/src/entities/Hero/model/HeroManager.test.ts new file mode 100644 index 000000000..db5b90b38 --- /dev/null +++ b/frontend-next-migration/src/entities/Hero/model/HeroManager.test.ts @@ -0,0 +1,39 @@ +import { HeroSlug } from '../types/hero'; +import { HeroManager } from './HeroManager'; +import { fetchAllHeroes, fetchHeroBySlug } from './heroApi'; + +jest.mock('./heroApi', () => ({ + fetchAllHeroes: jest.fn(), + fetchHeroBySlug: jest.fn(), +})); + +const mockedFetchAllHeroes = jest.mocked(fetchAllHeroes); +const mockedFetchHeroBySlug = jest.mocked(fetchHeroBySlug); + +describe('HeroManager Directus fallback behavior', () => { + const manager = () => new HeroManager((key) => key); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('does not use static heroes after a successful empty all-heroes response', async () => { + mockedFetchAllHeroes.mockResolvedValue([]); + + await expect(manager().getAllHeroesFromDirectus()).resolves.toEqual([]); + }); + + it('does not use a static hero after a successful no-match slug response', async () => { + mockedFetchHeroBySlug.mockResolvedValue(undefined); + + await expect(manager().getHeroBySlugAsync(HeroSlug.OVEREATER)).resolves.toBeUndefined(); + }); + + it('uses static heroes after a failed Directus request', async () => { + mockedFetchHeroBySlug.mockRejectedValue(new Error('network failure')); + + await expect(manager().getHeroBySlugAsync(HeroSlug.OVEREATER)).resolves.toEqual( + manager().getHeroBySlug(HeroSlug.OVEREATER), + ); + }); +}); diff --git a/frontend-next-migration/src/entities/Hero/model/HeroManager.ts b/frontend-next-migration/src/entities/Hero/model/HeroManager.ts index 69f8c06f9..5cf7e19fd 100644 --- a/frontend-next-migration/src/entities/Hero/model/HeroManager.ts +++ b/frontend-next-migration/src/entities/Hero/model/HeroManager.ts @@ -22,13 +22,8 @@ export class HeroManager { public async initializeFromDirectus(locale: Locale = 'en'): Promise { try { const directusGroups = await initializeHeroGroupsFromDirectus(locale); - // Only replace static data if Directus returned non-empty groups - if (directusGroups && Object.keys(directusGroups).length > 0) { - this.heroGroups = directusGroups; - this.heroesCache = null; // Clear cache to force recalculation - } else { - console.warn('[HeroManager] Directus returned empty groups, keeping static data'); - } + this.heroGroups = directusGroups; + this.heroesCache = null; // Clear cache to force recalculation } catch (error) { console.error('[HeroManager] Failed to initialize hero groups from Directus:', error); // Keep existing static data as fallback @@ -71,15 +66,7 @@ export class HeroManager { */ public async getAllHeroesFromDirectus(locale: Locale = 'en'): Promise { try { - const heroes = await fetchAllHeroes(locale); - // If Directus returns empty array, fallback to static data - if (heroes.length === 0) { - console.warn( - '[HeroManager] Directus returned empty heroes array, using static data', - ); - return this.getAllHeroes(); - } - return heroes; + return await fetchAllHeroes(locale); } catch (error) { console.error('[HeroManager] Failed to fetch all heroes from Directus:', error); // Fallback to static data @@ -114,12 +101,11 @@ export class HeroManager { locale: Locale = 'en', ): Promise { try { - const hero = await fetchHeroBySlug(slug, locale); - if (hero) return hero; + return await fetchHeroBySlug(slug, locale); } catch { // ignore error and fallback + return this.getHeroBySlug(slug); } - return this.getHeroBySlug(slug); } public getHeroesBySpecificGroup(group: HeroGroup): HeroWithGroup[] | undefined { diff --git a/frontend-next-migration/src/entities/Hero/model/heroApi.test.ts b/frontend-next-migration/src/entities/Hero/model/heroApi.test.ts index b4bc3e3dc..5cde26b82 100644 --- a/frontend-next-migration/src/entities/Hero/model/heroApi.test.ts +++ b/frontend-next-migration/src/entities/Hero/model/heroApi.test.ts @@ -66,6 +66,18 @@ describe('heroApi status gating', () => { expect(heroes[0].slug).toBe(HeroSlug.OVEREATER); }); + it('keeps a successful empty all-heroes response empty', async () => { + fetchMock.mockResolvedValue(response([])); + + await expect(fetchAllHeroes()).resolves.toEqual([]); + }); + + it('rejects when the all-heroes request fails', async () => { + fetchMock.mockResolvedValue({ ok: false, status: 503, text: async () => '' } as Response); + + await expect(fetchAllHeroes()).rejects.toThrow('Directus request failed with status 503'); + }); + it('groups the published heroes returned by the filtered request', async () => { fetchMock.mockImplementation(async (input) => { const url = new URL(String(input)); diff --git a/frontend-next-migration/src/entities/Hero/model/heroApi.ts b/frontend-next-migration/src/entities/Hero/model/heroApi.ts index 68f8e9494..ac958ae22 100644 --- a/frontend-next-migration/src/entities/Hero/model/heroApi.ts +++ b/frontend-next-migration/src/entities/Hero/model/heroApi.ts @@ -259,7 +259,7 @@ export async function fetchHeroBySlug( console.warn( '[fetchHeroBySlug] Directus host not configured - check NEXT_PUBLIC_DIRECTUS_HOST env var', ); - return undefined; + throw new Error('Directus host is not configured'); } const params = buildParams(locale, { slug, limit: '1' }); @@ -273,7 +273,7 @@ export async function fetchHeroBySlug( console.warn( `[fetchHeroBySlug] Directus error ${res.status} for "${slug}": ${errorText.substring(0, 200)}`, ); - return undefined; + throw new Error(`Directus request failed with status ${res.status}`); } const json = await res.json(); @@ -293,7 +293,7 @@ export async function fetchHeroBySlug( `[fetchHeroBySlug] ✗ Error fetching "${slug}":`, error instanceof Error ? error.message : error, ); - return undefined; + throw error; } } @@ -304,7 +304,7 @@ export async function fetchAllHeroes(locale: Locale = 'en'): Promise string): Record> { - try { - const heroes = await fetchAllHeroes(locale); - return groupHeroesByGroup(heroes); - } catch (error) { - // eslint-disable-next-line no-console - console.error( - '[initializeHeroGroupsFromDirectus] Failed to fetch hero groups from Directus:', - error, - ); - // Return empty record - caller should check and keep static data - return {} as Record; - } + const heroes = await fetchAllHeroes(locale); + return groupHeroesByGroup(heroes); } diff --git a/frontend-next-migration/src/features/NavigateHeroGroups/ui/HeroGroupNavMenu/HeroGroupNavMenu.tsx b/frontend-next-migration/src/features/NavigateHeroGroups/ui/HeroGroupNavMenu/HeroGroupNavMenu.tsx index 9a9cdc70f..3ba6866c1 100644 --- a/frontend-next-migration/src/features/NavigateHeroGroups/ui/HeroGroupNavMenu/HeroGroupNavMenu.tsx +++ b/frontend-next-migration/src/features/NavigateHeroGroups/ui/HeroGroupNavMenu/HeroGroupNavMenu.tsx @@ -25,14 +25,11 @@ const HeroGroupNavMenu: React.FC = ({ className: _classNa const selectedHeroGroup = pathname.split('/')[3]; // Try to fetch from Directus first, fallback to static data - const { data: directusGroups } = useGetHeroGroupsQuery({ locale }); + const { data: directusGroups, isError } = useGetHeroGroupsQuery({ locale }); const staticGroups = React.useMemo(() => initializeHeroGroups(t), [t]); const allHeroGroups = React.useMemo(() => { - if (directusGroups && Object.keys(directusGroups).length > 0) { - return directusGroups; - } - return staticGroups; - }, [directusGroups, staticGroups]); + return isError ? staticGroups : (directusGroups ?? ({} as typeof staticGroups)); + }, [directusGroups, isError, staticGroups]); function capitalizeString(inputString: HeroGroup | string) { if (!inputString) return ''; diff --git a/frontend-next-migration/src/preparedPages/DefenseGalleryPages/ui/DefenseGalleryPage.tsx b/frontend-next-migration/src/preparedPages/DefenseGalleryPages/ui/DefenseGalleryPage.tsx index 3c7a39649..44b2514b4 100644 --- a/frontend-next-migration/src/preparedPages/DefenseGalleryPages/ui/DefenseGalleryPage.tsx +++ b/frontend-next-migration/src/preparedPages/DefenseGalleryPages/ui/DefenseGalleryPage.tsx @@ -27,7 +27,7 @@ const DefenseGalleryPage = () => { const lng = (params?.lng as string) || 'en'; const locale = (lng === 'en' ? 'en' : lng === 'fi' ? 'fi' : 'ru') as 'en' | 'fi' | 'ru'; - // Try to fetch from Directus first, fallback to static data + // Try to fetch from Directus const { data: directusGroups, isError, error } = useGetHeroGroupsQuery({ locale }); const staticGroups = React.useMemo(() => initializeHeroGroups(t), [t]); const heroGroups = React.useMemo(() => { @@ -36,10 +36,7 @@ const DefenseGalleryPage = () => { console.warn('[DefenseGalleryPage] Directus query failed, using static data:', error); return staticGroups; } - if (directusGroups && Object.keys(directusGroups).length > 0) { - return directusGroups; - } - return staticGroups; + return directusGroups ?? {}; }, [directusGroups, staticGroups, isError, error]); // Create an array of all heroes with their group information diff --git a/frontend-next-migration/src/preparedPages/DefenseGalleryPages/ui/SingleDefensePage.tsx b/frontend-next-migration/src/preparedPages/DefenseGalleryPages/ui/SingleDefensePage.tsx index 19cfa12ba..3f1f67111 100644 --- a/frontend-next-migration/src/preparedPages/DefenseGalleryPages/ui/SingleDefensePage.tsx +++ b/frontend-next-migration/src/preparedPages/DefenseGalleryPages/ui/SingleDefensePage.tsx @@ -229,8 +229,6 @@ function useHeroGroupsWithFallback( ): Record { const { data: directusGroups, isError, error } = useGetHeroGroupsQuery({ locale }); const staticGroups = React.useMemo(() => initializeHeroGroups(t), [t]); - // Use Directus data only if it exists, has keys, and there's no error - const hasDirectusData = !isError && directusGroups && Object.keys(directusGroups).length > 0; if (isError) { // eslint-disable-next-line no-console console.warn( @@ -239,8 +237,9 @@ function useHeroGroupsWithFallback( ); } return React.useMemo( - () => (hasDirectusData ? directusGroups : staticGroups), - [hasDirectusData, directusGroups, staticGroups], + () => + isError ? staticGroups : (directusGroups ?? ({} as Record)), + [directusGroups, isError, staticGroups], ); } diff --git a/frontend-next-migration/src/preparedPages/HeroesPages/ui/SingleHeroPage.tsx b/frontend-next-migration/src/preparedPages/HeroesPages/ui/SingleHeroPage.tsx index 5adff5ad7..e6938ed38 100644 --- a/frontend-next-migration/src/preparedPages/HeroesPages/ui/SingleHeroPage.tsx +++ b/frontend-next-migration/src/preparedPages/HeroesPages/ui/SingleHeroPage.tsx @@ -56,7 +56,7 @@ const SingleHeroPage = (props: Props) => { const { isMobileSize, isTabletSize } = useSizes(); // Try to fetch from Directus if hero not provided from server - const { data: directusHero } = useGetHeroBySlugQuery( + const { data: directusHero, isError: isDirectusHeroError } = useGetHeroBySlugQuery( { slug, locale }, { skip: !!newSelectedHero }, ); @@ -73,7 +73,7 @@ const SingleHeroPage = (props: Props) => { // Determine selected hero and its group to populate localized titles const { titleText, hero } = useMemo<{ titleText: string; hero?: Hero }>(() => { - // Priority: 1. Server-provided hero, 2. Directus hero, 3. Static data fallback + // Priority: 1. Server-provided hero, 2. Directus hero, 3. static fallback after failure const selectedHero = newSelectedHero || directusHero; if (selectedHero) { // Merge Directus stats (levels) with baseline tiers (rarityClass) from the hero data. @@ -109,7 +109,11 @@ const SingleHeroPage = (props: Props) => { }; } - // Fallback to static data + if (!isDirectusHeroError) { + return { titleText: '', hero: undefined }; + } + + // Fall back to static data only when the Directus request failed. const heroGroups = initializeHeroGroups(t); for (const groupKey in heroGroups) { const group = heroGroups[groupKey as keyof typeof heroGroups]; @@ -122,7 +126,7 @@ const SingleHeroPage = (props: Props) => { } } return { titleText: '', hero: undefined }; - }, [newSelectedHero, directusHero, directusStats, slug, t]); + }, [newSelectedHero, directusHero, directusStats, isDirectusHeroError, slug, t]); const rarityLabel = useMemo(() => { if (!hero?.rarityClass) return ''; From 612a2b9ceb3cb71d4543425799483e667f08168b Mon Sep 17 00:00:00 2001 From: Patrick Varkoi Date: Tue, 22 Sep 2026 15:07:02 +0300 Subject: [PATCH 7/8] refactor(hero): remove fallback to static data if Directus returns empty Fixed defense-gallery/[herogroup]/_getPage.ts. Successful empty Directus responses now remain empty, including metadata/SEO generation. Static groups are used only when the Directus request throws. Validation passed: - Hero regression tests: 9 passed - Diagnostics: no errors in the touched route - git diff --check: passed --- .../[lng]/(helper)/defense-gallery/[herogroup]/_getPage.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/frontend-next-migration/src/app/[lng]/(helper)/defense-gallery/[herogroup]/_getPage.ts b/frontend-next-migration/src/app/[lng]/(helper)/defense-gallery/[herogroup]/_getPage.ts index c3d5fea3e..2635c4b24 100644 --- a/frontend-next-migration/src/app/[lng]/(helper)/defense-gallery/[herogroup]/_getPage.ts +++ b/frontend-next-migration/src/app/[lng]/(helper)/defense-gallery/[herogroup]/_getPage.ts @@ -34,10 +34,6 @@ export async function _getPage(lng: string, heroGroup: string) { let groups: Record; try { groups = await initializeHeroGroupsFromDirectus(lng as 'en' | 'fi' | 'ru'); - // If Directus returns empty, fallback to static - if (Object.keys(groups).length === 0) { - groups = initializeHeroGroups(t); - } } catch { groups = initializeHeroGroups(t); } From cd19cc0ac4642e189c4192778270f61abd4a82af Mon Sep 17 00:00:00 2001 From: Patrick Varkoi Date: Tue, 22 Sep 2026 18:23:48 +0300 Subject: [PATCH 8/8] test(hero): add unit tests for hero group initialization and navigation components --- .../entities/Hero/model/HeroManager.test.ts | 26 +++ .../Hero/model/initializeHeroGroups.test.ts | 38 +++++ .../HeroGroupNavMenu.test.tsx | 101 +++++++++++ .../SectionHeroesBlocks/ui/index.test.tsx | 160 ++++++++++++++++++ 4 files changed, 325 insertions(+) create mode 100644 frontend-next-migration/src/entities/Hero/model/initializeHeroGroups.test.ts create mode 100644 frontend-next-migration/src/features/NavigateHeroGroups/ui/HeroGroupNavMenu/HeroGroupNavMenu.test.tsx create mode 100644 frontend-next-migration/src/widgets/SectionHeroesBlocks/ui/index.test.tsx diff --git a/frontend-next-migration/src/entities/Hero/model/HeroManager.test.ts b/frontend-next-migration/src/entities/Hero/model/HeroManager.test.ts index db5b90b38..2338dbed7 100644 --- a/frontend-next-migration/src/entities/Hero/model/HeroManager.test.ts +++ b/frontend-next-migration/src/entities/Hero/model/HeroManager.test.ts @@ -23,6 +23,32 @@ describe('HeroManager Directus fallback behavior', () => { await expect(manager().getAllHeroesFromDirectus()).resolves.toEqual([]); }); + it('uses static heroes after a failed all-heroes request', async () => { + mockedFetchAllHeroes.mockRejectedValue(new Error('network failure')); + + await expect(manager().getAllHeroesFromDirectus()).resolves.toEqual( + manager().getAllHeroes(), + ); + }); + + it('keeps groups empty after a successful empty initialization response', async () => { + mockedFetchAllHeroes.mockResolvedValue([]); + const heroManager = manager(); + + await heroManager.initializeFromDirectus(); + + expect(heroManager.getGroupsWithHeroes()).toEqual({}); + }); + + it('keeps static groups after a failed initialization request', async () => { + mockedFetchAllHeroes.mockRejectedValue(new Error('network failure')); + const heroManager = manager(); + + await heroManager.initializeFromDirectus(); + + expect(Object.keys(heroManager.getGroupsWithHeroes()).length).toBeGreaterThan(0); + }); + it('does not use a static hero after a successful no-match slug response', async () => { mockedFetchHeroBySlug.mockResolvedValue(undefined); diff --git a/frontend-next-migration/src/entities/Hero/model/initializeHeroGroups.test.ts b/frontend-next-migration/src/entities/Hero/model/initializeHeroGroups.test.ts new file mode 100644 index 000000000..f436e9bf3 --- /dev/null +++ b/frontend-next-migration/src/entities/Hero/model/initializeHeroGroups.test.ts @@ -0,0 +1,38 @@ +import { fetchAllHeroes } from './heroApi'; +import { initializeHeroGroupsFromDirectus } from './initializeHeroGroups'; +import { HeroGroup, HeroSlug } from '../types/hero'; + +jest.mock('./heroApi', () => ({ + fetchAllHeroes: jest.fn(), +})); + +const mockedFetchAllHeroes = jest.mocked(fetchAllHeroes); + +describe('initializeHeroGroupsFromDirectus', () => { + it('groups the heroes returned by Directus', async () => { + mockedFetchAllHeroes.mockResolvedValue([ + { + id: 1, + slug: HeroSlug.OVEREATER, + title: 'Overeater', + groupEnum: HeroGroup.RETROFLECTOR, + groupName: 'Retroflector', + groupDescription: 'Group description', + groupBgColour: '#000', + groupLabel: '', + srcImg: '', + srcGif: '', + alt: '', + altGif: '', + rarityClass: 'common', + description: '', + stats: [], + }, + ]); + + const groups = await initializeHeroGroupsFromDirectus('en'); + + expect(groups.RETROFLECTOR.heroes).toHaveLength(1); + expect(groups.RETROFLECTOR.heroes[0].title).toBe('Overeater'); + }); +}); diff --git a/frontend-next-migration/src/features/NavigateHeroGroups/ui/HeroGroupNavMenu/HeroGroupNavMenu.test.tsx b/frontend-next-migration/src/features/NavigateHeroGroups/ui/HeroGroupNavMenu/HeroGroupNavMenu.test.tsx new file mode 100644 index 000000000..86a282750 --- /dev/null +++ b/frontend-next-migration/src/features/NavigateHeroGroups/ui/HeroGroupNavMenu/HeroGroupNavMenu.test.tsx @@ -0,0 +1,101 @@ +import { render, screen } from '@testing-library/react'; +import HeroGroupNavMenu from './HeroGroupNavMenu'; +import { useGetHeroGroupsQuery } from '@/entities/Hero/model/heroApi'; +import { useParams } from 'next/navigation'; + +jest.mock('next/navigation', () => ({ + usePathname: () => '/en/defense-gallery/RETROFLECTOR', + useParams: jest.fn(() => ({ lng: 'en' })), +})); + +jest.mock('@/shared/i18n', () => ({ + useClientTranslation: () => ({ t: (key: string) => key }), +})); + +jest.mock('@/entities/Hero/model/heroApi', () => ({ + useGetHeroGroupsQuery: jest.fn(), +})); + +jest.mock('@/entities/Hero/model/initializeHeroGroups', () => ({ + initializeHeroGroups: () => ({ + RETROFLECTOR: { name: 'Static group', heroes: [] }, + }), +})); + +jest.mock('@/shared/ui/NavMenuWithDropdownsV2', () => ({ + NavMenuWithDropdowns: ({ + title, + dropdownItems, + }: { + title: string; + dropdownItems: { elementText: string }[]; + }) => ( +
+ {title} + {dropdownItems.map((item) => ( + {item.elementText} + ))} +
+ ), +})); + +const mockedUseGetHeroGroupsQuery = jest.mocked(useGetHeroGroupsQuery); +const mockedUseParams = jest.mocked(useParams); + +describe('HeroGroupNavMenu', () => { + it('uses static groups when Directus fails', () => { + mockedUseGetHeroGroupsQuery.mockReturnValue({ isError: true } as unknown as ReturnType< + typeof useGetHeroGroupsQuery + >); + + render(); + + expect(screen.getByText('Static group')).toBeInTheDocument(); + }); + + it('uses published Directus groups when the request succeeds', () => { + mockedUseGetHeroGroupsQuery.mockReturnValue({ + isError: false, + data: { RETROFLECTOR: { name: 'directus group', heroes: [] } }, + } as unknown as ReturnType); + + render(); + + expect(screen.getByText('Directus group')).toBeInTheDocument(); + }); + + it('keeps the navigation empty after a successful empty response', () => { + mockedUseGetHeroGroupsQuery.mockReturnValue({ + isError: false, + data: undefined, + } as unknown as ReturnType); + + render(); + + expect(screen.queryByText('Static group')).not.toBeInTheDocument(); + }); + + it.each(['fi', 'ru'])('maps the %s route locale for Directus', (lng) => { + mockedUseParams.mockReturnValue({ lng }); + mockedUseGetHeroGroupsQuery.mockReturnValue({ + isError: false, + data: {}, + } as unknown as ReturnType); + + render(); + + expect(mockedUseGetHeroGroupsQuery).toHaveBeenLastCalledWith({ locale: lng }); + }); + + it('defaults to English and handles an empty group name', () => { + mockedUseParams.mockReturnValue({}); + mockedUseGetHeroGroupsQuery.mockReturnValue({ + isError: false, + data: { RETROFLECTOR: { name: '', heroes: [] } }, + } as unknown as ReturnType); + + render(); + + expect(mockedUseGetHeroGroupsQuery).toHaveBeenLastCalledWith({ locale: 'en' }); + }); +}); diff --git a/frontend-next-migration/src/widgets/SectionHeroesBlocks/ui/index.test.tsx b/frontend-next-migration/src/widgets/SectionHeroesBlocks/ui/index.test.tsx new file mode 100644 index 000000000..c41bad158 --- /dev/null +++ b/frontend-next-migration/src/widgets/SectionHeroesBlocks/ui/index.test.tsx @@ -0,0 +1,160 @@ +import { render, screen } from '@testing-library/react'; +import Main from './index'; +import { useGetHeroGroupsQuery } from '@/entities/Hero/model/heroApi'; +import { useParams } from 'next/navigation'; + +jest.mock('react-intersection-observer', () => ({ + useInView: () => ({ ref: jest.fn(), inView: false }), +})); + +jest.mock('next/navigation', () => ({ + useParams: jest.fn(() => ({ lng: 'en' })), +})); + +jest.mock('@/shared/i18n', () => ({ + useClientTranslation: () => ({ t: (key: string) => key }), +})); + +jest.mock('@/entities/Hero/model/heroApi', () => ({ + useGetHeroGroupsQuery: jest.fn(), +})); + +jest.mock('@/entities/Hero', () => ({ + HeroManager: jest.fn().mockImplementation(() => ({ + getGroupsWithHeroesAsArray: () => [ + { name: 'Static group', heroes: [], label: '', bgColour: '#000' }, + ], + })), +})); + +jest.mock('./heroesBlocks/HeroesBlocks', () => ({ + __esModule: true, + default: ({ labelText }: { labelText: string }) => ( +
{labelText}
+ ), +})); + +jest.mock('@/shared/ui/Container', () => ({ + Container: ({ children }: { children: React.ReactNode }) =>
{children}
, +})); + +jest.mock('@/shared/ui/Button', () => ({ + Button: jest + .requireActual('react') + .forwardRef( + ({ children }: { children: React.ReactNode }, ref: React.Ref) => ( + + ), + ), + ButtonSize: { XL: 'XL' }, + ButtonTheme: { Graffiti: 'Graffiti' }, +})); + +jest.mock('@/shared/ui/AppLink/AppLink', () => ({ + AppLink: ({ children }: { children: React.ReactNode }) => {children}, +})); + +const mockedUseGetHeroGroupsQuery = jest.mocked(useGetHeroGroupsQuery); +const mockedUseParams = jest.mocked(useParams); + +describe('SectionHeroesBlocks', () => { + it('renders no groups while Directus is loading', () => { + mockedUseGetHeroGroupsQuery.mockReturnValue({ isLoading: true } as unknown as ReturnType< + typeof useGetHeroGroupsQuery + >); + + render(
); + + expect(screen.queryByTestId('hero-group')).not.toBeInTheDocument(); + }); + + it('uses static groups after a Directus error', () => { + mockedUseGetHeroGroupsQuery.mockReturnValue({ + isLoading: false, + isError: true, + } as unknown as ReturnType); + + render(
); + + expect(screen.getByText('Static group')).toBeInTheDocument(); + }); + + it('renders Directus groups and the see-more link after success', () => { + mockedUseGetHeroGroupsQuery.mockReturnValue({ + isLoading: false, + isError: false, + data: { + RETROFLECTOR: { name: 'Directus group', heroes: [], label: '', bgColour: '#000' }, + }, + } as unknown as ReturnType); + + render( +
, + ); + + expect(screen.getByText('Directus group')).toBeInTheDocument(); + expect(screen.getByText('See more')).toBeInTheDocument(); + }); + + it('keeps the group list empty after a successful empty response', () => { + mockedUseGetHeroGroupsQuery.mockReturnValue({ + isLoading: false, + isError: false, + data: undefined, + } as unknown as ReturnType); + + render(
); + + expect(screen.queryByTestId('hero-group')).not.toBeInTheDocument(); + }); + + it('does not limit groups when maxGroupsPerPage is zero', () => { + mockedUseGetHeroGroupsQuery.mockReturnValue({ + isLoading: false, + isError: false, + data: { + RETROFLECTOR: { name: 'First group', heroes: [], label: '', bgColour: '#000' }, + DESENSITIZER: { name: 'Second group', heroes: [], label: '', bgColour: '#000' }, + }, + } as unknown as ReturnType); + + render( +
, + ); + + expect(screen.getAllByTestId('hero-group')).toHaveLength(2); + }); + + it.each(['fi', 'ru'])('maps the %s route locale for Directus', (lng) => { + mockedUseParams.mockReturnValue({ lng }); + mockedUseGetHeroGroupsQuery.mockReturnValue({ + isLoading: false, + isError: false, + data: undefined, + } as unknown as ReturnType); + + render(
); + + expect(mockedUseGetHeroGroupsQuery).toHaveBeenLastCalledWith({ locale: lng }); + }); + + it('defaults to English when the route has no locale', () => { + mockedUseParams.mockReturnValue({}); + mockedUseGetHeroGroupsQuery.mockReturnValue({ + isLoading: false, + isError: false, + data: undefined, + } as unknown as ReturnType); + + render(
); + + expect(mockedUseGetHeroGroupsQuery).toHaveBeenLastCalledWith({ locale: 'en' }); + }); +});