Measure the bar where it settles, not mid-spring
`widgets.spec.ts` took a one-shot `boundingBox()` of a fill that is still moving. Two ways that reads wrong. "a bar draws a row per reading" compared two widths that are both zero until the socket has delivered the readings, and a plain `expect` over a captured number does not retry. "a reading over its scale fills the track and no more" allowed the fill one pixel past the track, but the house spring is underdamped: `motion`'s own generator puts the fluksio look at 100.66% of the track 276ms in (material 100.91%, glass 103.58%), which on this tile's ~240px track is ~1.6px — over the tolerance, and reached only if the sample happens to land near the peak. Neither is the widget. `fractionOf` clamps the reading to [0, 1] and `.dui-bar-track` is `overflow: hidden`, so nothing past a full track is ever drawn; a bounding box just is not clipped by an ancestor's overflow. So both now poll: one until the shorter reading is drawn shorter, the other until the fill is flush with its track — which is a stricter reading of "and no more" than the pixel of slack was. The live-socket test gains the other half of what that spin cost: an ASGI task that never returns is one uvicorn's graceful shutdown waits on forever, which is why `fluksio serve` could only be SIGKILLed and left `serve.pid` behind.
This commit is contained in:
@@ -507,6 +507,12 @@ async def test_the_live_socket_ends_when_its_client_goes(
|
||||
`receive_text` raises for good once the peer has gone, so reading it again
|
||||
spun this loop at full tilt: the event loop stalled, `/health` answered
|
||||
503 and one queue getter was orphaned per turn.
|
||||
|
||||
It is also what made `fluksio serve` unstoppable. A handler that never
|
||||
returns is an ASGI task uvicorn never sees finish, and its graceful
|
||||
shutdown waits on those with no timeout — so SIGTERM logged "Waiting for
|
||||
background tasks to complete" and nothing else, leaving `serve.pid` behind
|
||||
because only SIGKILL ended the process. This returning is what ends that.
|
||||
"""
|
||||
monkeypatch.setattr(flows_route, "user_from_token", lambda session, token: object())
|
||||
|
||||
|
||||
@@ -312,14 +312,24 @@ test("a bar draws a row per reading", async ({ page }) => {
|
||||
await expect(rows).toHaveCount(2)
|
||||
|
||||
const fills = page.getByTestId("bar-fill")
|
||||
const outer = await fills.nth(0).boundingBox()
|
||||
const inner = await fills.nth(1).boundingBox()
|
||||
expect(outer).not.toBeNull()
|
||||
expect(inner).not.toBeNull()
|
||||
expect(
|
||||
inner!.width,
|
||||
"the smaller reading is not drawn as the shorter bar",
|
||||
).toBeLessThan(outer!.width)
|
||||
const width = (index: number) =>
|
||||
fills
|
||||
.nth(index)
|
||||
.boundingBox()
|
||||
.then((box) => box?.width ?? 0)
|
||||
// Both fills spring up from zero once the reading lands, so a single
|
||||
// measurement can be taken before the socket has delivered either — two
|
||||
// widths of nothing, where "shorter" is not true of anything.
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
const outer = await width(0)
|
||||
const inner = await width(1)
|
||||
return inner > 0 && inner < outer
|
||||
},
|
||||
{ message: "the smaller reading is not drawn as the shorter bar" },
|
||||
)
|
||||
.toBe(true)
|
||||
|
||||
// Told apart by colour rather than by nesting, which is what lets a row
|
||||
// carry a scale of its own.
|
||||
@@ -338,16 +348,26 @@ test("a reading over its scale fills the track and no more", async ({
|
||||
await publish(page, w("pv"), 120)
|
||||
await expect(page.getByText(/120\.00? kW/)).toBeVisible()
|
||||
|
||||
const fill = await page.getByTestId("bar-fill").nth(1).boundingBox()
|
||||
const track = await page
|
||||
.getByTestId("bar-row")
|
||||
.nth(1)
|
||||
.locator(".dui-bar-track")
|
||||
.boundingBox()
|
||||
expect(
|
||||
fill!.width,
|
||||
"a value over the scale runs off the end of its track",
|
||||
).toBeLessThanOrEqual(track!.width + 1)
|
||||
// `fractionOf` clamps the reading to the scale and the track clips what is
|
||||
// drawn, so nothing past a full track is ever *shown*. The fill element is
|
||||
// sprung there, and the house spring is underdamped — it reaches 100.7% of
|
||||
// the track about 280ms in, clipped and invisible but measurable, because a
|
||||
// bounding box is not clipped by an ancestor's overflow. Where it settles is
|
||||
// what "fills the track and no more" is about, so that is what is measured:
|
||||
// flush with the track, neither short of it nor over.
|
||||
const track = page.getByTestId("bar-row").nth(1).locator(".dui-bar-track")
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
const fill = await page.getByTestId("bar-fill").nth(1).boundingBox()
|
||||
const rail = await track.boundingBox()
|
||||
return Math.round(fill!.width - rail!.width)
|
||||
},
|
||||
{
|
||||
message: "a value over the scale does not settle flush with its track",
|
||||
},
|
||||
)
|
||||
.toBe(0)
|
||||
|
||||
await publish(page, w("pv"), 30)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user