1import { describe, expect, it } from "vitest"
2
3import {
4 footerTextWidth,
5 getLayout,
6 LAYOUTS,
7 QR_GAP,
8 QR_SIZE,
9 qrRect,
10 type LayoutId,
11} from "@/lib/layouts"
12
13const IDS: LayoutId[] = ["strip-4", "strip-3"]
14
15describe("geometri ekspor strip", () => {
16 it("kanvas kedua layout pas 600 x 1800 px", () => {
17 for (const id of IDS) {
18 expect(getLayout(id).canvas).toEqual({ width: 600, height: 1800 })
19 }
20 })
21
22 it("lebar pas: dua margin ditambah lebar slot sama dengan lebar kanvas", () => {
23 for (const id of IDS) {
24 const layout = getLayout(id)
25 expect(layout.margin * 2 + layout.slot.width).toBe(layout.canvas.width)
26 expect(layout.footerRect.width).toBe(layout.slot.width)
27 expect(layout.footerRect.x).toBe(layout.margin)
28 }
29 })
30
31 it("tinggi pas: margin, seluruh slot, jarak, dan kaki sama dengan tinggi kanvas", () => {
32 for (const id of IDS) {
33 const { margin, shotCount, slot, gap, footer, canvas } = getLayout(id)
34 const used = margin + shotCount * slot.height + (shotCount - 1) * gap + footer.height
35 expect(used).toBe(canvas.height)
36 }
37 })
38
39 it("kaki strip menyentuh tepi bawah kanvas", () => {
40 for (const id of IDS) {
41 const { footerRect, canvas } = getLayout(id)
42 expect(footerRect.y + footerRect.height).toBe(canvas.height)
43 }
44 })
45
46 it("slot berurutan dari atas tanpa tumpang tindih, berjarak tepat satu gap", () => {
47 for (const id of IDS) {
48 const { slotRects, gap, margin, slot } = getLayout(id)
49 slotRects.forEach((rect, index) => {
50 expect(rect.x).toBe(margin)
51 expect(rect.width).toBe(slot.width)
52 expect(rect.height).toBe(slot.height)
53 expect(rect.y).toBe(margin + index * (slot.height + gap))
54 if (index > 0) {
55 expect(rect.y - (slotRects[index - 1].y + slot.height)).toBe(gap)
56 }
57 })
58 }
59 })
60
61 it("jumlah slot mengikuti layout dan berhenti sebelum kaki strip", () => {
62 expect(getLayout("strip-4").shotCount).toBe(4)
63 expect(getLayout("strip-4").slotRects).toHaveLength(4)
64 expect(getLayout("strip-4").slot.height).toBe(372)
65
66 expect(getLayout("strip-3").shotCount).toBe(3)
67 expect(getLayout("strip-3").slotRects).toHaveLength(3)
68 expect(getLayout("strip-3").slot.height).toBe(504)
69
70 const lastSlot = getLayout("strip-4").slotRects.at(-1)
71 expect(lastSlot?.y).toBeDefined()
72 expect((lastSlot?.y ?? 0) + (lastSlot?.height ?? 0)).toBeLessThanOrEqual(
73 getLayout("strip-4").footerRect.y
74 )
75 })
76
77 it("label mengikuti DESIGN-SYSTEM §12", () => {
78 expect(LAYOUTS["strip-4"].label).toBe("Strip 4 foto")
79 expect(LAYOUTS["strip-3"].label).toBe("Strip 3 foto")
80 })
81})
82
83/* FR-03: Grid 2 x 2 selebar lembar 4R (1200 x 1800). */
84describe("layout grid 2 x 2", () => {
85 it("kanvas 1200 x 1800 dengan empat slot portret dua kolom", () => {
86 const layout = getLayout("grid-2x2")
87
88 expect(layout.label).toBe("Grid 2 x 2")
89 expect(layout.shotCount).toBe(4)
90 expect(layout.canvas).toEqual({ width: 1200, height: 1800 })
91 expect(layout.slot).toEqual({ width: 540, height: 744 })
92 expect(layout.slotRects).toEqual([
93 { x: 48, y: 48, width: 540, height: 744 },
94 { x: 612, y: 48, width: 540, height: 744 },
95 { x: 48, y: 816, width: 540, height: 744 },
96 { x: 612, y: 816, width: 540, height: 744 },
97 ])
98 expect(layout.footerRect).toEqual({
99 x: 48,
100 y: 1560,
101 width: 1104,
102 height: 240,
103 })
104 })
105
106 it("margin, jarak, dan kaki pas sampai tepi kanvas", () => {
107 const layout = getLayout("grid-2x2")
108 const kolomKedua = layout.slotRects[1]
109 const barisKedua = layout.slotRects[2]
110
111 expect(kolomKedua.x + kolomKedua.width + layout.margin).toBe(
112 layout.canvas.width
113 )
114 expect(kolomKedua.x - (layout.slotRects[0].x + layout.slotRects[0].width)).toBe(
115 layout.gap
116 )
117 expect(barisKedua.y - (layout.slotRects[0].y + layout.slotRects[0].height)).toBe(
118 layout.gap
119 )
120 expect(barisKedua.y + barisKedua.height).toBe(layout.footerRect.y)
121 expect(layout.footerRect.y + layout.footerRect.height).toBe(
122 layout.canvas.height
123 )
124 })
125})
126
127/* SH-04: QR 168 x 168 px di kanan kaki strip, 16 px dari kotak teks. */
128describe("geometri QR di kaki strip", () => {
129 it("kotak QR 168 px dan menempel tepi kanan kaki", () => {
130 for (const id of ["strip-4", "strip-3", "grid-2x2"] as LayoutId[]) {
131 const layout = getLayout(id)
132 const kotak = qrRect(layout)
133
134 expect(kotak.width).toBe(QR_SIZE)
135 expect(kotak.height).toBe(QR_SIZE)
136 expect(kotak.x + kotak.width).toBe(
137 layout.footerRect.x + layout.footerRect.width
138 )
139 /* Tegak lurus di tengah kaki strip. */
140 expect(kotak.y + kotak.height / 2).toBe(
141 layout.footerRect.y + layout.footerRect.height / 2
142 )
143 expect(kotak.y).toBeGreaterThanOrEqual(layout.footerRect.y)
144 expect(kotak.y + kotak.height).toBeLessThanOrEqual(
145 layout.footerRect.y + layout.footerRect.height
146 )
147 }
148 })
149
150 it("posisi QR sama dengan angka di rencana M6", () => {
151 expect(qrRect(getLayout("strip-4"))).toMatchObject({ x: 396, y: 1614 })
152 expect(qrRect(getLayout("grid-2x2"))).toMatchObject({ x: 984, y: 1596 })
153 })
154
155 it("kotak teks menyempit tepat selebar kaki dikurangi QR dan jaraknya", () => {
156 for (const id of ["strip-4", "grid-2x2"] as LayoutId[]) {
157 const layout = getLayout(id)
158 expect(footerTextWidth(layout, false)).toBe(layout.footerRect.width)
159 expect(footerTextWidth(layout, true)).toBe(
160 layout.footerRect.width - QR_SIZE - QR_GAP
161 )
162 /* Kotak teks tidak boleh menyentuh QR. */
163 expect(
164 layout.footerRect.x + footerTextWidth(layout, true) + QR_GAP
165 ).toBe(qrRect(layout).x)
166 }
167 })
168})
169