11import { expect , test } from "@playwright/test" ;
2- import { createBlock , goalRateButton , goto } from "./helpers" ;
2+ import { createBlock , goalRateButton } from "./helpers" ;
33
44/**
55 * Reactor neighbour bonus (#94): a heat block's reactor row carries a farm
6- * layout chip. Picking 2×2 scales each reactor's heat output ×3 (Py's breeder
7- * reactor has neighbour_bonus 1 = +100% per adjacent working reactor), so the
8- * solved reactor count drops to a third — and the choice persists.
6+ * layout chip. Picking 2×2 gives each reactor 2 working neighbours → Py's
7+ * breeder reactor (neighbour_bonus 1 = +100% each) makes ×3 heat, so the solved
8+ * reactor count drops to a third — and the choice persists.
9+ *
10+ * Project-independent (#122): the breeder's ×3 bonus is a Py constant, but its
11+ * raw power (hence the absolute reactor count) differs between mod configs
12+ * (2 GW in vanilla Py, 10 GW in py-hard-mode). So assert the INVARIANT — the
13+ * ×3 layout bonus and a strictly lower count — with the base count read at
14+ * runtime, not hardcoded. The heat goal also has several producers in some
15+ * configs, so pick the breeder from the picker when one opens.
916 */
1017test ( "reactor row: picking a 2×2 farm scales heat ×3 and persists" , async ( { page } ) => {
1118 await createBlock ( page ) ;
@@ -23,32 +30,45 @@ test("reactor row: picking a 2×2 farm scales heat ×3 and persists", async ({ p
2330 await rateInput . press ( "Enter" ) ;
2431 await expect ( goalRateButton ( page ) ) . toHaveText ( "6 GW" ) ;
2532
26- // the goal is unmade — click its icon to add the producing recipe (a single
27- // candidate — the breeder- reactor heat recipe — is auto-added, no dialog)
33+ // click the goal icon to add a producer: one heat candidate auto-adds, several
34+ // open a picker — pick the breeder reactor either way
2835 await page . locator ( 'button[title^="click to add a recipe that makes this goal"]' ) . click ( ) ;
36+ // the picker opens asynchronously (multi-candidate configs); give it a beat to
37+ // appear, then pick the breeder. A single-candidate config auto-adds — no dialog.
38+ const heatPicker = page . getByRole ( "dialog" , { name : / R e c i p e s t h a t m a k e / } ) ;
39+ await heatPicker . waitFor ( { state : "visible" , timeout : 2000 } ) . catch ( ( ) => { } ) ;
40+ if ( await heatPicker . isVisible ( ) . catch ( ( ) => false ) ) {
41+ await heatPicker . getByRole ( "button" , { name : / B r e e d e r r e a c t o r h e a t / } ) . click ( ) ;
42+ await expect ( heatPicker ) . toBeHidden ( ) ;
43+ }
44+ // the reactor row is in the grid now
2945 await expect ( page . getByText ( "Breeder reactor heat" ) ) . toBeVisible ( ) ;
3046
31- // flat-rated (1×1): 6 GW ÷ 2 GW per reactor = 3 reactors, chip shows no bonus.
32- // The building count is now its own click-to-fix field (#121) beside the
33- // machine icon — unpinned here, so it carries the "click to fix" title.
47+ // flat-rated (1×1): some whole+fraction of reactors, chip shows no bonus. The
48+ // building count is its own click-to-fix field (#121) beside the machine icon.
3449 const machineChip = page . locator ( 'button[title="click to fix the building count"]' ) ;
35- await expect ( machineChip ) . toContainText ( "3" ) ;
50+ await expect ( machineChip ) . toBeVisible ( ) ;
51+ const baseCount = Number ( ( await machineChip . textContent ( ) ) ?. trim ( ) ) ;
52+ expect ( baseCount ) . toBeGreaterThan ( 0 ) ;
3653 const layoutChip = page . locator ( 'button[title^="reactor farm"]' ) ;
3754 await expect ( layoutChip ) . toContainText ( "1×1" ) ;
3855 await expect ( layoutChip ) . not . toContainText ( "heat" ) ;
3956
40- // pick a 2×2 farm: each reactor gains 2 neighbours → ×3 heat → 1 reactor
57+ // pick a 2×2 farm: each reactor gains 2 neighbours → ×3 heat → a third the count
4158 await layoutChip . click ( ) ;
4259 await page . getByRole ( "menuitem" , { name : / ^ 2 × 2 / } ) . click ( ) ;
4360 await expect ( layoutChip ) . toContainText ( "2×2" ) ;
4461 await expect ( layoutChip ) . toContainText ( "×3 heat" ) ;
45- await expect ( machineChip ) . toContainText ( "1" ) ;
62+ const bonusCount = Number ( ( await machineChip . textContent ( ) ) ?. trim ( ) ) ;
63+ expect ( bonusCount ) . toBeCloseTo ( baseCount / 3 , 2 ) ;
4664
4765 // outlive the editor's 700ms auto-save debounce, then reload: the layout
4866 // (and the rescaled count) persisted with the block doc
4967 await page . waitForTimeout ( 1200 ) ;
5068 await page . reload ( ) ;
5169 await expect ( layoutChip ) . toContainText ( "2×2" ) ;
5270 await expect ( layoutChip ) . toContainText ( "×3 heat" ) ;
53- await expect ( machineChip ) . toContainText ( "1" ) ;
71+ await expect ( machineChip ) . toBeVisible ( ) ;
72+ const persisted = Number ( ( await machineChip . textContent ( ) ) ?. trim ( ) ) ;
73+ expect ( persisted ) . toBeCloseTo ( bonusCount , 2 ) ;
5474} ) ;
0 commit comments