@oxyhq/bloom 0.1.14 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/accordion/Accordion.js +230 -0
- package/lib/commonjs/accordion/Accordion.js.map +1 -0
- package/lib/commonjs/accordion/index.js +31 -0
- package/lib/commonjs/accordion/index.js.map +1 -0
- package/lib/commonjs/accordion/types.js +6 -0
- package/lib/commonjs/accordion/types.js.map +1 -0
- package/lib/commonjs/badge/Badge.js +173 -0
- package/lib/commonjs/badge/Badge.js.map +1 -0
- package/lib/commonjs/badge/index.js +13 -0
- package/lib/commonjs/badge/index.js.map +1 -0
- package/lib/commonjs/badge/types.js +6 -0
- package/lib/commonjs/badge/types.js.map +1 -0
- package/lib/commonjs/bottom-sheet/index.js +32 -14
- package/lib/commonjs/bottom-sheet/index.js.map +1 -1
- package/lib/commonjs/card/Card.js +165 -0
- package/lib/commonjs/card/Card.js.map +1 -0
- package/lib/commonjs/card/index.js +43 -0
- package/lib/commonjs/card/index.js.map +1 -0
- package/lib/commonjs/card/types.js +6 -0
- package/lib/commonjs/card/types.js.map +1 -0
- package/lib/commonjs/checkbox/Checkbox.js +177 -0
- package/lib/commonjs/checkbox/Checkbox.js.map +1 -0
- package/lib/commonjs/checkbox/index.js +13 -0
- package/lib/commonjs/checkbox/index.js.map +1 -0
- package/lib/commonjs/checkbox/types.js +6 -0
- package/lib/commonjs/checkbox/types.js.map +1 -0
- package/lib/commonjs/chip/Chip.js +180 -0
- package/lib/commonjs/chip/Chip.js.map +1 -0
- package/lib/commonjs/chip/index.js +13 -0
- package/lib/commonjs/chip/index.js.map +1 -0
- package/lib/commonjs/chip/types.js +6 -0
- package/lib/commonjs/chip/types.js.map +1 -0
- package/lib/commonjs/index.js +56 -2
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/tabs/Tabs.js +202 -0
- package/lib/commonjs/tabs/Tabs.js.map +1 -0
- package/lib/commonjs/tabs/index.js +25 -0
- package/lib/commonjs/tabs/index.js.map +1 -0
- package/lib/commonjs/tabs/types.js +6 -0
- package/lib/commonjs/tabs/types.js.map +1 -0
- package/lib/module/accordion/Accordion.js +225 -0
- package/lib/module/accordion/Accordion.js.map +1 -0
- package/lib/module/accordion/index.js +4 -0
- package/lib/module/accordion/index.js.map +1 -0
- package/lib/module/accordion/types.js +4 -0
- package/lib/module/accordion/types.js.map +1 -0
- package/lib/module/badge/Badge.js +168 -0
- package/lib/module/badge/Badge.js.map +1 -0
- package/lib/module/badge/index.js +4 -0
- package/lib/module/badge/index.js.map +1 -0
- package/lib/module/badge/types.js +4 -0
- package/lib/module/badge/types.js.map +1 -0
- package/lib/module/bottom-sheet/index.js +32 -14
- package/lib/module/bottom-sheet/index.js.map +1 -1
- package/lib/module/card/Card.js +160 -0
- package/lib/module/card/Card.js.map +1 -0
- package/lib/module/card/index.js +4 -0
- package/lib/module/card/index.js.map +1 -0
- package/lib/module/card/types.js +4 -0
- package/lib/module/card/types.js.map +1 -0
- package/lib/module/checkbox/Checkbox.js +172 -0
- package/lib/module/checkbox/Checkbox.js.map +1 -0
- package/lib/module/checkbox/index.js +4 -0
- package/lib/module/checkbox/index.js.map +1 -0
- package/lib/module/checkbox/types.js +4 -0
- package/lib/module/checkbox/types.js.map +1 -0
- package/lib/module/chip/Chip.js +175 -0
- package/lib/module/chip/Chip.js.map +1 -0
- package/lib/module/chip/index.js +4 -0
- package/lib/module/chip/index.js.map +1 -0
- package/lib/module/chip/types.js +4 -0
- package/lib/module/chip/types.js.map +1 -0
- package/lib/module/index.js +8 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/tabs/Tabs.js +197 -0
- package/lib/module/tabs/Tabs.js.map +1 -0
- package/lib/module/tabs/index.js +4 -0
- package/lib/module/tabs/index.js.map +1 -0
- package/lib/module/tabs/types.js +4 -0
- package/lib/module/tabs/types.js.map +1 -0
- package/lib/typescript/commonjs/__tests__/BloomThemeProvider.test.d.ts +2 -0
- package/lib/typescript/commonjs/__tests__/BloomThemeProvider.test.d.ts.map +1 -0
- package/lib/typescript/commonjs/__tests__/BottomSheet.test.d.ts +2 -0
- package/lib/typescript/commonjs/__tests__/BottomSheet.test.d.ts.map +1 -0
- package/lib/typescript/commonjs/__tests__/Button.test.d.ts +2 -0
- package/lib/typescript/commonjs/__tests__/Button.test.d.ts.map +1 -0
- package/lib/typescript/commonjs/__tests__/theme.test.d.ts +2 -0
- package/lib/typescript/commonjs/__tests__/theme.test.d.ts.map +1 -0
- package/lib/typescript/commonjs/accordion/Accordion.d.ts +7 -0
- package/lib/typescript/commonjs/accordion/Accordion.d.ts.map +1 -0
- package/lib/typescript/commonjs/accordion/index.d.ts +3 -0
- package/lib/typescript/commonjs/accordion/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/accordion/types.d.ts +38 -0
- package/lib/typescript/commonjs/accordion/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/badge/Badge.d.ts +4 -0
- package/lib/typescript/commonjs/badge/Badge.d.ts.map +1 -0
- package/lib/typescript/commonjs/badge/index.d.ts +3 -0
- package/lib/typescript/commonjs/badge/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/badge/types.d.ts +29 -0
- package/lib/typescript/commonjs/badge/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/bottom-sheet/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/card/Card.d.ts +9 -0
- package/lib/typescript/commonjs/card/Card.d.ts.map +1 -0
- package/lib/typescript/commonjs/card/index.d.ts +3 -0
- package/lib/typescript/commonjs/card/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/card/types.d.ts +34 -0
- package/lib/typescript/commonjs/card/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/checkbox/Checkbox.d.ts +4 -0
- package/lib/typescript/commonjs/checkbox/Checkbox.d.ts.map +1 -0
- package/lib/typescript/commonjs/checkbox/index.d.ts +3 -0
- package/lib/typescript/commonjs/checkbox/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/checkbox/types.d.ts +25 -0
- package/lib/typescript/commonjs/checkbox/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/chip/Chip.d.ts +4 -0
- package/lib/typescript/commonjs/chip/Chip.d.ts.map +1 -0
- package/lib/typescript/commonjs/chip/index.d.ts +3 -0
- package/lib/typescript/commonjs/chip/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/chip/types.d.ts +31 -0
- package/lib/typescript/commonjs/chip/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/index.d.ts +6 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/tabs/Tabs.d.ts +6 -0
- package/lib/typescript/commonjs/tabs/Tabs.d.ts.map +1 -0
- package/lib/typescript/commonjs/tabs/index.d.ts +3 -0
- package/lib/typescript/commonjs/tabs/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/tabs/types.d.ts +34 -0
- package/lib/typescript/commonjs/tabs/types.d.ts.map +1 -0
- package/lib/typescript/module/__tests__/BloomThemeProvider.test.d.ts +2 -0
- package/lib/typescript/module/__tests__/BloomThemeProvider.test.d.ts.map +1 -0
- package/lib/typescript/module/__tests__/BottomSheet.test.d.ts +2 -0
- package/lib/typescript/module/__tests__/BottomSheet.test.d.ts.map +1 -0
- package/lib/typescript/module/__tests__/Button.test.d.ts +2 -0
- package/lib/typescript/module/__tests__/Button.test.d.ts.map +1 -0
- package/lib/typescript/module/__tests__/theme.test.d.ts +2 -0
- package/lib/typescript/module/__tests__/theme.test.d.ts.map +1 -0
- package/lib/typescript/module/accordion/Accordion.d.ts +7 -0
- package/lib/typescript/module/accordion/Accordion.d.ts.map +1 -0
- package/lib/typescript/module/accordion/index.d.ts +3 -0
- package/lib/typescript/module/accordion/index.d.ts.map +1 -0
- package/lib/typescript/module/accordion/types.d.ts +38 -0
- package/lib/typescript/module/accordion/types.d.ts.map +1 -0
- package/lib/typescript/module/badge/Badge.d.ts +4 -0
- package/lib/typescript/module/badge/Badge.d.ts.map +1 -0
- package/lib/typescript/module/badge/index.d.ts +3 -0
- package/lib/typescript/module/badge/index.d.ts.map +1 -0
- package/lib/typescript/module/badge/types.d.ts +29 -0
- package/lib/typescript/module/badge/types.d.ts.map +1 -0
- package/lib/typescript/module/bottom-sheet/index.d.ts.map +1 -1
- package/lib/typescript/module/card/Card.d.ts +9 -0
- package/lib/typescript/module/card/Card.d.ts.map +1 -0
- package/lib/typescript/module/card/index.d.ts +3 -0
- package/lib/typescript/module/card/index.d.ts.map +1 -0
- package/lib/typescript/module/card/types.d.ts +34 -0
- package/lib/typescript/module/card/types.d.ts.map +1 -0
- package/lib/typescript/module/checkbox/Checkbox.d.ts +4 -0
- package/lib/typescript/module/checkbox/Checkbox.d.ts.map +1 -0
- package/lib/typescript/module/checkbox/index.d.ts +3 -0
- package/lib/typescript/module/checkbox/index.d.ts.map +1 -0
- package/lib/typescript/module/checkbox/types.d.ts +25 -0
- package/lib/typescript/module/checkbox/types.d.ts.map +1 -0
- package/lib/typescript/module/chip/Chip.d.ts +4 -0
- package/lib/typescript/module/chip/Chip.d.ts.map +1 -0
- package/lib/typescript/module/chip/index.d.ts +3 -0
- package/lib/typescript/module/chip/index.d.ts.map +1 -0
- package/lib/typescript/module/chip/types.d.ts +31 -0
- package/lib/typescript/module/chip/types.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts +6 -0
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/tabs/Tabs.d.ts +6 -0
- package/lib/typescript/module/tabs/Tabs.d.ts.map +1 -0
- package/lib/typescript/module/tabs/index.d.ts +3 -0
- package/lib/typescript/module/tabs/index.d.ts.map +1 -0
- package/lib/typescript/module/tabs/types.d.ts +34 -0
- package/lib/typescript/module/tabs/types.d.ts.map +1 -0
- package/package.json +79 -1
- package/src/__tests__/BloomThemeProvider.test.tsx +160 -0
- package/src/__tests__/BottomSheet.test.tsx +109 -0
- package/src/__tests__/Button.test.tsx +98 -0
- package/src/__tests__/theme.test.ts +148 -0
- package/src/accordion/Accordion.tsx +261 -0
- package/src/accordion/index.ts +8 -0
- package/src/accordion/types.ts +42 -0
- package/src/badge/Badge.tsx +151 -0
- package/src/badge/index.ts +8 -0
- package/src/badge/types.ts +30 -0
- package/src/bottom-sheet/index.tsx +30 -11
- package/src/card/Card.tsx +197 -0
- package/src/card/index.ts +10 -0
- package/src/card/types.ts +40 -0
- package/src/checkbox/Checkbox.tsx +166 -0
- package/src/checkbox/index.ts +2 -0
- package/src/checkbox/types.ts +26 -0
- package/src/chip/Chip.tsx +156 -0
- package/src/chip/index.ts +2 -0
- package/src/chip/types.ts +32 -0
- package/src/index.ts +8 -0
- package/src/tabs/Tabs.tsx +218 -0
- package/src/tabs/index.ts +2 -0
- package/src/tabs/types.ts +37 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Text } from 'react-native';
|
|
3
|
+
import { render } from '@testing-library/react-native';
|
|
4
|
+
|
|
5
|
+
import { BloomThemeProvider } from '../theme/BloomThemeProvider';
|
|
6
|
+
import { useTheme, useThemeColor, useBloomTheme } from '../theme/use-theme';
|
|
7
|
+
import type { AppColorName } from '../theme/color-presets';
|
|
8
|
+
|
|
9
|
+
function ThemeDisplay() {
|
|
10
|
+
const theme = useTheme();
|
|
11
|
+
return (
|
|
12
|
+
<>
|
|
13
|
+
<Text testID="mode">{theme.mode}</Text>
|
|
14
|
+
<Text testID="isDark">{String(theme.isDark)}</Text>
|
|
15
|
+
<Text testID="isLight">{String(theme.isLight)}</Text>
|
|
16
|
+
<Text testID="primary">{theme.colors.primary}</Text>
|
|
17
|
+
<Text testID="background">{theme.colors.background}</Text>
|
|
18
|
+
</>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function ColorDisplay({ colorKey }: { colorKey: 'primary' | 'error' | 'background' }) {
|
|
23
|
+
const color = useThemeColor(colorKey);
|
|
24
|
+
return <Text testID="color">{color}</Text>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function BloomThemeDisplay() {
|
|
28
|
+
const ctx = useBloomTheme();
|
|
29
|
+
return (
|
|
30
|
+
<>
|
|
31
|
+
<Text testID="ctx-mode">{ctx.mode}</Text>
|
|
32
|
+
<Text testID="ctx-preset">{ctx.colorPreset}</Text>
|
|
33
|
+
</>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
describe('BloomThemeProvider', () => {
|
|
38
|
+
it('provides a light theme by default', () => {
|
|
39
|
+
const { getByTestId } = render(
|
|
40
|
+
<BloomThemeProvider>
|
|
41
|
+
<ThemeDisplay />
|
|
42
|
+
</BloomThemeProvider>,
|
|
43
|
+
);
|
|
44
|
+
expect(getByTestId('mode').props.children).toBe('light');
|
|
45
|
+
expect(getByTestId('isDark').props.children).toBe('false');
|
|
46
|
+
expect(getByTestId('isLight').props.children).toBe('true');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('provides a dark theme when mode is dark', () => {
|
|
50
|
+
const { getByTestId } = render(
|
|
51
|
+
<BloomThemeProvider mode="dark">
|
|
52
|
+
<ThemeDisplay />
|
|
53
|
+
</BloomThemeProvider>,
|
|
54
|
+
);
|
|
55
|
+
expect(getByTestId('mode').props.children).toBe('dark');
|
|
56
|
+
expect(getByTestId('isDark').props.children).toBe('true');
|
|
57
|
+
expect(getByTestId('isLight').props.children).toBe('false');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('uses teal as default color preset', () => {
|
|
61
|
+
const { getByTestId } = render(
|
|
62
|
+
<BloomThemeProvider>
|
|
63
|
+
<BloomThemeDisplay />
|
|
64
|
+
</BloomThemeProvider>,
|
|
65
|
+
);
|
|
66
|
+
expect(getByTestId('ctx-preset').props.children).toBe('teal');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('applies the specified color preset', () => {
|
|
70
|
+
const { getByTestId } = render(
|
|
71
|
+
<BloomThemeProvider colorPreset="blue">
|
|
72
|
+
<ThemeDisplay />
|
|
73
|
+
</BloomThemeProvider>,
|
|
74
|
+
);
|
|
75
|
+
// Blue preset hex is #1d9bf0 (case may vary)
|
|
76
|
+
expect(getByTestId('primary').props.children.toLowerCase()).toBe('#1d9bf0');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('generates different background colors for light and dark modes', () => {
|
|
80
|
+
const { getByTestId: getLight } = render(
|
|
81
|
+
<BloomThemeProvider mode="light" colorPreset="teal">
|
|
82
|
+
<ThemeDisplay />
|
|
83
|
+
</BloomThemeProvider>,
|
|
84
|
+
);
|
|
85
|
+
const { getByTestId: getDark } = render(
|
|
86
|
+
<BloomThemeProvider mode="dark" colorPreset="teal">
|
|
87
|
+
<ThemeDisplay />
|
|
88
|
+
</BloomThemeProvider>,
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const lightBg = getLight('background').props.children;
|
|
92
|
+
const darkBg = getDark('background').props.children;
|
|
93
|
+
expect(lightBg).not.toBe(darkBg);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('renders all color presets without crashing', () => {
|
|
97
|
+
const presets: AppColorName[] = [
|
|
98
|
+
'teal', 'blue', 'green', 'amber', 'red',
|
|
99
|
+
'purple', 'pink', 'sky', 'orange', 'mint',
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
for (const preset of presets) {
|
|
103
|
+
const { unmount } = render(
|
|
104
|
+
<BloomThemeProvider colorPreset={preset} mode="light">
|
|
105
|
+
<ThemeDisplay />
|
|
106
|
+
</BloomThemeProvider>,
|
|
107
|
+
);
|
|
108
|
+
unmount();
|
|
109
|
+
|
|
110
|
+
const { unmount: unmountDark } = render(
|
|
111
|
+
<BloomThemeProvider colorPreset={preset} mode="dark">
|
|
112
|
+
<ThemeDisplay />
|
|
113
|
+
</BloomThemeProvider>,
|
|
114
|
+
);
|
|
115
|
+
unmountDark();
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('useTheme', () => {
|
|
121
|
+
it('throws when used outside BloomThemeProvider', () => {
|
|
122
|
+
// Suppress error boundary logging
|
|
123
|
+
const spy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
124
|
+
expect(() => render(<ThemeDisplay />)).toThrow(
|
|
125
|
+
'useTheme must be used within a <BloomThemeProvider>',
|
|
126
|
+
);
|
|
127
|
+
spy.mockRestore();
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('useThemeColor', () => {
|
|
132
|
+
it('returns the correct color value', () => {
|
|
133
|
+
const { getByTestId } = render(
|
|
134
|
+
<BloomThemeProvider colorPreset="teal">
|
|
135
|
+
<ColorDisplay colorKey="primary" />
|
|
136
|
+
</BloomThemeProvider>,
|
|
137
|
+
);
|
|
138
|
+
expect(getByTestId('color').props.children).toBe('#005c67');
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe('useBloomTheme', () => {
|
|
143
|
+
it('exposes mode and color preset', () => {
|
|
144
|
+
const { getByTestId } = render(
|
|
145
|
+
<BloomThemeProvider mode="dark" colorPreset="purple">
|
|
146
|
+
<BloomThemeDisplay />
|
|
147
|
+
</BloomThemeProvider>,
|
|
148
|
+
);
|
|
149
|
+
expect(getByTestId('ctx-mode').props.children).toBe('dark');
|
|
150
|
+
expect(getByTestId('ctx-preset').props.children).toBe('purple');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('throws when used outside BloomThemeProvider', () => {
|
|
154
|
+
const spy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
155
|
+
expect(() => render(<BloomThemeDisplay />)).toThrow(
|
|
156
|
+
'useBloomTheme must be used within a <BloomThemeProvider>',
|
|
157
|
+
);
|
|
158
|
+
spy.mockRestore();
|
|
159
|
+
});
|
|
160
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import React, { createRef } from 'react';
|
|
2
|
+
import { Dimensions } from 'react-native';
|
|
3
|
+
import { render } from '@testing-library/react-native';
|
|
4
|
+
|
|
5
|
+
import { BloomThemeProvider } from '../theme/BloomThemeProvider';
|
|
6
|
+
import BottomSheet, { type BottomSheetRef } from '../bottom-sheet';
|
|
7
|
+
|
|
8
|
+
function renderWithTheme(ui: React.ReactElement) {
|
|
9
|
+
return render(
|
|
10
|
+
<BloomThemeProvider mode="light" colorPreset="teal">
|
|
11
|
+
{ui}
|
|
12
|
+
</BloomThemeProvider>,
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function renderWithDarkTheme(ui: React.ReactElement) {
|
|
17
|
+
return render(
|
|
18
|
+
<BloomThemeProvider mode="dark" colorPreset="teal">
|
|
19
|
+
{ui}
|
|
20
|
+
</BloomThemeProvider>,
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe('BottomSheet', () => {
|
|
25
|
+
it('does not render content when not presented', () => {
|
|
26
|
+
const ref = createRef<BottomSheetRef>();
|
|
27
|
+
const { queryByText } = renderWithTheme(
|
|
28
|
+
<BottomSheet ref={ref}>
|
|
29
|
+
<React.Fragment>Sheet Content</React.Fragment>
|
|
30
|
+
</BottomSheet>,
|
|
31
|
+
);
|
|
32
|
+
// Not rendered until present() is called
|
|
33
|
+
expect(queryByText('Sheet Content')).toBeNull();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('exposes present/dismiss/close/expand/collapse/scrollTo on ref', () => {
|
|
37
|
+
const ref = createRef<BottomSheetRef>();
|
|
38
|
+
renderWithTheme(
|
|
39
|
+
<BottomSheet ref={ref}>
|
|
40
|
+
<React.Fragment>Content</React.Fragment>
|
|
41
|
+
</BottomSheet>,
|
|
42
|
+
);
|
|
43
|
+
expect(ref.current).not.toBeNull();
|
|
44
|
+
expect(typeof ref.current!.present).toBe('function');
|
|
45
|
+
expect(typeof ref.current!.dismiss).toBe('function');
|
|
46
|
+
expect(typeof ref.current!.close).toBe('function');
|
|
47
|
+
expect(typeof ref.current!.expand).toBe('function');
|
|
48
|
+
expect(typeof ref.current!.collapse).toBe('function');
|
|
49
|
+
expect(typeof ref.current!.scrollTo).toBe('function');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('rotation bug fix', () => {
|
|
53
|
+
it('uses Dimensions.addEventListener for screen dimension updates', () => {
|
|
54
|
+
const addEventSpy = jest.spyOn(Dimensions, 'addEventListener');
|
|
55
|
+
|
|
56
|
+
const ref = createRef<BottomSheetRef>();
|
|
57
|
+
renderWithTheme(
|
|
58
|
+
<BottomSheet ref={ref}>
|
|
59
|
+
<React.Fragment>Content</React.Fragment>
|
|
60
|
+
</BottomSheet>,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// The useScreenDimensions hook should have registered a listener
|
|
64
|
+
expect(addEventSpy).toHaveBeenCalledWith('change', expect.any(Function));
|
|
65
|
+
|
|
66
|
+
addEventSpy.mockRestore();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('does not use module-level cached screen dimensions', () => {
|
|
70
|
+
// This test verifies the fix: the component should NOT reference
|
|
71
|
+
// a module-level SCREEN_HEIGHT constant. Instead it uses
|
|
72
|
+
// useScreenDimensions() which subscribes to Dimensions changes.
|
|
73
|
+
|
|
74
|
+
// We verify this indirectly by checking that the component renders
|
|
75
|
+
// successfully even after a simulated dimension change.
|
|
76
|
+
const ref = createRef<BottomSheetRef>();
|
|
77
|
+
const { unmount } = renderWithTheme(
|
|
78
|
+
<BottomSheet ref={ref}>
|
|
79
|
+
<React.Fragment>Content</React.Fragment>
|
|
80
|
+
</BottomSheet>,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// Unmounting should clean up the Dimensions listener without error
|
|
84
|
+
unmount();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('dark mode fix', () => {
|
|
89
|
+
it('renders in light mode without errors', () => {
|
|
90
|
+
const ref = createRef<BottomSheetRef>();
|
|
91
|
+
const { unmount } = renderWithTheme(
|
|
92
|
+
<BottomSheet ref={ref}>
|
|
93
|
+
<React.Fragment>Light content</React.Fragment>
|
|
94
|
+
</BottomSheet>,
|
|
95
|
+
);
|
|
96
|
+
unmount();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('renders in dark mode without errors', () => {
|
|
100
|
+
const ref = createRef<BottomSheetRef>();
|
|
101
|
+
const { unmount } = renderWithDarkTheme(
|
|
102
|
+
<BottomSheet ref={ref}>
|
|
103
|
+
<React.Fragment>Dark content</React.Fragment>
|
|
104
|
+
</BottomSheet>,
|
|
105
|
+
);
|
|
106
|
+
unmount();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, fireEvent } from '@testing-library/react-native';
|
|
3
|
+
|
|
4
|
+
import { BloomThemeProvider } from '../theme/BloomThemeProvider';
|
|
5
|
+
import { Button, PrimaryButton, SecondaryButton, IconButton, GhostButton, TextButton } from '../button';
|
|
6
|
+
|
|
7
|
+
function renderWithTheme(ui: React.ReactElement) {
|
|
8
|
+
return render(
|
|
9
|
+
<BloomThemeProvider mode="light" colorPreset="teal">
|
|
10
|
+
{ui}
|
|
11
|
+
</BloomThemeProvider>,
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('Button', () => {
|
|
16
|
+
it('renders children as text', () => {
|
|
17
|
+
const { getByText } = renderWithTheme(<Button>Click me</Button>);
|
|
18
|
+
expect(getByText('Click me')).toBeTruthy();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('calls onPress when pressed', () => {
|
|
22
|
+
const onPress = jest.fn();
|
|
23
|
+
const { getByText } = renderWithTheme(
|
|
24
|
+
<Button onPress={onPress}>Press</Button>,
|
|
25
|
+
);
|
|
26
|
+
fireEvent.press(getByText('Press'));
|
|
27
|
+
expect(onPress).toHaveBeenCalledTimes(1);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('supports testID prop', () => {
|
|
31
|
+
const { getByTestId } = renderWithTheme(
|
|
32
|
+
<Button testID="my-button">Test</Button>,
|
|
33
|
+
);
|
|
34
|
+
expect(getByTestId('my-button')).toBeTruthy();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('has accessibilityRole button set on the Pressable', () => {
|
|
38
|
+
const { getByTestId } = renderWithTheme(
|
|
39
|
+
<Button testID="a11y-btn">A11y</Button>,
|
|
40
|
+
);
|
|
41
|
+
const btn = getByTestId('a11y-btn');
|
|
42
|
+
expect(btn.props.accessibilityRole).toBe('button');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('applies accessibilityLabel', () => {
|
|
46
|
+
const { getByLabelText } = renderWithTheme(
|
|
47
|
+
<Button accessibilityLabel="Save changes">Save</Button>,
|
|
48
|
+
);
|
|
49
|
+
expect(getByLabelText('Save changes')).toBeTruthy();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('sets disabled accessibility state', () => {
|
|
53
|
+
const { getByTestId } = renderWithTheme(
|
|
54
|
+
<Button testID="dis-btn" disabled>
|
|
55
|
+
Disabled
|
|
56
|
+
</Button>,
|
|
57
|
+
);
|
|
58
|
+
const btn = getByTestId('dis-btn');
|
|
59
|
+
expect(btn.props.accessibilityState).toEqual({ disabled: true });
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('Button variants', () => {
|
|
64
|
+
it('PrimaryButton renders without crashing', () => {
|
|
65
|
+
const { getByText } = renderWithTheme(
|
|
66
|
+
<PrimaryButton>Primary</PrimaryButton>,
|
|
67
|
+
);
|
|
68
|
+
expect(getByText('Primary')).toBeTruthy();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('SecondaryButton renders without crashing', () => {
|
|
72
|
+
const { getByText } = renderWithTheme(
|
|
73
|
+
<SecondaryButton>Secondary</SecondaryButton>,
|
|
74
|
+
);
|
|
75
|
+
expect(getByText('Secondary')).toBeTruthy();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('IconButton renders without crashing', () => {
|
|
79
|
+
const { getByTestId } = renderWithTheme(
|
|
80
|
+
<IconButton testID="icon-btn" />,
|
|
81
|
+
);
|
|
82
|
+
expect(getByTestId('icon-btn')).toBeTruthy();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('GhostButton renders without crashing', () => {
|
|
86
|
+
const { getByText } = renderWithTheme(
|
|
87
|
+
<GhostButton>Ghost</GhostButton>,
|
|
88
|
+
);
|
|
89
|
+
expect(getByText('Ghost')).toBeTruthy();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('TextButton renders without crashing', () => {
|
|
93
|
+
const { getByText } = renderWithTheme(
|
|
94
|
+
<TextButton>Text</TextButton>,
|
|
95
|
+
);
|
|
96
|
+
expect(getByText('Text')).toBeTruthy();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import {
|
|
2
|
+
APP_COLOR_PRESETS,
|
|
3
|
+
APP_COLOR_NAMES,
|
|
4
|
+
HEX_TO_APP_COLOR,
|
|
5
|
+
hexToAppColorName,
|
|
6
|
+
} from '../theme/color-presets';
|
|
7
|
+
import type { Theme, ThemeColors, ThemeMode } from '../theme/types';
|
|
8
|
+
|
|
9
|
+
describe('Theme system', () => {
|
|
10
|
+
describe('color presets', () => {
|
|
11
|
+
it('has all named presets defined', () => {
|
|
12
|
+
for (const name of APP_COLOR_NAMES) {
|
|
13
|
+
expect(APP_COLOR_PRESETS[name]).toBeDefined();
|
|
14
|
+
expect(APP_COLOR_PRESETS[name]!.name).toBe(name);
|
|
15
|
+
expect(APP_COLOR_PRESETS[name]!.hex).toMatch(/^#[0-9a-f]{6}$/i);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('each preset has both light and dark color variables', () => {
|
|
20
|
+
const requiredVars = [
|
|
21
|
+
'--background',
|
|
22
|
+
'--foreground',
|
|
23
|
+
'--surface',
|
|
24
|
+
'--primary',
|
|
25
|
+
'--border',
|
|
26
|
+
'--muted',
|
|
27
|
+
'--muted-foreground',
|
|
28
|
+
'--destructive',
|
|
29
|
+
'--input',
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
for (const name of APP_COLOR_NAMES) {
|
|
33
|
+
const preset = APP_COLOR_PRESETS[name]!;
|
|
34
|
+
for (const v of requiredVars) {
|
|
35
|
+
expect(preset.light[v]).toBeDefined();
|
|
36
|
+
expect(preset.dark[v]).toBeDefined();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('light and dark variants have different values', () => {
|
|
42
|
+
for (const name of APP_COLOR_NAMES) {
|
|
43
|
+
const preset = APP_COLOR_PRESETS[name]!;
|
|
44
|
+
// Background should differ between light and dark
|
|
45
|
+
expect(preset.light['--background']).not.toBe(preset.dark['--background']);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('hexToAppColorName', () => {
|
|
51
|
+
it('maps known hex values to correct color names', () => {
|
|
52
|
+
expect(hexToAppColorName('#005c67')).toBe('teal');
|
|
53
|
+
expect(hexToAppColorName('#1d9bf0')).toBe('blue');
|
|
54
|
+
expect(hexToAppColorName('#10b981')).toBe('green');
|
|
55
|
+
expect(hexToAppColorName('#ef4444')).toBe('red');
|
|
56
|
+
expect(hexToAppColorName('#c46ede')).toBe('oxy');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('is case-insensitive', () => {
|
|
60
|
+
expect(hexToAppColorName('#005C67')).toBe('teal');
|
|
61
|
+
expect(hexToAppColorName('#1D9BF0')).toBe('blue');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('defaults to teal for unknown hex values', () => {
|
|
65
|
+
expect(hexToAppColorName('#000000')).toBe('teal');
|
|
66
|
+
expect(hexToAppColorName('#ffffff')).toBe('teal');
|
|
67
|
+
expect(hexToAppColorName('#123456')).toBe('teal');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('HEX_TO_APP_COLOR mapping', () => {
|
|
72
|
+
it('has a hex entry for every named preset', () => {
|
|
73
|
+
const mappedNames = new Set(Object.values(HEX_TO_APP_COLOR));
|
|
74
|
+
for (const name of APP_COLOR_NAMES) {
|
|
75
|
+
expect(mappedNames.has(name)).toBe(true);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('hex keys match the preset hex values', () => {
|
|
80
|
+
for (const [hex, name] of Object.entries(HEX_TO_APP_COLOR)) {
|
|
81
|
+
const preset = APP_COLOR_PRESETS[name];
|
|
82
|
+
expect(preset).toBeDefined();
|
|
83
|
+
expect(preset!.hex.toLowerCase()).toBe(hex.toLowerCase());
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('Theme type shape', () => {
|
|
89
|
+
it('supports light and dark modes with isDark/isLight flags', () => {
|
|
90
|
+
const lightTheme: Theme = {
|
|
91
|
+
mode: 'light',
|
|
92
|
+
isDark: false,
|
|
93
|
+
isLight: true,
|
|
94
|
+
colors: createMockColors(),
|
|
95
|
+
};
|
|
96
|
+
expect(lightTheme.isDark).toBe(false);
|
|
97
|
+
expect(lightTheme.isLight).toBe(true);
|
|
98
|
+
|
|
99
|
+
const darkTheme: Theme = {
|
|
100
|
+
mode: 'dark',
|
|
101
|
+
isDark: true,
|
|
102
|
+
isLight: false,
|
|
103
|
+
colors: createMockColors(),
|
|
104
|
+
};
|
|
105
|
+
expect(darkTheme.isDark).toBe(true);
|
|
106
|
+
expect(darkTheme.isLight).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('ThemeMode includes all expected variants', () => {
|
|
110
|
+
const modes: ThemeMode[] = ['light', 'dark', 'system', 'adaptive'];
|
|
111
|
+
expect(modes).toHaveLength(4);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
function createMockColors(): ThemeColors {
|
|
117
|
+
return {
|
|
118
|
+
background: '#fff',
|
|
119
|
+
backgroundSecondary: '#f5f5f5',
|
|
120
|
+
backgroundTertiary: '#eee',
|
|
121
|
+
text: '#000',
|
|
122
|
+
textSecondary: '#666',
|
|
123
|
+
textTertiary: '#999',
|
|
124
|
+
border: '#ccc',
|
|
125
|
+
borderLight: '#ddd',
|
|
126
|
+
primary: '#005c67',
|
|
127
|
+
primaryLight: '#e0f7fa',
|
|
128
|
+
primaryDark: '#003d44',
|
|
129
|
+
secondary: '#005c67',
|
|
130
|
+
tint: '#005c67',
|
|
131
|
+
icon: '#666',
|
|
132
|
+
iconActive: '#005c67',
|
|
133
|
+
success: '#10B981',
|
|
134
|
+
error: '#EF4444',
|
|
135
|
+
warning: '#F59E0B',
|
|
136
|
+
info: '#3B82F6',
|
|
137
|
+
primarySubtle: '#e0f7fa',
|
|
138
|
+
primarySubtleForeground: '#003d44',
|
|
139
|
+
negative: '#B91C1C',
|
|
140
|
+
negativeForeground: '#FFFFFF',
|
|
141
|
+
negativeSubtle: '#fef2f2',
|
|
142
|
+
negativeSubtleForeground: '#B91C1C',
|
|
143
|
+
contrast50: '#f5f5f5',
|
|
144
|
+
card: '#f5f5f5',
|
|
145
|
+
shadow: 'rgba(0,0,0,0.1)',
|
|
146
|
+
overlay: 'rgba(0,0,0,0.5)',
|
|
147
|
+
};
|
|
148
|
+
}
|