dev
dart 340 lines 11.3 KB
Raw
1 import "package:cake_wallet/entities/balance_display_mode.dart";
2 import "package:cake_wallet/entities/bitcoin_amount_display_mode.dart";
3 import "package:cake_wallet/new-ui/widgets/money/money_settings_cubit.dart";
4 import "package:cake_wallet/new-ui/widgets/money/money_text.dart";
5 import "package:cw_core/amount/money.dart";
6 import "package:cw_core/crypto_currency.dart";
7 import "package:flutter/material.dart";
8 import "package:flutter_bloc/flutter_bloc.dart";
9 import "package:flutter_test/flutter_test.dart";
10
11 class FakeMoneySettingsCubit extends Cubit<MoneySettingsState> implements MoneySettingsCubit {
12 FakeMoneySettingsCubit(super.initialState);
13
14 void setState(MoneySettingsState state) => emit(state);
15 }
16
17 /// Visible, BTC shown as bitcoin (not base unit).
18 const _visibleBtc = MoneySettingsState(
19 bitcoinAmountDisplayMode: BitcoinAmountDisplayMode.bitcoin,
20 displayMode: BalanceDisplayMode.fullBalance,
21 );
22
23 /// Hidden balance.
24 const _hiddenBtc = MoneySettingsState(
25 bitcoinAmountDisplayMode: BitcoinAmountDisplayMode.bitcoin,
26 displayMode: BalanceDisplayMode.hiddenBalance,
27 );
28
29 /// Visible, satoshi mode -> BTC/BTCLN use base unit.
30 const _sats = MoneySettingsState(
31 bitcoinAmountDisplayMode: BitcoinAmountDisplayMode.satoshi,
32 displayMode: BalanceDisplayMode.fullBalance,
33 );
34
35 /// Visible, LN-only satoshi mode -> only BTCLN uses base unit.
36 const _lnSats = MoneySettingsState(
37 bitcoinAmountDisplayMode: BitcoinAmountDisplayMode.satoshiForLightning,
38 displayMode: BalanceDisplayMode.fullBalance,
39 );
40
41 // --- Money fixtures --------------------------------------------------------
42 final _btc012 = Money.fromInt(12345678, CryptoCurrency.btc); // 0.12345678 BTC
43 final _btcTrailing = Money.fromInt(12000000, CryptoCurrency.btc); // 0.12 BTC
44 final _btcGrouping = Money.fromInt(123450000000, CryptoCurrency.btc); // 1234.5 BTC
45 final _xmrHalf = Money.fromInt(500000000000, CryptoCurrency.xmr); // 0.5 XMR
46 final _btclnAmount = Money.fromInt(12345678, CryptoCurrency.btcln); // 0.12345678 BTC (LN)
47
48 const _hidden = "●●●●●●";
49
50 Future<FakeMoneySettingsCubit> _pump(
51 WidgetTester tester,
52 Widget child,
53 MoneySettingsState state,
54 ) async {
55 final cubit = FakeMoneySettingsCubit(state);
56 addTearDown(cubit.close);
57 await tester.pumpWidget(
58 MaterialApp(
59 home: BlocProvider<MoneySettingsCubit>.value(
60 value: cubit,
61 child: Scaffold(body: Center(child: child)),
62 ),
63 ),
64 );
65 return cubit;
66 }
67
68 void main() {
69 group("hidden amount", () {
70 testWidgets("isHiddenAmount:true hides even when state is visible", (tester) async {
71 await _pump(tester, MoneyText(_btc012, isHiddenAmount: true), _visibleBtc);
72
73 expect(find.text(_hidden), findsOneWidget);
74 expect(find.text("0.12345678 BTC"), findsNothing);
75 });
76
77 testWidgets("isHiddenAmount:false overrides a hidden state", (tester) async {
78 await _pump(tester, MoneyText(_btc012, isHiddenAmount: false), _hiddenBtc);
79
80 expect(find.text("0.12345678 BTC"), findsOneWidget);
81 expect(find.text(_hidden), findsNothing);
82 });
83
84 testWidgets("isHiddenAmount:null falls back to state.isHidden (hidden)", (tester) async {
85 await _pump(tester, MoneyText(_btc012), _hiddenBtc);
86
87 expect(find.text(_hidden), findsOneWidget);
88 });
89
90 testWidgets("isHiddenAmount:null falls back to state.isHidden (visible)", (tester) async {
91 await _pump(tester, MoneyText(_btc012), _visibleBtc);
92
93 expect(find.text("0.12345678 BTC"), findsOneWidget);
94 expect(find.text(_hidden), findsNothing);
95 });
96 });
97
98 group("symbol vs precision", () {
99 testWidgets("showSymbol:true appends the currency symbol", (tester) async {
100 await _pump(tester, MoneyText(_btc012), _visibleBtc);
101
102 expect(find.text("0.12345678 BTC"), findsOneWidget);
103 });
104
105 testWidgets("showSymbol:false omits the currency symbol", (tester) async {
106 await _pump(tester, MoneyText(_btc012, showSymbol: false), _visibleBtc);
107
108 expect(find.text("0.12345678"), findsOneWidget);
109 expect(find.text("0.12345678 BTC"), findsNothing);
110 });
111 });
112
113 group("withSymbolPrefix", () {
114 test("defaults to false on the constructor", () {
115 expect(MoneyText(_btc012).withSymbolPrefix, false);
116 });
117
118 test("defaults to false via optional", () {
119 expect((MoneyText.optional(_btc012) as MoneyText).withSymbolPrefix, false);
120 });
121
122 testWidgets("ignored when showSymbol is false", (tester) async {
123 await _pump(
124 tester,
125 MoneyText(_btc012, showSymbol: false, withSymbolPrefix: true),
126 _visibleBtc,
127 );
128
129 expect(find.text("0.12345678"), findsOneWidget);
130 expect(find.text("BTC 0.12345678"), findsNothing);
131 expect(find.text("0.12345678 BTC"), findsNothing);
132 });
133
134 testWidgets("prefixes the symbol before the amount", (tester) async {
135 await _pump(tester, MoneyText(_btc012, withSymbolPrefix: true), _visibleBtc);
136
137 expect(find.text("BTC 0.12345678"), findsOneWidget);
138 });
139
140 testWidgets("prefixed amount is still localized (en_US grouping)", (tester) async {
141 await _pump(tester, MoneyText(_btcGrouping, withSymbolPrefix: true), _visibleBtc);
142
143 expect(find.text("BTC 1,234.5"), findsOneWidget);
144 });
145
146 testWidgets("prefixed amount respects an explicit de_DE locale", (tester) async {
147 await _pump(
148 tester,
149 MoneyText(_btcGrouping, withSymbolPrefix: true, locale: const Locale("de", "DE")),
150 _visibleBtc,
151 );
152
153 expect(find.text("BTC 1.234,5"), findsOneWidget);
154 });
155
156 testWidgets("prefixes the base-unit ticker (sats)", (tester) async {
157 await _pump(
158 tester,
159 MoneyText(_btc012, withSymbolPrefix: true, useBaseUnit: true),
160 _visibleBtc,
161 );
162
163 expect(find.text("sats 12,345,678"), findsOneWidget);
164 });
165 });
166
167 group("base unit (useBaseUnit)", () {
168 testWidgets("explicit useBaseUnit:true renders sats", (tester) async {
169 await _pump(tester, MoneyText(_btc012, useBaseUnit: true), _visibleBtc);
170
171 // 12345678 sats, localized grouping applied by withLocalSeperator.
172 expect(find.text("12,345,678 sats"), findsOneWidget);
173 });
174
175 testWidgets("explicit useBaseUnit:false overrides a satoshi state", (tester) async {
176 await _pump(tester, MoneyText(_btc012, useBaseUnit: false), _sats);
177
178 expect(find.text("0.12345678 BTC"), findsOneWidget);
179 });
180
181 testWidgets("useBaseUnit:null -> state decides (BTC in satoshi mode -> sats)", (tester) async {
182 await _pump(tester, MoneyText(_btc012), _sats);
183
184 expect(find.text("12,345,678 sats"), findsOneWidget);
185 });
186
187 testWidgets("useBaseUnit:null -> state decides (BTC in bitcoin mode -> BTC)", (tester) async {
188 await _pump(tester, MoneyText(_btc012), _visibleBtc);
189
190 expect(find.text("0.12345678 BTC"), findsOneWidget);
191 });
192
193 testWidgets("non-BTC currency ignores satoshi mode", (tester) async {
194 await _pump(tester, MoneyText(_xmrHalf), _sats);
195
196 expect(find.text("0.5 XMR"), findsOneWidget);
197 });
198
199 testWidgets("LN-satoshi mode makes BTCLN use base unit", (tester) async {
200 await _pump(tester, MoneyText(_btclnAmount), _lnSats);
201
202 expect(find.text("12,345,678 sats"), findsOneWidget);
203 });
204
205 testWidgets("LN-satoshi mode leaves plain BTC untouched", (tester) async {
206 await _pump(tester, MoneyText(_btc012), _lnSats);
207
208 expect(find.text("0.12345678 BTC"), findsOneWidget);
209 });
210 });
211
212 group("fractionalDigits & trimZeros", () {
213 testWidgets("fractionalDigits truncates (does not round)", (tester) async {
214 await _pump(tester, MoneyText(_btc012, fractionalDigits: 2), _visibleBtc);
215
216 // 0.12345678 truncated to 2 digits -> 0.12 (NOT 0.13).
217 expect(find.text("0.12 BTC"), findsOneWidget);
218 });
219
220 testWidgets("trimZeros:true (default) removes trailing zeros", (tester) async {
221 await _pump(tester, MoneyText(_btcTrailing), _visibleBtc);
222
223 expect(find.text("0.12 BTC"), findsOneWidget);
224 });
225
226 testWidgets("trimZeros:false keeps trailing zeros", (tester) async {
227 await _pump(tester, MoneyText(_btcTrailing, trimZeros: false), _visibleBtc);
228
229 expect(find.text("0.12000000 BTC"), findsOneWidget);
230 });
231 });
232
233 group("locale & separators", () {
234 testWidgets('default locale (en_US) uses "," grouping and "." decimal', (tester) async {
235 await _pump(tester, MoneyText(_btcGrouping), _visibleBtc);
236
237 expect(find.text("1,234.5 BTC"), findsOneWidget);
238 });
239
240 testWidgets("explicit de_DE locale swaps grouping/decimal separators", (tester) async {
241 await _pump(
242 tester,
243 MoneyText(_btcGrouping, locale: const Locale("de", "DE")),
244 _visibleBtc,
245 );
246
247 expect(find.text("1.234,5 BTC"), findsOneWidget);
248 });
249 });
250
251 group("rebuild on cubit state change", () {
252 testWidgets("re-renders when the settings state changes", (tester) async {
253 final cubit = await _pump(tester, MoneyText(_btc012), _visibleBtc);
254 expect(find.text("0.12345678 BTC"), findsOneWidget);
255
256 cubit.setState(_hiddenBtc);
257 await tester.pump();
258
259 expect(find.text("0.12345678 BTC"), findsNothing);
260 expect(find.text(_hidden), findsOneWidget);
261 });
262 });
263
264 group("forwarding to the inner Text", () {
265 testWidgets("passes through Text properties incl. semanticsIdentifier", (tester) async {
266 await _pump(
267 tester,
268 MoneyText(
269 _btc012,
270 maxLines: 3,
271 textAlign: TextAlign.right,
272 overflow: TextOverflow.ellipsis,
273 style: const TextStyle(fontSize: 42),
274 semanticsIdentifier: "via-ctor",
275 ),
276 _visibleBtc,
277 );
278
279 final text = tester.widget<Text>(
280 find.descendant(of: find.byType(MoneyText), matching: find.byType(Text)),
281 );
282
283 expect(text.maxLines, 3);
284 expect(text.textAlign, TextAlign.right);
285 expect(text.overflow, TextOverflow.ellipsis);
286 expect(text.style?.fontSize, 42);
287 expect(text.semanticsIdentifier, "via-ctor");
288 });
289 });
290
291 group("MoneyText.optional", () {
292 test("returns SizedBox.shrink for a null amount", () {
293 final widget = MoneyText.optional(null);
294
295 expect(widget, isA<SizedBox>());
296 final box = widget as SizedBox;
297 expect(box.width, 0.0);
298 expect(box.height, 0.0);
299 });
300
301 test("returns a MoneyText for a non-null amount", () {
302 expect(MoneyText.optional(_btc012), isA<MoneyText>());
303 });
304
305 test("forwards money-specific parameters to MoneyText", () {
306 final widget = MoneyText.optional(
307 _btc012,
308 showSymbol: false,
309 fractionalDigits: 3,
310 trimZeros: false,
311 isHiddenAmount: true,
312 useBaseUnit: true,
313 withSymbolPrefix: true,
314 ) as MoneyText;
315
316 expect(widget.showSymbol, false);
317 expect(widget.fractionalDigits, 3);
318 expect(widget.trimZeros, false);
319 expect(widget.isHiddenAmount, true);
320 expect(widget.useBaseUnit, true);
321 expect(widget.withSymbolPrefix, true);
322 });
323
324 testWidgets("renders identically to a direct MoneyText", (tester) async {
325 await _pump(tester, MoneyText.optional(_btc012), _visibleBtc);
326
327 expect(find.byType(MoneyText), findsOneWidget);
328 expect(find.text("0.12345678 BTC"), findsOneWidget);
329 });
330
331 test(
332 "forwards semanticsIdentifier to MoneyText",
333 () {
334 final widget = MoneyText.optional(_btc012, semanticsIdentifier: "sid") as MoneyText;
335
336 expect(widget.semanticsIdentifier, "sid");
337 },
338 );
339 });
340 }