From 28475f2b3b0168ff3b66e6009cb2b74a9c7108e8 Mon Sep 17 00:00:00 2001 From: Maximilian Schoell Date: Thu, 28 May 2026 16:56:04 +0200 Subject: [PATCH] feat: add isThemeActive helper --- src/internal/index.ts | 2 + .../__tests__/is-theme-active.ssr.test.ts | 51 ++++++++++ .../__tests__/is-theme-active.test.ts | 99 +++++++++++++++++++ src/internal/visual-mode/index.ts | 29 ++++++ 4 files changed, 181 insertions(+) create mode 100644 src/internal/visual-mode/__tests__/is-theme-active.ssr.test.ts create mode 100644 src/internal/visual-mode/__tests__/is-theme-active.test.ts diff --git a/src/internal/index.ts b/src/internal/index.ts index d8d7436..1223f27 100644 --- a/src/internal/index.ts +++ b/src/internal/index.ts @@ -14,6 +14,8 @@ export { useDensityMode, useReducedMotion, useRuntimeVisualRefresh, + isThemeActive, + Theme, } from './visual-mode/index.js'; export { isDevelopment } from './is-development.js'; export { warnOnce, clearMessageCache } from './logging.js'; diff --git a/src/internal/visual-mode/__tests__/is-theme-active.ssr.test.ts b/src/internal/visual-mode/__tests__/is-theme-active.ssr.test.ts new file mode 100644 index 0000000..ea9b845 --- /dev/null +++ b/src/internal/visual-mode/__tests__/is-theme-active.ssr.test.ts @@ -0,0 +1,51 @@ +/** + * @jest-environment node + */ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { isThemeActive, Theme } from '../index'; +import { awsuiGlobalFlagsSymbol, awsuiVisualRefreshFlag, FlagsHolder } from '../../global-flags'; + +const globalWithFlags = globalThis as FlagsHolder; + +afterEach(() => { + delete globalWithFlags[awsuiVisualRefreshFlag]; + delete globalWithFlags[awsuiGlobalFlagsSymbol]; +}); + +test('ensure no window in this environment', () => { + expect(typeof window === 'undefined').toBe(true); +}); + +describe('Theme.VisualRefresh', () => { + test('returns false by default', () => { + expect(isThemeActive(Theme.VisualRefresh)).toBe(false); + }); + + test('returns true when awsui-visual-refresh-flag is set on globalThis', () => { + globalWithFlags[awsuiVisualRefreshFlag] = () => true; + expect(isThemeActive(Theme.VisualRefresh)).toBe(true); + }); + + test('returns false when awsui-visual-refresh-flag returns false', () => { + globalWithFlags[awsuiVisualRefreshFlag] = () => false; + expect(isThemeActive(Theme.VisualRefresh)).toBe(false); + }); +}); + +describe('Theme.OneTheme', () => { + test('returns false by default', () => { + expect(isThemeActive(Theme.OneTheme)).toBe(false); + }); + + test('returns true when oneTheme global flag is set on globalThis', () => { + globalWithFlags[awsuiGlobalFlagsSymbol] = { oneTheme: true }; + expect(isThemeActive(Theme.OneTheme)).toBe(true); + }); + + test('returns false when oneTheme global flag is false', () => { + globalWithFlags[awsuiGlobalFlagsSymbol] = { oneTheme: false }; + expect(isThemeActive(Theme.OneTheme)).toBe(false); + }); +}); diff --git a/src/internal/visual-mode/__tests__/is-theme-active.test.ts b/src/internal/visual-mode/__tests__/is-theme-active.test.ts new file mode 100644 index 0000000..636290d --- /dev/null +++ b/src/internal/visual-mode/__tests__/is-theme-active.test.ts @@ -0,0 +1,99 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { isThemeActive, Theme } from '../index'; +import { awsuiGlobalFlagsSymbol, awsuiVisualRefreshFlag, FlagsHolder } from '../../global-flags'; + +declare const window: Window & FlagsHolder; + +afterEach(() => { + document.body.classList.remove('awsui-visual-refresh'); + document.body.classList.remove('awsui-one-theme'); + delete window[awsuiVisualRefreshFlag]; + delete window[awsuiGlobalFlagsSymbol]; +}); + +describe('Theme.VisualRefresh', () => { + test('returns false when nothing is set', () => { + expect(isThemeActive(Theme.VisualRefresh)).toBe(false); + }); + + test('returns true when the awsui-visual-refresh class is present', () => { + document.body.classList.add('awsui-visual-refresh'); + expect(isThemeActive(Theme.VisualRefresh)).toBe(true); + }); + + test('returns true when awsui-visual-refresh-flag returns true', () => { + window[awsuiVisualRefreshFlag] = () => true; + expect(isThemeActive(Theme.VisualRefresh)).toBe(true); + }); + + test('returns false when awsui-visual-refresh-flag returns false', () => { + window[awsuiVisualRefreshFlag] = () => false; + expect(isThemeActive(Theme.VisualRefresh)).toBe(false); + }); + + test('class presence wins over a flag returning false', () => { + document.body.classList.add('awsui-visual-refresh'); + window[awsuiVisualRefreshFlag] = () => false; + expect(isThemeActive(Theme.VisualRefresh)).toBe(true); + }); +}); + +describe('Theme.OneTheme', () => { + test('returns false when nothing is set', () => { + expect(isThemeActive(Theme.OneTheme)).toBe(false); + }); + + test('returns true when the awsui-one-theme class is present', () => { + document.body.classList.add('awsui-one-theme'); + expect(isThemeActive(Theme.OneTheme)).toBe(true); + }); + + test('returns true when the oneTheme global flag is true', () => { + window[awsuiGlobalFlagsSymbol] = { oneTheme: true }; + expect(isThemeActive(Theme.OneTheme)).toBe(true); + }); + + test('returns false when the oneTheme global flag is false', () => { + window[awsuiGlobalFlagsSymbol] = { oneTheme: false }; + expect(isThemeActive(Theme.OneTheme)).toBe(false); + }); + + test('returns false when awsui-global-flags exists but oneTheme is unset', () => { + window[awsuiGlobalFlagsSymbol] = {}; + expect(isThemeActive(Theme.OneTheme)).toBe(false); + }); + + test('class presence wins over a flag set to false', () => { + document.body.classList.add('awsui-one-theme'); + window[awsuiGlobalFlagsSymbol] = { oneTheme: false }; + expect(isThemeActive(Theme.OneTheme)).toBe(true); + }); +}); + +describe('theme isolation', () => { + test('activating one theme via class does not activate visual refresh', () => { + document.body.classList.add('awsui-one-theme'); + expect(isThemeActive(Theme.VisualRefresh)).toBe(false); + expect(isThemeActive(Theme.OneTheme)).toBe(true); + }); + + test('activating visual refresh via class does not activate one theme', () => { + document.body.classList.add('awsui-visual-refresh'); + expect(isThemeActive(Theme.VisualRefresh)).toBe(true); + expect(isThemeActive(Theme.OneTheme)).toBe(false); + }); + + test('activating one theme via flag does not activate visual refresh', () => { + window[awsuiGlobalFlagsSymbol] = { oneTheme: true }; + expect(isThemeActive(Theme.VisualRefresh)).toBe(false); + expect(isThemeActive(Theme.OneTheme)).toBe(true); + }); + + test('activating visual refresh via symbol does not activate one theme', () => { + window[awsuiVisualRefreshFlag] = () => true; + expect(isThemeActive(Theme.VisualRefresh)).toBe(true); + expect(isThemeActive(Theme.OneTheme)).toBe(false); + }); +}); diff --git a/src/internal/visual-mode/index.ts b/src/internal/visual-mode/index.ts index ccdf069..745a85b 100644 --- a/src/internal/visual-mode/index.ts +++ b/src/internal/visual-mode/index.ts @@ -137,3 +137,32 @@ export function useRuntimeVisualRefresh() { } return visualRefreshState; } + +export enum Theme { + VisualRefresh = 'visual-refresh', + OneTheme = 'one-theme', +} + +interface ThemeConfig { + className: string; + isFlagActive: () => boolean; +} + +const THEMES: Record = { + [Theme.VisualRefresh]: { + className: 'awsui-visual-refresh', + isFlagActive: () => !!getGlobal()?.[awsuiVisualRefreshFlag]?.(), + }, + [Theme.OneTheme]: { + className: 'awsui-one-theme', + isFlagActive: () => !!getGlobalFlag('oneTheme'), + }, +}; + +export function isThemeActive(theme: Theme): boolean { + const config = THEMES[theme]; + if (typeof document !== 'undefined' && document.querySelector(`.${config.className}`)) { + return true; + } + return config.isFlagActive(); +}