943 lines
54 KiB
PHP
943 lines
54 KiB
PHP
<?php
|
|
|
|
use App\Http\Controllers\Web\Produktivitas\PengurusKelompokTaniController;
|
|
use App\Http\Controllers\Web\Produktivitas\AsetKelompokTaniController;
|
|
use App\Http\Controllers\Web\Produktivitas\BantuanKelompokTaniController;
|
|
use App\Http\Controllers\Web\Produktivitas\SurveyKelompokTaniController;
|
|
use App\Models\Pertanian\KelompokTani;
|
|
use App\Models\Pertanian\AsetKelompokTani;
|
|
use App\Models\Pertanian\BantuanKelompokTani;
|
|
use App\Models\User;
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Event;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Illuminate\Support\ViewErrorBag;
|
|
use Illuminate\Validation\ValidationException;
|
|
|
|
beforeEach(function () {
|
|
foreach ([
|
|
'master_provinsi' => null,
|
|
'master_kabupaten_kota' => 'provinsi_id',
|
|
'master_kecamatan' => 'kabupaten_kota_id',
|
|
'master_desa_kelurahan' => 'kecamatan_id',
|
|
'master_komoditas' => null,
|
|
] as $name => $parent) {
|
|
Schema::create($name, function (Blueprint $table) use ($parent) {
|
|
$table->id();
|
|
$table->string('nama');
|
|
$table->string('kode')->nullable();
|
|
$table->boolean('is_utama')->default(true);
|
|
if ($parent) $table->integer($parent);
|
|
$table->softDeletes();
|
|
});
|
|
}
|
|
Schema::create('survey_kelompok_tani', function (Blueprint $table) {
|
|
$table->id();
|
|
foreach ([
|
|
'uid', 'nama_kelompok', 'nama_responden', 'jabatan_responden', 'kontak_responden',
|
|
'dusun', 'jenis_kelompok', 'nama_gapoktan', 'cakupan_wilayah', 'nama_wilayah_cakupan',
|
|
'status_legalitas', 'nomor_sk', 'tanggal_tahun_penerbitan_sk', 'frekuensi_pertemuan',
|
|
'tanggal_pertemuan_terakhir',
|
|
'pembukuan_kas', 'pengelola_kas', 'masalah_pengelolaan_kas_simpan_pinjam', 'deskripsi_masalah_pengelolaan_kas_simpan_pinjam',
|
|
'potensi_kelompok', 'alasan_penilaian_potensi', 'konflik_kelompok', 'topik_konflik', 'penyelesaian_konflik', 'partisipasi_perempuan',
|
|
] as $field) {
|
|
$table->string($field)->nullable();
|
|
}
|
|
foreach ([
|
|
'desa_kelurahan_id', 'tahun_berdiri', 'jumlah_pengurus', 'jumlah_pengurus_aktif',
|
|
'jumlah_anggota_terdaftar', 'jumlah_anggota_aktif', 'jumlah_pengurus_laki_laki',
|
|
'jumlah_pengurus_perempuan', 'jumlah_anggota_laki_laki', 'jumlah_anggota_perempuan',
|
|
'jumlah_anggota_pemuda_laki_laki', 'jumlah_anggota_pemuda_perempuan',
|
|
'created_by', 'updated_by', 'deleted_by',
|
|
] as $field) {
|
|
$table->integer($field)->nullable();
|
|
}
|
|
$table->json('komoditas_id')->nullable();
|
|
$table->json('kegiatan_terlaksana')->nullable();
|
|
$table->json('fungsi_kelompok_bagi_anggota')->nullable();
|
|
$table->boolean('pernah_mendapatkan_bantuan')->nullable();
|
|
$table->timestamps();
|
|
$table->softDeletes();
|
|
});
|
|
Schema::create('survey_pengurus_kelompok_tani', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->string('uid');
|
|
$table->integer('survey_kelompok_tani_id');
|
|
$table->string('nama_pengurus');
|
|
$table->string('jabatan');
|
|
$table->string('kontak');
|
|
$table->boolean('status');
|
|
$table->integer('created_by')->nullable();
|
|
$table->integer('updated_by')->nullable();
|
|
$table->integer('deleted_by')->nullable();
|
|
$table->timestamps();
|
|
$table->softDeletes();
|
|
});
|
|
Schema::create('survey_aset_kelompok_tani', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->string('uid');
|
|
$table->integer('survey_kelompok_tani_id');
|
|
$table->string('jenis_aset');
|
|
$table->boolean('status_aset');
|
|
$table->string('status_pengelolaan_aset');
|
|
$table->string('kondisi_aset');
|
|
$table->integer('created_by')->nullable();
|
|
$table->integer('updated_by')->nullable();
|
|
$table->integer('deleted_by')->nullable();
|
|
$table->timestamps();
|
|
$table->softDeletes();
|
|
});
|
|
Schema::create('survey_bantuan_kelompok_tani', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->string('uid');
|
|
$table->integer('survey_kelompok_tani_id');
|
|
$table->string('nama_program');
|
|
$table->string('penyelenggara');
|
|
$table->string('bentuk_bantuan');
|
|
$table->integer('tahun')->nullable();
|
|
$table->integer('created_by')->nullable();
|
|
$table->integer('updated_by')->nullable();
|
|
$table->integer('deleted_by')->nullable();
|
|
$table->timestamps();
|
|
$table->softDeletes();
|
|
});
|
|
DB::table('master_provinsi')->insert(['id' => 1, 'nama' => 'NTT']);
|
|
DB::table('master_kabupaten_kota')->insert(['id' => 1, 'nama' => 'Ngada', 'provinsi_id' => 1]);
|
|
DB::table('master_kecamatan')->insert(['id' => 1, 'nama' => 'Kecamatan A', 'kabupaten_kota_id' => 1]);
|
|
DB::table('master_desa_kelurahan')->insert(['id' => 1, 'nama' => 'Desa A', 'kecamatan_id' => 1]);
|
|
|
|
$user = new User(['name' => 'Admin Test', 'email' => 'admin@example.test', 'role' => 'admin']);
|
|
$user->id = 7;
|
|
$this->be($user);
|
|
Event::forget('composing: *');
|
|
view()->share('errors', new ViewErrorBag());
|
|
|
|
$this->kelompok = fn (array $attributes = []) => KelompokTani::create([
|
|
'nama_kelompok' => 'Kelompok A', 'nama_responden' => 'Responden A',
|
|
'desa_kelurahan_id' => 1, 'jenis_kelompok' => 'Kelompok Tani (Poktan)', ...$attributes,
|
|
]);
|
|
$this->profileRequest = function (array $input = [], string $method = 'PUT') {
|
|
$request = Request::create('/survey/kelompok-tani/1/edit', $method, $input);
|
|
$request->setLaravelSession(app('session')->driver());
|
|
$this->app->instance('request', $request);
|
|
|
|
return $request;
|
|
};
|
|
$this->profileDom = function ($view) {
|
|
$dom = new DOMDocument();
|
|
$previous = libxml_use_internal_errors(true);
|
|
$dom->loadHTML($view->render());
|
|
libxml_clear_errors();
|
|
libxml_use_internal_errors($previous);
|
|
|
|
return new DOMXPath($dom);
|
|
};
|
|
});
|
|
|
|
test('profile saves identity and membership together on creation and update', function () {
|
|
$controller = app(SurveyKelompokTaniController::class);
|
|
$input = [
|
|
'nama_kelompok' => 'Kelompok Baru', 'nama_responden' => 'Budi',
|
|
'desa_kelurahan_id' => 1, 'jenis_kelompok' => 'Kelompok Tani (Poktan)',
|
|
'jumlah_pengurus' => 4, 'jumlah_pengurus_aktif' => 3, 'jumlah_anggota_terdaftar' => 20,
|
|
'jumlah_anggota_aktif' => 18, 'jumlah_pengurus_laki_laki' => 2, 'jumlah_pengurus_perempuan' => 2,
|
|
'jumlah_anggota_laki_laki' => 12, 'jumlah_anggota_perempuan' => 8,
|
|
'jumlah_anggota_pemuda_laki_laki' => 5, 'jumlah_anggota_pemuda_perempuan' => 3,
|
|
];
|
|
$locations = ['provinsi_id' => 1, 'kabupaten_id' => 1, 'kecamatan_id' => 1];
|
|
$controller->store(($this->profileRequest)([...$input, ...$locations], 'POST'));
|
|
$group = KelompokTani::firstOrFail();
|
|
foreach ($input as $field => $value) expect($group->{$field})->toBe($value);
|
|
|
|
$response = $controller->update(($this->profileRequest)([
|
|
...$input, ...$locations, 'nama_kelompok' => 'Kelompok Diubah', 'jumlah_anggota_terdaftar' => 25,
|
|
]), $group);
|
|
expect($group->refresh()->nama_kelompok)->toBe('Kelompok Diubah')
|
|
->and($group->jumlah_anggota_terdaftar)->toBe(25);
|
|
expect($response->getTargetUrl())->toBe(route('kelompok-tani.edit', $group->id) . '#section-pengurus');
|
|
});
|
|
|
|
test('invalid membership prevents partial profile saves', function () {
|
|
$group = ($this->kelompok)(['jumlah_anggota_terdaftar' => 20]);
|
|
$request = ($this->profileRequest)([
|
|
'nama_kelompok' => 'Tidak boleh tersimpan', 'nama_responden' => 'Budi',
|
|
'provinsi_id' => 1, 'kabupaten_id' => 1, 'kecamatan_id' => 1,
|
|
'desa_kelurahan_id' => 1, 'jenis_kelompok' => 'Kelompok Tani (Poktan)',
|
|
'jumlah_anggota_terdaftar' => -1,
|
|
]);
|
|
|
|
expect(fn () => app(SurveyKelompokTaniController::class)->update($request, $group))
|
|
->toThrow(ValidationException::class);
|
|
expect($group->refresh()->nama_kelompok)->toBe('Kelompok A')
|
|
->and($group->jumlah_anggota_terdaftar)->toBe(20);
|
|
});
|
|
|
|
test('membership fields belong to the profile form on create and edit', function () {
|
|
$controller = app(SurveyKelompokTaniController::class);
|
|
($this->profileRequest)([], 'GET');
|
|
$create = ($this->profileDom)($controller->create());
|
|
expect($create->query('//form[@id="addKelompokTaniForm"]//input[@name="jumlah_anggota_terdaftar"]')->length)->toBe(1);
|
|
|
|
$group = ($this->kelompok)(['jumlah_anggota_terdaftar' => 20]);
|
|
$group->pengurus()->create([
|
|
'uid' => 'PKT-TEST', 'nama_pengurus' => 'Ketua A', 'jabatan' => 'Ketua', 'kontak' => '08123456789', 'status' => true,
|
|
]);
|
|
$edit = ($this->profileDom)($controller->edit(($this->profileRequest)([], 'GET'), $group));
|
|
expect($edit->query('//form[@id="editKelompokTaniForm"]//input[@name="jumlah_anggota_terdaftar" and @value="20"]')->length)->toBe(1);
|
|
expect($edit->query('//div[@id="section-pengurus"]//input[@value="Ketua A"]')->length)->toBe(1);
|
|
expect($edit->query('//form[contains(@class,"form-repeater-pengurus")]')->length)->toBe(1);
|
|
expect($edit->query('//ul[contains(@class,"nav-pills")]//a[normalize-space()="Keanggotaan"]')->length)->toBe(0);
|
|
});
|
|
|
|
test('SK fields render enabled only for statuses with an existing SK', function (?string $status, bool $enabled) {
|
|
$group = ($this->kelompok)(['status_legalitas' => $status]);
|
|
$view = app(SurveyKelompokTaniController::class)->edit(
|
|
($this->profileRequest)(['section' => 'kelembagaan'], 'GET'), $group
|
|
);
|
|
$dom = ($this->profileDom)($view);
|
|
foreach (['klb-nomor-sk', 'klb-tanggal-penerbitan-sk'] as $id) {
|
|
expect($dom->query('//input[@id="' . $id . '"]')->item(0)->hasAttribute('disabled'))->toBe(!$enabled);
|
|
}
|
|
expect($dom->query('//label[@for="klb-nomor-sk"]')->item(0)->textContent)->toBe('Nomor SK');
|
|
})->with([
|
|
[null, false], ['Belum ada SK/dokumen apa pun', false], ['Sedang proses pengajuan', false],
|
|
['Tidak tahu', false], ['Sudah ada SK dari desa/kecamatan', true],
|
|
['Sudah ada SK dan terdaftar di Dinas/Kabupaten', true],
|
|
]);
|
|
|
|
test('inactive SK input is ignored and existing values survive status changes', function () {
|
|
$group = ($this->kelompok)(['nomor_sk' => 'SK-LAMA', 'tanggal_tahun_penerbitan_sk' => '2020']);
|
|
$controller = app(SurveyKelompokTaniController::class);
|
|
$controller->update(($this->profileRequest)([
|
|
'section' => 'kelembagaan', 'status_legalitas' => 'Tidak tahu',
|
|
'nomor_sk' => ['invalid'], 'tanggal_tahun_penerbitan_sk' => str_repeat('x', 100),
|
|
]), $group);
|
|
expect($group->refresh()->nomor_sk)->toBe('SK-LAMA')
|
|
->and($group->tanggal_tahun_penerbitan_sk)->toBe('2020');
|
|
|
|
$controller->update(($this->profileRequest)([
|
|
'section' => 'kelembagaan', 'status_legalitas' => 'Sudah ada SK dari desa/kecamatan',
|
|
'nomor_sk' => 'SK-BARU', 'tanggal_tahun_penerbitan_sk' => '2026',
|
|
]), $group);
|
|
expect($group->refresh()->nomor_sk)->toBe('SK-BARU')
|
|
->and($group->tanggal_tahun_penerbitan_sk)->toBe('2026');
|
|
});
|
|
|
|
test('pengurus actions and the former membership URL return to profile', function () {
|
|
$group = ($this->kelompok)();
|
|
$controller = app(PengurusKelompokTaniController::class);
|
|
$response = $controller->index($group);
|
|
expect($response->getTargetUrl())->toBe(route('kelompok-tani.edit', $group->id) . '#section-ringkasan');
|
|
$response = $controller->store(($this->profileRequest)(['pengurus' => [[
|
|
'nama_pengurus' => 'Ketua A', 'jabatan' => 'Ketua', 'kontak' => '08123456789', 'status' => 1,
|
|
]]], 'POST'), $group);
|
|
expect($response->getTargetUrl())->toBe(route('kelompok-tani.edit', $group->id) . '#section-pengurus');
|
|
$pengurus = $group->pengurus()->firstOrFail();
|
|
$response = $controller->destroy($group, $pengurus);
|
|
expect($response->getTargetUrl())->toBe(route('kelompok-tani.edit', $group->id) . '#section-pengurus');
|
|
expect($group->pengurus()->count())->toBe(0);
|
|
});
|
|
|
|
test('meeting fields are shown in Kegiatan with existing values and removed from Kelembagaan', function () {
|
|
$group = ($this->kelompok)([
|
|
'frekuensi_pertemuan' => '1x per bulan', 'tanggal_pertemuan_terakhir' => '2026-10-20',
|
|
]);
|
|
$controller = app(SurveyKelompokTaniController::class);
|
|
$kelembagaan = ($this->profileDom)($controller->edit(
|
|
($this->profileRequest)(['section' => 'kelembagaan'], 'GET'), $group
|
|
));
|
|
expect($kelembagaan->query('//select[@name="frekuensi_pertemuan"] | //input[@name="tanggal_pertemuan_terakhir"]')->length)->toBe(0);
|
|
|
|
$aktivitas = ($this->profileDom)($controller->edit(
|
|
($this->profileRequest)(['section' => 'aktivitas-fungsi'], 'GET'), $group
|
|
));
|
|
expect($aktivitas->query('//form//select[@name="frekuensi_pertemuan"]/option[@selected]')->item(0)->getAttribute('value'))->toBe('1x per bulan');
|
|
expect($aktivitas->query('//form//input[@name="tanggal_pertemuan_terakhir"]')->item(0)->getAttribute('value'))->toBe('20-10-2026');
|
|
expect($aktivitas->query('//ul[contains(@class,"nav-pills")]//a[normalize-space()="Operasional"]')->length)->toBe(1);
|
|
expect($aktivitas->query('//input[@name="tanggal_pertemuan_terakhir"]')->item(0)->hasAttribute('placeholder'))->toBeFalse();
|
|
});
|
|
|
|
test('Kegiatan saves meeting details and rejects invalid dates without partial updates', function () {
|
|
$group = ($this->kelompok)();
|
|
$controller = app(SurveyKelompokTaniController::class);
|
|
$response = $controller->update(($this->profileRequest)([
|
|
'section' => 'aktivitas-fungsi', 'frekuensi_pertemuan' => '1x per bulan',
|
|
'tanggal_pertemuan_terakhir' => '20-10-2026', 'kegiatan_terlaksana' => ['Pertemuan/diskusi rutin'],
|
|
]), $group);
|
|
expect($group->refresh()->frekuensi_pertemuan)->toBe('1x per bulan')
|
|
->and($group->tanggal_pertemuan_terakhir->format('Y-m-d'))->toBe('2026-10-20')
|
|
->and($group->kegiatan_terlaksana)->toBe(['Pertemuan/diskusi rutin']);
|
|
expect($response->getTargetUrl())->toBe(route('kelompok-tani.edit', $group->id) . '?section=aktivitas-fungsi#section-aset-baku');
|
|
|
|
$invalid = ($this->profileRequest)([
|
|
'section' => 'aktivitas-fungsi', 'frekuensi_pertemuan' => 'Tidak rutin/tidak terjadwal',
|
|
'tanggal_pertemuan_terakhir' => 'invalid-date',
|
|
]);
|
|
expect(fn () => $controller->update($invalid, $group))->toThrow(ValidationException::class);
|
|
expect($group->refresh()->frekuensi_pertemuan)->toBe('1x per bulan')
|
|
->and($group->tanggal_pertemuan_terakhir->format('Y-m-d'))->toBe('2026-10-20');
|
|
|
|
$controller->update(($this->profileRequest)([
|
|
'section' => 'kelembagaan', 'cakupan_wilayah' => 'Desa', 'frekuensi_pertemuan' => 'Diabaikan',
|
|
'tanggal_pertemuan_terakhir' => 'invalid-date',
|
|
]), $group);
|
|
expect($group->refresh()->cakupan_wilayah)->toBe('Desa')
|
|
->and($group->frekuensi_pertemuan)->toBe('1x per bulan');
|
|
});
|
|
|
|
test('all group tabs show Simpan with a check and Kembali to the group list', function () {
|
|
($this->profileRequest)([], 'GET');
|
|
$group = ($this->kelompok)();
|
|
foreach (['pengurus', 'aset', 'bantuan'] as $relation) $group->setRelation($relation, collect());
|
|
|
|
foreach (['add', 'edit', 'kelembagaan', 'aktivitas-fungsi', 'sosial-konflik'] as $template) {
|
|
$dom = ($this->profileDom)(view('produktivitas.survey-kelompok-tani.' . $template, [
|
|
'kelompokTani' => $template === 'add' ? null : $group,
|
|
'provinsi' => collect(), 'komoditas' => collect(),
|
|
]));
|
|
$buttons = $dom->query('//div[contains(@class,"kelompok-tani-page")]//button[@type="submit" and contains(@class,"btn-success")]');
|
|
expect($buttons->length)->toBeGreaterThan(0);
|
|
foreach ($buttons as $button) {
|
|
$form = $dom->query('ancestor::form', $button)->item(0);
|
|
expect($form->hasAttribute('novalidate'))->toBeTrue();
|
|
expect(trim($button->textContent))->toBe('Simpan');
|
|
expect($dom->query('.//i[contains(@class,"tabler-check")]', $button)->length)->toBe(1);
|
|
$back = $dom->query('./a[normalize-space()="Kembali"]', $button->parentNode);
|
|
expect($back->length)->toBe(1);
|
|
expect($back->item(0)->getAttribute('href'))->toBe(route('kelompok-tani.index'));
|
|
}
|
|
}
|
|
});
|
|
|
|
test('flash notifications appear once above the group summary regardless of section', function (string $key) {
|
|
($this->profileRequest)([], 'GET');
|
|
session()->flash($key, 'Pesan pemeriksaan');
|
|
session()->flash($key . '_section', 'kas');
|
|
$group = ($this->kelompok)();
|
|
$group->setRelation('pengurus', collect());
|
|
$dom = ($this->profileDom)(view('produktivitas.survey-kelompok-tani.edit', [
|
|
'kelompokTani' => $group, 'provinsi' => collect(), 'komoditas' => collect(),
|
|
]));
|
|
|
|
expect($dom->query('//div[@id="kelompok-tani-notifications"]')->length)->toBe(1);
|
|
expect($dom->query('//div[@id="kelompok-tani-summary"]/preceding-sibling::div[@id="kelompok-tani-notifications"]')->length)->toBe(1);
|
|
expect($dom->query('//div[@id="kelompok-tani-notifications"]//div[@role="alert"]')->length)->toBe(1);
|
|
expect($dom->query('//div[@id="kelompok-tani-notifications"]')->item(0)->textContent)->toContain('Pesan pemeriksaan');
|
|
})->with(['success', 'error', 'warning', 'info', 'status', 'message']);
|
|
|
|
test('validation errors are summarized above the group summary', function () {
|
|
($this->profileRequest)([], 'GET');
|
|
$errors = new ViewErrorBag();
|
|
$errors->put('default', new Illuminate\Support\MessageBag(['nomor_sk' => 'Nomor SK tidak valid.']));
|
|
view()->share('errors', $errors);
|
|
$dom = ($this->profileDom)(view('produktivitas.survey-kelompok-tani.kelembagaan', [
|
|
'kelompokTani' => ($this->kelompok)(),
|
|
]));
|
|
|
|
expect($dom->query('//div[@id="kelompok-tani-summary"]/preceding-sibling::div[@id="kelompok-tani-notifications"]')->length)->toBe(1);
|
|
expect($dom->query('//div[@id="kelompok-tani-notifications"]//li')->item(0)->textContent)->toBe('Nomor SK tidak valid.');
|
|
});
|
|
|
|
test('one asset submission saves standard and additional assets and skips empty rows', function () {
|
|
$group = ($this->kelompok)();
|
|
$controller = app(AsetKelompokTaniController::class);
|
|
$response = $controller->update(($this->profileRequest)([
|
|
'aset' => [[
|
|
'jenis_aset' => 'Timbangan', 'status_aset' => 1,
|
|
'status_pengelolaan_aset' => 'Hibah', 'kondisi_aset' => 'Berfungsi',
|
|
]],
|
|
'aset_lainnya' => [
|
|
['jenis_aset' => 'Mesin pengering', 'status_pengelolaan_aset' => 'Swadaya', 'kondisi_aset' => 'Berfungsi'],
|
|
['jenis_aset' => '', 'status_pengelolaan_aset' => '', 'kondisi_aset' => ''],
|
|
],
|
|
]), $group);
|
|
|
|
expect($group->aset()->count())->toBe(2);
|
|
expect($group->aset()->where('jenis_aset', 'Timbangan')->firstOrFail()->status_pengelolaan_aset)->toBe('Hibah');
|
|
expect($group->aset()->where('jenis_aset', 'Mesin pengering')->firstOrFail()->kondisi_aset)->toBe('Berfungsi');
|
|
expect($response->getTargetUrl())->toBe(route('kelompok-tani.edit', $group->id) . '?section=aktivitas-fungsi#section-status-bantuan');
|
|
});
|
|
|
|
test('invalid additional assets prevent standard assets from being changed', function () {
|
|
$group = ($this->kelompok)();
|
|
$existing = $group->aset()->create([
|
|
'uid' => 'AKT-TEST', 'jenis_aset' => 'Timbangan', 'status_aset' => true,
|
|
'status_pengelolaan_aset' => 'Hibah', 'kondisi_aset' => 'Berfungsi',
|
|
]);
|
|
$request = ($this->profileRequest)([
|
|
'aset' => [['jenis_aset' => 'Timbangan', 'status_aset' => 0]],
|
|
'aset_lainnya' => [['jenis_aset' => 'Timbangan']],
|
|
]);
|
|
expect(fn () => app(AsetKelompokTaniController::class)->update($request, $group))->toThrow(ValidationException::class);
|
|
expect($existing->fresh()->trashed())->toBeFalse();
|
|
expect($group->aset()->count())->toBe(1);
|
|
});
|
|
|
|
test('combined asset save restores and removes standard assets while keeping additional assets', function () {
|
|
$group = ($this->kelompok)();
|
|
$controller = app(AsetKelompokTaniController::class);
|
|
$existing = $group->aset()->create([
|
|
'uid' => 'AKT-TEST', 'jenis_aset' => 'Timbangan', 'status_aset' => true,
|
|
'status_pengelolaan_aset' => 'Hibah', 'kondisi_aset' => 'Berfungsi',
|
|
]);
|
|
$existing->delete();
|
|
$controller->update(($this->profileRequest)([
|
|
'aset' => [[
|
|
'jenis_aset' => 'Timbangan', 'status_aset' => 1,
|
|
'status_pengelolaan_aset' => 'Hibah', 'kondisi_aset' => 'Berfungsi',
|
|
]],
|
|
'aset_lainnya' => [['jenis_aset' => 'Mesin pengering']],
|
|
]), $group);
|
|
expect($existing->fresh()->trashed())->toBeFalse();
|
|
expect($group->aset()->withTrashed()->where('jenis_aset', 'Timbangan')->count())->toBe(1);
|
|
|
|
$controller->update(($this->profileRequest)([
|
|
'aset' => [['jenis_aset' => 'Timbangan', 'status_aset' => 0]],
|
|
]), $group);
|
|
expect($existing->fresh()->trashed())->toBeTrue();
|
|
expect($group->aset()->pluck('jenis_aset')->all())->toBe(['Mesin pengering']);
|
|
});
|
|
|
|
test('asset save rolls back standard changes if additional asset creation fails', function () {
|
|
$group = ($this->kelompok)();
|
|
$existing = $group->aset()->create([
|
|
'uid' => 'AKT-TEST', 'jenis_aset' => 'Timbangan', 'status_aset' => true,
|
|
'status_pengelolaan_aset' => 'Hibah', 'kondisi_aset' => 'Berfungsi',
|
|
]);
|
|
Event::listen('eloquent.creating: ' . AsetKelompokTani::class, function ($asset) {
|
|
if ($asset->jenis_aset === 'Mesin gagal') throw new RuntimeException('Simulated failure');
|
|
});
|
|
$request = ($this->profileRequest)([
|
|
'aset' => [['jenis_aset' => 'Timbangan', 'status_aset' => 0]],
|
|
'aset_lainnya' => [['jenis_aset' => 'Mesin gagal']],
|
|
]);
|
|
expect(fn () => app(AsetKelompokTaniController::class)->update($request, $group))->toThrow(RuntimeException::class);
|
|
expect($existing->fresh()->trashed())->toBeFalse();
|
|
expect($group->aset()->count())->toBe(1);
|
|
});
|
|
|
|
test('asset card has one shared form and preserves inputs after validation errors', function () {
|
|
$group = ($this->kelompok)();
|
|
$existing = $group->aset()->create([
|
|
'uid' => 'AKT-TEST', 'jenis_aset' => 'Mesin lama', 'status_aset' => true,
|
|
'status_pengelolaan_aset' => 'Hibah', 'kondisi_aset' => 'Berfungsi',
|
|
]);
|
|
($this->profileRequest)([], 'GET');
|
|
session()->flashInput([
|
|
'aset' => [['status_aset' => '1', 'status_pengelolaan_aset' => 'Pinjaman', 'kondisi_aset' => 'Mangkrak']],
|
|
'aset_lainnya' => [['jenis_aset' => 'Mesin baru', 'status_pengelolaan_aset' => 'Sewa', 'kondisi_aset' => 'Berfungsi']],
|
|
]);
|
|
$dom = ($this->profileDom)(app(SurveyKelompokTaniController::class)->edit(($this->profileRequest)(['section' => 'aktivitas-fungsi'], 'GET'), $group));
|
|
expect($dom->query('//div[@id="section-aset-baku"]//form[@id="aset-produktif-form"]')->length)->toBe(1);
|
|
expect($dom->query('//form[@id="aset-produktif-form"]//div[@id="section-aset-lainnya"]')->length)->toBe(1);
|
|
expect($dom->query('//form[@id="aset-produktif-form"]//button[contains(@class,"btn-success")]')->length)->toBe(1);
|
|
expect($dom->query('//form[@id="aset-produktif-form"]//form')->length)->toBe(0);
|
|
expect($dom->query('//form[@id="aset-produktif-form"]//input[@value="Mesin baru"]')->length)->toBe(1);
|
|
expect($dom->query('//form[@id="aset-produktif-form"]//select[@name="aset[0][status_pengelolaan_aset]"]/option[@selected]')->item(0)->getAttribute('value'))->toBe('Pinjaman');
|
|
expect($dom->query('//form[@id="aset-produktif-form"]//select[@name="status_pengelolaan_aset"]/option[@selected]')->item(0)->getAttribute('value'))->toBe('Sewa');
|
|
expect($dom->query('//button[@form="delete-aset-' . $existing->id . '"]')->length)->toBe(1);
|
|
expect($dom->query('//form[@id="delete-aset-' . $existing->id . '"]')->length)->toBe(1);
|
|
});
|
|
|
|
test('cash management is visible only when the cash asset is present or selected in old input', function (?bool $savedStatus, bool $deleted, ?string $oldStatus, bool $visible) {
|
|
$group = ($this->kelompok)();
|
|
if ($savedStatus !== null) {
|
|
$asset = $group->aset()->create([
|
|
'uid' => 'AKT-KAS', 'jenis_aset' => 'Kas/simpanan kelompok', 'status_aset' => $savedStatus,
|
|
'status_pengelolaan_aset' => 'Swadaya', 'kondisi_aset' => 'Berfungsi',
|
|
]);
|
|
if ($deleted) $asset->delete();
|
|
}
|
|
($this->profileRequest)([], 'GET');
|
|
if ($oldStatus !== null) {
|
|
$cashIndex = array_search('Kas/simpanan kelompok', AsetKelompokTaniController::JENIS_ASET_BAKU, true);
|
|
session()->flashInput(['aset' => [$cashIndex => ['status_aset' => $oldStatus]]]);
|
|
}
|
|
$dom = ($this->profileDom)(app(SurveyKelompokTaniController::class)->edit(($this->profileRequest)(['section' => 'aktivitas-fungsi'], 'GET'), $group));
|
|
expect($dom->query('//div[@id="section-kas"]')->item(0)->hasAttribute('hidden'))->toBe(!$visible);
|
|
expect($dom->query('//tr[@data-aset-kas]//input[@value="1"]')->item(0)->hasAttribute('checked'))->toBe($visible);
|
|
})->with([
|
|
[null, false, null, false],
|
|
[true, false, null, true],
|
|
[false, false, null, false],
|
|
[true, true, null, false],
|
|
[true, false, '0', false],
|
|
[null, false, '1', true],
|
|
]);
|
|
|
|
test('the combined tab contains activities assets and cash management with one header', function () {
|
|
$group = ($this->kelompok)();
|
|
$group->aset()->create([
|
|
'uid' => 'AKT-KAS', 'jenis_aset' => 'Kas/simpanan kelompok', 'status_aset' => true,
|
|
'status_pengelolaan_aset' => 'Swadaya', 'kondisi_aset' => 'Berfungsi',
|
|
]);
|
|
$request = ($this->profileRequest)(['section' => 'aktivitas-fungsi'], 'GET');
|
|
$route = (new Illuminate\Routing\Route('GET', '/survey/kelompok-tani/{kelompokTani}/edit', fn () => null))->name('kelompok-tani.edit');
|
|
$request->setRouteResolver(fn () => $route);
|
|
$dom = ($this->profileDom)(app(SurveyKelompokTaniController::class)->edit($request, $group));
|
|
|
|
expect($dom->query('//div[@id="kelompok-tani-summary"]')->length)->toBe(1);
|
|
expect($dom->query('//div[@id="section-aktivitas-fungsi"]')->length)->toBe(1);
|
|
expect($dom->query('//div[@id="section-aset-baku"]')->length)->toBe(1);
|
|
expect($dom->query('//div[@id="section-kas"]')->item(0)->hasAttribute('hidden'))->toBeFalse();
|
|
expect($dom->query('//div[@id="section-status-bantuan"]')->length)->toBe(1);
|
|
expect($dom->query('//div[@id="section-penilaian"]')->length)->toBe(0);
|
|
expect($dom->query('//ul[contains(@class,"nav-pills")]//a[normalize-space()="Bantuan & Penilaian"]')->length)->toBe(0);
|
|
expect($dom->query('//ul[contains(@class,"nav-pills")]//a[contains(@class,"active") and normalize-space()="Operasional"]')->length)->toBe(1);
|
|
expect($dom->query('//ul[contains(@class,"nav-pills")]//a[normalize-space()="Aset & Keuangan"]')->length)->toBe(0);
|
|
});
|
|
|
|
test('asset actions and cash saves return to the combined tab', function () {
|
|
$group = ($this->kelompok)();
|
|
$controller = app(AsetKelompokTaniController::class);
|
|
$url = route('kelompok-tani.edit', $group->id) . '?section=aktivitas-fungsi';
|
|
expect($controller->index($group)->getTargetUrl())->toBe($url . '#section-aset-baku');
|
|
|
|
$response = $controller->store(($this->profileRequest)([
|
|
'aset_lainnya' => [['jenis_aset' => 'Mesin pengering']],
|
|
], 'POST'), $group);
|
|
expect($response->getTargetUrl())->toBe($url . '#section-status-bantuan');
|
|
$response = $controller->destroy($group, $group->aset()->firstOrFail());
|
|
expect($response->getTargetUrl())->toBe($url . '#section-aset-lainnya');
|
|
|
|
$response = app(SurveyKelompokTaniController::class)->update(($this->profileRequest)([
|
|
'section' => 'kas', 'pengelola_kas' => 'Bendahara kelompok',
|
|
]), $group);
|
|
expect($response->getTargetUrl())->toBe($url . '#section-status-bantuan');
|
|
expect($group->refresh()->pengelola_kas)->toBe('Bendahara kelompok');
|
|
});
|
|
|
|
test('present assets require both management status and condition before saving any assets', function ($status) {
|
|
$group = ($this->kelompok)();
|
|
$request = ($this->profileRequest)([
|
|
'aset' => [['jenis_aset' => 'Timbangan', 'status_aset' => $status]],
|
|
'aset_lainnya' => [['jenis_aset' => 'Mesin pengering']],
|
|
]);
|
|
try {
|
|
app(AsetKelompokTaniController::class)->update($request, $group);
|
|
$this->fail('An asset marked Ada must require management status and condition.');
|
|
} catch (ValidationException $exception) {
|
|
expect(array_keys($exception->errors()))->toBe([
|
|
'aset.0.status_pengelolaan_aset', 'aset.0.kondisi_aset',
|
|
]);
|
|
expect($exception->errors()['aset.0.kondisi_aset'][0])->toBe('Kondisi wajib diisi jika aset dipilih Ada.');
|
|
}
|
|
expect($group->aset()->count())->toBe(0);
|
|
})->with(['1', 1, true]);
|
|
|
|
test('asset requirements follow each row and reject blank values', function (array $details, string $missingField) {
|
|
$group = ($this->kelompok)();
|
|
$request = ($this->profileRequest)([
|
|
'aset' => [
|
|
['jenis_aset' => 'Timbangan', 'status_aset' => '0'],
|
|
['jenis_aset' => 'Gudang/tempat simpan', 'status_aset' => '1', ...$details],
|
|
],
|
|
]);
|
|
try {
|
|
app(AsetKelompokTaniController::class)->update($request, $group);
|
|
$this->fail('The selected asset has a missing detail.');
|
|
} catch (ValidationException $exception) {
|
|
expect(array_keys($exception->errors()))->toBe(['aset.1.' . $missingField]);
|
|
}
|
|
expect($group->aset()->count())->toBe(0);
|
|
})->with([
|
|
[['status_pengelolaan_aset' => '', 'kondisi_aset' => 'Berfungsi'], 'status_pengelolaan_aset'],
|
|
[['status_pengelolaan_aset' => 'Hibah', 'kondisi_aset' => null], 'kondisi_aset'],
|
|
[['status_pengelolaan_aset' => 'Hibah', 'kondisi_aset' => ' '], 'kondisi_aset'],
|
|
]);
|
|
|
|
test('asset dropdowns are required only on rows selected Ada', function (bool $present) {
|
|
$group = ($this->kelompok)();
|
|
$group->aset()->create([
|
|
'uid' => 'AKT-TEST', 'jenis_aset' => 'Timbangan', 'status_aset' => $present,
|
|
'status_pengelolaan_aset' => 'Hibah', 'kondisi_aset' => 'Berfungsi',
|
|
]);
|
|
$request = ($this->profileRequest)(['section' => 'aktivitas-fungsi'], 'GET');
|
|
$dom = ($this->profileDom)(app(SurveyKelompokTaniController::class)->edit($request, $group));
|
|
$index = array_search('Timbangan', AsetKelompokTaniController::JENIS_ASET_BAKU, true);
|
|
foreach (['status_pengelolaan_aset', 'kondisi_aset'] as $field) {
|
|
$select = $dom->query('//select[@name="aset[' . $index . '][' . $field . ']"]')->item(0);
|
|
expect($select->hasAttribute('required'))->toBe($present);
|
|
expect($select->hasAttribute('disabled'))->toBe(!$present);
|
|
}
|
|
})->with([true, false]);
|
|
|
|
test('asset validation errors appear beside the marked required dropdowns', function () {
|
|
$group = ($this->kelompok)();
|
|
$request = ($this->profileRequest)(['section' => 'aktivitas-fungsi'], 'GET');
|
|
session()->flashInput(['aset' => [['status_aset' => '1']]]);
|
|
$messages = [
|
|
'aset.0.status_pengelolaan_aset' => 'Status Pengelolaan wajib diisi jika aset dipilih Ada.',
|
|
'aset.0.kondisi_aset' => 'Kondisi wajib diisi jika aset dipilih Ada.',
|
|
];
|
|
$errors = new ViewErrorBag();
|
|
$errors->put('default', new Illuminate\Support\MessageBag($messages));
|
|
view()->share('errors', $errors);
|
|
$dom = ($this->profileDom)(app(SurveyKelompokTaniController::class)->edit($request, $group));
|
|
|
|
expect($dom->query('//form[@id="aset-produktif-form"]//th/span[contains(@class,"text-danger")]')->length)->toBe(2);
|
|
foreach (['status_pengelolaan_aset' => 'status-pengelolaan', 'kondisi_aset' => 'kondisi'] as $field => $idPart) {
|
|
$id = 'aset-0-' . $idPart;
|
|
$select = $dom->query('//select[@id="' . $id . '"]')->item(0);
|
|
expect($select->getAttribute('class'))->toContain('is-invalid');
|
|
expect($select->getAttribute('aria-describedby'))->toBe($id . '-error');
|
|
expect(trim($dom->query('//div[@id="' . $id . '-error"]')->item(0)->textContent))->toBe($messages['aset.0.' . $field]);
|
|
}
|
|
});
|
|
|
|
test('saving assets advances to cash only when the cash section is available', function (bool $hasCash) {
|
|
$group = ($this->kelompok)();
|
|
$controller = app(AsetKelompokTaniController::class);
|
|
$input = ['aset' => [[
|
|
'jenis_aset' => 'Kas/simpanan kelompok', 'status_aset' => $hasCash,
|
|
'status_pengelolaan_aset' => 'Swadaya', 'kondisi_aset' => 'Berfungsi',
|
|
]]];
|
|
$response = $controller->update(($this->profileRequest)($input), $group);
|
|
$url = route('kelompok-tani.edit', $group->id) . '?section=aktivitas-fungsi';
|
|
expect($response->getTargetUrl())->toBe($url . ($hasCash ? '#section-kas' : '#section-status-bantuan'));
|
|
|
|
$input['aset'][0]['status_aset'] = false;
|
|
$response = $controller->update(($this->profileRequest)($input), $group);
|
|
expect($response->getTargetUrl())->toBe($url . '#section-status-bantuan');
|
|
})->with([true, false]);
|
|
|
|
test('saving aid status stays on the combined aid card in Operasional', function (bool $hasAid) {
|
|
$group = ($this->kelompok)();
|
|
$response = app(SurveyKelompokTaniController::class)->update(($this->profileRequest)([
|
|
'section' => 'status-bantuan', 'pernah_mendapatkan_bantuan' => $hasAid,
|
|
'bantuan' => $hasAid ? [['nama_program' => 'Program A', 'penyelenggara' => 'Dinas', 'bentuk_bantuan' => 'Bibit', 'tahun' => 2026]] : [],
|
|
]), $group);
|
|
$url = route('kelompok-tani.edit', $group->id) . '?section=aktivitas-fungsi';
|
|
expect($response->getTargetUrl())->toBe($url . '#section-status-bantuan');
|
|
})->with([true, false]);
|
|
|
|
test('legacy aid saves return to Operasional and failed saves retain the notification target', function () {
|
|
$group = ($this->kelompok)(['pernah_mendapatkan_bantuan' => true]);
|
|
$controller = app(BantuanKelompokTaniController::class);
|
|
$response = $controller->store(($this->profileRequest)(['bantuan' => [[
|
|
'nama_program' => 'Bibit Kakao', 'penyelenggara' => 'Dinas Pertanian', 'bentuk_bantuan' => 'Bibit', 'tahun' => 2026,
|
|
]]], 'POST'), $group);
|
|
$url = route('kelompok-tani.edit', $group->id) . '?section=aktivitas-fungsi';
|
|
expect($response->getTargetUrl())->toBe($url . '#section-status-bantuan');
|
|
|
|
$group->update(['pernah_mendapatkan_bantuan' => false]);
|
|
$response = $controller->store(($this->profileRequest)([], 'POST'), $group);
|
|
expect($response->getTargetUrl())->toBe($url . '#kelompok-tani-notifications');
|
|
expect($group->bantuan()->count())->toBe(1);
|
|
});
|
|
|
|
test('single and final section saves stay on their own box', function () {
|
|
$group = ($this->kelompok)();
|
|
$controller = app(SurveyKelompokTaniController::class);
|
|
foreach ([
|
|
'kelembagaan' => ['cakupan_wilayah' => 'Desa'],
|
|
'sosial-konflik' => ['konflik_kelompok' => 'Tidak pernah'],
|
|
] as $section => $input) {
|
|
$response = $controller->update(($this->profileRequest)(['section' => $section, ...$input]), $group);
|
|
expect(parse_url($response->getTargetUrl(), PHP_URL_FRAGMENT))->toBe('section-' . $section);
|
|
}
|
|
});
|
|
|
|
test('invalid required fields return Indonesian errors above the summary and beside each field', function (string $section, array $payload, array $messages) {
|
|
$group = ($this->kelompok)();
|
|
$url = route('kelompok-tani.edit', $group->id) . ($section === 'identitas' ? '' : '?section=' . ($section === 'status-bantuan' ? 'aktivitas-fungsi' : $section));
|
|
$response = $this->from($url)->put(route('kelompok-tani.update', $group->id), ['section' => $section, ...$payload]);
|
|
$response->assertRedirect($url)->assertSessionHasErrors(array_keys($messages));
|
|
$page = $this->get($url)->assertOk();
|
|
expect(strpos($page->getContent(), 'id="kelompok-tani-notifications"'))->toBeLessThan(strpos($page->getContent(), 'id="kelompok-tani-summary"'));
|
|
|
|
$dom = new DOMDocument();
|
|
libxml_use_internal_errors(true);
|
|
$dom->loadHTML($page->getContent());
|
|
libxml_clear_errors();
|
|
$xpath = new DOMXPath($dom);
|
|
$summary = $xpath->query('//div[@id="kelompok-tani-notifications"]')->item(0)->textContent;
|
|
$inlineMessages = collect($xpath->query('//div[contains(@class,"invalid-feedback")]'))->map(fn ($node) => trim($node->textContent))->all();
|
|
foreach ($messages as $message) {
|
|
expect($summary)->toContain($message);
|
|
expect($inlineMessages)->toContain($message);
|
|
}
|
|
expect($xpath->query('//input[contains(@class,"is-invalid")] | //select[contains(@class,"is-invalid")]')->length)->toBeGreaterThan(0);
|
|
expect($group->refresh()->nama_kelompok)->toBe('Kelompok A');
|
|
})->with([
|
|
['identitas', [], [
|
|
'nama_responden' => 'Nama responden wajib diisi.', 'nama_kelompok' => 'Nama kelompok wajib diisi.',
|
|
'provinsi_id' => 'Provinsi wajib diisi.', 'kabupaten_id' => 'Kabupaten/Kota wajib diisi.',
|
|
'kecamatan_id' => 'Kecamatan wajib diisi.', 'desa_kelurahan_id' => 'Desa/Kelurahan wajib dipilih.',
|
|
'jenis_kelompok' => 'Terdaftar/Dikenal Sebagai wajib diisi.',
|
|
]],
|
|
['identitas', ['jenis_kelompok' => 'Lainnya'], ['jenis_kelompok_lainnya' => 'Sebutkan Jenis Kelompok wajib diisi.']],
|
|
['status-bantuan', [], ['pernah_mendapatkan_bantuan' => 'Pengalaman Akses Bantuan/Program wajib diisi.']],
|
|
['status-bantuan', ['pernah_mendapatkan_bantuan' => '1', 'bantuan' => [[]]], [
|
|
'bantuan.0.nama_program' => 'Nama Program/Bantuan pada baris pertama wajib diisi.',
|
|
'bantuan.0.penyelenggara' => 'Penyelenggara pada baris pertama wajib diisi.',
|
|
'bantuan.0.bentuk_bantuan' => 'Bentuk Bantuan pada baris pertama wajib diisi.',
|
|
'bantuan.0.tahun' => 'Tahun pada baris pertama wajib diisi.',
|
|
]],
|
|
['sosial-konflik', ['konflik_kelompok' => 'Pernah, sudah selesai'], [
|
|
'topik_konflik' => 'Jika Pernah, Soal Apa? wajib diisi jika pernah terjadi konflik internal.',
|
|
]],
|
|
]);
|
|
|
|
test('asset errors show together above the summary and beside both conditional dropdowns', function () {
|
|
$group = ($this->kelompok)();
|
|
$url = route('kelompok-tani.edit', $group->id) . '?section=aktivitas-fungsi';
|
|
$this->from($url)->put(route('kelompok-tani.aset-keuangan.update', $group->id), [
|
|
'aset' => [['jenis_aset' => AsetKelompokTaniController::JENIS_ASET_BAKU[0], 'status_aset' => '1']],
|
|
])->assertRedirect($url)->assertSessionHasErrors(['aset.0.status_pengelolaan_aset', 'aset.0.kondisi_aset']);
|
|
$page = $this->get($url)->assertOk();
|
|
foreach (['Status Pengelolaan wajib diisi jika aset dipilih Ada.', 'Kondisi wajib diisi jika aset dipilih Ada.'] as $message) {
|
|
expect(substr_count($page->getContent(), $message))->toBeGreaterThanOrEqual(2);
|
|
}
|
|
expect($group->aset()->count())->toBe(0);
|
|
});
|
|
|
|
test('profile location choices survive a failed save on both create and edit', function (bool $creating) {
|
|
$group = ($this->kelompok)();
|
|
$url = $creating ? route('kelompok-tani.create') : route('kelompok-tani.edit', $group->id);
|
|
$input = ['provinsi_id' => 1, 'kabupaten_id' => 1, 'kecamatan_id' => 1, 'desa_kelurahan_id' => 1, 'jenis_kelompok' => 'Kelompok Tani (Poktan)'];
|
|
$response = $creating
|
|
? $this->from($url)->post(route('kelompok-tani.store'), $input)
|
|
: $this->from($url)->put(route('kelompok-tani.update', $group->id), $input);
|
|
$response->assertRedirect($url)->assertSessionHasErrors(['nama_responden', 'nama_kelompok']);
|
|
$page = $this->get($url)->assertOk();
|
|
$dom = new DOMDocument();
|
|
libxml_use_internal_errors(true);
|
|
$dom->loadHTML($page->getContent());
|
|
libxml_clear_errors();
|
|
$xpath = new DOMXPath($dom);
|
|
foreach (array_keys($input) as $field) {
|
|
expect($xpath->query('//select[@name="' . $field . '"]/option[@selected]')->length)->toBe(1);
|
|
}
|
|
expect($xpath->query('//select[@name="desa_kelurahan_id" and @disabled]')->length)->toBe(0);
|
|
})->with([true, false]);
|
|
|
|
test('invalid location input returns feedback without breaking the form reload', function ($invalidId) {
|
|
$url = route('kelompok-tani.create');
|
|
$this->from($url)->post(route('kelompok-tani.store'), [
|
|
'provinsi_id' => $invalidId, 'kabupaten_id' => $invalidId, 'kecamatan_id' => $invalidId, 'desa_kelurahan_id' => $invalidId,
|
|
])->assertRedirect($url)->assertSessionHasErrors(['provinsi_id', 'kabupaten_id', 'kecamatan_id', 'desa_kelurahan_id']);
|
|
$this->get($url)->assertOk()->assertSee('Pilihan Provinsi tidak valid.');
|
|
})->with([[['invalid']], ['1.5'], ['invalid']]);
|
|
|
|
test('aid experience and program entries are saved together in Operasional', function () {
|
|
$group = ($this->kelompok)(['pernah_mendapatkan_bantuan' => false]);
|
|
$response = app(SurveyKelompokTaniController::class)->update(($this->profileRequest)([
|
|
'section' => 'status-bantuan', 'pernah_mendapatkan_bantuan' => '1',
|
|
'bantuan' => [
|
|
['nama_program' => 'Bibit Kakao', 'penyelenggara' => 'Dinas Pertanian', 'bentuk_bantuan' => 'Bibit', 'tahun' => 2026],
|
|
['nama_program' => '', 'penyelenggara' => '', 'bentuk_bantuan' => '', 'tahun' => null],
|
|
],
|
|
]), $group);
|
|
expect($group->refresh()->pernah_mendapatkan_bantuan)->toBeTrue();
|
|
expect($group->bantuan()->pluck('nama_program')->all())->toBe(['Bibit Kakao']);
|
|
expect($response->getTargetUrl())->toBe(route('kelompok-tani.edit', $group->id) . '?section=aktivitas-fungsi#section-status-bantuan');
|
|
expect(session('success'))->toBe('Data Operasional berhasil disimpan.');
|
|
});
|
|
|
|
test('answering Belum Pernah ignores hidden program payloads and preserves existing history', function ($payload) {
|
|
$group = ($this->kelompok)(['pernah_mendapatkan_bantuan' => true]);
|
|
$group->bantuan()->create([
|
|
'uid' => 'BKT-TEST', 'nama_program' => 'Program Lama', 'penyelenggara' => 'Dinas', 'bentuk_bantuan' => 'Bibit',
|
|
]);
|
|
app(SurveyKelompokTaniController::class)->update(($this->profileRequest)([
|
|
'section' => 'status-bantuan', 'pernah_mendapatkan_bantuan' => '0', 'bantuan' => $payload,
|
|
]), $group);
|
|
expect($group->refresh()->pernah_mendapatkan_bantuan)->toBeFalse();
|
|
expect($group->bantuan()->pluck('nama_program')->all())->toBe(['Program Lama']);
|
|
})->with([
|
|
['malformed'],
|
|
[[['nama_program' => ['invalid'], 'tahun' => 'invalid']]],
|
|
]);
|
|
|
|
test('invalid aid entries cannot partially update the experience answer', function () {
|
|
$group = ($this->kelompok)(['pernah_mendapatkan_bantuan' => false]);
|
|
$request = ($this->profileRequest)([
|
|
'section' => 'status-bantuan', 'pernah_mendapatkan_bantuan' => '1',
|
|
'bantuan' => [['nama_program' => 'Program A', 'tahun' => date('Y') + 1]],
|
|
]);
|
|
expect(fn () => app(SurveyKelompokTaniController::class)->update($request, $group))->toThrow(ValidationException::class);
|
|
expect($group->refresh()->pernah_mendapatkan_bantuan)->toBeFalse();
|
|
expect($group->bantuan()->count())->toBe(0);
|
|
});
|
|
|
|
test('aid creation failures roll back the experience answer', function () {
|
|
$group = ($this->kelompok)(['pernah_mendapatkan_bantuan' => false]);
|
|
Event::listen('eloquent.creating: ' . BantuanKelompokTani::class, function () {
|
|
throw new RuntimeException('Simulated aid failure');
|
|
});
|
|
$request = ($this->profileRequest)([
|
|
'section' => 'status-bantuan', 'pernah_mendapatkan_bantuan' => '1',
|
|
'bantuan' => [['nama_program' => 'Program A', 'penyelenggara' => 'Dinas', 'bentuk_bantuan' => 'Bibit', 'tahun' => 2026]],
|
|
]);
|
|
expect(fn () => app(SurveyKelompokTaniController::class)->update($request, $group))->toThrow(RuntimeException::class);
|
|
expect($group->refresh()->pernah_mendapatkan_bantuan)->toBeFalse();
|
|
expect($group->bantuan()->count())->toBe(0);
|
|
});
|
|
|
|
test('aid list shares the experience box and is only visible for Pernah', function (?bool $savedAnswer, ?string $oldAnswer, bool $visible) {
|
|
$group = ($this->kelompok)(['pernah_mendapatkan_bantuan' => $savedAnswer]);
|
|
$request = ($this->profileRequest)(['section' => 'aktivitas-fungsi'], 'GET');
|
|
if ($oldAnswer !== null) {
|
|
session()->flashInput([
|
|
'pernah_mendapatkan_bantuan' => $oldAnswer,
|
|
'bantuan' => [['nama_program' => 'Program belum disimpan', 'penyelenggara' => 'Dinas', 'bentuk_bantuan' => 'Bibit', 'tahun' => 2026]],
|
|
]);
|
|
}
|
|
$dom = ($this->profileDom)(app(SurveyKelompokTaniController::class)->edit($request, $group));
|
|
expect($dom->query('//form[@id="pengalaman-bantuan-form"]//div[@id="section-bantuan"]')->length)->toBe(1);
|
|
expect($dom->query('//div[@id="section-bantuan"]')->item(0)->hasAttribute('hidden'))->toBe(!$visible);
|
|
expect($dom->query('//fieldset[@id="bantuan-fields"]')->item(0)->hasAttribute('disabled'))->toBe(!$visible);
|
|
expect($dom->query('//form[@id="pengalaman-bantuan-form"]//button[contains(@class,"btn-success")]')->length)->toBe(1);
|
|
expect($dom->query('//form[@id="pengalaman-bantuan-form"]//form')->length)->toBe(0);
|
|
expect($dom->query('//div[@id="section-penilaian"] | //input[@name="potensi_kelompok"] | //textarea[@name="alasan_penilaian_potensi"]')->length)->toBe(0);
|
|
if ($oldAnswer !== null) {
|
|
expect($dom->query('//form[@id="pengalaman-bantuan-form"]//input[@value="Program belum disimpan"]')->length)->toBe(1);
|
|
}
|
|
})->with([
|
|
[null, null, false], [false, null, false], [true, null, true], [true, '0', false], [false, '1', true],
|
|
]);
|
|
|
|
test('old aid URLs redirect to Operasional and removed assessment fields cannot be saved', function () {
|
|
$group = ($this->kelompok)(['potensi_kelompok' => 'Rendah']);
|
|
$url = route('kelompok-tani.edit', $group->id) . '?section=aktivitas-fungsi#section-status-bantuan';
|
|
expect(app(BantuanKelompokTaniController::class)->index($group)->getTargetUrl())->toBe($url);
|
|
$response = app(SurveyKelompokTaniController::class)->update(($this->profileRequest)([
|
|
'section' => 'penilaian', 'potensi_kelompok' => 'Tinggi', 'alasan_penilaian_potensi' => 'Diabaikan',
|
|
]), $group);
|
|
expect($response->getTargetUrl())->toBe($url);
|
|
expect($group->refresh()->potensi_kelompok)->toBe('Rendah');
|
|
});
|
|
|
|
test('Pernah requires every first-row aid field before saving', function (string $missing) {
|
|
$group = ($this->kelompok)(['pernah_mendapatkan_bantuan' => false]);
|
|
$row = ['nama_program' => 'Program A', 'penyelenggara' => 'Dinas', 'bentuk_bantuan' => 'Bibit', 'tahun' => 2026];
|
|
$row[$missing] = ' ';
|
|
$request = ($this->profileRequest)(['pernah_mendapatkan_bantuan' => '1', 'bantuan' => [$row]]);
|
|
try {
|
|
app(BantuanKelompokTaniController::class)->update($request, $group);
|
|
$this->fail('An incomplete first row must be rejected.');
|
|
} catch (ValidationException $exception) {
|
|
expect($exception->errors())->toHaveKey('bantuan.0.' . $missing);
|
|
}
|
|
expect($group->refresh()->pernah_mendapatkan_bantuan)->toBeFalse();
|
|
expect($group->bantuan()->count())->toBe(0);
|
|
})->with(['nama_program', 'penyelenggara', 'bentuk_bantuan', 'tahun']);
|
|
|
|
test('Pernah rejects missing first aid rows including attempts to skip the first index', function ($rows) {
|
|
$group = ($this->kelompok)();
|
|
$request = ($this->profileRequest)(['pernah_mendapatkan_bantuan' => '1', 'bantuan' => $rows]);
|
|
expect(fn () => app(BantuanKelompokTaniController::class)->update($request, $group))->toThrow(ValidationException::class);
|
|
expect($group->bantuan()->count())->toBe(0);
|
|
})->with([
|
|
[null], [[]], [[[]]],
|
|
[[1 => ['nama_program' => 'Program A', 'penyelenggara' => 'Dinas', 'bentuk_bantuan' => 'Bibit', 'tahun' => 2026]]],
|
|
]);
|
|
|
|
test('recorded aid meets the minimum without requiring a duplicate entry', function () {
|
|
$group = ($this->kelompok)(['pernah_mendapatkan_bantuan' => true]);
|
|
$group->bantuan()->create(['uid' => 'BKT-EXISTING', 'nama_program' => 'Program Lama', 'penyelenggara' => 'Dinas', 'bentuk_bantuan' => 'Bibit']);
|
|
app(BantuanKelompokTaniController::class)->update(($this->profileRequest)(['pernah_mendapatkan_bantuan' => '1']), $group);
|
|
expect($group->bantuan()->count())->toBe(1);
|
|
|
|
app(BantuanKelompokTaniController::class)->update(($this->profileRequest)([
|
|
'pernah_mendapatkan_bantuan' => '1', 'bantuan' => [['nama_program' => 'Program Tambahan'], []],
|
|
]), $group);
|
|
expect($group->bantuan()->count())->toBe(2);
|
|
});
|
|
|
|
test('an optional aid row with details still needs a program name', function () {
|
|
$group = ($this->kelompok)(['pernah_mendapatkan_bantuan' => true]);
|
|
$group->bantuan()->create(['uid' => 'BKT-EXISTING', 'nama_program' => 'Program Lama', 'penyelenggara' => 'Dinas', 'bentuk_bantuan' => 'Bibit']);
|
|
$request = ($this->profileRequest)(['pernah_mendapatkan_bantuan' => '1', 'bantuan' => [['penyelenggara' => 'Dinas']]]);
|
|
expect(fn () => app(BantuanKelompokTaniController::class)->update($request, $group))->toThrow(ValidationException::class);
|
|
expect($group->bantuan()->count())->toBe(1);
|
|
});
|
|
|
|
test('required aid markers only apply to the first unsaved row for Pernah', function (bool $pernah, bool $saved) {
|
|
$group = ($this->kelompok)(['pernah_mendapatkan_bantuan' => $pernah]);
|
|
if ($saved) {
|
|
$group->bantuan()->create(['uid' => 'BKT-EXISTING', 'nama_program' => 'Program Lama', 'penyelenggara' => 'Dinas', 'bentuk_bantuan' => 'Bibit']);
|
|
}
|
|
$request = ($this->profileRequest)(['section' => 'aktivitas-fungsi'], 'GET');
|
|
session()->flashInput(['bantuan' => [[], []]]);
|
|
$dom = ($this->profileDom)(app(SurveyKelompokTaniController::class)->edit($request, $group));
|
|
$expected = $pernah && !$saved ? 4 : 0;
|
|
expect($dom->query('//fieldset[@id="bantuan-fields"]//input[@required]')->length)->toBe($expected);
|
|
expect($dom->query('//fieldset[@id="bantuan-fields"]//span[contains(@class,"bantuan-required-mark") and not(@hidden)]')->length)->toBe($expected);
|
|
expect($dom->query('//div[@data-repeater-list="bantuan"]/div[2]//input[@required]')->length)->toBe(0);
|
|
expect($dom->query('//div[@id="section-bantuan"]/p[normalize-space()="Minimal satu data bantuan wajib diisi."]')->length)->toBe(1);
|
|
$firstRow = $saved
|
|
? '//div[@id="section-bantuan"]/div[1]'
|
|
: '//div[@data-repeater-list="bantuan"]/div[1]';
|
|
foreach (['Nama Program/Bantuan', 'Penyelenggara', 'Bentuk Bantuan', 'Tahun'] as $label) {
|
|
$marks = $dom->query($firstRow . '//label[starts-with(normalize-space(),"' . $label . '")]/span[contains(@class,"text-danger") and not(@hidden)]');
|
|
expect($marks->length)->toBe($saved || $pernah ? 1 : 0);
|
|
}
|
|
})->with([[true, false], [false, false], [true, true]]);
|
|
|
|
test('conflict topics are required for both Pernah answers without partial saves', function (string $answer, $topic) {
|
|
$group = ($this->kelompok)(['konflik_kelompok' => 'Tidak pernah', 'partisipasi_perempuan' => 'Hadir tapi pasif']);
|
|
$request = ($this->profileRequest)([
|
|
'section' => 'sosial-konflik', 'konflik_kelompok' => $answer,
|
|
'topik_konflik' => $topic, 'partisipasi_perempuan' => 'Tidak terlibat',
|
|
]);
|
|
try {
|
|
app(SurveyKelompokTaniController::class)->update($request, $group);
|
|
$this->fail('A conflict topic is required.');
|
|
} catch (ValidationException $exception) {
|
|
expect($exception->errors())->toHaveKey('topik_konflik');
|
|
}
|
|
expect($group->refresh()->konflik_kelompok)->toBe('Tidak pernah');
|
|
expect($group->partisipasi_perempuan)->toBe('Hadir tapi pasif');
|
|
})->with(['Pernah, sudah selesai', 'Pernah, masih berlanjut/belum selesai'])->with([null, '', ' ']);
|
|
|
|
test('social saves accept conflict topics and require none for other answers', function ($answer, bool $required) {
|
|
$group = ($this->kelompok)();
|
|
$request = ($this->profileRequest)([
|
|
'section' => 'sosial-konflik', 'konflik_kelompok' => $answer,
|
|
...($required ? ['topik_konflik' => 'Pembagian hasil'] : []),
|
|
]);
|
|
app(SurveyKelompokTaniController::class)->update($request, $group);
|
|
expect($group->refresh()->topik_konflik)->toBe($required ? 'Pembagian hasil' : null);
|
|
expect(session('success'))->toBe('Data Sosial berhasil disimpan.');
|
|
|
|
($this->profileRequest)(['section' => 'sosial-konflik'], 'GET');
|
|
$dom = ($this->profileDom)(view('produktivitas.survey-kelompok-tani.sosial-konflik', ['kelompokTani' => $group]));
|
|
$input = $dom->query('//input[@id="sk-topik-konflik"]')->item(0);
|
|
expect($input->hasAttribute('required'))->toBe($required);
|
|
expect($input->hasAttribute('disabled'))->toBe(!$required);
|
|
expect($dom->query('//label[@for="sk-topik-konflik"]/span[not(@hidden)]')->length)->toBe($required ? 1 : 0);
|
|
})->with([
|
|
['Pernah, sudah selesai', true], ['Pernah, masih berlanjut/belum selesai', true],
|
|
['Tidak pernah', false], ['Tidak bersedia menjawab', false], [null, false],
|
|
]);
|
|
|
|
test('conflict topic requirements and input follow the previous submission after validation', function () {
|
|
$group = ($this->kelompok)(['konflik_kelompok' => 'Tidak pernah']);
|
|
($this->profileRequest)([], 'GET');
|
|
session()->flashInput(['konflik_kelompok' => 'Pernah, sudah selesai', 'topik_konflik' => 'Pembagian hasil']);
|
|
$dom = ($this->profileDom)(view('produktivitas.survey-kelompok-tani.sosial-konflik', ['kelompokTani' => $group]));
|
|
$input = $dom->query('//input[@id="sk-topik-konflik"]')->item(0);
|
|
expect($input->hasAttribute('required'))->toBeTrue();
|
|
expect($input->getAttribute('value'))->toBe('Pembagian hasil');
|
|
expect($dom->query('//div[@id="sk-detail-konflik-wrapper"]')->item(0)->hasAttribute('style'))->toBeFalse();
|
|
});
|
|
|
|
test('the last recorded aid cannot be deleted while the answer remains Pernah', function () {
|
|
$group = ($this->kelompok)(['pernah_mendapatkan_bantuan' => true]);
|
|
$aid = $group->bantuan()->create(['uid' => 'BKT-EXISTING', 'nama_program' => 'Program Lama', 'penyelenggara' => 'Dinas', 'bentuk_bantuan' => 'Bibit']);
|
|
$controller = app(BantuanKelompokTaniController::class);
|
|
$response = $controller->destroy($group, $aid);
|
|
expect($response->getTargetUrl())->toEndWith('#kelompok-tani-notifications');
|
|
expect($group->bantuan()->count())->toBe(1);
|
|
$other = $group->bantuan()->create(['uid' => 'BKT-OTHER', 'nama_program' => 'Program Lain', 'penyelenggara' => 'Dinas', 'bentuk_bantuan' => 'Bibit']);
|
|
$controller->destroy($group, $aid);
|
|
expect($group->bantuan()->count())->toBe(1);
|
|
$group->update(['pernah_mendapatkan_bantuan' => false]);
|
|
$controller->destroy($group, $other);
|
|
expect($group->bantuan()->count())->toBe(0);
|
|
});
|