fix(channel): sync a cleared group DM name without a reload (#2882)

This commit is contained in:
Hampus
2026-09-21 15:34:18 +02:00
committed by GitHub
parent c7ec2a0f58
commit 7601bf98ee
5 changed files with 68 additions and 6 deletions
+1 -1
View File
@@ -150,7 +150,7 @@ function serializeGroupDMChannel(channel: Channel): ChannelResponse {
return {
...serializeBaseChannelFields(channel),
...serializeMessageableFields(channel),
name: channel.name ?? undefined,
name: channel.name ?? null,
icon: channel.iconHash ?? null,
owner_id: channel.ownerId ? channel.ownerId.toString() : null,
nicks: nicknameMap.size > 0 ? nicks : undefined,
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {createTestAccount} from '@app/api/auth/tests/AuthTestUtils';
import {createFriendship, createGroupDmChannel, getChannel} from '@app/api/channel/tests/ChannelTestUtils';
import {ensureSessionStarted} from '@app/api/message/tests/MessageTestUtils';
import {type ApiTestHarness, createApiTestHarness} from '@app/api/test/ApiTestHarness';
import {NoopGatewayService} from '@app/api/test/NoopGatewayService';
import {HTTP_STATUS} from '@app/api/test/TestConstants';
import {createBuilder} from '@app/api/test/TestRequestBuilder';
import type {ChannelResponse} from '@fluxer/schema/src/domains/channel/ChannelSchemas';
import {afterAll, beforeAll, beforeEach, describe, expect, it, vi} from 'vitest';
describe('Group DM name clear', () => {
let harness: ApiTestHarness;
beforeAll(async () => {
harness = await createApiTestHarness();
});
beforeEach(async () => {
await harness.reset();
});
afterAll(async () => {
await harness?.shutdown();
});
it.each([
['an empty string', ''],
['null', null],
])('sends a null name to every recipient when cleared with %s', async (_label, clearedName) => {
const user1 = await createTestAccount(harness);
const user2 = await createTestAccount(harness);
const user3 = await createTestAccount(harness);
await ensureSessionStarted(harness, user1.token);
await ensureSessionStarted(harness, user2.token);
await ensureSessionStarted(harness, user3.token);
await createFriendship(harness, user1, user2);
await createFriendship(harness, user1, user3);
const groupDm = await createGroupDmChannel(harness, user1.token, [user2.userId, user3.userId]);
await createBuilder<ChannelResponse>(harness, user1.token)
.patch(`/channels/${groupDm.id}`)
.body({name: 'Weekend plans'})
.expect(HTTP_STATUS.OK)
.execute();
const dispatchSpy = vi.spyOn(NoopGatewayService.prototype, 'dispatchPresence');
try {
const cleared = await createBuilder<ChannelResponse>(harness, user1.token)
.patch(`/channels/${groupDm.id}`)
.body({name: clearedName})
.expect(HTTP_STATUS.OK)
.execute();
expect(cleared).toHaveProperty('name', null);
const channelUpdates = dispatchSpy.mock.calls.filter(([params]) => params.event === 'CHANNEL_UPDATE');
expect(channelUpdates.map(([params]) => params.userId.toString()).sort()).toEqual(
[user1.userId, user2.userId, user3.userId].sort(),
);
for (const [params] of channelUpdates) {
expect(params.data).toHaveProperty('name', null);
}
} finally {
dispatchSpy.mockRestore();
}
expect(await getChannel(harness, user2.token, groupDm.id)).toHaveProperty('name', null);
});
});
+1 -1
View File
@@ -24224,7 +24224,7 @@
"description": "The ID of the guild this channel belongs to",
"$ref": "#/components/schemas/SnowflakeStringType"
},
"name": {"description": "The name of the channel", "type": "string"},
"name": {"description": "The name of the channel", "type": ["string", "null"]},
"topic": {"description": "The topic of the channel", "type": ["string", "null"]},
"url": {
"description": "The URL associated with the channel",
@@ -87,7 +87,7 @@ export class Channel {
this.instanceId = options?.instanceId ?? RuntimeConfig.localInstanceDomain;
this.id = channel.id;
this.guildId = channel.guild_id;
this.name = channel.name;
this.name = channel.name ?? undefined;
noteText(this.name);
this.topic = channel.topic ?? null;
this.url = channel.url ?? null;
@@ -223,7 +223,7 @@ export class Channel {
{
id: this.id,
guild_id: updates.guild_id ?? this.guildId,
name: updates.name ?? this.name,
name: updates.name !== undefined ? updates.name : this.name,
topic: updates.topic !== undefined ? updates.topic : this.topic,
url: updates.url !== undefined ? updates.url : this.url,
icon: updates.icon !== undefined ? updates.icon : this.icon,
@@ -50,7 +50,7 @@ export type CallEligibilityResponse = z.infer<typeof CallEligibilityResponse>;
export const ChannelResponse = z.object({
id: SnowflakeStringType.describe('The unique identifier (snowflake) for this channel'),
guild_id: SnowflakeStringType.optional().describe('The ID of the guild this channel belongs to'),
name: z.string().optional().describe('The name of the channel'),
name: z.string().nullish().describe('The name of the channel'),
topic: z.string().nullish().describe('The topic of the channel'),
url: z.url().nullish().describe('The URL associated with the channel'),
icon: z.string().nullish().describe('The icon hash of the channel (for group DMs)'),
@@ -141,7 +141,7 @@ export interface ChannelOverwrite {
export interface Channel {
readonly id: string;
readonly guild_id?: string;
readonly name?: string;
readonly name?: string | null;
readonly topic?: string | null;
readonly url?: string | null;
readonly icon?: string | null;