Files
feedBack/tests/js/v3_add_to_playlist_menu.test.js
T
3d97c07b2b feat(v3): add "Add to playlist" to a song's ⋮ More menu (#625)
* feat(v3): add "Add to playlist" to a song's ⋮ More menu

You could only add a song to a playlist via select-mode (checkbox → batch bar).
Add an "Add to playlist" row to each song card's ⋮ overflow menu that targets
that one song, reusing the same picker (pick a listed number or type a new name
to create the playlist).

The select-mode batch flow and the single-song menu now share one extracted
`addFilenamesToPlaylist(filenames)` helper; the menu is `openCardMenu`, shared by
grid cards and tree rows, so both views get it. Tests:
tests/js/v3_add_to_playlist_menu.test.js.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QbexxfTt8q2tAn436MqGWF

* fix(v3): don't clear the batch selection when the playlist picker is cancelled

The extract-helper refactor made batchAddToPlaylist() call finishBatch()
unconditionally, so cancelling (or a failed create) cleared the multi-select
and reloaded the grid — a regression from the original early-return-on-cancel
behaviour. addFilenamesToPlaylist() already returns null on cancel/failure;
gate finishBatch() on a truthy playlist id so the selection is preserved for
a retry. Adds a regression assertion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: byrongamatos <xasiklas@gmail.com>
2026-06-28 13:58:14 +02:00

38 lines
1.8 KiB
JavaScript

// Guard: a song's ⋮ "More" menu offers "Add to playlist" for a single song —
// not only the select-mode checkbox + batch-bar flow. Both paths share the
// extracted addFilenamesToPlaylist() helper. (Menu/DOM wiring isn't headlessly
// unit-testable, so these are source-level guards.)
'use strict';
const { test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const SONGS = fs.readFileSync(
path.join(__dirname, '..', '..', 'static', 'v3', 'songs.js'), 'utf8');
test('the ⋮ card menu lists an "Add to playlist" row', () => {
assert.match(SONGS, /id:\s*'__playlist',\s*label:\s*'Add to playlist'/);
});
test('the menu row adds the single song via the shared helper', () => {
assert.match(SONGS, /id === '__playlist'[\s\S]{0,100}addFilenamesToPlaylist\(\[song\.filename\]\)/);
});
test('batch and single-song add share addFilenamesToPlaylist()', () => {
assert.match(SONGS, /async function addFilenamesToPlaylist\(filenames\)/);
assert.match(SONGS, /async function batchAddToPlaylist\(\)[\s\S]{0,120}addFilenamesToPlaylist\(state\.selected\)/);
});
test('batch only finishes (clears selection) when the add succeeded, not on cancel', () => {
// addFilenamesToPlaylist returns null on a cancelled/failed picker; the
// batch caller must capture it and gate finishBatch() on a truthy pid, so
// cancelling preserves the multi-select (regression guard for the
// extract-helper refactor — previously finishBatch ran unconditionally).
assert.match(SONGS, /const pid = await addFilenamesToPlaylist\(state\.selected\)/,
'batch must capture the returned playlist id');
assert.match(SONGS, /if \(pid\) finishBatch\(\)/,
'finishBatch must be gated on a successful add (truthy pid)');
});