Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions __tests__/server/getAvalancheCenterPlatforms.server.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
const mockCenters: Record<
string,
{ warnings: boolean; forecasts: boolean; stations: boolean; obs: boolean; weather: boolean }
> = {
nwac: {
warnings: true,
forecasts: true,
stations: true,
obs: true,
weather: true,
},
sac: {
warnings: true,
forecasts: true,
stations: false,
obs: true,
weather: false,
},
}

const allFalsePlatforms = {
warnings: false,
forecasts: false,
stations: false,
obs: false,
weather: false,
}

// Mock the entire nac module, re-implementing getAvalancheCenterPlatforms
// with the real logic but using mocked data instead of fetching from the API
jest.mock('../../src/services/nac/nac', () => ({
getAvalancheCenterPlatforms: jest.fn(async (centerSlug: string) => {
const centerSlugToUse = centerSlug === 'dvac' ? 'nwac' : centerSlug

return mockCenters[centerSlugToUse] ?? allFalsePlatforms
}),
}))
Comment on lines +31 to +37
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replacing the function we're testing here with a mocked function that's only used in this test doesn't actually test the getAvalancheCenterPlatforms function.

Ideally, we would intercept the API requests and return mocked data using MSW or something similar so that we would be testing the actual implementation of getAvalancheCenterPlatforms.

If you don't want to set up MSW, I suggest removing this test file completely.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good callout - set up MSW you linked and implemented this in 251372b


import { getAvalancheCenterPlatforms } from '@/services/nac/nac'

describe('services: getAvalancheCenterPlatforms', () => {
it('returns platforms for a matching center slug', async () => {
const result = await getAvalancheCenterPlatforms('nwac')
expect(result).toEqual({
warnings: true,
forecasts: true,
stations: true,
obs: true,
weather: true,
})
})

it('maps dvac slug to nwac', async () => {
const result = await getAvalancheCenterPlatforms('dvac')
expect(result).toEqual({
warnings: true,
forecasts: true,
stations: true,
obs: true,
weather: true,
})
})

it('uppercases the slug for matching', async () => {
const result = await getAvalancheCenterPlatforms('sac')
expect(result).toEqual({
warnings: true,
forecasts: true,
stations: false,
obs: true,
weather: false,
})
})

it('returns all-false platforms when center is not found', async () => {
const result = await getAvalancheCenterPlatforms('unknown')
expect(result).toEqual(allFalsePlatforms)
})
})
63 changes: 63 additions & 0 deletions __tests__/server/getNACWidgetsConfig.server.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { getNACWidgetsConfig } from '@/utilities/getNACWidgetsConfig'

const mockConfig: { version: string; baseUrl: string; devMode?: boolean } = {
version: '20251207',
baseUrl: 'https://du6amfiq9m9h7.cloudfront.net/public/v2',
devMode: false,
}

jest.mock('../../src/utilities/getGlobals', () => ({
getCachedGlobal: () => () => Promise.resolve(mockConfig),
}))

