diff --git a/src/common/utils/index.ts b/src/common/utils/index.ts index c679e9c12..999cdfc03 100644 --- a/src/common/utils/index.ts +++ b/src/common/utils/index.ts @@ -32,6 +32,7 @@ export { detectLocale } from './detectLocale'; export { createRegExpForMetaPattern } from './createRegExpForMetaPattern'; export { formatCard } from './formatCard'; export { truncate } from './truncate'; +export { isEmojiSupported } from './isEmojiSupported'; export type { URLParams } from './getUrlParams'; export type { CountrySubdivision, Country } from './countries'; diff --git a/src/common/utils/isEmojiSupported.test.ts b/src/common/utils/isEmojiSupported.test.ts new file mode 100644 index 000000000..24e0e3313 --- /dev/null +++ b/src/common/utils/isEmojiSupported.test.ts @@ -0,0 +1,51 @@ +import { isEmojiSupported } from './isEmojiSupported'; + +describe('isEmojiSupported', () => { + let ctx; + + beforeEach(() => { + // Mock the canvas context + ctx = { + fillStyle: '', + fillRect: jest.fn(), + textBaseline: '', + font: '', + fillText: jest.fn(), + getImageData: jest.fn(), + }; + // Setup to return a fake image data array + ctx.getImageData.mockReturnValue({ + data: new Uint8ClampedArray(32 * 32 * 4).fill(0), // All pixels transparent + }); + // Mock canvas creation + document.createElement = jest.fn().mockReturnValue({ + getContext: () => ctx, + width: 32, + height: 32, + }); + }); + + test('should detect supported emoji by checking non-transparent pixels', () => { + // Mocking the context to simulate an emoji being supported (non-transparent pixels) + ctx.getImageData.mockReturnValue({ + data: new Uint8ClampedArray(32 * 32 * 4).fill(255), // All pixels non-transparent + }); + + expect(isEmojiSupported('😊')).toBe(true); + }); + + test('should detect unsupported emoji by checking all transparent pixels', () => { + // Using the initial all-transparent setup by default + + expect(isEmojiSupported('😊')).toBe(false); + }); + + test('should handle null canvas context gracefully', () => { + // Simulate canvas context not being available + (document.createElement as any).mockReturnValue({ + getContext: () => null, + }); + + expect(isEmojiSupported('😊')).toBe(false); + }); +}); diff --git a/src/common/utils/isEmojiSupported.ts b/src/common/utils/isEmojiSupported.ts new file mode 100644 index 000000000..a424cf0cd --- /dev/null +++ b/src/common/utils/isEmojiSupported.ts @@ -0,0 +1,37 @@ +import { isNil } from './isNil'; + +// Tests flag emoji +export function isEmojiSupported(emoji: string): boolean { + if (isNil(emoji)) { + return false; + } + const canvas = document.createElement('canvas'); + canvas.width = canvas.height = 32; // The size should be large enough to hold the emoji + const ctx = canvas.getContext('2d'); + if (!ctx) { + return false; // In case the browser doesn't support canvas + } + + ctx.fillStyle = 'white'; // Background color + ctx.fillRect(0, 0, 32, 32); // Fill the background + ctx.textBaseline = 'top'; + ctx.font = '32px Arial'; // A common font that may not support the emoji + + ctx.fillText(emoji, 0, 0); // Draw the emoji on the canvas + + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height).data; + + let isSupported = false; + + // Loop through pixel data; every four items in the array represent the RGBA values of a pixel + for (let i = 0; i < imageData.length; i += 4) { + const alpha = imageData[i + 3]; // Get the alpha value of the pixel + if (alpha > 0) { + // Check if the pixel is not completely transparent + isSupported = true; + break; + } + } + + return isSupported; +} diff --git a/src/components/ViewContainer/LocaleSelector.tsx b/src/components/ViewContainer/LocaleSelector.tsx index 455838141..701d7320e 100644 --- a/src/components/ViewContainer/LocaleSelector.tsx +++ b/src/components/ViewContainer/LocaleSelector.tsx @@ -1,8 +1,9 @@ import { ChevronDownIcon } from '@chakra-ui/icons'; import { Flex, Menu, MenuButton, MenuItem, MenuList, Text } from '@chakra-ui/react'; -import { useContext, useEffect, useState } from 'react'; +import { useContext, useEffect, useMemo, useState } from 'react'; import { CustomizationContext, Locale } from 'checkout/contexts'; +import { isEmojiSupported } from 'checkout/utils'; import { useLocale } from './useLocale'; @@ -68,13 +69,17 @@ export function LocaleSelector({ onLocaleChange }: LocaleSelectorProps) { } }, [localeState, activeLocaleCode]); + const isEmojiAvailable = useMemo(() => isEmojiSupported('🏳️'), []); + return ( -