@@ -4560,6 +4560,198 @@ public function test_get_checkout_variables_with_discount_code_in_order(): void
45604560 unset($ _REQUEST ['discount_code ' ]);
45614561 }
45624562
4563+ // -------------------------------------------------------------------------
4564+ // template_id validation — field_to_rule_key mapping (PR #800 fix)
4565+ // -------------------------------------------------------------------------
4566+
4567+ /**
4568+ * Test get_validation_rules maps template_selection required to template_id rule.
4569+ *
4570+ * The template_selection signup field has force_attributes() { required: true },
4571+ * but its POST key is template_id (not template_selection). Before PR #800
4572+ * the required rule was applied to the wrong key.
4573+ */
4574+ public function test_get_validation_rules_maps_template_selection_required_to_template_id (): void {
4575+
4576+ $ checkout = Checkout::get_instance ();
4577+ $ checkout ->step = [
4578+ 'fields ' => [
4579+ ['id ' => 'template_selection ' , 'required ' => true ],
4580+ ],
4581+ ];
4582+ $ checkout ->steps = [];
4583+ $ checkout ->step_name = null ;
4584+
4585+ unset($ _REQUEST ['pre-flight ' ], $ _REQUEST ['checkout_form ' ]);
4586+
4587+ $ rules = $ checkout ->get_validation_rules ();
4588+
4589+ // template_id rule must include 'required' (mapped from template_selection)
4590+ $ this ->assertArrayHasKey ('template_id ' , $ rules );
4591+ $ this ->assertStringContainsString ('required ' , $ rules ['template_id ' ]);
4592+
4593+ // template_selection should NOT have its own rule entry
4594+ $ this ->assertArrayNotHasKey ('template_selection ' , $ rules );
4595+ }
4596+
4597+ /**
4598+ * Test get_validation_rules adds min:1 to template_id when required.
4599+ *
4600+ * Rakit's required rule accepts integer 0 as "present", so min:1 is
4601+ * needed to reject template_id=0 during checkout.
4602+ */
4603+ public function test_get_validation_rules_adds_min_1_to_template_id (): void {
4604+
4605+ $ checkout = Checkout::get_instance ();
4606+ $ checkout ->step = [
4607+ 'fields ' => [
4608+ ['id ' => 'template_selection ' , 'required ' => true ],
4609+ ],
4610+ ];
4611+ $ checkout ->steps = [];
4612+ $ checkout ->step_name = null ;
4613+
4614+ unset($ _REQUEST ['pre-flight ' ], $ _REQUEST ['checkout_form ' ]);
4615+
4616+ $ rules = $ checkout ->get_validation_rules ();
4617+
4618+ $ this ->assertStringContainsString ('min:1 ' , $ rules ['template_id ' ]);
4619+ }
4620+
4621+ /**
4622+ * Test validate rejects template_id=0 when template_selection is required.
4623+ *
4624+ * This is the core regression test: a checkout with a required template
4625+ * selection field must not allow template_id=0 through validation.
4626+ */
4627+ public function test_validate_rejects_template_id_zero_when_required (): void {
4628+
4629+ $ checkout = Checkout::get_instance ();
4630+ $ checkout ->step = [
4631+ 'fields ' => [
4632+ ['id ' => 'template_selection ' , 'required ' => true ],
4633+ ],
4634+ ];
4635+ $ checkout ->steps = [];
4636+ $ checkout ->step_name = null ;
4637+
4638+ $ this ->ensure_session ($ checkout );
4639+
4640+ $ _REQUEST ['template_id ' ] = 0 ;
4641+
4642+ unset($ _REQUEST ['pre-flight ' ], $ _REQUEST ['checkout_form ' ]);
4643+
4644+ $ rules = $ checkout ->get_validation_rules ();
4645+ $ result = $ checkout ->validate ($ rules );
4646+
4647+ $ this ->assertInstanceOf (\WP_Error::class, $ result , 'template_id=0 should fail validation when template_selection is required ' );
4648+
4649+ unset($ _REQUEST ['template_id ' ]);
4650+ }
4651+
4652+ /**
4653+ * Test validate accepts a valid non-zero template_id when required.
4654+ *
4655+ * Uses min:1 rule directly to verify a positive integer passes.
4656+ */
4657+ public function test_validate_accepts_positive_template_id (): void {
4658+
4659+ $ checkout = Checkout::get_instance ();
4660+ $ checkout ->step = ['fields ' => []];
4661+ $ checkout ->steps = [];
4662+ $ checkout ->step_name = null ;
4663+
4664+ $ this ->ensure_session ($ checkout );
4665+
4666+ $ _REQUEST ['template_id ' ] = 5 ;
4667+
4668+ // min:1 should pass for value 5
4669+ $ result = $ checkout ->validate (['template_id ' => 'integer|min:1 ' ]);
4670+
4671+ $ this ->assertTrue ($ result );
4672+
4673+ unset($ _REQUEST ['template_id ' ]);
4674+ }
4675+
4676+ /**
4677+ * Test get_validation_rules does NOT add min:1 to template_id when
4678+ * template_selection is absent (no template step on the form).
4679+ *
4680+ * This ensures admin/API paths that don't include a template_selection
4681+ * field are not affected by the checkout-specific guard.
4682+ */
4683+ public function test_get_validation_rules_no_min_1_without_template_selection_field (): void {
4684+
4685+ $ checkout = Checkout::get_instance ();
4686+ $ checkout ->step = [
4687+ 'fields ' => [
4688+ ['id ' => 'email_address ' , 'required ' => true ],
4689+ ],
4690+ ];
4691+ $ checkout ->steps = [];
4692+ $ checkout ->step_name = null ;
4693+
4694+ unset($ _REQUEST ['pre-flight ' ], $ _REQUEST ['checkout_form ' ]);
4695+
4696+ $ rules = $ checkout ->get_validation_rules ();
4697+
4698+ // template_id should still have its base rule but NOT min:1
4699+ $ this ->assertArrayHasKey ('template_id ' , $ rules );
4700+ $ this ->assertStringNotContainsString ('min:1 ' , $ rules ['template_id ' ]);
4701+ }
4702+
4703+ /**
4704+ * Test base template_id rule (integer|site_template) allows 0
4705+ * when no template_selection field is present.
4706+ *
4707+ * This confirms admin/network site creation can still use template_id=0.
4708+ */
4709+ public function test_validate_allows_template_id_zero_without_template_selection_field (): void {
4710+
4711+ $ checkout = Checkout::get_instance ();
4712+ $ checkout ->step = ['fields ' => []];
4713+ $ checkout ->steps = [];
4714+ $ checkout ->step_name = null ;
4715+
4716+ $ this ->ensure_session ($ checkout );
4717+
4718+ $ _REQUEST ['template_id ' ] = 0 ;
4719+
4720+ // Base rule without required or min:1
4721+ $ result = $ checkout ->validate (['template_id ' => 'integer|site_template ' ]);
4722+
4723+ $ this ->assertTrue ($ result , 'template_id=0 should pass with base rule (admin/network context) ' );
4724+
4725+ unset($ _REQUEST ['template_id ' ]);
4726+ }
4727+
4728+ /**
4729+ * Test that a non-template required field still maps to itself.
4730+ *
4731+ * Ensures the field_to_rule_key mapping only affects template_selection
4732+ * and does not break other required fields.
4733+ */
4734+ public function test_get_validation_rules_non_template_required_field_maps_to_itself (): void {
4735+
4736+ $ checkout = Checkout::get_instance ();
4737+ $ checkout ->step = [
4738+ 'fields ' => [
4739+ ['id ' => 'site_title ' , 'required ' => true ],
4740+ ],
4741+ ];
4742+ $ checkout ->steps = [];
4743+ $ checkout ->step_name = null ;
4744+
4745+ unset($ _REQUEST ['pre-flight ' ], $ _REQUEST ['checkout_form ' ]);
4746+
4747+ $ rules = $ checkout ->get_validation_rules ();
4748+
4749+ $ this ->assertArrayHasKey ('site_title ' , $ rules );
4750+ $ this ->assertStringContainsString ('required ' , $ rules ['site_title ' ]);
4751+ // min:1 should NOT be added to non-template fields
4752+ $ this ->assertStringNotContainsString ('min:1 ' , $ rules ['site_title ' ]);
4753+ }
4754+
45634755 // -------------------------------------------------------------------------
45644756 // Teardown
45654757 // -------------------------------------------------------------------------
0 commit comments