1513 lines
97 KiB
PHP
1513 lines
97 KiB
PHP
<?php
|
|
|
|
use App\Enums\Bumdes\DukunganDesaBumdes;
|
|
use App\Enums\Bumdes\KeterlibatanKoordinasiBumdes;
|
|
use App\Enums\Bumdes\StatusPembukuanBumdes;
|
|
use App\Enums\Bumdes\StatusSortasiBumdes;
|
|
use App\Http\Controllers\Web\Bumdes\KelembagaanAktorBumdesController;
|
|
use App\Http\Controllers\Web\Bumdes\OperasionalBumdesController;
|
|
use App\Http\Controllers\Web\Bumdes\PengurusBumdesController;
|
|
use App\Http\Controllers\Web\Bumdes\RantaiNilaiBumdesController;
|
|
use App\Models\Bumdes\KelembagaanAktorBumdes;
|
|
use App\Models\Bumdes\PengurusBumdes;
|
|
use App\Models\User;
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Routing\Route;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Event;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Illuminate\Support\MessageBag;
|
|
use Illuminate\Support\ViewErrorBag;
|
|
use Illuminate\Validation\ValidationException;
|
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
|
|
|
beforeEach(function () {
|
|
foreach ([
|
|
'master_provinsi' => null,
|
|
'master_kabupaten_kota' => 'provinsi_id',
|
|
'master_kecamatan' => 'kabupaten_kota_id',
|
|
'master_desa_kelurahan' => 'kecamatan_id',
|
|
] as $name => $parent) {
|
|
Schema::create($name, function (Blueprint $table) use ($parent) {
|
|
$table->id();
|
|
$table->string('nama');
|
|
$table->string('kode')->nullable();
|
|
if ($parent) {
|
|
$table->integer($parent);
|
|
}
|
|
$table->softDeletes();
|
|
});
|
|
}
|
|
Schema::create('users', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->string('name');
|
|
$table->softDeletes();
|
|
});
|
|
Schema::create('activity_log', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->string('log_name')->nullable();
|
|
$table->text('description');
|
|
$table->nullableMorphs('subject');
|
|
$table->string('event')->nullable();
|
|
$table->nullableMorphs('causer');
|
|
$table->json('properties')->nullable();
|
|
$table->uuid('batch_uuid')->nullable();
|
|
$table->timestamps();
|
|
});
|
|
Schema::create('bumdes_pengurus', function (Blueprint $table) {
|
|
$table->id();
|
|
foreach (['uid', 'tempat_lahir', 'pendidikan_terakhir', 'alamat', 'nomor_telepon'] as $field) {
|
|
$table->string($field)->nullable();
|
|
}
|
|
$table->date('tanggal_lahir')->nullable();
|
|
$table->date('tanggal_mulai_menjabat')->nullable();
|
|
$table->integer('lama_menjabat_tahun')->nullable();
|
|
$table->integer('updated_by')->nullable();
|
|
$table->integer('deleted_by')->nullable();
|
|
$table->string('nama');
|
|
$table->boolean('jenis_kelamin');
|
|
$table->string('jabatan');
|
|
$table->string('jabatan_lainnya')->nullable();
|
|
$table->integer('desa_kelurahan_id');
|
|
$table->integer('created_by');
|
|
$table->timestamps();
|
|
$table->softDeletes();
|
|
});
|
|
(require database_path('migrations/2026_08_20_000001_create_bumdes_kelembagaan_aktor_table.php'))->up();
|
|
(require database_path('migrations/2026_11_04_000001_add_dukungan_ekosistem_to_bumdes_kelembagaan_aktor_table.php'))->up();
|
|
(require database_path('migrations/2026_11_04_000002_add_pengelolaan_layanan_to_bumdes_kelembagaan_aktor_table.php'))->up();
|
|
(require database_path('migrations/2026_11_04_000003_add_rantai_nilai_to_bumdes_kelembagaan_aktor_table.php'))->up();
|
|
(require database_path('migrations/2026_11_05_000001_add_peluang_nilai_tambah_to_bumdes_kelembagaan_aktor_table.php'))->up();
|
|
DB::table('users')->insert([
|
|
['id' => 7, 'name' => 'Admin Test'], ['id' => 8, 'name' => 'Enumerator'],
|
|
]);
|
|
DB::table('master_provinsi')->insert(['id' => 1, 'nama' => 'NTT']);
|
|
DB::table('master_kabupaten_kota')->insert(['id' => 1, 'nama' => 'Kabupaten Ngada', 'provinsi_id' => 1]);
|
|
DB::table('master_kecamatan')->insert([
|
|
['id' => 1, 'nama' => 'Kecamatan A', 'kabupaten_kota_id' => 1],
|
|
['id' => 2, 'nama' => 'Kecamatan B', 'kabupaten_kota_id' => 1],
|
|
]);
|
|
DB::table('master_desa_kelurahan')->insert([
|
|
['id' => 1, 'nama' => 'Desa A', 'kecamatan_id' => 1],
|
|
['id' => 2, 'nama' => 'Desa B', 'kecamatan_id' => 2],
|
|
]);
|
|
foreach ([
|
|
[1, 'Alpha', 1, 'direktur', 1, 7, null],
|
|
[2, 'Beta', 0, 'direktur_ketua', 1, 8, null],
|
|
[3, 'Citra', 0, 'sekretaris', 2, 7, null],
|
|
[4, 'Dihapus', 0, 'bendahara', 1, 7, '2026-01-01'],
|
|
] as [$id, $nama, $gender, $jabatan, $desa, $creator, $deleted]) {
|
|
DB::table('bumdes_pengurus')->insert([
|
|
'tempat_lahir' => 'Ngada', 'tanggal_lahir' => '1990-01-01', 'pendidikan_terakhir' => '04_sma',
|
|
'alamat' => 'Desa A', 'lama_menjabat_tahun' => 2,
|
|
'id' => $id, 'nama' => $nama, 'jenis_kelamin' => $gender, 'jabatan' => $jabatan,
|
|
'desa_kelurahan_id' => $desa, 'created_by' => $creator,
|
|
'created_at' => "2026-01-0{$id}", 'deleted_at' => $deleted,
|
|
]);
|
|
}
|
|
$user = new User(['name' => 'Admin Test', 'role' => 'admin']);
|
|
$user->id = 7;
|
|
$this->be($user);
|
|
Event::forget('composing: *');
|
|
view()->share('errors', new ViewErrorBag);
|
|
$this->bumdesAction = function (string $action, ?Request $base = null, ?string $id = null) {
|
|
[$controllerClass, $method, $routeName] = match ($action) {
|
|
'kelembagaan.index' => [KelembagaanAktorBumdesController::class, 'index', 'bumdes.kelembagaan-aktor.index'],
|
|
'kelembagaan.store' => [KelembagaanAktorBumdesController::class, 'store', 'bumdes.kelembagaan-aktor.store'],
|
|
'rantai-nilai.index' => [RantaiNilaiBumdesController::class, 'index', 'bumdes.rantai-nilai.index'],
|
|
'rantai-nilai.store' => [RantaiNilaiBumdesController::class, 'store', 'bumdes.rantai-nilai.store'],
|
|
'operasional.index' => [OperasionalBumdesController::class, 'index', 'bumdes.operasional.index'],
|
|
'operasional.store' => [OperasionalBumdesController::class, 'store', 'bumdes.operasional.store'],
|
|
default => [PengurusBumdesController::class, $action, 'bumdes.'.$action],
|
|
};
|
|
$base ??= Request::create('/bumdes'.($id ? '/'.$id : ''));
|
|
$base->setLaravelSession(app('session')->driver());
|
|
$model = $id ? PengurusBumdes::findOrFail($id) : null;
|
|
$route = new Route(['GET', 'POST', 'PUT', 'DELETE'], 'bumdes/{pengurusBumdes?}', fn () => null);
|
|
$route->name($routeName)->bind(Request::create('/bumdes'.($id ? '/'.$id : '')));
|
|
if ($model) {
|
|
$route->setParameter('pengurusBumdes', $model);
|
|
}
|
|
$requestClass = (new ReflectionMethod($controllerClass, $method))->getParameters()[0]->getType()->getName();
|
|
$request = $requestClass::createFrom($base);
|
|
$request->setContainer($this->app)->setRedirector(app('redirect'));
|
|
$request->setUserResolver(fn () => auth()->user())->setRouteResolver(fn () => $route);
|
|
$this->app->instance('request', $request);
|
|
$request->validateResolved();
|
|
$arguments = $model ? [$request, $model] : [$request];
|
|
|
|
return app($controllerClass)->$method(...$arguments);
|
|
};
|
|
$this->bumdesList = function (array $filters = []) {
|
|
$request = Request::create('/bumdes', 'GET', $filters);
|
|
$request->setLaravelSession(app('session')->driver());
|
|
$this->app->instance('request', $request);
|
|
|
|
return ($this->bumdesAction)('index', $request);
|
|
};
|
|
$this->profileInput = [
|
|
'nama' => 'Responden', 'jenis_kelamin' => '1', 'tempat_lahir' => 'Ngada',
|
|
'tanggal_lahir' => '01-01-1990', 'pendidikan_terakhir' => '04_sma',
|
|
'alamat' => 'Desa A', 'provinsi_id' => 1, 'kabupaten_id' => 1, 'kecamatan_id' => 1,
|
|
'desa_kelurahan_id' => 1, 'jabatan' => 'sekretaris', 'lama_menjabat_tahun' => 2,
|
|
];
|
|
});
|
|
|
|
test('bumdes activity and location filters update rows and summaries', function () {
|
|
$data = ($this->bumdesList)()->getData();
|
|
expect($data['totalBumdes'])->toBe(3)->and($data['totalBumdesAktif'])->toBe(0)->and($data['totalBerbadanHukum'])->toBe(0);
|
|
foreach ([
|
|
[1, 'aktif_penuh', 'sudah_berbadan_hukum'],
|
|
[2, 'tidak_aktif_vakum', 'belum_berbadan_hukum'],
|
|
[3, 'aktif_terbatas', 'proses_pendaftaran'],
|
|
[4, 'aktif_penuh', 'sudah_berbadan_hukum'],
|
|
] as [$id, $status, $hukum]) {
|
|
KelembagaanAktorBumdes::create([
|
|
'pengurus_bumdes_id' => $id, 'status_keaktifan' => $status, 'status_badan_hukum' => $hukum,
|
|
]);
|
|
}
|
|
$data = ($this->bumdesList)()->getData();
|
|
expect($data['totalBumdes'])->toBe(3)->and($data['totalBumdesAktif'])->toBe(2)
|
|
->and($data['totalBerbadanHukum'])->toBe(1)->and($data['totalDesaKelurahan'])->toBe(2);
|
|
$data = ($this->bumdesList)(['status_keaktifan' => 'aktif_penuh'])->getData();
|
|
expect($data['pengurus']->pluck('id')->all())->toBe([1]);
|
|
expect($data['totalBumdes'])->toBe(1)->and($data['totalDesaKelurahan'])->toBe(1)
|
|
->and($data['totalBumdesAktif'])->toBe(1)->and($data['totalBerbadanHukum'])->toBe(1);
|
|
|
|
$data = ($this->bumdesList)(['kecamatan_id' => 1, 'desa_kelurahan_id' => 1])->getData();
|
|
expect($data['pengurus']->pluck('id')->all())->toBe([2, 1]);
|
|
expect($data['totalBumdes'])->toBe(2);
|
|
expect($data['desaOptions']->pluck('id')->all())->toBe([1]);
|
|
expect(($this->bumdesList)(['search' => 'aLpHa'])->getData()['pengurus']->pluck('id')->all())->toBe([1]);
|
|
expect(($this->bumdesList)(['search' => 'dEsA a'])->getData()['pengurus']->pluck('id')->all())->toBe([2, 1]);
|
|
KelembagaanAktorBumdes::where('pengurus_bumdes_id', 1)->firstOrFail()->delete();
|
|
expect(($this->bumdesList)(['status_keaktifan' => 'aktif_penuh'])->getData()['pengurus']->count())->toBe(0);
|
|
$data = ($this->bumdesList)()->getData();
|
|
expect($data['totalBumdesAktif'])->toBe(1)->and($data['totalBerbadanHukum'])->toBe(0);
|
|
$data = ($this->bumdesList)([
|
|
'jenis_kelamin' => '1', 'jabatan' => 'sekretaris', 'status_badan_hukum' => 'invalid', 'unit_usaha' => 'invalid',
|
|
])->getData();
|
|
expect($data['pengurus']->pluck('id')->all())->toBe([3, 2, 1]);
|
|
expect($data['filters'])->not->toHaveKeys(['jenis_kelamin', 'jabatan', 'status_badan_hukum', 'unit_usaha']);
|
|
});
|
|
|
|
test('bumdes search and summaries respect enumerator ownership', function () {
|
|
auth()->user()->role = 'enumerator';
|
|
foreach ([1, 2] as $id) {
|
|
KelembagaanAktorBumdes::create([
|
|
'pengurus_bumdes_id' => $id, 'status_keaktifan' => 'aktif_penuh', 'status_badan_hukum' => 'sudah_berbadan_hukum',
|
|
]);
|
|
}
|
|
$data = ($this->bumdesList)(['search' => 'Desa A', 'status_keaktifan' => 'aktif_penuh'])->getData();
|
|
expect($data['pengurus']->pluck('id')->all())->toBe([1]);
|
|
expect($data['totalBumdes'])->toBe(1)->and($data['showEnumerator'])->toBeFalse()
|
|
->and($data['totalBumdesAktif'])->toBe(1)->and($data['totalBerbadanHukum'])->toBe(1);
|
|
});
|
|
|
|
test('bumdes sorting and pagination preserve filters across all results', function () {
|
|
foreach (['nama_asc' => [2, 1, 3], 'nama_desc' => [3, 2, 1], 'unknown' => [3, 2, 1]] as $sort => $ids) {
|
|
expect(($this->bumdesList)(['sort_by' => $sort])->getData()['pengurus']->pluck('id')->all())->toBe($ids);
|
|
}
|
|
KelembagaanAktorBumdes::create(['pengurus_bumdes_id' => 2, 'status_keaktifan' => 'aktif_penuh']);
|
|
for ($i = 0; $i < 12; $i++) {
|
|
$id = DB::table('bumdes_pengurus')->insertGetId([
|
|
'nama' => "Tambahan {$i}", 'jenis_kelamin' => 0, 'jabatan' => 'sekretaris',
|
|
'desa_kelurahan_id' => 1, 'created_by' => 7,
|
|
]);
|
|
KelembagaanAktorBumdes::create(['pengurus_bumdes_id' => $id, 'status_keaktifan' => 'aktif_penuh']);
|
|
}
|
|
$data = ($this->bumdesList)(['status_keaktifan' => 'aktif_penuh', 'sort_by' => 'nama_asc', 'per_page' => 10, 'page' => 2])->getData();
|
|
expect($data['totalBumdes'])->toBe(13)->and($data['pengurus']->count())->toBe(3);
|
|
parse_str(parse_url($data['pengurus']->url(1), PHP_URL_QUERY), $params);
|
|
expect($params)->toMatchArray(['status_keaktifan' => 'aktif_penuh', 'sort_by' => 'nama_asc', 'per_page' => '10']);
|
|
});
|
|
|
|
test('bumdes filters reject malformed input', function (array $filters) {
|
|
expect(fn () => ($this->bumdesList)($filters))->toThrow(ValidationException::class);
|
|
})->with([
|
|
[['status_keaktifan' => 'invalid']], [['kecamatan_id' => 999]], [['per_page' => 0]], [['search' => ['invalid']]],
|
|
]);
|
|
|
|
test('bumdes list and respondent forms render successfully', function () {
|
|
KelembagaanAktorBumdes::create([
|
|
'pengurus_bumdes_id' => 1, 'status_keaktifan' => 'aktif_penuh',
|
|
'status_badan_hukum' => 'proses_pendaftaran',
|
|
'unit_usaha' => ['simpan_pinjam', 'jasa_lainnya'], 'unit_usaha_jasa_lainnya' => 'Pengiriman hasil panen',
|
|
]);
|
|
$view = ($this->bumdesList)();
|
|
expect($view->getData()['pengurus']->every(fn ($item) => $item->relationLoaded('kelembagaanAktor')))->toBeTrue();
|
|
$html = $view->render();
|
|
expect($html)->toContain('bumdes-row', 'bumdes-filter-form', 'Semua Keaktifan', 'Data BUMDes',
|
|
'BUMDes · Desa A', 'Aktif beroperasi penuh', 'Proses pendaftaran', 'Simpan pinjam', 'Pengiriman hasil panen', 'Belum terisi');
|
|
$dom = new DOMDocument;
|
|
$previous = libxml_use_internal_errors(true);
|
|
$dom->loadHTML($html);
|
|
libxml_clear_errors();
|
|
libxml_use_internal_errors($previous);
|
|
$xpath = new DOMXPath($dom);
|
|
$overview = $xpath->query('//*[@id="bumdes-overview"]')->item(0);
|
|
expect($overview->textContent)->toContain('Total BUMDes', 'BUMDes Aktif', 'Berbadan Hukum', 'desa/kelurahan terdata')
|
|
->not->toContain('Pengurus BUMDes', 'Jabatan/Peran', 'Laki-laki', 'Perempuan');
|
|
expect($xpath->query('//form[@id="bumdes-filter-form"]//select[@name="status_badan_hukum" or @name="unit_usaha" or @name="jenis_kelamin" or @name="jabatan"]')->length)->toBe(0);
|
|
$header = $xpath->query('//table[contains(@class, "bumdes-list-table")]/thead')->item(0);
|
|
expect($header->textContent)->toContain('BUMDes', 'Keaktifan', 'Badan Hukum', 'Unit Usaha', 'Nama Responden')
|
|
->not->toContain('Jenis Kelamin', 'Usia', 'Jabatan/Peran', 'Lama Menjabat');
|
|
expect($xpath->query('//form[contains(@class, "bumdes-delete-form") and contains(@data-nama, "BUMDes")]')->length)->toBe(3);
|
|
|
|
$controller = $this->bumdesAction;
|
|
foreach ([$controller('create'), $controller('edit', null, '1')] as $view) {
|
|
$html = $view->render();
|
|
expect($html)->toContain('Data Responden', 'tabler-check me-2', '> Simpan</button>')
|
|
->not->toContain('Data Pribadi', 'Peran Kepengurusan BUMDes', 'Domisili dan Kontak', 'Edit Profil Pengurus BUMDes', 'Tambah Profil Pengurus BUMDes');
|
|
}
|
|
});
|
|
|
|
test('bumdes institutional and operational forms display the correct fields and saved dropdown selections', function () {
|
|
KelembagaanAktorBumdes::create([
|
|
'pengurus_bumdes_id' => 1, 'status_badan_hukum' => 'proses_pendaftaran',
|
|
'unit_usaha' => ['simpan_pinjam', 'jasa_lainnya'], 'unit_usaha_jasa_lainnya' => 'Pengiriman',
|
|
'status_ad_art' => 'ada_lengkap', 'tahun_ad_art' => 2025,
|
|
'status_keaktifan' => 'tidak_aktif_vakum', 'alasan_tidak_aktif' => 'Kekurangan modal',
|
|
'pengurus_inti' => [['jabatan' => 'sekretaris', 'nama' => 'Sekretaris Lama', 'aktif' => true]],
|
|
]);
|
|
($this->bumdesList)();
|
|
$controller = $this->bumdesAction;
|
|
$html = $controller('kelembagaan.index', null, '1')->render();
|
|
$dom = new DOMDocument;
|
|
$previous = libxml_use_internal_errors(true);
|
|
$dom->loadHTML($html);
|
|
libxml_clear_errors();
|
|
libxml_use_internal_errors($previous);
|
|
$xpath = new DOMXPath($dom);
|
|
expect($xpath->query('//select[@name="status_badan_hukum"]/option[@selected]')->item(0)->getAttribute('value'))->toBe('proses_pendaftaran');
|
|
expect($xpath->query('//input[@name="unit_usaha[]" and @type="checkbox" and @checked]')->length)->toBe(2);
|
|
expect($xpath->query('//select[@name="unit_usaha[]"]')->length)->toBe(0);
|
|
expect($xpath->query('//select[@name="status_unit_komoditas" or @name="status_ad_art"]')->length)->toBe(2);
|
|
expect($html)->toContain('Status Legalitas BUMDes', route('bumdes.operasional.index', 1))
|
|
->not->toContain('name="status_keaktifan"', 'Kepengurusan dan Staf', 'Modal dan Aset');
|
|
|
|
$html = $controller('operasional.index', null, '1')->render();
|
|
expect($html)->toContain('Modal dan Aset', route('bumdes.operasional.store', 1))
|
|
->not->toContain('name="status_badan_hukum"', 'name="unit_usaha[]"', 'name="status_keaktifan"', 'Kepengurusan dan Staf');
|
|
foreach ([$controller('edit', null, '1'), $controller('create')] as $view) {
|
|
$html = $view->render();
|
|
expect($html)->toContain('Status Keaktifan BUMDes', 'Kepengurusan dan Staf', 'Daftar Pengurus', 'switch switch-lg', 'role="switch"')
|
|
->not->toContain('Catat pengurus inti yang aktif menjabat saat ini', 'Modal dan Aset');
|
|
}
|
|
expect($controller('edit', null, '1')->render())->toContain('Sekretaris Lama', 'Kekurangan modal');
|
|
});
|
|
|
|
test('institutional conditional fields are required on the server', function (array $input, array $expectedErrors) {
|
|
$request = Request::create('/bumdes/1/kelembagaan-aktor', 'POST', $input);
|
|
$this->app->instance('request', $request);
|
|
try {
|
|
($this->bumdesAction)('kelembagaan.store', $request, '1');
|
|
$this->fail('Expected conditional validation errors.');
|
|
} catch (ValidationException $exception) {
|
|
expect(array_keys($exception->errors()))->toBe($expectedErrors);
|
|
foreach ($exception->errors() as $messages) {
|
|
expect($messages[0])->toContain('wajib diisi.');
|
|
}
|
|
}
|
|
expect(KelembagaanAktorBumdes::count())->toBe(0);
|
|
})->with([
|
|
[['status_badan_hukum' => 'sudah_berbadan_hukum'], ['nomor_dasar_hukum', 'tahun_dasar_hukum']],
|
|
[['status_unit_komoditas' => 'ya'], ['nama_unit_komoditas']],
|
|
[['status_ad_art' => 'ada_lengkap'], ['tahun_ad_art']],
|
|
[[
|
|
'status_badan_hukum' => 'sudah_berbadan_hukum', 'status_unit_komoditas' => 'ya', 'status_ad_art' => 'ada_lengkap',
|
|
], ['nomor_dasar_hukum', 'tahun_dasar_hukum', 'nama_unit_komoditas', 'tahun_ad_art']],
|
|
]);
|
|
|
|
test('disabled institutional fields ignore submitted values and clear stale data', function () {
|
|
KelembagaanAktorBumdes::create([
|
|
'pengurus_bumdes_id' => 1, 'status_badan_hukum' => 'sudah_berbadan_hukum',
|
|
'nomor_dasar_hukum' => 'SK-01', 'tahun_dasar_hukum' => 2025,
|
|
'status_unit_komoditas' => 'ya', 'nama_unit_komoditas' => 'Kopi',
|
|
'status_ad_art' => 'ada_lengkap', 'tahun_ad_art' => 2025,
|
|
]);
|
|
$request = Request::create('/bumdes/1/kelembagaan-aktor', 'POST', [
|
|
'status_badan_hukum' => 'proses_pendaftaran', 'nomor_dasar_hukum' => ['invalid'], 'tahun_dasar_hukum' => 'invalid',
|
|
'status_unit_komoditas' => 'direncanakan', 'nama_unit_komoditas' => ['invalid'],
|
|
'status_ad_art' => 'tidak_ada', 'tahun_ad_art' => 'invalid',
|
|
]);
|
|
$this->app->instance('request', $request);
|
|
($this->bumdesAction)('kelembagaan.store', $request, '1');
|
|
$data = KelembagaanAktorBumdes::firstOrFail();
|
|
expect($data->nomor_dasar_hukum)->toBeNull()->and($data->tahun_dasar_hukum)->toBeNull()
|
|
->and($data->nama_unit_komoditas)->toBeNull()->and($data->tahun_ad_art)->toBeNull();
|
|
});
|
|
|
|
test('institutional dependent inputs stay visible and have matching disabled required and asterisk states', function (bool $active) {
|
|
KelembagaanAktorBumdes::create([
|
|
'pengurus_bumdes_id' => 1,
|
|
'status_badan_hukum' => $active ? 'sudah_berbadan_hukum' : 'proses_pendaftaran',
|
|
'status_unit_komoditas' => $active ? 'ya' : 'direncanakan',
|
|
'status_ad_art' => $active ? 'ada_lengkap' : 'tidak_ada',
|
|
]);
|
|
($this->bumdesList)();
|
|
$html = ($this->bumdesAction)('kelembagaan.index', null, '1')->render();
|
|
$dom = new DOMDocument;
|
|
$previous = libxml_use_internal_errors(true);
|
|
$dom->loadHTML($html);
|
|
libxml_clear_errors();
|
|
libxml_use_internal_errors($previous);
|
|
$xpath = new DOMXPath($dom);
|
|
expect($xpath->query('//form[@id="kelembagaanAktorForm" and @novalidate]')->length)->toBe(1);
|
|
foreach (['nomor-dasar-hukum', 'tahun-dasar-hukum', 'nama-unit-komoditas', 'tahun-ad-art'] as $id) {
|
|
$input = $xpath->query('//input[@id="'.$id.'"]')->item(0);
|
|
expect($input->hasAttribute('disabled'))->toBe(! $active)
|
|
->and($input->hasAttribute('required'))->toBe($active);
|
|
$label = $xpath->query('//span[@data-required-for="'.$id.'"]')->item(0);
|
|
expect(str_contains($label->getAttribute('class'), 'd-none'))->toBe(! $active);
|
|
for ($node = $input; $node instanceof DOMElement; $node = $node->parentNode) {
|
|
expect(str_contains($node->getAttribute('class'), 'd-none'))->toBeFalse();
|
|
}
|
|
}
|
|
})->with([true, false]);
|
|
|
|
test('bumdes notifications collect required errors above the cards and preserve old profile locations', function () {
|
|
$request = Request::create('/bumdes', 'POST');
|
|
$this->app->instance('request', $request);
|
|
try {
|
|
($this->bumdesAction)('store', $request);
|
|
$this->fail('Expected profile validation errors.');
|
|
} catch (ValidationException $exception) {
|
|
expect($exception->errors())->toHaveKeys(['nama', 'provinsi_id', 'kabupaten_id', 'kecamatan_id', 'desa_kelurahan_id']);
|
|
$errors = (new ViewErrorBag)->put('default', new MessageBag($exception->errors()));
|
|
view()->share('errors', $errors);
|
|
}
|
|
session()->flashInput(['provinsi_id' => 1, 'kabupaten_id' => 1, 'kecamatan_id' => 1, 'desa_kelurahan_id' => 1]);
|
|
($this->bumdesList)();
|
|
$controller = $this->bumdesAction;
|
|
foreach ([$controller('create'), $controller('kelembagaan.index', null, '1'), $controller('operasional.index', null, '1')] as $view) {
|
|
$html = $view->render();
|
|
expect(substr_count($html, 'id="bumdes-notifications"'))->toBe(1)
|
|
->and(strpos($html, 'id="bumdes-notifications"'))->toBeLessThan(strpos($html, 'id="bumdes-profile-summary"'));
|
|
expect($html)->toContain('Nama harus diisi.', 'Provinsi wajib dipilih.', 'alert-danger alert-dismissible');
|
|
}
|
|
$data = $controller('create')->getData();
|
|
expect($data['kabupatenOptions']->pluck('id')->all())->toBe([1])
|
|
->and($data['kecamatanOptions']->pluck('id')->all())->toBe([1, 2])
|
|
->and($data['desaOptions']->pluck('id')->all())->toBe([1]);
|
|
});
|
|
|
|
test('bumdes creation saves profile status staff and active or inactive board members', function () {
|
|
$request = Request::create('/bumdes', 'POST', [...$this->profileInput,
|
|
'redirect_fragment' => 'section-kepengurusan',
|
|
'status_keaktifan' => 'tidak_aktif_vakum', 'alasan_tidak_aktif' => 'Perbaikan kantor',
|
|
'pengurus_inti' => [
|
|
['jabatan' => 'sekretaris', 'nama' => 'Budi', 'aktif' => '1'],
|
|
['jabatan' => 'bendahara', 'nama' => 'Siti', 'aktif' => '0'],
|
|
['jabatan' => 'penasihat_kepala_desa', 'nama' => '', 'aktif' => '0'],
|
|
],
|
|
'memiliki_staf' => '1', 'jumlah_staf' => 2, 'unit_usaha_staf' => 'Toko',
|
|
]);
|
|
$this->app->instance('request', $request);
|
|
$response = ($this->bumdesAction)('store', $request);
|
|
$data = KelembagaanAktorBumdes::firstOrFail();
|
|
expect($response->getTargetUrl())->toBe(route('bumdes.edit', $data->pengurus_bumdes_id).'#section-kepengurusan')
|
|
->and($data->status_keaktifan?->value)->toBe('tidak_aktif_vakum')
|
|
->and($data->alasan_tidak_aktif)->toBe('Perbaikan kantor')
|
|
->and($data->jumlah_staf)->toBe(2)
|
|
->and(count($data->pengurus_inti))->toBe(2)
|
|
->and($data->pengurus_inti[0]['aktif'])->toBe('1')
|
|
->and($data->pengurus_inti[1]['aktif'])->toBe('0');
|
|
});
|
|
|
|
test('profile update redirects the upper save to the next card and ignores unknown targets', function (?string $fragment, string $expectedFragment) {
|
|
$request = Request::create('/bumdes/1', 'PUT', [...$this->profileInput, 'redirect_fragment' => $fragment]);
|
|
$this->app->instance('request', $request);
|
|
$response = ($this->bumdesAction)('update', $request, '1');
|
|
|
|
expect($response->getTargetUrl())->toBe(route('bumdes.edit', 1).$expectedFragment)
|
|
->and(session('success'))->toBe('Data Profil berhasil disimpan.');
|
|
})->with([
|
|
['section-kepengurusan', '#section-kepengurusan'],
|
|
[null, ''],
|
|
['https://example.test', ''],
|
|
]);
|
|
|
|
test('invalid moved profile fields prevent saving respondent changes', function () {
|
|
$request = Request::create('/bumdes/1', 'PUT', [...$this->profileInput,
|
|
'status_keaktifan' => 'invalid', 'jumlah_staf' => -1,
|
|
'pengurus_inti' => [['jabatan' => 'sekretaris', 'nama' => 'Budi', 'aktif' => 'invalid']],
|
|
]);
|
|
$this->app->instance('request', $request);
|
|
try {
|
|
($this->bumdesAction)('update', $request, '1');
|
|
$this->fail('Expected profile validation errors.');
|
|
} catch (ValidationException $exception) {
|
|
expect($exception->errors())->toHaveKeys(['status_keaktifan', 'jumlah_staf', 'pengurus_inti.0.aktif']);
|
|
}
|
|
expect(DB::table('bumdes_pengurus')->where('id', 1)->value('nama'))->toBe('Alpha')
|
|
->and(KelembagaanAktorBumdes::count())->toBe(0);
|
|
});
|
|
|
|
test('capital addition details are optional when the answer is yes', function (?array $rows, int $expectedRows) {
|
|
$request = Request::create('/bumdes/1/operasional', 'POST', [
|
|
'pernah_penambahan_modal' => '1', 'penambahan_modal' => $rows,
|
|
]);
|
|
$response = ($this->bumdesAction)('operasional.store', $request, '1');
|
|
expect($response->getTargetUrl())->toBe(route('bumdes.operasional.index', 1));
|
|
expect(KelembagaanAktorBumdes::firstOrFail()->penambahan_modal)->toHaveCount($expectedRows);
|
|
})->with([
|
|
[null, 0], [[], 0], [[['tahun' => '', 'jumlah' => '']], 0],
|
|
[[['tahun' => 2025, 'jumlah' => '']], 1],
|
|
[[['tahun' => '', 'jumlah' => '1.000.000']], 1],
|
|
]);
|
|
|
|
test('capital addition fields follow the selected answer without becoming required', function (bool $active) {
|
|
KelembagaanAktorBumdes::create(['pengurus_bumdes_id' => 1, 'pernah_penambahan_modal' => $active]);
|
|
($this->bumdesList)();
|
|
$html = ($this->bumdesAction)('operasional.index', null, '1')->render();
|
|
$dom = new DOMDocument;
|
|
$previous = libxml_use_internal_errors(true);
|
|
$dom->loadHTML($html);
|
|
libxml_clear_errors();
|
|
libxml_use_internal_errors($previous);
|
|
$xpath = new DOMXPath($dom);
|
|
$inputs = $xpath->query('//div[@id="penambahan-modal-wrapper"]//input');
|
|
expect($inputs->length)->toBe(6);
|
|
foreach ($inputs as $input) {
|
|
expect($input->hasAttribute('disabled'))->toBe(! $active)
|
|
->and($input->hasAttribute('required'))->toBeFalse();
|
|
}
|
|
expect($xpath->query('//span[contains(@class, "penambahan-modal-required")]')->length)->toBe(0);
|
|
})->with([true, false]);
|
|
|
|
test('capital additions are ignored when the answer is no', function () {
|
|
$request = Request::create('/bumdes/1/operasional', 'POST', [
|
|
'pernah_penambahan_modal' => '0', 'penambahan_modal' => [['tahun' => '', 'jumlah' => '']],
|
|
]);
|
|
$this->app->instance('request', $request);
|
|
($this->bumdesAction)('operasional.store', $request, '1');
|
|
expect(KelembagaanAktorBumdes::firstOrFail()->penambahan_modal)->toBeNull();
|
|
});
|
|
|
|
test('bumdes deletion records the actor and soft deletes the dependent section data', function () {
|
|
$child = KelembagaanAktorBumdes::create(['pengurus_bumdes_id' => 1]);
|
|
$response = ($this->bumdesAction)('destroy', null, '1');
|
|
|
|
expect($response->getTargetUrl())->toBe(route('bumdes.index'));
|
|
$parent = PengurusBumdes::withTrashed()->findOrFail(1);
|
|
$child = KelembagaanAktorBumdes::withTrashed()->findOrFail($child->id);
|
|
expect($parent->trashed())->toBeTrue()->and((int) $parent->deleted_by)->toBe(7)
|
|
->and($child->trashed())->toBeTrue()->and((int) $child->deleted_by)->toBe(7)
|
|
->and(DB::table('master_desa_kelurahan')->where('id', 1)->value('deleted_at'))->toBeNull();
|
|
foreach ([$parent, $child] as $model) {
|
|
$activity = DB::table('activity_log')->where('subject_type', $model::class)
|
|
->where('subject_id', $model->id)->where('event', 'deleted')->first();
|
|
expect($activity)->not->toBeNull();
|
|
expect((int) $activity->causer_id)->toBe(7);
|
|
}
|
|
});
|
|
|
|
test('enumerators cannot read change or delete another enumerators bumdes', function (string $action) {
|
|
auth()->user()->role = 'enumerator';
|
|
try {
|
|
($this->bumdesAction)($action, Request::create('/bumdes/2', 'POST', $this->profileInput), '2');
|
|
$this->fail('Expected an ownership restriction.');
|
|
} catch (HttpException $exception) {
|
|
expect($exception->getStatusCode())->toBe(404);
|
|
}
|
|
expect(DB::table('bumdes_pengurus')->where('id', 2)->value('nama'))->toBe('Beta')
|
|
->and(KelembagaanAktorBumdes::count())->toBe(0);
|
|
})->with(['edit', 'update', 'destroy', 'kelembagaan.index', 'kelembagaan.store', 'operasional.index', 'operasional.store', 'rantai-nilai.index', 'rantai-nilai.store']);
|
|
|
|
test('enumerators retain access to their own bumdes through the web routes', function () {
|
|
auth()->user()->role = 'enumerator';
|
|
$this->get(route('bumdes.index'))->assertOk();
|
|
$this->get(route('bumdes.edit', 1))->assertOk();
|
|
$this->get(route('bumdes.kelembagaan-aktor.index', 1))->assertOk();
|
|
$this->get(route('bumdes.operasional.index', 1))->assertOk();
|
|
$this->get(route('bumdes.rantai-nilai.index', 1))->assertOk();
|
|
$this->get(route('bumdes.edit', 2))->assertNotFound();
|
|
});
|
|
|
|
test('workshop facilitators and coordinators do not gain bumdes survey access', function (string $role) {
|
|
auth()->user()->role = $role;
|
|
$this->get(route('bumdes.index'))->assertForbidden();
|
|
$this->put(route('bumdes.update', 1), $this->profileInput)->assertForbidden();
|
|
expect(DB::table('bumdes_pengurus')->where('id', 1)->value('nama'))->toBe('Alpha');
|
|
})->with(['fasilitator', 'koordinator']);
|
|
|
|
test('incomplete profiles keep secondary tabs locked on the server', function () {
|
|
DB::table('bumdes_pengurus')->where('id', 1)->update(['alamat' => null]);
|
|
try {
|
|
($this->bumdesAction)('operasional.index', null, '1');
|
|
$this->fail('Expected an incomplete-profile restriction.');
|
|
} catch (HttpException $exception) {
|
|
expect($exception->getStatusCode())->toBe(403);
|
|
}
|
|
});
|
|
|
|
test('profile creation rolls back if the dependent section cannot be saved', function () {
|
|
Event::listen('eloquent.creating: '.KelembagaanAktorBumdes::class, function () {
|
|
throw new RuntimeException('Failure during section persistence');
|
|
});
|
|
$request = Request::create('/bumdes', 'POST', $this->profileInput);
|
|
expect(fn () => ($this->bumdesAction)('store', $request))->toThrow(RuntimeException::class);
|
|
expect(DB::table('bumdes_pengurus')->count())->toBe(4)->and(KelembagaanAktorBumdes::count())->toBe(0);
|
|
});
|
|
|
|
test('bumdes resource routes support the complete web workflow with model binding', function () {
|
|
$this->get(route('bumdes.index'))->assertOk();
|
|
$this->get(route('bumdes.create'))->assertOk();
|
|
$this->post(route('bumdes.store'), [...$this->profileInput, 'redirect_fragment' => 'section-kepengurusan'])
|
|
->assertRedirect();
|
|
$pengurus = PengurusBumdes::latest('id')->firstOrFail();
|
|
$this->get(route('bumdes.show', $pengurus))->assertRedirect(route('bumdes.edit', $pengurus));
|
|
$this->get(route('bumdes.edit', $pengurus))->assertOk();
|
|
$this->post(route('bumdes.kelembagaan-aktor.store', $pengurus), [
|
|
'status_badan_hukum' => 'sudah_berbadan_hukum', 'nomor_dasar_hukum' => 'SK-02', 'tahun_dasar_hukum' => 2025,
|
|
'unit_usaha' => ['simpan_pinjam'],
|
|
])->assertRedirect(route('bumdes.kelembagaan-aktor.index', $pengurus));
|
|
$this->get(route('bumdes.kelembagaan-aktor.index', $pengurus))->assertOk();
|
|
$this->post(route('bumdes.operasional.store', $pengurus), ['modal_awal' => '1.000.000'])
|
|
->assertRedirect(route('bumdes.operasional.index', $pengurus));
|
|
$this->get(route('bumdes.operasional.index', $pengurus))->assertOk();
|
|
$this->put(route('bumdes.update', $pengurus), [...$this->profileInput, 'nama' => 'Nama Baru'])
|
|
->assertRedirect(route('bumdes.edit', $pengurus));
|
|
expect($pengurus->refresh()->nama)->toBe('Nama Baru');
|
|
$this->delete(route('bumdes.destroy', $pengurus))->assertRedirect(route('bumdes.index'));
|
|
$this->get(route('bumdes.edit', $pengurus))->assertNotFound();
|
|
expect(KelembagaanAktorBumdes::withTrashed()->where('pengurus_bumdes_id', $pengurus->id)->firstOrFail()->trashed())->toBeTrue();
|
|
});
|
|
|
|
test('web request validation keeps invalid optional capital values above the form', function () {
|
|
$url = route('bumdes.operasional.index', 1);
|
|
$this->from($url)->post(route('bumdes.operasional.store', 1), [
|
|
'pernah_penambahan_modal' => '1', 'penambahan_modal' => [['tahun' => 'invalid', 'jumlah' => ['invalid']]],
|
|
])->assertRedirect($url)->assertSessionHasErrors(['penambahan_modal.0.tahun', 'penambahan_modal.0.jumlah']);
|
|
$this->get($url)->assertOk()->assertSee('Tahun penambahan modal pada baris 1 harus berupa angka.')
|
|
->assertSee('data-has-errors="1"', false);
|
|
});
|
|
|
|
test('productive assets use per-row availability ownership and condition selects with legacy defaults', function () {
|
|
KelembagaanAktorBumdes::create([
|
|
'pengurus_bumdes_id' => 1, 'status_kepemilikan_aset' => 'milik_bumdes_sendiri',
|
|
'aset_produktif' => [['aset' => 'bangunan_kantor', 'dipilih' => true, 'jumlah' => '1', 'kondisi' => 'Baik']],
|
|
]);
|
|
($this->bumdesList)();
|
|
$html = ($this->bumdesAction)('operasional.index', null, '1')->render();
|
|
$dom = new DOMDocument;
|
|
$previous = libxml_use_internal_errors(true);
|
|
$dom->loadHTML($html);
|
|
libxml_clear_errors();
|
|
libxml_use_internal_errors($previous);
|
|
$xpath = new DOMXPath($dom);
|
|
expect($html)->toContain('Aset Produktif Milik BUMDes', 'Ada/Tidak', 'Status Kepemilikan');
|
|
expect($xpath->query('//div[@id="bumdes-aset-produktif"]//input[@type="radio"]')->length)->toBe(12);
|
|
foreach (['bangunan_kantor' => true, 'gudang_penyimpanan' => false] as $asset => $active) {
|
|
$selects = $xpath->query('//tr[@data-bumdes-aset="'.$asset.'"]//select');
|
|
expect($selects->length)->toBe(2);
|
|
foreach ($selects as $select) {
|
|
expect($select->hasAttribute('disabled'))->toBe(! $active)
|
|
->and($select->hasAttribute('required'))->toBe($active);
|
|
}
|
|
}
|
|
expect($xpath->query('//tr[@data-bumdes-aset="bangunan_kantor"]//select[contains(@name, "status_kepemilikan_aset")]/option[@selected]')->item(0)->getAttribute('value'))->toBe('milik_bumdes_sendiri');
|
|
expect($xpath->query('//tr[@data-bumdes-aset="bangunan_kantor"]//select[contains(@name, "kondisi")]/option[@selected]')->item(0)->getAttribute('value'))->toBe('berfungsi');
|
|
expect($xpath->query('//input[@name="status_kepemilikan_aset"]')->length)->toBe(0);
|
|
expect($xpath->query('//select[starts-with(@name, "infrastruktur_pascapanen") and contains(@name, "kondisi")]')->length)->toBe(6);
|
|
});
|
|
|
|
test('productive assets save different ownership and condition for each available asset', function () {
|
|
$request = Request::create('/bumdes/1/operasional', 'POST', ['aset_produktif' => [
|
|
['aset' => 'bangunan_kantor', 'dipilih' => '1', 'jumlah' => '2', 'status_kepemilikan_aset' => 'milik_bumdes_sendiri', 'kondisi' => 'berfungsi'],
|
|
['aset' => 'gudang_penyimpanan', 'dipilih' => '1', 'jumlah' => '1', 'status_kepemilikan_aset' => 'sewa_pinjam_pihak_lain', 'kondisi' => 'perlu_perbaikan_evaluasi'],
|
|
['aset' => 'lahan_usaha', 'dipilih' => '0', 'jumlah' => ['invalid'], 'status_kepemilikan_aset' => 'invalid', 'kondisi' => ['invalid']],
|
|
]]);
|
|
($this->bumdesAction)('operasional.store', $request, '1');
|
|
$assets = KelembagaanAktorBumdes::firstOrFail()->aset_produktif;
|
|
expect($assets)->toHaveCount(2)
|
|
->and($assets[0]['status_kepemilikan_aset'])->toBe('milik_bumdes_sendiri')
|
|
->and($assets[1]['status_kepemilikan_aset'])->toBe('sewa_pinjam_pihak_lain')
|
|
->and($assets[0]['kondisi'])->toBe('berfungsi')
|
|
->and($assets[1]['kondisi'])->toBe('perlu_perbaikan_evaluasi')
|
|
->and($assets[0]['jumlah'])->toBe('2');
|
|
});
|
|
|
|
test('available productive assets validate their required details and choice values', function (array $details, string $error) {
|
|
$request = Request::create('/bumdes/1/operasional', 'POST', ['aset_produktif' => [
|
|
['aset' => 'bangunan_kantor', 'dipilih' => '1', ...$details],
|
|
]]);
|
|
try {
|
|
($this->bumdesAction)('operasional.store', $request, '1');
|
|
$this->fail('Expected productive asset validation.');
|
|
} catch (ValidationException $exception) {
|
|
expect($exception->errors())->toHaveKey('aset_produktif.0.'.$error);
|
|
}
|
|
expect(KelembagaanAktorBumdes::count())->toBe(0);
|
|
})->with([
|
|
[['kondisi' => 'berfungsi'], 'status_kepemilikan_aset'],
|
|
[['status_kepemilikan_aset' => 'milik_bumdes_sendiri'], 'kondisi'],
|
|
[['status_kepemilikan_aset' => 'invalid', 'kondisi' => 'berfungsi'], 'status_kepemilikan_aset'],
|
|
[['status_kepemilikan_aset' => 'milik_bumdes_sendiri', 'kondisi' => 'invalid'], 'kondisi'],
|
|
]);
|
|
|
|
test('legacy productive asset conditions and global ownership remain available without data loss', function () {
|
|
$data = KelembagaanAktorBumdes::create([
|
|
'pengurus_bumdes_id' => 1, 'status_kepemilikan_aset' => 'milik_desa_dipinjamkan_dikelola',
|
|
'aset_produktif' => [['aset' => 'bangunan_kantor', 'dipilih' => true, 'kondisi' => 'Perlu penggantian mesin']],
|
|
]);
|
|
$request = Request::create('/bumdes/1/operasional', 'POST', ['aset_produktif' => [
|
|
['aset' => 'bangunan_kantor', 'dipilih' => '1', 'kondisi' => 'Perlu penggantian mesin'],
|
|
]]);
|
|
($this->bumdesAction)('operasional.store', $request, '1');
|
|
$data->refresh();
|
|
expect($data->aset_produktif[0]['kondisi'])->toBe('Perlu penggantian mesin')
|
|
->and($data->aset_produktif[0]['status_kepemilikan_aset'])->toBe('milik_desa_dipinjamkan_dikelola')
|
|
->and($data->status_kepemilikan_aset?->value)->toBe('milik_desa_dipinjamkan_dikelola');
|
|
$request = Request::create('/bumdes/1/operasional', 'POST', ['aset_produktif' => [
|
|
['aset' => 'bangunan_kantor', 'dipilih' => '0'],
|
|
]]);
|
|
($this->bumdesAction)('operasional.store', $request, '1');
|
|
expect($data->refresh()->aset_produktif)->toBe([]);
|
|
});
|
|
|
|
test('postharvest infrastructure uses availability radios and gated numeric fields like productive assets', function () {
|
|
KelembagaanAktorBumdes::create([
|
|
'pengurus_bumdes_id' => 1,
|
|
'infrastruktur_pascapanen' => [['infrastruktur' => 'timbangan', 'dipilih' => true, 'jumlah' => '2', 'kondisi' => 'baik_berfungsi']],
|
|
]);
|
|
($this->bumdesList)();
|
|
$html = ($this->bumdesAction)('operasional.index', null, '1')->render();
|
|
$dom = new DOMDocument;
|
|
$previous = libxml_use_internal_errors(true);
|
|
$dom->loadHTML($html);
|
|
libxml_clear_errors();
|
|
libxml_use_internal_errors($previous);
|
|
$xpath = new DOMXPath($dom);
|
|
expect($xpath->query('//div[@id="bumdes-infrastruktur-pascapanen"]//input[@type="radio"]')->length)->toBe(12)
|
|
->and($xpath->query('//div[@id="bumdes-infrastruktur-pascapanen"]//input[@type="checkbox"]')->length)->toBe(0);
|
|
foreach (['timbangan' => true, 'lainnya' => false] as $key => $active) {
|
|
$row = '//tr[@data-bumdes-infrastruktur="'.$key.'"]';
|
|
$quantity = $xpath->query($row.'//input[contains(@name, "[jumlah]")]')->item(0);
|
|
$condition = $xpath->query($row.'//select')->item(0);
|
|
expect($quantity->getAttribute('type'))->toBe('number')
|
|
->and($quantity->getAttribute('min'))->toBe('0')
|
|
->and($quantity->hasAttribute('disabled'))->toBe(! $active)
|
|
->and($quantity->hasAttribute('required'))->toBeFalse()
|
|
->and($condition->hasAttribute('disabled'))->toBe(! $active)
|
|
->and($condition->hasAttribute('required'))->toBe($active);
|
|
}
|
|
expect($xpath->query('//tr[@data-bumdes-infrastruktur="timbangan"]//input[contains(@name, "[jumlah]")]')->item(0)->getAttribute('value'))->toBe('2')
|
|
->and($xpath->query('//tr[@data-bumdes-infrastruktur="timbangan"]//option[@selected]')->item(0)->getAttribute('value'))->toBe('baik_berfungsi')
|
|
->and($xpath->query('//div[@id="bumdes-aset-produktif"]//input[@type="number" and @min="0"]')->length)->toBe(6);
|
|
});
|
|
|
|
test('postharvest infrastructure saves available rows and removes unavailable stale details', function () {
|
|
$url = route('bumdes.operasional.store', 1);
|
|
$this->post($url, [
|
|
'infrastruktur_pascapanen' => [
|
|
['infrastruktur' => 'timbangan', 'dipilih' => '1', 'jumlah' => '0', 'kondisi' => 'baik_berfungsi'],
|
|
['infrastruktur' => 'lainnya', 'dipilih' => '1', 'jumlah' => '3', 'kondisi' => 'rusak_tidak_terpakai'],
|
|
['infrastruktur' => 'alat_sortasi', 'dipilih' => '0', 'jumlah' => ['invalid'], 'kondisi' => ['invalid']],
|
|
],
|
|
'infrastruktur_pascapanen_lainnya' => 'Meja pengering',
|
|
])->assertSessionHasNoErrors()->assertRedirect(route('bumdes.operasional.index', 1));
|
|
$data = KelembagaanAktorBumdes::firstOrFail();
|
|
expect($data->infrastruktur_pascapanen)->toHaveCount(2)
|
|
->and($data->infrastruktur_pascapanen[0]['jumlah'])->toBe('0')
|
|
->and($data->infrastruktur_pascapanen[1]['kondisi'])->toBe('rusak_tidak_terpakai')
|
|
->and($data->infrastruktur_pascapanen_lainnya)->toBe('Meja pengering');
|
|
$this->post($url, [
|
|
'infrastruktur_pascapanen' => [['infrastruktur' => 'lainnya', 'dipilih' => '0', 'jumlah' => '-1', 'kondisi' => 'invalid']],
|
|
'infrastruktur_pascapanen_lainnya' => 'Meja pengering',
|
|
])->assertSessionHasNoErrors();
|
|
expect($data->refresh()->infrastruktur_pascapanen)->toBe([])
|
|
->and($data->infrastruktur_pascapanen_lainnya)->toBeNull();
|
|
});
|
|
|
|
test('available infrastructure validates required details and condition choices', function (array $row, string $error) {
|
|
$this->post(route('bumdes.operasional.store', 1), ['infrastruktur_pascapanen' => [$row]])
|
|
->assertSessionHasErrors($error);
|
|
expect(KelembagaanAktorBumdes::count())->toBe(0);
|
|
})->with([
|
|
[['infrastruktur' => 'timbangan', 'dipilih' => '1'], 'infrastruktur_pascapanen.0.kondisi'],
|
|
[['infrastruktur' => 'timbangan', 'dipilih' => '1', 'kondisi' => 'invalid'], 'infrastruktur_pascapanen.0.kondisi'],
|
|
[['infrastruktur' => 'lainnya', 'dipilih' => '1', 'kondisi' => 'baik_berfungsi'], 'infrastruktur_pascapanen_lainnya'],
|
|
]);
|
|
|
|
test('inventory quantities reject negative fractional and nonnumeric values', function (string $field, string $quantity) {
|
|
$row = $field === 'aset_produktif'
|
|
? ['aset' => 'bangunan_kantor', 'status_kepemilikan_aset' => 'milik_bumdes_sendiri', 'kondisi' => 'berfungsi']
|
|
: ['infrastruktur' => 'timbangan', 'kondisi' => 'baik_berfungsi'];
|
|
$this->post(route('bumdes.operasional.store', 1), [$field => [[...$row, 'dipilih' => '1', 'jumlah' => $quantity]]])
|
|
->assertSessionHasErrors($field.'.0.jumlah');
|
|
expect(KelembagaanAktorBumdes::count())->toBe(0);
|
|
})->with(['aset_produktif', 'infrastruktur_pascapanen'])->with(['-1', '1.5', 'dua']);
|
|
|
|
test('saving each bumdes tab preserves the other tabs data and redirects to the saved tab', function () {
|
|
$controller = $this->bumdesAction;
|
|
$save = function (string $method, array $input) use ($controller) {
|
|
$request = Request::create('/bumdes/1', 'POST', $input);
|
|
$request->setLaravelSession(app('session')->driver());
|
|
$this->app->instance('request', $request);
|
|
|
|
return $controller($method, $request, '1');
|
|
};
|
|
$save('update', [...$this->profileInput,
|
|
'status_keaktifan' => 'tidak_aktif_vakum', 'alasan_tidak_aktif' => 'Kekurangan modal',
|
|
'memiliki_staf' => '1', 'jumlah_staf' => 3, 'unit_usaha_staf' => 'Toko',
|
|
]);
|
|
$response = $save('operasional.store', [
|
|
'status_keaktifan' => 'tidak_aktif_vakum', 'alasan_tidak_aktif' => 'Kekurangan modal',
|
|
'memiliki_staf' => '1', 'jumlah_staf' => 3, 'unit_usaha_staf' => 'Toko',
|
|
'modal_awal' => '10.000.000', 'tahun_modal_awal' => 2024,
|
|
'pernah_penambahan_modal' => '1', 'penambahan_modal' => [['tahun' => 2025, 'jumlah' => '1.000.000']],
|
|
]);
|
|
expect($response->getTargetUrl())->toBe(route('bumdes.operasional.index', 1));
|
|
$response = $save('kelembagaan.store', [
|
|
'status_badan_hukum' => 'sudah_berbadan_hukum', 'nomor_dasar_hukum' => 'SK-01', 'tahun_dasar_hukum' => 2024,
|
|
'unit_usaha' => ['simpan_pinjam', 'jasa_lainnya'], 'unit_usaha_jasa_lainnya' => 'Pengiriman',
|
|
'status_unit_komoditas' => 'ya', 'nama_unit_komoditas' => 'Kopi',
|
|
'status_ad_art' => 'ada_lengkap', 'tahun_ad_art' => 2025,
|
|
'status_keaktifan' => 'aktif_penuh',
|
|
]);
|
|
expect($response->getTargetUrl())->toBe(route('bumdes.kelembagaan-aktor.index', 1));
|
|
$data = KelembagaanAktorBumdes::firstOrFail();
|
|
expect($data->status_keaktifan?->value)->toBe('tidak_aktif_vakum')
|
|
->and($data->alasan_tidak_aktif)->toBe('Kekurangan modal')
|
|
->and($data->jumlah_staf)->toBe(3)
|
|
->and($data->modal_awal)->toBe('10000000.00')
|
|
->and($data->penambahan_modal[0]['jumlah'])->toBe('1000000');
|
|
$save('operasional.store', ['status_keaktifan' => 'aktif_penuh', 'status_badan_hukum' => 'tidak_tahu']);
|
|
expect(KelembagaanAktorBumdes::firstOrFail()->status_keaktifan?->value)->toBe('tidak_aktif_vakum');
|
|
$save('update', [...$this->profileInput, 'status_keaktifan' => 'aktif_penuh', 'status_badan_hukum' => 'tidak_tahu']);
|
|
$data->refresh();
|
|
expect($data->status_badan_hukum?->value)->toBe('sudah_berbadan_hukum')
|
|
->and($data->nomor_dasar_hukum)->toBe('SK-01')
|
|
->and($data->unit_usaha->map(fn ($value) => $value->value)->all())->toBe(['simpan_pinjam', 'jasa_lainnya'])
|
|
->and($data->nama_unit_komoditas)->toBe('Kopi')
|
|
->and($data->tahun_ad_art)->toBe(2025)
|
|
->and($data->alasan_tidak_aktif)->toBeNull();
|
|
$save('kelembagaan.store', ['unit_usaha' => [], 'status_ad_art' => 'tidak_ada', 'status_unit_komoditas' => 'direncanakan']);
|
|
$data->refresh();
|
|
expect($data->unit_usaha->map(fn ($value) => $value->value)->all())->toBe([])
|
|
->and($data->unit_usaha_jasa_lainnya)->toBeNull()
|
|
->and($data->nama_unit_komoditas)->toBeNull()
|
|
->and($data->tahun_ad_art)->toBeNull()
|
|
->and($data->status_keaktifan?->value)->toBe('aktif_penuh');
|
|
});
|
|
|
|
test('ecosystem support is included in the operational form without question numbers or a separate tab', function () {
|
|
$data = KelembagaanAktorBumdes::create([
|
|
'pengurus_bumdes_id' => 1, 'ada_alokasi_desa' => false,
|
|
'dukungan_desa' => ['pendampingan_teknis', 'lainnya'], 'dukungan_desa_lainnya' => 'Pelatihan pemasaran',
|
|
'keterlibatan_koordinasi_desa' => 'kadang',
|
|
'kemitraan_eksternal' => ['ngo', 'lainnya'], 'kemitraan_eksternal_lainnya' => 'Perguruan tinggi',
|
|
]);
|
|
$response = $this->get(route('bumdes.operasional.index', 1))->assertOk();
|
|
$response->assertSee('Dukungan Ekosistem')->assertSee('Alokasi APBDes/Dana Desa Tahun Berjalan')
|
|
->assertSee('Dukungan Lain dari Pemerintah Desa')
|
|
->assertSee('Keterlibatan dalam Rapat Desa Terkait Komoditas')
|
|
->assertSee('Kemitraan Eksternal');
|
|
$dom = new DOMDocument;
|
|
$previous = libxml_use_internal_errors(true);
|
|
$dom->loadHTML($response->getContent());
|
|
libxml_clear_errors();
|
|
libxml_use_internal_errors($previous);
|
|
$xpath = new DOMXPath($dom);
|
|
expect($xpath->query('//input[@id="alokasi-desa-tidak"]')->item(0)->hasAttribute('checked'))->toBeTrue()
|
|
->and($xpath->query('//input[@id="jumlah-alokasi-desa"]')->item(0)->hasAttribute('disabled'))->toBeTrue()
|
|
->and($xpath->query('//input[@name="dukungan_desa[]"]')->length)->toBe(5)
|
|
->and($xpath->query('//input[@name="kemitraan_eksternal[]"]')->length)->toBe(6)
|
|
->and($xpath->query('//input[@name="keterlibatan_koordinasi_desa"]')->length)->toBe(3);
|
|
expect($xpath->query('//a[contains(@class, "nav-link") and contains(., "Dukungan Ekosistem")]')->length)->toBe(0)
|
|
->and($xpath->query('//form[@id="operasionalBumdesForm"]//input[@name="modal_awal"]')->length)->toBe(1)
|
|
->and($xpath->query('//form[@id="operasionalBumdesForm"]//input[@name="jumlah_alokasi_desa"]')->length)->toBe(1)
|
|
->and($xpath->query('//form[@id="operasionalBumdesForm"]//button[@type="submit"]')->length)->toBe(7);
|
|
foreach ($xpath->query('//form[@id="operasionalBumdesForm"]//legend') as $legend) {
|
|
expect(trim($legend->textContent))->not->toMatch('/^\d+\./');
|
|
}
|
|
foreach (['dukungan-desa-lainnya-detail', 'kemitraan-eksternal-lainnya-detail'] as $id) {
|
|
$input = $xpath->query('//input[@id="'.$id.'"]')->item(0);
|
|
expect($input->hasAttribute('disabled'))->toBeFalse()->and($input->hasAttribute('required'))->toBeTrue();
|
|
}
|
|
expect($data->dukungan_desa->first())->toBe(DukunganDesaBumdes::PENDAMPINGAN_TEKNIS)
|
|
->and($data->keterlibatan_koordinasi_desa)->toBe(KeterlibatanKoordinasiBumdes::KADANG);
|
|
});
|
|
|
|
test('operational form saves capital assets and ecosystem answers together without overwriting other tabs', function () {
|
|
$data = KelembagaanAktorBumdes::create([
|
|
'pengurus_bumdes_id' => 1, 'status_badan_hukum' => 'tidak_tahu',
|
|
'status_keaktifan' => 'aktif_penuh', 'modal_awal' => 2000000,
|
|
]);
|
|
$url = route('bumdes.operasional.index', 1);
|
|
$this->post(route('bumdes.operasional.store', 1), [
|
|
'ada_alokasi_desa' => '1', 'jumlah_alokasi_desa' => '10.000.000',
|
|
'dukungan_desa' => ['pendampingan_teknis', 'regulasi_perdes', 'lainnya'], 'dukungan_desa_lainnya' => 'Pelatihan pemasaran',
|
|
'keterlibatan_koordinasi_desa' => 'selalu',
|
|
'kemitraan_eksternal' => ['ngo', 'swasta_offtaker', 'lainnya'], 'kemitraan_eksternal_lainnya' => 'Perguruan tinggi',
|
|
'modal_awal' => '999', 'nama' => 'Nama tidak boleh berubah',
|
|
'aset_produktif' => [['aset' => 'bangunan_kantor', 'dipilih' => '1', 'jumlah' => 2, 'kondisi' => 'berfungsi', 'status_kepemilikan_aset' => 'milik_bumdes_sendiri']],
|
|
])->assertRedirect($url)->assertSessionHasNoErrors()->assertSessionHas('success', 'Data Operasional berhasil disimpan.');
|
|
$data->refresh();
|
|
expect($data->ada_alokasi_desa)->toBeTrue()
|
|
->and($data->jumlah_alokasi_desa)->toBe('10000000.00')
|
|
->and($data->dukungan_desa->map->value->all())->toBe(['pendampingan_teknis', 'regulasi_perdes', 'lainnya'])
|
|
->and($data->dukungan_desa_lainnya)->toBe('Pelatihan pemasaran')
|
|
->and($data->kemitraan_eksternal->map->value->all())->toBe(['ngo', 'swasta_offtaker', 'lainnya'])
|
|
->and($data->kemitraan_eksternal_lainnya)->toBe('Perguruan tinggi')
|
|
->and($data->keterlibatan_koordinasi_desa->value)->toBe('selalu')
|
|
->and($data->modal_awal)->toBe('999.00')
|
|
->and($data->aset_produktif[0]['jumlah'])->toBe(2)
|
|
->and($data->status_badan_hukum->value)->toBe('tidak_tahu')
|
|
->and($data->status_keaktifan->value)->toBe('aktif_penuh')
|
|
->and($data->created_by)->toBe(7)
|
|
->and($data->updated_by)->toBe(7)
|
|
->and(PengurusBumdes::findOrFail(1)->nama)->toBe('Alpha');
|
|
$this->get($url)->assertOk()->assertSee('value="10.000.000"', false)->assertSee('Pelatihan pemasaran');
|
|
$this->put(route('bumdes.update', 1), $this->profileInput)->assertSessionHasNoErrors();
|
|
$this->post(route('bumdes.kelembagaan-aktor.store', 1), ['status_badan_hukum' => 'tidak_tahu'])->assertSessionHasNoErrors();
|
|
expect($data->refresh()->jumlah_alokasi_desa)->toBe('10000000.00')
|
|
->and($data->dukungan_desa_lainnya)->toBe('Pelatihan pemasaran')
|
|
->and($data->kemitraan_eksternal_lainnya)->toBe('Perguruan tinggi');
|
|
});
|
|
|
|
test('ecosystem support clears disabled answers and allows unanswered questions', function () {
|
|
$data = KelembagaanAktorBumdes::create([
|
|
'pengurus_bumdes_id' => 1, 'ada_alokasi_desa' => true, 'jumlah_alokasi_desa' => 1000,
|
|
'dukungan_desa' => ['lainnya'], 'dukungan_desa_lainnya' => 'Pelatihan',
|
|
'kemitraan_eksternal' => ['lainnya'], 'kemitraan_eksternal_lainnya' => 'Universitas',
|
|
]);
|
|
$url = route('bumdes.operasional.store', 1);
|
|
$this->post($url, [
|
|
'ada_alokasi_desa' => '0', 'jumlah_alokasi_desa' => ['invalid'],
|
|
'dukungan_desa' => ['tidak_ada'], 'dukungan_desa_lainnya' => ['invalid'],
|
|
'kemitraan_eksternal' => ['belum_ada'], 'kemitraan_eksternal_lainnya' => ['invalid'],
|
|
])->assertSessionHasNoErrors();
|
|
expect($data->refresh()->ada_alokasi_desa)->toBeFalse()
|
|
->and($data->jumlah_alokasi_desa)->toBeNull()
|
|
->and($data->dukungan_desa_lainnya)->toBeNull()
|
|
->and($data->kemitraan_eksternal_lainnya)->toBeNull();
|
|
$this->post($url, [])->assertSessionHasNoErrors();
|
|
expect($data->refresh()->ada_alokasi_desa)->toBeNull()
|
|
->and($data->dukungan_desa->all())->toBe([])
|
|
->and($data->kemitraan_eksternal->all())->toBe([]);
|
|
});
|
|
|
|
test('ecosystem support validates conditional details choices and exclusive answers', function (array $input, string $field) {
|
|
$url = route('bumdes.operasional.index', 1);
|
|
$this->from($url)->post(route('bumdes.operasional.store', 1), $input)
|
|
->assertRedirect($url)->assertSessionHasErrors($field)->assertSessionHasInput(array_key_first($input));
|
|
$response = $this->get($url)->assertOk()->assertSee('data-has-errors="1"', false);
|
|
expect(strpos($response->getContent(), 'id="bumdes-notifications"'))->toBeLessThan(strpos($response->getContent(), 'id="operasionalBumdesForm"'));
|
|
expect(KelembagaanAktorBumdes::count())->toBe(0);
|
|
})->with([
|
|
[['ada_alokasi_desa' => '1'], 'jumlah_alokasi_desa'],
|
|
[['ada_alokasi_desa' => '1', 'jumlah_alokasi_desa' => '-1'], 'jumlah_alokasi_desa'],
|
|
[['ada_alokasi_desa' => '1', 'jumlah_alokasi_desa' => '1.5'], 'jumlah_alokasi_desa'],
|
|
[['ada_alokasi_desa' => '1', 'jumlah_alokasi_desa' => '10abc'], 'jumlah_alokasi_desa'],
|
|
[['ada_alokasi_desa' => '1', 'jumlah_alokasi_desa' => '10000000000000000'], 'jumlah_alokasi_desa'],
|
|
[['ada_alokasi_desa' => 'invalid'], 'ada_alokasi_desa'],
|
|
[['ada_alokasi_desa' => ['invalid']], 'ada_alokasi_desa'],
|
|
[['ada_alokasi_desa' => '1', 'jumlah_alokasi_desa' => ['invalid']], 'jumlah_alokasi_desa'],
|
|
[['dukungan_desa' => ['lainnya']], 'dukungan_desa_lainnya'],
|
|
[['kemitraan_eksternal' => ['lainnya']], 'kemitraan_eksternal_lainnya'],
|
|
[['dukungan_desa' => ['lainnya'], 'dukungan_desa_lainnya' => ['invalid']], 'dukungan_desa_lainnya'],
|
|
[['kemitraan_eksternal' => ['lainnya'], 'kemitraan_eksternal_lainnya' => ['invalid']], 'kemitraan_eksternal_lainnya'],
|
|
[['dukungan_desa' => ['tidak_ada', 'pendampingan_teknis']], 'dukungan_desa'],
|
|
[['kemitraan_eksternal' => ['belum_ada', 'ngo']], 'kemitraan_eksternal'],
|
|
[['dukungan_desa' => ['invalid']], 'dukungan_desa.0'],
|
|
[['kemitraan_eksternal' => ['invalid']], 'kemitraan_eksternal.0'],
|
|
[['dukungan_desa' => 'pendampingan_teknis'], 'dukungan_desa'],
|
|
[['kemitraan_eksternal' => ['ngo', 'ngo']], 'kemitraan_eksternal.0'],
|
|
[['keterlibatan_koordinasi_desa' => 'invalid'], 'keterlibatan_koordinasi_desa'],
|
|
]);
|
|
|
|
test('invalid ecosystem answers prevent partial updates to operational data', function () {
|
|
$data = KelembagaanAktorBumdes::create([
|
|
'pengurus_bumdes_id' => 1, 'modal_awal' => 2000000,
|
|
'dukungan_desa' => ['pendampingan_teknis'],
|
|
]);
|
|
$this->from(route('bumdes.operasional.index', 1))->post(route('bumdes.operasional.store', 1), [
|
|
'modal_awal' => '5000000', 'ada_alokasi_desa' => '1',
|
|
'dukungan_desa' => ['regulasi_perdes'],
|
|
])->assertSessionHasErrors('jumlah_alokasi_desa');
|
|
expect($data->refresh()->modal_awal)->toBe('2000000.00')
|
|
->and($data->dukungan_desa->map->value->all())->toBe(['pendampingan_teknis']);
|
|
$this->get(route('bumdes.operasional.index', 1))->assertOk()->assertSee('value="5.000.000"', false);
|
|
});
|
|
|
|
test('ecosystem support stays locked for incomplete profiles', function () {
|
|
DB::table('bumdes_pengurus')->where('id', 1)->update(['alamat' => null]);
|
|
$this->get(route('bumdes.operasional.index', 1))->assertForbidden();
|
|
$this->post(route('bumdes.operasional.store', 1), [])->assertForbidden();
|
|
});
|
|
|
|
test('ecosystem allocation accepts zero without treating it as an empty answer', function () {
|
|
$this->post(route('bumdes.operasional.store', 1), ['ada_alokasi_desa' => '1', 'jumlah_alokasi_desa' => '0'])
|
|
->assertSessionHasNoErrors();
|
|
expect(KelembagaanAktorBumdes::firstOrFail()->jumlah_alokasi_desa)->toBe('0.00');
|
|
});
|
|
|
|
test('ecosystem support migration can roll back without losing existing tab data', function () {
|
|
$data = KelembagaanAktorBumdes::create(['pengurus_bumdes_id' => 1, 'modal_awal' => 2000000]);
|
|
$migration = require database_path('migrations/2026_11_04_000001_add_dukungan_ekosistem_to_bumdes_kelembagaan_aktor_table.php');
|
|
$migration->down();
|
|
expect(Schema::hasColumn('bumdes_kelembagaan_aktor', 'ada_alokasi_desa'))->toBeFalse()
|
|
->and(DB::table('bumdes_kelembagaan_aktor')->where('id', $data->id)->value('modal_awal'))->toEqual(2000000);
|
|
$migration->up();
|
|
expect(Schema::hasColumn('bumdes_kelembagaan_aktor', 'ada_alokasi_desa'))->toBeTrue()
|
|
->and(DB::table('bumdes_kelembagaan_aktor')->where('id', $data->id)->value('modal_awal'))->toEqual(2000000);
|
|
});
|
|
|
|
test('operational management questions are grouped into separate cards with save and back buttons', function () {
|
|
$response = $this->get(route('bumdes.operasional.index', 1))->assertOk();
|
|
$dom = new DOMDocument;
|
|
$previous = libxml_use_internal_errors(true);
|
|
$dom->loadHTML($response->getContent());
|
|
libxml_clear_errors();
|
|
libxml_use_internal_errors($previous);
|
|
$xpath = new DOMXPath($dom);
|
|
$cards = $xpath->query('//form[@id="operasionalBumdesForm"]/div[contains(@class, "card")]');
|
|
expect($cards->length)->toBe(7);
|
|
foreach ($cards as $card) {
|
|
expect($xpath->query('.//button[@type="submit"]', $card)->length)->toBe(1)
|
|
->and($xpath->query('.//a[normalize-space(.)="Kembali"]', $card)->length)->toBe(1);
|
|
}
|
|
foreach (['kapasitas-keuangan', 'jangkauan-layanan', 'layanan-petani', 'rantai-nilai', 'hambatan-usaha'] as $section) {
|
|
expect($xpath->query('//div[@id="section-'.$section.'"]')->length)->toBe(1);
|
|
}
|
|
foreach (['sifat_audit[]', 'pelaksana_audit', 'tahun_audit', 'tahun_laporan_musdes', 'nominal_kontribusi_pades', 'desa_lain_dilayani', 'jumlah_petani_desa_lain', 'nama_offtaker', 'tahun_mulai_offtaker', 'alasan_berhenti_offtaker'] as $field) {
|
|
foreach ($xpath->query('//*[@name="'.$field.'"]') as $input) {
|
|
expect($input->hasAttribute('disabled'))->toBeTrue();
|
|
}
|
|
}
|
|
foreach (['jumlah_petani_desa_sendiri', 'jumlah_petani_desa_lain'] as $field) {
|
|
$input = $xpath->query('//input[@name="'.$field.'"]')->item(0);
|
|
expect($input->getAttribute('type'))->toBe('number')->and($input->getAttribute('min'))->toBe('0');
|
|
}
|
|
foreach ($xpath->query('//form[@id="operasionalBumdesForm"]//legend') as $legend) {
|
|
expect(trim($legend->textContent))->not->toMatch('/^\d+[.)]/');
|
|
}
|
|
});
|
|
|
|
test('operational management answers are stored with enums and restored without changing other tabs', function () {
|
|
$data = KelembagaanAktorBumdes::create(['pengurus_bumdes_id' => 1, 'status_badan_hukum' => 'tidak_tahu', 'status_keaktifan' => 'aktif_penuh']);
|
|
$input = [
|
|
'modal_awal' => '1.000.000',
|
|
'status_pembukuan' => 'rutin_tertib', 'format_pembukuan' => 'digital_excel',
|
|
'pernah_audit' => '1', 'sifat_audit' => ['internal', 'eksternal'], 'pelaksana_audit' => 'Inspektorat', 'tahun_audit' => 2025,
|
|
'pertanggungjawaban_musdes' => 'tahunan', 'tahun_laporan_musdes' => 2025,
|
|
'kondisi_keuangan' => 'untung_surplus', 'kontribusi_pades' => 'tidak_rutin', 'nominal_kontribusi_pades' => '2.500.000',
|
|
'cakupan_layanan' => 'desa_sendiri_dan_lain', 'desa_lain_dilayani' => 'Desa B, Desa C',
|
|
'jumlah_petani_desa_sendiri' => 25, 'jumlah_petani_desa_lain' => 0, 'pencatatan_petani' => 'rapi',
|
|
'layanan_petani' => ['simpan_pinjam', 'pelatihan', 'lainnya'], 'layanan_petani_lainnya' => 'Pengiriman hasil panen',
|
|
'peran_rantai_nilai' => 'mengolah_sendiri', 'pengalaman_offtaker' => 'berjalan', 'nama_offtaker' => 'Mitra Kopi', 'tahun_mulai_offtaker' => 2024,
|
|
'hambatan_usaha' => ['modal', 'akses_pasar', 'lainnya'], 'hambatan_usaha_lainnya' => 'Kendala logistik',
|
|
];
|
|
$this->post(route('bumdes.operasional.store', 1), $input)
|
|
->assertRedirect(route('bumdes.operasional.index', 1))->assertSessionHasNoErrors()
|
|
->assertSessionHas('success', 'Data Operasional berhasil disimpan.');
|
|
$data->refresh();
|
|
expect($data->status_pembukuan)->toBe(StatusPembukuanBumdes::RUTIN_TERTIB)
|
|
->and($data->sifat_audit->map->value->all())->toBe(['internal', 'eksternal'])
|
|
->and($data->pelaksana_audit)->toBe('Inspektorat')
|
|
->and($data->tahun_audit)->toBe(2025)
|
|
->and($data->tahun_laporan_musdes)->toBe(2025)
|
|
->and($data->nominal_kontribusi_pades)->toBe('2500000.00')
|
|
->and($data->jumlah_petani_desa_lain)->toBe(0)
|
|
->and($data->desa_lain_dilayani)->toBe('Desa B, Desa C')
|
|
->and($data->layanan_petani->map->value->all())->toBe(['simpan_pinjam', 'pelatihan', 'lainnya'])
|
|
->and($data->layanan_petani_lainnya)->toBe('Pengiriman hasil panen')
|
|
->and($data->nama_offtaker)->toBe('Mitra Kopi')
|
|
->and($data->tahun_mulai_offtaker)->toBe(2024)
|
|
->and($data->hambatan_usaha->map->value->all())->toBe(['modal', 'akses_pasar', 'lainnya'])
|
|
->and($data->hambatan_usaha_lainnya)->toBe('Kendala logistik')
|
|
->and($data->status_badan_hukum->value)->toBe('tidak_tahu')
|
|
->and($data->status_keaktifan->value)->toBe('aktif_penuh');
|
|
$this->get(route('bumdes.operasional.index', 1))->assertOk()->assertSee('Inspektorat')->assertSee('value="2.500.000"', false)->assertSee('Mitra Kopi');
|
|
$this->put(route('bumdes.update', 1), $this->profileInput)->assertSessionHasNoErrors();
|
|
$this->post(route('bumdes.kelembagaan-aktor.store', 1), ['status_badan_hukum' => 'tidak_tahu'])->assertSessionHasNoErrors();
|
|
expect($data->refresh()->nama_offtaker)->toBe('Mitra Kopi')->and($data->tahun_audit)->toBe(2025);
|
|
});
|
|
|
|
test('operational management skips disabled details and clears prior branch data', function () {
|
|
$data = KelembagaanAktorBumdes::create([
|
|
'pengurus_bumdes_id' => 1, 'pernah_audit' => true, 'sifat_audit' => ['internal'], 'pelaksana_audit' => 'Pengawas', 'tahun_audit' => 2025,
|
|
'pertanggungjawaban_musdes' => 'tahunan', 'tahun_laporan_musdes' => 2025,
|
|
'kontribusi_pades' => 'rutin', 'nominal_kontribusi_pades' => 1000000,
|
|
'cakupan_layanan' => 'desa_sendiri_dan_lain', 'desa_lain_dilayani' => 'Desa B', 'jumlah_petani_desa_lain' => 12,
|
|
'pengalaman_offtaker' => 'berjalan', 'nama_offtaker' => 'Mitra', 'tahun_mulai_offtaker' => 2024,
|
|
'layanan_petani' => ['lainnya'], 'layanan_petani_lainnya' => 'Pengiriman',
|
|
'hambatan_usaha' => ['lainnya'], 'hambatan_usaha_lainnya' => 'Logistik',
|
|
]);
|
|
$this->post(route('bumdes.operasional.store', 1), [
|
|
'pernah_audit' => '0', 'sifat_audit' => 'invalid', 'pelaksana_audit' => ['invalid'], 'tahun_audit' => 'invalid',
|
|
'pertanggungjawaban_musdes' => 'belum_pernah', 'tahun_laporan_musdes' => 'invalid',
|
|
'kontribusi_pades' => 'belum_pernah', 'nominal_kontribusi_pades' => ['invalid'],
|
|
'cakupan_layanan' => 'desa_sendiri', 'desa_lain_dilayani' => ['invalid'], 'jumlah_petani_desa_lain' => 'invalid',
|
|
'pengalaman_offtaker' => 'belum_pernah', 'nama_offtaker' => ['invalid'], 'tahun_mulai_offtaker' => 'invalid', 'alasan_berhenti_offtaker' => ['invalid'],
|
|
'layanan_petani' => ['belum_ada'], 'layanan_petani_lainnya' => ['invalid'],
|
|
'hambatan_usaha' => ['modal'], 'hambatan_usaha_lainnya' => ['invalid'],
|
|
])->assertSessionHasNoErrors();
|
|
$data->refresh();
|
|
expect($data->sifat_audit->all())->toBe([]);
|
|
foreach (['pelaksana_audit', 'tahun_audit', 'tahun_laporan_musdes', 'nominal_kontribusi_pades', 'desa_lain_dilayani', 'jumlah_petani_desa_lain', 'nama_offtaker', 'tahun_mulai_offtaker', 'alasan_berhenti_offtaker', 'layanan_petani_lainnya', 'hambatan_usaha_lainnya'] as $field) {
|
|
expect($data->$field)->toBeNull();
|
|
}
|
|
$this->post(route('bumdes.operasional.store', 1), ['pengalaman_offtaker' => 'berhenti', 'alasan_berhenti_offtaker' => 'Harga tidak sesuai'])->assertSessionHasNoErrors();
|
|
expect($data->refresh()->alasan_berhenti_offtaker)->toBe('Harga tidak sesuai');
|
|
});
|
|
|
|
test('operational management rejects missing dependent answers and invalid values without partial saves', function (array $input, array $errors) {
|
|
$data = KelembagaanAktorBumdes::create(['pengurus_bumdes_id' => 1, 'modal_awal' => 2000000]);
|
|
$url = route('bumdes.operasional.index', 1);
|
|
$this->from($url)->post(route('bumdes.operasional.store', 1), [...$input, 'modal_awal' => '5000000'])
|
|
->assertRedirect($url)->assertSessionHasErrors($errors);
|
|
expect($data->refresh()->modal_awal)->toBe('2000000.00');
|
|
$this->get($url)->assertOk()->assertSee('data-has-errors="1"', false);
|
|
})->with([
|
|
[['pernah_audit' => '1'], ['sifat_audit', 'pelaksana_audit', 'tahun_audit']],
|
|
[['pernah_audit' => '1', 'sifat_audit' => ['invalid'], 'pelaksana_audit' => 'Pengawas', 'tahun_audit' => 2025], ['sifat_audit.0']],
|
|
[['pertanggungjawaban_musdes' => 'tahunan'], ['tahun_laporan_musdes']],
|
|
[['pertanggungjawaban_musdes' => 'tahunan', 'tahun_laporan_musdes' => 1899], ['tahun_laporan_musdes']],
|
|
[['kontribusi_pades' => 'rutin'], ['nominal_kontribusi_pades']],
|
|
[['kontribusi_pades' => 'tidak_rutin', 'nominal_kontribusi_pades' => '-1'], ['nominal_kontribusi_pades']],
|
|
[['kontribusi_pades' => 'rutin', 'nominal_kontribusi_pades' => '1.5'], ['nominal_kontribusi_pades']],
|
|
[['kontribusi_pades' => 'rutin', 'nominal_kontribusi_pades' => ['invalid']], ['nominal_kontribusi_pades']],
|
|
[['cakupan_layanan' => 'desa_sendiri_dan_lain'], ['desa_lain_dilayani', 'jumlah_petani_desa_lain']],
|
|
[['jumlah_petani_desa_sendiri' => -1], ['jumlah_petani_desa_sendiri']],
|
|
[['jumlah_petani_desa_sendiri' => '1.5'], ['jumlah_petani_desa_sendiri']],
|
|
[['pengalaman_offtaker' => 'berjalan'], ['nama_offtaker', 'tahun_mulai_offtaker']],
|
|
[['pengalaman_offtaker' => 'berhenti'], ['alasan_berhenti_offtaker']],
|
|
[['layanan_petani' => ['lainnya']], ['layanan_petani_lainnya']],
|
|
[['layanan_petani' => ['belum_ada', 'pelatihan']], ['layanan_petani']],
|
|
[['hambatan_usaha' => ['lainnya']], ['hambatan_usaha_lainnya']],
|
|
[['hambatan_usaha' => ['modal', 'modal']], ['hambatan_usaha.0']],
|
|
[['layanan_petani' => ['lainnya'], 'layanan_petani_lainnya' => ['invalid']], ['layanan_petani_lainnya']],
|
|
]);
|
|
|
|
test('operational fixed choices reject unknown values', function (string $field) {
|
|
$this->post(route('bumdes.operasional.store', 1), [$field => 'invalid'])->assertSessionHasErrors($field);
|
|
expect(KelembagaanAktorBumdes::count())->toBe(0);
|
|
})->with(['status_pembukuan', 'format_pembukuan', 'pernah_audit', 'pertanggungjawaban_musdes', 'kondisi_keuangan', 'kontribusi_pades', 'cakupan_layanan', 'pencatatan_petani', 'peran_rantai_nilai', 'pengalaman_offtaker', 'layanan_petani', 'hambatan_usaha']);
|
|
|
|
test('operational historical years cannot be in the future', function () {
|
|
$future = now()->year + 1;
|
|
$this->post(route('bumdes.operasional.store', 1), [
|
|
'pernah_audit' => '1', 'sifat_audit' => ['internal'], 'pelaksana_audit' => 'Pengawas', 'tahun_audit' => $future,
|
|
'pertanggungjawaban_musdes' => 'tahunan', 'tahun_laporan_musdes' => $future,
|
|
'pengalaman_offtaker' => 'berjalan', 'nama_offtaker' => 'Mitra', 'tahun_mulai_offtaker' => $future,
|
|
])->assertSessionHasErrors(['tahun_audit', 'tahun_laporan_musdes', 'tahun_mulai_offtaker']);
|
|
expect(KelembagaanAktorBumdes::count())->toBe(0);
|
|
});
|
|
|
|
test('management migration rolls back only its new columns', function () {
|
|
$data = KelembagaanAktorBumdes::create(['pengurus_bumdes_id' => 1, 'modal_awal' => 2000000, 'ada_alokasi_desa' => false]);
|
|
$migration = require database_path('migrations/2026_11_04_000002_add_pengelolaan_layanan_to_bumdes_kelembagaan_aktor_table.php');
|
|
$migration->down();
|
|
expect(Schema::hasColumn('bumdes_kelembagaan_aktor', 'status_pembukuan'))->toBeFalse()
|
|
->and(Schema::hasColumn('bumdes_kelembagaan_aktor', 'hambatan_usaha_lainnya'))->toBeFalse()
|
|
->and(DB::table('bumdes_kelembagaan_aktor')->where('id', $data->id)->value('modal_awal'))->toEqual(2000000);
|
|
$migration->up();
|
|
expect(Schema::hasColumn('bumdes_kelembagaan_aktor', 'status_pembukuan'))->toBeTrue();
|
|
});
|
|
|
|
test('value chain tab renders separate cards quantities and the product by harvest price matrix', function () {
|
|
$response = $this->get(route('bumdes.rantai-nilai.index', 1))->assertOk();
|
|
$dom = new DOMDocument;
|
|
$previous = libxml_use_internal_errors(true);
|
|
$dom->loadHTML($response->getContent());
|
|
libxml_clear_errors();
|
|
libxml_use_internal_errors($previous);
|
|
$xpath = new DOMXPath($dom);
|
|
expect($xpath->query('//form[@id="rantaiNilaiBumdesForm"]/div[contains(@class, "card")]')->length)->toBe(8)
|
|
->and($xpath->query('//form[@id="rantaiNilaiBumdesForm"]//button[@type="submit"]')->length)->toBe(8)
|
|
->and($xpath->query('//form[@id="rantaiNilaiBumdesForm"]//a[normalize-space(.)="Kembali"]')->length)->toBe(8)
|
|
->and($xpath->query('//fieldset[@data-rantai-section and @disabled]')->length)->toBe(4)
|
|
->and($xpath->query('//tr[@data-rantai-price-product]')->length)->toBe(4)
|
|
->and($xpath->query('//input[starts-with(@name, "harga_beli_petani[")]')->length)->toBe(12)
|
|
->and($xpath->query('//input[starts-with(@name, "volume_") and @type="number" and @step="0.001" and @min="0"]')->length)->toBe(6);
|
|
foreach ($xpath->query('//form[@id="rantaiNilaiBumdesForm"]//legend') as $legend) {
|
|
expect(trim($legend->textContent))->not->toMatch('/^\d+[.)]/');
|
|
}
|
|
$response->assertSee('Saluran Pembelian/Sumber Pasokan')->assertSee('Saluran Penjualan Utama')
|
|
->assertSee('Sifat Hubungan dengan Petani Pemasok')->assertSee('Harga Beli BUMDes dari Petani');
|
|
});
|
|
|
|
test('value chain save buttons identify the next box without a standalone page heading', function () {
|
|
$response = $this->get(route('bumdes.rantai-nilai.index', 1))->assertOk();
|
|
$dom = new DOMDocument;
|
|
$previous = libxml_use_internal_errors(true);
|
|
$dom->loadHTML($response->getContent());
|
|
libxml_clear_errors();
|
|
libxml_use_internal_errors($previous);
|
|
$xpath = new DOMXPath($dom);
|
|
expect($xpath->query('//div[contains(@class, "bumdes-page")]/h4')->length)->toBe(0);
|
|
foreach (['pembelian' => 'penjualan', 'penjualan' => 'hubungan-pemasok', 'hubungan-pemasok' => 'harga-beli', 'harga-beli' => 'sortasi', 'sortasi' => 'pengeringan', 'pengeringan' => 'fermentasi', 'fermentasi' => 'premium-harga'] as $current => $next) {
|
|
$button = $xpath->query('//div[@id="section-rantai-'.$current.'"]//button[@type="submit"]')->item(0);
|
|
expect($button->getAttribute('name'))->toBe('redirect_fragment')
|
|
->and($button->getAttribute('value'))->toBe('section-rantai-'.$next);
|
|
}
|
|
expect($xpath->query('//div[@id="section-rantai-premium-harga"]//button[@type="submit"]')->item(0)->hasAttribute('name'))->toBeFalse();
|
|
$response->assertDontSee('Memetakan peran BUMDes dalam rantai nilai komoditas, dari pembelian hasil panen hingga akses pasar.');
|
|
});
|
|
|
|
test('value chain saves advance to the next box only when the bumdes trades', function (string $fragment, string $trading) {
|
|
$url = route('bumdes.rantai-nilai.index', 1);
|
|
$this->post(route('bumdes.rantai-nilai.store', 1), ['melakukan_jual_beli' => $trading, 'melakukan_pengolahan_pascapanen' => '0', 'redirect_fragment' => $fragment])
|
|
->assertSessionHasNoErrors()->assertRedirect($url.($trading === '1' ? '#'.$fragment : ''))
|
|
->assertSessionHas('success', 'Data Rantai Nilai & Akses Pasar berhasil disimpan.');
|
|
expect(KelembagaanAktorBumdes::firstOrFail()->melakukan_jual_beli)->toBe($trading === '1');
|
|
})->with(['section-rantai-penjualan', 'section-rantai-hubungan-pemasok', 'section-rantai-harga-beli'])->with(['1', '0']);
|
|
|
|
test('value chain validation errors stay at the notification instead of advancing', function () {
|
|
$url = route('bumdes.rantai-nilai.index', 1);
|
|
$this->from($url)->post(route('bumdes.rantai-nilai.store', 1), [
|
|
'melakukan_jual_beli' => '1', 'redirect_fragment' => 'section-rantai-penjualan', 'sumber_pasokan' => ['lainnya'],
|
|
])->assertRedirect($url)->assertSessionHasErrors('sumber_pasokan_lainnya');
|
|
expect(KelembagaanAktorBumdes::count())->toBe(0);
|
|
$this->get($url)->assertOk()->assertSee('data-has-errors="1"', false);
|
|
});
|
|
|
|
test('value chain save redirects accept only known box identifiers', function () {
|
|
$this->post(route('bumdes.rantai-nilai.store', 1), ['melakukan_jual_beli' => '1', 'redirect_fragment' => 'unknown-box'])
|
|
->assertSessionHasErrors('redirect_fragment');
|
|
expect(KelembagaanAktorBumdes::count())->toBe(0);
|
|
});
|
|
|
|
test('value chain answers store multiple choices volumes and per-product prices without changing other tabs', function () {
|
|
$data = KelembagaanAktorBumdes::create(['pengurus_bumdes_id' => 1, 'status_badan_hukum' => 'tidak_tahu', 'modal_awal' => 2000000]);
|
|
$input = [
|
|
'melakukan_jual_beli' => '1', 'melakukan_pengolahan_pascapanen' => '0',
|
|
'sumber_pasokan' => ['anggota_kelompok', 'lainnya'], 'sumber_pasokan_lainnya' => 'Kebun desa',
|
|
'bentuk_produk_dibeli' => ['buah_segar', 'biji_basah'],
|
|
'volume_pembelian' => ['panen_terakhir' => '1234.125', 'tahun_lalu' => '0', 'dua_tahun_lalu' => '25.5'],
|
|
'saluran_penjualan' => ['offtaker_eksportir', 'lainnya'], 'saluran_penjualan_lainnya' => 'Hotel setempat',
|
|
'bentuk_produk_dijual' => ['biji_kering', 'terfermentasi'], 'volume_penjualan' => ['panen_terakhir' => '250.75', 'tahun_lalu' => '0'],
|
|
'hubungan_petani_pemasok' => ['bebas', 'semi_kontrak', 'lainnya'], 'hubungan_petani_pemasok_lainnya' => 'Kemitraan kebun desa',
|
|
'mekanisme_pembayaran_petani' => 'lainnya', 'mekanisme_pembayaran_lainnya' => 'Transfer saat serah terima',
|
|
'perjanjian_petani' => 'tertulis',
|
|
'harga_beli_petani' => [
|
|
'buah_segar' => ['panen_terakhir' => '12.500', 'tahun_lalu' => '10.000', 'dua_tahun_lalu' => '0'],
|
|
'biji_basah' => ['panen_terakhir' => '0'],
|
|
'biji_kering' => ['panen_terakhir' => ['invalid']],
|
|
],
|
|
'penetapan_harga_beli' => 'lainnya', 'penetapan_harga_beli_lainnya' => 'Musyawarah dengan pemasok',
|
|
'modal_awal' => '999',
|
|
];
|
|
$url = route('bumdes.rantai-nilai.index', 1);
|
|
$this->post(route('bumdes.rantai-nilai.store', 1), $input)->assertRedirect($url)->assertSessionHasNoErrors()
|
|
->assertSessionHas('success', 'Data Rantai Nilai & Akses Pasar berhasil disimpan.');
|
|
$data->refresh();
|
|
expect($data->melakukan_jual_beli)->toBeTrue()
|
|
->and($data->sumber_pasokan->map->value->all())->toBe(['anggota_kelompok', 'lainnya'])
|
|
->and($data->bentuk_produk_dibeli->map->value->all())->toBe(['buah_segar', 'biji_basah'])
|
|
->and($data->volume_pembelian)->toBe(['panen_terakhir' => '1234.125', 'tahun_lalu' => '0', 'dua_tahun_lalu' => '25.5'])
|
|
->and($data->saluran_penjualan->map->value->all())->toBe(['offtaker_eksportir', 'lainnya'])
|
|
->and($data->volume_penjualan)->toBe(['panen_terakhir' => '250.75', 'tahun_lalu' => '0'])
|
|
->and($data->harga_beli_petani)->toBe(['buah_segar' => ['panen_terakhir' => '12500', 'tahun_lalu' => '10000', 'dua_tahun_lalu' => '0'], 'biji_basah' => ['panen_terakhir' => '0']])
|
|
->and($data->hubungan_petani_pemasok->map->value->all())->toBe(['bebas', 'semi_kontrak', 'lainnya'])
|
|
->and($data->mekanisme_pembayaran_lainnya)->toBe('Transfer saat serah terima')
|
|
->and($data->penetapan_harga_beli_lainnya)->toBe('Musyawarah dengan pemasok')
|
|
->and($data->modal_awal)->toBe('2000000.00');
|
|
$this->get($url)->assertOk()->assertSee('value="12.500"', false)->assertSee('Kebun desa')->assertSee('Hotel setempat');
|
|
$this->put(route('bumdes.update', 1), $this->profileInput)->assertSessionHasNoErrors();
|
|
$this->post(route('bumdes.operasional.store', 1), ['modal_awal' => '3000000'])->assertSessionHasNoErrors();
|
|
$this->post(route('bumdes.kelembagaan-aktor.store', 1), ['status_badan_hukum' => 'tidak_tahu'])->assertSessionHasNoErrors();
|
|
expect($data->refresh()->sumber_pasokan_lainnya)->toBe('Kebun desa')
|
|
->and($data->harga_beli_petani['buah_segar']['panen_terakhir'])->toBe('12500');
|
|
});
|
|
|
|
test('not trading skips and clears the whole value chain questionnaire while preserving operational answers', function () {
|
|
$data = KelembagaanAktorBumdes::create([
|
|
'pengurus_bumdes_id' => 1, 'modal_awal' => 2000000, 'melakukan_jual_beli' => true,
|
|
'sumber_pasokan' => ['lainnya'], 'sumber_pasokan_lainnya' => 'Kebun desa',
|
|
'bentuk_produk_dibeli' => ['buah_segar'], 'harga_beli_petani' => ['buah_segar' => ['panen_terakhir' => '12500']],
|
|
]);
|
|
$this->post(route('bumdes.rantai-nilai.store', 1), [
|
|
'melakukan_jual_beli' => '0', 'melakukan_pengolahan_pascapanen' => '0', 'sumber_pasokan' => 'invalid', 'sumber_pasokan_lainnya' => ['invalid'],
|
|
'volume_pembelian' => ['invalid'], 'bentuk_produk_dibeli' => 'invalid', 'harga_beli_petani' => 'invalid',
|
|
'penetapan_harga_beli' => 'invalid',
|
|
])->assertSessionHasNoErrors();
|
|
expect($data->refresh()->melakukan_jual_beli)->toBeFalse()
|
|
->and($data->sumber_pasokan->all())->toBe([])
|
|
->and($data->sumber_pasokan_lainnya)->toBeNull()
|
|
->and($data->harga_beli_petani)->toBe([])
|
|
->and($data->modal_awal)->toBe('2000000.00');
|
|
$this->get(route('bumdes.rantai-nilai.index', 1))->assertOk();
|
|
});
|
|
|
|
test('not yet selling clears product and sales volumes and ignores their invalid details', function () {
|
|
$this->post(route('bumdes.rantai-nilai.store', 1), [
|
|
'melakukan_jual_beli' => '1', 'melakukan_pengolahan_pascapanen' => '0', 'saluran_penjualan' => ['belum_menjual'],
|
|
'bentuk_produk_dijual' => 'invalid', 'volume_penjualan' => ['panen_terakhir' => ['invalid']],
|
|
])->assertSessionHasNoErrors();
|
|
$data = KelembagaanAktorBumdes::firstOrFail();
|
|
expect($data->bentuk_produk_dijual->all())->toBe([])->and($data->volume_penjualan)->toBe([]);
|
|
});
|
|
|
|
test('value chain validates applicability other choices quantities and price matrix data', function (array $input, array $errors) {
|
|
$url = route('bumdes.rantai-nilai.index', 1);
|
|
$this->from($url)->post(route('bumdes.rantai-nilai.store', 1), $input)->assertRedirect($url)->assertSessionHasErrors($errors);
|
|
expect(KelembagaanAktorBumdes::count())->toBe(0);
|
|
$this->get($url)->assertOk()->assertSee('data-has-errors="1"', false);
|
|
})->with([
|
|
[[], ['melakukan_jual_beli']],
|
|
[['melakukan_jual_beli' => 'invalid'], ['melakukan_jual_beli']],
|
|
[['melakukan_jual_beli' => ['invalid']], ['melakukan_jual_beli']],
|
|
[['melakukan_jual_beli' => '1', 'sumber_pasokan' => ['lainnya']], ['sumber_pasokan_lainnya']],
|
|
[['melakukan_jual_beli' => '1', 'saluran_penjualan' => ['lainnya']], ['saluran_penjualan_lainnya']],
|
|
[['melakukan_jual_beli' => '1', 'hubungan_petani_pemasok' => ['lainnya']], ['hubungan_petani_pemasok_lainnya']],
|
|
[['melakukan_jual_beli' => '1', 'mekanisme_pembayaran_petani' => 'lainnya'], ['mekanisme_pembayaran_lainnya']],
|
|
[['melakukan_jual_beli' => '1', 'penetapan_harga_beli' => 'lainnya'], ['penetapan_harga_beli_lainnya']],
|
|
[['melakukan_jual_beli' => '1', 'sumber_pasokan' => ['lainnya'], 'sumber_pasokan_lainnya' => ['invalid']], ['sumber_pasokan_lainnya']],
|
|
[['melakukan_jual_beli' => '1', 'saluran_penjualan' => ['belum_menjual', 'pasar_lokal']], ['saluran_penjualan']],
|
|
[['melakukan_jual_beli' => '1', 'sumber_pasokan' => ['anggota_kelompok', 'anggota_kelompok']], ['sumber_pasokan.0']],
|
|
[['melakukan_jual_beli' => '1', 'hubungan_petani_pemasok' => ['invalid']], ['hubungan_petani_pemasok.0']],
|
|
[['melakukan_jual_beli' => '1', 'perjanjian_petani' => 'invalid'], ['perjanjian_petani']],
|
|
[['melakukan_jual_beli' => '1', 'bentuk_produk_dibeli' => ['invalid']], ['bentuk_produk_dibeli.0']],
|
|
[['melakukan_jual_beli' => '1', 'volume_pembelian' => ['panen_terakhir' => '-1']], ['volume_pembelian.panen_terakhir']],
|
|
[['melakukan_jual_beli' => '1', 'volume_pembelian' => ['panen_terakhir' => '1.2345']], ['volume_pembelian.panen_terakhir']],
|
|
[['melakukan_jual_beli' => '1', 'volume_penjualan' => ['tahun_lalu' => 'abc']], ['volume_penjualan.tahun_lalu']],
|
|
[['melakukan_jual_beli' => '1', 'volume_pembelian' => ['invalid' => 1]], ['volume_pembelian']],
|
|
[['melakukan_jual_beli' => '1', 'bentuk_produk_dibeli' => ['buah_segar'], 'harga_beli_petani' => ['buah_segar' => ['panen_terakhir' => '-1']]], ['harga_beli_petani.buah_segar.panen_terakhir']],
|
|
[['melakukan_jual_beli' => '1', 'bentuk_produk_dibeli' => ['buah_segar'], 'harga_beli_petani' => ['buah_segar' => ['panen_terakhir' => '1.5']]], ['harga_beli_petani.buah_segar.panen_terakhir']],
|
|
[['melakukan_jual_beli' => '1', 'bentuk_produk_dibeli' => ['buah_segar'], 'harga_beli_petani' => ['buah_segar' => ['panen_terakhir' => ['invalid']]]], ['harga_beli_petani.buah_segar.panen_terakhir']],
|
|
[['melakukan_jual_beli' => '1', 'harga_beli_petani' => ['invalid' => []]], ['harga_beli_petani']],
|
|
[['melakukan_jual_beli' => '1', 'bentuk_produk_dibeli' => ['buah_segar'], 'harga_beli_petani' => ['buah_segar' => ['invalid' => '1000']]], ['harga_beli_petani.buah_segar']],
|
|
]);
|
|
|
|
test('value chain skips inactive other descriptions and unselected product prices', function () {
|
|
$this->post(route('bumdes.rantai-nilai.store', 1), [
|
|
'melakukan_jual_beli' => '1', 'melakukan_pengolahan_pascapanen' => '0', 'bentuk_produk_dibeli' => ['buah_segar'],
|
|
'harga_beli_petani' => ['buah_segar' => ['panen_terakhir' => '0'], 'biji_basah' => 'invalid'],
|
|
'sumber_pasokan' => ['anggota_kelompok'], 'sumber_pasokan_lainnya' => ['invalid'],
|
|
'mekanisme_pembayaran_petani' => 'tunai', 'mekanisme_pembayaran_lainnya' => ['invalid'],
|
|
'penetapan_harga_beli' => 'pasar_setempat', 'penetapan_harga_beli_lainnya' => ['invalid'],
|
|
])->assertSessionHasNoErrors();
|
|
$data = KelembagaanAktorBumdes::firstOrFail();
|
|
expect($data->harga_beli_petani)->toBe(['buah_segar' => ['panen_terakhir' => '0']])
|
|
->and($data->sumber_pasokan_lainnya)->toBeNull()->and($data->mekanisme_pembayaran_lainnya)->toBeNull();
|
|
});
|
|
|
|
test('value chain is locked for incomplete profiles and workshop roles', function () {
|
|
DB::table('bumdes_pengurus')->where('id', 1)->update(['alamat' => null]);
|
|
$this->get(route('bumdes.rantai-nilai.index', 1))->assertForbidden();
|
|
$this->post(route('bumdes.rantai-nilai.store', 1), ['melakukan_jual_beli' => '0'])->assertForbidden();
|
|
DB::table('bumdes_pengurus')->where('id', 1)->update(['alamat' => 'Desa A']);
|
|
auth()->user()->role = 'fasilitator';
|
|
$this->get(route('bumdes.rantai-nilai.index', 1))->assertForbidden();
|
|
});
|
|
|
|
test('value chain migration rolls back without changing earlier operational data', function () {
|
|
$data = KelembagaanAktorBumdes::create(['pengurus_bumdes_id' => 1, 'modal_awal' => 2000000, 'status_pembukuan' => 'rutin_tertib']);
|
|
$migration = require database_path('migrations/2026_11_04_000003_add_rantai_nilai_to_bumdes_kelembagaan_aktor_table.php');
|
|
$migration->down();
|
|
expect(Schema::hasColumn('bumdes_kelembagaan_aktor', 'sumber_pasokan'))->toBeFalse()
|
|
->and(DB::table('bumdes_kelembagaan_aktor')->where('id', $data->id)->value('status_pembukuan'))->toBe('rutin_tertib');
|
|
$migration->up();
|
|
expect(Schema::hasColumn('bumdes_kelembagaan_aktor', 'sumber_pasokan'))->toBeTrue();
|
|
});
|
|
|
|
test('postharvest opportunity cards show all choices and start with inactive follow-up fields', function () {
|
|
$response = $this->get(route('bumdes.rantai-nilai.index', 1))->assertOk();
|
|
$dom = new DOMDocument;
|
|
$previous = libxml_use_internal_errors(true);
|
|
$dom->loadHTML($response->getContent());
|
|
libxml_clear_errors();
|
|
libxml_use_internal_errors($previous);
|
|
$xpath = new DOMXPath($dom);
|
|
expect($xpath->query('//div[@data-postharvest-card]')->length)->toBe(4)
|
|
->and($xpath->query('//fieldset[@data-postharvest-section and @disabled]')->length)->toBe(4)
|
|
->and($xpath->query('//input[@name="status_sortasi"]')->length)->toBe(3)
|
|
->and($xpath->query('//input[@name="status_pengeringan"]')->length)->toBe(3)
|
|
->and($xpath->query('//input[@name="status_fermentasi"]')->length)->toBe(4)
|
|
->and($xpath->query('//input[@name="model_fermentasi[]"]')->length)->toBe(4);
|
|
foreach (['kriteria_sortasi', 'cara_tempat_pengeringan', 'proses_fermentasi'] as $field) {
|
|
$input = $xpath->query('//textarea[@name="'.$field.'"]')->item(0);
|
|
expect($input->hasAttribute('disabled'))->toBeTrue()->and($input->hasAttribute('required'))->toBeFalse();
|
|
}
|
|
$response->assertSee('Peluang Nilai Tambah')->assertSee('Sortasi & Pemilihan Kualitas', false)
|
|
->assertSee('Praktik Pengeringan')->assertSee('Praktik Fermentasi')->assertSee('Besaran Premium Harga');
|
|
});
|
|
|
|
test('postharvest activities save independently of trading and retain existing operational answers', function (string $trading) {
|
|
$data = KelembagaanAktorBumdes::create(['pengurus_bumdes_id' => 1, 'modal_awal' => 2000000, 'status_badan_hukum' => 'tidak_tahu']);
|
|
$input = [
|
|
'melakukan_jual_beli' => $trading, 'melakukan_pengolahan_pascapanen' => '1',
|
|
'status_sortasi' => 'rutin_standar', 'kriteria_sortasi' => 'Ukuran seragam dan bebas cacat',
|
|
'status_pengeringan' => 'kadang', 'cara_tempat_pengeringan' => 'Dijemur di atas para-para beratap',
|
|
'status_fermentasi' => 'pernah_mencoba', 'model_fermentasi' => ['individual', 'unit_bumdes', 'campuran'],
|
|
'proses_fermentasi' => 'Fermentasi terkontrol dalam kotak kayu', 'premium_harga_pascapanen' => '2.500',
|
|
'redirect_fragment' => 'section-rantai-pengeringan',
|
|
];
|
|
$url = route('bumdes.rantai-nilai.index', 1);
|
|
$this->post(route('bumdes.rantai-nilai.store', 1), $input)->assertSessionHasNoErrors()->assertRedirect($url.'#section-rantai-pengeringan');
|
|
$data->refresh();
|
|
expect($data->melakukan_pengolahan_pascapanen)->toBeTrue()
|
|
->and($data->status_sortasi)->toBe(StatusSortasiBumdes::RUTIN_STANDAR)
|
|
->and($data->kriteria_sortasi)->toBe('Ukuran seragam dan bebas cacat')
|
|
->and($data->status_pengeringan->value)->toBe('kadang')
|
|
->and($data->status_fermentasi->value)->toBe('pernah_mencoba')
|
|
->and($data->model_fermentasi->map->value->all())->toBe(['individual', 'unit_bumdes', 'campuran'])
|
|
->and($data->proses_fermentasi)->toBe('Fermentasi terkontrol dalam kotak kayu')
|
|
->and($data->premium_harga_pascapanen)->toBe('2500.00')
|
|
->and($data->modal_awal)->toBe('2000000.00')
|
|
->and($data->status_badan_hukum->value)->toBe('tidak_tahu');
|
|
$this->get($url)->assertOk()->assertSee('value="2.500"', false)->assertSee('Ukuran seragam dan bebas cacat');
|
|
$this->put(route('bumdes.update', 1), $this->profileInput)->assertSessionHasNoErrors();
|
|
$this->post(route('bumdes.operasional.store', 1), ['modal_awal' => '3000000'])->assertSessionHasNoErrors();
|
|
expect($data->refresh()->premium_harga_pascapanen)->toBe('2500.00')->and($data->kriteria_sortasi)->toBe('Ukuran seragam dan bebas cacat');
|
|
})->with(['1', '0']);
|
|
|
|
test('inactive postharvest follow-ups ignore malformed values and clear old details', function () {
|
|
$data = KelembagaanAktorBumdes::create([
|
|
'pengurus_bumdes_id' => 1, 'melakukan_jual_beli' => true, 'melakukan_pengolahan_pascapanen' => true,
|
|
'status_sortasi' => 'rutin_standar', 'kriteria_sortasi' => 'Ukuran', 'status_pengeringan' => 'rutin', 'cara_tempat_pengeringan' => 'Jemur',
|
|
'status_fermentasi' => 'rutin', 'model_fermentasi' => ['unit_bumdes'], 'proses_fermentasi' => 'Kotak kayu',
|
|
]);
|
|
$this->post(route('bumdes.rantai-nilai.store', 1), [
|
|
'melakukan_jual_beli' => '1', 'melakukan_pengolahan_pascapanen' => '1',
|
|
'status_sortasi' => 'belum_pernah', 'kriteria_sortasi' => ['invalid'],
|
|
'status_pengeringan' => 'tidak_pernah', 'cara_tempat_pengeringan' => ['invalid'],
|
|
'status_fermentasi' => 'direncanakan', 'model_fermentasi' => 'invalid', 'proses_fermentasi' => ['invalid'],
|
|
'premium_harga_pascapanen' => '0',
|
|
])->assertSessionHasNoErrors();
|
|
expect($data->refresh()->kriteria_sortasi)->toBeNull()->and($data->cara_tempat_pengeringan)->toBeNull()
|
|
->and($data->model_fermentasi->all())->toBe([])->and($data->proses_fermentasi)->toBeNull()
|
|
->and($data->premium_harga_pascapanen)->toBe('0.00');
|
|
});
|
|
|
|
test('no postharvest processing clears its questionnaire without clearing buying and selling', function () {
|
|
$data = KelembagaanAktorBumdes::create([
|
|
'pengurus_bumdes_id' => 1, 'melakukan_pengolahan_pascapanen' => true, 'status_sortasi' => 'rutin_standar',
|
|
'kriteria_sortasi' => 'Ukuran', 'model_fermentasi' => ['unit_bumdes'], 'premium_harga_pascapanen' => 2500,
|
|
]);
|
|
$this->post(route('bumdes.rantai-nilai.store', 1), [
|
|
'melakukan_jual_beli' => '1', 'sumber_pasokan' => ['anggota_kelompok'],
|
|
'melakukan_pengolahan_pascapanen' => '0', 'status_sortasi' => 'invalid', 'kriteria_sortasi' => ['invalid'],
|
|
'model_fermentasi' => 'invalid', 'premium_harga_pascapanen' => ['invalid'], 'redirect_fragment' => 'section-rantai-pengeringan',
|
|
])->assertSessionHasNoErrors()->assertRedirect(route('bumdes.rantai-nilai.index', 1));
|
|
expect($data->refresh()->melakukan_pengolahan_pascapanen)->toBeFalse()
|
|
->and($data->status_sortasi)->toBeNull()->and($data->kriteria_sortasi)->toBeNull()
|
|
->and($data->model_fermentasi->all())->toBe([])->and($data->premium_harga_pascapanen)->toBeNull()
|
|
->and($data->sumber_pasokan->map->value->all())->toBe(['anggota_kelompok']);
|
|
});
|
|
|
|
test('postharvest details validate active answers before any data is saved', function (array $input, array $errors) {
|
|
$url = route('bumdes.rantai-nilai.index', 1);
|
|
$this->from($url)->post(route('bumdes.rantai-nilai.store', 1), ['melakukan_jual_beli' => '0', 'melakukan_pengolahan_pascapanen' => '1', ...$input])
|
|
->assertRedirect($url)->assertSessionHasErrors($errors);
|
|
expect(KelembagaanAktorBumdes::count())->toBe(0);
|
|
$this->get($url)->assertOk()->assertSee('data-has-errors="1"', false);
|
|
})->with([
|
|
[['melakukan_pengolahan_pascapanen' => ['invalid']], ['melakukan_pengolahan_pascapanen']],
|
|
[['status_sortasi' => 'rutin_standar'], ['kriteria_sortasi']],
|
|
[['status_sortasi' => 'tidak_konsisten'], ['kriteria_sortasi']],
|
|
[['status_sortasi' => 'invalid'], ['status_sortasi']],
|
|
[['status_pengeringan' => 'rutin'], ['cara_tempat_pengeringan']],
|
|
[['status_pengeringan' => 'kadang'], ['cara_tempat_pengeringan']],
|
|
[['status_pengeringan' => 'invalid'], ['status_pengeringan']],
|
|
[['status_fermentasi' => 'rutin'], ['model_fermentasi', 'proses_fermentasi']],
|
|
[['status_fermentasi' => 'pernah_mencoba'], ['model_fermentasi', 'proses_fermentasi']],
|
|
[['status_fermentasi' => 'invalid'], ['status_fermentasi']],
|
|
[['status_fermentasi' => 'rutin', 'model_fermentasi' => ['invalid'], 'proses_fermentasi' => 'Kotak kayu'], ['model_fermentasi.0']],
|
|
[['status_fermentasi' => 'rutin', 'model_fermentasi' => ['unit_bumdes', 'unit_bumdes'], 'proses_fermentasi' => 'Kotak kayu'], ['model_fermentasi.0']],
|
|
[['status_sortasi' => 'rutin_standar', 'kriteria_sortasi' => ['invalid']], ['kriteria_sortasi']],
|
|
[['premium_harga_pascapanen' => '-1'], ['premium_harga_pascapanen']],
|
|
[['premium_harga_pascapanen' => '1.5'], ['premium_harga_pascapanen']],
|
|
[['premium_harga_pascapanen' => '10000000000000000'], ['premium_harga_pascapanen']],
|
|
[['premium_harga_pascapanen' => ['invalid']], ['premium_harga_pascapanen']],
|
|
]);
|
|
|
|
test('postharvest save targets follow processing eligibility', function (string $fragment, string $processing) {
|
|
$url = route('bumdes.rantai-nilai.index', 1);
|
|
$this->post(route('bumdes.rantai-nilai.store', 1), [
|
|
'melakukan_jual_beli' => '0', 'melakukan_pengolahan_pascapanen' => $processing, 'redirect_fragment' => $fragment,
|
|
])->assertSessionHasNoErrors()->assertRedirect($url.($processing === '1' ? '#'.$fragment : ''));
|
|
})->with(['section-rantai-pengeringan', 'section-rantai-fermentasi', 'section-rantai-premium-harga'])->with(['1', '0']);
|
|
|
|
test('postharvest migration rolls back only the newly added opportunity fields', function () {
|
|
$data = KelembagaanAktorBumdes::create(['pengurus_bumdes_id' => 1, 'melakukan_jual_beli' => true, 'sumber_pasokan' => ['campuran']]);
|
|
$migration = require database_path('migrations/2026_11_05_000001_add_peluang_nilai_tambah_to_bumdes_kelembagaan_aktor_table.php');
|
|
$migration->down();
|
|
expect(Schema::hasColumn('bumdes_kelembagaan_aktor', 'status_sortasi'))->toBeFalse()
|
|
->and(DB::table('bumdes_kelembagaan_aktor')->where('id', $data->id)->value('sumber_pasokan'))->toBe('["campuran"]');
|
|
$migration->up();
|
|
expect(Schema::hasColumn('bumdes_kelembagaan_aktor', 'status_sortasi'))->toBeTrue();
|
|
});
|
|
|
|
test('postharvest applicability requires an explicit answer and displays its error above the form', function () {
|
|
$url = route('bumdes.rantai-nilai.index', 1);
|
|
$this->from($url)->post(route('bumdes.rantai-nilai.store', 1), ['melakukan_jual_beli' => '1'])
|
|
->assertRedirect($url)->assertSessionHasErrors(['melakukan_pengolahan_pascapanen' => 'Kegiatan pengolahan pascapanen wajib dipilih.']);
|
|
expect(KelembagaanAktorBumdes::count())->toBe(0);
|
|
$response = $this->get($url)->assertOk()->assertSee('Kegiatan pengolahan pascapanen wajib dipilih.')
|
|
->assertSee('data-has-errors="1"', false);
|
|
$dom = new DOMDocument;
|
|
$previous = libxml_use_internal_errors(true);
|
|
$dom->loadHTML($response->getContent());
|
|
libxml_clear_errors();
|
|
libxml_use_internal_errors($previous);
|
|
$xpath = new DOMXPath($dom);
|
|
expect($xpath->query('//input[@name="melakukan_pengolahan_pascapanen" and @required]')->length)->toBe(2)
|
|
->and($xpath->query('//legend[contains(., "Kegiatan Pengolahan Pascapanen")]/span[contains(@class, "text-danger")]')->length)->toBe(1);
|
|
});
|
|
|
|
test('postharvest applicability accepts either yes or no as a required answer', function (string $answer) {
|
|
$this->post(route('bumdes.rantai-nilai.store', 1), ['melakukan_jual_beli' => '0', 'melakukan_pengolahan_pascapanen' => $answer])
|
|
->assertSessionHasNoErrors();
|
|
expect(KelembagaanAktorBumdes::firstOrFail()->melakukan_pengolahan_pascapanen)->toBe($answer === '1');
|
|
})->with(['0', '1']);
|
|
|
|
test('reported bumdes value chain URL resolves the parent model for both display and save', function () {
|
|
$url = '/bumdes/2/rantai-nilai';
|
|
$this->get($url)->assertOk()->assertSee('rantaiNilaiBumdesForm', false);
|
|
$this->post($url, ['melakukan_jual_beli' => '0', 'melakukan_pengolahan_pascapanen' => '0'])
|
|
->assertRedirect(route('bumdes.rantai-nilai.index', 2))->assertSessionHasNoErrors();
|
|
$data = KelembagaanAktorBumdes::where('pengurus_bumdes_id', 2)->firstOrFail();
|
|
expect($data->melakukan_jual_beli)->toBeFalse()->and($data->melakukan_pengolahan_pascapanen)->toBeFalse();
|
|
});
|
|
|
|
test('shared bumdes profile card presents institutional data across all profile tabs', function () {
|
|
KelembagaanAktorBumdes::create([
|
|
'pengurus_bumdes_id' => 1, 'status_keaktifan' => 'aktif_penuh', 'status_badan_hukum' => 'proses_pendaftaran',
|
|
'unit_usaha' => ['simpan_pinjam', 'perdagangan_toko_desa', 'jasa_lainnya'], 'unit_usaha_jasa_lainnya' => 'Pengiriman hasil panen',
|
|
'status_unit_komoditas' => 'ya', 'nama_unit_komoditas' => 'Unit Pengolahan Kopi',
|
|
'peran_rantai_nilai' => 'mengolah_sendiri', 'cakupan_layanan' => 'desa_sendiri_dan_lain',
|
|
'jumlah_petani_desa_sendiri' => 120, 'jumlah_petani_desa_lain' => 35, 'desa_lain_dilayani' => 'Desa B, Desa C',
|
|
]);
|
|
DB::table('bumdes_pengurus')->where('id', 1)->update(['nomor_telepon' => '081234567890']);
|
|
foreach (['bumdes.edit', 'bumdes.kelembagaan-aktor.index', 'bumdes.operasional.index', 'bumdes.rantai-nilai.index'] as $route) {
|
|
$response = $this->get(route($route, 1))->assertOk();
|
|
$dom = new DOMDocument;
|
|
$previous = libxml_use_internal_errors(true);
|
|
$dom->loadHTML($response->getContent());
|
|
libxml_clear_errors();
|
|
libxml_use_internal_errors($previous);
|
|
$xpath = new DOMXPath($dom);
|
|
$card = $xpath->query('//section[@id="bumdes-profile-summary"]')->item(0);
|
|
expect($card)->not->toBeNull();
|
|
expect($card->textContent)->toContain('Desa A', 'Aktif beroperasi penuh', 'Proses pendaftaran', 'Alpha', '081234567890', 'Pengiriman hasil panen')
|
|
->not->toContain('Jenis Kelamin', 'Alamat Domisili', 'Petani Terlayani', 'Jangkauan Layanan', 'Peran Rantai Nilai', 'Unit Komoditas', 'Unit Pengolahan Kopi', 'Desa B, Desa C');
|
|
expect($xpath->query('.//*[@data-bumdes-farmer-total]', $card)->length)->toBe(0);
|
|
expect($xpath->query('.//button[@data-bs-target="#bumdes-summary-extra-units"]', $card)->length)->toBe(1);
|
|
expect($xpath->query('//a[contains(@class, "nav-link") and @href="'.route('bumdes.rantai-nilai.index', 1).'"]')->length)->toBe(1);
|
|
}
|
|
});
|
|
|
|
test('bumdes summary uses the latest profile or tab update and handles a new unsaved profile', function () {
|
|
KelembagaanAktorBumdes::create(['pengurus_bumdes_id' => 1]);
|
|
DB::table('bumdes_pengurus')->where('id', 1)->update(['updated_at' => '2026-01-01 08:00:00']);
|
|
DB::table('bumdes_kelembagaan_aktor')->where('pengurus_bumdes_id', 1)->update(['updated_at' => '2026-02-02 09:00:00']);
|
|
$summary = ($this->bumdesAction)('edit', null, '1')->getData()['ringkasanProfil'];
|
|
expect($summary['diperbarui']->toDateTimeString())->toBe('2026-02-02 09:00:00')
|
|
->and($summary['keaktifan']['label'])->toBe('Belum terisi')
|
|
->and($summary['unit_usaha'])->toBe([]);
|
|
DB::table('bumdes_pengurus')->where('id', 1)->update(['updated_at' => '2026-03-03 10:00:00']);
|
|
expect(($this->bumdesAction)('edit', null, '1')->getData()['ringkasanProfil']['diperbarui']->toDateTimeString())->toBe('2026-03-03 10:00:00');
|
|
$view = ($this->bumdesAction)('create');
|
|
$summary = $view->getData()['ringkasanProfil'];
|
|
expect($summary['judul'])->toBe('Profil BUMDes Baru')->and($summary['unit_usaha'])->toBe([])
|
|
->and($summary['responden'])->toBe('Belum terisi')->and($summary['diperbarui'])->toBeNull();
|
|
expect($view->render())->toContain('Belum tersimpan');
|
|
});
|