@planningcenter/chat-react-native 4.4.1-qa-staging.38 → 4.4.1-qa-staging.40

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.
Files changed (28) hide show
  1. package/build/components/display/filter_dropdown.d.ts +1 -0
  2. package/build/components/display/filter_dropdown.d.ts.map +1 -1
  3. package/build/components/display/filter_dropdown.js.map +1 -1
  4. package/build/navigation/index.d.ts +2 -9
  5. package/build/navigation/index.d.ts.map +1 -1
  6. package/build/navigation/index.js +3 -11
  7. package/build/navigation/index.js.map +1 -1
  8. package/build/navigation/screenLayout.d.ts.map +1 -1
  9. package/build/navigation/screenLayout.js +45 -10
  10. package/build/navigation/screenLayout.js.map +1 -1
  11. package/build/screens/conversations/components/filter_dropdown_list_header_component.d.ts.map +1 -1
  12. package/build/screens/conversations/components/filter_dropdown_list_header_component.js +22 -7
  13. package/build/screens/conversations/components/filter_dropdown_list_header_component.js.map +1 -1
  14. package/build/screens/settings_screen.d.ts.map +1 -1
  15. package/build/screens/settings_screen.js +73 -96
  16. package/build/screens/settings_screen.js.map +1 -1
  17. package/package.json +3 -3
  18. package/src/components/display/filter_dropdown.tsx +1 -0
  19. package/src/navigation/index.tsx +1 -6
  20. package/src/navigation/screenLayout.tsx +18 -1
  21. package/src/screens/conversations/components/__tests__/filter_dropdown_list_header_component.test.tsx +59 -1
  22. package/src/screens/conversations/components/filter_dropdown_list_header_component.tsx +24 -9
  23. package/src/screens/settings_screen.tsx +0 -5
  24. package/build/screens/flag_test_screen.d.ts +0 -2
  25. package/build/screens/flag_test_screen.d.ts.map +0 -1
  26. package/build/screens/flag_test_screen.js +0 -274
  27. package/build/screens/flag_test_screen.js.map +0 -1
  28. package/src/screens/flag_test_screen.tsx +0 -133
@@ -1,6 +1,6 @@
1
1
  import { fireEvent, render, screen } from '@testing-library/react-native'
2
2
  import React from 'react'