describe('utilities: getNACWidgetsConfig', () => {
const originalNodeEnv = process.env.NODE_ENV

afterEach(() => {
Object.defineProperty(process.env, 'NODE_ENV', {
value: originalNodeEnv,
configurable: true,
})
mockConfig.devMode = false
})

it('returns version and baseUrl from the global config', async () => {
const result = await getNACWidgetsConfig()
expect(result.version).toBe('20251207')
expect(result.baseUrl).toBe('https://du6amfiq9m9h7.cloudfront.net/public/v2')
})

it('forces devMode to false in production even when global config has it enabled', async () => {
Object.defineProperty(process.env, 'NODE_ENV', { value: 'production', configurable: true })
mockConfig.devMode = true

const result = await getNACWidgetsConfig()
expect(result.devMode).toBe(false)
})
Comment on lines +44 to +51
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description says that nacWidgetsConfig.devMode should be respected when NAC_WIDGET_DEV_MODE is not set.

Copy link
Copy Markdown
Collaborator Author

@rchlfryn rchlfryn Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah good shout - this was implemented incorrectly and overengineered. I somehow got confused. The priority is:

  • if prod -> always false
  • nacWidgetsConfig.devMode from admin panel (if env var not set)
  • false (if neither is set)


it('defaults devMode to false when not set in global config', async () => {
Object.defineProperty(process.env, 'NODE_ENV', { value: 'development', configurable: true })
mockConfig.devMode = undefined

const result = await getNACWidgetsConfig()
expect(result.devMode).toBe(false)
})

it('throws when version is missing', async () => {
const original = mockConfig.version
mockConfig.version = ''

await expect(getNACWidgetsConfig()).rejects.toThrow('Could not determine NAC widgets version')

mockConfig.version = original
})

it('throws when baseUrl is missing', async () => {
const original = mockConfig.baseUrl
mockConfig.baseUrl = ''

await expect(getNACWidgetsConfig()).rejects.toThrow('Could not determine NAC widgets base url')

mockConfig.baseUrl = original
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export default async function Page({ params }: Args) {
notFound()
}

const { version, baseUrl } = await getNACWidgetsConfig()
const { version, baseUrl, devMode } = await getNACWidgetsConfig()

return (
<>
Expand All @@ -64,6 +64,7 @@ export default async function Page({ params }: Args) {
widget={'forecast'}
widgetsVersion={version}
widgetsBaseUrl={baseUrl}
widgetsDevMode={devMode}
/>
</div>
</>
Expand Down
3 changes: 2 additions & 1 deletion src/app/(frontend)/[center]/forecasts/avalanche/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export default async function Page({ params }: Args) {
notFound()
}

const { version, baseUrl } = await getNACWidgetsConfig()
const { version, baseUrl, devMode } = await getNACWidgetsConfig()

return (
<>
Expand All @@ -54,6 +54,7 @@ export default async function Page({ params }: Args) {
widget="forecast"
widgetsVersion={version}
widgetsBaseUrl={baseUrl}
widgetsDevMode={devMode}
/>
</div>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ export default function SingleObservationPage({
center,
widgetsVersion,
widgetsBaseUrl,
widgetsDevMode,
}: {
title: string
center: string
widgetsVersion: string
widgetsBaseUrl: string
widgetsDevMode?: boolean
}) {
return (
<div className="container flex flex-col gap-8">
Expand All @@ -34,6 +36,7 @@ export default function SingleObservationPage({
widget={'observations'}
widgetsVersion={widgetsVersion}
widgetsBaseUrl={widgetsBaseUrl}
widgetsDevMode={widgetsDevMode}
/>
</div>
)
Expand Down
3 changes: 2 additions & 1 deletion src/app/(frontend)/[center]/observations/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default async function Page({ params }: Args) {
notFound()
}

const { version, baseUrl } = await getNACWidgetsConfig()
const { version, baseUrl, devMode } = await getNACWidgetsConfig()

return (
<>
Expand All @@ -36,6 +36,7 @@ export default async function Page({ params }: Args) {
center={center}
widgetsVersion={version}
widgetsBaseUrl={baseUrl}
widgetsDevMode={devMode}
/>
</>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default async function Page({ params }: Args) {
notFound()
}

const { version, baseUrl } = await getNACWidgetsConfig()
const { version, baseUrl, devMode } = await getNACWidgetsConfig()

return (
<>
Expand All @@ -40,6 +40,7 @@ export default async function Page({ params }: Args) {
center={center}
widgetsVersion={version}
widgetsBaseUrl={baseUrl}
widgetsDevMode={devMode}
/>
</>
)
Expand Down
3 changes: 2 additions & 1 deletion src/app/(frontend)/[center]/observations/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default async function Page({ params }: Args) {
notFound()
}

const { version, baseUrl } = await getNACWidgetsConfig()
const { version, baseUrl, devMode } = await getNACWidgetsConfig()

return (
<>
Expand All @@ -65,6 +65,7 @@ export default async function Page({ params }: Args) {
widget={'observations'}
widgetsVersion={version}
widgetsBaseUrl={baseUrl}
widgetsDevMode={devMode}
/>
</div>
</>
Expand Down
3 changes: 2 additions & 1 deletion src/app/(frontend)/[center]/observations/submit/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default async function Page({ params }: Args) {
notFound()
}

const { version, baseUrl } = await getNACWidgetsConfig()
const { version, baseUrl, devMode } = await getNACWidgetsConfig()

return (
<>
Expand All @@ -57,6 +57,7 @@ export default async function Page({ params }: Args) {
widget={'observations'}
widgetsVersion={version}
widgetsBaseUrl={baseUrl}
widgetsDevMode={devMode}
/>
</div>
</>
Expand Down
5 changes: 3 additions & 2 deletions src/app/(frontend)/[center]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@ export default async function Page({ params }: Args) {
if (!isValidTenantSlug(center)) {
notFound()
}

const { version, baseUrl } = await getNACWidgetsConfig()
const { version, baseUrl, devMode } = await getNACWidgetsConfig()
const { quickLinks, highlightedContent, layout } =
(await getCachedHomePage(center, draft)()) ?? {}

Expand Down Expand Up @@ -82,6 +81,7 @@ export default async function Page({ params }: Args) {
widget="warnings"
widgetsVersion={version}
widgetsBaseUrl={baseUrl}
widgetsDevMode={devMode}
/>
<div className="py-4 md:py-6 flex flex-col gap-8 md:gap-14">
<div className="container flex flex-col md:flex-row gap-4 md:gap-8">
Expand All @@ -98,6 +98,7 @@ export default async function Page({ params }: Args) {
widget="map"
widgetsVersion={version}
widgetsBaseUrl={baseUrl}
widgetsDevMode={devMode}
/>
</div>
{quickLinks && quickLinks.length > 0 && (
Expand Down
3 changes: 2 additions & 1 deletion src/app/(frontend)/[center]/weather/forecast/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default async function Page({ params }: Args) {
notFound()
}

const { version, baseUrl } = await getNACWidgetsConfig()
const { version, baseUrl, devMode } = await getNACWidgetsConfig()

return (
<>
Expand All @@ -57,6 +57,7 @@ export default async function Page({ params }: Args) {
widget={'forecast'}
widgetsVersion={version}
widgetsBaseUrl={baseUrl}
widgetsDevMode={devMode}
/>
</div>
</>
Expand Down
3 changes: 2 additions & 1 deletion src/app/(frontend)/[center]/weather/stations/map/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default async function Page({ params }: Args) {
notFound()
}

const { version, baseUrl } = await getNACWidgetsConfig()
const { version, baseUrl, devMode } = await getNACWidgetsConfig()

return (
<>
Expand All @@ -57,6 +57,7 @@ export default async function Page({ params }: Args) {
widget={'stations'}
widgetsVersion={version}
widgetsBaseUrl={baseUrl}
widgetsDevMode={devMode}
/>
</div>
</>
Expand Down
3 changes: 2 additions & 1 deletion src/app/api/[center]/nac-config/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ export async function GET(
const { center } = await params

const avalancheCenterPlatforms = await getAvalancheCenterPlatforms(center)
const { version, baseUrl } = await getNACWidgetsConfig()
const { version, baseUrl, devMode } = await getNACWidgetsConfig()

return NextResponse.json({
avalancheCenterPlatforms,
version,
baseUrl,
devMode,
})
}
1 change: 1 addition & 0 deletions src/blocks/NACMedia/Component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export const NACMediaBlockComponent = (props: NACMediaBlockProps) => {
widget={'media'}
widgetsVersion={data.version}
widgetsBaseUrl={data.baseUrl}
widgetsDevMode={data.devMode}
mediaMode={mode}
/>
)}
Expand Down
4 changes: 3 additions & 1 deletion src/components/NACWidget/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,14 @@ export function NACWidget({
widget,
widgetsVersion,
widgetsBaseUrl,
widgetsDevMode = false,
mediaMode,
}: {
center: string
widget: Widget
widgetsVersion: string
widgetsBaseUrl: string
widgetsDevMode?: boolean
mediaMode?: 'carousel' | 'grid'
}) {
const widgetId = `nac-widget-${widget}`
Expand Down Expand Up @@ -120,7 +122,7 @@ export function NACWidget({
const widgetData = {
googleMapsApiKey: process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY || '',
centerId: fallbackCenter.toUpperCase(),
devMode: false,
devMode: widgetsDevMode,
mountId: `#${widgetId}`,
baseUrl: baseUrl,
controlledMount: true,
Expand Down
1 change: 1 addition & 0 deletions src/endpoints/seed/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const defaultNacWidgetsConfig = {
requiredFields: {
version: '20251207',
baseUrl: 'https://du6amfiq9m9h7.cloudfront.net/public/v2',
devMode: false,
},
}

Expand Down
10 changes: 10 additions & 0 deletions src/globals/NACWidgetsConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ export const NACWidgetsConfig: GlobalConfig = {
validate: validateExternalUrl,
required: true,
},
{
type: 'checkbox',
name: 'devMode',
defaultValue: false,
admin: {
description:
'When enabled, widgets use a staging base URL instead of production. Always forced to false in production.',
},
required: true,
},
],
hooks: {
afterChange: [revalidateWidgetPages],
Expand Down
Loading
Loading