diff options
| author | Kostya <mail@sartin.in> | 2026-09-10 11:55:50 +0300 |
|---|---|---|
| committer | Kostya <mail@sartin.in> | 2026-09-10 11:55:50 +0300 |
| commit | 1a8b75f96f0c6855581be3a38069f09eca37d1f5 (patch) | |
| tree | de174d70987afa21fd0588c551c111e028ff020f /ui-desktop/src/test/java/market/guess/ui | |
| parent | f23c20c1c66b39fb1f49307e08e8593fa708e026 (diff) | |
| download | guess-market-master.tar.gz guess-market-master.tar.xz guess-market-master.zip | |
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/market/guess/ui')
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 @@ | |||
| 1 | package market.guess.ui.desktop; | ||
| 2 | |||
| 3 | import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| 4 | |||
| 5 | import market.guess.ui.desktop.components.graphic.GraphicHelper; | ||
| 6 | import market.guess.ui.desktop.components.graphic.MarketChartGraphic; | ||
| 7 | import market.guess.ui.desktop.components.graphic.Motion; | ||
| 8 | import market.guess.ui.desktop.components.graphic.SparklineGraphic; | ||
| 9 | import 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 | */ | ||
| 16 | class 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 @@ | |||
| 1 | package market.guess.ui.desktop; | ||
| 2 | |||
| 3 | import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| 4 | import static org.junit.jupiter.api.Assertions.assertNotNull; | ||
| 5 | import static org.junit.jupiter.api.Assertions.assertTrue; | ||
| 6 | |||
| 7 | import java.math.BigDecimal; | ||
| 8 | import java.nio.file.Path; | ||
| 9 | import java.util.concurrent.atomic.AtomicBoolean; | ||
| 10 | import market.guess.api.CatalogContext; | ||
| 11 | import market.guess.model.event.CommissionTiming; | ||
| 12 | import market.guess.model.event.CreateEventRequest; | ||
| 13 | import market.guess.model.event.MechanismType; | ||
| 14 | import market.guess.ui.desktop.components.AppTab; | ||
| 15 | import market.guess.ui.desktop.components.ModalDialog; | ||
| 16 | import org.junit.jupiter.api.BeforeEach; | ||
| 17 | import org.junit.jupiter.api.Test; | ||
| 18 | |||
| 19 | class 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 @@ | |||
| 1 | package market.guess.ui.desktop; | ||
| 2 | |||
| 3 | import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| 4 | import static org.junit.jupiter.api.Assertions.assertTrue; | ||
| 5 | |||
| 6 | import market.guess.ui.desktop.util.Charts; | ||
| 7 | import org.junit.jupiter.api.Test; | ||
| 8 | |||
| 9 | class 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 @@ | |||
| 1 | package market.guess.ui.desktop; | ||
| 2 | |||
| 3 | import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| 4 | |||
| 5 | import java.math.BigDecimal; | ||
| 6 | import market.guess.ui.desktop.util.Format; | ||
| 7 | import org.junit.jupiter.api.Test; | ||
| 8 | |||
| 9 | class 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 | } | ||