Merge remote-tracking branch 'origin/main' into wails-v3
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* The track list on a phone (plan 016 B2 phase 4).
|
||||
*
|
||||
* Measured on the device this was built for: at 424 CSS px the four
|
||||
* configured columns fit the row *exactly* — `--grid-cols` came out
|
||||
* `24px 102px 101px 101px 80px` — and not one of them fit its content.
|
||||
* "Duration" did not fit its own header. The columns were never too
|
||||
* wide; there were too many of them.
|
||||
*
|
||||
* So a phone draws one column of two lines plus the duration, and the
|
||||
* split is a *column set* rather than a second row template: everything
|
||||
* about a row that is not "how many columns" keeps working, which is
|
||||
* what these tests pin. The `matchMedia` stub is the same one
|
||||
* `now-playing.test.ts` uses — the component reads the breakpoint from
|
||||
* JS because its grid is computed in JS, so this is the seam.
|
||||
*/
|
||||
import { describe, expect, it, afterEach } from 'vitest';
|
||||
import type { LitElement } from 'lit';
|
||||
|
||||
import '@components/track-list/track-list';
|
||||
import { fixture, shadow, shadowAll } from '@test/support/render';
|
||||
|
||||
const TRACKS = Array.from({ length: 12 }, (_, i) => ({
|
||||
FilePath: `/music/track-${i}.mp3`,
|
||||
TrackName: `Track ${i}`,
|
||||
ArtistName: `Artist ${i}`,
|
||||
Album: 'An Album',
|
||||
Duration: 180 + i,
|
||||
})) as never[];
|
||||
|
||||
const real = window.matchMedia.bind(window);
|
||||
|
||||
/** Force the shell's phone breakpoint on or off. */
|
||||
function stubPhone(phone: boolean): void {
|
||||
window.matchMedia = ((q: string) =>
|
||||
q.includes('max-width: 599px')
|
||||
? {
|
||||
matches: phone,
|
||||
media: q,
|
||||
addEventListener() {},
|
||||
removeEventListener() {},
|
||||
}
|
||||
: real(q)) as typeof window.matchMedia;
|
||||
}
|
||||
|
||||
async function mount(phone: boolean): Promise<LitElement> {
|
||||
stubPhone(phone);
|
||||
|
||||
// Narrow, so a desktop layout at this width would be the cramped one
|
||||
// the plan describes rather than a comfortable one.
|
||||
const el = await fixture<LitElement>('track-list', {
|
||||
externalTracks: TRACKS,
|
||||
});
|
||||
|
||||
el.style.width = '424px';
|
||||
el.style.height = '400px';
|
||||
await el.updateComplete;
|
||||
|
||||
return el;
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
window.matchMedia = real;
|
||||
localStorage.removeItem('track-list-column-widths');
|
||||
});
|
||||
|
||||
describe('the track list at phone width', () => {
|
||||
it('draws the title with the artist under it, and the duration', async () => {
|
||||
const el = await mount(true);
|
||||
const row = shadow(el, '.track-row');
|
||||
|
||||
expect(row).not.toBeNull();
|
||||
expect(shadow(el, '.stacked-title')?.textContent?.trim()).toBe('Track 0');
|
||||
expect(shadow(el, '.stacked-sub')?.textContent?.trim()).toBe('Artist 0');
|
||||
|
||||
// Two drawn columns plus the favourite: three grid tracks, not five.
|
||||
const tracks = getComputedStyle(row as Element)
|
||||
.gridTemplateColumns.split(/\s+/)
|
||||
.filter(Boolean);
|
||||
|
||||
expect(tracks).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('drops the column headers and the resize handles', async () => {
|
||||
const el = await mount(true);
|
||||
|
||||
// Both are pointer affordances: a header is where a click sorts and
|
||||
// a handle is where a drag resizes, and a phone can do neither.
|
||||
expect(shadow(el, '.header-row')).toBeNull();
|
||||
expect(shadowAll(el, '.col-resize-handle')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('keeps every sort the desktop offers', async () => {
|
||||
const el = await mount(true);
|
||||
const header = shadow(el, 'page-header') as
|
||||
| (Element & { sortOptions?: { id: string }[] })
|
||||
| null;
|
||||
|
||||
// The regression this guards: building the sort list from the
|
||||
// *drawn* columns would leave a phone able to sort by title and
|
||||
// duration only, with no column headers to reach the rest by.
|
||||
const ids = (header?.sortOptions ?? []).map((o) => o.id);
|
||||
|
||||
expect(ids).toContain('artistName');
|
||||
expect(ids).toContain('album');
|
||||
});
|
||||
|
||||
it('still marks the row a screen reader has to understand', async () => {
|
||||
const el = await mount(true);
|
||||
const row = shadow(el, '.track-row');
|
||||
|
||||
// The row is the same row: only the cells inside it changed, which
|
||||
// is the entire argument for doing this as a column set.
|
||||
expect(row?.getAttribute('role')).toBe('row');
|
||||
expect(row?.getAttribute('aria-selected')).toBe('false');
|
||||
expect(row?.getAttribute('data-testid')).toBe('track-row');
|
||||
expect(shadowAll(el, '[role="gridcell"]').length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('ignores widths saved for the desktop, and does not overwrite them', async () => {
|
||||
// The bug the device found, in the fixture that reproduces it.
|
||||
// `loadColumnWidths` is keyed by column *id* and fills a gap with
|
||||
// MIN_COLUMN_WIDTH, so the phone's stacked column -- which nothing
|
||||
// can ever have saved a width for -- came out at the minimum while
|
||||
// the duration column inherited a width dragged on a wide window:
|
||||
// measured `24px 148px 236px` on a 424px phone.
|
||||
const desktop = { trackName: 300, artistName: 200, album: 200, trackLength: 236 };
|
||||
|
||||
localStorage.setItem('track-list-column-widths', JSON.stringify(desktop));
|
||||
|
||||
const el = await mount(true);
|
||||
const tracks = getComputedStyle(shadow(el, '.track-row') as Element)
|
||||
.gridTemplateColumns.split(/\s+/)
|
||||
.filter(Boolean)
|
||||
.map((t) => Math.round(parseFloat(t)));
|
||||
|
||||
// Favourite, then the stacked column, then the duration -- and the
|
||||
// stacked one is the widest thing in the row.
|
||||
expect(tracks[1]).toBeGreaterThan(tracks[2] ?? 0);
|
||||
|
||||
// And the desktop's own widths survive being on a phone: writing the
|
||||
// computed phone widths back would silently replace the width the
|
||||
// user dragged for the same column id.
|
||||
expect(JSON.parse(localStorage.getItem('track-list-column-widths') ?? '{}'))
|
||||
.toMatchObject(desktop);
|
||||
});
|
||||
|
||||
it('leaves the desktop alone', async () => {
|
||||
const el = await mount(false);
|
||||
const row = shadow(el, '.track-row');
|
||||
|
||||
expect(shadow(el, '.header-row')).not.toBeNull();
|
||||
expect(shadow(el, '.stacked-title')).toBeNull();
|
||||
|
||||
const tracks = getComputedStyle(row as Element)
|
||||
.gridTemplateColumns.split(/\s+/)
|
||||
.filter(Boolean);
|
||||
|
||||
expect(tracks).toHaveLength(5);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user