113 lines
4.5 KiB
TypeScript
113 lines
4.5 KiB
TypeScript
import { afterEach, expect, it, vi } from 'vitest';
|
|
import { render } from 'svelte/server';
|
|
import { writable } from 'svelte/store';
|
|
import ChatPanel from '../../src/lib/ui/components/ChatPanel.svelte';
|
|
import PlayerScoreboard from '../../src/lib/ui/components/PlayerScoreboard.svelte';
|
|
import { moderationEventDetail, moderationHref } from '../../src/lib/ui/moderation.js';
|
|
import type { UserRole } from '../../src/lib/auth.js';
|
|
|
|
const currentPage = writable({
|
|
url: new URL('http://localhost/servers/t6_zm_transit_1'),
|
|
data: { user: { role: 'moderator' as UserRole } }
|
|
});
|
|
vi.mock('$app/stores', () => ({
|
|
page: {
|
|
subscribe: (run: Parameters<typeof currentPage.subscribe>[0]) => currentPage.subscribe(run)
|
|
}
|
|
}));
|
|
afterEach(() =>
|
|
currentPage.update((value) => ({ ...value, data: { user: { role: 'moderator' } } }))
|
|
);
|
|
|
|
it('keeps ambiguous engine chat IDs in search and preserves selected evidence for verified lookup', () => {
|
|
const body = render(ChatPanel, {
|
|
props: {
|
|
game: 't6',
|
|
history: [
|
|
{
|
|
type: 'message',
|
|
matchId: 12,
|
|
message: {
|
|
guid: 'abcdef',
|
|
player: 'Tank',
|
|
message: 'Review & investigate',
|
|
slot: 2,
|
|
time: '0:30'
|
|
},
|
|
context: { realTime: null, round: null, matchElapsedMs: null }
|
|
}
|
|
]
|
|
}
|
|
}).body;
|
|
const target = new URL(
|
|
body.match(/href="([^"]*\/moderation[^"]*)"/)![1].replaceAll('&', '&'),
|
|
'http://localhost'
|
|
);
|
|
expect(target.searchParams.get('q')).toBe('abcdef');
|
|
expect(target.searchParams.has('guid')).toBe(false);
|
|
expect(target.searchParams.get('game')).toBe('t6');
|
|
expect(target.searchParams.get('matchId')).toBe('12');
|
|
expect(target.searchParams.get('message')).toBe('Review & investigate');
|
|
});
|
|
|
|
it('links scoreboard actions to the exact telemetry GUID and hides moderation from ordinary viewers', () => {
|
|
const props = {
|
|
report: {
|
|
server: 't6_zm_transit_1',
|
|
map: 'zm_transit',
|
|
event: 'game_start' as const,
|
|
receivedAt: '2026-10-15T12:00:00Z',
|
|
players: [{ guid: '18446744073709551615', name: 'Tank' }]
|
|
}
|
|
};
|
|
const body = render(PlayerScoreboard, { props }).body;
|
|
expect(body).toContain('guid=18446744073709551615');
|
|
expect(body).toContain('server=t6_zm_transit_1');
|
|
currentPage.update((value) => ({ ...value, data: { user: { role: 'viewer' } } }));
|
|
expect(render(PlayerScoreboard, { props }).body).not.toContain('/moderation?');
|
|
});
|
|
|
|
it('does not invent a game namespace for an unscoped profile', () => {
|
|
const target = new URL(moderationHref({ guid: '42', game: 'unknown' }), 'http://localhost');
|
|
expect(target.searchParams.get('guid')).toBe('42');
|
|
expect(target.searchParams.has('game')).toBe(false);
|
|
});
|
|
|
|
it('presents case decisions and ownership changes as readable history while preserving staff text', () => {
|
|
expect(
|
|
moderationEventDetail(
|
|
JSON.stringify({
|
|
title: { from: 'open', to: 'constructor' },
|
|
assignee: { from: 'open', to: 'constructor' }
|
|
}),
|
|
'case.updated'
|
|
)
|
|
).toBe('Title: open → constructor\nAssigned to: open → constructor');
|
|
expect(
|
|
moderationEventDetail(
|
|
JSON.stringify({
|
|
title: { from: 'Report', to: 'Appeal review' },
|
|
assignee: { from: null, to: 'Moderator' },
|
|
assigneeId: { from: null, to: 'private-internal-user-id' },
|
|
status: { from: 'open', to: 'in_review' },
|
|
reason: 'Reviewing additional evidence'
|
|
}),
|
|
'case.updated'
|
|
)
|
|
).toBe(
|
|
'Title: Report → Appeal review\nAssigned to: Unassigned → Moderator\nStatus: Open → In review\nReviewing additional evidence'
|
|
);
|
|
expect(
|
|
moderationEventDetail(
|
|
JSON.stringify({ from: 'appeal_denied', reason: 'New evidence' }),
|
|
'case.reopened'
|
|
)
|
|
).toBe('Previous outcome: Appeal denied\nNew evidence');
|
|
expect(moderationEventDetail('Decision with {unusual text}', 'case.resolved')).toBe(
|
|
'Decision with {unusual text}'
|
|
);
|
|
expect(
|
|
moderationEventDetail(JSON.stringify({ count: 2, matchId: 42 }), 'case.evidence_attached')
|
|
).toBe('2 archived chat entries attached from match #42.');
|
|
});
|