fix(player): toggle current track without restarting playback
ci / ts / ts (packages + web) (push) Failing after 7m39s
release / android (APK в релиз) (push) Successful in 6m47s
release / desktop (Linux и Windows в релиз) (push) Successful in 15m18s
release / release (версия и заметки) (push) Successful in 9s
release / prune (старые файлы и прогоны) (push) Successful in 10s

This commit is contained in:
mrleo1nid committed 2026-10-29 22:44:54 +03:00
1 parent 5876ae29cc
commit 6a1382dad5
3 files changed
+45 -4

No files matched your search

+35
View File
@@ -531,6 +531,41 @@ describe('search box', () => {
});
describe('player', () => {
it.each(['album', 'artist'] as const)('toggles the current %s row without restarting or replacing the listening attempt', async (kind) => {
const alice = makeUser('alice', 'user');
const id = kind === 'artist' ? '00000000-0000-4000-8000-0000000000aa' : '00000000-0000-4000-8000-0000000000bb';
const { user, api } = await renderApp([alice], `/${kind}/${id}`, { signedInAs: alice });
const row = (await screen.findByText('Закрой за мной дверь')).closest('[role="listitem"]')!;
await user.click(row);
const bar = screen.getByRole('contentinfo', { name: 'Плеер' });
await waitFor(() => expect(within(bar).getByRole('button', { name: 'Пауза' })).toBeInTheDocument());
await user.keyboard('{ArrowRight}');
const progress = within(bar).getByRole('slider', { name: 'Позиция' });
expect(progress).toHaveValue('10000');
await user.click(row);
expect(within(bar).getByRole('button', { name: 'Слушать' })).toBeInTheDocument();
expect(progress).toHaveValue('10000');
await user.click(row);
expect(within(bar).getByRole('button', { name: 'Пауза' })).toBeInTheDocument();
expect(progress).toHaveValue('10000');
await user.keyboard('{Enter}');
expect(within(bar).getByRole('button', { name: 'Слушать' })).toBeInTheDocument();
await user.keyboard('{Enter}');
expect(within(bar).getByRole('button', { name: 'Пауза' })).toBeInTheDocument();
expect(progress).toHaveValue('10000');
window.dispatchEvent(new Event('pagehide'));
await waitFor(() => {
const events = api.calls.filter((c) => c.path === '/api/v1/events').flatMap((c) => (c.body as { events: { type: string; context_type?: string }[] }).events);
expect(events.filter((event) => event.type === 'play_start')).toHaveLength(1);
expect(events.find((event) => event.type === 'play_start')).toMatchObject({ context_type: kind });
expect(events.some((event) => event.type === 'skip')).toBe(false);
});
const other = screen.getAllByText('Группа крови').map((node) => node.closest('[role="listitem"]')).find(Boolean)!;
await user.click(other);
await waitFor(() => expect(within(bar).getByText('Группа крови')).toBeInTheDocument());
expect(progress).toHaveValue('0');
});
it('opens a shared track after login and copies its page URL from the player', async () => {
const alice = makeUser('alice', 'user');
const path = '/track/00000000-0000-4000-8000-000000000101';
+8 -4
View File
@@ -11,7 +11,7 @@ import { useSession } from '../app/session';
import { formatDuration } from '../shared/lib/format';
import { usePlayer, usePlayerState } from '../player/context';
import type { PlayContext } from '../player/player';
import { albumIcon, likeFillIcon, likeIcon, personIcon, playIcon, undoIcon} from '../shared/ui/icons';
import { albumIcon, likeFillIcon, likeIcon, pauseIcon, personIcon, playIcon, undoIcon} from '../shared/ui/icons';
import { cx, Icon } from '../shared/ui/ui';
import s from './catalog.module.css';
@@ -143,7 +143,7 @@ export function AlbumGrid({ albums }: { albums: AlbumSummary[] }) {
/**
* Список треков. showAlbum — колонка альбома (поиск); numbered — номер трека (страница альбома).
* Клик по строке запускает весь список с этого трека в контексте context; Enter — то же с клавиатуры.
* Клик или Enter запускает список с выбранного трека; у текущего трека переключает паузу без перезапуска.
*/
export function TrackList({
tracks,
@@ -183,7 +183,11 @@ export function TrackList({
const row = (tr: Track, i: number) => {
const isCurrent = current?.id === tr.id;
const play = () => tr.available && player.playList(tracks, i, context);
const play = () => {
if (!tr.available) return;
if (isCurrent) player.toggle();
else player.playList(tracks, i, context);
};
const liked = tr.reaction === 'like';
const meta = rowMeta?.(tr, i);
return (
@@ -206,7 +210,7 @@ export function TrackList({
{numbered ? (tr.track_no ?? i + 1) : <Cover album={tr.album} size={lead ? 44 : 40} />}
{tr.available && (
<span className={s.playHint} aria-hidden>
<Icon src={playIcon} size={20} />
<Icon src={isCurrent && playing ? pauseIcon : playIcon} size={20} />
</span>
)}
</span>
+2
View File
@@ -472,6 +472,8 @@ export function fakeApi(initial: FakeUser[]) {
return json({ ...kino, album_count: 1, track_count: 0 });
case `GET /api/v1/artists/${kino.id}/albums`:
return json({ items: [albumSummary] });
case `GET /api/v1/artists/${kino.id}/top-tracks`:
return json({ items: albumTracks });
}
if (req.method === 'POST' && /^\/api\/v1\/admin\/users\/[^/]+\/reset-recommendations$/.test(path)) {
onboarding.set(path.split('/')[5]!, null);