diff --git a/resources/js/components/fieldtypes/Fieldtype.vue b/resources/js/components/fieldtypes/Fieldtype.vue index 66d06618729..de178069eeb 100644 --- a/resources/js/components/fieldtypes/Fieldtype.vue +++ b/resources/js/components/fieldtypes/Fieldtype.vue @@ -49,7 +49,10 @@ export default { }, isReadOnly() { - return this.readOnly || this.config.visibility === 'read_only' || false; + return this.readOnly + || this.config.visibility === 'read_only' + || this.config.visibility === 'computed' + || false; }, replicatorPreview() { diff --git a/src/Auth/Eloquent/User.php b/src/Auth/Eloquent/User.php index f0a4e586212..34a2165ca30 100644 --- a/src/Auth/Eloquent/User.php +++ b/src/Auth/Eloquent/User.php @@ -284,11 +284,6 @@ public function get($key, $default = null) return is_null($value) ? $default : $value; } - public function value($key) - { - return $this->get($key); - } - public function set($key, $value) { if ($key === 'password') { diff --git a/src/Auth/File/User.php b/src/Auth/File/User.php index 74d2792cba3..7157860f09f 100644 --- a/src/Auth/File/User.php +++ b/src/Auth/File/User.php @@ -57,11 +57,6 @@ public function data($data = null) return $this; } - public function value($key) - { - return $this->get($key); - } - public function id($id = null) { return $this->fluentlyGetOrSet('id')->args(func_get_args()); diff --git a/src/Auth/User.php b/src/Auth/User.php index 33ce0058246..92e6f4b07a5 100644 --- a/src/Auth/User.php +++ b/src/Auth/User.php @@ -17,6 +17,7 @@ use Statamic\Contracts\Data\Augmentable; use Statamic\Contracts\Data\Augmented; use Statamic\Contracts\GraphQL\ResolvesValues as ResolvesValuesContract; +use Statamic\Data\ContainsComputedData; use Statamic\Data\HasAugmentedInstance; use Statamic\Data\TracksQueriedColumns; use Statamic\Data\TracksQueriedRelations; @@ -42,14 +43,14 @@ abstract class User implements ArrayAccess, Arrayable { - use Authorizable, Notifiable, CanResetPassword, HasAugmentedInstance, TracksQueriedColumns, TracksQueriedRelations, HasAvatar, ResolvesValues; + use Authorizable, Notifiable, CanResetPassword, HasAugmentedInstance, TracksQueriedColumns, TracksQueriedRelations, HasAvatar, ResolvesValues, ContainsComputedData; protected $afterSaveCallbacks = []; protected $withEvents = true; - abstract public function get($key, $fallback = null); + abstract public function data($data = null); - abstract public function value($key); + abstract public function get($key, $fallback = null); abstract public function has($key); @@ -57,6 +58,15 @@ abstract public function set($key, $value); abstract public function remove($key); + public function value($key) + { + if ($this->hasComputedCallback($key)) { + return $this->getComputed($key); + } + + return $this->get($key); + } + public function reference() { return "user::{$this->id()}"; @@ -285,4 +295,9 @@ public function setPreferredLocale($locale) { return $this->setPreference('locale', $locale); } + + protected function getComputedCallbacks() + { + return Facades\User::getComputedCallbacks(); + } } diff --git a/src/Auth/UserRepository.php b/src/Auth/UserRepository.php index ec6e03934bd..706ca3e6223 100644 --- a/src/Auth/UserRepository.php +++ b/src/Auth/UserRepository.php @@ -4,12 +4,15 @@ use Statamic\Contracts\Auth\User; use Statamic\Contracts\Auth\UserRepository as RepositoryContract; +use Statamic\Data\StoresComputedFieldCallbacks; use Statamic\Events\UserBlueprintFound; use Statamic\Facades\Blueprint; use Statamic\OAuth\Provider; abstract class UserRepository implements RepositoryContract { + use StoresComputedFieldCallbacks; + public function create() { // TODO: Factory? diff --git a/src/Data/ContainsComputedData.php b/src/Data/ContainsComputedData.php new file mode 100644 index 00000000000..5ab175b9ab0 --- /dev/null +++ b/src/Data/ContainsComputedData.php @@ -0,0 +1,46 @@ +getComputedCallbacks())->map(function ($callback, $field) { + return $this->getComputed($field); + }); + } + + public function getComputed($key) + { + $instance = $this->instanceWithoutComputed(); + + $value = method_exists($this, 'get') ? $instance->get($key) : null; + + if ($this->withComputedData && $callback = $this->getComputedCallbacks()->get($key)) { + return $callback($instance, $value); + } + + return $value; + } + + protected function hasComputedCallback($key) + { + return $this->getComputedCallbacks()->has($key); + } + + protected function instanceWithoutComputed() + { + $clone = clone $this; + + $clone->withComputedData = false; + + return $clone; + } +} diff --git a/src/Data/HasOrigin.php b/src/Data/HasOrigin.php index 3829f5cecf7..70dad8301c5 100644 --- a/src/Data/HasOrigin.php +++ b/src/Data/HasOrigin.php @@ -8,14 +8,32 @@ trait HasOrigin public function values() { + $originFallbackValues = method_exists($this, 'getOriginFallbackValues') ? $this->getOriginFallbackValues() : collect(); + $originValues = $this->hasOrigin() ? $this->origin()->values() : collect(); - return $originValues->merge($this->data); + $computedData = method_exists($this, 'computedData') ? $this->computedData() : []; + + return collect() + ->merge($originFallbackValues) + ->merge($originValues) + ->merge($this->data) + ->merge($computedData); } public function value($key) { - return $this->values()->get($key); + $originFallbackValue = method_exists($this, 'getOriginFallbackValue') ? $this->getOriginFallbackValue($key) : null; + + $originValue = $this->hasOrigin() ? $this->origin()->value($key) : $originFallbackValue; + + $value = $this->has($key) ? $this->get($key) : $originValue; + + if (method_exists($this, 'hasComputedCallback') && $this->hasComputedCallback($key)) { + return $this->getComputed($key) ?? $value; + } + + return $value; } public function origin($origin = null) diff --git a/src/Data/StoresComputedFieldCallbacks.php b/src/Data/StoresComputedFieldCallbacks.php new file mode 100644 index 00000000000..52763c73d99 --- /dev/null +++ b/src/Data/StoresComputedFieldCallbacks.php @@ -0,0 +1,21 @@ +computedFieldCallbacks[$field] = $callback; + } + + public function getComputedCallbacks(): Collection + { + return collect($this->computedFieldCallbacks); + } +} diff --git a/src/Data/StoresScopedComputedFieldCallbacks.php b/src/Data/StoresScopedComputedFieldCallbacks.php new file mode 100644 index 00000000000..1dbf6cdd686 --- /dev/null +++ b/src/Data/StoresScopedComputedFieldCallbacks.php @@ -0,0 +1,24 @@ +computedFieldCallbacks["$scope.$field"] = $callback; + } + + public function getComputedCallbacks(string $scope): Collection + { + return collect($this->computedFieldCallbacks) + ->filter(fn ($_, $key) => Str::startsWith($key, "{$scope}.")) + ->keyBy(fn ($_, $key) => Str::after($key, "{$scope}.")); + } +} diff --git a/src/Entries/Entry.php b/src/Entries/Entry.php index 0b158daedac..b28a8f58043 100644 --- a/src/Entries/Entry.php +++ b/src/Entries/Entry.php @@ -14,6 +14,7 @@ use Statamic\Contracts\Entries\EntryRepository; use Statamic\Contracts\GraphQL\ResolvesValues as ResolvesValuesContract; use Statamic\Contracts\Query\ContainsQueryableValues; +use Statamic\Data\ContainsComputedData; use Statamic\Data\ContainsData; use Statamic\Data\ExistsAsFile; use Statamic\Data\HasAugmentedInstance; @@ -47,7 +48,7 @@ class Entry implements Contract, Augmentable, Responsable, Localization, Protect uri as routableUri; } - use ContainsData, ExistsAsFile, HasAugmentedInstance, FluentlyGetsAndSets, Revisable, Publishable, TracksQueriedColumns, TracksQueriedRelations, TracksLastModified; + use ContainsData, ContainsComputedData, ExistsAsFile, HasAugmentedInstance, FluentlyGetsAndSets, Revisable, Publishable, TracksQueriedColumns, TracksQueriedRelations, TracksLastModified; use ResolvesValues { resolveGqlValue as traitResolveGqlValue; } @@ -734,9 +735,14 @@ protected function getOriginByString($origin) return Facades\Entry::find($origin); } - public function values() + protected function getOriginFallbackValues() { - return $this->collection()->cascade()->merge($this->originValues()); + return $this->collection()->cascade(); + } + + protected function getOriginFallbackValue($key) + { + return $this->collection()->cascade()->get($key); } public function defaultAugmentedArrayKeys() @@ -839,4 +845,9 @@ public function getQueryableValue(string $field) return $field->fieldtype()->toQueryableValue($value); } + + protected function getComputedCallbacks() + { + return Facades\Collection::getComputedCallbacks($this->collection); + } } diff --git a/src/Facades/Collection.php b/src/Facades/Collection.php index 55388a0750c..d1a48d283cb 100644 --- a/src/Facades/Collection.php +++ b/src/Facades/Collection.php @@ -14,6 +14,8 @@ * @method static \Illuminate\Support\Collection handles() * @method static bool handleExists(string $handle) * @method static \Illuminate\Support\Collection whereStructured() + * @method static \Illuminate\Support\Collection getComputedCallbacks(string $collection) + * @method static void computed(string $collection, string $field, \Closure $callback) * * @see \Illuminate\Support\Collection * @see \Statamic\Entries\Collection diff --git a/src/Facades/User.php b/src/Facades/User.php index b39b044d1bd..fd3d4a35a29 100644 --- a/src/Facades/User.php +++ b/src/Facades/User.php @@ -16,6 +16,8 @@ * @method static void save(User $user); * @method static void delete(User $user); * @method static \Statamic\Fields\Blueprint blueprint(); + * @method static \Illuminate\Support\Collection getComputedCallbacks() + * @method static void computed(string $field, \Closure $callback) * * @see \Statamic\Contracts\Auth\UserRepository */ diff --git a/src/Fields/Fields.php b/src/Fields/Fields.php index 6f92e27183e..bb5de22adf7 100644 --- a/src/Fields/Fields.php +++ b/src/Fields/Fields.php @@ -17,6 +17,7 @@ class Fields protected $parentField; protected $filled = []; protected $withValidatableValues = false; + protected $withComputedValues = false; public function __construct($items = [], $parent = null, $parentField = null) { @@ -82,6 +83,13 @@ public function withValidatableValues() return $this; } + public function withComputedValues() + { + $this->withComputedValues = true; + + return $this; + } + public function items() { return $this->items; @@ -154,17 +162,20 @@ public function addValues(array $values) public function values() { - $values = $this->fields->mapWithKeys(function ($field) { - return [$field->handle() => $field->value()]; - }); - - if ($this->withValidatableValues) { - $values = $values->filter(function ($field, $handle) { - return in_array($handle, $this->filled); + return $this->fields + ->reject(function ($field) { + return $this->withComputedValues === false + ? $field->visibility() === 'computed' + : false; + }) + ->mapWithKeys(function ($field) { + return [$field->handle() => $field->value()]; + }) + ->filter(function ($field, $handle) { + return $this->withValidatableValues + ? in_array($handle, $this->filled) + : true; }); - } - - return $values; } public function process() @@ -178,7 +189,7 @@ public function preProcess() { return $this->newInstance()->setFields( $this->fields->map->preProcess() - ); + )->withComputedValues(); } public function preProcessValidatables() diff --git a/src/Http/Controllers/CP/Collections/EntriesController.php b/src/Http/Controllers/CP/Collections/EntriesController.php index a12b91964df..b20e9ff2391 100644 --- a/src/Http/Controllers/CP/Collections/EntriesController.php +++ b/src/Http/Controllers/CP/Collections/EntriesController.php @@ -257,7 +257,7 @@ public function create(Request $request, $collection, $site) $blueprint->ensureFieldHasConfig('author', ['visibility' => 'read_only']); } - $values = []; + $values = Entry::make()->collection($collection)->values()->all(); if ($collection->hasStructure() && $request->parent) { $values['parent'] = $request->parent; @@ -408,11 +408,11 @@ protected function extractFromFields($entry, $blueprint) { // The values should only be data merged with the origin data. // We don't want injected collection values, which $entry->values() would have given us. + $values = collect(); $target = $entry; - $values = $target->data(); - while ($target->hasOrigin()) { + while ($target) { + $values = $target->data()->merge($target->computedData())->merge($values); $target = $target->origin(); - $values = $target->data()->merge($values); } $values = $values->all(); diff --git a/src/Http/Controllers/CP/Fields/FieldsController.php b/src/Http/Controllers/CP/Fields/FieldsController.php index 4e743e084ef..0d02d356b73 100644 --- a/src/Http/Controllers/CP/Fields/FieldsController.php +++ b/src/Http/Controllers/CP/Fields/FieldsController.php @@ -135,6 +135,7 @@ protected function blueprint($blueprint) 'options' => [ 'visible' => __('Visible'), 'read_only' => __('Read Only'), + 'computed' => __('Computed'), 'hidden' => __('Hidden'), ], 'default' => 'visible', diff --git a/src/Http/Controllers/CP/Users/UsersController.php b/src/Http/Controllers/CP/Users/UsersController.php index 86e09c4ce1f..5ce1e1e993a 100644 --- a/src/Http/Controllers/CP/Users/UsersController.php +++ b/src/Http/Controllers/CP/Users/UsersController.php @@ -175,11 +175,15 @@ public function edit(Request $request, $user) $blueprint->ensureField('groups', ['visibility' => 'read_only']); } + $values = $user->data() + ->merge($user->computedData()) + ->merge(['email' => $user->email()]); + $fields = $blueprint ->removeField('password') ->removeField('password_confirmation') ->fields() - ->addValues($user->data()->merge(['email' => $user->email()])->all()) + ->addValues($values->all()) ->preProcess(); $viewData = [ diff --git a/src/Stache/Repositories/CollectionRepository.php b/src/Stache/Repositories/CollectionRepository.php index 4a64bb175d4..62ad7cda24f 100644 --- a/src/Stache/Repositories/CollectionRepository.php +++ b/src/Stache/Repositories/CollectionRepository.php @@ -5,11 +5,14 @@ use Illuminate\Support\Collection as IlluminateCollection; use Statamic\Contracts\Entries\Collection; use Statamic\Contracts\Entries\CollectionRepository as RepositoryContract; +use Statamic\Data\StoresScopedComputedFieldCallbacks; use Statamic\Facades\Blink; use Statamic\Stache\Stache; class CollectionRepository implements RepositoryContract { + use StoresScopedComputedFieldCallbacks; + protected $stache; protected $store; protected $additionalPreviewTargets = []; diff --git a/tests/Auth/UserContractTests.php b/tests/Auth/UserContractTests.php index 211f0b0e168..a6433f12743 100644 --- a/tests/Auth/UserContractTests.php +++ b/tests/Auth/UserContractTests.php @@ -82,6 +82,65 @@ public function it_gets_data() ], $this->additionalDataValues()), $this->user()->data()->all()); } + /** @test */ + public function it_gets_custom_computed_data() + { + Facades\User::computed('balance', function ($user) { + return $user->name().'\'s balance is $25 owing.'; + }); + + $user = $this->makeUser()->data(['name' => 'Han Solo']); + + $expectedData = [ + 'name' => 'Han Solo', + ]; + + $expectedComputedData = [ + 'balance' => 'Han Solo\'s balance is $25 owing.', + ]; + + $expectedValues = array_merge($expectedData, $expectedComputedData); + + $this->assertArraySubset($expectedData, $user->data()->all()); + $this->assertEquals($expectedComputedData, $user->computedData()->all()); + $this->assertEquals($expectedValues['name'], $user->value('name')); + $this->assertEquals($expectedValues['balance'], $user->value('balance')); + } + + /** @test */ + public function it_gets_empty_computed_data_by_default() + { + $this->assertEquals([], $this->user()->computedData()->all()); + } + + /** @test */ + public function it_doesnt_recursively_get_computed_data_when_callback_uses_value_method() + { + Facades\User::computed('balance', function ($user) { + return $user->value('balance') ?? $user->name().'\'s balance is $25 owing.'; + }); + + $user = $this->makeUser()->data(['name' => 'Han Solo']); + + $this->assertEquals('Han Solo\'s balance is $25 owing.', $user->value('balance')); + } + + /** @test */ + public function it_can_use_actual_data_to_compose_computed_data() + { + Facades\User::computed('nickname', function ($user, $value) { + return $value ?? 'Nameless'; + }); + + $user = $this->makeUser(); + + $this->assertEquals('Nameless', $user->value('nickname')); + + $user->data(['nickname' => 'The Hoff']); + + $this->assertEquals('The Hoff', $user->value('nickname')); + } + public function additionalDataValues() { return []; diff --git a/tests/Data/DataRepositoryTest.php b/tests/Data/DataRepositoryTest.php index 6d7230db42b..35093af8c1c 100644 --- a/tests/Data/DataRepositoryTest.php +++ b/tests/Data/DataRepositoryTest.php @@ -22,8 +22,6 @@ public function setUp(): void { parent::setUp(); - static::$functions = Mockery::mock(); - $this->data = (new DataRepository); } @@ -83,6 +81,8 @@ public function it_bails_early_when_finding_null() /** @test */ public function when_a_repository_key_isnt_provided_it_will_loop_through_repositories() { + $this->mockMethodExists(); + $this->app->instance('FooRepository', Mockery::mock('FooRepository', function ($m) { self::$functions->shouldReceive('method_exists')->with('FooRepository', 'find')->once()->andReturnTrue(); $m->shouldReceive('find')->once()->with('123')->andReturnNull(); @@ -268,7 +268,6 @@ public function findByRequestUrlNoRootSiteProvider() private function findByRequestUrlTest($requestUrl, $entryId) { - self::$functions->shouldReceive('method_exists')->with(EntryRepository::class, 'findByUri')->andReturnTrue(); $this->data->setRepository('entry', EntryRepository::class); $c = tap(Collection::make('pages')->sites(['english', 'french'])->routes('{slug}')->structureContents(['root' => true]))->save(); @@ -295,6 +294,11 @@ private function findByRequestUrlTest($requestUrl, $entryId) $this->assertNull($found); } } + + private function mockMethodExists() + { + static::$functions = Mockery::mock(); + } } namespace Statamic\Data; diff --git a/tests/Data/Entries/EntryTest.php b/tests/Data/Entries/EntryTest.php index f0ec673495f..2952ca26edd 100644 --- a/tests/Data/Entries/EntryTest.php +++ b/tests/Data/Entries/EntryTest.php @@ -342,6 +342,118 @@ public function if_the_value_is_explicitly_set_to_null_then_it_should_not_fall_b $this->assertEquals('four in entry', $entry->value('four')); } + /** @test */ + public function it_gets_custom_computed_data() + { + Facades\Collection::computed('articles', 'description', function ($entry) { + return $entry->get('title').' AND MORE!'; + }); + + $collection = tap(Collection::make('articles'))->save(); + $entry = (new Entry)->collection($collection)->data(['title' => 'Pop Rocks']); + + $expectedData = [ + 'title' => 'Pop Rocks', + ]; + + $expectedComputedData = [ + 'description' => 'Pop Rocks AND MORE!', + ]; + + $expectedValues = array_merge($expectedData, $expectedComputedData); + + $this->assertArraySubset($expectedData, $entry->data()->all()); + $this->assertEquals($expectedComputedData, $entry->computedData()->all()); + $this->assertEquals($expectedValues, $entry->values()->all()); + $this->assertEquals($expectedValues['title'], $entry->value('title')); + $this->assertEquals($expectedValues['description'], $entry->value('description')); + } + + /** @test */ + public function it_gets_empty_computed_data_by_default() + { + $collection = tap(Collection::make('test'))->save(); + $entry = (new Entry)->collection($collection)->data(['title' => 'Pop Rocks']); + + $this->assertEquals([], $entry->computedData()->all()); + } + + /** @test */ + public function it_doesnt_recursively_get_computed_data_when_callback_uses_value_methods() + { + Facades\Collection::computed('articles', 'description', function ($entry) { + return $entry->value('title').' '.$entry->values()->get('suffix'); + }); + + $collection = tap(Collection::make('articles'))->save(); + $entry = (new Entry)->collection($collection)->data(['title' => 'Pop Rocks', 'suffix' => 'AND MORE!']); + + $this->assertEquals('Pop Rocks AND MORE!', $entry->value('description')); + } + + /** @test */ + public function it_can_use_actual_data_to_compose_computed_data() + { + Facades\Collection::computed('articles', 'description', function ($entry, $value) { + return $value ?? 'N/A'; + }); + + $collection = tap(Collection::make('articles'))->save(); + + $entry = (new Entry)->collection($collection); + + $this->assertEquals('N/A', $entry->value('description')); + + $entry->data(['description' => 'Raddest article ever!']); + + $this->assertEquals('Raddest article ever!', $entry->value('description')); + } + + /** @test */ + public function it_can_use_origin_data_to_compose_computed_data() + { + Facades\Collection::computed('articles', 'description', function ($entry, $value) { + return $entry->value('description') ?? 'N/A'; + }); + + $collection = tap(Collection::make('articles'))->save(); + + (new Entry)->collection($collection)->id('origin')->data([ + 'description' => 'Dill Pickles', + ])->save(); + + $entry = (new Entry)->collection($collection)->origin('origin'); + + $this->assertEquals('Dill Pickles', $entry->values()->get('description')); + $this->assertEquals('Dill Pickles', $entry->value('description')); + + $entry->data(['description' => 'Raddest article ever!']); + + $this->assertEquals('Raddest article ever!', $entry->values()->get('description')); + $this->assertEquals('Raddest article ever!', $entry->value('description')); + } + + /** @test */ + public function it_properly_scopes_custom_computed_data_by_collection_handle() + { + Facades\Collection::computed('articles', 'description', function ($entry) { + return $entry->get('title').' AND MORE!'; + }); + + Facades\Collection::computed('events', 'french_description', function ($entry) { + return $entry->get('title').' ET PLUS!'; + }); + + $articleEntry = (new Entry)->collection(tap(Collection::make('articles'))->save())->data(['title' => 'Pop Rocks']); + $eventEntry = (new Entry)->collection(tap(Collection::make('events'))->save())->data(['title' => 'Jazz Concert']); + + $this->assertEquals('Pop Rocks AND MORE!', $articleEntry->value('description')); + + $this->assertNull($articleEntry->value('french_description')); + $this->assertNull($eventEntry->value('description')); + $this->assertEquals('Jazz Concert ET PLUS!', $eventEntry->value('french_description')); + } + /** @test */ public function it_gets_the_url_from_the_collection() { diff --git a/tests/Data/StoresComputedFieldCallbacksTest.php b/tests/Data/StoresComputedFieldCallbacksTest.php new file mode 100644 index 00000000000..c6035582ae2 --- /dev/null +++ b/tests/Data/StoresComputedFieldCallbacksTest.php @@ -0,0 +1,33 @@ +computed('some_field', $closureA = function ($item, $value) { + // + }); + + $repository->computed('another_field', $closureB = function ($item, $value) { + // + }); + + $this->assertEquals([ + 'some_field' => $closureA, + 'another_field' => $closureB, + ], $repository->getComputedCallbacks()->all()); + } +} + +class FakeRepository +{ + use StoresComputedFieldCallbacks; +} diff --git a/tests/Data/StoresScopedComputedFieldCallbacksTest.php b/tests/Data/StoresScopedComputedFieldCallbacksTest.php new file mode 100644 index 00000000000..ce531c68775 --- /dev/null +++ b/tests/Data/StoresScopedComputedFieldCallbacksTest.php @@ -0,0 +1,43 @@ +computed('events', 'some_field', $closureA = function ($item, $value) { + // + }); + + $repository->computed('articles', 'some_field', $closureB = function ($item, $value) { + // + }); + + $repository->computed('articles', 'another_field', $closureC = function ($item, $value) { + // + }); + + $this->assertEquals([ + 'some_field' => $closureA, + ], $repository->getComputedCallbacks('events')->all()); + + $this->assertEquals([ + 'some_field' => $closureB, + 'another_field' => $closureC, + ], $repository->getComputedCallbacks('articles')->all()); + + $this->assertEquals([], $repository->getComputedCallbacks('products')->all()); + } +} + +class FakeRepositoryWithScopedCallbacks +{ + use StoresScopedComputedFieldCallbacks; +} diff --git a/tests/Fields/FieldsTest.php b/tests/Fields/FieldsTest.php index d542d2e7fc3..996aeee3bd6 100644 --- a/tests/Fields/FieldsTest.php +++ b/tests/Fields/FieldsTest.php @@ -651,6 +651,60 @@ public function process($data) ], $processed->values()->all()); } + /** @test */ + public function it_doesnt_return_computed_field_values() + { + FieldRepository::shouldReceive('find')->with('one')->andReturnUsing(function () { + return new Field('one', ['type' => 'text']); + }); + FieldRepository::shouldReceive('find')->with('two')->andReturnUsing(function () { + return new Field('two', ['type' => 'text', 'visibility' => 'computed', 'default' => 'gandalf']); + }); + FieldRepository::shouldReceive('find')->with('three')->andReturnUsing(function () { + return new Field('three', ['type' => 'text']); + }); + + $fields = new Fields([ + ['handle' => 'one', 'field' => 'one'], + ['handle' => 'two', 'field' => 'two'], + ['handle' => 'three', 'field' => 'three'], + ]); + + $this->assertEquals(['one' => null, 'three' => null], $fields->values()->all()); + $this->assertEquals(['one' => null, 'three' => null], $fields->process()->values()->all()); + + $fields = $fields->addValues(['one' => 'foo', 'two' => 'bar', 'three' => 'baz']); + + $this->assertEquals(['one' => 'foo', 'three' => 'baz'], $fields->values()->all()); + $this->assertEquals(['one' => 'foo', 'three' => 'baz'], $fields->process()->values()->all()); + } + + /** @test */ + public function it_does_return_computed_field_values_when_pre_processed() + { + FieldRepository::shouldReceive('find')->with('one')->andReturnUsing(function () { + return new Field('one', ['type' => 'text']); + }); + FieldRepository::shouldReceive('find')->with('two')->andReturnUsing(function () { + return new Field('two', ['type' => 'text', 'visibility' => 'computed', 'default' => 'gandalf']); + }); + FieldRepository::shouldReceive('find')->with('three')->andReturnUsing(function () { + return new Field('three', ['type' => 'text']); + }); + + $fields = new Fields([ + ['handle' => 'one', 'field' => 'one'], + ['handle' => 'two', 'field' => 'two'], + ['handle' => 'three', 'field' => 'three'], + ]); + + $this->assertEquals(['one' => null, 'two' => 'gandalf', 'three' => null], $fields->preProcess()->values()->all()); + + $fields = $fields->addValues(['one' => 'foo', 'two' => 'bar', 'three' => 'baz']); + + $this->assertEquals(['one' => 'foo', 'two' => 'bar', 'three' => 'baz'], $fields->preProcess()->values()->all()); + } + /** @test */ public function it_preprocesses_each_fields_values_by_its_fieldtype() {