fix: RC3 queue link underline, genre album artist split, Artists "Other" bucket (#977)

* fix: RC3 queue link underline, genre album artist split, Artists "Other" bucket

Three zunoz reports, one change:

- Queue now-playing card: the artist and album links now underline on hover
  (not just recolour), matching clickable names everywhere else. The album
  link sits on .queue-current-sub itself; artist links are nested .is-link
  spans — both selectors covered.

- Genre album cards split multi-artist credits into individual links like the
  rest of the app. Root cause was the local-index genre query hardcoding NULL
  for the album raw_json column, so OpenSubsonic artists[] never reached the
  card and it fell back to the flat "A • B" single link. Select a.raw_json
  instead (parity with the All Albums / advanced-search album queries).

- Artists page alphabet index: # is now digits-only, and a new "Other" bucket
  collects accented Latin (Æ/Ø/Å…) and non-Latin scripts (CJK, Cyrillic, …)
  that previously fell into the # catch-all. Adds artistBucketKey/compareBuckets
  helpers (unit-tested), an OTHER bucket sorted last, and the artists.other
  label across 9 locales.

* docs(changelog): queue link hover, genre card artist split, Artists Other bucket (#977)
This commit is contained in:
Frank Stellmacher
2026-06-04 03:13:45 +02:00
committed by GitHub
parent 88df194808
commit 47e16ebfef
17 changed files with 109 additions and 18 deletions
@@ -0,0 +1,48 @@
import { describe, expect, it } from 'vitest';
import { artistBucketKey, compareBuckets, OTHER_BUCKET, ALPHABET } from './artistsHelpers';
describe('artistBucketKey', () => {
it('buckets AZ names by their uppercased first letter', () => {
expect(artistBucketKey('Adele')).toBe('A');
expect(artistBucketKey('zz top')).toBe('Z');
expect(artistBucketKey('mGla')).toBe('M');
});
it('puts digit-leading names in #', () => {
expect(artistBucketKey('2Pac')).toBe('#');
expect(artistBucketKey('50 Cent')).toBe('#');
expect(artistBucketKey('999')).toBe('#');
});
it('puts accented Latin and non-Latin scripts in Other (not #)', () => {
expect(artistBucketKey('Ärzte')).toBe(OTHER_BUCKET);
expect(artistBucketKey('Øde')).toBe(OTHER_BUCKET);
expect(artistBucketKey('Å-band')).toBe(OTHER_BUCKET);
expect(artistBucketKey('이영지')).toBe(OTHER_BUCKET); // Korean
expect(artistBucketKey('くるり')).toBe(OTHER_BUCKET); // Japanese
expect(artistBucketKey('Кино')).toBe(OTHER_BUCKET); // Cyrillic
expect(artistBucketKey('王菲')).toBe(OTHER_BUCKET); // Chinese
});
it('puts symbol-leading and empty names in Other', () => {
expect(artistBucketKey('!!!')).toBe(OTHER_BUCKET);
expect(artistBucketKey(' ')).toBe(OTHER_BUCKET);
expect(artistBucketKey('')).toBe(OTHER_BUCKET);
});
it('ignores leading whitespace', () => {
expect(artistBucketKey(' Beatles')).toBe('B');
});
});
describe('compareBuckets', () => {
it('orders # first, then AZ, then Other last', () => {
const shuffled = ['OTHER', 'M', '#', 'A', 'Z'];
expect([...shuffled].sort(compareBuckets)).toEqual(['#', 'A', 'M', 'Z', 'OTHER']);
});
it('ALPHABET ends with the Other bucket', () => {
expect(ALPHABET[ALPHABET.length - 1]).toBe(OTHER_BUCKET);
expect(ALPHABET).toContain('#');
});
});