Skip to content

Commit b6923e7

Browse files
REST API: Test expanded reaction support in comments controller.
Add tests covering the updated reaction validation, summary, and schema behaviors: - Accept hex-codepoint emoji slugs (e.g. 1f468-200d-1f4bb). - Reject raw emoji bytes. - Allow re-adding a reaction after the previous one is trashed. - reaction_summary aggregates per-emoji counts with the current user's reacted state and reaction ID. - reaction_emojis schema property exposes the curated slug list. - A note's children link targets reactions, not nested notes. Update test_get_item_schema for the two new schema properties, and test_get_note_with_children_link to expect type=reaction for note children. See #63191.
1 parent 8fec4c9 commit b6923e7

1 file changed

Lines changed: 258 additions & 4 deletions

File tree

tests/phpunit/tests/rest-api/rest-comments-controller.php

Lines changed: 258 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)