Ctrl K
ComunidadGuías…

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

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

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

story("maps grouped and collapsed switches to the timeline settings", async ({ mount }) => {
  const component = await mount("settings-timeline-detail--interactive")
  await component.getByRole("button", { name: "Advanced", exact: true }).click()
  const shell = component.getByRole("group", { name: "Shell", exact: true })
  const grouped = shell.getByRole("switch", { name: "Shell grouped", exact: true })
  const collapsed = shell.getByRole("switch", { name: "Shell collapsed", exact: true })
  const value = component.locator('[data-slot="timeline-detail-fixture-value"]')

  await expect(component.getByRole("switch")).toHaveCount(10)
  await expect(component.getByText("Activity", { exact: true })).toHaveCount(0)
  await expect(grouped).toBeChecked()
  await expect(collapsed).toBeChecked()
  await shell.locator('[data-field="placement"] [data-slot="switch-control"]').click()
  await shell.locator('[data-field="details"] [data-slot="switch-control"]').click()
  await expect(value).toContainText('"shell":{"placement":"separate","details":"expanded"}')
  await grouped.focus()
  await grouped.press("Space")
  await collapsed.focus()
  await collapsed.press("Space")
  await expect(value).toContainText('"shell":{"placement":"grouped","details":"collapsed"}')
  await expect(component.getByRole("group", { name: "Subagents", exact: true }).getByRole("switch")).toHaveCount(1)
})

story("replaces hidden switches with solid lines and restores options", async ({ mount, page }) => {
  const component = await mount("settings-timeline-detail--interactive")
  await component.getByRole("button", { name: "Advanced", exact: true }).click()
  const shell = component.getByRole("group", { name: "Shell", exact: true })
  const visibility = shell.getByRole("button", { name: "Shell visibility" })
  const label = shell.locator('[data-slot="timeline-detail-activity"] > label')
  const color = await label.evaluate((element) => getComputedStyle(element).color)
  const iconColor = await visibility.evaluate((element) => getComputedStyle(element).color)
  await shell.locator('[data-field="placement"] [data-slot="switch-control"]').click()
  await shell.locator('[data-field="details"] [data-slot="switch-control"]').click()
  await visibility.hover()
  await expect(page.getByRole("tooltip")).toHaveText("Hide")
  expect(await page.getByRole("tooltip").evaluate((element) => element.getBoundingClientRect().bottom)).toBeLessThan(
    await visibility.evaluate((element) => element.getBoundingClientRect().top),
  )
  await visibility.click()

  await expect(visibility).toHaveAttribute("aria-pressed", "false")
  await expect(shell.getByRole("switch")).toHaveCount(0)
  await expect(shell.locator('[data-slot="timeline-detail-unavailable"]')).toHaveCount(2)
  await expect(shell.locator('[data-slot="timeline-detail-unavailable"]').first()).toHaveCSS(
    "border-top-style",
    "solid",
  )
  await expect(component.locator('[data-slot="timeline-detail-fixture-value"]')).toContainText(
    '"shell":{"placement":"hidden","details":"expanded"}',
  )
  await expect(label).not.toHaveCSS("color", color)
  await expect(visibility.locator("use")).toHaveAttribute("href", "#opencode-v2-icon-outline-eye-slash")
  await component.getByRole("button", { name: "Advanced", exact: true }).hover()
  await expect(visibility).not.toHaveCSS("color", iconColor)
  await expect(shell.locator('[data-slot="timeline-detail-unavailable"]').first()).toHaveCSS(
    "border-top-color",
    await visibility.evaluate((element) => getComputedStyle(element).color),
  )
  await visibility.hover()
  await expect(page.getByRole("tooltip")).toHaveText("Show")
  expect(await page.getByRole("tooltip").evaluate((element) => element.getBoundingClientRect().bottom)).toBeLessThan(
    await visibility.evaluate((element) => element.getBoundingClientRect().top),
  )
  await visibility.click()

  await expect(visibility).toHaveAttribute("aria-pressed", "true")
  await expect(label).toHaveCSS("color", color)
  await expect(visibility.locator("use")).toHaveAttribute("href", "#opencode-v2-icon-outline-eye")
  await expect(shell.locator('[data-slot="timeline-detail-unavailable"]')).toHaveCount(0)
  await expect(shell.getByRole("switch", { name: "Shell grouped", exact: true })).toBeEnabled()
  await expect(shell.getByRole("switch", { name: "Shell collapsed", exact: true })).toBeEnabled()
  await expect(shell.getByRole("switch", { name: "Shell grouped", exact: true })).not.toBeChecked()
  await expect(shell.getByRole("switch", { name: "Shell collapsed", exact: true })).not.toBeChecked()
})

