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
4 changes: 3 additions & 1 deletion tests/Unit/ChatScroll.test.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/// <reference types="node" />
import assert from 'node:assert/strict';
import * as React from 'react';
import type { ReactElement, ReactNode } from 'react';
Expand Down Expand Up @@ -28,6 +29,7 @@ vi.doMock('react', () => ({
},
useMemo: (compute: () => unknown) => compute(),
useCallback: (callback: unknown) => callback,
useId: () => 'id',
useEffect: (effect: () => unknown) => {
effects.push(effect);
},
Expand All @@ -41,7 +43,7 @@ vi.doMock('react', () => ({
}));
vi.doMock('../../resources/js/hooks/use-chat', () => ({
useThread: () => thread,
useChat: () => ({}),
useChat: () => ({ mutePreferences: {} }),
}));
vi.doMock('../../resources/js/hooks/use-stash', () => ({
useStash: () => ({}),
Expand Down
4 changes: 3 additions & 1 deletion tests/Unit/CommitImages.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/// <reference types="node" />
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { renderToStaticMarkup } from 'react-dom/server';
import { test } from 'vite-plus/test';
import {
CommitImages,
gridImageObjectFit,
Expand Down Expand Up @@ -36,6 +36,8 @@ const commit: Commit = {
website: null,
company: null,
has_sponsors_listing: false,
timezone: null,
show_local_time: false,
has_github_account: true,
lane: 0,
created_at: '2026-10-16T10:00:00Z',
Expand Down
2 changes: 1 addition & 1 deletion tests/Unit/CommitQuoteButton.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/// <reference types="node" />

import assert from 'node:assert/strict';
import { test } from 'node:test';
import { createInertiaApp } from '@inertiajs/react';
import { renderToString } from 'react-dom/server';
import { test } from 'vite-plus/test';
import { CommitQuoteButton } from '../../resources/js/components/commit-quote-button';
import { TooltipProvider } from '../../resources/js/components/ui/tooltip';
import type { Commit } from '../../resources/js/types';
Expand Down
2 changes: 1 addition & 1 deletion tests/Unit/CommitSecondaryActions.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/// <reference types="node" />

import assert from 'node:assert/strict';
import { test } from 'node:test';
import { createInertiaApp } from '@inertiajs/react';
import { renderToString } from 'react-dom/server';
import { test } from 'vite-plus/test';
import { CommitSecondaryActions } from '../../resources/js/components/commit-secondary-actions';
import { TooltipProvider } from '../../resources/js/components/ui/tooltip';
import type { Commit } from '../../resources/js/types';
Expand Down
2 changes: 1 addition & 1 deletion tests/Unit/CommitStarButton.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/// <reference types="node" />

import assert from 'node:assert/strict';
import { test } from 'node:test';
import { createInertiaApp } from '@inertiajs/react';
import { renderToString } from 'react-dom/server';
import { test } from 'vite-plus/test';
import { CommitStarButton } from '../../resources/js/components/commit-star-button';
import { TooltipProvider } from '../../resources/js/components/ui/tooltip';
import type { Commit } from '../../resources/js/types';
Expand Down
2 changes: 1 addition & 1 deletion tests/Unit/FollowersYouKnow.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/// <reference types="node" />

import assert from 'node:assert/strict';
import { test } from 'node:test';
import { createInertiaApp } from '@inertiajs/react';
import { renderToString } from 'react-dom/server';
import { test } from 'vite-plus/test';
import { FollowersYouKnow } from '../../resources/js/components/followers-you-know';
import type { User } from '../../resources/js/types';

Expand Down
4 changes: 4 additions & 0 deletions tests/Unit/LinkifiedText.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,10 @@ for (const kind of ['direct', 'org'] as const) {
partner_read_through_id: 0,
partner_read_at: null,
is_archived: false,
last_incoming_message_id: null,
last_incoming_sender: null,
pinned_at: null,
muted_until_at: null,
},
]}
/>,
Expand Down
7 changes: 1 addition & 6 deletions tests/Unit/LocalTime.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,9 @@ test('shows the time in the given timezone with its offset', () => {
const html = renderToStaticMarkup(
<LocalTime timeZone="Europe/Berlin" as="p" className="mt-2" />,
);
const expected = new Date('2026-07-01T10:06:00Z').toLocaleTimeString(
undefined,
{ hour: 'numeric', minute: '2-digit', timeZone: 'Europe/Berlin' },
);

assert.ok(html.startsWith('<p class="flex items-center gap-1.5 mt-2"'));
assert.ok(html.includes('dateTime="2026-07-01T10:06:00.000Z"'));
assert.ok(html.includes(`${expected}</time> (UTC +02:00)`));
assert.match(html, />12:06(?: PM)?<\/time> \(UTC \+02:00\)/);
});

test('renders as a list item by default, for the profile details', () => {
Expand Down
72 changes: 46 additions & 26 deletions tests/Unit/NotificationInbox.test.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/// <reference types="node" />

import assert from 'node:assert/strict';
import { test } from 'node:test';
import { createInertiaApp } from '@inertiajs/react';
import { renderToString } from 'react-dom/server';
import { test } from 'vite-plus/test';
import { TooltipProvider } from '../../resources/js/components/ui/tooltip';
import Index from '../../resources/js/pages/notification/index';
import type { NotificationGroup, User } from '../../resources/js/types';
import type { Commit, NotificationGroup, User } from '../../resources/js/types';

const timestamp = '2026-10-16T12:00:00Z';
const actor: User = {
Expand All @@ -26,6 +26,23 @@ const actor: User = {
created_at: timestamp,
updated_at: timestamp,
};
const quoteCommit: Omit<Commit, 'user'> = {
id: 'quote-commit',
user_id: actor.id,
parent_id: null,
hot_score: 0,
body: 'My quote commentary',
mentions: {},
body_segments: [{ type: 'text', text: 'My quote commentary' }],
preview_segments: null,
status: 'approved',
created_at: timestamp,
updated_at: timestamp,
stars_count: 0,
pasteis_count: 0,
starred: false,
quoted_commit_id: 'quoted-commit',
};
const quote: NotificationGroup = {
id: 'notice',
recipient_id: 'recipient',
Expand All @@ -43,20 +60,7 @@ const quote: NotificationGroup = {
unread_count: 0,
created_at: timestamp,
updated_at: timestamp,
commit: {
id: 'quote-commit',
user_id: actor.id,
parent_id: null,
body: 'My quote commentary',
body_segments: [{ type: 'text', text: 'My quote commentary' }],
preview_segments: null,
status: 'approved',
created_at: timestamp,
updated_at: timestamp,
stars_count: 0,
starred: false,
quoted_commit_id: 'quoted-commit',
},
commit: quoteCommit,
};

async function renderInbox(notifications: NotificationGroup[], type = 'all') {
Expand Down Expand Up @@ -101,6 +105,21 @@ async function renderInbox(notifications: NotificationGroup[], type = 'all') {
return result.body.replaceAll('<!-- -->', '');
}

// The filters render once for mobile and once for desktop, so count each category once.
function categories(html: string, pressed: boolean): Set<string> {
return new Set(
Array.from(
html.matchAll(
new RegExp(
`aria-pressed="${pressed}"[^>]*>([^<]+)</button>`,
'g',
),
),
(match) => match[1],
),
);
}

void test('renders quote notifications with their label and quoting commit destination', async () => {
const html = await renderInbox([quote]);

Expand All @@ -115,10 +134,11 @@ void test('shows the quoted commit under a quote, or a tombstone once it is dele
{
...quote,
commit: {
...quote.commit,
...quoteCommit,
quoted_commit: {
id: 'quoted-commit',
body_segments: [{ type: 'text', text: 'My quoted commit' }],
deleted_at: null,
},
},
},
Expand All @@ -131,7 +151,7 @@ void test('shows the quoted commit under a quote, or a tombstone once it is dele
{
...quote,
commit: {
...quote.commit,
...quoteCommit,
quoted_commit: {
id: 'quoted-commit',
parent_id: null,
Expand Down Expand Up @@ -162,7 +182,7 @@ void test('renders a follow-back control outside the follower profile link', asy
html,
/Quoter<\/a>.*@quoter<\/a>(?:<\/span>)? <a\b[^>]*data-notification-link[^>]*>followed you<\/a>/s,
);
assert.match(html, /<button[^>]*aria-label="Follow @quoter"/);
assert.match(html, /<button[^>]*aria-label="Follow @quoter back"/);
assert.doesNotMatch(html, /<a\b[^>]*>(?:(?!<\/a>).)*<button\b/s);
assert.match(
await renderInbox([
Expand All @@ -187,7 +207,7 @@ void test('leaves follow-back out of a group of followers until it is expanded',

assert.match(
html,
/Quoter<\/a> and 1 other <a\b[^>]*data-notification-link[^>]*>followed you<\/a>/,
/Quoter<\/a> <span[^>]*>and 1 other <\/span><a\b[^>]*data-notification-link[^>]*>followed you<\/a>/,
);
assert.match(html, /aria-expanded="false"[^>]*>.*2 followers/s);
assert.doesNotMatch(html, /aria-label="(Follow|Unfollow) @/);
Expand All @@ -198,7 +218,7 @@ void test('renders notification code without altering emoji shortcodes or nestin
{
...quote,
commit: {
...quote.commit,
...quoteCommit,
body: '`echo ":fire:"`',
body_segments: [{ type: 'code', code: 'echo ":fire:"' }],
},
Expand All @@ -214,7 +234,7 @@ void test('renders highlighted notification blocks without a nested copy button'
{
...quote,
commit: {
...quote.commit,
...quoteCommit,
body: '```php\nreturn ":fire:";\n```',
body_segments: [
{
Expand All @@ -236,16 +256,16 @@ void test('renders highlighted notification blocks without a nested copy button'

void test('renders all notification categories as enabled toggle buttons by default', async () => {
const body = await renderInbox([]);
assert.equal((body.match(/aria-pressed="true"/g) ?? []).length, 6);
assert.equal(categories(body, true).size, 6);
assert.doesNotMatch(body, /aria-current="page"|role="tab"/);
});

void test('renders independent category selections and the all-off empty state', async () => {
const selected = await renderInbox([], 'replies,mentions');
assert.equal((selected.match(/aria-pressed="true"/g) ?? []).length, 2);
assert.equal((selected.match(/aria-pressed="false"/g) ?? []).length, 4);
assert.deepEqual([...categories(selected, true)], ['Replies', 'Mentions']);
assert.equal(categories(selected, false).size, 4);
const none = await renderInbox([], 'none');
assert.equal((none.match(/aria-pressed="false"/g) ?? []).length, 6);
assert.equal(categories(none, false).size, 6);
assert.match(none, /All categories are turned off/);
assert.match(none, /Show all notifications/);
});
8 changes: 4 additions & 4 deletions tests/Unit/NotificationIndex.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/// <reference types="node" />

import assert from 'node:assert/strict';
import { test } from 'node:test';
import { createInertiaApp } from '@inertiajs/react';
import type { ReactElement } from 'react';
import { renderToString } from 'react-dom/server';
import { test } from 'vite-plus/test';
import { TooltipProvider } from '../../resources/js/components/ui/tooltip';
import { laneColor } from '../../resources/js/lib/utils';
import Index, {
Expand Down Expand Up @@ -136,7 +136,7 @@ void test('separates last-visit activity without a border under the preceding no
});

void test('colors each notification rail by actor and ends it in a dot', async () => {
const nextActor = { ...other, lane: 5 };
const nextActor = { ...other, lane: 5, is_followed: false };
const body = await render([
notification,
{
Expand Down Expand Up @@ -186,7 +186,7 @@ for (const isFollowing of [false, true]) {
assert.match(
body,
new RegExp(
`aria-label="${isFollowing ? 'Unfollow' : 'Follow'} @mnapoli"`,
`aria-label="${isFollowing ? 'Unfollow @mnapoli' : 'Follow @mnapoli back'}"`,
),
);
assert.match(body, /followed you/);
Expand Down Expand Up @@ -218,7 +218,7 @@ void test('shows the image of a starred image-only commit beside who starred it'

assert.match(
body,
/and 2 others <a\b[^>]*data-notification-link[^>]*>starred your image<\/a>/,
/and 2 others <\/span><a\b[^>]*data-notification-link[^>]*>starred your image<\/a>/,
);
assert.match(body, /<img[^>]*src="\/commit-images\/image"/);
assert.doesNotMatch(body, /<div[^>]*>(?:<div[^>]*>)*(?:<\/div>)+<\/a>/);
Expand Down
2 changes: 1 addition & 1 deletion tests/Unit/QuotedCommit.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/// <reference types="node" />

import assert from 'node:assert/strict';
import { test } from 'node:test';
import { createInertiaApp } from '@inertiajs/react';
import { renderToString } from 'react-dom/server';
import { test } from 'vite-plus/test';
import { QuotedCommit } from '../../resources/js/components/quoted-commit';
import type {
LinkPreview,
Expand Down
53 changes: 1 addition & 52 deletions tests/draft-images.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,57 +11,6 @@ export default defineConfig({
env: {
VITE_APP_NAME: '/dev/null',
},
include: [
'tests/Unit/CommitPhoto.test.tsx',
'tests/Unit/CommitSize.test.tsx',
'tests/Unit/ThreadHistory.test.tsx',
'tests/Unit/ThreadMutationHistory.test.tsx',
'tests/Unit/ActivityCells.test.tsx',
'tests/Unit/AuthorHover.test.tsx',
'tests/Unit/FollowState.test.tsx',
'tests/Unit/FollowRequests.test.tsx',
'tests/Unit/GuestFollow.test.tsx',
'tests/Unit/DraftStash.test.tsx',
'tests/Unit/DraftImageReads.test.tsx',
'tests/Unit/DraftImagePreview.test.tsx',
'tests/Unit/ImageAttachments.test.tsx',
'tests/Unit/ComposerDraft.test.tsx',
'tests/Unit/QuoteDraft.test.tsx',
'tests/Unit/EmojiSearch.test.tsx',
'tests/Unit/IssueReference.test.tsx',
'tests/Unit/ReactionTray.test.tsx',
'tests/Unit/NotificationFilters.test.tsx',
'tests/Unit/EmojiShortcodes.test.tsx',
'tests/Unit/QuoteReply.test.tsx',
'tests/Unit/CommitThread.test.tsx',
'tests/Unit/SearchDialogComposition.test.tsx',
'tests/Unit/TabSlide.test.tsx',
'tests/Unit/ReportResolution.test.tsx',
'tests/Unit/LinkifiedText.test.tsx',
'tests/Unit/CommitPoll.test.tsx',
'tests/Unit/GraphCanvas.test.tsx',
'tests/Unit/RailEntrance.test.tsx',
'tests/Unit/LazyTooltip.test.tsx',
'tests/Unit/ReportDialog.test.tsx',
'tests/Unit/StandaloneCommitGraphs.test.tsx',
'tests/Unit/LogInsertion.test.tsx',
'tests/Unit/StagedRail.test.tsx',
'tests/Unit/ComposerPreview.test.tsx',
'tests/Unit/UseNow.test.tsx',
'tests/Unit/LinkEmbedHeight.test.tsx',
'tests/Unit/CommitRules.test.tsx',
'tests/Unit/TimeoutRefusals.test.tsx',
'tests/Unit/VoiceVolume.test.tsx',
'tests/Unit/VoiceVolumeHover.test.tsx',
'tests/Unit/VoiceSpeed.test.tsx',
'tests/Unit/VoiceNoteSpeed.test.tsx',
'tests/Unit/OrgMentionSuggestions.test.tsx',
'tests/Unit/RemoteNotifications.test.tsx',
'tests/Unit/RemotePush.test.tsx',
'tests/Unit/RemotePins.test.tsx',
'tests/Unit/RemotePreferences.test.tsx',
'tests/Unit/ChatNavigation.test.tsx',
'tests/Unit/CommittedToast.test.tsx',
],
include: ['tests/Unit/*.test.tsx'],
Comment thread
hbenabdallah marked this conversation as resolved.
},
});
Loading