11import { DatabaseSync } from "node:sqlite" ;
2- import { expect , test } from "@playwright/test" ;
2+ import { expect , type Page , test } from "@playwright/test" ;
33import { activeProjectDbFile , goto , toast , undoButton , uniqueName } from "./helpers" ;
44
5+ async function dismissDataDriftPrompt ( page : Page ) {
6+ const ignore = page . getByRole ( "button" , { name : "Ignore for now" } ) ;
7+ try {
8+ await expect ( ignore ) . toBeVisible ( { timeout : 2_000 } ) ;
9+ await ignore . click ( ) ;
10+ } catch {
11+ // Most seeded runs match the installed mods and have no prompt.
12+ }
13+ }
14+
515test ( "what-if target stays editable while the factory re-solves" , async ( { page } ) => {
616 await goto ( page , "/factory/scenario" ) ;
17+ await dismissDataDriftPrompt ( page ) ;
718
819 const firstDemand = page . getByRole ( "spinbutton" ) . first ( ) ;
920 await expect ( firstDemand ) . toBeVisible ( ) ;
@@ -22,8 +33,8 @@ test("what-if target stays editable while the factory re-solves", async ({ page
2233} ) ;
2334
2435/**
25- * What-if "Apply all" (whole-factory re-balance) : overriding a final product's
26- * target surfaces the per-block changes, and Apply all commits every one of them
36+ * Applying a Scenario target : overriding a final product's target surfaces the
37+ * per-block changes, and Apply scenario commits every one of them
2738 * in a single undoable step. Drives the real flow end-to-end against the isolated
2839 * mut server, then undoes the factory-wide change so the shared scratch db is left
2940 * as it was.
@@ -32,11 +43,12 @@ test("what-if target stays editable while the factory re-solves", async ({ page
3243 * floor (rounding), so the test forces a genuine change by DOUBLING a demand — far
3344 * above the floor — rather than depending on the seed happening to be imbalanced.
3445 */
35- test ( "what-if 'apply all' re-balances the factory in one undoable step" , async ( { page } ) => {
46+ test ( "applying a scenario re-balances the factory in one undoable step" , async ( { page } ) => {
3647 // applying + undoing a whole-factory re-balance re-solves every touched block, so
3748 // give the round trips generous room over the default per-test budget
3849 test . setTimeout ( 120_000 ) ;
3950 await goto ( page , "/factory/scenario" ) ;
51+ await dismissDataDriftPrompt ( page ) ;
4052
4153 // the Final products card is the only place with numeric (spinbutton) inputs
4254 const firstDemand = page . getByRole ( "spinbutton" ) . first ( ) ;
@@ -45,13 +57,13 @@ test("what-if 'apply all' re-balances the factory in one undoable step", async (
4557 // 2× + 1 guarantees a real increase even if the current target reads 0
4658 await firstDemand . fill ( String ( current * 2 + 1 ) ) ;
4759
48- // the doubled demand cascades into real per-block changes → Apply all enables
49- const applyAll = page . getByRole ( "button" , { name : "Apply all " } ) ;
60+ // the doubled demand cascades into real per-block changes → Apply scenario enables
61+ const applyAll = page . getByRole ( "button" , { name : "Apply scenario " } ) ;
5062 await expect ( applyAll ) . toBeEnabled ( { timeout : 15_000 } ) ;
5163 await applyAll . click ( ) ;
5264
5365 // confirm dialog summarizes the change; commit it
54- const dialog = page . getByRole ( "dialog" , { name : / R e - b a l a n c e t h e w h o l e f a c t o r y / } ) ;
66+ const dialog = page . getByRole ( "dialog" , { name : / A p p l y t h i s f a c t o r y s c e n a r i o / } ) ;
5567 await expect ( dialog ) . toBeVisible ( ) ;
5668 await dialog . getByRole ( "button" , { name : / ^ A p p l y \d + c h a n g e / } ) . click ( ) ;
5769 // the apply iterates the solve to convergence across every touched block, so the
@@ -72,14 +84,68 @@ test("what-if 'apply all' re-balances the factory in one undoable step", async (
7284 await expect ( undoButton ( page ) ) . toBeEnabled ( ) ;
7385 await undoButton ( page ) . click ( ) ;
7486 await expect ( toast ( page , / U n d i d : R e - b a l a n c e f a c t o r y / ) ) . toBeVisible ( { timeout : 30_000 } ) ;
87+ } ) ;
88+
89+ test ( "Scenario can start a valid zero-rate producer" , async ( { page } ) => {
90+ test . setTimeout ( 120_000 ) ;
91+ // Tar's current byproducts can eventually cover all Coke demand. Disable that
92+ // competing incidental source in the scratch project so this test isolates
93+ // the idle configured Coke producer rather than supply substitution.
94+ const setup = new DatabaseSync ( activeProjectDbFile ( ) ) ;
95+ const tarEnabled = ( setup . prepare ( "SELECT enabled FROM blocks WHERE id = 38" ) . get ( ) as {
96+ enabled : number ;
97+ } ) . enabled ;
98+ try {
99+ setup . prepare ( "UPDATE blocks SET enabled = 0 WHERE id = 38" ) . run ( ) ;
100+ } finally {
101+ setup . close ( ) ;
102+ }
75103
104+ try {
105+ await goto ( page , "/factory/scenario" ) ;
106+ await dismissDataDriftPrompt ( page ) ;
107+
108+ const change = page . getByRole ( "link" , { name : / ^ C o k e / } ) ;
109+ await expect ( change ) . toContainText ( "0" ) ;
110+ await expect ( change ) . toContainText ( "start" ) ;
111+
112+ await page . getByRole ( "button" , { name : "Balance factory" , exact : true } ) . click ( ) ;
113+ const dialog = page . getByRole ( "dialog" , { name : / B a l a n c e t h e w h o l e f a c t o r y / } ) ;
114+ await dialog . getByRole ( "button" , { name : / ^ A p p l y \d + c h a n g e / } ) . click ( ) ;
115+ await expect ( dialog ) . toBeHidden ( { timeout : 45_000 } ) ;
116+ await expect ( undoButton ( page ) ) . toHaveAccessibleName ( / U n d o : R e - b a l a n c e f a c t o r y / , {
117+ timeout : 15_000 ,
118+ } ) ;
119+
120+ const saved = new DatabaseSync ( activeProjectDbFile ( ) , { readOnly : true } ) ;
121+ try {
122+ const row = saved . prepare ( "SELECT data FROM blocks WHERE name = 'Coke'" ) . get ( ) as {
123+ data : string ;
124+ } ;
125+ const doc = JSON . parse ( row . data ) as { goals : { name : string ; rate : number } [ ] } ;
126+ expect ( doc . goals . find ( ( goal ) => goal . name === "coke" ) ?. rate ) . toBeGreaterThan ( 0 ) ;
127+ } finally {
128+ saved . close ( ) ;
129+ }
130+
131+ await undoButton ( page ) . click ( ) ;
132+ await expect ( toast ( page , / U n d i d : R e - b a l a n c e f a c t o r y / ) ) . toBeVisible ( { timeout : 30_000 } ) ;
133+ } finally {
134+ const restore = new DatabaseSync ( activeProjectDbFile ( ) ) ;
135+ try {
136+ restore . prepare ( "UPDATE blocks SET enabled = ? WHERE id = 38" ) . run ( tarEnabled ) ;
137+ } finally {
138+ restore . close ( ) ;
139+ }
140+ }
76141} ) ;
77142
78143test ( "Scenario applies a secondary consume goal independently" , async ( { page } ) => {
79144 test . setTimeout ( 120_000 ) ;
80145 // Resolve the copied project's existing projections before inserting the two
81146 // self-contained test blocks and their cached factory boundary flows.
82147 await goto ( page , "/factory/scenario" ) ;
148+ await dismissDataDriftPrompt ( page ) ;
83149
84150 const sourceName = uniqueName ( "Acetaldehyde surplus" ) ;
85151 const sinkName = uniqueName ( "Secondary sink" ) ;
@@ -120,12 +186,12 @@ test("Scenario applies a secondary consume goal independently", async ({ page })
120186 }
121187
122188 await goto ( page , "/factory/scenario" ) ;
123- const change = page . getByRole ( "link" , { name : / ^ A c e t a l d e h y d e / } ) ;
189+ const change = page . getByRole ( "link" , { name : / ^ A c e t a l d e h y d e - 2 / } ) ;
124190 await expect ( change ) . toContainText ( "-2" ) ;
125191 await expect ( change ) . toContainText ( "-10" ) ;
126192
127- await page . getByRole ( "button" , { name : "Apply all" } ) . click ( ) ;
128- const dialog = page . getByRole ( "dialog" , { name : / R e - b a l a n c e t h e w h o l e f a c t o r y / } ) ;
193+ await page . getByRole ( "button" , { name : "Balance factory" , exact : true } ) . click ( ) ;
194+ const dialog = page . getByRole ( "dialog" , { name : / B a l a n c e t h e w h o l e f a c t o r y / } ) ;
129195 await dialog . getByRole ( "button" , { name : / ^ A p p l y \d + c h a n g e / } ) . click ( ) ;
130196 await expect ( dialog ) . toBeHidden ( { timeout : 45_000 } ) ;
131197 await expect ( toast ( page , / R e - b a l a n c e d \d + b l o c k / ) ) . toBeVisible ( { timeout : 15_000 } ) ;
0 commit comments