Proyek kalkulator melatih state berupa beberapa nilai yang saling menggantikan. Proyek kedua ini melatih wujud state yang jauh lebih umum di aplikasi nyata: daftar. Aplikasi todo terlihat sederhana di mata penggunanya — tulis tugas baru, centang yang sudah selesai, hapus yang tidak diperlukan lagi, dan daftar tetap utuh ketika tab ditutup lalu dibuka kembali. Di balik kesederhanaan itu ada pertanyaan desain yang nyata: bagaimana mewakili satu item di dalam daftar, bagaimana menambah dan mengurangi isi array tanpa mengubah array lama, dan bagaimana daftar itu bertahan di luar memori halaman.

Empat pertanyaan itu dijawab bertahap dalam satu produk utuh: aplikasi todo dengan form input, kotak centang status, tiga pilihan filter, ringkasan sisa tugas, dan penyimpanan localStorage. Pendekatan utamanya sama dengan kalkulator — pemisahan logika dari tampilan. Seluruh keputusan data hidup di src/tugas.mjs, module murni tanpa satu pun document maupun localStorage, sehingga seluruh perilakunya bisa dibuktikan lewat node --test dari terminal. Berkas HTML hanya menampilkan state dan meneruskan peristiwa ke logika.

Proyek dikerjakan dalam empat bagian berurutan:

  • Bagian 1: Menambah dan Menghapus Tugas — merancang state berupa array yang berisi object { id, teks }, menulis tiga fungsi murni pertama (tambahTugas, hapusTugas, daftarTugas), menyusun kerangka antarmuka HTML beserta CSS-nya, dan mengunci perilaku awal dengan delapan uji otomatis. Kita belajar memberi identitas id pada setiap item dan menjaga immutability di setiap operasi.
  • Bagian 2: Status dan Filter — menambahkan field selesai pada setiap tugas, fungsi ubahStatus yang membaliknya, filterTugas dengan tiga nilai sah (semua, aktif, selesai), dan hitungAktif untuk ringkasan sisa pekerjaan. Antarmuka bertambah kotak centang, tombol filter, dan paragraf ringkasan; delapan uji tumbuh menjadi tujuh belas.
  • Bagian 3: Penyimpanan Lokal — memasang localStorage tanpa membuat uji Node gagal jalan: simpanTugas dan muatTugas bekerja dengan string JSON di dalam logika murni, sementara pembungkus localStorage yang tipis tinggal di antarmuka. muatTugas menjaga data dengan tiga lapis pemeriksaan, dan uji mencapai dua puluh empat.
  • Bagian 4: Pengujian — meninjau seluruh strategi pengujian proyek sebagai satu gambaran utuh: peta sepuluh suite uji, pola Arrange-Act-Assert, cara menjalankan dan membaca keluaran node --test, serta opsi menguji antarmuka dengan jsdom yang dibahas tuntas pada section Testing.

Struktur Berkas Proyek #

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

text
aplikasi-todo/
├── src/
│   ├── tugas.mjs        # logika inti: daftar tugas, status, filter, penyimpanan
│   └── index.html.txt   # contoh antarmuka (salin ulang sebagai index.html)
├── styles/
│   └── styles.css       # tampilan aplikasi todo
└── tests/
    └── tugas.test.mjs   # uji otomatis untuk tugas.mjs

Peran tiap berkas:

BerkasPeran
src/tugas.mjsOtak aplikasi: menambah, menghapus, mengubah status, memfilter, dan mengubah daftar menjadi JSON. Tidak menyentuh DOM sama sekali.
src/index.html.txtWajah aplikasi: form input, kotak centang, tombol filter, ringkasan, dan skrip penghubung ke logika.
styles/styles.cssPenampil: kartu aplikasi di tengah layar, baris tugas, dan tombol-tombolnya.
tests/tugas.test.mjsPenjaga: dua puluh empat 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-todo
node --test

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

text
ℹ tests 24
ℹ suites 10
ℹ pass 24
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 81.670704

Angka pass 24 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/tugas.test.mjs.

Mulailah dari Bagian 1 dan ikuti urutannya — setiap bagian dibangun di atas bagian sebelumnya, dan pada bagian akhir aplikasi todo siap dipakai lengkap dengan penyimpanan lokalnya serta dua puluh empat uji yang menjaganya.

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 todo sederhana */

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: 340px;
  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-tugas {
  display: flex;
  gap: 8px;
  margin-bottom: 12px;
}

.form-tugas input {
  flex: 1;
  padding: 10px;
  border: 1px solid #d1d5db;
  border-radius: 8px;
  font-size: 1rem;
}

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

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

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

.filter {
  display: flex;
  gap: 8px;
  margin-bottom: 12px;
}

.filter-tombol {
  flex: 1;
  padding: 8px 0;
  border: 1px solid #d1d5db;
  border-radius: 8px;
  background-color: #f9fafb;
  font-size: 0.9rem;
  cursor: pointer;
}

.filter-tombol:hover {
  background-color: #e5e7eb;
}

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

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

.tugas {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px;
  background-color: #f9fafb;
  border-radius: 8px;
}

.tugas span {
  flex: 1;
}

.tugas--selesai span {
  color: #9ca3af;
  text-decoration: line-through;
}

.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;
}