Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useRouter } from 'next/router';
import post from '../../../../__tests__/fixture/post';
import { TestBootProvider } from '../../../../__tests__/helpers/boot';
import type { LiveRoomPost } from '../../../graphql/liveRooms';
import { LiveRoomStatus } from '../../../graphql/liveRooms';
import { LiveRoomMode, LiveRoomStatus } from '../../../graphql/liveRooms';
import { PostType } from '../../../graphql/posts';
import type { Post } from '../../../graphql/posts';
import type { PostCardProps } from '../common/common';
Expand All @@ -20,6 +20,7 @@ jest.mock('next/router', () => ({
const room: LiveRoomPost = {
id: 'room-1',
topic: 'Weekly product standup',
mode: LiveRoomMode.Moderated,
status: LiveRoomStatus.Created,
scheduledStart: '2026-05-20T10:00:00.000Z',
subscribed: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import { useRouter } from 'next/router';
import post from '../../../../__tests__/fixture/post';
import { TestBootProvider } from '../../../../__tests__/helpers/boot';
import type { LiveRoomPost } from '../../../graphql/liveRooms';
import { LiveRoomStatus } from '../../../graphql/liveRooms';
import {
LiveRoomActivityStatus,
LiveRoomMode,
LiveRoomStatus,
} from '../../../graphql/liveRooms';
import { PostType } from '../../../graphql/posts';
import type { Post } from '../../../graphql/posts';
import type { PostCardProps } from '../common/common';
Expand All @@ -20,6 +24,7 @@ jest.mock('next/router', () => ({
const room: LiveRoomPost = {
id: 'room-1',
topic: 'Weekly product standup',
mode: LiveRoomMode.Moderated,
status: LiveRoomStatus.Created,
scheduledStart: '2026-05-20T10:00:00.000Z',
subscribed: false,
Expand Down Expand Up @@ -77,3 +82,23 @@ it('renders live room post list content and standup route link', async () => {
'/standups/room-1',
);
});

it('shows community-moderated durable-created rooms as live when activity is live', async () => {
renderComponent({
post: {
...liveRoomPost,
liveRoom: {
...room,
mode: LiveRoomMode.CommunityModerated,
status: LiveRoomStatus.Created,
activityStatus: LiveRoomActivityStatus.Live,
},
},
});

expect(await screen.findByText('Live')).toBeInTheDocument();
expect(screen.queryByText(/May 20 at/)).not.toBeInTheDocument();
expect(
screen.queryByRole('button', { name: /RSVP/ }),
).not.toBeInTheDocument();
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
ViewSize,
} from '../../../hooks';
import { LiveRoomStatus } from '../../../graphql/liveRooms';
import { isLiveRoomEffectivelyLive } from '../../../lib/liveRoom/status';
import FeedItemContainer from '../common/list/FeedItemContainer';
import { combinedClicks } from '../../../lib/click';
import { CardContainer, CardContent, CardTitle } from '../common/list/ListCard';
Expand Down Expand Up @@ -79,8 +80,7 @@ export const LiveRoomPostList = forwardRef(function LiveRoomPostList(
const metadata = useMemo(() => {
const authorName = post.author?.name ?? post.source?.name;
const isActiveOrEnded =
room.status === LiveRoomStatus.Live ||
room.status === LiveRoomStatus.Ended;
isLiveRoomEffectivelyLive(room) || room.status === LiveRoomStatus.Ended;
const bottomLabel = isActiveOrEnded ? (
<LiveRoomPostStatusBadge room={room} />
) : (
Expand Down
3 changes: 2 additions & 1 deletion packages/shared/src/components/cards/liveRoom/common.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import post from '../../../../__tests__/fixture/post';
import type { LiveRoomPost } from '../../../graphql/liveRooms';
import { LiveRoomStatus } from '../../../graphql/liveRooms';
import { LiveRoomMode, LiveRoomStatus } from '../../../graphql/liveRooms';
import { PostType } from '../../../graphql/posts';
import type { Post } from '../../../graphql/posts';
import { getLiveRoomPostRoom, getLiveRoomPostTitle } from './common';

const createRoom = (room: Partial<LiveRoomPost> = {}): LiveRoomPost => ({
id: 'room-1',
topic: 'Weekly product standup',
mode: LiveRoomMode.Moderated,
status: LiveRoomStatus.Created,
scheduledStart: '2026-05-20T10:00:00.000Z',
subscribed: false,
Expand Down
9 changes: 7 additions & 2 deletions packages/shared/src/components/cards/liveRoom/common.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { LiveRoomStatus } from '../../../graphql/liveRooms';
import { webappUrl } from '../../../lib/constants';
import { anchorDefaultRel } from '../../../lib/strings';
import { formatLiveRoomScheduledStart } from '../../../lib/liveRoom/date';
import { isLiveRoomEffectivelyLive } from '../../../lib/liveRoom/status';
import { Button, ButtonSize, ButtonVariant } from '../../buttons/Button';
import { BellIcon, CalendarIcon, MicrophoneIcon, VIcon } from '../../icons';
import { IconSize } from '../../Icon';
Expand Down Expand Up @@ -45,7 +46,7 @@ export const LiveRoomPostStatusBadge = ({
className?: string;
room: LiveRoomPost;
}): ReactElement | null => {
if (room.status === LiveRoomStatus.Live) {
if (isLiveRoomEffectivelyLive(room)) {
return (
<span
className={classNames(
Expand Down Expand Up @@ -105,7 +106,11 @@ export const LiveRoomPostScheduledStart = ({
className?: string;
room: LiveRoomPost;
}): ReactElement | null => {
if (!room.scheduledStart) {
if (
!room.scheduledStart ||
isLiveRoomEffectivelyLive(room) ||
room.status === LiveRoomStatus.Ended
) {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,95 @@ describe('CreateLiveRoomForm', () => {
expect(onCreated).toHaveBeenCalledTimes(1);
});

it('creates a community moderated standup with community parameters', async () => {
const onCreated = jest.fn();

render(<CreateLiveRoomForm onCreated={onCreated} />);

fireEvent.change(screen.getByPlaceholderText('Topic'), {
target: { value: 'Community room' },
});
fireEvent.click(screen.getByLabelText('Community moderated'));
fireEvent.change(screen.getByLabelText('Minimum participants to go live'), {
target: { value: '4' },
});
fireEvent.change(screen.getByLabelText('Speaker limit'), {
target: { value: '8' },
});
fireEvent.click(screen.getByRole('button', { name: 'Create standup' }));

await waitFor(() => {
expect(mockCreateLiveRoom).toHaveBeenCalledWith({
topic: 'Community room',
mode: LiveRoomMode.CommunityModerated,
scheduledStart: undefined,
minParticipantsToGoLive: 4,
speakerLimit: 8,
description: undefined,
});
});
expect(onCreated).toHaveBeenCalledTimes(1);
expect(mockLogEvent).toHaveBeenCalledWith(
expect.objectContaining({
event_name: 'create standup',
extra: expect.stringContaining('"mode":"community_moderated"'),
}),
);
expect(mockLogEvent).toHaveBeenCalledWith(
expect.objectContaining({
extra: expect.stringContaining('"min_participants_to_go_live":4'),
}),
);
expect(mockLogEvent).toHaveBeenCalledWith(
expect.objectContaining({
extra: expect.stringContaining('"speaker_limit":8'),
}),
);
});

it('requires a participant minimum for community moderated standups', async () => {
render(<CreateLiveRoomForm onCreated={jest.fn()} />);

fireEvent.change(screen.getByPlaceholderText('Topic'), {
target: { value: 'Community room' },
});
fireEvent.click(screen.getByLabelText('Community moderated'));
fireEvent.change(screen.getByLabelText('Minimum participants to go live'), {
target: { value: '' },
});
fireEvent.click(screen.getByRole('button', { name: 'Create standup' }));

expect(
await screen.findByText(
'Minimum participants is required for community-moderated rooms',
),
).toBeInTheDocument();
expect(mockCreateLiveRoom).not.toHaveBeenCalled();
});

it('requires the speaker limit to fit the community participant minimum', async () => {
render(<CreateLiveRoomForm onCreated={jest.fn()} />);

fireEvent.change(screen.getByPlaceholderText('Topic'), {
target: { value: 'Community room' },
});
fireEvent.click(screen.getByLabelText('Community moderated'));
fireEvent.change(screen.getByLabelText('Minimum participants to go live'), {
target: { value: '6' },
});
fireEvent.change(screen.getByLabelText('Speaker limit'), {
target: { value: '4' },
});
fireEvent.click(screen.getByRole('button', { name: 'Create standup' }));

expect(
await screen.findByText(
'Speaker limit must be greater than or equal to the participant minimum',
),
).toBeInTheDocument();
expect(mockCreateLiveRoom).not.toHaveBeenCalled();
});

it('submits scheduled lobby fields as UTC and explains the local delta', async () => {
jest.useFakeTimers().setSystemTime(new Date('2026-05-04T07:00:00.000Z'));

Expand Down
Loading
Loading