Ctrl K
ComunidadGuías…

packages/app/component-tests/timeline-virtualizer.spec.ts

Rama dev · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { expect, sourceURL, story } from "../../stories/playwright/story"

const fixture = sourceURL(new URL("./timeline-virtualizer.fixture.tsx", import.meta.url))

story.beforeEach(async ({ mount }) => {
  const component = await mount("opencode-composer-flow--mixed-attachments")
  await expect(component.getByRole("textbox", { name: "Prompt", exact: true })).toBeVisible()
})

story("spaces the first mobile message without changing desktop spacing", async ({ page }) => {
  await page.setViewportSize({ width: 390, height: 844 })
  await page.evaluate(async (fixture) => {
    const { mountTimelineVirtualizer } = await import(fixture)
    mountTimelineVirtualizer({ count: 1, rowHeight: 60, immediate: true })
  }, fixture)
  const root = page.getByTestId("timeline-virtualizer-fixture")
  await root.getByRole("button", { name: "Complete Markdown", exact: true }).click()
  const content = root.locator("[data-timeline-virtual-content]")
  await expect(content).toHaveCSS("visibility", "visible")

  const gap = () =>
    root.locator('[data-timeline-key="user-message:message-0"]').evaluate((element) => {
      const viewport = element.closest("[data-scrollable]")!

      return element.getBoundingClientRect().top - viewport.getBoundingClientRect().top
    })

  await expect.poll(gap).toBe(16)
  await root.evaluate((element) => element.setAttribute("dir", "rtl"))
  await expect.poll(gap).toBe(16)
  await page.setViewportSize({ width: 1280, height: 900 })
  await expect.poll(gap).toBe(0)
  await page.setViewportSize({ width: 390, height: 844 })
  await expect.poll(gap).toBe(16)
})

story("bounds the cheap suffix and reveals only ready measured rows", async ({ page }) => {
  await page.evaluate(async (fixture) => {
    const { mountTimelineVirtualizer } = await import(fixture)
    mountTimelineVirtualizer({ count: 100, rowHeight: 60, immediate: true })
  }, fixture)
  const root = page.getByTestId("timeline-virtualizer-fixture")
  const content = root.locator("[data-timeline-virtual-content]")
  await expect(root).toHaveAttribute("data-observed-height", "180")
  await expect(content).toHaveCSS("visibility", "hidden")
  await expect(content.locator("[data-timeline-key]")).toHaveCount(4)
  await root.getByRole("button", { name: "Complete Markdown", exact: true }).click()
  await expect(content).toHaveCSS("visibility", "visible")
  await expect(root).toHaveAttribute("data-first-reveal", /.+/)
  expect(await root.evaluate((element) => JSON.parse(element.dataset.firstReveal!))).toMatchObject({
    rows: [96, 97, 98, 99],
    pendingMarkdown: 0,
    clipped: [],
    viewportHeight: 180,
  })
})

story("reveals at the end when Markdown never reports ready", async ({ page }) => {
  // A block that never set data-markdown-ready kept every reveal check waiting, and the timeline stayed invisible.
  await page.evaluate(async (fixture) => {
    const { mountTimelineVirtualizer } = await import(fixture)
    mountTimelineVirtualizer({ count: 100, rowHeight: 60, immediate: true })
  }, fixture)
  const root = page.getByTestId("timeline-virtualizer-fixture")
  const content = root.locator("[data-timeline-virtual-content]")
  await expect(content).toHaveCSS("visibility", "hidden")
  await expect(content).toHaveCSS("visibility", "visible")
  const reveal = await root.evaluate((element) => JSON.parse(element.dataset.firstReveal!))
  expect(reveal.pendingMarkdown).toBeGreaterThan(0)
  expect(reveal.rows).toContain(99)
  const viewport = root.locator("[data-scrollable]")
  await expect
    .poll(() => viewport.evaluate((element) => element.scrollHeight - element.clientHeight - element.scrollTop))
    .toBeLessThanOrEqual(1)
})

story("treats the reachable end as the end under page zoom", async ({ page }) => {
  // At 110% the browser stops scrolling more than a pixel short of scrollHeight - clientHeight. A mount that waited
  // for that distance to close stayed blank, or froze the renderer in a microtask loop, and scrolling back down to
  // the bottom never resumed following.
  await page.evaluate(async (fixture) => {
    document.documentElement.style.zoom = "1.1"
    const { mountTimelineVirtualizer } = await import(fixture)
    mountTimelineVirtualizer({ count: 4, rowHeight: 57.8, immediate: true, viewportHeight: 200.5 })
  }, fixture)
  const root = page.getByTestId("timeline-virtualizer-fixture")
  const content = root.locator("[data-timeline-virtual-content]")
  await root.getByRole("button", { name: "Complete Markdown", exact: true }).click()
  await expect(content).toHaveCSS("visibility", "visible")
  const viewport = root.locator("[data-scrollable]")
  expect(
    await viewport.evaluate((element) => element.scrollHeight - element.clientHeight - element.scrollTop),
  ).toBeGreaterThan(1)

  const controls = root.getByTestId("timeline-controls")
  await viewport.dispatchEvent("wheel", { deltaY: -1 })
  await expect(controls).toHaveAttribute("data-pinned", "false")

  for (const top of [40, Number.MAX_SAFE_INTEGER])
    await viewport.evaluate(
      (element, top) =>
        new Promise((resolve) => {
          element.addEventListener("scroll", resolve, { once: true })
          element.scrollTop = top
        }),
      top,
    )
  await expect(controls).toHaveAttribute("data-pinned", "true")
})

