isEmpty())->toBeTrue(); expect($data->toArray())->toBe([]); }); it('can be created from array', function () { $fields = ['title' => 'Test', 'content' => 'Content']; $data = DocumentData::fromArray($fields); expect($data->toArray())->toBe($fields); expect($data->isEmpty())->toBeFalse(); }); it('can add fields immutably', function () { $data = DocumentData::empty(); $newData = $data->with('title', 'Test Title'); expect($data->isEmpty())->toBeTrue(); // Original unchanged expect($newData->get('title'))->toBe('Test Title'); expect($newData->has('title'))->toBeTrue(); }); it('can remove fields immutably', function () { $data = DocumentData::fromArray(['title' => 'Test', 'content' => 'Content']); $newData = $data->without('title'); expect($data->has('title'))->toBeTrue(); // Original unchanged expect($newData->has('title'))->toBeFalse(); expect($newData->has('content'))->toBeTrue(); }); it('gets field values with defaults', function () { $data = DocumentData::fromArray(['title' => 'Test']); expect($data->get('title'))->toBe('Test'); expect($data->get('missing'))->toBeNull(); expect($data->get('missing', 'default'))->toBe('default'); }); it('extracts text fields only', function () { $data = DocumentData::fromArray([ 'title' => 'Text Title', 'content' => 'Text Content', 'price' => 123.45, 'active' => true, 'empty_text' => '', 'whitespace' => ' ', ]); $textFields = $data->getTextFields(); expect($textFields)->toBe([ 'title' => 'Text Title', 'content' => 'Text Content', ]); }); it('extracts facet fields only', function () { $data = DocumentData::fromArray([ 'title' => 'Text Title', 'price' => 123.45, 'active' => true, 'count' => 42, 'nested' => ['key' => 'value'], ]); $facetFields = $data->getFacetFields(); expect($facetFields)->toBe([ 'title' => 'Text Title', 'price' => 123.45, 'active' => true, 'count' => 42, ]); }); it('validates field keys', function () { expect(fn () => DocumentData::fromArray(['' => 'value']))->toThrow(InvalidArgumentException::class); expect(fn () => DocumentData::fromArray([' ' => 'value']))->toThrow(InvalidArgumentException::class); expect(fn () => DocumentData::fromArray([123 => 'value']))->toThrow(InvalidArgumentException::class); }); it('validates field key length', function () { $longKey = str_repeat('a', 256); expect(fn () => DocumentData::fromArray([$longKey => 'value']))->toThrow(InvalidArgumentException::class); }); it('validates field key format', function () { expect(fn () => DocumentData::fromArray(['1invalid' => 'value']))->toThrow(InvalidArgumentException::class); expect(fn () => DocumentData::fromArray(['invalid-key' => 'value']))->toThrow(InvalidArgumentException::class); expect(fn () => DocumentData::fromArray(['invalid key' => 'value']))->toThrow(InvalidArgumentException::class); }); it('allows valid field keys', function () { expect(fn () => DocumentData::fromArray(['valid' => 'value']))->not->toThrow(InvalidArgumentException::class); expect(fn () => DocumentData::fromArray(['valid_key' => 'value']))->not->toThrow(InvalidArgumentException::class); expect(fn () => DocumentData::fromArray(['valid123' => 'value']))->not->toThrow(InvalidArgumentException::class); expect(fn () => DocumentData::fromArray(['a' => 'value']))->not->toThrow(InvalidArgumentException::class); }); it('validates field values for serializability', function () { $resource = fopen('php://memory', 'r'); expect(fn () => DocumentData::fromArray(['resource' => $resource]))->toThrow(InvalidArgumentException::class); fclose($resource); }); it('allows serializable values', function () { $data = DocumentData::fromArray([ 'string' => 'text', 'int' => 123, 'float' => 12.34, 'bool' => true, 'null' => null, 'array' => [1, 2, 3], ]); expect($data->toArray())->toHaveCount(6); }); });