diff --git a/packages/hint-meta-theme-color/src/hint.ts b/packages/hint-meta-theme-color/src/hint.ts index 2855b7c2b58..9fb0928f128 100644 --- a/packages/hint-meta-theme-color/src/hint.ts +++ b/packages/hint-meta-theme-color/src/hint.ts @@ -38,14 +38,14 @@ export default class MetaThemeColorHint implements IHint { public constructor(context: HintContext) { let bodyElementWasReached: boolean = false; - let firstThemeColorMetaElement: HTMLElement; + let themeColorElementsAndMedia: Map = new Map(); const checkIfThemeColorMetaElementWasSpecified = (event: TraverseEnd) => { const pageDOM = context.pageDOM as HTMLDocument; const { resource } = event; const linksToManifest = pageDOM.querySelectorAll('link[rel="manifest"]').length > 0; - if (!firstThemeColorMetaElement && linksToManifest) { + if (themeColorElementsAndMedia.size === 0 && linksToManifest) { context.report( resource, getMessage('metaElementNotSpecified', context.language), @@ -150,16 +150,21 @@ export default class MetaThemeColorHint implements IHint { } /* - * Check if a `theme-color` meta element was already specified. + * Check if a `theme-color` meta element with the same media attribute was already specified. * - * From https://html.spec.whatwg.org/multipage/semantics.html#meta-theme-color + * Multiple theme-color meta elements are allowed if they have different media attributes. + * From MDN: "Most meta properties can be used only once. However, theme-color can be + * used multiple times if unique media values are provided." * - * " There must not be more than one meta element with its - * name attribute value set to an ASCII case-insensitive - * match for theme-color per document. " + * References: + * - https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name/theme-color + * - https://html.spec.whatwg.org/multipage/semantics.html#meta-theme-color */ - if (firstThemeColorMetaElement) { + const mediaAttributeValue = normalizeString(element.getAttribute('media'), ''); + const mediaKey = mediaAttributeValue || 'default'; // Use 'default' for elements without media attribute + + if (themeColorElementsAndMedia.has(mediaKey)) { context.report( resource, getMessage('metaElementDuplicated', context.language), @@ -171,7 +176,7 @@ export default class MetaThemeColorHint implements IHint { return; } - firstThemeColorMetaElement = element; + themeColorElementsAndMedia.set(mediaKey, element); // Check if the `theme-color` meta element: diff --git a/packages/hint-meta-theme-color/tests/tests.ts b/packages/hint-meta-theme-color/tests/tests.ts index 5e01d7538c5..38073231c33 100644 --- a/packages/hint-meta-theme-color/tests/tests.ts +++ b/packages/hint-meta-theme-color/tests/tests.ts @@ -34,8 +34,9 @@ const validColorValues = [ 'transparent' ]; -const generateThemeColorMetaElement = (contentValue = '#f00', nameValue = 'theme-color') => { - return ``; +const generateThemeColorMetaElement = (contentValue = '#f00', nameValue = 'theme-color', mediaValue?: string) => { + const mediaAttr = mediaValue ? ` media="${mediaValue}"` : ''; + return ``; }; const generateTest = (colorValues: string[], valueType = 'valid', reason?: string) => { @@ -112,6 +113,42 @@ const defaultTests: HintTest[] = [ } ]; +// New tests for multiple theme-color elements with media attributes +const mediaAttributeTests: HintTest[] = [ + { + name: `Multiple 'theme-color' elements with different media attributes should pass`, + serverConfig: generateHTMLPage(`${generateThemeColorMetaElement('#f00', 'theme-color', '(prefers-color-scheme: light)')}${generateThemeColorMetaElement('#333', 'theme-color', '(prefers-color-scheme: dark)')}`) + }, + { + name: `Multiple 'theme-color' elements with same media attribute should fail`, + reports: [{ + message: metaElementIsNotNeededErrorMessage, + severity: Severity.warning + }], + serverConfig: generateHTMLPage(`${generateThemeColorMetaElement('#f00', 'theme-color', '(prefers-color-scheme: light)')}${generateThemeColorMetaElement('#333', 'theme-color', '(prefers-color-scheme: light)')}`) + }, + { + name: `One 'theme-color' without media and one with media should pass`, + serverConfig: generateHTMLPage(`${generateThemeColorMetaElement('#f00')}${generateThemeColorMetaElement('#333', 'theme-color', '(prefers-color-scheme: dark)')}`) + }, + { + name: `Multiple 'theme-color' elements without media attributes should fail`, + reports: [{ + message: metaElementIsNotNeededErrorMessage, + severity: Severity.warning + }], + serverConfig: generateHTMLPage(`${generateThemeColorMetaElement('#f00')}${generateThemeColorMetaElement('#333')}`) + }, + { + name: `Multiple 'theme-color' elements with various different media attributes should pass`, + serverConfig: generateHTMLPage(`${generateThemeColorMetaElement('#f00', 'theme-color', '(prefers-color-scheme: light)')}${generateThemeColorMetaElement('#333', 'theme-color', '(prefers-color-scheme: dark)')}${generateThemeColorMetaElement('#666', 'theme-color', '(prefers-contrast: high)')}`) + }, + { + name: `Issue #6001 scenario: theme-color with prefers-color-scheme dark and light should pass`, + serverConfig: generateHTMLPage(`${generateThemeColorMetaElement('#13171a', 'theme-color', '(prefers-color-scheme: dark)')}${generateThemeColorMetaElement('#f8f4f0', 'theme-color', '(prefers-color-scheme: light)')}`) + } +]; + const testForNoSupportForHexWithAlpha: HintTest[] = [...generateTest(notAlwaysSupportedColorValues, 'unsupported', 'because of the targeted browsers')]; testHint(hintPath, defaultTests, { @@ -120,4 +157,10 @@ testHint(hintPath, defaultTests, { 'firefox 60' ] }); +testHint(hintPath, mediaAttributeTests, { + browserslist: [ + 'chrome 65', + 'firefox 60' + ] +}); testHint(hintPath, testForNoSupportForHexWithAlpha, { browserslist: ['chrome 50'] });