Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,18 @@
"flarum/flags": "*"
},
"scripts": {
"test": [
"@php -r \"echo 'Available test commands:'.PHP_EOL.' composer test:setup'.PHP_EOL.' composer test:integration'.PHP_EOL;\""
],
"test:setup": "@php tests/integration/setup.php",
"test:integration": "phpunit -c tests/phpunit.integration.xml",
"analyse:phpstan": "phpstan analyse",
"clear-cache:phpstan": "phpstan clear-result-cache"
},
"scripts-descriptions": {
"test": "Show available test commands",
"test:setup": "Set up integration test environment",
"test:integration": "Run integration tests",
"analyse:phpstan": "Run static analysis"
},
"minimum-stability": "beta",
Expand Down
12 changes: 12 additions & 0 deletions src/Api/Commands/SplitDiscussionHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
namespace FoF\Split\Api\Commands;

use Flarum\Discussion\Discussion;
use Flarum\Discussion\UserState;
use Flarum\Extension\ExtensionManager;
use Flarum\Post\Post;
use Flarum\Post\PostRepository;
Expand Down Expand Up @@ -83,6 +84,9 @@ public function handle(SplitDiscussion $command)
new DiscussionWasSplit($command->actor, $affectedPosts, $originalDiscussion, $discussion)
);

$originalDiscussion = $this->refreshDiscussion($originalDiscussion);
$this->clampReadStates($originalDiscussion);

return $discussion;
}

Expand Down Expand Up @@ -156,6 +160,14 @@ protected function refreshDiscussion(Discussion $discussion)
return Discussion::find($discussion->id);
}

protected function clampReadStates(Discussion $discussion): void
{
UserState::query()
->where('discussion_id', $discussion->id)
->where('last_read_post_number', '>', $discussion->last_post_number)
->update(['last_read_post_number' => $discussion->last_post_number]);
}

/**
* Sets the tags for the new discussion based on the old one.
*
Expand Down
165 changes: 165 additions & 0 deletions tests/integration/api/SplitReadStateTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
<?php

namespace FoF\Split\Tests\integration\api;

use Flarum\Discussion\Discussion;
use Flarum\Discussion\UserState;
use Flarum\Post\Post;
use Flarum\Testing\integration\TestCase;
use PHPUnit\Framework\Attributes\Test;

class SplitReadStateTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();

$this->extension('fof-split');

$this->prepareDatabase([
'users' => [
[
'id' => 1,
'username' => 'admin',
'email' => 'admin@example.test',
'is_email_confirmed' => true,
'password' => 'password',
'joined_at' => '2026-01-01 00:00:00',
],
[
'id' => 2,
'username' => 'moderator',
'email' => 'moderator@example.test',
'is_email_confirmed' => true,
'password' => 'password',
'joined_at' => '2026-01-01 00:00:00',
],
],
'group_user' => [
['user_id' => 1, 'group_id' => 1],
['user_id' => 2, 'group_id' => 4],
],
'discussions' => [
[
'id' => 1,
'title' => 'Source Discussion',
'slug' => 'source-discussion',
'user_id' => 1,
'comment_count' => 5,
'participant_count' => 1,
'created_at' => '2026-01-01 00:00:00',
'last_posted_at' => '2026-01-01 00:04:00',
'last_post_number' => 5,
'first_post_id' => 10,
'last_post_id' => 14,
],
],
'posts' => [
$this->commentPost(10, 1, 1, 1, '2026-01-01 00:00:00', '<p>Post 1</p>'),
$this->commentPost(11, 1, 1, 2, '2026-01-01 00:01:00', '<p>Post 2</p>'),
$this->commentPost(12, 1, 1, 3, '2026-01-01 00:02:00', '<p>Post 3</p>'),
$this->commentPost(13, 1, 1, 4, '2026-01-01 00:03:00', '<p>Post 4</p>'),
$this->commentPost(14, 1, 1, 5, '2026-01-01 00:04:00', '<p>Post 5</p>'),
],
'discussion_user' => [
[
'discussion_id' => 1,
'user_id' => 1,
'last_read_post_number' => 5,
'last_read_at' => '2026-01-01 00:04:00',
],
],
]);
}

#[Test]
public function it_clamps_stale_read_state_after_split_and_keeps_the_source_unread_after_a_new_reply(): void
{
$splitResponse = $this->send(
$this->request('POST', '/api/split', [
'authenticatedAs' => 1,
'json' => [
'title' => 'Split Child',
'start_post_id' => 12,
'end_post_number' => 5,
],
])
);

$this->assertSame(200, $splitResponse->getStatusCode());

/** @var Discussion $sourceDiscussion */
$sourceDiscussion = Discussion::query()->findOrFail(1);
/** @var UserState $userState */
$userState = UserState::query()
->where('discussion_id', 1)
->where('user_id', 1)
->firstOrFail();

$this->assertSame(2, $sourceDiscussion->last_post_number);
$this->assertSame(2, $userState->last_read_post_number);

$replyResponse = $this->send(
$this->request('POST', '/api/posts', [
'authenticatedAs' => 2,
'json' => [
'data' => [
'type' => 'posts',
'attributes' => [
'content' => 'Moderator reply after split',
],
'relationships' => [
'discussion' => [
'data' => [
'type' => 'discussions',
'id' => '1',
],
],
],
],
],
])
);

$this->assertSame(201, $replyResponse->getStatusCode());

$sourceDiscussion->refresh();
$userState = UserState::query()
->where('discussion_id', 1)
->where('user_id', 1)
->firstOrFail();

$this->assertSame(4, $sourceDiscussion->last_post_number);
$this->assertSame(2, $userState->last_read_post_number);

$showResponse = $this->send(
$this->request('GET', '/api/discussions/1', ['authenticatedAs' => 1])
);

$this->assertSame(200, $showResponse->getStatusCode());

$showJson = json_decode($showResponse->getBody()->getContents(), true);

$this->assertSame(4, $showJson['data']['attributes']['lastPostNumber']);
$this->assertSame(2, $showJson['data']['attributes']['lastReadPostNumber']);
}

protected function commentPost(
int $id,
int $discussionId,
int $userId,
int $number,
string $createdAt,
string $content
): array {
return [
'id' => $id,
'discussion_id' => $discussionId,
'user_id' => $userId,
'type' => 'comment',
'number' => $number,
'created_at' => $createdAt,
'content' => $content,
];
}
}
9 changes: 9 additions & 0 deletions tests/integration/setup.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

use Flarum\Testing\integration\Setup\SetupScript;

require __DIR__.'/../../vendor/autoload.php';

$setup = new SetupScript();

$setup->run();
11 changes: 11 additions & 0 deletions tests/phpunit.integration.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
bootstrap="integration/setup.php"
colors="true">
<testsuites>
<testsuite name="Integration">
<directory suffix="Test.php">./integration</directory>
</testsuite>
</testsuites>
</phpunit>
Loading