Skip to content
Merged
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
2 changes: 1 addition & 1 deletion KNOWLEDGE_BASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ Livestream history: on [day two at 7:31:27](https://www.youtube.com/watch?v=nfmV

### Polls and voting

Add a poll to a top-level commit with **two to four distinct options**, each up to **50 characters** (with the same emoji suggestions and `:shortcode:` conversion as the commit body), and a duration of **one hour, one day, three days, or seven days**. Polls cannot accompany images or be added to replies. Voting opens when the commit is approved, so time spent pending does not consume the voting period.
Add a poll to a top-level commit with **two to four distinct options**, each up to **50 characters** (with the same emoji suggestions and `:shortcode:` conversion as the commit body), and a duration of **one hour, one day, three days, or seven days**. Polls cannot accompany images or be added to replies or quotes. Voting opens when the commit is approved, so time spent pending does not consume the voting period.

Signed-in readers can choose one option and vote once while an approved, visible poll is open; a vote cannot be changed or withdrawn. The author cannot vote in their own poll. Results are visible to the author and people who have voted, and to everyone once the poll closes. Other people's individual votes are not revealed.

Expand Down
4 changes: 3 additions & 1 deletion app/Http/Requests/CreateCommitRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,14 @@ public function rules(): array
$user = $this->user();
assert($user instanceof User);
$body = $this->input('body');
$attachmentRules = self::attachmentRules();
$attachmentRules['poll'][] = 'prohibits:quoted_commit_id';

return [
'body' => self::bodyRules($this->hasFile('images')),
'quoted_commit_id' => ['nullable', 'uuid', 'prohibits:parent_id', new VisibleCommit($user, 'That commit is no longer available to quote.')],
'link_preview_url' => ['nullable', 'string', Rule::in(is_string($body) ? resolve(Urls::class)->all($body) : [])],
...self::attachmentRules(),
...$attachmentRules,
];
}

Expand Down
28 changes: 22 additions & 6 deletions tests/Feature/Controllers/CommitPollsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
}
})->with(['chosen' => 'https://example.com/second', 'removed' => null]);

it('keeps polls and link card choices on quotes through approval without exposing quoted votes', function (?string $linkPreviewUrl): void {
it('keeps link card choices on quotes through approval without exposing quoted votes', function (?string $linkPreviewUrl): void {
Queue::fake([UnfurlLink::class]);

$user = User::factory()->create();
Expand All @@ -79,22 +79,21 @@
'body' => 'Compare @quoted-writer https://example.com/first and https://example.com/second',
'quoted_commit_id' => $quoted->commit_id,
'link_preview_url' => $linkPreviewUrl,
'poll' => ['options' => ['First', 'Second'], 'duration' => PollDuration::OneDay->value],
])
->assertSessionHasNoErrors()
->assertRedirectToRoute('log.show', Log::Following);

$quote = $user->commits()->sole();

expect($quote->quoted_commit_id)->toBe($quoted->commit_id)
->and($quote->poll->opened_at)->toBeNull()
->and($quote->poll)->toBeNull()
->and($quote->linkPreview?->url)->toBe($linkPreviewUrl);

resolve(UpdateCommitStatus::class)->handle($quote, CommitStatus::Approved);

$notification = $quotedAuthor->notifications()->sole();

expect($quote->poll->refresh()->opened_at?->toIso8601String())->toBe(now()->toIso8601String())
expect($quote->refresh()->status)->toBe(CommitStatus::Approved)
->and($user->notifications()->exists())->toBeFalse()
->and($notification->type)->toBe(NotificationType::Quoted)
->and($notification->actor_id)->toBe($user->id)
Expand All @@ -106,8 +105,7 @@
->assertOk()
->assertInertia(fn ($page) => $page
->where('auth.user', null)
->where('commit.poll.options.0.label', 'First')
->where('commit.poll.total_votes', null)
->where('commit.poll', null)
->where('commit.quoted_commit.id', $quoted->commit_id)
->where('commit.quoted_commit.poll.options.0.label', 'Private A')
->where('commit.quoted_commit.poll.total_votes', null))
Expand Down Expand Up @@ -164,6 +162,24 @@
expect($parent->replies()->count())->toBe(0);
});

it('may not attach a poll to a quote', function (): void {
$user = User::factory()->create();
$quoted = Commit::factory()->create();

$this->actingAs($user)
->fromRoute('log.show', Log::Following)
->post(route('commit.store'), [
'body' => 'Tabs or spaces?',
'quoted_commit_id' => $quoted->id,
'poll' => ['options' => ['Tabs', 'Spaces'], 'duration' => PollDuration::OneDay->value],
])
->assertRedirectToRoute('log.show', Log::Following)
->assertSessionHasErrors(['poll' => 'The poll field prohibits quoted commit id from being present.']);

expect($user->commits()->exists())->toBeFalse()
->and(Poll::query()->exists())->toBeFalse();
});

it('may not attach a poll to a commit with images', function (): void {
Storage::fake();

Expand Down
Loading