IKHSAN
EN Read in EnglishID

snapme

Publik
Branch
master
Commit terakhir
130fdb828 Oktober 2026

layouts.test.ts

Buka raw
import { describe, expect, it } from "vitest"

import {
  footerTextWidth,
  getLayout,
  LAYOUTS,
  QR_GAP,
  QR_SIZE,
  qrRect,
  type LayoutId,
} from "@/lib/layouts"

const IDS: LayoutId[] = ["strip-4", "strip-3"]

describe("geometri ekspor strip", () => {
  it("kanvas kedua layout pas 600 x 1800 px", () => {
    for (const id of IDS) {
      expect(getLayout(id).canvas).toEqual({ width: 600, height: 1800 })
    }
  })

  it("lebar pas: dua margin ditambah lebar slot sama dengan lebar kanvas", () => {
    for (const id of IDS) {
      const layout = getLayout(id)
      expect(layout.margin * 2 + layout.slot.width).toBe(layout.canvas.width)
      expect(layout.footerRect.width).toBe(layout.slot.width)
      expect(layout.footerRect.x).toBe(layout.margin)
    }
  })

  it("tinggi pas: margin, seluruh slot, jarak, dan kaki sama dengan tinggi kanvas", () => {
    for (const id of IDS) {
      const { margin, shotCount, slot, gap, footer, canvas } = getLayout(id)
      const used = margin + shotCount * slot.height + (shotCount - 1) * gap + footer.height
      expect(used).toBe(canvas.height)
    }
  })

  it("kaki strip menyentuh tepi bawah kanvas", () => {
    for (const id of IDS) {
      const { footerRect, canvas } = getLayout(id)
      expect(footerRect.y + footerRect.height).toBe(canvas.height)
    }
  })

  it("slot berurutan dari atas tanpa tumpang tindih, berjarak tepat satu gap", () => {
    for (const id of IDS) {
      const { slotRects, gap, margin, slot } = getLayout(id)
      slotRects.forEach((rect, index) => {
        expect(rect.x).toBe(margin)
        expect(rect.width).toBe(slot.width)
        expect(rect.height).toBe(slot.height)
        expect(rect.y).toBe(margin + index * (slot.height + gap))
        if (index > 0) {
          expect(rect.y - (slotRects[index - 1].y + slot.height)).toBe(gap)
        }
      })
    }
  })

  it("jumlah slot mengikuti layout dan berhenti sebelum kaki strip", () => {
    expect(getLayout("strip-4").shotCount).toBe(4)
    expect(getLayout("strip-4").slotRects).toHaveLength(4)
    expect(getLayout("strip-4").slot.height).toBe(372)

    expect(getLayout("strip-3").shotCount).toBe(3)
    expect(getLayout("strip-3").slotRects).toHaveLength(3)
    expect(getLayout("strip-3").slot.height).toBe(504)

    const lastSlot = getLayout("strip-4").slotRects.at(-1)
    expect(lastSlot?.y).toBeDefined()
    expect((lastSlot?.y ?? 0) + (lastSlot?.height ?? 0)).toBeLessThanOrEqual(
      getLayout("strip-4").footerRect.y
    )
  })

  it("label mengikuti DESIGN-SYSTEM §12", () => {
    expect(LAYOUTS["strip-4"].label).toBe("Strip 4 foto")
    expect(LAYOUTS["strip-3"].label).toBe("Strip 3 foto")
  })
})

/* FR-03: Grid 2 x 2 selebar lembar 4R (1200 x 1800). */
describe("layout grid 2 x 2", () => {
  it("kanvas 1200 x 1800 dengan empat slot portret dua kolom", () => {
    const layout = getLayout("grid-2x2")

    expect(layout.label).toBe("Grid 2 x 2")
    expect(layout.shotCount).toBe(4)
    expect(layout.canvas).toEqual({ width: 1200, height: 1800 })
    expect(layout.slot).toEqual({ width: 540, height: 744 })
    expect(layout.slotRects).toEqual([
      { x: 48, y: 48, width: 540, height: 744 },
      { x: 612, y: 48, width: 540, height: 744 },
      { x: 48, y: 816, width: 540, height: 744 },
      { x: 612, y: 816, width: 540, height: 744 },
    ])
    expect(layout.footerRect).toEqual({
      x: 48,
      y: 1560,
      width: 1104,
      height: 240,
    })
  })

  it("margin, jarak, dan kaki pas sampai tepi kanvas", () => {
    const layout = getLayout("grid-2x2")
    const kolomKedua = layout.slotRects[1]
    const barisKedua = layout.slotRects[2]

    expect(kolomKedua.x + kolomKedua.width + layout.margin).toBe(
      layout.canvas.width
    )
    expect(kolomKedua.x - (layout.slotRects[0].x + layout.slotRects[0].width)).toBe(
      layout.gap
    )
    expect(barisKedua.y - (layout.slotRects[0].y + layout.slotRects[0].height)).toBe(
      layout.gap
    )
    expect(barisKedua.y + barisKedua.height).toBe(layout.footerRect.y)
    expect(layout.footerRect.y + layout.footerRect.height).toBe(
      layout.canvas.height
    )
  })
})

/* SH-04: QR 168 x 168 px di kanan kaki strip, 16 px dari kotak teks. */
describe("geometri QR di kaki strip", () => {
  it("kotak QR 168 px dan menempel tepi kanan kaki", () => {
    for (const id of ["strip-4", "strip-3", "grid-2x2"] as LayoutId[]) {
      const layout = getLayout(id)
      const kotak = qrRect(layout)

      expect(kotak.width).toBe(QR_SIZE)
      expect(kotak.height).toBe(QR_SIZE)
      expect(kotak.x + kotak.width).toBe(
        layout.footerRect.x + layout.footerRect.width
      )
      /* Tegak lurus di tengah kaki strip. */
      expect(kotak.y + kotak.height / 2).toBe(
        layout.footerRect.y + layout.footerRect.height / 2
      )
      expect(kotak.y).toBeGreaterThanOrEqual(layout.footerRect.y)
      expect(kotak.y + kotak.height).toBeLessThanOrEqual(
        layout.footerRect.y + layout.footerRect.height
      )
    }
  })

  it("posisi QR sama dengan angka di rencana M6", () => {
    expect(qrRect(getLayout("strip-4"))).toMatchObject({ x: 396, y: 1614 })
    expect(qrRect(getLayout("grid-2x2"))).toMatchObject({ x: 984, y: 1596 })
  })

  it("kotak teks menyempit tepat selebar kaki dikurangi QR dan jaraknya", () => {
    for (const id of ["strip-4", "grid-2x2"] as LayoutId[]) {
      const layout = getLayout(id)
      expect(footerTextWidth(layout, false)).toBe(layout.footerRect.width)
      expect(footerTextWidth(layout, true)).toBe(
        layout.footerRect.width - QR_SIZE - QR_GAP
      )
      /* Kotak teks tidak boleh menyentuh QR. */
      expect(
        layout.footerRect.x + footerTextWidth(layout, true) + QR_GAP
      ).toBe(qrRect(layout).x)
    }
  })
})
byte
5.777
baris
169
Jenis
ts
Commit terakhir
28 Oktober 2026