3
- import { Pressable, Text } from 'react-native'
3
+ import { Pressable, StyleSheet, Text } from 'react-native'
4
4
  import {
5
5
  AudioAdapter,
6
6
  ChatAdapters,
@@ -115,6 +115,25 @@ const setRouteParams = (params: Record<string, unknown>) => {
115
115
  const menuItemNames = () =>
116
116
  screen.getAllByRole('menuitem').map(item => item.props.accessibilityLabel)
117
117
 
118
+ const ROW_WIDTH = 360
119
+ const ROW_CONTENT_WIDTH = ROW_WIDTH - 16 * 2
120
+ const HEADING_ROW_GAP = 12
121
+ const WIDEST_LABEL_WIDTH = 150
122
+
123
+ const layOut = (testID: string, width: number) =>
124
+ fireEvent(screen.getByTestId(testID, { includeHiddenElements: true }), 'layout', {
125
+ nativeEvent: { layout: { width } },
126
+ })
127
+
128
+ const layOutHeader = ({ actionsWidth }: { actionsWidth: number }) => {
129
+ layOut('conversations-filter-heading-row', ROW_WIDTH)
130
+ layOut('conversations-filter-widest-label', WIDEST_LABEL_WIDTH)
131
+ layOut('conversations-filter-heading-actions', actionsWidth)
132
+ }
133
+
134
+ const triggerMaxWidth = (): number =>
135
+ StyleSheet.flatten(screen.getByRole('button', { name: 'All conversations' }).props.style).maxWidth
136
+
118
137
  describe('FilterDropdownListHeaderComponent', () => {
119
138
  beforeEach(() => {
120
139
  mockNavigate.mockClear()
@@ -286,6 +305,45 @@ describe('FilterDropdownListHeaderComponent', () => {
286
305
  )
287
306
  })
288
307
 
308
+ it('caps the trigger short of the room beside the actions so a full label never wraps them', () => {
309
+ render(<FilterDropdownListHeaderComponent />)
310
+
311
+ layOutHeader({ actionsWidth: 120.4 })
312
+
313
+ expect(triggerMaxWidth()).toBeGreaterThanOrEqual(WIDEST_LABEL_WIDTH)
314
+ expect(triggerMaxWidth() + HEADING_ROW_GAP + 120.4).toBeLessThan(ROW_CONTENT_WIDTH)
315
+ })
316
+
317
+ it('holds the cap when the actions remeasure by less than a point', () => {
318
+ render(<FilterDropdownListHeaderComponent />)
319
+
320
+ layOutHeader({ actionsWidth: 120.4 })
321
+ const capBefore = triggerMaxWidth()
322
+
323
+ layOut('conversations-filter-heading-actions', 120.9)
324
+
325
+ expect(triggerMaxWidth()).toBe(capBefore)
326
+ })
327
+
328
+ it('recomputes the cap when the actions grow by a button', () => {
329
+ render(<FilterDropdownListHeaderComponent />)
330
+
331
+ layOutHeader({ actionsWidth: 120.4 })
332
+ const capBefore = triggerMaxWidth()
333
+
334
+ layOut('conversations-filter-heading-actions', 160)
335
+
336
+ expect(triggerMaxWidth()).toBeLessThan(capBefore)
337
+ })
338
+
339
+ it('caps the trigger to the row once All conversations no longer fits beside the actions', () => {
340
+ render(<FilterDropdownListHeaderComponent />)
341
+
342
+ layOutHeader({ actionsWidth: 200 })
343
+
344
+ expect(triggerMaxWidth()).toBe(ROW_CONTENT_WIDTH)
345
+ })
346
+
289
347
  it('shows the search button only when search is enabled', () => {
290
348
  mockSearchEnabled.mockReturnValue(false)
291
349
  const view = render(<FilterDropdownListHeaderComponent />)
@@ -30,6 +30,8 @@ import { ListHeaderActionButton } from './list_header_action_button'
30
30
 
31
31
  const ROW_PADDING_HORIZONTAL = 16
32
32
  const HEADING_ROW_GAP = 12
33
+ const TRIGGER_CAP_SLACK = 2
34
+ const LAYOUT_JITTER = 1
33
35
  const ALL_CONVERSATIONS_LABEL = 'All conversations'
34
36
  const ARCHIVED_LABEL = 'Archived'
35
37
 
@@ -42,6 +44,9 @@ enum FilterTypes {
42
44
  More = 'More',
43
45
  }
44
46
 
47
+ const holdSubPointJitter = (previous: number | undefined, next: number) =>
48
+ previous !== undefined && Math.abs(next - previous) < LAYOUT_JITTER ? previous : next
49
+
45
50
  const FILTER_PARAMS: Record<Exclude<FilterTypes, FilterTypes.More>, ConversationFiltersParams> = {
46
51
  [FilterTypes.All]: {},
47
52
  [FilterTypes.Groups]: { group_source_app_name: 'Groups' },
@@ -217,14 +222,14 @@ export const FilterDropdownListHeaderComponent = () => {
217
222
  const [rowWidth, setRowWidth] = useState<number>()
218
223
  const [actionsWidth, setActionsWidth] = useState<number>()
219
224
  const [widestFixedLabelWidth, setWidestFixedLabelWidth] = useState<number>()
220
- const handleRowLayout = useCallback((event: LayoutChangeEvent) => {
221
- setRowWidth(event.nativeEvent.layout.width)
225
+ const handleRowLayout = useCallback(({ nativeEvent }: LayoutChangeEvent) => {
226
+ setRowWidth(previous => holdSubPointJitter(previous, nativeEvent.layout.width))
222
227
  }, [])
223
- const handleActionsLayout = useCallback((event: LayoutChangeEvent) => {
224
- setActionsWidth(event.nativeEvent.layout.width)
228
+ const handleActionsLayout = useCallback(({ nativeEvent }: LayoutChangeEvent) => {
229
+ setActionsWidth(previous => holdSubPointJitter(previous, nativeEvent.layout.width))
225
230
  }, [])
226
- const handleWidestFixedLabelLayout = useCallback((event: LayoutChangeEvent) => {
227
- setWidestFixedLabelWidth(event.nativeEvent.layout.width)
231
+ const handleWidestFixedLabelLayout = useCallback(({ nativeEvent }: LayoutChangeEvent) => {
232
+ setWidestFixedLabelWidth(previous => holdSubPointJitter(previous, nativeEvent.layout.width))
228
233
  }, [])
229
234
  const triggerMaxWidth = useMemo(() => {
230
235
  if (rowWidth === undefined) return undefined
@@ -232,7 +237,8 @@ export const FilterDropdownListHeaderComponent = () => {
232
237
  const rowContentWidth = rowWidth - ROW_PADDING_HORIZONTAL * 2
233
238
  if (actionsWidth === undefined || widestFixedLabelWidth === undefined) return rowContentWidth
234
239
 
235
- const roomBesideActions = rowContentWidth - actionsWidth - HEADING_ROW_GAP
240
+ const roomBesideActions =
241
+ Math.floor(rowContentWidth - actionsWidth - HEADING_ROW_GAP) - TRIGGER_CAP_SLACK
236
242
  return roomBesideActions >= widestFixedLabelWidth ? roomBesideActions : rowContentWidth
237
243
  }, [rowWidth, actionsWidth, widestFixedLabelWidth])
238
244
 
@@ -242,11 +248,16 @@ export const FilterDropdownListHeaderComponent = () => {
242
248
  <FilterDropdownTrigger
243
249
  label={ALL_CONVERSATIONS_LABEL}
244
250
  onLayout={handleWidestFixedLabelLayout}
251
+ testID="conversations-filter-widest-label"
245
252
  accessibilityElementsHidden
246
253
  importantForAccessibility="no-hide-descendants"
247
254
  />
248
255
  </View>
249
- <View style={styles.headingRow} onLayout={handleRowLayout}>
256
+ <View
257
+ style={styles.headingRow}
258
+ onLayout={handleRowLayout}
259
+ testID="conversations-filter-heading-row"
260
+ >
250
261
  <View style={styles.titleContainer}>
251
262
  <FilterDropdown
252
263
  options={options}
@@ -258,7 +269,11 @@ export const FilterDropdownListHeaderComponent = () => {
258
269
  maxWidth={triggerMaxWidth}
259
270
  />
260
271
  </View>
261
- <View style={styles.actionsContainer} onLayout={handleActionsLayout}>
272
+ <View
273
+ style={styles.actionsContainer}
274
+ onLayout={handleActionsLayout}
275
+ testID="conversations-filter-heading-actions"
276
+ >
262
277
  <ListHeaderActionButton
263
278
  name="general.cog"
264
279
  onPress={handleChatSettings}
@@ -22,10 +22,6 @@ export function SettingsScreen({}: SettingsScreenProps) {
22
22
  navigation.navigate('NotificationSettings', {})
23
23
  }, [navigation])
24
24
 
25
- const handleFlagTest = useCallback(() => {
26
- navigation.navigate('FlagTest')
27
- }, [navigation])
28
-
29
25
  const handleGetHelp = useCallback(() => {
30
26
  navigation.navigate('GetHelp', { type: 'chat' })
31
27
  }, [navigation])
@@ -55,7 +51,6 @@ export function SettingsScreen({}: SettingsScreenProps) {
55
51
  )}
56
52
  <NavRow label="Notification settings" onPress={handleNotificationSettings} />
57
53
  {appName !== 'services' && <NavRow label="Get help" onPress={handleGetHelp} />}
58
- <NavRow label="Flag test (staging)" onPress={handleFlagTest} />
59
54
  </View>
60
55
  </ScrollView>
61
56
  <View style={styles.footer}>
@@ -1,2 +0,0 @@
1
- export declare function FlagTestScreen(): import("react/jsx-runtime").JSX.Element;
2
- //# sourceMappingURL=flag_test_screen.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"flag_test_screen.d.ts","sourceRoot":"","sources":["../../src/screens/flag_test_screen.tsx"],"names":[],"mappings":"AAiEA,wBAAgB,cAAc,4CAuB7B"}
@@ -1,274 +0,0 @@
1
- import { c as _c } from "react/compiler-runtime";
2
- import { useQuery } from '@tanstack/react-query';
3
- import React from 'react';
4
- import { ScrollView, StyleSheet, View } from 'react-native';
5
- import { Heading, Text } from '../components';
6
- import { useApiClient, useTheme } from '../hooks';
7
- import { chatReactNativeVersion } from '../utils/package_version';
8
-
9
- // Staging-only: checks the test flags from planningcenter/chat#2642 on both API versions. Never merge.
10
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
11
- const OLD_VERSION = '2018-08-01';
12
- const NEW_VERSION = '2026-10-26';
13
- const TEST_FLAGS = {
14
- ROLLOUT_expected_on_because_meets_minimum_crn_version: {
15
- old: true,
16
- new: true
17
- },
18
- ROLLOUT_expected_off_because_below_minimum_crn_version: {
19
- old: true,
20
- new: false
21
- },
22
- ROLLOUT_expected_on_because_meets_minimum_host_version: {
23
- old: true,
24
- new: true
25
- },
26
- ROLLOUT_expected_off_because_below_minimum_host_version: {
27
- old: true,
28
- new: false,
29
- note: 'Only checked inside Church Center or Services; other hosts see it on'
30
- },
31
- ROLLOUT_expected_off_because_legacy_false: {
32
- old: false,
33
- new: true
34
- },
35
- ROLLOUT_expected_off_because_killed: {
36
- old: false,
37
- new: false,
38
- note: 'Needs DISABLE_MOBILE_ROLLOUT_expected_off_because_killed enabled in staging Flipper'
39
- },
40
- ROLLOUT_expected_off_because_fixed_off: {
41
- old: false,
42
- new: false
43
- },
44
- ROLLOUT_expected_on_because_flipper: {
45
- old: true,
46
- new: true,
47
- note: 'Needs a staging Flipper record enabled for your org'
48
- },
49
- ROLLOUT_expected_off_because_unregistered: {
50
- old: false,
51
- new: false
52
- }
53
- };
54
- const flagNames = Object.keys(TEST_FLAGS);
55
- const useFlagAnswers = version => {
56
- const $ = _c(8);
57
- const apiClient = useApiClient();
58
- let t0;
59
- if ($[0] !== version) {
60
- t0 = ["flag-test", version];
61
- $[0] = version;
62
- $[1] = t0;
63
- } else {
64
- t0 = $[1];
65
- }
66
- let t1;
67
- if ($[2] !== apiClient || $[3] !== version) {
68
- t1 = async () => {
69
- const response = await apiClient.chat.get({
70
- url: "/me/features",
71
- headers: {
72
- "X-PCO-API-Version": version,
73
- "X-PCO-Chat-React-Native-Version": chatReactNativeVersion
74
- },
75
- data: {
76
- where: {
77
- name: flagNames.join(",")
78
- },
79
- fields: {
80
- Feature: ["name", "enabled"]
81
- }
82
- }
83
- });
84
- return Object.fromEntries(response.data.map(_temp));
85
- };
86
- $[2] = apiClient;
87
- $[3] = version;
88
- $[4] = t1;
89
- } else {
90
- t1 = $[4];
91
- }
92
- let t2;
93
- if ($[5] !== t0 || $[6] !== t1) {
94
- t2 = {
95
- queryKey: t0,
96
- queryFn: t1
97
- };
98
- $[5] = t0;
99
- $[6] = t1;
100
- $[7] = t2;
101
- } else {
102
- t2 = $[7];
103
- }
104
- return useQuery(t2);
105
- };
106
- export function FlagTestScreen() {
107
- const $ = _c(16);
108
- const styles = useStyles();
109
- const oldAnswers = useFlagAnswers(OLD_VERSION);
110
- const newAnswers = useFlagAnswers(NEW_VERSION);
111
- const t0 = styles.container;
112
- let t1;
113
- if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
114
- t1 = /*#__PURE__*/_jsxs(Text, {
115
- variant: "tertiary",
116
- children: ["Sending chat-react-native ", chatReactNativeVersion, ". Old is ", OLD_VERSION, ", new is", " ", NEW_VERSION, "."]
117
- });
118
- $[0] = t1;
119
- } else {
120
- t1 = $[0];
121
- }
122
- let t2;
123
- if ($[1] !== oldAnswers.error || $[2] !== styles.fail) {
124
- t2 = oldAnswers.error && /*#__PURE__*/_jsxs(Text, {
125
- style: styles.fail,
126
- children: ["Old: ", String(oldAnswers.error)]
127
- });
128
- $[1] = oldAnswers.error;
129
- $[2] = styles.fail;
130
- $[3] = t2;
131
- } else {
132
- t2 = $[3];
133
- }
134
- let t3;
135
- if ($[4] !== newAnswers.error || $[5] !== styles.fail) {
136
- t3 = newAnswers.error && /*#__PURE__*/_jsxs(Text, {
137
- style: styles.fail,
138
- children: ["New: ", String(newAnswers.error)]
139
- });
140
- $[4] = newAnswers.error;
141
- $[5] = styles.fail;
142
- $[6] = t3;
143
- } else {
144
- t3 = $[6];
145
- }
146
- let t4;
147
- if ($[7] !== newAnswers.data || $[8] !== oldAnswers.data || $[9] !== styles.row) {
148
- t4 = flagNames.map(name => /*#__PURE__*/_jsxs(View, {
149
- style: styles.row,
150
- children: [/*#__PURE__*/_jsx(Heading, {
151
- variant: "h3",
152
- children: name.replace("ROLLOUT_", "")
153
- }), /*#__PURE__*/_jsx(Answer, {
154
- label: "Old",
155
- expected: TEST_FLAGS[name].old,
156
- actual: oldAnswers.data?.[name]
157
- }), /*#__PURE__*/_jsx(Answer, {
158
- label: "New",
159
- expected: TEST_FLAGS[name].new,
160
- actual: newAnswers.data?.[name]
161
- }), TEST_FLAGS[name].note && /*#__PURE__*/_jsx(Text, {
162
- variant: "tertiary",
163
- children: TEST_FLAGS[name].note
164
- })]
165
- }, name));
166
- $[7] = newAnswers.data;
167
- $[8] = oldAnswers.data;
168
- $[9] = styles.row;
169
- $[10] = t4;
170
- } else {
171
- t4 = $[10];
172
- }
173
- let t5;
174
- if ($[11] !== styles.container || $[12] !== t2 || $[13] !== t3 || $[14] !== t4) {
175
- t5 = /*#__PURE__*/_jsxs(ScrollView, {
176
- contentContainerStyle: t0,
177
- children: [t1, t2, t3, t4]
178
- });
179
- $[11] = styles.container;
180
- $[12] = t2;
181
- $[13] = t3;
182
- $[14] = t4;
183
- $[15] = t5;
184
- } else {
185
- t5 = $[15];
186
- }
187
- return t5;
188
- }
189
- function Answer(t0) {
190
- const $ = _c(8);
191
- const {
192
- label,
193
- expected,
194
- actual
195
- } = t0;
196
- const styles = useStyles();
197
- const onOff = _temp2;
198
- if (actual === undefined) {
199
- let t1;
200
- if ($[0] !== label) {
201
- t1 = /*#__PURE__*/_jsxs(Text, {
202
- variant: "secondary",
203
- children: [label, ": \u2026"]
204
- });
205
- $[0] = label;
206
- $[1] = t1;
207
- } else {
208
- t1 = $[1];
209
- }
210
- return t1;
211
- }
212
- const passed = actual === expected;
213
- const t1 = passed ? styles.pass : styles.fail;
214
- const t2 = passed ? "\u2713" : "\u2717";
215
- const t3 = onOff(actual);
216
- const t4 = onOff(expected);
217
- let t5;
218
- if ($[2] !== label || $[3] !== t1 || $[4] !== t2 || $[5] !== t3 || $[6] !== t4) {
219
- t5 = /*#__PURE__*/_jsxs(Text, {
220
- style: t1,
221
- children: [t2, " ", label, ": ", t3, " (expected ", t4, ")"]
222
- });
223
- $[2] = label;
224
- $[3] = t1;
225
- $[4] = t2;
226
- $[5] = t3;
227
- $[6] = t4;
228
- $[7] = t5;
229
- } else {
230
- t5 = $[7];
231
- }
232
- return t5;
233
- }
234
- function _temp2(value) {
235
- return value ? "on" : "off";
236
- }
237
- const useStyles = () => {
238
- const $ = _c(4);
239
- const {
240
- colors
241
- } = useTheme();
242
- let t0;
243
- if ($[0] !== colors.borderColorDefaultBase || $[1] !== colors.statusErrorText || $[2] !== colors.statusSuccessIcon) {
244
- t0 = StyleSheet.create({
245
- container: {
246
- padding: 16,
247
- gap: 16
248
- },
249
- row: {
250
- gap: 4,
251
- paddingBottom: 12,
252
- borderBottomWidth: 1,
253
- borderBottomColor: colors.borderColorDefaultBase
254
- },
255
- pass: {
256
- color: colors.statusSuccessIcon
257
- },
258
- fail: {
259
- color: colors.statusErrorText
260
- }
261
- });
262
- $[0] = colors.borderColorDefaultBase;
263
- $[1] = colors.statusErrorText;
264
- $[2] = colors.statusSuccessIcon;
265
- $[3] = t0;
266
- } else {
267
- t0 = $[3];
268
- }
269
- return t0;
270
- };
271
- function _temp(feature) {
272
- return [feature.name, feature.enabled];
273
- }
274
- //# sourceMappingURL=flag_test_screen.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"flag_test_screen.js","names":["useQuery","React","ScrollView","StyleSheet","View","Heading","Text","useApiClient","useTheme","chatReactNativeVersion","jsxs","_jsxs","jsx","_jsx","OLD_VERSION","NEW_VERSION","TEST_FLAGS","ROLLOUT_expected_on_because_meets_minimum_crn_version","old","new","ROLLOUT_expected_off_because_below_minimum_crn_version","ROLLOUT_expected_on_because_meets_minimum_host_version","ROLLOUT_expected_off_because_below_minimum_host_version","note","ROLLOUT_expected_off_because_legacy_false","ROLLOUT_expected_off_because_killed","ROLLOUT_expected_off_because_fixed_off","ROLLOUT_expected_on_because_flipper","ROLLOUT_expected_off_because_unregistered","flagNames","Object","keys","useFlagAnswers","version","$","_c","apiClient","t0","t1","response","chat","get","url","headers","data","where","name","join","fields","Feature","fromEntries","map","_temp","t2","queryKey","queryFn","FlagTestScreen","styles","useStyles","oldAnswers","newAnswers","container","Symbol","for","variant","children","error","fail","style","String","t3","t4","row","replace","Answer","label","expected","actual","t5","contentContainerStyle","onOff","_temp2","undefined","passed","pass","value","colors","borderColorDefaultBase","statusErrorText","statusSuccessIcon","create","padding","gap","paddingBottom","borderBottomWidth","borderBottomColor","color","feature","enabled"],"sources":["../../src/screens/flag_test_screen.tsx"],"sourcesContent":["import { useQuery } from '@tanstack/react-query'\nimport React from 'react'\nimport { ScrollView, StyleSheet, View } from 'react-native'\nimport { Heading, Text } from '../components'\nimport { useApiClient, useTheme } from '../hooks'\nimport { ApiCollection } from '../types'\nimport type { FeatureResource } from '../types/resources/feature_resource'\nimport { chatReactNativeVersion } from '../utils/package_version'\n\n// Staging-only: checks the test flags from planningcenter/chat#2642 on both API versions. Never merge.\n\nconst OLD_VERSION = '2018-08-01'\nconst NEW_VERSION = '2026-10-26'\n\ntype Expected = { old: boolean; new: boolean; note?: string }\n\nconst TEST_FLAGS: Record<string, Expected> = {\n ROLLOUT_expected_on_because_meets_minimum_crn_version: { old: true, new: true },\n ROLLOUT_expected_off_because_below_minimum_crn_version: { old: true, new: false },\n ROLLOUT_expected_on_because_meets_minimum_host_version: { old: true, new: true },\n ROLLOUT_expected_off_because_below_minimum_host_version: {\n old: true,\n new: false,\n note: 'Only checked inside Church Center or Services; other hosts see it on',\n },\n ROLLOUT_expected_off_because_legacy_false: { old: false, new: true },\n ROLLOUT_expected_off_because_killed: {\n old: false,\n new: false,\n note: 'Needs DISABLE_MOBILE_ROLLOUT_expected_off_because_killed enabled in staging Flipper',\n },\n ROLLOUT_expected_off_because_fixed_off: { old: false, new: false },\n ROLLOUT_expected_on_because_flipper: {\n old: true,\n new: true,\n note: 'Needs a staging Flipper record enabled for your org',\n },\n ROLLOUT_expected_off_because_unregistered: { old: false, new: false },\n}\n\nconst flagNames = Object.keys(TEST_FLAGS)\n\nconst useFlagAnswers = (version: string) => {\n const apiClient = useApiClient()\n\n return useQuery({\n queryKey: ['flag-test', version],\n queryFn: async () => {\n const response = await apiClient.chat.get<ApiCollection<FeatureResource>>({\n url: '/me/features',\n headers: {\n 'X-PCO-API-Version': version,\n 'X-PCO-Chat-React-Native-Version': chatReactNativeVersion,\n },\n data: {\n where: { name: flagNames.join(',') },\n fields: { Feature: ['name', 'enabled'] },\n },\n })\n\n return Object.fromEntries(response.data.map(feature => [feature.name, feature.enabled]))\n },\n })\n}\n\nexport function FlagTestScreen() {\n const styles = useStyles()\n const oldAnswers = useFlagAnswers(OLD_VERSION)\n const newAnswers = useFlagAnswers(NEW_VERSION)\n\n return (\n <ScrollView contentContainerStyle={styles.container}>\n <Text variant=\"tertiary\">\n Sending chat-react-native {chatReactNativeVersion}. Old is {OLD_VERSION}, new is{' '}\n {NEW_VERSION}.\n </Text>\n {oldAnswers.error && <Text style={styles.fail}>Old: {String(oldAnswers.error)}</Text>}\n {newAnswers.error && <Text style={styles.fail}>New: {String(newAnswers.error)}</Text>}\n {flagNames.map(name => (\n <View key={name} style={styles.row}>\n <Heading variant=\"h3\">{name.replace('ROLLOUT_', '')}</Heading>\n <Answer label=\"Old\" expected={TEST_FLAGS[name].old} actual={oldAnswers.data?.[name]} />\n <Answer label=\"New\" expected={TEST_FLAGS[name].new} actual={newAnswers.data?.[name]} />\n {TEST_FLAGS[name].note && <Text variant=\"tertiary\">{TEST_FLAGS[name].note}</Text>}\n </View>\n ))}\n </ScrollView>\n )\n}\n\nfunction Answer({\n label,\n expected,\n actual,\n}: {\n label: string\n expected: boolean\n actual: boolean | undefined\n}) {\n const styles = useStyles()\n const onOff = (value: boolean) => (value ? 'on' : 'off')\n\n if (actual === undefined) return <Text variant=\"secondary\">{label}: …</Text>\n\n const passed = actual === expected\n return (\n <Text style={passed ? styles.pass : styles.fail}>\n {passed ? '✓' : '✗'} {label}: {onOff(actual)} (expected {onOff(expected)})\n </Text>\n )\n}\n\nconst useStyles = () => {\n const { colors } = useTheme()\n return StyleSheet.create({\n container: {\n padding: 16,\n gap: 16,\n },\n row: {\n gap: 4,\n paddingBottom: 12,\n borderBottomWidth: 1,\n borderBottomColor: colors.borderColorDefaultBase,\n },\n pass: {\n color: colors.statusSuccessIcon,\n },\n fail: {\n color: colors.statusErrorText,\n },\n })\n}\n"],"mappings":"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","ignoreList":[]}
@@ -1,133 +0,0 @@
1
- import { useQuery } from '@tanstack/react-query'
2
- import React from 'react'
3
- import { ScrollView, StyleSheet, View } from 'react-native'
4
- import { Heading, Text } from '../components'
5
- import { useApiClient, useTheme } from '../hooks'
6
- import { ApiCollection } from '../types'
7
- import type { FeatureResource } from '../types/resources/feature_resource'
8
- import { chatReactNativeVersion } from '../utils/package_version'
9
-
10
- // Staging-only: checks the test flags from planningcenter/chat#2642 on both API versions. Never merge.
11
-
12
- const OLD_VERSION = '2018-08-01'
13
- const NEW_VERSION = '2026-10-26'
14
-
15
- type Expected = { old: boolean; new: boolean; note?: string }
16
-
17
- const TEST_FLAGS: Record<string, Expected> = {
18
- ROLLOUT_expected_on_because_meets_minimum_crn_version: { old: true, new: true },
19
- ROLLOUT_expected_off_because_below_minimum_crn_version: { old: true, new: false },
20
- ROLLOUT_expected_on_because_meets_minimum_host_version: { old: true, new: true },
21
- ROLLOUT_expected_off_because_below_minimum_host_version: {
22
- old: true,
23
- new: false,
24
- note: 'Only checked inside Church Center or Services; other hosts see it on',
25
- },
26
- ROLLOUT_expected_off_because_legacy_false: { old: false, new: true },
27
- ROLLOUT_expected_off_because_killed: {
28
- old: false,
29
- new: false,
30
- note: 'Needs DISABLE_MOBILE_ROLLOUT_expected_off_because_killed enabled in staging Flipper',
31
- },
32
- ROLLOUT_expected_off_because_fixed_off: { old: false, new: false },
33
- ROLLOUT_expected_on_because_flipper: {
34
- old: true,
35
- new: true,
36
- note: 'Needs a staging Flipper record enabled for your org',
37
- },
38
- ROLLOUT_expected_off_because_unregistered: { old: false, new: false },
39
- }
40
-
41
- const flagNames = Object.keys(TEST_FLAGS)
42
-
43
- const useFlagAnswers = (version: string) => {
44
- const apiClient = useApiClient()
45
-
46
- return useQuery({
47
- queryKey: ['flag-test', version],
48
- queryFn: async () => {
49
- const response = await apiClient.chat.get<ApiCollection<FeatureResource>>({
50
- url: '/me/features',
51
- headers: {
52
- 'X-PCO-API-Version': version,
53
- 'X-PCO-Chat-React-Native-Version': chatReactNativeVersion,
54
- },
55
- data: {
56
- where: { name: flagNames.join(',') },
57
- fields: { Feature: ['name', 'enabled'] },
58
- },
59
- })
60
-
61
- return Object.fromEntries(response.data.map(feature => [feature.name, feature.enabled]))
62
- },
63
- })
64
- }
65
-
66
- export function FlagTestScreen() {
67
- const styles = useStyles()
68
- const oldAnswers = useFlagAnswers(OLD_VERSION)
69
- const newAnswers = useFlagAnswers(NEW_VERSION)
70
-
71
- return (
72
- <ScrollView contentContainerStyle={styles.container}>
73
- <Text variant="tertiary">
74
- Sending chat-react-native {chatReactNativeVersion}. Old is {OLD_VERSION}, new is{' '}
75
- {NEW_VERSION}.
76
- </Text>
77
- {oldAnswers.error && <Text style={styles.fail}>Old: {String(oldAnswers.error)}</Text>}
78
- {newAnswers.error && <Text style={styles.fail}>New: {String(newAnswers.error)}</Text>}
79
- {flagNames.map(name => (
80
- <View key={name} style={styles.row}>
81
- <Heading variant="h3">{name.replace('ROLLOUT_', '')}</Heading>
82
- <Answer label="Old" expected={TEST_FLAGS[name].old} actual={oldAnswers.data?.[name]} />
83
- <Answer label="New" expected={TEST_FLAGS[name].new} actual={newAnswers.data?.[name]} />
84
- {TEST_FLAGS[name].note && <Text variant="tertiary">{TEST_FLAGS[name].note}</Text>}
85
- </View>
86
- ))}
87
- </ScrollView>
88
- )
89
- }
90
-
91
- function Answer({
92
- label,
93
- expected,
94
- actual,
95
- }: {
96
- label: string
97
- expected: boolean
98
- actual: boolean | undefined
99
- }) {
100
- const styles = useStyles()
101
- const onOff = (value: boolean) => (value ? 'on' : 'off')
102
-
103
- if (actual === undefined) return <Text variant="secondary">{label}: …</Text>
104
-
105
- const passed = actual === expected
106
- return (
107
- <Text style={passed ? styles.pass : styles.fail}>
108
- {passed ? '✓' : '✗'} {label}: {onOff(actual)} (expected {onOff(expected)})
109
- </Text>
110
- )
111
- }
112
-
113
- const useStyles = () => {
114
- const { colors } = useTheme()
115
- return StyleSheet.create({
116
- container: {
117
- padding: 16,
118
- gap: 16,
119
- },
120
- row: {
121
- gap: 4,
122
- paddingBottom: 12,
123
- borderBottomWidth: 1,
124
- borderBottomColor: colors.borderColorDefaultBase,
125
- },
126
- pass: {
127
- color: colors.statusSuccessIcon,
128
- },
129
- fail: {
130
- color: colors.statusErrorText,
131
- },
132
- })
133
- }