D
P
0
← Semua artikel Read in English

JavaScript, DOM & Animasi Browser

Klik Foto Mengunci Scroll Tapi Layar Kosong? JS Men-toggle Class, CSS Membuka lewat Atribut

· · 4 menit baca
Klik Foto Mengunci Scroll Tapi Layar Kosong? JS Men-toggle Class, CSS Membuka lewat Atribut

Bagian paling membingungkan dari bug ini justru bagian yang berhasil. Klik satu foto di galeri properti, scroll halaman langsung terkunci, persis seperti yang seharusnya dilakukan lightbox waktu dia terbuka. Tapi tidak ada foto yang muncul. Layarnya tetap seperti semula, cuma sekarang tidak bisa digulir dan tidak ada tombol tutup untuk keluar.

Setengah mekanisme jalan, setengahnya tidak, dan itu sebenarnya petunjuk paling jelas. Mengunci scroll adalah kerjaan JavaScript, jadi handler-nya pasti terpanggil. Yang tidak pernah terjadi cuma satu langkah terakhir: lightbox-nya tidak pernah terlihat.

Dua sisi kontrak yang tidak pernah bertemu

Penyebabnya ada di penanda state yang dipakai dua berkas berbeda untuk hal yang sama. Sisi JS men-toggle class .gallery-lightbox--open, sementara stylesheet galeri membuka lightbox lewat atribut [data-state="open"]. Akibatnya klik foto mengunci scroll tapi tidak menampilkan apa pun.

// JS menandai state terbuka lewat class
lightbox.classList.add('gallery-lightbox--open');
document.body.style.overflow = 'hidden';
/* Stylesheet membuka lightbox lewat atribut, bukan lewat class itu */
.gallery-lightbox[data-state="open"] {
  /* aturan yang membuat lightbox terlihat */
}

Yang bikin drift semacam ini lolos dari review kode: dua berkas itu masing-masing benar. JS-nya masuk akal dibaca sendirian, CSS-nya juga. Yang salah cuma perjanjian di antara keduanya, dan tidak ada apa pun yang menegakkan perjanjian itu. Tidak ada compiler yang peduli, tidak ada linter yang tahu bahwa class ini pernah punya pasangan di stylesheet.

Lightbox-nya sendiri digerakkan dari DOM: dia membaca node bertanda [data-lightbox-image] untuk tahu foto mana saja yang harus dia tampilkan. Jadi sebenarnya ada lebih dari satu kontrak implisit yang harus tetap sinkron di fitur sekecil ini.

Ini regresi, bukan fitur yang belum jadi

Di versi tema 1.14.1, lightbox galeri tercatat bisa terbuka, dan itu bukan kesimpulan dari membaca kode melainkan hasil verifikasi langsung. Versi berikutnya, 1.15.0, juga diverifikasi hidup lewat klik nyata, delapan perbaikan diklik satu per satu. Perbaikan lightbox beku baru masuk di versi sesudahnya lagi, 1.15.1.

Jadi ini bukan fitur setengah jadi yang belum pernah hidup. Ini sesuatu yang sempat bekerja, lalu salah satu sisinya bergeser sendiri di tengah rentetan rilis yang rapat.

Perbaikannya

Yang perlu diperbaiki bukan sisi JS atau sisi CSS, tapi jarak di antara keduanya. Satu penanda state, dipakai dua-duanya.

Perbaikannya ikut di rilis 1.15.1, dan manifest unggahnya memuat lima berkas. Salah satunya berkas CSS lightbox dalam bentuk terkompilasi, gallery.min.css, ditambah purge cache. Berkas JS lightbox tidak ada di manifest itu, jadi yang dikirim ulang justru sisi stylesheet.

Bentuk min itu bukan detail kosmetik. Helper aset tema otomatis memilih dist/*.min.{ext}, jadi mengedit berkas sumber tanpa menjalankan build sama saja dengan tidak mengirim apa-apa ke produksi. Pemeriksaan visual lewat screenshot juga diperlakukan wajib di proyek ini, bukan opsional, karena pemeriksaan jaringan saja pernah lolos dua kali secara kebetulan dan melewatkan regresi yang nyata.

Satu hal yang saya catat apa adanya: versi 1.15.1 itu diunggah ulang oleh klien, dan saat catatannya ditulis perbaikan ini belum diverifikasi lewat klik di produksi.

Selector yang sama, drift yang kedua

Lightbox yang sama kena lagi, kali ini di panahnya. Selector panah di stylesheet diganti ke __prev dan __next, sebelumnya __nav--prev dan __nav--next, ditambah rule untuk __overlay dan glyph tutup yang lebih besar.

/* Sebelum perbaikan: */
/* CSS menata .gallery-lightbox__nav--prev / __nav--next */
/* JS memanggil .gallery-lightbox__prev / __next */

Perbaikan panah dan overlay itu masuk versi 1.16.0 dan kali ini benar-benar diverifikasi hidup di produksi. Dalam daftar hasil verifikasi klik mode test, panah dan overlay lightbox tercatat sudah hidup.

Kenapa ini kelas bug, bukan insiden tunggal

Drift __nav-- lawan __prev/next ini dicatat sebagai satu dari tiga bug sekelas yang sudah muncul di proyek yang sama, bersama bug closest di fungsi yang memantulkan state booking dan skip "deferred" pada unggah foto. Audit yang lebih luas menemukan empat alur terpisah rusak karena JS berevolusi diam-diam menjauh dari realitas sisi server.

Kesimpulan yang dijadikan aturan proyek: setiap kontrol interaktif harus benar-benar diklik di browser nyata dan efek sampingnya dikonfirmasi, karena code review tidak bisa menangkap handler yang keluar diam-diam lebih awal.

Rencana pencegahannya lebih mekanis daripada mengandalkan kejelian pembaca. Petakan secara statis setiap data-action, tombol, form, dan link ke handler JS-nya, lalu ke endpoint atau route REST-nya, dan tandai contract drift (handler hilang, route 404, selector tidak cocok, stub deferred atau TODO) sebelum semuanya diklik satu per satu. Lightbox galeri di halaman properti sendiri masuk daftar kontrol yang ditandai rawan drift dan belum diverifikasi klik secara menyeluruh.

Pelajaran

Klien yang merangkumnya paling ringkas. Temuannya: setiap kali sesuatu benar-benar diklik, selalu ketemu bug baru, artinya masih banyak fungsi klik yang belum diuji sama sekali. Permintaannya juga tidak ambigu, tes dengan klik nyata dan bukan dengan membaca kode, jangan sampai ada yang tersisa. Dalam satu sesi yang sama, ketelitian audit dieskalasi tiga kali, dan screenshot statis tidak dihitung sebagai audit.

Buat saya pelajarannya begini. Class dan atribut sama-sama penanda state yang sah, dan keduanya tidak salah dipakai. Yang berbahaya adalah membiarkan dua berkas memilih penanda yang berbeda untuk state yang sama, karena tidak ada yang akan memberi tahu kamu. Bug-nya tidak muncul sebagai error, dia muncul sebagai setengah fitur yang jalan, dan setengah itu justru yang bikin kamu percaya semuanya baik-baik saja.