@@ -782,4 +782,120 @@ Serve on a #plate{}.
782782 } ) ;
783783 } ) ;
784784 } ) ;
785+
786+ // ============================================================================
787+ // variant option in getIngredientQuantities — step-number interpretation
788+ // ============================================================================
789+
790+ describe ( "variant step-index interpretation" , ( ) => {
791+ // recipeWithStepVariants steps (all-steps order):
792+ // 0: "Preheat..." — no variant (active everywhere)
793+ // 1: "[vegan] Use flax eggs" — vegan only
794+ // 2: "[*] Crack eggs" — default only
795+ // 3: "Mix flour and sugar" — no variant (active everywhere)
796+ //
797+ // Visible in "vegan" variant: [0, 1, 3] → indices 0, 1, 2
798+ // Visible in "*" variant: [0, 2, 3] → indices 0, 1, 2
799+
800+ it ( "without variant option, step index uses raw all-steps order (backward compat)" , ( ) => {
801+ const recipe = new Recipe ( recipeWithStepVariants ) ;
802+ // Step index 3 in all-steps = "Mix flour and sugar"
803+ const ingredients = recipe . getIngredientQuantities ( { step : 3 } ) ;
804+ const names = ingredients
805+ . filter ( ( i ) => i . usedAsPrimary )
806+ . map ( ( i ) => i . name ) ;
807+ expect ( names ) . toContain ( "flour" ) ;
808+ expect ( names ) . toContain ( "sugar" ) ;
809+ } ) ;
810+
811+ it ( "with variant='vegan', step 1 maps to the [vegan] step" , ( ) => {
812+ const recipe = new Recipe ( recipeWithStepVariants ) ;
813+ const ingredients = recipe . getIngredientQuantities ( {
814+ step : 1 ,
815+ variant : "vegan" ,
816+ choices : { variant : "vegan" } ,
817+ } ) ;
818+ const names = ingredients
819+ . filter ( ( i ) => i . usedAsPrimary )
820+ . map ( ( i ) => i . name ) ;
821+ expect ( names ) . toContain ( "flax eggs" ) ;
822+ expect ( names ) . not . toContain ( "eggs" ) ;
823+ } ) ;
824+
825+ it ( "with variant='*', step 1 maps to the [*] step" , ( ) => {
826+ const recipe = new Recipe ( recipeWithStepVariants ) ;
827+ const ingredients = recipe . getIngredientQuantities ( {
828+ step : 1 ,
829+ variant : "*" ,
830+ } ) ;
831+ const names = ingredients
832+ . filter ( ( i ) => i . usedAsPrimary )
833+ . map ( ( i ) => i . name ) ;
834+ expect ( names ) . toContain ( "eggs" ) ;
835+ expect ( names ) . not . toContain ( "flax eggs" ) ;
836+ } ) ;
837+
838+ it ( "with variant='vegan', step 2 maps to the last active step (Mix)" , ( ) => {
839+ const recipe = new Recipe ( recipeWithStepVariants ) ;
840+ // vegan-active steps: [Preheat(0), UseFlaxEggs(1), Mix(2)]
841+ const ingredients = recipe . getIngredientQuantities ( {
842+ step : 2 ,
843+ variant : "vegan" ,
844+ choices : { variant : "vegan" } ,
845+ } ) ;
846+ const names = ingredients
847+ . filter ( ( i ) => i . usedAsPrimary )
848+ . map ( ( i ) => i . name ) ;
849+ expect ( names ) . toContain ( "flour" ) ;
850+ expect ( names ) . toContain ( "sugar" ) ;
851+ } ) ;
852+
853+ it ( "out-of-bounds step index with variant returns no primary ingredients" , ( ) => {
854+ const recipe = new Recipe ( recipeWithStepVariants ) ;
855+ // vegan-active steps: 3 steps (indices 0-2); index 3 is out-of-bounds
856+ const ingredients = recipe . getIngredientQuantities ( {
857+ step : 3 ,
858+ variant : "vegan" ,
859+ choices : { variant : "vegan" } ,
860+ } ) ;
861+ const primaries = ingredients . filter ( ( i ) => i . usedAsPrimary ) ;
862+ expect ( primaries ) . toHaveLength ( 0 ) ;
863+ } ) ;
864+
865+ it ( "step object reference is unaffected by variant option" , ( ) => {
866+ const recipe = new Recipe ( recipeWithStepVariants ) ;
867+ const steps = recipe . sections [ 0 ] ! . content . filter (
868+ ( item ) : item is Step => item . type === "step" ,
869+ ) ;
870+ // Pass the [vegan] step object directly — variant option should not change behaviour
871+ const ingredients = recipe . getIngredientQuantities ( {
872+ step : steps [ 1 ] ! ,
873+ variant : "vegan" ,
874+ choices : { variant : "vegan" } ,
875+ } ) ;
876+ const names = ingredients
877+ . filter ( ( i ) => i . usedAsPrimary )
878+ . map ( ( i ) => i . name ) ;
879+ expect ( names ) . toContain ( "flax eggs" ) ;
880+ } ) ;
881+
882+ it ( "works correctly combined with section filter" , ( ) => {
883+ const recipe = new Recipe ( recipeWithSectionAndStepVariants ) ;
884+ // Section 0 "[vegan] Plant-based Additions":
885+ // all steps: [paneer step (vegetarian), tofu step (untagged)]
886+ // vegan-active steps: [tofu step] (paneer excluded as it's [vegetarian])
887+ // So step index 0 with variant="vegan" → tofu step
888+ const ingredients = recipe . getIngredientQuantities ( {
889+ section : 0 ,
890+ step : 0 ,
891+ variant : "vegan" ,
892+ choices : { variant : "vegan" } ,
893+ } ) ;
894+ const names = ingredients
895+ . filter ( ( i ) => i . usedAsPrimary )
896+ . map ( ( i ) => i . name ) ;
897+ expect ( names ) . toContain ( "tofu" ) ;
898+ expect ( names ) . not . toContain ( "paneer" ) ;
899+ } ) ;
900+ } ) ;
785901} ) ;
0 commit comments