Proyek terakhir dalam rangkaian ini merangkai kebiasaan yang sudah kita latih sejak proyek kalkulator — logika murni yang terpisah dari tampilan, dan uji otomatis yang berjalan dari terminal — lalu menambahkan dua hal yang belum Anda lakukan sebelumnya. Pertama, state aplikasi tidak lagi berpindah tangan lewat argumen seperti pada aplikasi todo; kali ini daftar catatan tinggal di dalam module, dan setiap fungsi membaca maupun memperbaruinya dari satu tempat yang sama. Kedua, proyek ditutup bukan sekadar dengan menghitung uji yang lulus, melainkan dengan refactoring terukur: merapikan kode tanpa mengubah perilakunya, dibuktikan oleh uji yang tetap hijau sebelum dan sesudahnya.

Aplikasi catatannya sendiri merupakan produk kecil yang utuh: pengguna menulis catatan berjudul dan berisi, mengedit maupun menghapusnya, mencari lewat kotak pencarian yang tidak peka huruf besar-kecil, dan menemukan seluruh catatannya tetap ada ketika tab dibuka ulang berkat localStorage. Seluruh keputusan data hidup di src/catatan.mjs — buatCatatan(judul, isi), ubahCatatan(id, perubahan), hapusCatatan(id), daftarCatatan(), plus cariCatatan, simpanCatatan, dan muatCatatan — sementara berkas HTML hanya menampilkan state dan meneruskan peristiwa ke logika. Setiap catatan diberi id oleh penghitung di dalam module, bukan oleh crypto.randomUUID(): angka yang berurutan (1, 2, 3, …) membuat keluaran uji maupun demo bisa diklaim secara pasti, sesuatu yang tidak mungkin pada id acak.

Proyek dikerjakan dalam empat bagian berurutan:

  • Bagian 1: Membuat dan Mengedit Catatan — memilih pola “state tinggal di module”, merancang bentuk satu catatan { id, judul, isi }, menulis empat fungsi pertama dengan validasi input, menyusun antarmuka dengan alur buat dan edit yang memakai form yang sama, dan mengunci perilaku awal dengan tiga belas uji otomatis.
  • Bagian 2: Pencarian Catatan — menulis cariCatatan(catatan, kataKunci) sebagai fungsi murni: normalisasi teks (huruf kecil, tanpa spasi ujung), pencocokan potongan kata, dan aturan multi-kata di mana semua kata harus ditemukan, boleh terpisah antara judul dan isi. Kotak pencarian di antarmuka ikut menyala, dan uji bertambah menjadi dua puluh.
  • Bagian 3: Penyimpanan Data — membelah pekerjaan penyimpanan menjadi serialisasi JSON yang murni (simpanCatatan, muatCatatan) dan pembungkus localStorage yang tipis di antarmuka. muatCatatan memeriksa data tersimpan berlapis-lapis dan melaporkan hasilnya sebagai object { catatan, alasan }, lalu uji mencapai dua puluh sembilan.
  • Bagian 4: Pengujian dan Refactoring — memetakan delapan suite uji sebagai satu gambaran utuh, lalu melakukan dua refactoring nyata pada catatan.mjs: mengganti magic number dengan konstanta bernama dan mengekstrak duplikasi validasi menjadi helper — dengan seluruh uji terbukti tetap lulus.

Struktur Berkas Proyek #

Seluruh kode proyek tinggal di tiga folder pada content/javascript/proyek/aplikasi-catatan/:

text
aplikasi-catatan/
├── src/
│   ├── catatan.mjs        # logika inti: CRUD catatan, pencarian, serialisasi
│   └── index.html.txt     # contoh antarmuka (salin ulang sebagai index.html)
├── styles/
│   └── styles.css         # tampilan aplikasi catatan
└── tests/
    └── catatan.test.mjs   # uji otomatis untuk catatan.mjs

Peran tiap berkas:

BerkasPeran
src/catatan.mjsOtak aplikasi: menyimpan daftar catatan dan penghitung id, membuat, mengubah, menghapus, mencari, serta mengubah data menjadi JSON. Tidak menyentuh DOM maupun localStorage.
src/index.html.txtWajah aplikasi: form buat/edit, kotak pencarian, kartu catatan, dan skrip penghubung ke logika.
styles/styles.cssPenampil: kartu aplikasi di tengah layar, form dua kolom, kartu catatan, dan tombol-tombolnya.
tests/catatan.test.mjsPenjaga: dua puluh sembilan uji otomatis yang dijalankan test runner bawaan Node.

