@@ -669,12 +669,10 @@ public static function provideValidationOfOptions(): iterable
669669
670670 public function testInteractMutationsCarryThroughToExecute (): void
671671 {
672- // Supply neither the positional argument nor the --force flag.
673- // interact() populates both; bind() and validate() run afterwards, so
674- // execute() should see the mutated values fully bound and validated.
675672 $ command = new InteractFixtureCommand (new Commands ());
676673 $ command ->run ([], []);
677674
675+ $ this ->assertTrue ($ command ->isInteractive ());
678676 $ this ->assertSame (['name ' => 'from-interact ' ], $ command ->executedArguments );
679677 $ this ->assertTrue ($ command ->executedOptions ['force ' ]);
680678 }
@@ -684,6 +682,7 @@ public function testInteractIsSkippedWhenNoInteractionFlagIsPassed(): void
684682 $ command = new InteractFixtureCommand (new Commands ());
685683 $ command ->run ([], ['no-interaction ' => null ]);
686684
685+ $ this ->assertFalse ($ command ->isInteractive ());
687686 $ this ->assertSame (['name ' => 'anonymous ' ], $ command ->executedArguments );
688687 $ this ->assertFalse ($ command ->executedOptions ['force ' ]);
689688 }
@@ -693,6 +692,7 @@ public function testInteractIsSkippedWhenShortcutFlagIsPassed(): void
693692 $ command = new InteractFixtureCommand (new Commands ());
694693 $ command ->run ([], ['N ' => null ]);
695694
695+ $ this ->assertFalse ($ command ->isInteractive ());
696696 $ this ->assertSame (['name ' => 'anonymous ' ], $ command ->executedArguments );
697697 $ this ->assertFalse ($ command ->executedOptions ['force ' ]);
698698 }
@@ -701,8 +701,11 @@ public function testInteractIsSkippedWhenSetInteractiveFalseIsCalled(): void
701701 {
702702 $ command = new InteractFixtureCommand (new Commands ());
703703 $ command ->setInteractive (false );
704+ $ this ->assertFalse ($ command ->isInteractive ());
705+
704706 $ command ->run ([], []);
705707
708+ $ this ->assertFalse ($ command ->isInteractive ());
706709 $ this ->assertSame (['name ' => 'anonymous ' ], $ command ->executedArguments );
707710 $ this ->assertFalse ($ command ->executedOptions ['force ' ]);
708711 }
@@ -714,10 +717,41 @@ public function testSetInteractiveTrueOverridesNoInteractionFlag(): void
714717 $ command ->setInteractive (true );
715718 $ command ->run ([], ['no-interaction ' => null ]);
716719
720+ $ this ->assertTrue ($ command ->isInteractive ());
721+ $ this ->assertSame (['name ' => 'from-interact ' ], $ command ->executedArguments );
722+ $ this ->assertTrue ($ command ->executedOptions ['force ' ]);
723+ }
724+
725+ public function testNoInteractionFlagDoesNotLeakAcrossRuns (): void
726+ {
727+ $ command = new InteractFixtureCommand (new Commands ());
728+
729+ $ command ->run ([], ['no-interaction ' => null ]);
730+ $ this ->assertFalse ($ command ->isInteractive ());
731+ $ this ->assertSame (['name ' => 'anonymous ' ], $ command ->executedArguments );
732+ $ this ->assertFalse ($ command ->executedOptions ['force ' ]);
733+
734+ $ command ->run ([], []);
735+ $ this ->assertTrue ($ command ->isInteractive ());
717736 $ this ->assertSame (['name ' => 'from-interact ' ], $ command ->executedArguments );
718737 $ this ->assertTrue ($ command ->executedOptions ['force ' ]);
719738 }
720739
740+ public function testSetInteractiveCallPersistsAcrossRuns (): void
741+ {
742+ $ command = new InteractFixtureCommand (new Commands ());
743+ $ command ->setInteractive (false );
744+ $ this ->assertFalse ($ command ->isInteractive ());
745+
746+ $ command ->run ([], []);
747+ $ this ->assertFalse ($ command ->isInteractive ());
748+ $ this ->assertSame (['name ' => 'anonymous ' ], $ command ->executedArguments );
749+
750+ $ command ->run ([], []);
751+ $ this ->assertFalse ($ command ->isInteractive ());
752+ $ this ->assertSame (['name ' => 'anonymous ' ], $ command ->executedArguments );
753+ }
754+
721755 public function testIsInteractiveReflectsExplicitState (): void
722756 {
723757 $ command = new InteractFixtureCommand (new Commands ());
@@ -740,6 +774,7 @@ public function testNoInteractionCascadesToSubCommandsViaCall(): void
740774 $ exitCode = $ command ->run ([], ['no-interaction ' => null ]);
741775
742776 $ this ->assertSame (EXIT_SUCCESS , $ exitCode );
777+ $ this ->assertFalse ($ command ->isInteractive ());
743778 $ this ->assertFalse (InteractiveStateProbeCommand::$ interactCalled );
744779 $ this ->assertFalse (InteractiveStateProbeCommand::$ observedInteractive );
745780 }
@@ -751,6 +786,7 @@ public function testSubCommandStaysInteractiveWhenParentIsInteractive(): void
751786 $ exitCode = $ command ->run ([], []);
752787
753788 $ this ->assertSame (EXIT_SUCCESS , $ exitCode );
789+ $ this ->assertTrue ($ command ->isInteractive ());
754790 $ this ->assertTrue (InteractiveStateProbeCommand::$ interactCalled );
755791 $ this ->assertTrue (InteractiveStateProbeCommand::$ observedInteractive );
756792 }
@@ -764,6 +800,7 @@ public function testCallAllowsSubCommandInteractiveEvenWhenParentIsNonInteractiv
764800 $ exitCode = $ command ->run ([], []);
765801
766802 $ this ->assertSame (EXIT_SUCCESS , $ exitCode );
803+ $ this ->assertFalse ($ command ->isInteractive ());
767804 $ this ->assertTrue (InteractiveStateProbeCommand::$ interactCalled );
768805 $ this ->assertTrue (InteractiveStateProbeCommand::$ observedInteractive );
769806 }
@@ -777,16 +814,18 @@ public function testCallForcesSubCommandNonInteractiveEvenWhenParentIsInteractiv
777814 $ exitCode = $ command ->run ([], []);
778815
779816 $ this ->assertSame (EXIT_SUCCESS , $ exitCode );
817+ $ this ->assertTrue ($ command ->isInteractive ());
780818 $ this ->assertFalse (InteractiveStateProbeCommand::$ interactCalled );
781819 $ this ->assertFalse (InteractiveStateProbeCommand::$ observedInteractive );
782820 }
783821
822+ /**
823+ * Caller passes --no-interaction in the sub-command's options, but also
824+ * sets noInteractionOverride to false: the explicit parameter wins and
825+ * the inherited flag is stripped under both its long name and its shortcut.
826+ */
784827 public function testCallStripsInheritedNoInteractionWhenCallerAllowsInteraction (): void
785828 {
786- // Caller passes --no-interaction in the sub-command's options, but also
787- // sets noInteractionOverride to false: the explicit parameter wins and
788- // the inherited flag is stripped under both its long name and its shortcut.
789-
790829 $ command = new ParentCallsInteractFixtureCommand (new Commands ());
791830
792831 $ command ->childNoInteractionOverride = false ;
@@ -796,16 +835,18 @@ public function testCallStripsInheritedNoInteractionWhenCallerAllowsInteraction(
796835 $ exitCode = $ command ->run ([], []);
797836
798837 $ this ->assertSame (EXIT_SUCCESS , $ exitCode );
838+ $ this ->assertTrue ($ command ->isInteractive ());
799839 $ this ->assertTrue (InteractiveStateProbeCommand::$ interactCalled );
800840 $ this ->assertTrue (InteractiveStateProbeCommand::$ observedInteractive );
801841 }
802842
843+ /**
844+ * When $noInteractionOverride is true and the caller already supplied the flag,
845+ * the resolver must not touch the caller's entry. The child still sees a
846+ * non-interactive state.
847+ */
803848 public function testCallPreservesCallerFlagWhenForcingNonInteractive (): void
804849 {
805- // When $noInteractionOverride is true and the caller already supplied the flag,
806- // the resolver must not touch the caller's entry. The child still sees a
807- // non-interactive state.
808-
809850 $ command = new ParentCallsInteractFixtureCommand (new Commands ());
810851
811852 $ command ->childNoInteractionOverride = true ;
@@ -815,6 +856,7 @@ public function testCallPreservesCallerFlagWhenForcingNonInteractive(): void
815856 $ exitCode = $ command ->run ([], []);
816857
817858 $ this ->assertSame (EXIT_SUCCESS , $ exitCode );
859+ $ this ->assertTrue ($ command ->isInteractive ());
818860 $ this ->assertFalse (InteractiveStateProbeCommand::$ interactCalled );
819861 $ this ->assertFalse (InteractiveStateProbeCommand::$ observedInteractive );
820862 }
0 commit comments