story("leaves a reader who scrolled up in place after a scroll to the zoomed end", async ({ page }) => {
  // At 110% the end the browser reaches sits more than a pixel short of scrollHeight - clientHeight. TanStack kept
  // scrolling toward that max on every frame for five seconds, so a row that grew meanwhile pulled the reader back down.
  await page.evaluate(async (fixture) => {
    document.documentElement.style.zoom = "1.1"
    const { mountTimelineVirtualizer } = await import(fixture)
    mountTimelineVirtualizer({ count: 40, rowHeight: 57.8, immediate: true, viewportHeight: 200.5 })
  }, fixture)
  const root = page.getByTestId("timeline-virtualizer-fixture")
  await root.getByRole("button", { name: "Complete Markdown", exact: true }).click()
  await expect(root.locator("[data-timeline-virtual-content]")).toHaveCSS("visibility", "visible")
  const viewport = root.locator("[data-scrollable]")
  const distance = () => viewport.evaluate((element) => element.scrollHeight - element.clientHeight - element.scrollTop)
  expect(await distance()).toBeGreaterThan(1.01)

  // A scroll command settles or scrolls again on its next frame.
  const frames = () =>
    page.evaluate(() => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve))))

  await root.getByRole("button", { name: "Scroll to end", exact: true }).click()
  await frames()
  // The wheel and the scroll it causes arrive together. A wheel alone would leave an earlier arrival at the end free to
  // pin again once that scroll settles.
  await viewport.evaluate(
    (element) =>
      new Promise((resolve) => {
        element.addEventListener("scroll", resolve, { once: true })
        element.dispatchEvent(new WheelEvent("wheel", { deltaY: -1, bubbles: true }))
        element.scrollTop -= 40
      }),
  )
  await expect(root.getByTestId("timeline-controls")).toHaveAttribute("data-pinned", "false")
  const height = await viewport.evaluate((element) => element.scrollHeight)
  await root.getByRole("button", { name: "Grow last row", exact: true }).click()
  await expect.poll(() => viewport.evaluate((element) => element.scrollHeight)).toBeGreaterThan(height + 30)
  await frames()
  expect(await distance()).toBeGreaterThan(70)
})

story("holds a revealed tool inside a group of several through a later resize", async ({ page }) => {
  // The anchor looked only for an element naming the tool alone, so a grouped tool let go on the next resize.
  await page.evaluate(async (fixture) => {
    const { mountTimelineVirtualizer } = await import(fixture)
    mountTimelineVirtualizer({ count: 40, rowHeight: 60, immediate: true })
  }, fixture)
  const root = page.getByTestId("timeline-virtualizer-fixture")
  const controls = root.getByTestId("timeline-controls")
  await root.getByRole("button", { name: "Complete Markdown", exact: true }).click()
  await expect(root.locator("[data-timeline-virtual-content]")).toHaveCSS("visibility", "visible")
  await expect(controls).toHaveAttribute("data-pinned", "true")

  await root.getByRole("button", { name: "Anchor first row", exact: true }).click()
  await expect(root.locator('[data-timeline-part-ids="prt_first,prt_grouped"]')).toBeAttached()
  await root.getByRole("button", { name: "Grow last row", exact: true }).click()

  const viewport = root.locator("[data-scrollable]")
  await expect.poll(() => viewport.evaluate((element) => element.scrollTop)).toBe(0)
  await expect(controls).toHaveAttribute("data-anchored", "true")
})

for (const input of [
  { name: "offset-only", count: 1, rowHeight: 600, reveal: { scrollTop: 0 } },
  { name: "zero-height", count: 4, rowHeight: 60, reveal: {} },
]) {
  story(`reveals ready measured rows after an ${input.name} reconnect`, async ({ page }) => {
    await page.evaluate(
      async ({ fixture, input }) => {
        const { mountTimelineVirtualizer } = await import(fixture)
        mountTimelineVirtualizer(input)
      },
      { fixture, input },
    )
    const root = page.getByTestId("timeline-virtualizer-fixture")
    const content = root.locator("[data-timeline-virtual-content]")
    await expect(root).toHaveAttribute("data-observed-height", "180")
    await expect(content).toHaveCSS("visibility", "hidden")
    await expect(content.locator("[data-timeline-key]")).toHaveCount(1)

    if (input.name === "offset-only") {
      // Pinned to the end: the 600px row and the virtualizer's 92px tail, less the 180px viewport.
      await expect(root).toHaveAttribute("data-last-scroll-top", "512")
      await root.locator("[data-scrollable]").dispatchEvent("wheel", { deltaY: -1 })
      await expect(root.getByTestId("timeline-controls")).toHaveAttribute("data-pinned", "false")
    }

    if (input.name === "zero-height") {
      await root.getByRole("button", { name: "Hide viewport", exact: true }).click()
      // Wait for ResizeObserver to clear the actual range, not just for display:none.
      await expect(root).toHaveAttribute("data-observed-height", "0")
      await expect(content.locator("[data-timeline-key]")).toHaveCount(0)
    }

    await expect(root).not.toHaveAttribute("data-first-reveal")
    const resizes = await root.getAttribute("data-viewport-resizes")
    await root.getByRole("button", { name: "Reconnect ready rows", exact: true }).click()
    await expect(content).toHaveCSS("visibility", "visible")
    await expect(root).toHaveAttribute("data-first-reveal", /.+/)
    expect(await root.evaluate((element) => JSON.parse(element.dataset.firstReveal!))).toMatchObject({
      rows: input.count === 1 ? [0] : [0, 1, 2, 3],
      pendingMarkdown: 0,
      clipped: [],
      viewportHeight: 180,
      ...input.reveal,
    })

    if (input.name === "offset-only") {
      // This repair must not depend on another native scroll or resize delivery.
      await expect(root).toHaveAttribute("data-scrolls", "0")
      await expect(root).toHaveAttribute("data-viewport-resizes", resizes!)
    }
  })
}