react-native-acoustic-connect-beta 19.0.20 → 19.0.22
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/CHANGELOG.md +10 -0
- package/README.md +84 -1
- package/android/src/main/assets/ConnectBasicConfig.properties +1 -1
- package/package.json +4 -1
- package/Examples/SampleUI/__tests__/App.test.tsx +0 -17
- package/Examples/SampleUI/src/styles/__tests__/fonts.test.js +0 -290
- package/cli/__tests__/doctor.test.mjs +0 -88
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
## 19.0.22 (2026-09-10)
|
|
2
|
+
|
|
3
|
+
### Reverts
|
|
4
|
+
|
|
5
|
+
* Revert "Beta ReactNativeConnect build: 18.0.36" ([609ad7d](https://github.com/aipoweredmarketer/react-native-acoustic-connect-beta/commit/609ad7d3244ec06f27dced5864d40585c5b3c9cf))
|
|
6
|
+
## 19.0.21 (2026-09-09)
|
|
7
|
+
|
|
8
|
+
### Reverts
|
|
9
|
+
|
|
10
|
+
* Revert "Beta ReactNativeConnect build: 18.0.36" ([609ad7d](https://github.com/aipoweredmarketer/react-native-acoustic-connect-beta/commit/609ad7d3244ec06f27dced5864d40585c5b3c9cf))
|
|
1
11
|
## 19.0.20 (2026-09-09)
|
|
2
12
|
|
|
3
13
|
### Reverts
|
package/README.md
CHANGED
|
@@ -569,13 +569,22 @@ one. To turn screenshot capture off on both platforms:
|
|
|
569
569
|
"Connect": {
|
|
570
570
|
"layoutConfig": {
|
|
571
571
|
"AutoLayout": {
|
|
572
|
-
"GlobalScreenSettings": { "ScreenShot": false
|
|
572
|
+
"GlobalScreenSettings": { "ScreenShot": false }
|
|
573
573
|
}
|
|
574
574
|
}
|
|
575
575
|
}
|
|
576
576
|
}
|
|
577
577
|
```
|
|
578
578
|
|
|
579
|
+
`ScreenShot` is the cross-platform switch — set it in `layoutConfig` (or per
|
|
580
|
+
platform) and both native SDKs honour it. `CaptureScreenshotOn` is a related
|
|
581
|
+
but **iOS-only** key: iOS's native auto-instrumentation reads it, but on
|
|
582
|
+
Android the equivalent reader has no callers, so `CaptureScreenshotOn` is
|
|
583
|
+
accepted wherever you put it and does nothing there. Set it only under
|
|
584
|
+
`layoutConfigIos` if you want it; leaving it out of `layoutConfigAndroid` (or
|
|
585
|
+
the shared `layoutConfig`) costs nothing on Android and avoids implying it
|
|
586
|
+
does something it doesn't.
|
|
587
|
+
|
|
579
588
|
Put anything common in `layoutConfig` and use `layoutConfigIos` /
|
|
580
589
|
`layoutConfigAndroid` only for what differs. The platform block is **deep-merged**
|
|
581
590
|
over the shared one, so it refines the baseline rather than replacing it: an
|
|
@@ -584,6 +593,32 @@ rules, and every other shared value. Arrays are replaced outright rather than
|
|
|
584
593
|
concatenated, so a platform block can shorten or clear a shared list such as
|
|
585
594
|
`MaskIdList`.
|
|
586
595
|
|
|
596
|
+
#### Masking is selection, then redaction — an unmatched pattern leaves data unmasked
|
|
597
|
+
|
|
598
|
+
A screen rule's `Masking` block works in two independent steps: a control is
|
|
599
|
+
first **selected** by a matching `MaskIdList` (control id), `MaskValueList`
|
|
600
|
+
(control value), `MaskAccessibilityIdList` (accessibility id), or
|
|
601
|
+
`MaskAccessibilityLabelList` (accessibility label) pattern, and only a
|
|
602
|
+
*selected* control is redacted — its value, and (Connect iOS 2.1.22+ / Android
|
|
603
|
+
11.0.23-beta+ only — older SDKs serialised the accessibility object verbatim
|
|
604
|
+
regardless of masking) its accessibility label and hint. A control whose value
|
|
605
|
+
never matches any pattern in any of the four lists is not masked in any field.
|
|
606
|
+
That's by design, not a bug — but it means a client whose patterns don't
|
|
607
|
+
happen to match a given value can believe that value is masked when it never
|
|
608
|
+
was.
|
|
609
|
+
|
|
610
|
+
Email addresses are an easy miss: they rarely match a card-number or
|
|
611
|
+
`SECRET-`-style pattern, and free-text fields (bios, support messages,
|
|
612
|
+
usernames) routinely contain one. For any screen that collects email
|
|
613
|
+
addresses, add a pattern to `MaskValueList`:
|
|
614
|
+
|
|
615
|
+
```json
|
|
616
|
+
{ "MaskValueList": ["[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"] }
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
(The `bare-workflow` and `expo` sample `ConnectConfig.example.json` already
|
|
620
|
+
ship this pattern on `GlobalScreenSettings`.)
|
|
621
|
+
|
|
587
622
|
When no block applies, each native SDK keeps the defaults from its own bundled
|
|
588
623
|
layout config:
|
|
589
624
|
|
|
@@ -636,6 +671,54 @@ every shipped template uses. To stand layout capture down deliberately, set
|
|
|
636
671
|
`CaptureLayoutOn: 0` on the rule instead — it is the key that means that, and it
|
|
637
672
|
leaves `NumberOfWebViews` free to describe the screen.
|
|
638
673
|
|
|
674
|
+
#### WebView capture on Android: prerequisites and two open limitations
|
|
675
|
+
|
|
676
|
+
A screen that hosts a `react-native-webview` `WebView` has capture behaviour
|
|
677
|
+
beyond the config above:
|
|
678
|
+
|
|
679
|
+
- **The WebView must be scrolled on screen.** A capture only records what is
|
|
680
|
+
currently visible — the native tree-walk skips subtrees outside the
|
|
681
|
+
viewport by design. A `WebView` sitting below the fold at the default
|
|
682
|
+
scroll position yields a layout with no WebView nodes at all, which looks
|
|
683
|
+
exactly like WebView capture never engaged. Scroll it fully into view
|
|
684
|
+
before triggering (or waiting on) a capture.
|
|
685
|
+
- **`GoogleWebViewEnabled`** is a top-level `Connect` key (Android only,
|
|
686
|
+
default `true`) that gates whether the SDK instruments WebViews at all:
|
|
687
|
+
|
|
688
|
+
```json
|
|
689
|
+
{ "Connect": { "GoogleWebViewEnabled": false } }
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
Set to `false`, the SDK does not discover or walk into any `WebView` on the
|
|
693
|
+
screen — the rest of that screen still captures normally. It does not gate
|
|
694
|
+
anything else on this list: screen views, clicks, and non-WebView layout
|
|
695
|
+
content are unaffected either way.
|
|
696
|
+
- **A discovered WebView currently drops that screen's whole layout
|
|
697
|
+
message.** Once the SDK finds a `WebView`, it waits for a DOM-capture
|
|
698
|
+
correlation id from the page before it can finish and post the layout — and
|
|
699
|
+
a React Native-hosted `WebView` never supplies one, so the wait never
|
|
700
|
+
resolves and the *entire* screen's layout message is dropped, not just the
|
|
701
|
+
WebView's portion. Confirmed present in Android Connect **11.0.23-beta**, the
|
|
702
|
+
newest published artifact at the time of writing, and in every earlier version
|
|
703
|
+
in the supported range — measured on an emulator, where a capture on a screen
|
|
704
|
+
with a visible `WebView` posted **no messages at all** for that session, since
|
|
705
|
+
the layout's queue placeholder blocks the batch it sits in. There is no
|
|
706
|
+
client-side fix; `GoogleWebViewEnabled: false` (below) avoids it by not
|
|
707
|
+
instrumenting the `WebView` in the first place. A fix exists in the native SDK
|
|
708
|
+
but is not in any published artifact yet — check the Android Connect release
|
|
709
|
+
notes for a version above 11.0.23-beta before assuming this still applies.
|
|
710
|
+
- **Enabling WebView capture replaces the app's `WebViewClient`.** The native
|
|
711
|
+
SDK installs its own `WebViewClient` on that `WebView` to instrument it, in
|
|
712
|
+
place of the one `react-native-webview` had set — so callbacks such as
|
|
713
|
+
`onHttpError` and `onNavigationStateChange` on that `WebView` stop firing
|
|
714
|
+
while capture is active. Same status as the item above: present through Android
|
|
715
|
+
Connect **11.0.23-beta**, fixed in the native SDK but not yet published, and
|
|
716
|
+
the rest of the screen is unaffected either way.
|
|
717
|
+
|
|
718
|
+
Both of the limitations above only occur once the SDK is instrumenting a
|
|
719
|
+
`WebView`, so `GoogleWebViewEnabled: false` avoids both — at the cost of
|
|
720
|
+
getting no WebView capture on that screen at all.
|
|
721
|
+
|
|
639
722
|
#### Capture timing (`CaptureLayoutDelay`)
|
|
640
723
|
|
|
641
724
|
`AutoLayout.GlobalScreenSettings.CaptureLayoutDelay` is how long, in
|
package/package.json
CHANGED
|
@@ -92,12 +92,15 @@
|
|
|
92
92
|
"plugin/swift",
|
|
93
93
|
"plugin/src",
|
|
94
94
|
"cli",
|
|
95
|
+
"!cli/__tests__/**",
|
|
95
96
|
"ConnectConfig.example.json",
|
|
96
97
|
"*.podspec",
|
|
97
98
|
"README.md",
|
|
98
99
|
"CHANGELOG.md",
|
|
99
100
|
"scripts",
|
|
100
101
|
"Examples/SampleUI/**/*",
|
|
102
|
+
"!Examples/SampleUI/**/__tests__/**",
|
|
103
|
+
"!Examples/SampleUI/**/*.test.*",
|
|
101
104
|
"!Examples/SampleUI/node_modules/**",
|
|
102
105
|
"!Examples/SampleUI/temp/**",
|
|
103
106
|
"!Examples/SampleUI/logs/**",
|
|
@@ -230,7 +233,7 @@
|
|
|
230
233
|
"source": "src/index",
|
|
231
234
|
"summary": "react-native ios android tealeaf connect cxa wxca er enhanced-replay",
|
|
232
235
|
"types": "./lib/typescript/src/index.d.ts",
|
|
233
|
-
"version": "19.0.
|
|
236
|
+
"version": "19.0.22",
|
|
234
237
|
"workspaces": [
|
|
235
238
|
"example",
|
|
236
239
|
"Examples/bare-workflow"
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @format
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import 'react-native';
|
|
6
|
-
import React from 'react';
|
|
7
|
-
import App from '../App';
|
|
8
|
-
|
|
9
|
-
// Note: import explicitly to use the types shipped with jest.
|
|
10
|
-
import {it} from '@jest/globals';
|
|
11
|
-
|
|
12
|
-
// Note: test renderer must be required after react-native.
|
|
13
|
-
import renderer from 'react-test-renderer';
|
|
14
|
-
|
|
15
|
-
it('renders correctly', () => {
|
|
16
|
-
renderer.create(<App />);
|
|
17
|
-
});
|
|
@@ -1,290 +0,0 @@
|
|
|
1
|
-
import configureFonts, { fontConfig } from '../fonts';
|
|
2
|
-
import { typescale } from '../themes/v3/tokens';
|
|
3
|
-
|
|
4
|
-
const mockPlatform = (OS) => {
|
|
5
|
-
jest.resetModules();
|
|
6
|
-
jest.doMock('react-native/Libraries/Utilities/Platform', () => ({
|
|
7
|
-
OS,
|
|
8
|
-
select: (objs) => objs[OS],
|
|
9
|
-
}));
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
const customFont = {
|
|
13
|
-
custom: {
|
|
14
|
-
fontFamily: 'sans-serif',
|
|
15
|
-
fontWeight: 'bold',
|
|
16
|
-
},
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const customFontV3 = {
|
|
20
|
-
displayLarge: {
|
|
21
|
-
fontFamily: 'NotoSans',
|
|
22
|
-
letterSpacing: 0,
|
|
23
|
-
fontWeight: '400',
|
|
24
|
-
lineHeight: 64,
|
|
25
|
-
fontSize: 57,
|
|
26
|
-
},
|
|
27
|
-
displayMedium: {
|
|
28
|
-
fontFamily: 'NotoSans',
|
|
29
|
-
letterSpacing: 0,
|
|
30
|
-
fontWeight: '400',
|
|
31
|
-
lineHeight: 52,
|
|
32
|
-
fontSize: 45,
|
|
33
|
-
},
|
|
34
|
-
displaySmall: {
|
|
35
|
-
fontFamily: 'NotoSans',
|
|
36
|
-
letterSpacing: 0,
|
|
37
|
-
fontWeight: '400',
|
|
38
|
-
lineHeight: 44,
|
|
39
|
-
fontSize: 36,
|
|
40
|
-
},
|
|
41
|
-
|
|
42
|
-
headlineLarge: {
|
|
43
|
-
fontFamily: 'NotoSans',
|
|
44
|
-
letterSpacing: 0,
|
|
45
|
-
fontWeight: '400',
|
|
46
|
-
lineHeight: 40,
|
|
47
|
-
fontSize: 32,
|
|
48
|
-
},
|
|
49
|
-
headlineMedium: {
|
|
50
|
-
fontFamily: 'NotoSans',
|
|
51
|
-
letterSpacing: 0,
|
|
52
|
-
fontWeight: '400',
|
|
53
|
-
lineHeight: 36,
|
|
54
|
-
fontSize: 28,
|
|
55
|
-
},
|
|
56
|
-
headlineSmall: {
|
|
57
|
-
fontFamily: 'NotoSans',
|
|
58
|
-
letterSpacing: 0,
|
|
59
|
-
fontWeight: '400',
|
|
60
|
-
lineHeight: 32,
|
|
61
|
-
fontSize: 24,
|
|
62
|
-
},
|
|
63
|
-
|
|
64
|
-
titleLarge: {
|
|
65
|
-
fontFamily: 'NotoSans',
|
|
66
|
-
letterSpacing: 0,
|
|
67
|
-
fontWeight: '400',
|
|
68
|
-
lineHeight: 28,
|
|
69
|
-
fontSize: 22,
|
|
70
|
-
},
|
|
71
|
-
titleMedium: {
|
|
72
|
-
fontFamily: 'NotoSans',
|
|
73
|
-
letterSpacing: 0,
|
|
74
|
-
fontWeight: '500',
|
|
75
|
-
lineHeight: 24,
|
|
76
|
-
fontSize: 16,
|
|
77
|
-
},
|
|
78
|
-
titleSmall: {
|
|
79
|
-
fontFamily: 'NotoSans',
|
|
80
|
-
fontWeight: '500',
|
|
81
|
-
letterSpacing: 0,
|
|
82
|
-
lineHeight: 20,
|
|
83
|
-
fontSize: 14,
|
|
84
|
-
},
|
|
85
|
-
|
|
86
|
-
labelLarge: {
|
|
87
|
-
fontFamily: 'NotoSans',
|
|
88
|
-
fontWeight: '500',
|
|
89
|
-
letterSpacing: 0,
|
|
90
|
-
lineHeight: 20,
|
|
91
|
-
fontSize: 14,
|
|
92
|
-
},
|
|
93
|
-
labelMedium: {
|
|
94
|
-
fontFamily: 'NotoSans',
|
|
95
|
-
fontWeight: '500',
|
|
96
|
-
letterSpacing: 0,
|
|
97
|
-
lineHeight: 16,
|
|
98
|
-
fontSize: 12,
|
|
99
|
-
},
|
|
100
|
-
labelSmall: {
|
|
101
|
-
fontFamily: 'NotoSans',
|
|
102
|
-
fontWeight: '500',
|
|
103
|
-
letterSpacing: 0,
|
|
104
|
-
lineHeight: 16,
|
|
105
|
-
fontSize: 11,
|
|
106
|
-
},
|
|
107
|
-
|
|
108
|
-
bodyLarge: {
|
|
109
|
-
fontFamily: 'NotoSans',
|
|
110
|
-
letterSpacing: 0,
|
|
111
|
-
fontWeight: '400',
|
|
112
|
-
lineHeight: 24,
|
|
113
|
-
fontSize: 16,
|
|
114
|
-
},
|
|
115
|
-
bodyMedium: {
|
|
116
|
-
fontFamily: 'NotoSans',
|
|
117
|
-
fontWeight: '400',
|
|
118
|
-
letterSpacing: 0,
|
|
119
|
-
lineHeight: 20,
|
|
120
|
-
fontSize: 14,
|
|
121
|
-
},
|
|
122
|
-
bodySmall: {
|
|
123
|
-
fontFamily: 'NotoSans',
|
|
124
|
-
fontWeight: '400',
|
|
125
|
-
letterSpacing: 0,
|
|
126
|
-
lineHeight: 16,
|
|
127
|
-
fontSize: 12,
|
|
128
|
-
},
|
|
129
|
-
|
|
130
|
-
default: {
|
|
131
|
-
fontFamily: 'NotoSans',
|
|
132
|
-
letterSpacing: 0,
|
|
133
|
-
fontWeight: '400',
|
|
134
|
-
},
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
describe('configureFonts', () => {
|
|
138
|
-
it('adds custom fonts to the iOS config', () => {
|
|
139
|
-
mockPlatform('ios');
|
|
140
|
-
expect(
|
|
141
|
-
configureFonts({
|
|
142
|
-
config: {
|
|
143
|
-
ios: {
|
|
144
|
-
...fontConfig.ios,
|
|
145
|
-
customFont,
|
|
146
|
-
},
|
|
147
|
-
},
|
|
148
|
-
isV3: false,
|
|
149
|
-
})
|
|
150
|
-
).toEqual({
|
|
151
|
-
...fontConfig.ios,
|
|
152
|
-
customFont,
|
|
153
|
-
});
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it('adds custom fonts to the Android config', () => {
|
|
157
|
-
mockPlatform('android');
|
|
158
|
-
expect(
|
|
159
|
-
configureFonts({
|
|
160
|
-
config: {
|
|
161
|
-
android: {
|
|
162
|
-
...fontConfig.android,
|
|
163
|
-
customFont,
|
|
164
|
-
},
|
|
165
|
-
},
|
|
166
|
-
isV3: false,
|
|
167
|
-
})
|
|
168
|
-
).toEqual({
|
|
169
|
-
...fontConfig.android,
|
|
170
|
-
customFont,
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
it('adds custom fonts to the Web config', () => {
|
|
175
|
-
mockPlatform('web');
|
|
176
|
-
expect(
|
|
177
|
-
configureFonts({
|
|
178
|
-
config: {
|
|
179
|
-
web: {
|
|
180
|
-
...fontConfig.web,
|
|
181
|
-
customFont,
|
|
182
|
-
},
|
|
183
|
-
},
|
|
184
|
-
isV3: false,
|
|
185
|
-
})
|
|
186
|
-
).toEqual({
|
|
187
|
-
...fontConfig.web,
|
|
188
|
-
customFont,
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it('overrides properties passed in config for all variants', () => {
|
|
193
|
-
expect(
|
|
194
|
-
configureFonts({
|
|
195
|
-
config: {
|
|
196
|
-
fontFamily: 'NotoSans',
|
|
197
|
-
letterSpacing: 0,
|
|
198
|
-
},
|
|
199
|
-
})
|
|
200
|
-
).toEqual(customFontV3);
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
it('overrides properties passed in config for several variants', () => {
|
|
204
|
-
expect(
|
|
205
|
-
configureFonts({
|
|
206
|
-
config: {
|
|
207
|
-
bodyLarge: {
|
|
208
|
-
fontFamily: 'NotoSans',
|
|
209
|
-
fontSize: 18,
|
|
210
|
-
fontStyle: 'italic',
|
|
211
|
-
},
|
|
212
|
-
headlineMedium: {
|
|
213
|
-
fontSize: 30,
|
|
214
|
-
},
|
|
215
|
-
bodySmall: {
|
|
216
|
-
fontStyle: 'italic',
|
|
217
|
-
},
|
|
218
|
-
},
|
|
219
|
-
})
|
|
220
|
-
).toEqual({
|
|
221
|
-
...typescale,
|
|
222
|
-
bodyLarge: {
|
|
223
|
-
fontFamily: 'NotoSans',
|
|
224
|
-
letterSpacing: 0.15,
|
|
225
|
-
fontWeight: '400',
|
|
226
|
-
fontStyle: 'italic',
|
|
227
|
-
lineHeight: 24,
|
|
228
|
-
fontSize: 18,
|
|
229
|
-
},
|
|
230
|
-
headlineMedium: {
|
|
231
|
-
fontFamily: 'System',
|
|
232
|
-
letterSpacing: 0,
|
|
233
|
-
fontWeight: '400',
|
|
234
|
-
lineHeight: 36,
|
|
235
|
-
fontSize: 30,
|
|
236
|
-
},
|
|
237
|
-
bodySmall: {
|
|
238
|
-
fontFamily: 'System',
|
|
239
|
-
fontWeight: '400',
|
|
240
|
-
fontStyle: 'italic',
|
|
241
|
-
letterSpacing: 0.4,
|
|
242
|
-
lineHeight: 16,
|
|
243
|
-
fontSize: 12,
|
|
244
|
-
},
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
it('adds custom variant to theme fonts', () => {
|
|
249
|
-
expect(
|
|
250
|
-
configureFonts({
|
|
251
|
-
config: {
|
|
252
|
-
customVariant: {
|
|
253
|
-
fontFamily: 'NotoSans',
|
|
254
|
-
letterSpacing: 0,
|
|
255
|
-
fontWeight: '400',
|
|
256
|
-
fontStyle: 'italic',
|
|
257
|
-
lineHeight: 64,
|
|
258
|
-
fontSize: 57,
|
|
259
|
-
},
|
|
260
|
-
},
|
|
261
|
-
})
|
|
262
|
-
).toEqual({
|
|
263
|
-
...typescale,
|
|
264
|
-
customVariant: {
|
|
265
|
-
fontFamily: 'NotoSans',
|
|
266
|
-
letterSpacing: 0,
|
|
267
|
-
fontWeight: '400',
|
|
268
|
-
fontStyle: 'italic',
|
|
269
|
-
lineHeight: 64,
|
|
270
|
-
fontSize: 57,
|
|
271
|
-
},
|
|
272
|
-
});
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
it('should be deterministic', () => {
|
|
276
|
-
// first call that should not mutate the original config
|
|
277
|
-
configureFonts({
|
|
278
|
-
config: {
|
|
279
|
-
labelMedium: {
|
|
280
|
-
color: 'coral',
|
|
281
|
-
},
|
|
282
|
-
},
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
// second call that should return the original config without modification
|
|
286
|
-
const fontsB = configureFonts({ config: {} });
|
|
287
|
-
|
|
288
|
-
expect(fontsB.labelMedium.color).toBeUndefined();
|
|
289
|
-
});
|
|
290
|
-
});
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
// Copyright (C) 2026 Acoustic, L.P. All rights reserved.
|
|
2
|
-
//
|
|
3
|
-
// Unit tests for ensureConnectConfig's local→bundled→missing fallback in
|
|
4
|
-
// cli/doctor.mjs. Uses Node's built-in test runner (node:test) — no build
|
|
5
|
-
// step needed, cli/ is plain ESM.
|
|
6
|
-
//
|
|
7
|
-
// npm run test:cli
|
|
8
|
-
|
|
9
|
-
import { test } from 'node:test'
|
|
10
|
-
import assert from 'node:assert/strict'
|
|
11
|
-
import * as fs from 'node:fs'
|
|
12
|
-
import * as os from 'node:os'
|
|
13
|
-
import * as path from 'node:path'
|
|
14
|
-
|
|
15
|
-
import { ensureConnectConfig } from '../doctor.mjs'
|
|
16
|
-
import { Reporter } from '../lib.mjs'
|
|
17
|
-
|
|
18
|
-
function tmpDir() {
|
|
19
|
-
return fs.mkdtempSync(path.join(os.tmpdir(), 'connect-doctor-test-'))
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// ensureConnectConfig's 'exists' branch reports as `pass('ConnectConfig.json present')`
|
|
23
|
-
// (label includes the detail) while the other branches use a plain
|
|
24
|
-
// 'ConnectConfig.json' label with a separate detail string — match by prefix
|
|
25
|
-
// so the assertion doesn't depend on that formatting quirk.
|
|
26
|
-
function statusFor(reporter, labelPrefix) {
|
|
27
|
-
return reporter.results.find((r) => r.label.startsWith(labelPrefix))?.status
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
test('ensureConnectConfig: prefers a project-local ConnectConfig.example.json over the bundled one', () => {
|
|
31
|
-
const dir = tmpDir()
|
|
32
|
-
fs.writeFileSync(
|
|
33
|
-
path.join(dir, 'ConnectConfig.example.json'),
|
|
34
|
-
JSON.stringify({ Connect: { AppKey: 'from-local-example' } }),
|
|
35
|
-
)
|
|
36
|
-
const reporter = new Reporter()
|
|
37
|
-
|
|
38
|
-
ensureConnectConfig(reporter, dir, {
|
|
39
|
-
bundledExample: path.join(dir, 'never-read.json'),
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
const written = JSON.parse(fs.readFileSync(path.join(dir, 'ConnectConfig.json'), 'utf8'))
|
|
43
|
-
assert.equal(written.Connect.AppKey, 'from-local-example')
|
|
44
|
-
assert.equal(statusFor(reporter, 'ConnectConfig.json'), 'warn')
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
test('ensureConnectConfig: falls back to the bundled example when no project-local one exists', () => {
|
|
48
|
-
const dir = tmpDir()
|
|
49
|
-
const bundledDir = tmpDir()
|
|
50
|
-
const bundledExample = path.join(bundledDir, 'ConnectConfig.example.json')
|
|
51
|
-
fs.writeFileSync(bundledExample, JSON.stringify({ Connect: { AppKey: 'from-bundled-example' } }))
|
|
52
|
-
const reporter = new Reporter()
|
|
53
|
-
|
|
54
|
-
ensureConnectConfig(reporter, dir, { bundledExample })
|
|
55
|
-
|
|
56
|
-
const written = JSON.parse(fs.readFileSync(path.join(dir, 'ConnectConfig.json'), 'utf8'))
|
|
57
|
-
assert.equal(written.Connect.AppKey, 'from-bundled-example')
|
|
58
|
-
assert.equal(statusFor(reporter, 'ConnectConfig.json'), 'warn')
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
test('ensureConnectConfig: fails cleanly (no crash, no file written) when both are missing', () => {
|
|
62
|
-
const dir = tmpDir()
|
|
63
|
-
const reporter = new Reporter()
|
|
64
|
-
|
|
65
|
-
ensureConnectConfig(reporter, dir, {
|
|
66
|
-
bundledExample: path.join(dir, 'does-not-exist.json'),
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
assert.equal(fs.existsSync(path.join(dir, 'ConnectConfig.json')), false)
|
|
70
|
-
assert.equal(statusFor(reporter, 'ConnectConfig.json'), 'fail')
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
test('ensureConnectConfig: never overwrites an existing ConnectConfig.json', () => {
|
|
74
|
-
const dir = tmpDir()
|
|
75
|
-
fs.writeFileSync(
|
|
76
|
-
path.join(dir, 'ConnectConfig.json'),
|
|
77
|
-
JSON.stringify({ Connect: { AppKey: 'already-configured' } }),
|
|
78
|
-
)
|
|
79
|
-
const reporter = new Reporter()
|
|
80
|
-
|
|
81
|
-
ensureConnectConfig(reporter, dir, {
|
|
82
|
-
bundledExample: path.join(dir, 'never-read.json'),
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
const written = JSON.parse(fs.readFileSync(path.join(dir, 'ConnectConfig.json'), 'utf8'))
|
|
86
|
-
assert.equal(written.Connect.AppKey, 'already-configured')
|
|
87
|
-
assert.equal(statusFor(reporter, 'ConnectConfig.json'), 'pass')
|
|
88
|
-
})
|