Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions resources/js/components/ui/Publish/Container.vue
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,7 @@ onUnmounted(() => {
defineExpose({
name: props.name,
values,
meta,
saving,
saved,
revealerFields,
Expand Down
4 changes: 4 additions & 0 deletions resources/js/components/ui/Publish/SavePipeline.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import axios from 'axios';
import resetValuesFromResponse from '@/util/resetValuesFromResponse.js';
import resetMetaFromResponse from '@/util/resetMetaFromResponse.js';
import { reveal } from '@api';
import { UPDATE_DEBOUNCE_MS } from '@/components/fieldtypes/constants';

Expand Down Expand Up @@ -77,6 +78,9 @@ export class Request extends Step {
);
container.value.setExtraValues(response.data.data.extraValues);
}
if (container && response.data.data?.hasOwnProperty('meta')) {
container.value.setMeta(resetMetaFromResponse(response.data.data.meta, container.value));
}
Comment thread
duncanmcclean marked this conversation as resolved.
resolve(response);
})
.catch((e) => {
Expand Down
64 changes: 62 additions & 2 deletions resources/js/tests/components/ui/Publish/SavePipeline.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { ref } from 'vue';
import axios from 'axios';
import Hooks from '@/components/Hooks.js';
import { AfterSaveHooks, BeforeSaveHooks, Pipeline, PipelineStopped } from '@/components/ui/Publish/SavePipeline.js';
import { AfterSaveHooks, BeforeSaveHooks, Pipeline, PipelineStopped, Request } from '@/components/ui/Publish/SavePipeline.js';

vi.mock('axios', () => ({
default: { patch: vi.fn() },
}));

let saving;
let errors;
Expand Down Expand Up @@ -34,10 +39,65 @@ function throwingStep(error) {
beforeEach(() => {
saving = ref(false);
errors = ref({});
container = ref({ saving: vi.fn(), saved: vi.fn() });
container = ref({
saving: vi.fn(),
saved: vi.fn(),
visibleValues: {},
values: { id: 'the-id' },
meta: {},
revealerFields: [],
setValues: vi.fn(),
setExtraValues: vi.fn(),
setMeta: vi.fn(),
});
global.Statamic = { $hooks: new Hooks() };
});

test('the request applies the values, extra values and meta from the response', async () => {
axios.patch.mockResolvedValue({
data: {
data: {
values: { title: 'Saved' },
extraValues: { depth: 1 },
meta: { tags: { data: [{ id: 'tags::alfa', title: 'Alfa' }] } },
},
},
});

await pipeline([new Request('/entries/1', 'PATCH', {})]);

expect(container.value.setValues).toHaveBeenCalledWith({ id: 'the-id', title: 'Saved' });
expect(container.value.setExtraValues).toHaveBeenCalledWith({ depth: 1 });
expect(container.value.setMeta).toHaveBeenCalledWith({ tags: { data: [{ id: 'tags::alfa', title: 'Alfa' }] } });
});

test('the request keeps whether the slug is automatic when refreshing the meta', async () => {
container.value.meta = { slug: { auto: true }, tags: { data: [] } };
axios.patch.mockResolvedValue({
data: {
data: {
values: { title: 'Saved' },
meta: { slug: null, tags: { data: [{ id: 'tags::alfa', title: 'Alfa' }] } },
},
},
});

await pipeline([new Request('/entries/1', 'PATCH', {})]);

expect(container.value.setMeta).toHaveBeenCalledWith({
slug: { auto: true },
tags: { data: [{ id: 'tags::alfa', title: 'Alfa' }] },
});
});

test('the request leaves the meta alone when the response has none', async () => {
axios.patch.mockResolvedValue({ data: { data: { values: { title: 'Saved' } } } });

await pipeline([new Request('/entries/1', 'PATCH', {})]);

expect(container.value.setMeta).not.toHaveBeenCalled();
});

test('it runs the steps and finishes', async () => {
const middle = step();

Expand Down
8 changes: 8 additions & 0 deletions resources/js/util/resetMetaFromResponse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default function resetMetaFromResponse(responseMeta, container) {
// The slug fieldtype tracks whether the slug still follows its source. The server never sends it.
const auto = container.meta.slug?.auto;

if (auto === undefined) return responseMeta;

return { ...responseMeta, slug: { ...responseMeta.slug, auto } };
}
1 change: 1 addition & 0 deletions src/Http/Controllers/CP/Collections/EntriesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ public function update(Request $request, $collection, $entry)
return [
'data' => array_merge((new EntryResource($entry->fresh()))->resolve()['data'], [
'values' => $values,
'meta' => $meta,
'extraValues' => $extraValues,
]),
'saved' => $saved,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Statamic\Http\Controllers\CP\Collections;

use Statamic\Support\Arr;

trait ExtractsFromEntryFields
{
protected function extractFromFields($entry, $blueprint)
Expand All @@ -11,7 +13,8 @@ protected function extractFromFields($entry, $blueprint)
$values = collect();
$target = $entry;
while ($target) {
$values = $target->data()->merge($target->computedData())->merge($values);
$data = $target->isRoot() ? collect(Arr::removeNullValues($target->data()->all())) : $target->data();
$values = $data->merge($target->computedData())->merge($values);
$target = $target->origin();
}
$values = $values->all();
Expand Down
10 changes: 7 additions & 3 deletions src/Http/Controllers/CP/Taxonomies/ExtractsFromTermFields.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@

namespace Statamic\Http\Controllers\CP\Taxonomies;

use Statamic\Support\Arr;

trait ExtractsFromTermFields
{
protected function extractFromFields($term, $blueprint)
{
// The values should only be data merged with the origin data.
// We don't want injected taxonomy values, which $term->values() would have given us.
$values = $term->inDefaultLocale()->data()->merge(
$term->data()
);
$values = collect(Arr::removeNullValues($term->inDefaultLocale()->data()->all()));

if (! $term->isRoot()) {
$values = $values->merge($term->data());
}

$fields = $blueprint
->fields()
Expand Down
3 changes: 2 additions & 1 deletion src/Http/Controllers/CP/Taxonomies/TermsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -196,13 +196,14 @@ public function update(Request $request, $taxonomy, $term, $site)

$saved = $term->updateLastModified(User::current())->save();

[$values] = $this->extractFromFields($term, $term->blueprint());
[$values, $meta] = $this->extractFromFields($term, $term->blueprint());

return (new TermResource($term))
->additional([
'saved' => $saved,
'data' => [
'values' => $values,
'meta' => $meta,
],
]);
}
Expand Down
95 changes: 95 additions & 0 deletions tests/Feature/Entries/UpdateEntryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
use Statamic\Facades\Collection;
use Statamic\Facades\Entry;
use Statamic\Facades\Role;
use Statamic\Facades\Taxonomy;
use Statamic\Facades\Term;
use Statamic\Facades\User;
use Statamic\Structures\CollectionStructure;
use Tests\FakesRoles;
Expand Down Expand Up @@ -386,6 +388,99 @@ public function slug_and_auto_title_get_generated_after_save()
$this->assertEquals('auto-avada-kedavra.md', pathinfo($entry->path(), PATHINFO_BASENAME));
}

#[Test]
public function default_values_are_returned_for_fields_saved_empty()
{
[$user, $collection] = $this->seedUserAndCollection();
$this->seedBlueprintFields($collection, [
'nutrition_table' => [
'type' => 'grid',
'default' => [['name' => 'Sugar'], ['name' => 'Salt']],
'fields' => [['handle' => 'name', 'field' => ['type' => 'text']]],
],
]);

$entry = EntryFactory::collection($collection)
->slug('existing-entry')
->data(['title' => 'Existing Entry'])
->create();

$response = $this
->actingAs($user)
->update($entry)
->assertOk();

$this->assertEquals(['Sugar', 'Salt'], collect($response->json('data.values.nutrition_table'))->pluck('name')->all());
}

#[Test]
public function localized_fields_saved_empty_stay_empty()
{
$this->setSites([
'en' => ['locale' => 'en', 'url' => '/'],
'fr' => ['locale' => 'fr', 'url' => '/fr/'],
]);

[$user, $collection] = $this->seedUserAndCollection();
$collection->sites(['en', 'fr'])->save();
$this->seedBlueprintFields($collection, [
'nutrition_table' => [
'type' => 'grid',
'default' => [['name' => 'Sugar'], ['name' => 'Salt']],
'fields' => [['handle' => 'name', 'field' => ['type' => 'text']]],
],
]);

$origin = EntryFactory::collection($collection)
->locale('en')
->slug('origin')
->data(['title' => 'Origin', 'nutrition_table' => [['name' => 'Fat']]])
->create();

$localization = EntryFactory::collection($collection)
->locale('fr')
->origin($origin)
->slug('localization')
->create();

$response = $this
->actingAs($user)
->update($localization, ['nutrition_table' => [], '_localized' => ['nutrition_table']])
->assertOk();

$this->assertSame([], $response->json('data.values.nutrition_table'));
}

#[Test]
public function meta_reflects_values_changed_while_saving()
{
[$user, $collection] = $this->seedUserAndCollection();
$this->seedBlueprintFields($collection, [
'tags' => ['type' => 'terms', 'taxonomies' => ['tags']],
]);

Role::find('test')->addPermission('view tags terms');
Taxonomy::make('tags')->save();
Term::make()->taxonomy('tags')->inDefaultLocale()->slug('alfa')->data(['title' => 'Alfa'])->save();

Event::listen(EntrySaving::class, function (EntrySaving $event) {
$event->entry->set('tags', ['alfa']);
});

$entry = EntryFactory::collection($collection)
->slug('existing-entry')
->data(['title' => 'Existing Entry'])
->create();

$response = $this
->actingAs($user)
->update($entry)
->assertOk();

$this->assertEquals(['tags::alfa'], $response->json('data.values.tags'));
$this->assertEquals('Alfa', $response->json('data.meta.tags.data.0.title'));
}

#[Test]
public function auto_title_only_gets_saved_on_localization_when_different_from_origin()
{
Expand Down
Loading
Loading