diff --git a/superset-frontend/src/features/home/Menu.test.tsx b/superset-frontend/src/features/home/Menu.test.tsx index 81babfb00da..45335087f4c 100644 --- a/superset-frontend/src/features/home/Menu.test.tsx +++ b/superset-frontend/src/features/home/Menu.test.tsx @@ -841,8 +841,12 @@ test('brand link falls back to brand.path when theme brandLogoUrl is absent', as // already has the root stripped — the value the production Router will then // safely re-prepend. +describe('brand link single-prefix regressions (subdirectory deployment)', () => { + beforeEach(() => { + observedGenericLinkTo = null; + }); + test('brand link hands a root-stripped path to GenericLink when brand.path arrives already rooted (SPA route)', async () => { - observedGenericLinkTo = null; applicationRootMock.mockReturnValue('/superset'); staticAssetsPrefixMock.mockReturnValue('/superset'); useSelectorMock.mockReturnValue({ roles: user.roles }); @@ -911,7 +915,6 @@ test('brand link is single-prefix when brand.path arrives already rooted (non-SP }); test('brand link strips a nested application root before handing to GenericLink', async () => { - observedGenericLinkTo = null; applicationRootMock.mockReturnValue('/preset/superset'); staticAssetsPrefixMock.mockReturnValue('/preset/superset'); useSelectorMock.mockReturnValue({ roles: user.roles }); @@ -941,3 +944,33 @@ test('brand link strips a nested application root before handing to GenericLink' }); expect(observedGenericLinkTo).toBe('/welcome/'); }); + +test('brand link from theme.brandLogoHref is single-prefix when already rooted', async () => { + applicationRootMock.mockReturnValue('/superset'); + staticAssetsPrefixMock.mockReturnValue('/superset'); + useSelectorMock.mockReturnValue({ roles: user.roles }); + + useThemeMock.mockReturnValue({ + ...CoreTheme.supersetTheme, + brandLogoUrl: '/superset/static/assets/images/custom-logo.png', + brandLogoHref: '/superset/welcome/', + }); + + render(, { + useRedux: true, + useQueryParams: true, + useRouter: true, + useTheme: true, + }); + + const brandLink = await screen.findByRole('link', { + name: /apache superset/i, + }); + expect(brandLink).toHaveAttribute('href', '/superset/welcome/'); + const brandImg = brandLink.querySelector('img'); + expect(brandImg).toHaveAttribute( + 'src', + '/superset/static/assets/images/custom-logo.png', + ); +}); +}); diff --git a/superset-frontend/src/utils/pathUtils.test.ts b/superset-frontend/src/utils/pathUtils.test.ts index 377b92193b3..fbcbf6311dc 100644 --- a/superset-frontend/src/utils/pathUtils.test.ts +++ b/superset-frontend/src/utils/pathUtils.test.ts @@ -275,6 +275,12 @@ test('stripAppRoot returns "/" when given the bare application root', async () = expect(stripAppRoot('/superset')).toBe('/'); }); +test('stripAppRoot returns "/" when given the application root with a trailing slash', async () => { + const { stripAppRoot } = await loadPathUtils('/superset/'); + + expect(stripAppRoot('/superset/')).toBe('/'); +}); + test('stripAppRoot respects segment boundaries — `/supersetfoo` is not the root', async () => { const { stripAppRoot } = await loadPathUtils('/superset/');