diff --git a/frontend/src/components/cover-grid/cover-grid-styles.ts b/frontend/src/components/cover-grid/cover-grid-styles.ts index 946283f..544c60a 100644 --- a/frontend/src/components/cover-grid/cover-grid-styles.ts +++ b/frontend/src/components/cover-grid/cover-grid-styles.ts @@ -111,13 +111,32 @@ const gridStyles = css` scale: 0.95; } + /* Title and year on one line, and only the title truncates. + + The year used to be part of the same run of text, so it was the + first thing an ellipsis ate: a card wide enough for a long album + name never showed its year, and browsing by year showed years + only for the albums with short names -- the sort said one thing + and the cards showed another. + + A flex row rather than a second line, because the card's height + is what the virtualizer measures rows by. */ .album-name { font-size: var(--album-name-font, 14px); font-weight: 400; color: var(--yj-text-primary, #fff); + display: flex; + justify-content: center; + align-items: baseline; + gap: 0.35em; + min-width: 0; + } + + .album-title { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + min-width: 0; } .artist-name { @@ -131,6 +150,8 @@ const gridStyles = css` .album-year { color: var(--yj-text-tertiary, #888); + flex: 0 0 auto; + white-space: nowrap; } /* ======================================== diff --git a/frontend/src/components/cover-grid/cover-grid.ts b/frontend/src/components/cover-grid/cover-grid.ts index 540f4ad..83ac1aa 100644 --- a/frontend/src/components/cover-grid/cover-grid.ts +++ b/frontend/src/components/cover-grid/cover-grid.ts @@ -1898,10 +1898,10 @@ export class CoverGrid class="album-name" title="${album.Name}" > - ${album.Name}${album.Year - ? html` - - (${album.Year})${album.Name}${album.Year + ? html`(${album.Year})` : nothing} diff --git a/frontend/test/components/album-card-year.test.ts b/frontend/test/components/album-card-year.test.ts new file mode 100644 index 0000000..ce57a48 --- /dev/null +++ b/frontend/test/components/album-card-year.test.ts @@ -0,0 +1,87 @@ +/** + * The year on an album card survives a long album name. + * + * The year used to be part of the same run of text as the title, inside + * one `text-overflow: ellipsis` box — so it was the first thing the + * ellipsis ate. A card wide enough for a long name never showed its + * year at all, which means sorting the grid *by year* showed years only + * for the albums with short names: the sort said one thing and the + * cards showed another. + * + * The fix is a flex row in which only the title truncates, rather than + * a second line, because the card's height is what the virtualizer + * measures rows by. + */ +import { describe, expect, it, beforeEach } from 'vitest'; +import type { LitElement } from 'lit'; + +import '@components/cover-grid/cover-grid'; +import { emit, stub, flush, resetHarness } from '@test/support/harness'; +import { Events } from '../../src/events'; +import { fixture, shadowAll } from '@test/support/render'; + +const LONG = + 'The Rise and Fall of a Midwest Princess in the Key of Everything'; + +/** Long names throughout: the fault only shows on a card under + * pressure, and a grid of "Album 3" proves nothing. */ +const ALBUMS = Array.from({ length: 12 }, (_, i) => ({ + ID: i + 1, + Name: `${LONG} ${i + 1}`, + ArtistName: 'Aurora Fields', + Year: 2019 + (i % 5), +})); + +/** Give the virtualizer a viewport; a zero-height host renders nothing. */ +function sized(el: HTMLElement): void { + el.style.display = 'block'; + el.style.height = '600px'; + el.style.width = '900px'; +} + +async function settle(el: LitElement): Promise { + await flush(); + await el.updateComplete; + await new Promise((r) => setTimeout(r, 80)); +} + +describe('the album card’s year', () => { + beforeEach(() => { + resetHarness(); + stub('library.Library.GetAlbums', ALBUMS); + stub('library.Library.GetTracks', []); + emit(Events.LibraryScanComplete); + }); + + it('is rendered on every card, however long the name', async () => { + const el = await fixture('cover-grid'); + + sized(el); + await settle(el); + + const cards = shadowAll(el, '.album-card'); + const years = shadowAll(el, '.album-year'); + + expect(cards.length).toBeGreaterThan(0); + expect(years).toHaveLength(cards.length); + expect(years.every((y) => /^\(\d{4}\)$/.test(y.textContent!.trim()))).toBe( + true, + ); + }); + + it('is not what the ellipsis eats', async () => { + const el = await fixture('cover-grid'); + + sized(el); + await settle(el); + + const year = shadowAll(el, '.album-year')[0]!; + const title = shadowAll(el, '.album-title')[0]!; + + // The title is the box that gives way... + expect(title.scrollWidth).toBeGreaterThan(title.clientWidth); + // ...and the year keeps every pixel it asked for. + expect(year.clientWidth).toBeGreaterThan(0); + expect(year.scrollWidth).toBeLessThanOrEqual(year.clientWidth + 1); + }); +});