story("toggles visibility by clicking the activity label", async ({ mount }) => {
  const component = await mount("settings-timeline-detail--interactive")
  await component.getByRole("button", { name: "Advanced", exact: true }).click()
  const shell = component.getByRole("group", { name: "Shell", exact: true })
  const label = shell.locator('[data-slot="timeline-detail-activity"] > label')
  const visibility = shell.getByRole("button", { name: "Shell visibility" })

  await expect(label).toHaveCSS("cursor", "default")
  await label.click()
  await expect(visibility).toHaveAttribute("aria-pressed", "false")
  await expect(shell.getByRole("switch")).toHaveCount(0)
  await expect(shell.locator('[data-slot="timeline-detail-unavailable"]')).toHaveCount(2)
  await label.click()
  await expect(visibility).toHaveAttribute("aria-pressed", "true")
  await expect(shell.getByRole("switch", { name: "Shell grouped", exact: true })).toBeChecked()
  await expect(shell.getByRole("switch", { name: "Shell collapsed", exact: true })).toBeChecked()
})

story("only highlights the eye when hovering the icon, not its activity label", async ({ mount }) => {
  const component = await mount("settings-timeline-detail--interactive")
  await component.getByRole("button", { name: "Advanced", exact: true }).click()
  const shell = component.getByRole("group", { name: "Shell", exact: true })
  const visibility = shell.getByRole("button", { name: "Shell visibility" })
  const label = shell.locator('[data-slot="timeline-detail-activity"] > label')

  for (const hidden of [false, true]) {
    if (hidden) await label.click()
    await visibility.hover()
    await expect(visibility).not.toHaveCSS("background-color", "rgba(0, 0, 0, 0)")
    await label.hover()
    await expect(visibility).toHaveCSS("background-color", "rgba(0, 0, 0, 0)")
  }
})

story("opens advanced on returning to custom settings but not presets", async ({ mount }) => {
  const component = await mount("settings-timeline-detail--interactive")
  const advanced = component.getByRole("button", { name: "Advanced", exact: true })
  await expect(advanced).toHaveAttribute("aria-expanded", "false")
  await advanced.click()
  await component.locator('[data-category="shell"][data-field="placement"] [data-slot="switch-control"]').click()
  await expect(component.getByRole("slider")).toHaveAttribute("aria-valuetext", "Custom")
  await advanced.click()
  await expect(advanced).toHaveAttribute("aria-expanded", "false")
  await component.getByRole("button", { name: "Leave settings" }).click()
  await expect(advanced).toHaveCount(0)
  await component.getByRole("button", { name: "Return to settings" }).click()
  await expect(advanced).toHaveAttribute("aria-expanded", "true")
  await expect(component.getByRole("switch", { name: "Shell grouped", exact: true })).not.toBeChecked()

  await component.getByRole("slider").press("End")
  await expect(component.getByRole("slider")).toHaveAttribute("aria-valuetext", "Everything")
  await component.getByRole("button", { name: "Leave settings" }).click()
  await component.getByRole("button", { name: "Return to settings" }).click()
  await expect(advanced).toHaveAttribute("aria-expanded", "false")
})

