From 5874efff3ce67603971bbf6c979e8ca2f32dc007 Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Mon, 27 Apr 2026 19:02:44 -0400 Subject: [PATCH 1/4] refactor(status-page): apply review feedback on theme-fields backend - StatusPage Mongoose model: import DEFAULT_STATUS_PAGE_THEME / DEFAULT_STATUS_PAGE_THEME_MODE from types/statusPage so the four sources of the default literal stay locked together - openapi.json: add explicit "default" alongside the enum on StatusPageTheme and StatusPageThemeMode component schemas; documentation already mentioned the defaults but the JSON didn't carry them - statusPageController: add JSDoc-style comments above withoutThemeFields and applyDefaultTheme spelling out the asymmetric strip-on-write / force-on-read semantics so the next reviewer doesn't have to re-derive the design --- server/openapi.json | 2 ++ server/src/controllers/statusPageController.ts | 7 +++++++ server/src/db/models/StatusPage.ts | 12 +++++++++--- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/server/openapi.json b/server/openapi.json index e3ff57cc7..de0103a38 100644 --- a/server/openapi.json +++ b/server/openapi.json @@ -3354,11 +3354,13 @@ "StatusPageTheme": { "type": "string", "enum": ["refined", "modern", "bold", "editorial"], + "default": "refined", "description": "Visual theme for the public status page. Defaults to 'refined'." }, "StatusPageThemeMode": { "type": "string", "enum": ["auto", "light", "dark"], + "default": "auto", "description": "Forces a light/dark mode on the public page. 'auto' follows the visitor's OS preference. Defaults to 'auto'." }, "SuccessResponse": { diff --git a/server/src/controllers/statusPageController.ts b/server/src/controllers/statusPageController.ts index 2e338f787..20fadbab6 100644 --- a/server/src/controllers/statusPageController.ts +++ b/server/src/controllers/statusPageController.ts @@ -40,6 +40,9 @@ class StatusPageController implements IStatusPageController { return StatusPageController.SERVICE_NAME; } + // Used when STATUS_PAGE_THEMES_ENABLED is false: drop incoming theme fields + // before persisting so an admin client can't accidentally store a value the + // public page won't honour. private withoutThemeFields = (body: Record): Record => { const rest = { ...body }; delete rest.theme; @@ -47,6 +50,10 @@ class StatusPageController implements IStatusPageController { return rest; }; + // Used when STATUS_PAGE_THEMES_ENABLED is false: overwrite the stored + // theme/themeMode with their defaults on read so callers see consistent + // values regardless of what's in the DB. Whatever a user previously chose + // is preserved in Mongo and resurfaces if the flag is flipped back on. private applyDefaultTheme = (statusPage: StatusPage): StatusPage => ({ ...statusPage, theme: DEFAULT_STATUS_PAGE_THEME, diff --git a/server/src/db/models/StatusPage.ts b/server/src/db/models/StatusPage.ts index 0a1b5830c..0b6451492 100644 --- a/server/src/db/models/StatusPage.ts +++ b/server/src/db/models/StatusPage.ts @@ -1,6 +1,12 @@ import { Schema, model, type Types } from "mongoose"; import type { StatusPage, StatusPageLogoDocument } from "@/types/statusPage.js"; -import { StatusPageTypes, StatusPageThemes, StatusPageThemeModes } from "@/types/statusPage.js"; +import { + DEFAULT_STATUS_PAGE_THEME, + DEFAULT_STATUS_PAGE_THEME_MODE, + StatusPageThemeModes, + StatusPageThemes, + StatusPageTypes, +} from "@/types/statusPage.js"; type StatusPageDocumentBase = Omit< StatusPage, @@ -111,12 +117,12 @@ const StatusPageSchema = new Schema( theme: { type: String, enum: StatusPageThemes, - default: "refined", + default: DEFAULT_STATUS_PAGE_THEME, }, themeMode: { type: String, enum: StatusPageThemeModes, - default: "auto", + default: DEFAULT_STATUS_PAGE_THEME_MODE, }, }, { timestamps: true } From d80d939bd8e9cb8805fba44d2dbd4898f9243a33 Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Mon, 27 Apr 2026 19:26:16 -0400 Subject: [PATCH 2/4] test(status-page): cover theme-flag helpers and document remaining caveats MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address review follow-ups on PR #3568: - Extract the controller's withoutThemeFields and applyDefaultTheme helpers from a private class field into a standalone module (controllers/statusPageThemeFlag.ts) so they can be tested without a controller harness; the controller is now a one-line consumer - Add 9 unit tests covering both helpers (test/unit/controllers/statusPageThemeFlag.test.ts): no-mutation guarantees, partial inputs, all-fields-preserved verbatim, override-with-defaults semantics, and "page has no theme set yet" — the flag-off path is now exercised in both directions - Expand the comment over withoutThemeFields to spell out why the strip is silent rather than a 400: this is an ops kill switch, not API semantics; clients shouldn't need to know whether themes are disabled today Two caveats called out in review that this commit does NOT change: - Mongoose `default:` directives only inject during hydration, not on partial updates. Legacy documents updated with `findOneAndUpdate` still won't have `theme` / `themeMode` written to Mongo. They render as defaults on read because the schema injects them — functionally equivalent, but the DB will hold a mix of present and absent fields indefinitely. Acceptable per the original commit intent. - docs/plans/status-page-themes.md (304 lines) covers both backend and frontend work for the themes feature. It lives in this PR as the design artefact that drove both branches; moving it for purity would break the "where did this come from?" trail. Leaving it in. --- .../src/controllers/statusPageController.ts | 34 ++---- server/src/controllers/statusPageThemeFlag.ts | 33 ++++++ .../controllers/statusPageThemeFlag.test.ts | 106 ++++++++++++++++++ 3 files changed, 146 insertions(+), 27 deletions(-) create mode 100644 server/src/controllers/statusPageThemeFlag.ts create mode 100644 server/test/unit/controllers/statusPageThemeFlag.test.ts diff --git a/server/src/controllers/statusPageController.ts b/server/src/controllers/statusPageController.ts index 20fadbab6..547321d78 100644 --- a/server/src/controllers/statusPageController.ts +++ b/server/src/controllers/statusPageController.ts @@ -12,7 +12,7 @@ import { IStatusPageService } from "@/service/business/statusPageService.js"; import { IMonitorsRepository } from "@/repositories/index.js"; import { ISettingsService } from "@/service/system/settingsService.js"; import { NormalizeData } from "@/utils/dataUtils.js"; -import { DEFAULT_STATUS_PAGE_THEME, DEFAULT_STATUS_PAGE_THEME_MODE, type StatusPage } from "@/types/statusPage.js"; +import { applyDefaultTheme, withoutThemeFields } from "@/controllers/statusPageThemeFlag.js"; const SERVICE_NAME = "statusPageController"; @@ -40,26 +40,6 @@ class StatusPageController implements IStatusPageController { return StatusPageController.SERVICE_NAME; } - // Used when STATUS_PAGE_THEMES_ENABLED is false: drop incoming theme fields - // before persisting so an admin client can't accidentally store a value the - // public page won't honour. - private withoutThemeFields = (body: Record): Record => { - const rest = { ...body }; - delete rest.theme; - delete rest.themeMode; - return rest; - }; - - // Used when STATUS_PAGE_THEMES_ENABLED is false: overwrite the stored - // theme/themeMode with their defaults on read so callers see consistent - // values regardless of what's in the DB. Whatever a user previously chose - // is preserved in Mongo and resurfaces if the flag is flipped back on. - private applyDefaultTheme = (statusPage: StatusPage): StatusPage => ({ - ...statusPage, - theme: DEFAULT_STATUS_PAGE_THEME, - themeMode: DEFAULT_STATUS_PAGE_THEME_MODE, - }); - createStatusPage = async (req: Request, res: Response, next: NextFunction) => { try { createStatusPageBodyValidation.parse(req.body); @@ -68,7 +48,7 @@ class StatusPageController implements IStatusPageController { } const themesEnabled = this.settingsService.areStatusPageThemesEnabled(); - const body = themesEnabled ? req.body : this.withoutThemeFields(req.body); + const body = themesEnabled ? req.body : withoutThemeFields(req.body); const teamId = requireTeamId(req?.user?.teamId); const userId = requireUserId(req?.user?.id); const statusPage = await this.statusPageService.createStatusPage(userId, teamId, req.file, body); @@ -76,7 +56,7 @@ class StatusPageController implements IStatusPageController { return res.status(200).json({ success: true, msg: "Status page created successfully", - data: themesEnabled ? statusPage : this.applyDefaultTheme(statusPage), + data: themesEnabled ? statusPage : applyDefaultTheme(statusPage), }); } catch (error) { next(error); @@ -91,7 +71,7 @@ class StatusPageController implements IStatusPageController { } const themesEnabled = this.settingsService.areStatusPageThemesEnabled(); - const body = themesEnabled ? req.body : this.withoutThemeFields(req.body); + const body = themesEnabled ? req.body : withoutThemeFields(req.body); const teamId = requireTeamId(req?.user?.teamId); const statusPageId = req.params.id as string; if (!statusPageId) { @@ -104,7 +84,7 @@ class StatusPageController implements IStatusPageController { res.status(200).json({ success: true, msg: "Status page updated successfully", - data: themesEnabled ? statusPage : this.applyDefaultTheme(statusPage), + data: themesEnabled ? statusPage : applyDefaultTheme(statusPage), }); } catch (error) { next(error); @@ -155,7 +135,7 @@ class StatusPageController implements IStatusPageController { success: true, msg: "Status page retrieved successfully", data: { - statusPage: themesEnabled ? statusPage : this.applyDefaultTheme(statusPage), + statusPage: themesEnabled ? statusPage : applyDefaultTheme(statusPage), monitors: normalizedMonitors, }, }); @@ -172,7 +152,7 @@ class StatusPageController implements IStatusPageController { return res.status(200).json({ success: true, msg: "Status pages retrieved successfully", - data: themesEnabled ? statusPages : statusPages.map((sp) => this.applyDefaultTheme(sp)), + data: themesEnabled ? statusPages : statusPages.map((sp) => applyDefaultTheme(sp)), }); } catch (error) { next(error); diff --git a/server/src/controllers/statusPageThemeFlag.ts b/server/src/controllers/statusPageThemeFlag.ts new file mode 100644 index 000000000..bd65d5cb2 --- /dev/null +++ b/server/src/controllers/statusPageThemeFlag.ts @@ -0,0 +1,33 @@ +import { DEFAULT_STATUS_PAGE_THEME, DEFAULT_STATUS_PAGE_THEME_MODE, type StatusPage } from "@/types/statusPage.js"; + +/** + * When STATUS_PAGE_THEMES_ENABLED is false, the controller drops incoming + * theme fields before persisting so an admin client can't accidentally store + * a value the public page won't honour. Whatever a caller sends here for + * `theme` / `themeMode` is silently ignored — the rest of the body is + * preserved verbatim. + * + * The strip is silent (not a 400) because the kill switch is an ops-side + * concern: API clients should not have to know whether the operator has + * disabled themes today, only that their saved value won't take effect. + * If we later decide we'd rather fail loudly, change this to throw a + * 400 AppError; downstream consumers already handle it. + */ +export const withoutThemeFields = (body: Record): Record => { + const rest = { ...body }; + delete rest.theme; + delete rest.themeMode; + return rest; +}; + +/** + * When STATUS_PAGE_THEMES_ENABLED is false, callers reading a status page + * should see the canonical default theme / mode regardless of what's stored + * in Mongo. Whatever the user previously chose stays in the DB so flipping + * the flag back on resurfaces their selection. + */ +export const applyDefaultTheme = (statusPage: StatusPage): StatusPage => ({ + ...statusPage, + theme: DEFAULT_STATUS_PAGE_THEME, + themeMode: DEFAULT_STATUS_PAGE_THEME_MODE, +}); diff --git a/server/test/unit/controllers/statusPageThemeFlag.test.ts b/server/test/unit/controllers/statusPageThemeFlag.test.ts new file mode 100644 index 000000000..0ae10556a --- /dev/null +++ b/server/test/unit/controllers/statusPageThemeFlag.test.ts @@ -0,0 +1,106 @@ +import { describe, expect, it } from "@jest/globals"; +import { applyDefaultTheme, withoutThemeFields } from "../../../src/controllers/statusPageThemeFlag.ts"; +import { DEFAULT_STATUS_PAGE_THEME, DEFAULT_STATUS_PAGE_THEME_MODE } from "../../../src/types/statusPage.ts"; +import type { StatusPage } from "../../../src/types/index.ts"; + +const makeStatusPage = (overrides?: Partial): StatusPage => + ({ + id: "sp-1", + teamId: "team-1", + userId: "user-1", + url: "my-status-page", + companyName: "Test Co", + monitors: ["mon-1"], + theme: "modern", + themeMode: "dark", + createdAt: "2026-01-01T00:00:00Z", + updatedAt: "2026-01-01T00:00:00Z", + ...overrides, + }) as StatusPage; + +describe("statusPageThemeFlag", () => { + describe("withoutThemeFields", () => { + it("removes theme and themeMode from a body that has them", () => { + const body = { companyName: "Co", theme: "modern", themeMode: "dark" }; + const result = withoutThemeFields(body); + expect(result).toEqual({ companyName: "Co" }); + }); + + it("is a no-op when neither field is present", () => { + const body = { companyName: "Co", url: "page" }; + const result = withoutThemeFields(body); + expect(result).toEqual({ companyName: "Co", url: "page" }); + }); + + it("removes only the theme fields when one is present", () => { + const body = { companyName: "Co", theme: "bold" }; + const result = withoutThemeFields(body); + expect(result).toEqual({ companyName: "Co" }); + }); + + it("does not mutate the input body", () => { + const body = { companyName: "Co", theme: "modern", themeMode: "dark" }; + withoutThemeFields(body); + expect(body).toEqual({ companyName: "Co", theme: "modern", themeMode: "dark" }); + }); + + it("preserves all other fields verbatim", () => { + const body = { + companyName: "Co", + monitors: ["m-1", "m-2"], + isPublished: true, + showCharts: false, + color: "#13715B", + theme: "editorial", + themeMode: "light", + }; + const result = withoutThemeFields(body); + expect(result).toEqual({ + companyName: "Co", + monitors: ["m-1", "m-2"], + isPublished: true, + showCharts: false, + color: "#13715B", + }); + }); + }); + + describe("applyDefaultTheme", () => { + it("overwrites theme and themeMode with the canonical defaults", () => { + const sp = makeStatusPage({ theme: "modern", themeMode: "dark" }); + const result = applyDefaultTheme(sp); + expect(result.theme).toBe(DEFAULT_STATUS_PAGE_THEME); + expect(result.themeMode).toBe(DEFAULT_STATUS_PAGE_THEME_MODE); + }); + + it("leaves all other fields untouched", () => { + const sp = makeStatusPage({ + companyName: "Test Co", + url: "my-page", + monitors: ["mon-1", "mon-2"], + isPublished: true, + color: "#13715B", + }); + const result = applyDefaultTheme(sp); + expect(result.companyName).toBe("Test Co"); + expect(result.url).toBe("my-page"); + expect(result.monitors).toEqual(["mon-1", "mon-2"]); + expect(result.isPublished).toBe(true); + expect(result.color).toBe("#13715B"); + }); + + it("does not mutate the input status page", () => { + const sp = makeStatusPage({ theme: "modern", themeMode: "dark" }); + applyDefaultTheme(sp); + expect(sp.theme).toBe("modern"); + expect(sp.themeMode).toBe("dark"); + }); + + it("works on a status page that has no theme set yet", () => { + const sp = makeStatusPage({ theme: undefined, themeMode: undefined }); + const result = applyDefaultTheme(sp); + expect(result.theme).toBe(DEFAULT_STATUS_PAGE_THEME); + expect(result.themeMode).toBe(DEFAULT_STATUS_PAGE_THEME_MODE); + }); + }); +}); From a2786a77ec3f401d5848d4e16080d95293a09dde Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Mon, 27 Apr 2026 19:33:03 -0400 Subject: [PATCH 3/4] refactor(status-page): tighten the theme-fields backend after second review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply review feedback: - Extract makeStatusPage from the two test files that duplicated it (test/unit/services/statusPageService.test.ts and test/unit/controllers/statusPageThemeFlag.test.ts) into test/helpers/makeStatusPage.ts. Both files now import the shared helper, matching the existing test/helpers/ convention used for repository fakes and provider contracts - Add a blank line in statusPageController.getStatusPageByUrl between the normalizedMonitors map and the themesEnabled lookup so the response-shaping section breathes a bit - Drop the obsolete localStorage references from the plan doc that survived from an earlier draft of the design — the public page never persisted a per-visitor mode choice in localStorage, so the test bullet under 2.11 and the risk row both pointed at functionality that does not exist --- docs/plans/status-page-themes.md | 3 +-- server/src/controllers/statusPageController.ts | 1 + server/test/helpers/makeStatusPage.ts | 14 ++++++++++++++ .../controllers/statusPageThemeFlag.test.ts | 17 +---------------- .../unit/services/statusPageService.test.ts | 17 +---------------- 5 files changed, 18 insertions(+), 34 deletions(-) create mode 100644 server/test/helpers/makeStatusPage.ts diff --git a/docs/plans/status-page-themes.md b/docs/plans/status-page-themes.md index eb460766e..f7fb97331 100644 --- a/docs/plans/status-page-themes.md +++ b/docs/plans/status-page-themes.md @@ -232,7 +232,7 @@ Keys to add: **Component tests (existing test setup):** - Admin form: selecting a theme card updates form state and survives submit - Admin form: `themeMode` dropdown changes payload -- `StatusPageThemeProvider`: `auto` mode resolves to `prefers-color-scheme` when no localStorage; localStorage overrides when set; admin-forced mode wins over both +- `StatusPageThemeProvider`: `auto` mode resolves to `prefers-color-scheme`; admin-forced `light`/`dark` modes win over the OS setting - Mode resolution: `themeMode="auto"` picks up `prefers-color-scheme`; `themeMode="light"` and `"dark"` lock the mode regardless of OS setting - Admin picker shows both light + dark preview SVGs for every theme, regardless of admin UI mode - Jest/Vitest snapshot tests (plain, no visual-regression tool): `Status/index.tsx` rendered under each of `refined | modern | bold | editorial` × `light | dark`. 8 snapshots via a single parametrised test. @@ -263,7 +263,6 @@ Two PRs, small-enough individually to review carefully. | Four themes = 4× visual-regression surface going forward | Parametrised snapshot tests in PR 2 cover all 4 themes × both modes × key status states. Regressions caught at PR time. | | Editorial hairline dividers fail WCAG 3:1 for UI borders | Axe pass in 2.12; darken divider token until it clears. | | Older clients posting create/update without `theme` break | Validator makes fields optional; model default handles it. Covered by backend test 1.6. | -| Per-page localStorage theme mode leaks across status pages | Storage key namespaced by `statusPage.url`. Covered in 2.3 and 2.11. | | `customCSS` collides with theme tokens | Themes set CSS variables on a wrapper; `customCSS` is injected inline after and can override any `--sp-*` variable. No code change. Document in settings help text. | | Shared chart components look off-brand on bold/modern dark | Decided in 2.6: pass an optional `paletteOverride` prop from the status page subtree. | | Rollback needed if feature misbehaves in prod | Feature flag `STATUS_PAGE_THEMES_ENABLED` in 1.5 disables writes and forces `refined` on reads. | diff --git a/server/src/controllers/statusPageController.ts b/server/src/controllers/statusPageController.ts index 547321d78..706ee5ebd 100644 --- a/server/src/controllers/statusPageController.ts +++ b/server/src/controllers/statusPageController.ts @@ -130,6 +130,7 @@ class StatusPageController implements IStatusPageController { } return { ...monitor, checks: normalizedChecks }; }); + const themesEnabled = this.settingsService.areStatusPageThemesEnabled(); return res.status(200).json({ success: true, diff --git a/server/test/helpers/makeStatusPage.ts b/server/test/helpers/makeStatusPage.ts new file mode 100644 index 000000000..23c13bca1 --- /dev/null +++ b/server/test/helpers/makeStatusPage.ts @@ -0,0 +1,14 @@ +import type { StatusPage } from "../../src/types/index.ts"; + +export const makeStatusPage = (overrides?: Partial): StatusPage => + ({ + id: "sp-1", + teamId: "team-1", + userId: "user-1", + url: "my-status-page", + companyName: "Test Co", + monitors: ["mon-1"], + createdAt: "2026-01-01T00:00:00Z", + updatedAt: "2026-01-01T00:00:00Z", + ...overrides, + }) as StatusPage; diff --git a/server/test/unit/controllers/statusPageThemeFlag.test.ts b/server/test/unit/controllers/statusPageThemeFlag.test.ts index 0ae10556a..b4f9435a9 100644 --- a/server/test/unit/controllers/statusPageThemeFlag.test.ts +++ b/server/test/unit/controllers/statusPageThemeFlag.test.ts @@ -1,22 +1,7 @@ import { describe, expect, it } from "@jest/globals"; import { applyDefaultTheme, withoutThemeFields } from "../../../src/controllers/statusPageThemeFlag.ts"; import { DEFAULT_STATUS_PAGE_THEME, DEFAULT_STATUS_PAGE_THEME_MODE } from "../../../src/types/statusPage.ts"; -import type { StatusPage } from "../../../src/types/index.ts"; - -const makeStatusPage = (overrides?: Partial): StatusPage => - ({ - id: "sp-1", - teamId: "team-1", - userId: "user-1", - url: "my-status-page", - companyName: "Test Co", - monitors: ["mon-1"], - theme: "modern", - themeMode: "dark", - createdAt: "2026-01-01T00:00:00Z", - updatedAt: "2026-01-01T00:00:00Z", - ...overrides, - }) as StatusPage; +import { makeStatusPage } from "../../helpers/makeStatusPage.ts"; describe("statusPageThemeFlag", () => { describe("withoutThemeFields", () => { diff --git a/server/test/unit/services/statusPageService.test.ts b/server/test/unit/services/statusPageService.test.ts index ea3d1ce82..d50fd92f7 100644 --- a/server/test/unit/services/statusPageService.test.ts +++ b/server/test/unit/services/statusPageService.test.ts @@ -1,22 +1,7 @@ import { describe, expect, it, jest } from "@jest/globals"; import { StatusPageService } from "../../../src/service/business/statusPageService.ts"; import type { IStatusPagesRepository } from "../../../src/repositories/status-pages/IStatusPagesRepository.ts"; -import type { StatusPage } from "../../../src/types/index.ts"; - -// ── Helpers ────────────────────────────────────────────────────────────────── - -const makeStatusPage = (overrides?: Partial): StatusPage => - ({ - id: "sp-1", - teamId: "team-1", - userId: "user-1", - url: "my-status-page", - companyName: "Test Co", - monitors: ["mon-1"], - createdAt: "2026-01-01T00:00:00Z", - updatedAt: "2026-01-01T00:00:00Z", - ...overrides, - }) as StatusPage; +import { makeStatusPage } from "../../helpers/makeStatusPage.ts"; const createRepo = () => ({ From 0322a96090ba37b6b40b338ea498262652760e27 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 29 Apr 2026 11:41:22 -0700 Subject: [PATCH 4/4] update settings service tests --- .../test/unit/services/settingsService.test.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/server/test/unit/services/settingsService.test.ts b/server/test/unit/services/settingsService.test.ts index 5c4935deb..da75d3ee5 100644 --- a/server/test/unit/services/settingsService.test.ts +++ b/server/test/unit/services/settingsService.test.ts @@ -15,6 +15,7 @@ const makeEnv = (overrides?: Partial): ValidatedEnv => CLIENT_HOST: "http://localhost:5173", DB_CONNECTION_STRING: "mongodb://localhost:27017/test_db", DB_TYPE: "mongodb", + STATUS_PAGE_THEMES_ENABLED: false, ...overrides, }) as ValidatedEnv; @@ -81,6 +82,7 @@ describe("SettingsService", () => { clientHost: "http://localhost:5173", dbConnectionString: "mongodb://localhost:27017/test_db", dbType: "mongodb", + statusPageThemesEnabled: false, }); }); @@ -103,6 +105,22 @@ describe("SettingsService", () => { }); }); + // ── areStatusPageThemesEnabled ────────────────────────────────────────── + + describe("areStatusPageThemesEnabled", () => { + it("returns false when STATUS_PAGE_THEMES_ENABLED env is false", () => { + const { service } = createService({ STATUS_PAGE_THEMES_ENABLED: false }); + + expect(service.areStatusPageThemesEnabled()).toBe(false); + }); + + it("returns true when STATUS_PAGE_THEMES_ENABLED env is true", () => { + const { service } = createService({ STATUS_PAGE_THEMES_ENABLED: true }); + + expect(service.areStatusPageThemesEnabled()).toBe(true); + }); + }); + // ── setRepository ─────────────────────────────────────────────────────── describe("setRepository", () => {