Setiap bagian menampilkan kondisi berkas pada akhir bagian tersebut: blok kode yang membahas sebuah berkas menyediakan tautan unduhan salinannya di header blok. Versi lengkap dan terakhir dari seluruh berkas selalu ada di folder src/, styles/, dan tests/ di atas — jika Anda mengikuti proyek dari Bagian 1, berkas-berkas itu tumbuh bertahap mengikuti pembahasan.

Menjalankan Pengujian #

Uji otomatis dijalankan dari folder proyek dengan test runner bawaan Node.js:

bash
cd aplikasi-catatan
node --test

Node mencari berkas uji dengan pola nama seperti *.test.mjs, menemukan tests/catatan.test.mjs, lalu menjalankan seluruh ujinya. Ringkasan di ujung keluarannya pada akhir proyek berbunyi:

text
ℹ tests 29
ℹ suites 8
ℹ pass 29
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 77.443539

Angka pass 29 berarti seluruh uji lulus; duration_ms akan berbeda-beda di mesin Anda karena mengukur waktu eksekusi. Bila ingin menyasar satu berkas uji secara eksplisit, tulis nama berkasnya utuh: node --test tests/catatan.test.mjs.

Mulailah dari Bagian 1 dan ikuti urutannya — setiap bagian dibangun di atas bagian sebelumnya, dan pada bagian akhir Anda memegang aplikasi catatan yang lengkap, terjaga dua puluh sembilan uji, serta kode yang sudah dirapikan dengan disiplin refactoring.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

styles/styles.css

Unduh berkas
css
/* styles.css — tampilan aplikasi catatan */

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #f3f4f6;
  font-family: system-ui, sans-serif;
}

.aplikasi {
  width: 380px;
  padding: 20px;
  background-color: #ffffff;
  border-radius: 12px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.aplikasi h1 {
  margin: 0 0 16px;
  font-size: 1.4rem;
}

.form-catatan {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 12px;
}

.form-catatan input,
.form-catatan textarea {
  padding: 10px;
  border: 1px solid #d1d5db;
  border-radius: 8px;
  font-size: 1rem;
  font-family: inherit;
}

.form-catatan textarea {
  resize: vertical;
}

.aksi-form {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

.tombol-kirim {
  padding: 8px 16px;
  border: none;
  border-radius: 8px;
  background-color: #3b82f6;
  color: #ffffff;
  font-size: 0.95rem;
  cursor: pointer;
}

.tombol-kirim:hover {
  background-color: #2563eb;
}

.tombol-batal {
  padding: 8px 16px;
  border: none;
  border-radius: 8px;
  background-color: #e5e7eb;
  font-size: 0.95rem;
  cursor: pointer;
}

.tombol-batal:hover {
  background-color: #d1d5db;
}

.pencarian {
  margin-bottom: 12px;
}

.pencarian input {
  width: 100%;
  box-sizing: border-box;
  padding: 10px;
  border: 1px solid #d1d5db;
  border-radius: 8px;
  font-size: 1rem;
}

.pesan {
  margin: 0 0 12px;
  color: #b91c1c;
  font-size: 0.9rem;
}

.ringkasan {
  margin: 0 0 12px;
  color: #6b7280;
  font-size: 0.9rem;
}

.daftar-catatan {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 10px;
}

.kartu {
  padding: 12px;
  background-color: #f9fafb;
  border-radius: 8px;
}

.kartu h2 {
  margin: 0 0 6px;
  font-size: 1.05rem;
}

.kartu p {
  margin: 0 0 10px;
  color: #374151;
  white-space: pre-wrap;
}

.aksi-kartu {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

.tombol-ubah {
  padding: 6px 10px;
  border: none;
  border-radius: 6px;
  background-color: #fde68a;
  font-size: 0.85rem;
  cursor: pointer;
}

.tombol-ubah:hover {
  background-color: #fcd34d;
}

.tombol-hapus {
  padding: 6px 10px;
  border: none;
  border-radius: 6px;
  background-color: #fecaca;
  font-size: 0.85rem;
  cursor: pointer;
}

.tombol-hapus:hover {
  background-color: #fca5a5;
}