story("keeps visibility and switches in sync with the preset slider", async ({ mount }) => {
  const component = await mount("settings-timeline-detail--interactive")
  await component.getByRole("button", { name: "Advanced", exact: true }).click()
  const slider = component.getByRole("slider", { name: "Timeline detail" })
  await slider.focus()
  await slider.press("Home")
  await expect(slider).toHaveAttribute("aria-valuetext", "Messages only")
  // Messages only hides every activity but keeps commentary, which is text.
  await expect(component.getByRole("switch")).toHaveAccessibleName("Commentary grouped")
  await expect(component.locator('[data-slot="timeline-detail-unavailable"]')).toHaveCount(9)
  await expect(component.locator('[data-action="timeline-detail-visibility"][aria-pressed="false"]')).toHaveCount(6)
  await component.getByRole("button", { name: "Shell visibility" }).click()
  await expect(component.getByRole("switch", { name: "Shell grouped", exact: true })).toBeChecked()
  await expect(slider).toHaveAttribute("aria-valuetext", "Custom")
  await slider.focus()
  await slider.press("End")
  await expect(slider).toHaveAttribute("aria-valuetext", "Everything")
  await expect(component.getByRole("switch")).toHaveCount(10)
  await expect(component.locator('[data-slot="timeline-detail-unavailable"]')).toHaveCount(0)
  await expect(component.locator('[data-action="timeline-detail-visibility"][aria-pressed="true"]')).toHaveCount(7)
  await expect(component.getByRole("switch", { checked: true })).toHaveCount(0)
})

