From 93ebc6e6124e37ff1cc439120f8f9e0dce384720 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Wed, 26 Nov 2025 17:43:03 -0800 Subject: [PATCH 1/4] test(scripts): replace mode with theme --- core/scripts/testing/scripts.js | 100 +++++++++++------- .../test/playwright/page/utils/set-content.ts | 15 ++- 2 files changed, 69 insertions(+), 46 deletions(-) diff --git a/core/scripts/testing/scripts.js b/core/scripts/testing/scripts.js index fa43991fd1d..9c0ee7d04d4 100644 --- a/core/scripts/testing/scripts.js +++ b/core/scripts/testing/scripts.js @@ -14,14 +14,16 @@ * The following URL parameters are supported: * - `rtl`: Set to `true` to enable right-to-left directionality. * - `ionic:_testing`: Set to `true` to identify testing environments. - * - `ionic:mode`: Set to `ios` or `md` to load a specific mode. - * Defaults to `md`. + * - `ionic:theme`: Set to `ionic`, `ios`, or `md` to load a specific + * theme. Defaults to `md`. * - `palette`: Set to `light`, `dark`, `high-contrast`, or * `high-contrast-dark` to load a specific palette. Defaults to `light`. */ -(function() { +const DEFAULT_THEME = 'md'; +(function() { + /** * The `rtl` param is used to set the directionality of the * document. This can be `true` or `false`. @@ -48,6 +50,34 @@ document.head.appendChild(style); } + /** + * The `theme` param is used to load a specific theme. + * This can be `ionic`, `ios`, or `md`. Default to `md` for tests. + */ + const themeQuery = window.location.search.match(/ionic:theme=([a-z0-9]+)/i); + const themeHash = window.location.hash.match(/ionic:theme=([a-z0-9]+)/i); + const themeAttr = document.documentElement.getAttribute('theme'); + const themeName = themeQuery?.[1] || themeHash?.[1] || themeAttr || DEFAULT_THEME; + + // TODO(): Remove this when the tokens are working for all components + // and the themes all use the same bundle + if ((themeQuery && themeQuery[1] === 'ionic') || themeAttr === 'ionic') { + const ionicThemeLinkTag = document.querySelector('link[href*="css/ionic/bundle.ionic.css"]'); + + if (!ionicThemeLinkTag) { + const linkTag = document.createElement('link'); + linkTag.setAttribute('rel', 'stylesheet'); + linkTag.setAttribute('type', 'text/css'); + linkTag.setAttribute('href', '/css/ionic/bundle.ionic.css'); + document.head.appendChild(linkTag); + } + + const defaultThemeLinkTag = document.querySelector('link[href*="css/ionic.bundle.css"]'); + if (defaultThemeLinkTag) { + defaultThemeLinkTag.remove(); + } + } + /** * The `palette` param is used to load a specific palette * for the theme. @@ -63,46 +93,40 @@ const paletteName = paletteQuery?.[1] || paletteHash?.[1] || darkClass || 'light'; - if (paletteName !== 'light') { - const linkTag = document.createElement('link'); - linkTag.setAttribute('rel', 'stylesheet'); - linkTag.setAttribute('type', 'text/css'); - linkTag.setAttribute('href', `/css/palettes/${paletteName}.always.css`); - document.head.appendChild(linkTag); + // Load theme tokens if the theme is valid + const validThemes = ['ionic', 'ios', 'md']; + if (themeName && validThemes.includes(themeName)) { + loadThemeTokens(themeName, paletteName); + } else if(themeName) { + console.warn( + `Unsupported theme "${themeName}". Supported themes are: ${validThemes.join(', ')}. Defaulting to ${DEFAULT_THEME}.` + ); } - /** - * The `ionic` theme uses a different stylesheet than the `iOS` and `md` themes. - * This is to ensure that the `ionic` theme is loaded when the `ionic:theme=ionic` - * or when the HTML tag has the `theme="ionic"` attribute. This is useful for - * the snapshot tests, where the `ionic` theme is not loaded by default. - */ - const themeQuery = window.location.search.match(/ionic:theme=([a-z]+)/); - const themeAttr = document.documentElement.getAttribute('theme'); - - if ((themeQuery && themeQuery[1] === 'ionic') || themeAttr === 'ionic') { - const ionicThemeLinkTag = document.querySelector('link[href*="css/ionic/bundle.ionic.css"]'); + async function loadThemeTokens(themeName, paletteName) { + try { + // Load the default tokens for the theme + const defaultTokens = await import(`/themes/${themeName}/default.tokens.js`); + const theme = defaultTokens.defaultTheme; - if (!ionicThemeLinkTag) { - const linkTag = document.createElement('link'); - linkTag.setAttribute('rel', 'stylesheet'); - linkTag.setAttribute('type', 'text/css'); - linkTag.setAttribute('href', '/css/ionic/bundle.ionic.css'); - document.head.appendChild(linkTag); - } + // If a specific palette is requested, modify the palette structure + // to set the enabled property to 'always' + if (paletteName === 'dark' && theme.palette?.dark) { + theme.palette.dark.enabled = 'always'; + } - const utilsBundleLinkTag = document.querySelector('link[href*="css/utils.bundle.css"]'); - if (!utilsBundleLinkTag) { - const linkTag = document.createElement('link'); - linkTag.setAttribute('rel', 'stylesheet'); - linkTag.setAttribute('type', 'text/css'); - linkTag.setAttribute('href', '/css/utils.bundle.css'); - document.head.appendChild(linkTag); - } + // Apply the theme tokens to Ionic config + window.Ionic = window.Ionic || {}; + window.Ionic.config = window.Ionic.config || {}; + window.Ionic.config.customTheme = theme; - const defaultThemeLinkTag = document.querySelector('link[href*="css/ionic.bundle.css"]'); - if (defaultThemeLinkTag) { - defaultThemeLinkTag.remove(); + // Re-apply the global theme + if (window.Ionic.config.get && window.Ionic.config.set) { + const themeModule = await import('/themes/utils/theme.js'); + themeModule.applyGlobalTheme(theme); + } + } catch (error) { + console.error(`Failed to load theme tokens for ${themeName}:`, error); } } diff --git a/core/src/utils/test/playwright/page/utils/set-content.ts b/core/src/utils/test/playwright/page/utils/set-content.ts index 7ee403d2e4e..adeb97c86e5 100644 --- a/core/src/utils/test/playwright/page/utils/set-content.ts +++ b/core/src/utils/test/playwright/page/utils/set-content.ts @@ -19,18 +19,18 @@ export const setContent = async (page: Page, html: string, testInfo: TestInfo, o let mode: Mode; let direction: Direction; - let theme: Theme; + let themeName: Theme; let palette: Palette; if (options == undefined) { mode = testInfo.project.metadata.mode; direction = testInfo.project.metadata.rtl ? 'rtl' : 'ltr'; - theme = testInfo.project.metadata.theme; + themeName = testInfo.project.metadata.theme; palette = testInfo.project.metadata.palette; } else { mode = options.mode; direction = options.direction; - theme = options.theme; + themeName = options.theme; palette = options.palette; } @@ -40,7 +40,7 @@ export const setContent = async (page: Page, html: string, testInfo: TestInfo, o // config passes in the importIonicFromCDN option. This is useful // when testing with the CDN version of Ionic. let ionicCSSImports = - theme === 'ionic' + themeName === 'ionic' ? ` @@ -54,7 +54,7 @@ export const setContent = async (page: Page, html: string, testInfo: TestInfo, o if (options?.importIonicFromCDN) { ionicCSSImports = - theme === 'ionic' + themeName === 'ionic' ? ` @@ -77,14 +77,13 @@ export const setContent = async (page: Page, html: string, testInfo: TestInfo, o ${ionicCSSImports} - ${palette !== 'light' ? `` : ''} ${ionicJSImports} @@ -107,7 +106,7 @@ export const setContent = async (page: Page, html: string, testInfo: TestInfo, o testInfo.annotations.push({ type: 'theme', - description: theme, + description: themeName, }); if (baseUrl) { From 0ee350979c90cb204ce11af77d796543f6ba2691 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Wed, 26 Nov 2025 17:50:48 -0800 Subject: [PATCH 2/4] docs(scripts): update comment --- core/scripts/testing/scripts.js | 100 ++++++++++++-------------------- 1 file changed, 38 insertions(+), 62 deletions(-) diff --git a/core/scripts/testing/scripts.js b/core/scripts/testing/scripts.js index 9c0ee7d04d4..3fd6a12295c 100644 --- a/core/scripts/testing/scripts.js +++ b/core/scripts/testing/scripts.js @@ -14,16 +14,14 @@ * The following URL parameters are supported: * - `rtl`: Set to `true` to enable right-to-left directionality. * - `ionic:_testing`: Set to `true` to identify testing environments. - * - `ionic:theme`: Set to `ionic`, `ios`, or `md` to load a specific - * theme. Defaults to `md`. + * - `ionic:theme`: Set to `ios` or `md` to load a specific theme. + * Defaults to `md`. * - `palette`: Set to `light`, `dark`, `high-contrast`, or * `high-contrast-dark` to load a specific palette. Defaults to `light`. */ -const DEFAULT_THEME = 'md'; - (function() { - + /** * The `rtl` param is used to set the directionality of the * document. This can be `true` or `false`. @@ -50,34 +48,6 @@ const DEFAULT_THEME = 'md'; document.head.appendChild(style); } - /** - * The `theme` param is used to load a specific theme. - * This can be `ionic`, `ios`, or `md`. Default to `md` for tests. - */ - const themeQuery = window.location.search.match(/ionic:theme=([a-z0-9]+)/i); - const themeHash = window.location.hash.match(/ionic:theme=([a-z0-9]+)/i); - const themeAttr = document.documentElement.getAttribute('theme'); - const themeName = themeQuery?.[1] || themeHash?.[1] || themeAttr || DEFAULT_THEME; - - // TODO(): Remove this when the tokens are working for all components - // and the themes all use the same bundle - if ((themeQuery && themeQuery[1] === 'ionic') || themeAttr === 'ionic') { - const ionicThemeLinkTag = document.querySelector('link[href*="css/ionic/bundle.ionic.css"]'); - - if (!ionicThemeLinkTag) { - const linkTag = document.createElement('link'); - linkTag.setAttribute('rel', 'stylesheet'); - linkTag.setAttribute('type', 'text/css'); - linkTag.setAttribute('href', '/css/ionic/bundle.ionic.css'); - document.head.appendChild(linkTag); - } - - const defaultThemeLinkTag = document.querySelector('link[href*="css/ionic.bundle.css"]'); - if (defaultThemeLinkTag) { - defaultThemeLinkTag.remove(); - } - } - /** * The `palette` param is used to load a specific palette * for the theme. @@ -93,40 +63,46 @@ const DEFAULT_THEME = 'md'; const paletteName = paletteQuery?.[1] || paletteHash?.[1] || darkClass || 'light'; - // Load theme tokens if the theme is valid - const validThemes = ['ionic', 'ios', 'md']; - if (themeName && validThemes.includes(themeName)) { - loadThemeTokens(themeName, paletteName); - } else if(themeName) { - console.warn( - `Unsupported theme "${themeName}". Supported themes are: ${validThemes.join(', ')}. Defaulting to ${DEFAULT_THEME}.` - ); + if (paletteName !== 'light') { + const linkTag = document.createElement('link'); + linkTag.setAttribute('rel', 'stylesheet'); + linkTag.setAttribute('type', 'text/css'); + linkTag.setAttribute('href', `/css/palettes/${paletteName}.always.css`); + document.head.appendChild(linkTag); } - async function loadThemeTokens(themeName, paletteName) { - try { - // Load the default tokens for the theme - const defaultTokens = await import(`/themes/${themeName}/default.tokens.js`); - const theme = defaultTokens.defaultTheme; + /** + * The `ionic` theme uses a different stylesheet than the `iOS` and `md` themes. + * This is to ensure that the `ionic` theme is loaded when the `ionic:theme=ionic` + * or when the HTML tag has the `theme="ionic"` attribute. This is useful for + * the snapshot tests, where the `ionic` theme is not loaded by default. + */ + const themeQuery = window.location.search.match(/ionic:theme=([a-z]+)/); + const themeAttr = document.documentElement.getAttribute('theme'); - // If a specific palette is requested, modify the palette structure - // to set the enabled property to 'always' - if (paletteName === 'dark' && theme.palette?.dark) { - theme.palette.dark.enabled = 'always'; - } + if ((themeQuery && themeQuery[1] === 'ionic') || themeAttr === 'ionic') { + const ionicThemeLinkTag = document.querySelector('link[href*="css/ionic/bundle.ionic.css"]'); + + if (!ionicThemeLinkTag) { + const linkTag = document.createElement('link'); + linkTag.setAttribute('rel', 'stylesheet'); + linkTag.setAttribute('type', 'text/css'); + linkTag.setAttribute('href', '/css/ionic/bundle.ionic.css'); + document.head.appendChild(linkTag); + } - // Apply the theme tokens to Ionic config - window.Ionic = window.Ionic || {}; - window.Ionic.config = window.Ionic.config || {}; - window.Ionic.config.customTheme = theme; + const utilsBundleLinkTag = document.querySelector('link[href*="css/utils.bundle.css"]'); + if (!utilsBundleLinkTag) { + const linkTag = document.createElement('link'); + linkTag.setAttribute('rel', 'stylesheet'); + linkTag.setAttribute('type', 'text/css'); + linkTag.setAttribute('href', '/css/utils.bundle.css'); + document.head.appendChild(linkTag); + } - // Re-apply the global theme - if (window.Ionic.config.get && window.Ionic.config.set) { - const themeModule = await import('/themes/utils/theme.js'); - themeModule.applyGlobalTheme(theme); - } - } catch (error) { - console.error(`Failed to load theme tokens for ${themeName}:`, error); + const defaultThemeLinkTag = document.querySelector('link[href*="css/ionic.bundle.css"]'); + if (defaultThemeLinkTag) { + defaultThemeLinkTag.remove(); } } From c998e2d2b0c9a92650b8a2574c25de607b02937a Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Mon, 1 Dec 2025 09:31:28 -0800 Subject: [PATCH 3/4] refactor(set-content): undo changes --- .../test/playwright/page/utils/set-content.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/core/src/utils/test/playwright/page/utils/set-content.ts b/core/src/utils/test/playwright/page/utils/set-content.ts index adeb97c86e5..2263a63258f 100644 --- a/core/src/utils/test/playwright/page/utils/set-content.ts +++ b/core/src/utils/test/playwright/page/utils/set-content.ts @@ -19,18 +19,18 @@ export const setContent = async (page: Page, html: string, testInfo: TestInfo, o let mode: Mode; let direction: Direction; - let themeName: Theme; + let theme: Theme; let palette: Palette; if (options == undefined) { mode = testInfo.project.metadata.mode; direction = testInfo.project.metadata.rtl ? 'rtl' : 'ltr'; - themeName = testInfo.project.metadata.theme; + theme = testInfo.project.metadata.theme; palette = testInfo.project.metadata.palette; } else { mode = options.mode; direction = options.direction; - themeName = options.theme; + theme = options.theme; palette = options.palette; } @@ -40,7 +40,7 @@ export const setContent = async (page: Page, html: string, testInfo: TestInfo, o // config passes in the importIonicFromCDN option. This is useful // when testing with the CDN version of Ionic. let ionicCSSImports = - themeName === 'ionic' + theme === 'ionic' ? ` @@ -54,7 +54,7 @@ export const setContent = async (page: Page, html: string, testInfo: TestInfo, o if (options?.importIonicFromCDN) { ionicCSSImports = - themeName === 'ionic' + theme === 'ionic' ? ` @@ -77,13 +77,14 @@ export const setContent = async (page: Page, html: string, testInfo: TestInfo, o ${ionicCSSImports} + ${palette !== 'light' ? `` : ''} ${ionicJSImports} @@ -106,7 +107,7 @@ export const setContent = async (page: Page, html: string, testInfo: TestInfo, o testInfo.annotations.push({ type: 'theme', - description: themeName, + description: theme, }); if (baseUrl) { From d794056d446fa52ad2b67605526b09df530159b0 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Mon, 1 Dec 2025 09:32:05 -0800 Subject: [PATCH 4/4] refactor(set-content): remove extra comma --- core/src/utils/test/playwright/page/utils/set-content.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/utils/test/playwright/page/utils/set-content.ts b/core/src/utils/test/playwright/page/utils/set-content.ts index 2263a63258f..7ee403d2e4e 100644 --- a/core/src/utils/test/playwright/page/utils/set-content.ts +++ b/core/src/utils/test/playwright/page/utils/set-content.ts @@ -84,7 +84,7 @@ export const setContent = async (page: Page, html: string, testInfo: TestInfo, o window.Ionic = { config: { mode: '${mode}', - theme: '${theme}', + theme: '${theme}' } }