From ce401c0f59179beb8be382f7d254026f6f5b7276 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Mon, 1 Jun 2026 17:59:19 +0200 Subject: [PATCH] test: cover withBasePath base-path fallback prefixing --- lib/__tests__/base-path.test.ts | 74 +++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 lib/__tests__/base-path.test.ts diff --git a/lib/__tests__/base-path.test.ts b/lib/__tests__/base-path.test.ts new file mode 100644 index 00000000..929018cd --- /dev/null +++ b/lib/__tests__/base-path.test.ts @@ -0,0 +1,74 @@ +import { describe, it, expect, vi, afterEach } from 'vitest'; + +// `browser-navigation` reads NEXT_PUBLIC_BASE_PATH into a module-load constant +// (mirroring how next.config.ts bakes basePath in at build time), so each case +// has to re-import the module under a fresh env. These tests pin down the +// promise made to subpath deployments: the built-in branding *defaults* +// (FAVICON_URL, LOGIN_LOGO_*) — not just admin-set custom values — get the +// mount prefix, because every consumer wraps them in withBasePath(). See #330. +async function loadNav(basePath?: string) { + vi.resetModules(); + if (basePath === undefined) { + delete process.env.NEXT_PUBLIC_BASE_PATH; + } else { + process.env.NEXT_PUBLIC_BASE_PATH = basePath; + } + return import('@/lib/browser-navigation'); +} + +// The fallback values from CONFIG_ENV_MAP that ship in the box. +const DEFAULT_FAVICON = '/branding/Bulwark_Favicon.svg'; +const DEFAULT_LOGIN_LOGO_LIGHT = '/branding/Bulwark_Logo_Color.svg'; +const DEFAULT_LOGIN_LOGO_DARK = '/branding/Bulwark_Logo_White.svg'; + +afterEach(() => { + delete process.env.NEXT_PUBLIC_BASE_PATH; + vi.resetModules(); +}); + +describe('withBasePath — asset-URL fallbacks under a subpath', () => { + it('leaves branding defaults untouched when no base path is set', async () => { + const { withBasePath } = await loadNav(undefined); + // jsdom's window.location.pathname is "/", so runtime detection finds no + // prefix either — the default paths pass through unchanged. + expect(withBasePath(DEFAULT_FAVICON)).toBe(DEFAULT_FAVICON); + expect(withBasePath(DEFAULT_LOGIN_LOGO_LIGHT)).toBe(DEFAULT_LOGIN_LOGO_LIGHT); + expect(withBasePath(DEFAULT_LOGIN_LOGO_DARK)).toBe(DEFAULT_LOGIN_LOGO_DARK); + }); + + it('prefixes the built-in favicon and login-logo defaults', async () => { + const { withBasePath } = await loadNav('/webmail'); + expect(withBasePath(DEFAULT_FAVICON)).toBe('/webmail/branding/Bulwark_Favicon.svg'); + expect(withBasePath(DEFAULT_LOGIN_LOGO_LIGHT)).toBe('/webmail/branding/Bulwark_Logo_Color.svg'); + expect(withBasePath(DEFAULT_LOGIN_LOGO_DARK)).toBe('/webmail/branding/Bulwark_Logo_White.svg'); + }); + + it('is idempotent for an already-prefixed value (no double prefix)', async () => { + const { withBasePath } = await loadNav('/webmail'); + expect(withBasePath('/webmail/branding/Bulwark_Favicon.svg')).toBe('/webmail/branding/Bulwark_Favicon.svg'); + expect(withBasePath('/webmail')).toBe('/webmail'); + }); + + it('passes through external, protocol-relative, data, and empty values', async () => { + const { withBasePath } = await loadNav('/webmail'); + expect(withBasePath('https://cdn.example.com/logo.svg')).toBe('https://cdn.example.com/logo.svg'); + expect(withBasePath('//cdn.example.com/logo.svg')).toBe('//cdn.example.com/logo.svg'); + expect(withBasePath('data:image/svg+xml,')).toBe('data:image/svg+xml,'); + expect(withBasePath('')).toBe(''); + expect(withBasePath(null)).toBe(''); + expect(withBasePath(undefined)).toBe(''); + }); + + it('strips a trailing slash on the configured base path', async () => { + const { withBasePath, getPathPrefix } = await loadNav('/webmail/'); + expect(getPathPrefix()).toBe('/webmail'); + expect(withBasePath(DEFAULT_FAVICON)).toBe('/webmail/branding/Bulwark_Favicon.svg'); + }); + + it('getPathPrefix reports the static base path on the server (no window needed)', async () => { + const { getPathPrefix } = await loadNav('/webmail'); + // SSR consumers (layout.tsx favicon metadata, manifest.ts) rely on this + // resolving from the build-time constant rather than window.location. + expect(getPathPrefix()).toBe('/webmail'); + }); +});