for (const direction of ["ltr", "rtl"]) {
  for (const theme of ["light", "dark"]) {
    story(`fits narrow and wide layouts in ${direction}, ${theme}`, async ({ mount, page }, testInfo) => {
      await page.setViewportSize({ width: 900, height: 900 })
      const component = await mount("settings-timeline-detail--interactive", { globals: { direction, theme } })
      await expect(component.locator('[data-slot="timeline-detail-scale"]')).toHaveCSS("margin-top", "16px")
      const advanced = component.locator('[data-slot="timeline-detail-advanced"]')
      await expect(advanced).toHaveCSS("margin-top", "0px")
      await expect(advanced).toHaveCSS("padding-top", "8px")
      await expect(component.locator('[data-slot="timeline-detail-summary"]')).toHaveCSS(
        "color",
        await component
          .locator('[data-slot="settings-row-title"]')
          .evaluate((element) => getComputedStyle(element).color),
      )
      await component.getByRole("button", { name: "Advanced", exact: true }).click()
      const track = component.locator('[data-slot="timeline-detail-track"]')
      await expect(track).toHaveCSS(
        "--timeline-detail-track-background",
        await track.evaluate(
          (element, theme) =>
            getComputedStyle(element)
              .getPropertyValue(theme === "light" ? "--v2-background-bg-layer-04" : "--v2-background-bg-layer-03")
              .trim(),
          theme,
        ),
      )

      if (theme === "light") {
        await expect(track.locator("span").last()).toHaveCSS("background-image", "none")
        await expect(track).toHaveCSS(
          "--timeline-detail-marker-background",
          await track.evaluate((element) => getComputedStyle(element).getPropertyValue("--v2-grey-500").trim()),
        )
      }

      if (theme === "dark") {
        await expect(track.locator("span").last()).not.toHaveCSS("background-image", "none")
      }

      await page.screenshot({ path: testInfo.outputPath(`timeline-${theme}-${direction}.png`) })
      const list = component.locator('[data-slot="timeline-detail-list"]')
      const columns = component.locator('[data-slot="timeline-detail-columns"]')
      await expect(component.locator('[data-slot="timeline-detail-categories"]')).toHaveCSS("margin-top", "0px")
      const advancedTrigger = component.getByRole("button", { name: "Advanced", exact: true })
      expect(
        await advancedTrigger.evaluate((element) => {
          const trigger = element.getBoundingClientRect()

          const heading = document
            .querySelector('[data-slot="timeline-detail-columns"] > :nth-child(2)')!
            .getBoundingClientRect()

          return Math.abs(trigger.y + trigger.height / 2 - (heading.y + heading.height / 2))
        }),
      ).toBeLessThan(1)
      expect(
        await list.evaluate((element) => {
          const list = element.getBoundingClientRect()

          const trigger = document
            .querySelector('[data-slot="timeline-detail-advanced"] > [data-slot="collapsible-trigger"]')!
            .getBoundingClientRect()

          return list.y - trigger.bottom
        }),
      ).toBe(8)

      for (const [column, field] of [
        [2, "placement"],
        [3, "details"],
      ] as const) {
        const heading = await columns.locator(`> :nth-child(${column})`).evaluate((element) => {
          const rect = element.getBoundingClientRect()

          return rect.x + rect.width / 2
        })

        const toggle = await component
          .locator(`[data-category="shell"][data-field="${field}"] [data-slot="switch-control"]`)
          .evaluate((element) => {
            const rect = element.getBoundingClientRect()

            return rect.x + rect.width / 2
          })

        expect(Math.abs(heading - toggle)).toBeLessThan(1)
      }

      await expect(component.locator('[data-slot="timeline-detail-activity"]').first()).toHaveCSS("gap", "8px")

      for (const width of [900, 320]) {
        await page.setViewportSize({ width, height: 900 })
        await expect(component.getByRole("switch", { name: "Shell grouped", exact: true })).toBeVisible()
        expect(await list.evaluate((element) => element.scrollWidth <= element.clientWidth)).toBe(true)
        const visibility = component.getByRole("button", { name: "Shell visibility" })
        await visibility.focus()
        await visibility.press("Space")
        await expect(visibility).toHaveAttribute("aria-pressed", "false")
        await expect(component.getByRole("switch", { name: "Shell grouped", exact: true })).toHaveCount(0)
        await expect(
          component
            .getByRole("group", { name: "Shell", exact: true })
            .locator('[data-slot="timeline-detail-unavailable"]'),
        ).toHaveCount(2)
        await visibility.press("Space")
        await expect(visibility).toHaveAttribute("aria-pressed", "true")

        const slider = component.getByRole("slider", { name: "Timeline detail" })
        const track = component.locator('[data-slot="timeline-detail-track"]')
        await expect(slider).not.toHaveCSS("cursor", "pointer")
        expect(await track.evaluate((element) => element.getBoundingClientRect().width)).toBe(
          await component
            .locator('[data-slot="timeline-detail-scale"]')
            .evaluate((element) => element.getBoundingClientRect().width),
        )
        await slider.focus()
        await slider.press("Home")

        for (const position of [0, 1, 2, 3, 4]) {
          if (position > 0) await slider.press("ArrowUp")
          await expect(track).toHaveCSS("--timeline-detail-progress", `${position * 25}%`)
          await expect(track.locator("span[data-selected]")).toHaveCount(position + 1)
          await expect(track.locator("span").nth(position)).toHaveCSS(
            "background-color",
            await track.evaluate((element) => getComputedStyle(element, "::before").backgroundColor),
          )

          const fill = await track.evaluate((element) => {
            const style = getComputedStyle(element, "::before")

            return {
              fraction: parseFloat(style.width) / element.getBoundingClientRect().width,
              start: style.getPropertyValue("inset-inline-start"),
              color: style.backgroundColor,
              remainder: getComputedStyle(element).backgroundColor,
            }
          })

          expect(fill.fraction).toBeCloseTo(position / 4, 2)
          expect(fill.start).toBe("0px")
          expect(fill.color).not.toBe(fill.remainder)
          const marker = await track.locator("span").nth(position).boundingBox()
          const bounds = await track.boundingBox()
          expect(marker).not.toBeNull()
          expect(bounds).not.toBeNull()
          expect((marker!.x + marker!.width / 2 - bounds!.x) / bounds!.width).toBeCloseTo(
            direction === "rtl" ? 1 - position / 4 : position / 4,
            2,
          )
        }
      }
    })
  }
}