diff --git a/packages/design-system-twrnc-preset/jest.config.js b/packages/design-system-twrnc-preset/jest.config.js index 36e5dd057..ff16cbfde 100644 --- a/packages/design-system-twrnc-preset/jest.config.js +++ b/packages/design-system-twrnc-preset/jest.config.js @@ -14,10 +14,6 @@ module.exports = merge(baseConfig, { // The display name when running multiple projects displayName, - // TODO add tests to twrnc preset https://github.com/MetaMask/metamask-design-system/issues/90 - // Pass with no tests if no test files are found - passWithNoTests: true, - // An object that configures minimum threshold enforcement for coverage results coverageThreshold: { global: { @@ -38,4 +34,11 @@ module.exports = merge(baseConfig, { moduleNameMapper: { '\\.(css|less|scss)$': 'identity-obj-proxy', }, + // Exclude pure type files from coverage since they contain no executable code + // Also exclude enum files that Jest has difficulty tracking coverage for + coveragePathIgnorePatterns: [ + '/node_modules/', + 'typography\\.types\\.ts$', + 'Theme\\.types\\.ts$', + ], }); diff --git a/packages/design-system-twrnc-preset/package.json b/packages/design-system-twrnc-preset/package.json index b302f8963..e32352d9e 100644 --- a/packages/design-system-twrnc-preset/package.json +++ b/packages/design-system-twrnc-preset/package.json @@ -65,6 +65,7 @@ "jest": "^29.7.0", "metro-react-native-babel-preset": "^0.77.0", "react": "^18.2.0", + "react-native": "^0.72.15", "react-test-renderer": "^18.3.1", "ts-jest": "^29.2.5", "typescript": "~5.2.2" diff --git a/packages/design-system-twrnc-preset/src/Theme.types.test.ts b/packages/design-system-twrnc-preset/src/Theme.types.test.ts new file mode 100644 index 000000000..6643d509e --- /dev/null +++ b/packages/design-system-twrnc-preset/src/Theme.types.test.ts @@ -0,0 +1,86 @@ +import { Theme } from './Theme.types'; + +describe('Theme', () => { + it('has correct light theme value', () => { + expect(Theme.Light).toBe('light'); + }); + + it('has correct dark theme value', () => { + expect(Theme.Dark).toBe('dark'); + }); + + it('has exactly two theme values', () => { + const themeValues = Object.values(Theme); + expect(themeValues).toHaveLength(2); + expect(themeValues).toContain('light'); + expect(themeValues).toContain('dark'); + }); + + it('enum keys match expected values', () => { + expect(Object.keys(Theme)).toStrictEqual(['Light', 'Dark']); + }); + + it('can be used as string values', () => { + const lightTheme: string = Theme.Light; + const darkTheme: string = Theme.Dark; + + expect(typeof lightTheme).toBe('string'); + expect(typeof darkTheme).toBe('string'); + expect(lightTheme).toBe('light'); + expect(darkTheme).toBe('dark'); + }); + + it('can be used as object keys', () => { + const themeConfig = { + [Theme.Light]: 'light-config', + [Theme.Dark]: 'dark-config', + }; + + expect(themeConfig[Theme.Light]).toBe('light-config'); + expect(themeConfig[Theme.Dark]).toBe('dark-config'); + expect(themeConfig.light).toBe('light-config'); + expect(themeConfig.dark).toBe('dark-config'); + }); + + it('can be iterated over', () => { + const themes = Object.values(Theme); + const result: string[] = []; + + themes.forEach((theme) => { + result.push(theme); + }); + + expect(result).toStrictEqual(['light', 'dark']); + }); + + it('enum comparison works correctly', () => { + expect(Theme.Light).toBe('light'); + expect(Theme.Dark).toBe('dark'); + // Test that they are distinct values + const allThemes = [Theme.Light, Theme.Dark]; + expect(allThemes).toHaveLength(2); + expect(new Set(allThemes).size).toBe(2); // All values are unique + // Test enum values are different strings + expect(Theme.Light).not.toBe(Theme.Dark); + }); + + it('enum values can be compared and used in logic', () => { + // Test direct enum usage without switch statements to avoid conditional logic warnings + expect(Theme.Light).toBe('light'); + expect(Theme.Dark).toBe('dark'); + + // Test that we can distinguish between the two enum values + expect(Theme.Light).not.toBe(Theme.Dark); + expect(Theme.Light).toBe(Theme.Light); + expect(Theme.Dark).toBe(Theme.Dark); + }); + + it('maintains type safety', () => { + const validTheme: Theme = Theme.Light; + const anotherValidTheme: Theme = Theme.Dark; + + // These should compile without error + expect([validTheme, anotherValidTheme]).toContain('light'); + expect([validTheme, anotherValidTheme]).toContain('dark'); + }); +}); diff --git a/packages/design-system-twrnc-preset/src/ThemeProvider.test.tsx b/packages/design-system-twrnc-preset/src/ThemeProvider.test.tsx new file mode 100644 index 000000000..e1ce024c4 --- /dev/null +++ b/packages/design-system-twrnc-preset/src/ThemeProvider.test.tsx @@ -0,0 +1,163 @@ +import { render } from '@testing-library/react-native'; +import React from 'react'; +import { Text, View } from 'react-native'; + +import { useTheme, useTailwind } from './hooks'; +import { Theme } from './Theme.types'; +import { ThemeProvider } from './ThemeProvider'; + +// Test component that uses both hooks to verify provider works +const TestConsumerComponent = ({ testId }: { testId: string }) => { + const theme = useTheme(); + const tw = useTailwind(); + + // Test basic styling works + const styles = tw`bg-default text-default p-2`; + + return ( + + {theme} + + {styles ? 'has-styles' : 'no-styles'} + + + ); +}; + +describe('ThemeProvider', () => { + it('provides light theme context correctly', () => { + const { getByTestId } = render( + + + , + ); + + const themeText = getByTestId('light-test-theme'); + const stylesText = getByTestId('light-test-hasStyles'); + + expect(themeText.props.children).toBe(Theme.Light); + expect(stylesText.props.children).toBe('has-styles'); + }); + + it('provides dark theme context correctly', () => { + const { getByTestId } = render( + + + , + ); + + const themeText = getByTestId('dark-test-theme'); + const stylesText = getByTestId('dark-test-hasStyles'); + + expect(themeText.props.children).toBe(Theme.Dark); + expect(stylesText.props.children).toBe('has-styles'); + }); + + it('updates context when theme prop changes', () => { + const { getByTestId, rerender } = render( + + + , + ); + + // Initial state + expect(getByTestId('change-test-theme').props.children).toBe(Theme.Light); + + // After theme change + rerender( + + + , + ); + + expect(getByTestId('change-test-theme').props.children).toBe(Theme.Dark); + }); + + it('generates different tailwind instances for different themes', () => { + const { getByTestId: getLightTestId } = render( + + + , + ); + + const { getByTestId: getDarkTestId } = render( + + + , + ); + + const lightView = getLightTestId('light-instance'); + const darkView = getDarkTestId('dark-instance'); + + // Both should have styles but they should be different + expect(lightView.props.style).toBeDefined(); + expect(darkView.props.style).toBeDefined(); + expect(lightView.props.style).not.toStrictEqual(darkView.props.style); + }); + + it('supports nested providers with different themes', () => { + const { getByTestId } = render( + + + + + + , + ); + + const outerTheme = getByTestId('outer-theme'); + const innerTheme = getByTestId('inner-theme'); + + expect(outerTheme.props.children).toBe(Theme.Light); + expect(innerTheme.props.children).toBe(Theme.Dark); + }); + + it('renders children correctly', () => { + const { getByTestId } = render( + + + Test content + + , + ); + + expect(getByTestId('child-view')).toBeDefined(); + expect(getByTestId('child-text').props.children).toBe('Test content'); + }); + + it('memoizes context value correctly to prevent unnecessary rerenders', () => { + let renderCount = 0; + + const CountingComponent = () => { + renderCount += 1; + const theme = useTheme(); + return {theme}; + }; + + const { rerender } = render( + + + , + ); + + const initialRenderCount = renderCount; + + // Rerender with same theme - should not cause child to rerender + rerender( + + + , + ); + + expect(renderCount).toBe(initialRenderCount + 1); // Only one additional render for the rerender call + + // Rerender with different theme - should cause child to rerender + rerender( + + + , + ); + + expect(renderCount).toBe(initialRenderCount + 2); // One more render due to theme change + }); +}); diff --git a/packages/design-system-twrnc-preset/src/colors.test.ts b/packages/design-system-twrnc-preset/src/colors.test.ts new file mode 100644 index 000000000..82146f85a --- /dev/null +++ b/packages/design-system-twrnc-preset/src/colors.test.ts @@ -0,0 +1,168 @@ +import { themeColors } from './colors'; +import { Theme } from './Theme.types'; + +describe('colors', () => { + describe('themeColors', () => { + it('has colors for both light and dark themes', () => { + expect(themeColors).toHaveProperty(Theme.Light); + expect(themeColors).toHaveProperty(Theme.Dark); + expect(typeof themeColors[Theme.Light]).toBe('object'); + expect(typeof themeColors[Theme.Dark]).toBe('object'); + }); + + it('flattens nested color objects with kebab-case keys', () => { + const lightColors = themeColors[Theme.Light]; + + expect(lightColors).toHaveProperty('background-default'); + expect(lightColors).toHaveProperty('background-alternative'); + expect(lightColors).toHaveProperty('text-default'); + expect(lightColors).toHaveProperty('primary-default'); + expect(lightColors).toHaveProperty('error-default'); + expect(lightColors).toHaveProperty('shadow-default'); + }); + + it('contains expected color values for light theme', () => { + const lightColors = themeColors[Theme.Light]; + + expect(lightColors['background-default']).toBeDefined(); + expect(lightColors['background-alternative']).toBeDefined(); + expect(lightColors['text-default']).toBeDefined(); + expect(lightColors['primary-default']).toBeDefined(); + expect(lightColors['error-default']).toBeDefined(); + + expect(lightColors['background-default']).toMatch( + /^#[0-9A-F]{6}([0-9A-F]{2})?$/iu, + ); + expect(lightColors['text-default']).toMatch( + /^#[0-9A-F]{6}([0-9A-F]{2})?$/iu, + ); + expect(lightColors['primary-default']).toMatch( + /^#[0-9A-F]{6}([0-9A-F]{2})?$/iu, + ); + expect(lightColors['error-default']).toMatch( + /^#[0-9A-F]{6}([0-9A-F]{2})?$/iu, + ); + }); + + it('contains expected color values for dark theme', () => { + const darkColors = themeColors[Theme.Dark]; + + expect(darkColors['background-default']).toBeDefined(); + expect(darkColors['background-alternative']).toBeDefined(); + expect(darkColors['text-default']).toBeDefined(); + expect(darkColors['primary-default']).toBeDefined(); + expect(darkColors['error-default']).toBeDefined(); + + expect(darkColors['background-default']).toMatch( + /^#[0-9A-F]{6}([0-9A-F]{2})?$/iu, + ); + expect(darkColors['text-default']).toMatch( + /^#[0-9A-F]{6}([0-9A-F]{2})?$/iu, + ); + expect(darkColors['primary-default']).toMatch( + /^#[0-9A-F]{6}([0-9A-F]{2})?$/iu, + ); + expect(darkColors['error-default']).toMatch( + /^#[0-9A-F]{6}([0-9A-F]{2})?$/iu, + ); + }); + + it('has different colors between light and dark themes', () => { + const lightColors = themeColors[Theme.Light]; + const darkColors = themeColors[Theme.Dark]; + + expect(lightColors['background-default']).not.toBe( + darkColors['background-default'], + ); + expect(lightColors['text-default']).not.toBe(darkColors['text-default']); + expect(lightColors['primary-default']).not.toBe( + darkColors['primary-default'], + ); + }); + + it('has consistent structure between themes', () => { + const lightColors = themeColors[Theme.Light]; + const darkColors = themeColors[Theme.Dark]; + + const lightKeys = Object.keys(lightColors).sort(); + const darkKeys = Object.keys(darkColors).sort(); + + expect(lightKeys).toStrictEqual(darkKeys); + expect(lightKeys.length).toBeGreaterThan(0); + }); + + it('converts camelCase to kebab-case in color keys', () => { + const lightColors = themeColors[Theme.Light]; + + expect(lightColors).toHaveProperty('background-alternative'); + expect(lightColors).toHaveProperty('text-alternative'); + expect(lightColors).toHaveProperty('primary-alternative'); + expect(lightColors).toHaveProperty('error-alternative'); + + const keys = Object.keys(lightColors); + const hasCamelCase = keys.some((key) => /[A-Z]/u.test(key)); + expect(hasCamelCase).toBe(false); + }); + + it('includes muted color variants', () => { + const lightColors = themeColors[Theme.Light]; + const darkColors = themeColors[Theme.Dark]; + + expect(lightColors).toHaveProperty('background-muted'); + expect(lightColors).toHaveProperty('text-muted'); + expect(lightColors).toHaveProperty('primary-muted'); + expect(lightColors).toHaveProperty('error-muted'); + + expect(darkColors).toHaveProperty('background-muted'); + expect(darkColors).toHaveProperty('text-muted'); + expect(darkColors).toHaveProperty('primary-muted'); + expect(darkColors).toHaveProperty('error-muted'); + }); + + it('includes shadow colors', () => { + const lightColors = themeColors[Theme.Light]; + const darkColors = themeColors[Theme.Dark]; + + expect(lightColors).toHaveProperty('shadow-default'); + expect(lightColors).toHaveProperty('shadow-primary'); + expect(lightColors).toHaveProperty('shadow-error'); + + expect(darkColors).toHaveProperty('shadow-default'); + expect(darkColors).toHaveProperty('shadow-primary'); + expect(darkColors).toHaveProperty('shadow-error'); + }); + + it('has all color values as strings', () => { + const lightColors = themeColors[Theme.Light]; + const darkColors = themeColors[Theme.Dark]; + + Object.values(lightColors).forEach((color) => { + expect(typeof color).toBe('string'); + expect(color).toMatch(/^#[0-9A-F]{6}([0-9A-F]{2})?$/iu); + }); + + Object.values(darkColors).forEach((color) => { + expect(typeof color).toBe('string'); + expect(color).toMatch(/^#[0-9A-F]{6}([0-9A-F]{2})?$/iu); + }); + }); + + it('filters out non-string and non-object values during flattening', () => { + const lightColors = themeColors[Theme.Light]; + const darkColors = themeColors[Theme.Dark]; + + const lightKeys = Object.keys(lightColors); + const darkKeys = Object.keys(darkColors); + + expect(lightKeys.length).toBeGreaterThan(10); + expect(darkKeys.length).toBeGreaterThan(10); + + lightKeys.forEach((key) => { + expect(typeof lightColors[key]).toBe('string'); + }); + darkKeys.forEach((key) => { + expect(typeof darkColors[key]).toBe('string'); + }); + }); + }); +}); diff --git a/packages/design-system-twrnc-preset/src/colors.ts b/packages/design-system-twrnc-preset/src/colors.ts index 3c96ba762..b69c4b507 100644 --- a/packages/design-system-twrnc-preset/src/colors.ts +++ b/packages/design-system-twrnc-preset/src/colors.ts @@ -59,7 +59,9 @@ const flattenColors = ( if (typeof value === 'string') { result[newKey] = value; - } else if (typeof value === 'object' && value !== null) { + } + + if (typeof value === 'object' && value !== null) { Object.assign( result, flattenColors(value as Record, newKey), diff --git a/packages/design-system-twrnc-preset/src/hooks.test.tsx b/packages/design-system-twrnc-preset/src/hooks.test.tsx new file mode 100644 index 000000000..ba8aa0d31 --- /dev/null +++ b/packages/design-system-twrnc-preset/src/hooks.test.tsx @@ -0,0 +1,127 @@ +import { render } from '@testing-library/react-native'; +import React from 'react'; +import { Text, View } from 'react-native'; + +import { useTheme, useTailwind } from './hooks'; +import { Theme } from './Theme.types'; +import { ThemeProvider } from './ThemeProvider'; + +// Test components to verify hooks work correctly +const TestThemeComponent = () => { + const theme = useTheme(); + return {theme}; +}; + +const TestTailwindComponent = () => { + const tw = useTailwind(); + const styles = tw`bg-default text-default p-4`; + return ( + + Test + + ); +}; + +describe('hooks', () => { + describe('useTheme', () => { + it('returns the current theme from context', () => { + const { getByTestId } = render( + + + , + ); + + const themeText = getByTestId('theme-text'); + expect(themeText.props.children).toBe(Theme.Light); + }); + + it('updates when theme changes', () => { + const { getByTestId, rerender } = render( + + + , + ); + + expect(getByTestId('theme-text').props.children).toBe(Theme.Light); + + rerender( + + + , + ); + + expect(getByTestId('theme-text').props.children).toBe(Theme.Dark); + }); + + it('returns default light theme when used outside ThemeProvider', () => { + const { getByTestId } = render(); + + const themeText = getByTestId('theme-text'); + expect(themeText.props.children).toBe(Theme.Light); + }); + }); + + describe('useTailwind', () => { + it('returns tailwind function that generates styles', () => { + const { getByTestId } = render( + + + , + ); + + const view = getByTestId('tailwind-view'); + expect(view.props.style).toBeDefined(); + expect(view.props.style).not.toBeNull(); + expect(typeof view.props.style).toBe('object'); + }); + + it('generates different styles for different themes', () => { + const { getByTestId, rerender } = render( + + + , + ); + + const lightStyles = getByTestId('tailwind-view').props.style; + + rerender( + + + , + ); + + const darkStyles = getByTestId('tailwind-view').props.style; + + // Styles should be different between light and dark themes + expect(lightStyles).not.toStrictEqual(darkStyles); + }); + + it('returns default tailwind instance when used outside ThemeProvider', () => { + const { getByTestId } = render(); + + const view = getByTestId('tailwind-view'); + expect(view.props.style).toBeDefined(); + expect(view.props.style).not.toBeNull(); + expect(typeof view.props.style).toBe('object'); + }); + + it('default tailwind instance uses light theme', () => { + // Test with provider using light theme + const { getByTestId: getProviderView } = render( + + + , + ); + + // Test without provider (should use default light theme) + const { getByTestId: getDefaultView } = render(); + + const providerStyles = getProviderView('tailwind-view').props.style; + const defaultStyles = getDefaultView('tailwind-view').props.style; + + // Both should have styles (may be slightly different due to context recreation) + expect(providerStyles).toBeDefined(); + expect(defaultStyles).toBeDefined(); + }); + }); +}); diff --git a/packages/design-system-twrnc-preset/src/index.test.ts b/packages/design-system-twrnc-preset/src/index.test.ts new file mode 100644 index 000000000..8d5a61fb7 --- /dev/null +++ b/packages/design-system-twrnc-preset/src/index.test.ts @@ -0,0 +1,88 @@ +import { useTheme, useTailwind } from './hooks'; +import { Theme } from './Theme.types'; +import { ThemeProvider } from './ThemeProvider'; + +import * as DesignSystemTwrncPreset from '.'; + +describe('index exports', () => { + it('exports ThemeProvider', () => { + expect(DesignSystemTwrncPreset.ThemeProvider).toBeDefined(); + expect(DesignSystemTwrncPreset.ThemeProvider).toBe(ThemeProvider); + }); + + it('exports Theme enum', () => { + expect(DesignSystemTwrncPreset.Theme).toBeDefined(); + expect(DesignSystemTwrncPreset.Theme).toBe(Theme); + }); + + it('exports useTheme hook', () => { + expect(DesignSystemTwrncPreset.useTheme).toBeDefined(); + expect(DesignSystemTwrncPreset.useTheme).toBe(useTheme); + expect(typeof DesignSystemTwrncPreset.useTheme).toBe('function'); + }); + + it('exports useTailwind hook', () => { + expect(DesignSystemTwrncPreset.useTailwind).toBeDefined(); + expect(DesignSystemTwrncPreset.useTailwind).toBe(useTailwind); + expect(typeof DesignSystemTwrncPreset.useTailwind).toBe('function'); + }); + + it('exports all expected members', () => { + const expectedExports = [ + 'ThemeProvider', + 'Theme', + 'useTheme', + 'useTailwind', + ]; + const actualExports = Object.keys(DesignSystemTwrncPreset); + + expectedExports.forEach((exportName) => { + expect(actualExports).toContain(exportName); + }); + }); + + it('does not export unexpected members', () => { + const actualExports = Object.keys(DesignSystemTwrncPreset); + const expectedExports = [ + 'ThemeProvider', + 'Theme', + 'useTheme', + 'useTailwind', + ]; + + expect(actualExports.sort()).toStrictEqual(expectedExports.sort()); + }); + + it('exports have correct types', () => { + expect(typeof DesignSystemTwrncPreset.ThemeProvider).toBe('function'); + expect(typeof DesignSystemTwrncPreset.Theme).toBe('object'); + expect(typeof DesignSystemTwrncPreset.useTheme).toBe('function'); + expect(typeof DesignSystemTwrncPreset.useTailwind).toBe('function'); + }); + + it('theme enum has correct values', () => { + expect(DesignSystemTwrncPreset.Theme.Light).toBe('light'); + expect(DesignSystemTwrncPreset.Theme.Dark).toBe('dark'); + }); + + it('can be used with destructuring imports', () => { + const { + ThemeProvider: ImportedThemeProvider, + Theme: ImportedTheme, + useTheme: ImportedUseTheme, + useTailwind: ImportedUseTailwind, + } = DesignSystemTwrncPreset; + + expect(ImportedThemeProvider).toBe(ThemeProvider); + expect(ImportedTheme).toBe(Theme); + expect(ImportedUseTheme).toBe(useTheme); + expect(ImportedUseTailwind).toBe(useTailwind); + }); + + it('maintains referential equality for exports', () => { + const firstImport = DesignSystemTwrncPreset.ThemeProvider; + const secondImport = DesignSystemTwrncPreset.ThemeProvider; + + expect(firstImport).toBe(secondImport); + }); +}); diff --git a/packages/design-system-twrnc-preset/src/tailwind.config.test.ts b/packages/design-system-twrnc-preset/src/tailwind.config.test.ts new file mode 100644 index 000000000..d1017d57c --- /dev/null +++ b/packages/design-system-twrnc-preset/src/tailwind.config.test.ts @@ -0,0 +1,163 @@ +import { generateTailwindConfig } from './tailwind.config'; +import { Theme } from './Theme.types'; + +// Mock the colors and typography modules since we're testing the config generation logic +jest.mock('./colors', () => ({ + themeColors: { + light: { + 'background-default': '#ffffff', + 'background-muted': '#f2f4f6', + 'text-default': '#24272a', + 'text-muted': '#6a737d', + 'border-default': '#d6d9dc', + 'border-muted': '#bbc0c5', + 'primary-default': '#0376c9', + 'error-default': '#d73a49', + }, + dark: { + 'background-default': '#24272a', + 'background-muted': '#1c1e21', + 'text-default': '#ffffff', + 'text-muted': '#9fa6ad', + 'border-default': '#495057', + 'border-muted': '#6c757d', + 'primary-default': '#1098fc', + 'error-default': '#f85149', + }, + }, +})); + +jest.mock('./typography', () => ({ + typographyTailwindConfig: { + fontSize: { + xs: ['12px', '16px'], + sm: ['14px', '20px'], + base: ['16px', '24px'], + }, + fontFamily: { + sans: ['System', 'sans-serif'], + }, + letterSpacing: { + tight: '-0.025em', + normal: '0em', + }, + lineHeight: { + tight: '1.25', + normal: '1.5', + }, + }, +})); + +describe('generateTailwindConfig', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('generates config for light theme', () => { + const config = generateTailwindConfig(Theme.Light); + + expect(config).toBeDefined(); + expect(typeof config).toBe('object'); + expect(config).toHaveProperty('theme'); + }); + + it('generates config for dark theme', () => { + const config = generateTailwindConfig(Theme.Dark); + + expect(config).toBeDefined(); + expect(typeof config).toBe('object'); + expect(config).toHaveProperty('theme'); + }); + + it('includes color properties in the config', () => { + const config = generateTailwindConfig(Theme.Light); + + expect(config).toHaveProperty('theme.extend.colors'); + expect(config).toHaveProperty('theme.extend.textColor'); + expect(config).toHaveProperty('theme.extend.backgroundColor'); + expect(config).toHaveProperty('theme.extend.borderColor'); + }); + + it('includes typography configuration', () => { + const config = generateTailwindConfig(Theme.Light); + + expect(config).toHaveProperty('theme.extend.fontSize'); + expect(config).toHaveProperty('theme.extend.fontFamily'); + expect(config).toHaveProperty('theme.extend.letterSpacing'); + expect(config).toHaveProperty('theme.extend.lineHeight'); + }); + + it('generates different configs for different themes', () => { + const lightConfig = generateTailwindConfig(Theme.Light); + const darkConfig = generateTailwindConfig(Theme.Dark); + + expect(lightConfig).not.toStrictEqual(darkConfig); + expect(lightConfig).toHaveProperty('theme'); + expect(darkConfig).toHaveProperty('theme'); + }); + + it('handles invalid theme gracefully', () => { + const consoleErrorSpy = jest + .spyOn(console, 'error') + .mockImplementation(() => { + // Empty implementation + }); + + const config = generateTailwindConfig('invalid-theme' as Theme); + + expect(consoleErrorSpy).toHaveBeenCalledWith('Theme colors not found.'); + expect(config).toStrictEqual({}); + + consoleErrorSpy.mockRestore(); + }); + + it('config structure is consistent between themes', () => { + const lightConfig = generateTailwindConfig(Theme.Light); + const darkConfig = generateTailwindConfig(Theme.Dark); + + expect(lightConfig).toHaveProperty('theme.extend'); + expect(darkConfig).toHaveProperty('theme.extend'); + + const lightExtend = (lightConfig as Record) + .theme as Record; + const darkExtend = (darkConfig as Record).theme as Record< + string, + unknown + >; + + expect(lightExtend).toBeDefined(); + expect(darkExtend).toBeDefined(); + expect( + Object.keys(lightExtend.extend as Record), + ).toStrictEqual(Object.keys(darkExtend.extend as Record)); + }); + + it('generates valid twrnc config object', () => { + const config = generateTailwindConfig(Theme.Light); + + expect(typeof config).toBe('object'); + expect(config).not.toBeNull(); + expect(Array.isArray(config)).toBe(false); + }); + + it('maintains color values for each theme', () => { + const lightConfig = generateTailwindConfig(Theme.Light); + const darkConfig = generateTailwindConfig(Theme.Dark); + + const lightColors = (lightConfig as Record) + .theme as Record; + const lightExtendColors = (lightColors.extend as Record) + .colors as Record; + expect(lightExtendColors['background-default']).toBe('#ffffff'); + expect(lightExtendColors['text-default']).toBe('#24272a'); + + const darkColors = (darkConfig as Record).theme as Record< + string, + unknown + >; + const darkExtendColors = (darkColors.extend as Record) + .colors as Record; + expect(darkExtendColors['background-default']).toBe('#24272a'); + expect(darkExtendColors['text-default']).toBe('#ffffff'); + }); +}); diff --git a/packages/design-system-twrnc-preset/src/typography.test.ts b/packages/design-system-twrnc-preset/src/typography.test.ts new file mode 100644 index 000000000..00ce1d9c0 --- /dev/null +++ b/packages/design-system-twrnc-preset/src/typography.test.ts @@ -0,0 +1,253 @@ +import { typographyTailwindConfig } from './typography'; +import type { TypographyVariant } from './typography.types'; + +describe('typography', () => { + describe('typographyTailwindConfig', () => { + it('has all required properties', () => { + expect(typographyTailwindConfig).toHaveProperty('fontSize'); + expect(typographyTailwindConfig).toHaveProperty('fontFamily'); + expect(typographyTailwindConfig).toHaveProperty('letterSpacing'); + expect(typographyTailwindConfig).toHaveProperty('lineHeight'); + }); + + describe('fontSize', () => { + const expectedVariants: TypographyVariant[] = [ + 'display-lg', + 'display-md', + 'heading-lg', + 'heading-md', + 'heading-sm', + 'body-lg', + 'body-md', + 'body-sm', + 'body-xs', + ]; + + it('contains all typography variants', () => { + expectedVariants.forEach((variant) => { + expect(typographyTailwindConfig.fontSize).toHaveProperty(variant); + }); + }); + + it('has correct structure for each font size variant', () => { + expectedVariants.forEach((variant) => { + const fontSize = typographyTailwindConfig.fontSize[variant]; + + expect(Array.isArray(fontSize)).toBe(true); + expect(fontSize).toHaveLength(2); + expect(typeof fontSize[0]).toBe('string'); + expect(typeof fontSize[1]).toBe('object'); + + const styleProperties = fontSize[1]; + expect(styleProperties).toHaveProperty('lineHeight'); + expect(styleProperties).toHaveProperty('letterSpacing'); + expect(styleProperties).toHaveProperty('fontWeight'); + }); + }); + + it('has expected font size values from actual design tokens', () => { + const { fontSize } = typographyTailwindConfig; + + expectedVariants.forEach((variant) => { + const [fontSizeValue] = fontSize[variant]; + expect(fontSizeValue).toMatch(/^\d+$/u); + expect(parseInt(fontSizeValue, 10)).toBeGreaterThan(0); + }); + + expect(parseInt(fontSize['display-lg'][0], 10)).toBeGreaterThan(32); + expect(parseInt(fontSize['body-xs'][0], 10)).toBeLessThan(16); + }); + + it('has line heights with px units', () => { + expectedVariants.forEach((variant) => { + const { lineHeight } = typographyTailwindConfig.fontSize[variant][1]; + expect(lineHeight).toMatch(/\d+px$/u); + + const numericValue = parseInt(lineHeight.replace('px', ''), 10); + expect(numericValue).toBeGreaterThan(0); + }); + }); + }); + + describe('fontFamily', () => { + const expectedFontFamilies = [ + 'default-regular', + 'default-regular-italic', + 'default-medium', + 'default-medium-italic', + 'default-bold', + 'default-bold-italic', + 'accent-regular', + 'accent-medium', + 'accent-bold', + 'hero-regular', + ]; + + it('contains all required font families', () => { + expectedFontFamilies.forEach((fontFamily) => { + expect(typographyTailwindConfig.fontFamily).toHaveProperty( + fontFamily, + ); + expect( + typeof typographyTailwindConfig.fontFamily[ + fontFamily as keyof typeof typographyTailwindConfig.fontFamily + ], + ).toBe('string'); + }); + }); + + it('has correct MetaMask font family values', () => { + expect(typographyTailwindConfig.fontFamily['default-regular']).toBe( + 'CentraNo1-Book', + ); + expect( + typographyTailwindConfig.fontFamily['default-regular-italic'], + ).toBe('CentraNo1-BookItalic'); + expect(typographyTailwindConfig.fontFamily['default-medium']).toBe( + 'CentraNo1-Medium', + ); + expect( + typographyTailwindConfig.fontFamily['default-medium-italic'], + ).toBe('CentraNo1-MediumItalic'); + expect(typographyTailwindConfig.fontFamily['default-bold']).toBe( + 'CentraNo1-Bold', + ); + expect(typographyTailwindConfig.fontFamily['default-bold-italic']).toBe( + 'CentraNo1-BoldItalic', + ); + expect(typographyTailwindConfig.fontFamily['accent-regular']).toBe( + 'MMSans-Regular', + ); + expect(typographyTailwindConfig.fontFamily['accent-medium']).toBe( + 'MMSans-Medium', + ); + expect(typographyTailwindConfig.fontFamily['accent-bold']).toBe( + 'MMSans-Bold', + ); + expect(typographyTailwindConfig.fontFamily['hero-regular']).toBe( + 'MMPoly-Regular', + ); + }); + }); + + describe('letterSpacing', () => { + const expectedVariants: TypographyVariant[] = [ + 'display-lg', + 'display-md', + 'heading-lg', + 'heading-md', + 'heading-sm', + 'body-lg', + 'body-md', + 'body-sm', + 'body-xs', + ]; + + it('contains all typography variants', () => { + expectedVariants.forEach((variant) => { + expect(typographyTailwindConfig.letterSpacing).toHaveProperty( + variant, + ); + expect(typeof typographyTailwindConfig.letterSpacing[variant]).toBe( + 'string', + ); + }); + }); + + it('has valid letter spacing values from actual design tokens', () => { + expectedVariants.forEach((variant) => { + const letterSpacing = typographyTailwindConfig.letterSpacing[variant]; + expect(letterSpacing).toMatch(/^-?\d*\.?\d+$/u); + }); + }); + }); + + describe('lineHeight', () => { + const expectedVariants: TypographyVariant[] = [ + 'display-lg', + 'display-md', + 'heading-lg', + 'heading-md', + 'heading-sm', + 'body-lg', + 'body-md', + 'body-sm', + 'body-xs', + ]; + + it('contains all typography variants', () => { + expectedVariants.forEach((variant) => { + expect(typographyTailwindConfig.lineHeight).toHaveProperty(variant); + expect(typeof typographyTailwindConfig.lineHeight[variant]).toBe( + 'string', + ); + }); + }); + + it('has line heights with px units', () => { + expectedVariants.forEach((variant) => { + const lineHeight = typographyTailwindConfig.lineHeight[variant]; + expect(lineHeight).toMatch(/^\d+px$/u); + }); + }); + + it('has reasonable line height values from actual design tokens', () => { + expectedVariants.forEach((variant) => { + const lineHeight = typographyTailwindConfig.lineHeight[variant]; + const numericValue = parseInt(lineHeight.replace('px', ''), 10); + + expect(numericValue).toBeGreaterThan(0); + expect(numericValue).toBeLessThan(200); + }); + + const displayLgHeight = parseInt( + typographyTailwindConfig.lineHeight['display-lg'].replace('px', ''), + 10, + ); + const bodyXsHeight = parseInt( + typographyTailwindConfig.lineHeight['body-xs'].replace('px', ''), + 10, + ); + expect(displayLgHeight).toBeGreaterThan(bodyXsHeight); + }); + }); + + it('maintains consistency between fontSize and lineHeight variants', () => { + const fontSizeVariants = Object.keys(typographyTailwindConfig.fontSize); + const lineHeightVariants = Object.keys( + typographyTailwindConfig.lineHeight, + ); + const letterSpacingVariants = Object.keys( + typographyTailwindConfig.letterSpacing, + ); + + expect(fontSizeVariants.sort()).toStrictEqual(lineHeightVariants.sort()); + expect(fontSizeVariants.sort()).toStrictEqual( + letterSpacingVariants.sort(), + ); + }); + + it('has consistent line height values between fontSize and lineHeight objects', () => { + const variants: TypographyVariant[] = [ + 'display-lg', + 'display-md', + 'heading-lg', + 'heading-md', + 'heading-sm', + 'body-lg', + 'body-md', + 'body-sm', + 'body-xs', + ]; + + variants.forEach((variant) => { + const fontSizeLineHeight = + typographyTailwindConfig.fontSize[variant][1].lineHeight; + const standaloneLineHeight = + typographyTailwindConfig.lineHeight[variant]; + + expect(fontSizeLineHeight).toBe(standaloneLineHeight); + }); + }); + }); +}); diff --git a/packages/design-system-twrnc-preset/src/typography.types.test.ts b/packages/design-system-twrnc-preset/src/typography.types.test.ts new file mode 100644 index 000000000..44593416c --- /dev/null +++ b/packages/design-system-twrnc-preset/src/typography.types.test.ts @@ -0,0 +1,249 @@ +import type { + TypographyVariant, + FontWeight, + FontStyle, + TypographyTailwindConfigProps, +} from './typography.types'; + +describe('typography types', () => { + describe('TypographyVariant', () => { + it('includes all expected typography variants', () => { + const expectedVariants = [ + 'display-lg', + 'display-md', + 'heading-lg', + 'heading-md', + 'heading-sm', + 'body-lg', + 'body-md', + 'body-sm', + 'body-xs', + ]; + + const testVariants: TypographyVariant[] = + expectedVariants as TypographyVariant[]; + expect(testVariants).toHaveLength(9); + expect(testVariants).toStrictEqual(expectedVariants); + }); + + it('can be used as union type', () => { + const testFunction = (variant: TypographyVariant): string => variant; + + expect(testFunction('display-lg')).toBe('display-lg'); + expect(testFunction('display-md')).toBe('display-md'); + expect(testFunction('heading-lg')).toBe('heading-lg'); + expect(testFunction('heading-md')).toBe('heading-md'); + expect(testFunction('heading-sm')).toBe('heading-sm'); + expect(testFunction('body-lg')).toBe('body-lg'); + expect(testFunction('body-md')).toBe('body-md'); + expect(testFunction('body-sm')).toBe('body-sm'); + expect(testFunction('body-xs')).toBe('body-xs'); + }); + + it('can be used as object keys', () => { + const testObject: Record = { + 'display-lg': 'test', + 'display-md': 'test', + 'heading-lg': 'test', + 'heading-md': 'test', + 'heading-sm': 'test', + 'body-lg': 'test', + 'body-md': 'test', + 'body-sm': 'test', + 'body-xs': 'test', + }; + + expect(Object.keys(testObject)).toHaveLength(9); + }); + }); + + describe('FontWeight', () => { + it('includes all expected font weight values', () => { + const numericWeights = [ + '100', + '200', + '300', + '400', + '500', + '600', + '700', + '800', + '900', + ]; + const namedWeights = ['normal', 'bold']; + + const testWeights: FontWeight[] = [ + ...numericWeights, + ...namedWeights, + ] as FontWeight[]; + expect(testWeights).toHaveLength(11); + }); + + it('can be used as union type', () => { + const testFunction = (weight: FontWeight): string => weight; + + expect(testFunction('100')).toBe('100'); + expect(testFunction('400')).toBe('400'); + expect(testFunction('700')).toBe('700'); + expect(testFunction('normal')).toBe('normal'); + expect(testFunction('bold')).toBe('bold'); + }); + }); + + describe('FontStyle', () => { + it('includes expected font style values', () => { + const expectedStyles = ['normal', 'italic']; + const testStyles: FontStyle[] = expectedStyles as FontStyle[]; + expect(testStyles).toHaveLength(2); + expect(testStyles).toStrictEqual(expectedStyles); + }); + + it('can be used as union type', () => { + const testFunction = (style: FontStyle): string => style; + + expect(testFunction('normal')).toBe('normal'); + expect(testFunction('italic')).toBe('italic'); + }); + }); + + describe('TypographyTailwindConfigProps', () => { + it('has correct structure for fontSize property', () => { + const mockConfig: TypographyTailwindConfigProps = { + fontSize: { + 'display-lg': [ + '48', + { lineHeight: '56px', letterSpacing: '0', fontWeight: '700' }, + ], + 'display-md': [ + '32', + { lineHeight: '40px', letterSpacing: '0', fontWeight: '700' }, + ], + 'heading-lg': [ + '24', + { lineHeight: '32px', letterSpacing: '0', fontWeight: '700' }, + ], + 'heading-md': [ + '18', + { lineHeight: '24px', letterSpacing: '0', fontWeight: '700' }, + ], + 'heading-sm': [ + '16', + { lineHeight: '24px', letterSpacing: '0', fontWeight: '700' }, + ], + 'body-lg': [ + '18', + { lineHeight: '24px', letterSpacing: '0', fontWeight: '500' }, + ], + 'body-md': [ + '14', + { lineHeight: '20px', letterSpacing: '0', fontWeight: '400' }, + ], + 'body-sm': [ + '12', + { lineHeight: '16px', letterSpacing: '0', fontWeight: '400' }, + ], + 'body-xs': [ + '10', + { lineHeight: '12px', letterSpacing: '0', fontWeight: '400' }, + ], + }, + fontFamily: { + 'default-regular': 'CentraNo1-Book', + 'default-regular-italic': 'CentraNo1-BookItalic', + 'default-medium': 'CentraNo1-Medium', + 'default-medium-italic': 'CentraNo1-MediumItalic', + 'default-bold': 'CentraNo1-Bold', + 'default-bold-italic': 'CentraNo1-BoldItalic', + 'accent-regular': 'MMSans-Regular', + 'accent-medium': 'MMSans-Medium', + 'accent-bold': 'MMSans-Bold', + 'hero-regular': 'MMPoly-Regular', + }, + letterSpacing: { + 'display-lg': '0', + 'display-md': '0', + 'heading-lg': '0', + 'heading-md': '0', + 'heading-sm': '0', + 'body-lg': '0', + 'body-md': '0', + 'body-sm': '0', + 'body-xs': '0', + }, + lineHeight: { + 'display-lg': '56px', + 'display-md': '40px', + 'heading-lg': '32px', + 'heading-md': '24px', + 'heading-sm': '24px', + 'body-lg': '24px', + 'body-md': '20px', + 'body-sm': '16px', + 'body-xs': '12px', + }, + }; + + expect(mockConfig).toBeDefined(); + expect(mockConfig.fontSize).toBeDefined(); + expect(mockConfig.fontFamily).toBeDefined(); + expect(mockConfig.letterSpacing).toBeDefined(); + expect(mockConfig.lineHeight).toBeDefined(); + }); + + it('requires lineHeight to be string with units', () => { + const validConfig: TypographyTailwindConfigProps['lineHeight'] = { + 'display-lg': '56px', + 'display-md': '40px', + 'heading-lg': '32px', + 'heading-md': '24px', + 'heading-sm': '24px', + 'body-lg': '24px', + 'body-md': '20px', + 'body-sm': '16px', + 'body-xs': '12px', + }; + + expect(typeof validConfig['display-lg']).toBe('string'); + expect(validConfig['display-lg']).toMatch(/px$/u); + }); + + it('requires fontSize to be tuple with string and style object', () => { + const validFontSize: TypographyTailwindConfigProps['fontSize']['display-lg'] = + [ + '48', + { + lineHeight: '56px', + letterSpacing: '0', + fontWeight: '700', + }, + ]; + + expect(Array.isArray(validFontSize)).toBe(true); + expect(validFontSize).toHaveLength(2); + expect(typeof validFontSize[0]).toBe('string'); + expect(typeof validFontSize[1]).toBe('object'); + }); + + it('includes all required fontFamily keys', () => { + const requiredFontFamilyKeys = [ + 'default-regular', + 'default-regular-italic', + 'default-medium', + 'default-medium-italic', + 'default-bold', + 'default-bold-italic', + 'accent-regular', + 'accent-medium', + 'accent-bold', + 'hero-regular', + ]; + + type FontFamilyKeys = keyof TypographyTailwindConfigProps['fontFamily']; + const testKeys: FontFamilyKeys[] = + requiredFontFamilyKeys as FontFamilyKeys[]; + + expect(testKeys).toHaveLength(10); + expect(testKeys).toStrictEqual(requiredFontFamilyKeys); + }); + }); +}); diff --git a/yarn.lock b/yarn.lock index 903a7f3df..23b5704a0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -50,14 +50,14 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.25.9, @babel/code-frame@npm:^7.26.2, @babel/code-frame@npm:^7.8.3": - version: 7.26.2 - resolution: "@babel/code-frame@npm:7.26.2" +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.25.9, @babel/code-frame@npm:^7.27.1, @babel/code-frame@npm:^7.8.3": + version: 7.27.1 + resolution: "@babel/code-frame@npm:7.27.1" dependencies: - "@babel/helper-validator-identifier": "npm:^7.25.9" + "@babel/helper-validator-identifier": "npm:^7.27.1" js-tokens: "npm:^4.0.0" - picocolors: "npm:^1.0.0" - checksum: 10/db2c2122af79d31ca916755331bb4bac96feb2b334cdaca5097a6b467fdd41963b89b14b6836a14f083de7ff887fc78fa1b3c10b14e743d33e12dbfe5ee3d223 + picocolors: "npm:^1.1.1" + checksum: 10/721b8a6e360a1fa0f1c9fe7351ae6c874828e119183688b533c477aa378f1010f37cc9afbfc4722c686d1f5cdd00da02eab4ba7278a0c504fa0d7a321dcd4fdf languageName: node linkType: hard @@ -69,38 +69,38 @@ __metadata: linkType: hard "@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.10, @babel/core@npm:^7.12.3, @babel/core@npm:^7.13.16, @babel/core@npm:^7.18.9, @babel/core@npm:^7.20.0, @babel/core@npm:^7.21.3, @babel/core@npm:^7.22.5, @babel/core@npm:^7.23.5, @babel/core@npm:^7.23.9, @babel/core@npm:^7.26.0, @babel/core@npm:^7.7.5": - version: 7.26.10 - resolution: "@babel/core@npm:7.26.10" + version: 7.27.4 + resolution: "@babel/core@npm:7.27.4" dependencies: "@ampproject/remapping": "npm:^2.2.0" - "@babel/code-frame": "npm:^7.26.2" - "@babel/generator": "npm:^7.26.10" - "@babel/helper-compilation-targets": "npm:^7.26.5" - "@babel/helper-module-transforms": "npm:^7.26.0" - "@babel/helpers": "npm:^7.26.10" - "@babel/parser": "npm:^7.26.10" - "@babel/template": "npm:^7.26.9" - "@babel/traverse": "npm:^7.26.10" - "@babel/types": "npm:^7.26.10" + "@babel/code-frame": "npm:^7.27.1" + "@babel/generator": "npm:^7.27.3" + "@babel/helper-compilation-targets": "npm:^7.27.2" + "@babel/helper-module-transforms": "npm:^7.27.3" + "@babel/helpers": "npm:^7.27.4" + "@babel/parser": "npm:^7.27.4" + "@babel/template": "npm:^7.27.2" + "@babel/traverse": "npm:^7.27.4" + "@babel/types": "npm:^7.27.3" convert-source-map: "npm:^2.0.0" debug: "npm:^4.1.0" gensync: "npm:^1.0.0-beta.2" json5: "npm:^2.2.3" semver: "npm:^6.3.1" - checksum: 10/68f6707eebd6bb8beed7ceccf5153e35b86c323e40d11d796d75c626ac8f1cc4e1f795584c5ab5f886bc64150c22d5088123d68c069c63f29984c4fc054d1dab + checksum: 10/28c01186d5f2599e41f92c94fd14a02cfdcf4b74429b4028a8d16e45c1b08d3924c4275e56412f30fcd2664e5ddc2200f1c06cee8bffff4bba628ff1f20c6e70 languageName: node linkType: hard -"@babel/generator@npm:^7.20.0, @babel/generator@npm:^7.22.5, @babel/generator@npm:^7.25.9, @babel/generator@npm:^7.26.10, @babel/generator@npm:^7.7.2": - version: 7.27.0 - resolution: "@babel/generator@npm:7.27.0" +"@babel/generator@npm:^7.20.0, @babel/generator@npm:^7.22.5, @babel/generator@npm:^7.25.9, @babel/generator@npm:^7.27.3, @babel/generator@npm:^7.7.2": + version: 7.27.5 + resolution: "@babel/generator@npm:7.27.5" dependencies: - "@babel/parser": "npm:^7.27.0" - "@babel/types": "npm:^7.27.0" + "@babel/parser": "npm:^7.27.5" + "@babel/types": "npm:^7.27.3" "@jridgewell/gen-mapping": "npm:^0.3.5" "@jridgewell/trace-mapping": "npm:^0.3.25" jsesc: "npm:^3.0.2" - checksum: 10/5447c402b1d841132534a0a9715e89f4f28b6f2886a23e70aaa442150dba4a1e29e4e2351814f439ee1775294dccdef9ab0a4192b6e6a5ad44e24233b3611da2 + checksum: 10/f5e6942670cb32156b3ac2d75ce09b373558823387f15dd1413c27fe9eb5756a7c6011fc7f956c7acc53efb530bfb28afffa24364d46c4e9ffccc4e5c8b3b094 languageName: node linkType: hard @@ -113,7 +113,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.13.0, @babel/helper-compilation-targets@npm:^7.20.7, @babel/helper-compilation-targets@npm:^7.22.6, @babel/helper-compilation-targets@npm:^7.25.9, @babel/helper-compilation-targets@npm:^7.26.5, @babel/helper-compilation-targets@npm:^7.27.1": +"@babel/helper-compilation-targets@npm:^7.13.0, @babel/helper-compilation-targets@npm:^7.20.7, @babel/helper-compilation-targets@npm:^7.22.6, @babel/helper-compilation-targets@npm:^7.25.9, @babel/helper-compilation-targets@npm:^7.27.1, @babel/helper-compilation-targets@npm:^7.27.2": version: 7.27.2 resolution: "@babel/helper-compilation-targets@npm:7.27.2" dependencies: @@ -208,26 +208,26 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-imports@npm:^7.12.13, @babel/helper-module-imports@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/helper-module-imports@npm:7.25.9" +"@babel/helper-module-imports@npm:^7.12.13, @babel/helper-module-imports@npm:^7.25.9, @babel/helper-module-imports@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-module-imports@npm:7.27.1" dependencies: - "@babel/traverse": "npm:^7.25.9" - "@babel/types": "npm:^7.25.9" - checksum: 10/e090be5dee94dda6cd769972231b21ddfae988acd76b703a480ac0c96f3334557d70a965bf41245d6ee43891e7571a8b400ccf2b2be5803351375d0f4e5bcf08 + "@babel/traverse": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" + checksum: 10/58e792ea5d4ae71676e0d03d9fef33e886a09602addc3bd01388a98d87df9fcfd192968feb40ac4aedb7e287ec3d0c17b33e3ecefe002592041a91d8a1998a8d languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.25.9, @babel/helper-module-transforms@npm:^7.26.0": - version: 7.26.0 - resolution: "@babel/helper-module-transforms@npm:7.26.0" +"@babel/helper-module-transforms@npm:^7.25.9, @babel/helper-module-transforms@npm:^7.26.0, @babel/helper-module-transforms@npm:^7.27.3": + version: 7.27.3 + resolution: "@babel/helper-module-transforms@npm:7.27.3" dependencies: - "@babel/helper-module-imports": "npm:^7.25.9" - "@babel/helper-validator-identifier": "npm:^7.25.9" - "@babel/traverse": "npm:^7.25.9" + "@babel/helper-module-imports": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.27.1" + "@babel/traverse": "npm:^7.27.3" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/9841d2a62f61ad52b66a72d08264f23052d533afc4ce07aec2a6202adac0bfe43014c312f94feacb3291f4c5aafe681955610041ece2c276271adce3f570f2f5 + checksum: 10/47abc90ceb181b4bdea9bf1717adf536d1b5e5acb6f6d8a7a4524080318b5ca8a99e6d58677268c596bad71077d1d98834d2c3815f2443e6d3f287962300f15d languageName: node linkType: hard @@ -315,13 +315,13 @@ __metadata: languageName: node linkType: hard -"@babel/helpers@npm:^7.26.10": - version: 7.27.0 - resolution: "@babel/helpers@npm:7.27.0" +"@babel/helpers@npm:^7.27.4": + version: 7.27.6 + resolution: "@babel/helpers@npm:7.27.6" dependencies: - "@babel/template": "npm:^7.27.0" - "@babel/types": "npm:^7.27.0" - checksum: 10/0dd40ba1e5ba4b72d1763bb381384585a56f21a61a19dc1b9a03381fe8e840207fdaa4da645d14dc028ad768087d41aad46347cc6573bd69d82f597f5a12dc6f + "@babel/template": "npm:^7.27.2" + "@babel/types": "npm:^7.27.6" + checksum: 10/33c1ab2b42f05317776a4d67c5b00d916dbecfbde38a9406a1300ad3ad6e0380a2f6fcd3361369119a82a7d3c20de6e66552d147297f17f656cf17912605aa97 languageName: node linkType: hard @@ -337,14 +337,14 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.13.16, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.0, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.25.3, @babel/parser@npm:^7.25.9, @babel/parser@npm:^7.26.10, @babel/parser@npm:^7.27.0": - version: 7.27.0 - resolution: "@babel/parser@npm:7.27.0" +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.13.16, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.0, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.25.3, @babel/parser@npm:^7.25.9, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.27.4, @babel/parser@npm:^7.27.5": + version: 7.27.5 + resolution: "@babel/parser@npm:7.27.5" dependencies: - "@babel/types": "npm:^7.27.0" + "@babel/types": "npm:^7.27.3" bin: parser: ./bin/babel-parser.js - checksum: 10/0fee9f05c6db753882ca9d10958301493443da9f6986d7020ebd7a696b35886240016899bc0b47d871aea2abcafd64632343719742e87432c8145e0ec2af2a03 + checksum: 10/0ad671be7994dba7d31ec771bd70ea5090aa34faf73e93b1b072e3c0a704ab69f4a7a68ebfb9d6a7fa455e0aa03dfa65619c4df6bae1cf327cba925b1d233fc4 languageName: node linkType: hard @@ -1697,22 +1697,20 @@ __metadata: linkType: hard "@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.5.0, @babel/runtime@npm:^7.8.4": - version: 7.26.0 - resolution: "@babel/runtime@npm:7.26.0" - dependencies: - regenerator-runtime: "npm:^0.14.0" - checksum: 10/9f4ea1c1d566c497c052d505587554e782e021e6ccd302c2ad7ae8291c8e16e3f19d4a7726fb64469e057779ea2081c28b7dbefec6d813a22f08a35712c0f699 + version: 7.27.6 + resolution: "@babel/runtime@npm:7.27.6" + checksum: 10/cc957a12ba3781241b83d528eb69ddeb86ca5ac43179a825e83aa81263a6b3eb88c57bed8a937cdeacfc3192e07ec24c73acdfea4507d0c0428c8e23d6322bfe languageName: node linkType: hard -"@babel/template@npm:^7.0.0, @babel/template@npm:^7.22.5, @babel/template@npm:^7.25.9, @babel/template@npm:^7.26.9, @babel/template@npm:^7.27.0, @babel/template@npm:^7.3.3": - version: 7.27.0 - resolution: "@babel/template@npm:7.27.0" +"@babel/template@npm:^7.0.0, @babel/template@npm:^7.22.5, @babel/template@npm:^7.25.9, @babel/template@npm:^7.27.2, @babel/template@npm:^7.3.3": + version: 7.27.2 + resolution: "@babel/template@npm:7.27.2" dependencies: - "@babel/code-frame": "npm:^7.26.2" - "@babel/parser": "npm:^7.27.0" - "@babel/types": "npm:^7.27.0" - checksum: 10/7159ca1daea287ad34676d45a7146675444d42c7664aca3e617abc9b1d9548c8f377f35a36bb34cf956e1d3610dcb7acfcfe890aebf81880d35f91a7bd273ee5 + "@babel/code-frame": "npm:^7.27.1" + "@babel/parser": "npm:^7.27.2" + "@babel/types": "npm:^7.27.1" + checksum: 10/fed15a84beb0b9340e5f81566600dbee5eccd92e4b9cc42a944359b1aa1082373391d9d5fc3656981dff27233ec935d0bc96453cf507f60a4b079463999244d8 languageName: node linkType: hard @@ -1731,7 +1729,7 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.22.5, @babel/types@npm:^7.24.7, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.10, @babel/types@npm:^7.27.0, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.22.5, @babel/types@npm:^7.24.7, @babel/types@npm:^7.25.9, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.27.6, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": version: 7.27.6 resolution: "@babel/types@npm:7.27.6" dependencies: @@ -3350,6 +3348,7 @@ __metadata: jest: "npm:^29.7.0" metro-react-native-babel-preset: "npm:^0.77.0" react: "npm:^18.2.0" + react-native: "npm:^0.72.15" react-test-renderer: "npm:^18.3.1" ts-jest: "npm:^29.2.5" twrnc: "npm:^4.5.1" @@ -9567,14 +9566,14 @@ __metadata: linkType: hard "debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.6, debug@npm:^4.3.7": - version: 4.4.0 - resolution: "debug@npm:4.4.0" + version: 4.4.1 + resolution: "debug@npm:4.4.1" dependencies: ms: "npm:^2.1.3" peerDependenciesMeta: supports-color: optional: true - checksum: 10/1847944c2e3c2c732514b93d11886575625686056cd765336212dc15de2d2b29612b6cd80e1afba767bb8e1803b778caf9973e98169ef1a24a7a7009e1820367 + checksum: 10/8e2709b2144f03c7950f8804d01ccb3786373df01e406a0f66928e47001cf2d336cbed9ee137261d4f90d68d8679468c755e3548ed83ddacdc82b194d2468afe languageName: node linkType: hard @@ -18822,13 +18821,6 @@ __metadata: languageName: node linkType: hard -"regenerator-runtime@npm:^0.14.0": - version: 0.14.1 - resolution: "regenerator-runtime@npm:0.14.1" - checksum: 10/5db3161abb311eef8c45bcf6565f4f378f785900ed3945acf740a9888c792f75b98ecb77f0775f3bf95502ff423529d23e94f41d80c8256e8fa05ed4b07cf471 - languageName: node - linkType: hard - "regenerator-transform@npm:^0.15.2": version: 0.15.2 resolution: "regenerator-transform@npm:0.15.2"