@@ -3310,7 +3310,7 @@ public function test_get_item_schema() {
33103310 $ response = rest_get_server ()->dispatch ( $ request );
33113311 $ data = $ response ->get_data ();
33123312 $ properties = $ data ['schema ' ]['properties ' ];
3313- $ this ->assertCount ( 17 , $ properties );
3313+ $ this ->assertCount ( 19 , $ properties );
33143314 $ this ->assertArrayHasKey ( 'id ' , $ properties );
33153315 $ this ->assertArrayHasKey ( 'author ' , $ properties );
33163316 $ this ->assertArrayHasKey ( 'author_avatar_urls ' , $ properties );
@@ -3326,6 +3326,8 @@ public function test_get_item_schema() {
33263326 $ this ->assertArrayHasKey ( 'meta ' , $ properties );
33273327 $ this ->assertArrayHasKey ( 'parent ' , $ properties );
33283328 $ this ->assertArrayHasKey ( 'post ' , $ properties );
3329+ $ this ->assertArrayHasKey ( 'reaction_emojis ' , $ properties );
3330+ $ this ->assertArrayHasKey ( 'reaction_summary ' , $ properties );
33293331 $ this ->assertArrayHasKey ( 'status ' , $ properties );
33303332 $ this ->assertArrayHasKey ( 'type ' , $ properties );
33313333
@@ -4080,7 +4082,11 @@ public function data_note_status_provider() {
40804082 /**
40814083 * Test children link for note comment type. Based on test_get_comment_with_children_link.
40824084 *
4085+ * Notes expose a `children` link that targets their reaction children
4086+ * (not nested notes), so embedded children resolve to reactions.
4087+ *
40834088 * @ticket 64152
4089+ * @ticket 63191
40844090 */
40854091 public function test_get_note_with_children_link () {
40864092 $ parent_comment_id = self ::factory ()->comment ->create (
@@ -4099,8 +4105,8 @@ public function test_get_note_with_children_link() {
40994105 'comment_parent ' => $ parent_comment_id ,
41004106 'comment_post_ID ' => self ::$ post_id ,
41014107 'user_id ' => self ::$ admin_id ,
4102- 'comment_type ' => 'note ' ,
4103- 'comment_content ' => 'First child note comment ' ,
4108+ 'comment_type ' => 'reaction ' ,
4109+ 'comment_content ' => 'heart ' ,
41044110 )
41054111 );
41064112
@@ -4131,7 +4137,7 @@ public function test_get_note_with_children_link() {
41314137
41324138 // Verify the href attribute contains the expected status and type parameters.
41334139 $ this ->assertStringContainsString ( 'status=all ' , $ children [0 ]['href ' ] );
4134- $ this ->assertStringContainsString ( 'type=note ' , $ children [0 ]['href ' ] );
4140+ $ this ->assertStringContainsString ( 'type=reaction ' , $ children [0 ]['href ' ] );
41354141 }
41364142
41374143 /**
@@ -4520,4 +4526,252 @@ public function test_create_reaction_requires_login() {
45204526 $ response = rest_get_server ()->dispatch ( $ request );
45214527 $ this ->assertErrorResponse ( 'rest_comment_login_required ' , $ response , 401 );
45224528 }
4529+
4530+ /**
4531+ * A hex-codepoint sequence (e.g. `1f44d` for 👍) is accepted as a
4532+ * reaction slug, supporting emojis outside the curated set.
4533+ *
4534+ * @ticket 63191
4535+ */
4536+ public function test_create_reaction_accepts_hex_codepoint_slug () {
4537+ wp_set_current_user ( self ::$ editor_id );
4538+
4539+ $ post_id = self ::factory ()->post ->create ();
4540+ $ note_id = self ::factory ()->comment ->create (
4541+ array (
4542+ 'comment_post_ID ' => $ post_id ,
4543+ 'comment_type ' => 'note ' ,
4544+ 'comment_approved ' => 1 ,
4545+ 'user_id ' => self ::$ editor_id ,
4546+ 'comment_content ' => 'Test note ' ,
4547+ )
4548+ );
4549+
4550+ $ request = new WP_REST_Request ( 'POST ' , '/wp/v2/comments ' );
4551+ $ request ->add_header ( 'Content-Type ' , 'application/json ' );
4552+ $ request ->set_body (
4553+ wp_json_encode (
4554+ array (
4555+ 'post ' => $ post_id ,
4556+ 'parent ' => $ note_id ,
4557+ 'content ' => '1f468-200d-1f4bb ' ,
4558+ 'type ' => 'reaction ' ,
4559+ 'author ' => self ::$ editor_id ,
4560+ )
4561+ )
4562+ );
4563+
4564+ $ response = rest_get_server ()->dispatch ( $ request );
4565+ $ this ->assertSame ( 201 , $ response ->get_status () );
4566+
4567+ $ new_comment = get_comment ( $ response ->get_data ()['id ' ] );
4568+ $ this ->assertSame ( '1f468-200d-1f4bb ' , $ new_comment ->comment_content );
4569+ }
4570+
4571+ /**
4572+ * Raw emoji bytes must be rejected — clients are expected to normalize
4573+ * to a curated slug or hex-codepoint sequence before submitting.
4574+ *
4575+ * @ticket 63191
4576+ */
4577+ public function test_create_reaction_rejects_raw_emoji () {
4578+ wp_set_current_user ( self ::$ editor_id );
4579+
4580+ $ post_id = self ::factory ()->post ->create ();
4581+ $ note_id = self ::factory ()->comment ->create (
4582+ array (
4583+ 'comment_post_ID ' => $ post_id ,
4584+ 'comment_type ' => 'note ' ,
4585+ 'comment_approved ' => 1 ,
4586+ 'user_id ' => self ::$ editor_id ,
4587+ 'comment_content ' => 'Test note ' ,
4588+ )
4589+ );
4590+
4591+ $ request = new WP_REST_Request ( 'POST ' , '/wp/v2/comments ' );
4592+ $ request ->add_header ( 'Content-Type ' , 'application/json ' );
4593+ $ request ->set_body (
4594+ wp_json_encode (
4595+ array (
4596+ 'post ' => $ post_id ,
4597+ 'parent ' => $ note_id ,
4598+ 'content ' => '👍 ' ,
4599+ 'type ' => 'reaction ' ,
4600+ 'author ' => self ::$ editor_id ,
4601+ )
4602+ )
4603+ );
4604+
4605+ $ response = rest_get_server ()->dispatch ( $ request );
4606+ $ this ->assertErrorResponse ( 'rest_reaction_invalid_emoji ' , $ response , 400 );
4607+ }
4608+
4609+ /**
4610+ * After trashing a reaction, the same user may re-add the same emoji
4611+ * to the same note. Trashed reactions are invisible and must not block
4612+ * re-adding.
4613+ *
4614+ * @ticket 63191
4615+ */
4616+ public function test_create_reaction_after_trashing_previous_one () {
4617+ wp_set_current_user ( self ::$ editor_id );
4618+
4619+ $ post_id = self ::factory ()->post ->create ();
4620+ $ note_id = self ::factory ()->comment ->create (
4621+ array (
4622+ 'comment_post_ID ' => $ post_id ,
4623+ 'comment_type ' => 'note ' ,
4624+ 'comment_approved ' => 1 ,
4625+ 'user_id ' => self ::$ editor_id ,
4626+ 'comment_content ' => 'Test note ' ,
4627+ )
4628+ );
4629+
4630+ // Existing reaction in trash should not block re-adding.
4631+ self ::factory ()->comment ->create (
4632+ array (
4633+ 'comment_post_ID ' => $ post_id ,
4634+ 'comment_type ' => 'reaction ' ,
4635+ 'comment_parent ' => $ note_id ,
4636+ 'comment_approved ' => 'trash ' ,
4637+ 'user_id ' => self ::$ editor_id ,
4638+ 'comment_content ' => 'heart ' ,
4639+ )
4640+ );
4641+
4642+ $ request = new WP_REST_Request ( 'POST ' , '/wp/v2/comments ' );
4643+ $ request ->add_header ( 'Content-Type ' , 'application/json ' );
4644+ $ request ->set_body (
4645+ wp_json_encode (
4646+ array (
4647+ 'post ' => $ post_id ,
4648+ 'parent ' => $ note_id ,
4649+ 'content ' => 'heart ' ,
4650+ 'type ' => 'reaction ' ,
4651+ 'author ' => self ::$ editor_id ,
4652+ )
4653+ )
4654+ );
4655+
4656+ $ response = rest_get_server ()->dispatch ( $ request );
4657+ $ this ->assertSame ( 201 , $ response ->get_status () );
4658+ }
4659+
4660+ /**
4661+ * The note response exposes a `reaction_summary` field aggregating
4662+ * counts per emoji slug, plus per-user `reacted` and `my_reaction_id`.
4663+ *
4664+ * @ticket 63191
4665+ */
4666+ public function test_note_response_includes_reaction_summary () {
4667+ wp_set_current_user ( self ::$ editor_id );
4668+
4669+ $ post_id = self ::factory ()->post ->create ();
4670+ $ note_id = self ::factory ()->comment ->create (
4671+ array (
4672+ 'comment_post_ID ' => $ post_id ,
4673+ 'comment_type ' => 'note ' ,
4674+ 'comment_approved ' => 1 ,
4675+ 'user_id ' => self ::$ editor_id ,
4676+ 'comment_content ' => 'Test note ' ,
4677+ )
4678+ );
4679+
4680+ $ heart_id = self ::factory ()->comment ->create (
4681+ array (
4682+ 'comment_post_ID ' => $ post_id ,
4683+ 'comment_type ' => 'reaction ' ,
4684+ 'comment_parent ' => $ note_id ,
4685+ 'comment_approved ' => 1 ,
4686+ 'user_id ' => self ::$ editor_id ,
4687+ 'comment_content ' => 'heart ' ,
4688+ )
4689+ );
4690+
4691+ self ::factory ()->comment ->create (
4692+ array (
4693+ 'comment_post_ID ' => $ post_id ,
4694+ 'comment_type ' => 'reaction ' ,
4695+ 'comment_parent ' => $ note_id ,
4696+ 'comment_approved ' => 1 ,
4697+ 'user_id ' => self ::$ subscriber_id ,
4698+ 'comment_content ' => 'rocket ' ,
4699+ )
4700+ );
4701+
4702+ $ request = new WP_REST_Request ( 'GET ' , '/wp/v2/comments/ ' . $ note_id );
4703+ $ request ->set_param ( 'context ' , 'edit ' );
4704+ $ response = rest_get_server ()->dispatch ( $ request );
4705+ $ data = $ response ->get_data ();
4706+
4707+ $ this ->assertArrayHasKey ( 'reaction_summary ' , $ data );
4708+ $ this ->assertArrayHasKey ( 'heart ' , $ data ['reaction_summary ' ] );
4709+ $ this ->assertSame ( 1 , $ data ['reaction_summary ' ]['heart ' ]['count ' ] );
4710+ $ this ->assertTrue ( $ data ['reaction_summary ' ]['heart ' ]['reacted ' ] );
4711+ $ this ->assertSame ( $ heart_id , $ data ['reaction_summary ' ]['heart ' ]['my_reaction_id ' ] );
4712+
4713+ $ this ->assertArrayHasKey ( 'rocket ' , $ data ['reaction_summary ' ] );
4714+ $ this ->assertSame ( 1 , $ data ['reaction_summary ' ]['rocket ' ]['count ' ] );
4715+ $ this ->assertFalse ( $ data ['reaction_summary ' ]['rocket ' ]['reacted ' ] );
4716+ $ this ->assertSame ( 0 , $ data ['reaction_summary ' ]['rocket ' ]['my_reaction_id ' ] );
4717+ }
4718+
4719+ /**
4720+ * Comment schema exposes the curated reaction emoji list so clients
4721+ * can discover which slugs the server accepts.
4722+ *
4723+ * @ticket 63191
4724+ */
4725+ public function test_comment_schema_exposes_reaction_emojis () {
4726+ $ request = new WP_REST_Request ( 'OPTIONS ' , '/wp/v2/comments ' );
4727+ $ response = rest_get_server ()->dispatch ( $ request );
4728+ $ schema = $ response ->get_data ()['schema ' ];
4729+
4730+ $ this ->assertArrayHasKey ( 'reaction_emojis ' , $ schema ['properties ' ] );
4731+ $ slugs = wp_list_pluck ( $ schema ['properties ' ]['reaction_emojis ' ]['default ' ], 'value ' );
4732+ $ this ->assertSame ( array ( 'heart ' , 'celebration ' , 'smile ' , 'eyes ' , 'rocket ' ), $ slugs );
4733+ }
4734+
4735+ /**
4736+ * The `children` link on a note response points at reaction children,
4737+ * not at notes — so embedded children resolve to reactions.
4738+ *
4739+ * @ticket 63191
4740+ */
4741+ public function test_note_children_link_targets_reactions () {
4742+ wp_set_current_user ( self ::$ editor_id );
4743+
4744+ $ post_id = self ::factory ()->post ->create ();
4745+ $ note_id = self ::factory ()->comment ->create (
4746+ array (
4747+ 'comment_post_ID ' => $ post_id ,
4748+ 'comment_type ' => 'note ' ,
4749+ 'comment_approved ' => 1 ,
4750+ 'user_id ' => self ::$ editor_id ,
4751+ 'comment_content ' => 'Test note ' ,
4752+ )
4753+ );
4754+
4755+ // Create a reaction child so the note exposes a children link.
4756+ self ::factory ()->comment ->create (
4757+ array (
4758+ 'comment_post_ID ' => $ post_id ,
4759+ 'comment_type ' => 'reaction ' ,
4760+ 'comment_parent ' => $ note_id ,
4761+ 'comment_approved ' => 1 ,
4762+ 'user_id ' => self ::$ editor_id ,
4763+ 'comment_content ' => 'heart ' ,
4764+ )
4765+ );
4766+
4767+ $ request = new WP_REST_Request ( 'GET ' , '/wp/v2/comments/ ' . $ note_id );
4768+ $ request ->set_param ( 'context ' , 'edit ' );
4769+ $ response = rest_get_server ()->dispatch ( $ request );
4770+ $ links = $ response ->get_links ();
4771+
4772+ $ this ->assertArrayHasKey ( 'children ' , $ links );
4773+ $ href = $ links ['children ' ][0 ]['href ' ];
4774+ $ this ->assertStringContainsString ( 'type=reaction ' , $ href );
4775+ $ this ->assertStringNotContainsString ( 'type=note ' , $ href );
4776+ }
45234777}
0 commit comments