mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-07-21 12:21:49 +00:00
Crossing-arrows toggle next to Play all / Play album on the playlist detail page. When on, playQueue.start Fisher-Yates-shuffles the queue once at start — on a copy, so the stored playlist order is untouched — swapping per-slot album arrangements in lockstep so each slot keeps its pinned arrangement (#685 contract preserved). Prev-less queue semantics are unchanged: auto-advance simply walks the shuffled order. Preference is global, persisted as localStorage v3PlaylistShuffle. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
87 lines
3.5 KiB
JavaScript
87 lines
3.5 KiB
JavaScript
// playQueue.start({ shuffle: true }): the queue is Fisher-Yates-shuffled ONCE
|
|
// at start. Per-slot arrangements must swap in lockstep with their files
|
|
// (albums pass arrangements aligned by index, #685), the caller's arrays must
|
|
// not be mutated, and shuffle:false / absent must preserve order. Extract the
|
|
// playQueue IIFE from app.js and drive it against a playSong stub.
|
|
'use strict';
|
|
const test = require('node:test');
|
|
const assert = require('node:assert');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
function makeQueue() {
|
|
const src = fs.readFileSync(path.join(__dirname, '..', '..', 'static', 'app.js'), 'utf8');
|
|
const start = src.indexOf('window.feedBack.playQueue = (function () {');
|
|
assert.ok(start !== -1, 'playQueue IIFE found in app.js');
|
|
const end = src.indexOf('})();', start);
|
|
assert.ok(end !== -1, 'playQueue IIFE terminator found');
|
|
const iife = src.slice(start, end + 5);
|
|
const played = [];
|
|
const sandbox = {
|
|
window: {
|
|
feedBack: {},
|
|
playSong: (fn, arr, opts) => played.push({ fn: decodeURIComponent(fn), arr, opts }),
|
|
fbNotify: null,
|
|
},
|
|
};
|
|
// eslint-disable-next-line no-new-func
|
|
new Function('window', 'encodeURIComponent', iife)(sandbox.window, encodeURIComponent);
|
|
return { q: sandbox.window.feedBack.playQueue, played };
|
|
}
|
|
|
|
function drain(q, played) {
|
|
while (q.hasNext()) q.advance();
|
|
return played.map((p) => p.fn);
|
|
}
|
|
|
|
test('shuffle: same multiset, order from the seeded RNG, arrangements follow files', () => {
|
|
const files = ['a.sloppak', 'b.sloppak', 'c.sloppak', 'd.sloppak'];
|
|
const arrs = [0, 1, 2, 3]; // arrangement i belongs to files[i]
|
|
const origRandom = Math.random;
|
|
try {
|
|
// Deterministic RNG so the expected order is checkable.
|
|
let calls = 0;
|
|
const seq = [0.1, 0.9, 0.5];
|
|
Math.random = () => seq[calls++ % seq.length];
|
|
const { q, played } = makeQueue();
|
|
q.start(files.slice(), { arrangements: arrs.slice(), shuffle: true });
|
|
const order = drain(q, played);
|
|
assert.deepStrictEqual(order.slice().sort(), files.slice().sort()); // nothing lost/duplicated
|
|
// Each played file carries the arrangement it started with.
|
|
played.forEach((p) => {
|
|
assert.strictEqual(p.arr, arrs[files.indexOf(p.fn)]);
|
|
});
|
|
} finally {
|
|
Math.random = origRandom;
|
|
}
|
|
});
|
|
|
|
test('shuffle can change the order', () => {
|
|
const origRandom = Math.random;
|
|
try {
|
|
Math.random = () => 0; // j = 0 every swap → deterministic rotation, ≠ input order
|
|
const { q, played } = makeQueue();
|
|
q.start(['a', 'b', 'c'], { shuffle: true });
|
|
const order = drain(q, played);
|
|
assert.notDeepStrictEqual(order, ['a', 'b', 'c']);
|
|
} finally {
|
|
Math.random = origRandom;
|
|
}
|
|
});
|
|
|
|
test('no shuffle opt preserves order and caller arrays are never mutated', () => {
|
|
const files = ['a', 'b', 'c'];
|
|
const arrs = [2, 0, 1];
|
|
const { q, played } = makeQueue();
|
|
q.start(files, { arrangements: arrs });
|
|
assert.deepStrictEqual(drain(q, played), ['a', 'b', 'c']);
|
|
assert.deepStrictEqual(files, ['a', 'b', 'c']);
|
|
assert.deepStrictEqual(arrs, [2, 0, 1]);
|
|
|
|
// shuffle:true must also leave the caller's arrays alone (start slices).
|
|
const { q: q2 } = makeQueue();
|
|
q2.start(files, { arrangements: arrs, shuffle: true });
|
|
assert.deepStrictEqual(files, ['a', 'b', 'c']);
|
|
assert.deepStrictEqual(arrs, [2, 0, 1]);
|
|
});
|