aboutsummaryrefslogtreecommitdiffstats
path: root/ui-desktop/src/test/java
diff options
context:
space:
mode:
authorKostya <mail@sartin.in>2026-09-10 11:55:50 +0300
committerKostya <mail@sartin.in>2026-09-10 11:55:50 +0300
commit1a8b75f96f0c6855581be3a38069f09eca37d1f5 (patch)
treede174d70987afa21fd0588c551c111e028ff020f /ui-desktop/src/test/java
parentf23c20c1c66b39fb1f49307e08e8593fa708e026 (diff)
downloadguess-market-master.tar.gz
guess-market-master.tar.xz
guess-market-master.zip
tests: cover the ex-2 loader, order book trading and desktop stateHEAD2.0master
Add tests for the behaviour introduced in the last few commits, so later changes to matching, settlement or the UI state can't quietly break it. On the service side, Ex2LoaderTest loads the ex-2 fixtures and checks that every invalid file is rejected for its own reason, including events with no market maker or two of them, and v1 files. The order book tests cover buys and sells that match or rest in the ladder, self-trade prevention, cash and shares reserved by resting orders, price bounds, risk rejections, and resolution cancelling resting orders and paying the holders. A simulation checks that LMSR settlement conserves money. LedgerMapperTest used to pin the unmapped seq and at fields to their defaults. Now that the mapper fills them from the entry id and time, assert the real values and add a case for a null time. On the desktop side, AppStateTest drives the real ServiceEngine through loading, event creation, LMSR and order book trading, and checks that the observable state updates. Smaller tests check chart timestamps, money and probability formatting, and that every animation settles on its final frame, which is what the UI shows when animations are off.
Diffstat (limited to 'ui-desktop/src/test/java')
-rw-r--r--ui-desktop/src/test/java/market/guess/ui/desktop/AnimTest.java70
-rw-r--r--ui-desktop/src/test/java/market/guess/ui/desktop/AppStateTest.java499
-rw-r--r--ui-desktop/src/test/java/market/guess/ui/desktop/ChartsTest.java32
-rw-r--r--ui-desktop/src/test/java/market/guess/ui/desktop/FormatTest.java29
4 files changed, 630 insertions, 0 deletions
diff --git a/ui-desktop/src/test/java/market/guess/ui/desktop/AnimTest.java b/ui-desktop/src/test/java/market/guess/ui/desktop/AnimTest.java
new file mode 100644
index 0000000..1a39ba1
--- /dev/null
+++ b/ui-desktop/src/test/java/market/guess/ui/desktop/AnimTest.java
@@ -0,0 +1,70 @@
1package market.guess.ui.desktop;
2
3import static org.junit.jupiter.api.Assertions.assertEquals;
4
5import market.guess.ui.desktop.components.graphic.GraphicHelper;
6import market.guess.ui.desktop.components.graphic.MarketChartGraphic;
7import market.guess.ui.desktop.components.graphic.Motion;
8import market.guess.ui.desktop.components.graphic.SparklineGraphic;
9import org.junit.jupiter.api.Test;
10
11/**
12 * The handoff requires "animations off" to show the frozen final frame, fully legible and never
13 * blank. Since every graphic renders as a function of progress, that contract is exactly: at
14 * progress 1, every driver lands on its designed end value.
15 */
16class AnimTest {
17 private static final double[] BAR1 = {0, .35, .30, 1, .60, .6, 1, .35};
18
19 @Test
20 void keyframesHoldTheirEndValue() {
21 assertEquals(.35, GraphicHelper.at(0, BAR1), 1e-9);
22 assertEquals(1.0, GraphicHelper.at(.30, BAR1), 1e-9);
23 assertEquals(.35, GraphicHelper.at(1, BAR1), 1e-9);
24 assertEquals(.675, GraphicHelper.at(.15, BAR1), 1e-9); // interpolates linearly between stops
25 assertEquals(.35, Motion.at(0, BAR1), 1e-9);
26 }
27
28 @Test
29 void ridersEndOnTheLastPointOfTheirPath() {
30 assertEquals(
31 MarketChartGraphic.YES_CURVE[6],
32 MarketChartGraphic.rideCurve(MarketChartGraphic.YES_CURVE, 1)[0],
33 1e-6);
34 assertEquals(
35 MarketChartGraphic.YES_CURVE[7],
36 MarketChartGraphic.rideCurve(MarketChartGraphic.YES_CURVE, 1)[1],
37 1e-6);
38
39 int last = SparklineGraphic.SPARK.length;
40 assertEquals(
41 SparklineGraphic.SPARK[last - 2],
42 SparklineGraphic.ridePolyline(SparklineGraphic.SPARK, 1)[0],
43 1e-6);
44 assertEquals(
45 SparklineGraphic.SPARK[last - 1],
46 SparklineGraphic.ridePolyline(SparklineGraphic.SPARK, 1)[1],
47 1e-6);
48 }
49
50 @Test
51 void ridersStartOnTheFirstPointOfTheirPath() {
52 assertEquals(
53 MarketChartGraphic.YES_CURVE[0],
54 MarketChartGraphic.rideCurve(MarketChartGraphic.YES_CURVE, 0)[0],
55 1e-6);
56 assertEquals(
57 MarketChartGraphic.YES_CURVE[1],
58 MarketChartGraphic.rideCurve(MarketChartGraphic.YES_CURVE, 0)[1],
59 1e-6);
60
61 assertEquals(
62 SparklineGraphic.SPARK[0],
63 SparklineGraphic.ridePolyline(SparklineGraphic.SPARK, 0)[0],
64 1e-6);
65 assertEquals(
66 SparklineGraphic.SPARK[1],
67 SparklineGraphic.ridePolyline(SparklineGraphic.SPARK, 0)[1],
68 1e-6);
69 }
70}
diff --git a/ui-desktop/src/test/java/market/guess/ui/desktop/AppStateTest.java b/ui-desktop/src/test/java/market/guess/ui/desktop/AppStateTest.java
new file mode 100644
index 0000000..dbb1762
--- /dev/null
+++ b/ui-desktop/src/test/java/market/guess/ui/desktop/AppStateTest.java
@@ -0,0 +1,499 @@
1package market.guess.ui.desktop;
2
3import static org.junit.jupiter.api.Assertions.assertEquals;
4import static org.junit.jupiter.api.Assertions.assertNotNull;
5import static org.junit.jupiter.api.Assertions.assertTrue;
6
7import java.math.BigDecimal;
8import java.nio.file.Path;
9import java.util.concurrent.atomic.AtomicBoolean;
10import market.guess.api.CatalogContext;
11import market.guess.model.event.CommissionTiming;
12import market.guess.model.event.CreateEventRequest;
13import market.guess.model.event.MechanismType;
14import market.guess.ui.desktop.components.AppTab;
15import market.guess.ui.desktop.components.ModalDialog;
16import org.junit.jupiter.api.BeforeEach;
17import org.junit.jupiter.api.Test;
18
19class AppStateTest {
20 private AppState state;
21 private CatalogContext catalog;
22 private ServiceEngine engine;
23
24 @BeforeEach
25 void setUp() {
26 engine = new ServiceEngine();
27 var path = Path.of("test-data/ex-2/small.xml");
28 var res = engine.getCatalogContext().loadEvents(path);
29 assertTrue(res.isSuccess(), "Loading small.xml should succeed");
30 catalog = engine.getCatalogContext();
31 state = new AppState(engine);
32 state.setLoadedFile(path.toString());
33 state.refreshData();
34 }
35
36 @Test
37 void reactivePropertiesNotifyListeners() {
38 AtomicBoolean skinChanged = new AtomicBoolean(false);
39 state.skinProperty().addListener((obs, oldVal, newVal) -> skinChanged.set(true));
40 state.setSkin(Skin.CATPPUCCIN);
41 assertTrue(skinChanged.get());
42 assertEquals(Skin.CATPPUCCIN, state.getSkin());
43
44 AtomicBoolean tabChanged = new AtomicBoolean(false);
45 state.activeTabProperty().addListener((obs, oldVal, newVal) -> tabChanged.set(true));
46 state.setActiveTab(AppTab.USERS);
47 assertTrue(tabChanged.get());
48 assertEquals(AppTab.USERS, state.getActiveTab());
49
50 AtomicBoolean dialogChanged = new AtomicBoolean(false);
51 state.dialogProperty().addListener((obs, oldVal, newVal) -> dialogChanged.set(true));
52 state.setDialog(ModalDialog.CREATE_EVENT);
53 assertTrue(dialogChanged.get());
54 assertEquals(ModalDialog.CREATE_EVENT, state.getDialog());
55 }
56
57 @Test
58 void selectedEventUpdatesWhenIdChanges() {
59 assertNotNull(state.getSelectedEvent());
60
61 // Add a new event
62 var req =
63 new CreateEventRequest(
64 "New Test Event",
65 "Test Description",
66 MechanismType.LMSR,
67 5,
68 CommissionTiming.ON_PURCHASE,
69 "Avrum",
70 150,
71 null,
72 java.util.List.of("YES", "NO"),
73 true);
74 var res = catalog.createEvent(req);
75 assertTrue(res.isSuccess(), "Create event should succeed: " + res.getMessage());
76 state.refreshData();
77
78 String newId = res.getData().summary().key();
79 state.setSelectedEventId(newId);
80 assertEquals(newId, state.getSelectedEvent().id);
81 assertEquals("New Test Event", state.getSelectedEvent().name);
82 }
83
84 @Test
85 void actingUserUpdatesWhenNameChanges() {
86 assertNotNull(state.getActingUser());
87 assertEquals("Avrum", state.getActingUserName());
88
89 state.setActingUserName("Menash");
90 assertNotNull(state.getActingUser());
91 assertEquals("Menash", state.getActingUser().name);
92 }
93
94 @Test
95 void createEventWithFullParameters() {
96 var req =
97 new CreateEventRequest(
98 "Summit 2026",
99 "Will the summit take place?",
100 MechanismType.ORDER_BOOK,
101 10,
102 CommissionTiming.ON_CLOSE,
103 "Avrum",
104 null,
105 new BigDecimal("2.50"),
106 java.util.List.of("YES", "NO"),
107 false);
108 var res = catalog.createEvent(req);
109 assertTrue(res.isSuccess(), "Create event should succeed: " + res.getMessage());
110 var detail = res.getData();
111 assertEquals("Summit 2026", detail.summary().name());
112 assertEquals(MechanismType.ORDER_BOOK, detail.summary().mechanism());
113 assertEquals(10, detail.summary().commissionPercent());
114 assertEquals(CommissionTiming.ON_CLOSE, detail.summary().commissionTiming());
115 }
116
117 @Test
118 void testServiceEngineAndXmlLoad() {
119 assertEquals(2, state.getEvents().size());
120 assertEquals(3, state.getUsers().size());
121 assertNotNull(state.getActingUser());
122 }
123
124 @Test
125 void testLmsrTradingAndUpdate() {
126 var openRes = catalog.openEvent("1");
127 assertTrue(openRes.isSuccess(), "Opening event 1 should succeed: " + openRes.getMessage());
128 state.refreshData();
129
130 var evBefore = state.event("1");
131 assertNotNull(evBefore);
132 System.out.println(
133 "Before trade: yesPrice=" + evBefore.yesPrice + ", noPrice=" + evBefore.noPrice);
134
135 var orderRes =
136 engine.getMarketContext().placeOrder("Avrum", "1", "YES", "BUY", evBefore.yesPrice, 20);
137 assertTrue(orderRes.isSuccess(), "Place order should succeed: " + orderRes.getMessage());
138
139 state.refreshData();
140 var evAfter = state.event("1");
141 assertEquals(new BigDecimal("0.55"), evAfter.yesPrice);
142 assertEquals(new BigDecimal("0.45"), evAfter.noPrice);
143 assertEquals(new BigDecimal("0.55"), state.getSelectedEvent().yesPrice);
144
145 System.out.println("=== LMSR EVENT AFTER TRADE ===");
146 System.out.println("evAfter.trades.size() = " + evAfter.trades.size());
147 for (var t : evAfter.trades) {
148 System.out.println(
149 " Trade: n="
150 + t.n()
151 + ", user="
152 + t.user()
153 + ", option="
154 + t.option()
155 + ", yes="
156 + t.yesOption()
157 + ", shares="
158 + t.shares()
159 + ", paid="
160 + t.paid());
161 }
162 System.out.println("evAfter.participants.size() = " + evAfter.participants.size());
163 for (var p : evAfter.participants) {
164 System.out.println(
165 " Participant: user="
166 + p.user()
167 + ", tag="
168 + p.tag()
169 + ", yes="
170 + p.yes()
171 + ", no="
172 + p.no()
173 + ", val="
174 + p.value()
175 + ", fees="
176 + p.fees());
177 }
178 var avrum = state.user("Avrum");
179 System.out.println("Avrum events size: " + avrum.events.size());
180 for (var ue : avrum.events) {
181 System.out.println(
182 " userEvent: name="
183 + ue.eventName()
184 + ", role="
185 + ue.role()
186 + ", yes="
187 + ue.yes()
188 + ", no="
189 + ue.no());
190 }
191 System.out.println("Avrum balanceHistory size: " + avrum.balanceHistory.size());
192 for (var cp : avrum.balanceHistory) {
193 System.out.println(" BalancePoint: x=" + cp.x() + ", y=" + cp.y());
194 }
195 assertTrue(avrum.balanceHistory.size() >= 2);
196 assertTrue(avrum.balanceHistory.get(0).x() > 1_000_000_000);
197
198 // Avrum sells 10 YES shares in LMSR
199 var sellRes = engine.getMarketContext().placeOrder("Avrum", "1", "YES", "SELL", null, 10);
200 assertTrue(
201 sellRes.isSuccess(), "Place LMSR sell order should succeed: " + sellRes.getMessage());
202 state.refreshData();
203
204 var evAfterSell = state.event("1");
205 var avrumParticipant =
206 evAfterSell.participants.stream()
207 .filter(p -> p.user().equals("Avrum"))
208 .findFirst()
209 .orElseThrow();
210 assertEquals(10, avrumParticipant.yes(), "Avrum should hold 10 YES shares after selling 10");
211 assertEquals(10, evAfterSell.yesShares, "Event circulating YES shares should be 10");
212 assertEquals(2, evAfterSell.trades.size(), "Event should have 2 trades recorded");
213
214 var avrumAfterSell = state.user("Avrum");
215 var avrumEv =
216 avrumAfterSell.events.stream()
217 .filter(e -> e.eventId().equals("1"))
218 .findFirst()
219 .orElseThrow();
220 assertEquals(10, avrumEv.yes(), "Avrum user event row should show 10 shares");
221 }
222
223 @Test
224 void testOrderBookTradingAndParticipants() {
225 var openRes = catalog.openEvent("2");
226 assertTrue(openRes.isSuccess());
227 state.refreshData();
228
229 var ev2 = state.event("2");
230 System.out.println("=== ORDER BOOK EVENT BEFORE TRADE ===");
231 System.out.println("ev2.participants.size() = " + ev2.participants.size());
232 for (var p : ev2.participants) {
233 System.out.println(
234 " Participant: user="
235 + p.user()
236 + ", tag="
237 + p.tag()
238 + ", yes="
239 + p.yes()
240 + ", no="
241 + p.no()
242 + ", val="
243 + p.value()
244 + ", fees="
245 + p.fees());
246 }
247
248 // Avrum (MM) quotes his minted YES shares; opening the event doesn't do it for him.
249 var quote =
250 engine
251 .getMarketContext()
252 .placeOrder("Avrum", "2", "YES", "SELL", new BigDecimal("0.50"), 100);
253 assertTrue(quote.isSuccess(), "Avrum quote should succeed: " + quote.getMessage());
254
255 // Menash buys 20 YES @ 0.50 on event 2
256 var res =
257 engine
258 .getMarketContext()
259 .placeOrder("Menash", "2", "YES", "BUY", new BigDecimal("0.50"), 20);
260 assertTrue(res.isSuccess(), "Menash place order should succeed: " + res.getMessage());
261 state.refreshData();
262
263 var ev2After = state.event("2");
264 System.out.println("=== ORDER BOOK EVENT AFTER TRADE ===");
265 System.out.println("ev2After.trades.size() = " + ev2After.trades.size());
266 for (var t : ev2After.trades) {
267 System.out.println(
268 " Trade: n="
269 + t.n()
270 + ", user="
271 + t.user()
272 + ", option="
273 + t.option()
274 + ", yes="
275 + t.yesOption()
276 + ", shares="
277 + t.shares()
278 + ", paid="
279 + t.paid());
280 }
281 System.out.println("ev2After.participants.size() = " + ev2After.participants.size());
282 for (var p : ev2After.participants) {
283 System.out.println(
284 " Participant: user="
285 + p.user()
286 + ", tag="
287 + p.tag()
288 + ", yes="
289 + p.yes()
290 + ", no="
291 + p.no()
292 + ", val="
293 + p.value()
294 + ", fees="
295 + p.fees());
296 }
297 var menash = state.user("Menash");
298 System.out.println("Menash events size: " + menash.events.size());
299 for (var ue : menash.events) {
300 System.out.println(
301 " Menash userEvent: name="
302 + ue.eventName()
303 + ", role="
304 + ue.role()
305 + ", yes="
306 + ue.yes()
307 + ", no="
308 + ue.no());
309 }
310
311 // Menash SELLS 10 YES @ 0.40 (better price than Avrum's 0.50)
312 var sellRes =
313 engine
314 .getMarketContext()
315 .placeOrder("Menash", "2", "YES", "SELL", new BigDecimal("0.40"), 10);
316 assertTrue(sellRes.isSuccess(), "Menash sell order should succeed: " + sellRes.getMessage());
317 state.refreshData();
318
319 // Tikva BUYS 10 YES @ 0.50 (crosses Menash's 0.40 ask!)
320 var tikvaBuy =
321 engine
322 .getMarketContext()
323 .placeOrder("Tikva", "2", "YES", "BUY", new BigDecimal("0.50"), 10);
324 assertTrue(tikvaBuy.isSuccess());
325 state.refreshData();
326
327 var ev2AfterSell = state.event("2");
328 System.out.println("=== ORDER BOOK EVENT AFTER SELL TRADE ===");
329 System.out.println("ev2AfterSell.trades.size() = " + ev2AfterSell.trades.size());
330 for (var t : ev2AfterSell.trades) {
331 System.out.println(
332 " Trade: n="
333 + t.n()
334 + ", user="
335 + t.user()
336 + ", option="
337 + t.option()
338 + ", yes="
339 + t.yesOption()
340 + ", shares="
341 + t.shares()
342 + ", paid="
343 + t.paid());
344 }
345 var domainEv2 = engine.getCatalogContext().getEvent("2");
346 System.out.println("Domain event 2 trades count: " + domainEv2.getData());
347 long mShares0 =
348 engine.getCatalogContext().getEvent("2").getData().participants().stream()
349 .filter(p -> p.user().equals("Menash"))
350 .findFirst()
351 .get()
352 .yes();
353 System.out.println("Menash YES shares in ParticipantDTO: " + mShares0);
354 System.out.println("ev2AfterSell.participants.size() = " + ev2AfterSell.participants.size());
355 for (var p : ev2AfterSell.participants) {
356 System.out.println(
357 " Participant: user="
358 + p.user()
359 + ", tag="
360 + p.tag()
361 + ", yes="
362 + p.yes()
363 + ", no="
364 + p.no()
365 + ", val="
366 + p.value()
367 + ", fees="
368 + p.fees());
369 }
370 menash = state.user("Menash");
371 for (var ue : menash.events) {
372 System.out.println(
373 " Menash userEvent after sell: name="
374 + ue.eventName()
375 + ", role="
376 + ue.role()
377 + ", yes="
378 + ue.yes()
379 + ", no="
380 + ue.no());
381 }
382
383 var menashP =
384 ev2AfterSell.participants.stream()
385 .filter(p -> p.user().equals("Menash"))
386 .findFirst()
387 .orElseThrow();
388 assertEquals(10, menashP.yes(), "Menash should have 10 YES shares after selling 10 of 20");
389 var tikvaP =
390 ev2AfterSell.participants.stream()
391 .filter(p -> p.user().equals("Tikva"))
392 .findFirst()
393 .orElseThrow();
394 assertEquals(10, tikvaP.yes(), "Tikva should have 10 YES shares after buying 10");
395 var avrumP =
396 ev2AfterSell.participants.stream()
397 .filter(p -> p.user().equals("Avrum"))
398 .findFirst()
399 .orElseThrow();
400 assertEquals(80, avrumP.yes(), "Avrum (MM) should have 80 YES shares");
401 assertEquals(2, ev2AfterSell.trades.size(), "Order book event should have 2 trades");
402 }
403
404 @Test
405 void testEventsTabControllerProgressBar() throws Exception {
406 try {
407 javafx.application.Platform.startup(() -> {});
408 } catch (IllegalStateException ignored) {
409 }
410
411 var loader = new javafx.fxml.FXMLLoader(getClass().getResource("controllers/events_tab.fxml"));
412 javafx.scene.Parent root = loader.load();
413 market.guess.ui.desktop.controllers.EventsTabController ctrl = loader.getController();
414
415 ctrl.init(state);
416 ctrl.refresh();
417
418 var field =
419 market.guess.ui.desktop.controllers.EventsTabController.class.getDeclaredField(
420 "lmsrYesProgressBar");
421 field.setAccessible(true);
422 javafx.scene.control.ProgressBar pb = (javafx.scene.control.ProgressBar) field.get(ctrl);
423
424 assertEquals(0.5, pb.getProgress(), 0.001);
425
426 // Switch to event 2 (Order Book)
427 state.setSelectedEventId("2");
428 // Switch back to event 1 (LMSR)
429 state.setSelectedEventId("1");
430 assertEquals(0.5, pb.getProgress(), 0.001);
431
432 // Open event 1
433 catalog.openEvent("1");
434 state.refreshData();
435 assertEquals(0.5, pb.getProgress(), 0.001);
436
437 // Trade 20 YES on event 1
438 var res =
439 engine
440 .getMarketContext()
441 .placeOrder("Avrum", "1", "YES", "BUY", state.event("1").yesPrice, 20);
442 assertTrue(res.isSuccess());
443 state.refreshData();
444 assertEquals(0.55, pb.getProgress(), 0.001);
445
446 root.getStyleClass().add("gm-root");
447 javafx.scene.Scene scene = new javafx.scene.Scene(root);
448 scene.getStylesheets().add(getClass().getResource("theme.css").toExternalForm());
449 root.applyCss();
450 root.layout();
451
452 javafx.scene.Node track = pb.lookup(".track");
453 javafx.scene.Node bar = pb.lookup(".bar");
454 assertNotNull(track);
455 assertNotNull(bar);
456 if (bar instanceof javafx.scene.layout.Region rBar) {
457 assertEquals(0.0, rBar.getPadding().getTop(), 0.001);
458 assertEquals(0.0, rBar.getPadding().getBottom(), 0.001);
459 assertNotNull(rBar.getBackground());
460 assertEquals(1, rBar.getBackground().getFills().size());
461 assertEquals(0.0, rBar.getBackground().getFills().get(0).getInsets().getTop(), 0.001);
462 }
463 if (track instanceof javafx.scene.layout.Region rTrack) {
464 assertEquals(0.0, rTrack.getPadding().getTop(), 0.001);
465 assertEquals(0.0, rTrack.getPadding().getBottom(), 0.001);
466 assertNotNull(rTrack.getBackground());
467 }
468 }
469
470 @Test
471 void testEventDataAndUserDataEquals() {
472 var ev1 = state.event("1");
473 assertNotNull(ev1);
474 var ev1Copy =
475 new market.guess.ui.desktop.model.EventData(
476 ev1.id,
477 ev1.num,
478 ev1.name,
479 ev1.desc,
480 ev1.type,
481 ev1.status,
482 ev1.mm,
483 ev1.feePercent,
484 ev1.feeMode,
485 ev1.contract.doubleValue(),
486 ev1.liquidityB,
487 ev1.baseValueD);
488 assertEquals(ev1, ev1Copy);
489 assertEquals(ev1.hashCode(), ev1Copy.hashCode());
490
491 var u1 = state.user("Avrum");
492 assertNotNull(u1);
493 var u1Copy =
494 new market.guess.ui.desktop.model.UserData(
495 "Avrum", u1.role, u1.balance.doubleValue(), u1.blocked, u1.isMm);
496 assertEquals(u1, u1Copy);
497 assertEquals(u1.hashCode(), u1Copy.hashCode());
498 }
499}
diff --git a/ui-desktop/src/test/java/market/guess/ui/desktop/ChartsTest.java b/ui-desktop/src/test/java/market/guess/ui/desktop/ChartsTest.java
new file mode 100644
index 0000000..c5cb280
--- /dev/null
+++ b/ui-desktop/src/test/java/market/guess/ui/desktop/ChartsTest.java
@@ -0,0 +1,32 @@
1package market.guess.ui.desktop;
2
3import static org.junit.jupiter.api.Assertions.assertEquals;
4import static org.junit.jupiter.api.Assertions.assertTrue;
5
6import market.guess.ui.desktop.util.Charts;
7import org.junit.jupiter.api.Test;
8
9class ChartsTest {
10
11 @Test
12 void testToEpochSecondFromIndex() {
13 double t0 = Charts.toEpochSecond(0);
14 double t1 = Charts.toEpochSecond(1);
15 double t2 = Charts.toEpochSecond(2);
16
17 assertEquals(Charts.BASE_TIME, t0, 1e-6);
18 assertEquals(Charts.BASE_TIME + 1800, t1, 1e-6);
19 assertEquals(Charts.BASE_TIME + 3600, t2, 1e-6);
20 }
21
22 @Test
23 void testToEpochSecondIdempotentForTimestamps() {
24 long epoch = 1_700_000_000L;
25 assertEquals(epoch, Charts.toEpochSecond(epoch), 1e-6);
26 }
27
28 @Test
29 void testBaseTimeIsReasonable() {
30 assertTrue(Charts.BASE_TIME > 1_700_000_000L);
31 }
32}
diff --git a/ui-desktop/src/test/java/market/guess/ui/desktop/FormatTest.java b/ui-desktop/src/test/java/market/guess/ui/desktop/FormatTest.java
new file mode 100644
index 0000000..956a322
--- /dev/null
+++ b/ui-desktop/src/test/java/market/guess/ui/desktop/FormatTest.java
@@ -0,0 +1,29 @@
1package market.guess.ui.desktop;
2
3import static org.junit.jupiter.api.Assertions.assertEquals;
4
5import java.math.BigDecimal;
6import market.guess.ui.desktop.util.Format;
7import org.junit.jupiter.api.Test;
8
9class FormatTest {
10
11 @Test
12 void testMoneyFormatting() {
13 assertEquals("$0.00", Format.money((BigDecimal) null));
14 assertEquals("$0.00", Format.money(BigDecimal.ZERO));
15 assertEquals("$0.50", Format.money(new BigDecimal("0.50")));
16 assertEquals("$123.45", Format.money(new BigDecimal("123.45")));
17 assertEquals("-$5.00", Format.money(new BigDecimal("-5.00")));
18 assertEquals("$100.00", Format.money(100.0));
19 assertEquals("-$2.50", Format.money(-2.50));
20 }
21
22 @Test
23 void testProbFormatting() {
24 assertEquals("0.50", Format.prob(0.50));
25 assertEquals("0.75", Format.prob(0.751));
26 assertEquals("0.00", Format.prob(0.00));
27 assertEquals("1.00", Format.prob(1.00));
28 }
29}