Skip to content
Merged
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
7 changes: 6 additions & 1 deletion resources/js/components/blueprints/ImportField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,10 @@
<script>
import Field from './Field.vue';
import FieldSettings from '../fields/ImportSettings.vue';
import PreservesImportedSections from './PreservesImportedSections.js';

export default {
mixins: [Field],
mixins: [Field, PreservesImportedSections],

components: { FieldSettings },

Expand Down Expand Up @@ -76,6 +77,10 @@ export default {
},

sectionBehavior() {
if (!this.canPreserveImportedSections) {
return 'flatten';
}

return this.field.section_behavior ?? 'preserve';
},

Expand Down
11 changes: 9 additions & 2 deletions resources/js/components/blueprints/LinkFields.vue
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
</Field>

<Field
v-if="selectedFieldsetHasSections"
v-if="canChooseSectionBehavior"
:label="__('Section Behavior')"
:instructions="__('messages.fieldset_import_section_behavior_instructions')"
class="mt-6"
Expand Down Expand Up @@ -112,8 +112,11 @@
import { nanoid as uniqid } from 'nanoid';
import { Combobox, Button, Input, Heading, Field, Stack, StackClose, RadioGroup, Radio } from '@/components/ui';
import { usePage } from '@inertiajs/vue3';
import PreservesImportedSections from './PreservesImportedSections.js';

export default {
mixins: [PreservesImportedSections],

components: { Heading, Combobox, Button, Input, Field, Stack, StackClose, RadioGroup, Radio },

props: {
Expand Down Expand Up @@ -162,6 +165,10 @@ export default {

return this.fieldsets.find((f) => f.handle === this.fieldset)?.has_sections === true;
},

canChooseSectionBehavior() {
return this.selectedFieldsetHasSections && this.canPreserveImportedSections;
},
},

watch: {
Expand Down Expand Up @@ -207,7 +214,7 @@ export default {
prefix: this.importPrefix,
};

if (this.selectedFieldsetHasSections) {
if (this.canChooseSectionBehavior) {
field.section_behavior = this.sectionBehavior;
}

Expand Down
15 changes: 15 additions & 0 deletions resources/js/components/blueprints/PreservesImportedSections.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Imported fieldset sections are only kept when the import sits at the top level of a blueprint.
// Fieldsets and nested fields (e.g. replicator sets, grids) always flatten them.
export default {
inject: {
importIsInsideSet: { from: 'isInsideSet', default: false },
importIsInsideConfigFields: { from: 'isInsideConfigFields', default: false },
importIsInsideFieldset: { from: 'isInsideFieldset', default: false },
},

computed: {
canPreserveImportedSections() {
return !this.importIsInsideSet && !this.importIsInsideConfigFields && !this.importIsInsideFieldset;
},
},
};
9 changes: 8 additions & 1 deletion resources/js/components/fields/ImportSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
</Field>

<Field
v-if="fieldsetHasSections"
v-if="canChooseSectionBehavior"
:label="__('Section Behavior')"
:instructions="sectionBehaviorInstructions"
class="form-group field-w-100"
Expand All @@ -43,8 +43,11 @@

<script>
import { Button, Heading, CardPanel, Field, Input, StackHeader, StackContent, RadioGroup, Radio } from '@/components/ui';
import PreservesImportedSections from '../blueprints/PreservesImportedSections.js';

export default {
mixins: [PreservesImportedSections],

components: { StackContent, StackHeader, Heading, Button, CardPanel, Field, Input, RadioGroup, Radio },

props: ['config', 'isInsideSet'],
Expand Down Expand Up @@ -77,6 +80,10 @@ export default {
return this.fieldsetMeta?.has_sections === true;
},

canChooseSectionBehavior() {
return this.fieldsetHasSections && !this.isInsideSet && this.canPreserveImportedSections;
},

sectionBehavior() {
return this.values.section_behavior ?? 'preserve';
},
Expand Down
4 changes: 4 additions & 0 deletions resources/js/pages/fieldsets/Edit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ export default {

props: ['action', 'initialFieldset'],

provide: {
isInsideFieldset: true,
},

data() {
return {
method: 'patch',
Expand Down
122 changes: 122 additions & 0 deletions resources/js/tests/components/ImportedFieldsetSections.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { shallowMount } from '@vue/test-utils';
import { describe, expect, test, vi } from 'vitest';
import * as Globals from '@/bootstrap/globals';
import LinkFields from '@/components/blueprints/LinkFields.vue';
import ImportField from '@/components/blueprints/ImportField.vue';
import ImportSettings from '@/components/fields/ImportSettings.vue';

const fieldsets = {
sectioned: { handle: 'sectioned', title: 'Sectioned', fields: [], has_sections: true, sections_count: 2 },
flat: { handle: 'flat', title: 'Flat', fields: [], has_sections: false, sections_count: 0 },
};

vi.mock('@inertiajs/vue3', () => ({
usePage: () => ({ props: { fieldsets } }),
}));

Object.keys(Globals).forEach((fn) => (window[fn] = Globals[fn]));
window.__ = (key) => key;
window.__n = (key) => key;
window.cp_url = (url) => url;

const nestedContexts = [
['a replicator or bard set', { isInsideSet: true }],
['nested field settings (e.g. a grid or group)', { isInsideConfigFields: true }],
['a fieldset', { isInsideFieldset: true }],
];

function mount(component, { props = {}, provide = {} } = {}) {
return shallowMount(component, {
props,
global: {
provide,
renderStubDefaultSlot: true,
mocks: { $page: { props: { fieldsets } } },
},
});
}

describe('linking a fieldset', () => {
function linkFieldset(handle, provide = {}) {
const wrapper = mount(LinkFields, { provide });
wrapper.vm.fieldset = handle;
wrapper.vm.linkFieldset();

return { wrapper, field: wrapper.emitted('linked')[0][0] };
}

test('offers section behavior for a sectioned fieldset at the top level of a blueprint', () => {
const wrapper = mount(LinkFields);
wrapper.vm.fieldset = 'sectioned';

expect(wrapper.vm.canChooseSectionBehavior).toBe(true);
expect(linkFieldset('sectioned').field.section_behavior).toBe('preserve');
});

test('does not offer section behavior for a flat fieldset', () => {
const wrapper = mount(LinkFields);
wrapper.vm.fieldset = 'flat';

expect(wrapper.vm.canChooseSectionBehavior).toBe(false);
expect(linkFieldset('flat').field).not.toHaveProperty('section_behavior');
});

test.each(nestedContexts)('does not offer section behavior inside %s', (_, provide) => {
const wrapper = mount(LinkFields, { provide });
wrapper.vm.fieldset = 'sectioned';

expect(wrapper.vm.canChooseSectionBehavior).toBe(false);
expect(linkFieldset('sectioned', provide).field).not.toHaveProperty('section_behavior');
});
});

describe('import settings', () => {
test('shows the section behavior control for a sectioned fieldset at the top level of a blueprint', () => {
const wrapper = mount(ImportSettings, { props: { config: { fieldset: 'sectioned' } } });

expect(wrapper.html()).toContain('Section Behavior');
});

test('hides the section behavior control for a flat fieldset', () => {
const wrapper = mount(ImportSettings, { props: { config: { fieldset: 'flat' } } });

expect(wrapper.html()).not.toContain('Section Behavior');
});

test.each(nestedContexts)('hides the section behavior control inside %s', (_, provide) => {
const wrapper = mount(ImportSettings, { props: { config: { fieldset: 'sectioned' } }, provide });

expect(wrapper.html()).not.toContain('Section Behavior');
});

test('hides the section behavior control when the isInsideSet prop is passed', () => {
const wrapper = mount(ImportSettings, { props: { config: { fieldset: 'sectioned' }, isInsideSet: true } });

expect(wrapper.html()).not.toContain('Section Behavior');
});
});

describe('import field badge', () => {
function badge(field, provide = {}) {
return mount(ImportField, { props: { field: { _id: 'a', type: 'import', ...field } }, provide }).vm
.sectionBadgeText;
}

test('shows sections are kept at the top level of a blueprint', () => {
expect(badge({ fieldset: 'sectioned' })).toBe('Has Section|Has Sections');
});

test('shows sections are ignored when flattened at the top level of a blueprint', () => {
expect(badge({ fieldset: 'sectioned', section_behavior: 'flatten' })).toBe('Ignoring Section|Ignoring Sections');
});

test('shows no badge for a flat fieldset', () => {
expect(badge({ fieldset: 'flat' })).toBeNull();
});

test.each(nestedContexts)('shows sections are ignored inside %s, even when set to preserve', (_, provide) => {
expect(badge({ fieldset: 'sectioned', section_behavior: 'preserve' }, provide)).toBe(
'Ignoring Section|Ignoring Sections',
);
});
});
Loading