d2d.emegrendeles.hu/tests/Feature/PricelistRichTextTest.php
E98Developer 3cfaa6c9d5 FIX EV3-357 rich text cellák kezelése az árlista beolvasásban
A d2d-n a megrendelő fájljának megnyitásakor a sorok táblája eltört:
TextColumn::getDescriptionBelow(): Return value must be of type
Htmlable|string|null, array returned.

Nem UI-hiba volt, hanem adatvesztés. Ha egy cellán belül több formázású
szövegrész van (pl. a terméknév egy része félkövér), a PhpSpreadsheet
getValue()-ja RichText objektumot ad vissza. A json_encode ezt ÜRESRE
serializálja (a szövegrészek protected propertyben vannak), így a payloadba
{} kerül, ami visszaolvasva üres tömb - a cella tartalma nyomtalanul eltűnt,
a felület pedig tömböt kapott string helyett.

- normalizeCellValue(): a cella értéke a payloadba kerülés ELŐTT skalárrá
  alakul (RichText -> getPlainText, dátum -> szöveg, tömb -> összefűzött
  szöveg, __toString-es objektum -> string)
- LinesRelationManager::payloadText(): a MÁR ELMENTETT rekordokban maradt
  tömbök nem törhetik el a nézetet - a beolvasási javítás azokat
  visszamenőleg nem gyógyítja meg
- isBlankPayloadValue(): a kötelező mező ellenőrzése is_string() alapú volt,
  ezért az üres tömböt KITÖLTÖTTNEK látta - egy elveszett terméknév némán
  átment a validáláson. Ugyanez a védelem a Hooreyca-blokk vizsgálatában és a
  végrehajtás mezőkonverziójában is (utóbbi különben szó szerint "Array"
  néven hozta volna létre a terméket).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 13:51:11 +02:00

109 lines
4.2 KiB
PHP

<?php
use App\Enums\PricelistFileLineStatusEnum;
use App\Enums\PricelistFileStatusEnum;
use App\Filament\Resources\PricelistFiles\RelationManagers\LinesRelationManager;
use App\Filament\Resources\PricelistFiles\Pages\ViewPricelistFile;
use App\Models\PricelistFile;
use App\Models\PricelistFileLine;
use App\Models\Role;
use App\Models\Supplier;
use App\Models\User;
use App\Services\PriceListService;
use App\Services\PricelistFileProcessService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use Tests\TestCase;
uses(TestCase::class, RefreshDatabase::class);
function invokeProtected(string $method, mixed ...$args): mixed
{
$service = app(PricelistFileProcessService::class);
$reflection = new ReflectionMethod($service, $method);
$reflection->setAccessible(true);
return $reflection->invoke($service, ...$args);
}
/**
* Formázott (rich text) cella: a terméknév egy része félkövér. A PhpSpreadsheet ilyenkor
* nem stringet, hanem RichText objektumot ad vissza a getValue()-ból.
*/
function richTextCellValue(): RichText
{
$richText = new RichText();
$richText->createText('Trappista sajt ');
$richText->createTextRun('PRÉMIUM')->getFont()->setBold(true);
return $richText;
}
test('a rich text cella értéke sima szövegként normalizálódik', function () {
// json_encode a nyers objektumot üresre serializálná (a szövegrészek protected
// propertyben vannak), így a payloadba üres tömb kerülne és a szöveg elveszne.
expect(json_encode(['x' => richTextCellValue()]))->toBe('{"x":{}}');
expect(invokeProtected('normalizeCellValue', richTextCellValue()))
->toBe('Trappista sajt PRÉMIUM');
});
test('a normalizálás a skalárokat változatlanul hagyja', function () {
expect(invokeProtected('normalizeCellValue', 'Sajt'))->toBe('Sajt')
->and(invokeProtected('normalizeCellValue', 1250.5))->toBe(1250.5)
->and(invokeProtected('normalizeCellValue', null))->toBeNull();
});
test('a dátum cella szöveggé alakul, nem tömbbé', function () {
expect(invokeProtected('normalizeCellValue', new DateTimeImmutable('2026-08-24 10:30:00')))
->toBe('2026-08-24 10:30:00');
});
test('az üres tömb hiányzó kötelező értéknek számít', function () {
// A régi rekordokban a rich text cellák üres tömbként landoltak. A korábbi,
// is_string() alapú vizsgálat ezeket KITÖLTÖTTNEK látta, így egy elveszett
// terméknév némán átment a kötelező mező ellenőrzésén.
expect(invokeProtected('isBlankPayloadValue', []))->toBeTrue()
->and(invokeProtected('isBlankPayloadValue', null))->toBeTrue()
->and(invokeProtected('isBlankPayloadValue', ' '))->toBeTrue()
->and(invokeProtected('isBlankPayloadValue', 'Sajt'))->toBeFalse()
->and(invokeProtected('isBlankPayloadValue', 0))->toBeFalse();
});
test('a már elmentett tömbös payload nem töri el a sorok táblát', function () {
$supplier = Supplier::factory()->create();
$file = PricelistFile::create([
'filename' => 'arlista.xlsx',
'supplier_id' => $supplier->id,
'available_date' => now()->addWeek()->toDateString(),
'status' => PricelistFileStatusEnum::waiting_for_approval,
]);
// Pontosan az az állapot, ami a d2d-n a nézetet eltörte:
// TextColumn::getDescriptionBelow(): Return value must be of type
// Htmlable|string|null, array returned
PricelistFileLine::create([
'pricelist_file_id' => $file->id,
'row_number' => 5,
'status' => PricelistFileLineStatusEnum::error,
'payload' => [
PriceListService::EXPECTED_HEADERS[0] => 'SKU-1',
PriceListService::EXPECTED_HEADERS[4] => [],
],
'validation_messages' => ['name' => 'Hiányzó kötelező mező'],
]);
$user = User::factory()->create();
$user->addRole(Role::create(['name' => 'admin', 'display_name' => 'Admin']));
$this->actingAs($user);
Livewire::test(LinesRelationManager::class, [
'ownerRecord' => $file,
'pageClass' => ViewPricelistFile::class,
])
->assertOk()
->assertSee('SKU-1');
});