1import { describe, expect, it } from "vitest"
2
3import { SESSION_VERSION, serializeSession, validateRecord } from "@/features/session/sessionDb"
4import type { SessionState } from "@/features/session/sessionDb"
5import type { Shot } from "@/stores/booth"
6
7/* FR-14: bentuk rekaman sesi di IndexedDB. Validasi adalah satu-satunya pagar
8 antara penyimpanan dan store, jadi kasus rusaknya diuji langsung. */
9
10const SHOT: Shot = { still: "data:image/jpeg;base64,AAAA", capturedAt: 1 }
11
12function validState(): SessionState {
13 return {
14 layoutId: "strip-4",
15 shots: [SHOT, null, null, null],
16 frameId: "malam",
17 effectId: "retro",
18 stickers: [
19 { id: "s1", assetId: "star", x: 300, y: 900, rotation: -6, width: 120 },
20 ],
21 texts: [{ id: "t1", value: "halo", x: 300, y: 1696, rotation: 0, fontSize: 44 }],
22 showDate: true,
23 share: { qrEnabled: true, strip: { id: "stripid12345", expiresAt: null } },
24 relay: null,
25 localOnly: false,
26 }
27}
28
29describe("rekaman sesi", () => {
30 it("serialisasi menyalin seluruh bidang dengan versi saat ini", () => {
31 const state = validState()
32 expect(serializeSession(state)).toEqual({ version: SESSION_VERSION, ...state })
33 })
34
35 it("menerima rekaman hasil serialisasi", () => {
36 const record = serializeSession(validState())
37 expect(validateRecord(record)).toEqual(record)
38 })
39
40 /* M7: bingkai Strip Hidup hanya di memori. Rekaman harus membuangnya, bukan
41 menyerahkan `ImageBitmap` ke IndexedDB. */
42 it("membuang bingkai klip dari rekaman", () => {
43 const state = validState()
44 const clip = [{ close: () => {} }] as unknown as ImageBitmap[]
45 const record = serializeSession({
46 ...state,
47 shots: [{ ...SHOT, clip }, null, null, null],
48 })
49 expect(record.shots[0]).toEqual(SHOT)
50 expect(record.shots[0]).not.toHaveProperty("clip")
51 expect(validateRecord(record)?.shots).toEqual([SHOT, null, null, null])
52 })
53
54 it("menerima rekaman tanpa layout dengan nol foto", () => {
55 const record = serializeSession({
56 layoutId: null,
57 shots: [],
58 frameId: "klasik",
59 effectId: "asli",
60 stickers: [],
61 texts: [],
62 showDate: false,
63 share: { qrEnabled: false, strip: null },
64 })
65 expect(validateRecord(record)).toEqual(record)
66 })
67
68 it("menolak versi yang tidak dikenal dan bukan objek", () => {
69 expect(validateRecord(null)).toBeNull()
70 expect(validateRecord("rekaman")).toBeNull()
71 expect(validateRecord({ ...serializeSession(validState()), version: 2 })).toBeNull()
72 })
73
74 it("menolak layout tak dikenal dan jumlah slot yang tidak cocok", () => {
75 const record = serializeSession(validState())
76 expect(validateRecord({ ...record, layoutId: "strip-9" })).toBeNull()
77 expect(validateRecord({ ...record, shots: [SHOT] })).toBeNull()
78 expect(validateRecord({ ...record, shots: [SHOT, SHOT, SHOT, SHOT, SHOT] })).toBeNull()
79 /* Tanpa layout, daftar foto harus kosong. */
80 expect(
81 validateRecord({ ...record, layoutId: null, shots: [SHOT] })
82 ).toBeNull()
83 })
84
85 it("menolak tipe bidang yang salah", () => {
86 const record = serializeSession(validState())
87 expect(validateRecord({ ...record, frameId: 7 })).toBeNull()
88 expect(validateRecord({ ...record, showDate: "ya" })).toBeNull()
89 expect(validateRecord({ ...record, shots: [{ still: 1, capturedAt: 0 }, null, null, null] })).toBeNull()
90 expect(
91 validateRecord({ ...record, stickers: [{ id: "s1", assetId: "star" }] })
92 ).toBeNull()
93 expect(
94 validateRecord({ ...record, texts: [{ id: "t1", value: "halo" }] })
95 ).toBeNull()
96 })
97
98 /* M6: rekaman lama tidak punya `share`, dan bentuk yang rusak harus ditolak
99 sebelum masuk store. */
100 it("menerima rekaman lama tanpa irisan bagikan", () => {
101 const record = serializeSession(validState())
102 const tanpaShare: Record<string, unknown> = { ...record }
103 delete tanpaShare.share
104 expect(validateRecord(tanpaShare)).toEqual({
105 ...record,
106 share: { qrEnabled: false, strip: null },
107 })
108 })
109
110 it("menerima link yang kedaluwarsanya belum diketahui", () => {
111 const record = serializeSession(validState())
112 expect(
113 validateRecord({
114 ...record,
115 share: { qrEnabled: false, strip: { id: "stripid12345", expiresAt: "2026-10-19T09:00:00.000Z" } },
116 })?.share
117 ).toEqual({
118 qrEnabled: false,
119 strip: { id: "stripid12345", expiresAt: "2026-10-19T09:00:00.000Z" },
120 })
121 })
122
123 it("menolak irisan bagikan yang rusak", () => {
124 const record = serializeSession(validState())
125 expect(validateRecord({ ...record, share: "nyala" })).toBeNull()
126 expect(validateRecord({ ...record, share: { qrEnabled: "ya", strip: null } })).toBeNull()
127 expect(
128 validateRecord({ ...record, share: { qrEnabled: true, strip: { id: "pendek", expiresAt: null } } })
129 ).toBeNull()
130 expect(
131 validateRecord({ ...record, share: { qrEnabled: true, strip: { id: "stripid12345", expiresAt: 7 } } })
132 ).toBeNull()
133 })
134})
135