diff options
Diffstat (limited to 'ui-desktop')
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 | } | ||