feat(shell): take the top bar out of the phone's layout
The row is deleted from the grid template below 600px, not the header hidden. That is 3.25em of a 439 CSS px viewport -- the single biggest vertical win the reference device has to give, and the reason the issue asks for the row rather than for a smaller bar. Each of the five things the bar held has somewhere else to be there: nav-history is the platform's own back gesture and was already gone from 899 down, the job indicator is <job-band> (#62, which is what this was blocked on), the search box is a modal opened from the view's own header, the library filter is Settings -> Libraries, and the wordmark stays where it is. Three things are load-bearing. **The header is visually hidden rather than display: none**, because that h1 is the document's top-level heading and several pages have no other one -- page-header renders no h1 when its heading is empty, and Settings has no page-header at all. Its four controls are display: none *inside* it, which is what keeps them out of the tab order: a visually-hidden container is still focusable, and tabbing into a search box nobody can see is worse than not having one. **The fit pass stands down**, from the bar's computed position rather than from a width. With the bar out of flow there is no content box to measure children against, and a pass that ran would collapse the wordmark on every resize and report success about a 1px box. **top-bar-fit.spec.ts keeps 390 and asserts the stronger property.** "Nothing hangs out of the bar" is trivially true of a bar with no row and would pass on a build that merely broke it, so what that width asks now is that the content starts where the row above it ends. Measuring against the window instead would have been asserting "and no background job is running", which that spec is not about and cannot arrange. Closes #57
This commit is contained in:
@@ -26,10 +26,22 @@ type Page = import('@playwright/test').Page;
|
||||
* 600 is the bottom of the Compact band (#24) and where the defect
|
||||
* lands; 899 and 900 straddle `nav-history` appearing (68px more to
|
||||
* find, at the width that just gained the sidebar's labels); 800 is the
|
||||
* enforced minimum; 390 is a phone, where the answer must be that
|
||||
* nothing collapses because the media queries already did the work.
|
||||
* enforced minimum.
|
||||
*
|
||||
* **390 is kept, and what it asks changed with #57.** There is no bar
|
||||
* to fit below 600px any more — it is out of the grid and visually
|
||||
* hidden — so "nothing hangs out of it" is a claim about an element
|
||||
* with no row, and would pass on a build that had merely broken the
|
||||
* bar. Dropping the width would be dropping the one place this file
|
||||
* can still say something true about a phone, so it asserts the
|
||||
* *stronger* property instead, below: the bar is out of the layout
|
||||
* altogether, which is the thing #57 wanted and the thing that makes
|
||||
* fitting moot.
|
||||
*/
|
||||
const WIDTHS = [390, 600, 800, 899, 900, 1440];
|
||||
const WIDTHS = [600, 800, 899, 900, 1440];
|
||||
|
||||
/** Where #57 leaves the bar, and where the desktop still has one. */
|
||||
const PHONE_WIDTH = 390;
|
||||
|
||||
/**
|
||||
* A scan whose title is as long as a real one gets. The label is capped
|
||||
@@ -90,6 +102,56 @@ const collapsed = (page: Page) =>
|
||||
}));
|
||||
|
||||
test.describe('the top bar fits the window', () => {
|
||||
/**
|
||||
* The phone's answer, which is not "it fits" (#57).
|
||||
*
|
||||
* The bar has no grid row below 600px, so measuring its children
|
||||
* against its content box is measuring a 1px box that is already
|
||||
* invisible — a fit pass would collapse the wordmark every time and
|
||||
* report success about nothing, which is why `measureTopBarFit`
|
||||
* declines to run at all when the bar is out of flow. What is worth
|
||||
* asserting here is that the fit pass has not quietly started
|
||||
* *undoing* that: a rule that put the bar back in the layout would
|
||||
* pass every assertion in this file and cost a 439px screen 12% of
|
||||
* its height.
|
||||
*/
|
||||
test(`the bar is out of the layout at ${PHONE_WIDTH}px, with a job running`, async ({
|
||||
app,
|
||||
testctl,
|
||||
}) => {
|
||||
await app.setViewportSize({ width: PHONE_WIDTH, height: 600 });
|
||||
await testctl.emit('JobsChanged', [LONG_JOB]);
|
||||
|
||||
// Not merely hidden: `display: none` on the header would satisfy
|
||||
// "invisible" and leave the 3.25em row exactly where it was. So
|
||||
// the assertion is that the content starts where the row above it
|
||||
// ends -- and with a job staged, the row above it is the jobs
|
||||
// band, which is the whole reason this row could go.
|
||||
await expect
|
||||
.poll(() =>
|
||||
app.evaluate(() => {
|
||||
const bar = document.querySelector<HTMLElement>('header.top-bar')!;
|
||||
const main = document.querySelector<HTMLElement>('.main-panel')!;
|
||||
const band = document.querySelector<HTMLElement>('job-band')!;
|
||||
|
||||
return {
|
||||
position: getComputedStyle(bar).position,
|
||||
gap:
|
||||
Math.round(main.getBoundingClientRect().top) -
|
||||
Math.round(band.getBoundingClientRect().bottom),
|
||||
};
|
||||
}),
|
||||
)
|
||||
.toEqual({ position: 'absolute', gap: 0 });
|
||||
|
||||
// And the work is still visible, in the band that replaced the
|
||||
// indicator (#62) — which is what made this row removable at all.
|
||||
await expect(app.locator('job-indicator')).toBeHidden();
|
||||
await expect(app.locator('job-band').locator('job-row')).toHaveCount(1);
|
||||
|
||||
await app.setViewportSize({ width: 1440, height: 900 });
|
||||
});
|
||||
|
||||
for (const width of WIDTHS) {
|
||||
test(`no control sits outside the bar at ${width}px, idle`, async ({
|
||||
app,
|
||||
@@ -108,23 +170,7 @@ test.describe('the top bar fits the window', () => {
|
||||
|
||||
// The indicator has to actually be up, or this test passes by
|
||||
// measuring the idle case under another name.
|
||||
//
|
||||
// Below 600px there is deliberately no indicator to measure:
|
||||
// #62 stands it down and puts the rows in `<job-band>` instead,
|
||||
// in the layout under the bar. So at 390 the assertion is that
|
||||
// it *is* away and the bar still fits -- which is the same
|
||||
// property (the bar has nothing hanging out of it) reached by the
|
||||
// other branch of the same rule, rather than a width quietly
|
||||
// dropped from the list.
|
||||
const phone = width < 600;
|
||||
|
||||
await expect(app.locator('job-indicator'))[
|
||||
phone ? 'toBeHidden' : 'toBeVisible'
|
||||
]();
|
||||
|
||||
if (phone) {
|
||||
await expect(app.locator('job-band').locator('job-row')).toHaveCount(1);
|
||||
}
|
||||
await expect(app.locator('job-indicator')).toBeVisible();
|
||||
|
||||
await expect.poll(() => overflowingChildren(app)).toEqual([]);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user