133 lines
4.5 KiB
TypeScript
133 lines
4.5 KiB
TypeScript
import React from 'react'
|
|
import { readFileSync } from 'node:fs'
|
|
import { resolve } from 'node:path'
|
|
import ReactTestRenderer, { act } from 'react-test-renderer'
|
|
import { AccessibilityInfo, Animated, StyleSheet, View } from 'react-native'
|
|
import {
|
|
MetalCard,
|
|
NativeThemeProvider,
|
|
PhosphorText,
|
|
PhysicalButton,
|
|
WaveBars,
|
|
d3roNativePalette,
|
|
} from '@d3ro/ui-native'
|
|
import { getMobileThemePalette } from '../src/theme/mobile-theme'
|
|
|
|
const THEME_PARITY_SCREENS = [
|
|
'AccountScreen.tsx',
|
|
'LoginScreen.tsx',
|
|
'SignUpScreen.tsx',
|
|
'ForgotPasswordScreen.tsx',
|
|
'UpdatePasswordScreen.tsx',
|
|
'HistoryScreen.tsx',
|
|
'HistoryDetailScreen.tsx',
|
|
'RecordScreen.tsx',
|
|
'ProPaywallScreen.tsx',
|
|
] as const
|
|
|
|
function hostByTestId(
|
|
root: ReactTestRenderer.ReactTestInstance,
|
|
testID: string,
|
|
): ReactTestRenderer.ReactTestInstance {
|
|
const matches = root.findAllByProps({ testID })
|
|
const host = matches[matches.length - 1]
|
|
if (host === undefined) throw new Error(`Missing host node: ${testID}`)
|
|
return host
|
|
}
|
|
|
|
function ThemeFixture({ mode }: { mode: 'light' | 'dark' }): React.ReactElement {
|
|
const palette = getMobileThemePalette(mode)
|
|
return (
|
|
<NativeThemeProvider palette={palette}>
|
|
<MetalCard testID="theme-card">
|
|
<PhosphorText testID="theme-text" color="primary">
|
|
Theme probe
|
|
</PhosphorText>
|
|
<PhysicalButton testID="theme-button" label="Continue" />
|
|
</MetalCard>
|
|
</NativeThemeProvider>
|
|
)
|
|
}
|
|
|
|
describe('ui-native runtime theme boundary', () => {
|
|
test.each(THEME_PARITY_SCREENS)(
|
|
'%s resolves colors from mobile preferences at render time',
|
|
(screenName) => {
|
|
const source = readFileSync(
|
|
resolve(__dirname, '..', 'src', 'screens', screenName),
|
|
'utf8',
|
|
)
|
|
expect(source).toContain('useMobilePreferences')
|
|
expect(source).not.toContain('d3roNativePalette')
|
|
expect(source).not.toMatch(/const\s+styles\s*=\s*StyleSheet\.create/)
|
|
},
|
|
)
|
|
|
|
test('updates shared component colors immediately when the provider palette changes', () => {
|
|
let renderer: ReactTestRenderer.ReactTestRenderer
|
|
act(() => {
|
|
renderer = ReactTestRenderer.create(<ThemeFixture mode="dark" />)
|
|
})
|
|
|
|
const dark = getMobileThemePalette('dark')
|
|
const darkText = hostByTestId(renderer!.root, 'theme-text')
|
|
const darkCard = hostByTestId(renderer!.root, 'theme-card')
|
|
const darkButton = hostByTestId(renderer!.root, 'theme-button')
|
|
expect(StyleSheet.flatten(darkText.props.style).color).toBe(dark.text.primary)
|
|
expect(StyleSheet.flatten(darkCard.props.style).backgroundColor).toBe(dark.bg.card)
|
|
expect(StyleSheet.flatten(darkButton.props.style).backgroundColor).toBe(dark.accent.main)
|
|
|
|
act(() => {
|
|
renderer!.update(<ThemeFixture mode="light" />)
|
|
})
|
|
|
|
const light = getMobileThemePalette('light')
|
|
const lightText = hostByTestId(renderer!.root, 'theme-text')
|
|
const lightCard = hostByTestId(renderer!.root, 'theme-card')
|
|
const lightButton = hostByTestId(renderer!.root, 'theme-button')
|
|
expect(StyleSheet.flatten(lightText.props.style).color).toBe(light.text.primary)
|
|
expect(StyleSheet.flatten(lightCard.props.style).backgroundColor).toBe(light.bg.card)
|
|
expect(StyleSheet.flatten(lightButton.props.style).backgroundColor).toBe(light.accent.main)
|
|
|
|
act(() => renderer!.unmount())
|
|
})
|
|
|
|
test('keeps the legacy fallback usable outside a provider', () => {
|
|
let renderer: ReactTestRenderer.ReactTestRenderer
|
|
act(() => {
|
|
renderer = ReactTestRenderer.create(
|
|
<View>
|
|
<PhosphorText testID="fallback-text">Fallback</PhosphorText>
|
|
</View>,
|
|
)
|
|
})
|
|
const fallback = hostByTestId(renderer!.root, 'fallback-text')
|
|
expect(fallback.props.children).toBe('Fallback')
|
|
expect(StyleSheet.flatten(fallback.props.style).color).toBe(
|
|
d3roNativePalette.text.primary,
|
|
)
|
|
act(() => renderer!.unmount())
|
|
})
|
|
|
|
test('keeps waveform motion stopped when the OS requests reduced motion', async () => {
|
|
const reduceMotion = jest
|
|
.spyOn(AccessibilityInfo, 'isReduceMotionEnabled')
|
|
.mockResolvedValue(true)
|
|
const loop = jest.spyOn(Animated, 'loop')
|
|
let renderer: ReactTestRenderer.ReactTestRenderer
|
|
|
|
await act(async () => {
|
|
renderer = ReactTestRenderer.create(
|
|
<NativeThemeProvider palette={getMobileThemePalette('dark')}>
|
|
<WaveBars active barCount={2} />
|
|
</NativeThemeProvider>,
|
|
)
|
|
await Promise.resolve()
|
|
})
|
|
|
|
expect(loop).not.toHaveBeenCalled()
|
|
act(() => renderer!.unmount())
|
|
loop.mockRestore()
|
|
reduceMotion.mockRestore()
|
|
})
|
|
})
|