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
11 changes: 10 additions & 1 deletion resources/js/components/ui/Publish/Field.vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const {
} = injectFieldsContext();

const { direction } = useUiDirection();
const isFormSubmission = inject('isFormSubmission', false);

const asConfig = computed(() => fieldsAsConfig.value ?? containerAsConfig.value ?? false);
const fieldPathPrefix = computed(() => props.fieldPathPrefix || injectedFieldPathPrefix.value);
Expand Down Expand Up @@ -155,6 +156,12 @@ const shouldShowField = computed(() => {
).showField(props.config, fullPath.value);
});

// Hidden fieldtypes are mounted like any other field so they take part in field
// conditions, but they only become visible on a form submission.
const isHiddenFieldtype = computed(() => props.config.type === 'hidden' && !isFormSubmission);

const shouldRenderField = computed(() => shouldShowField.value && !isHiddenFieldtype.value);

const shouldShowLabelText = computed(() => !props.config.hide_display);

// Whether the label renders anything visible. When it doesn't, we avoid rendering
Expand All @@ -169,6 +176,8 @@ const shouldShowLabel = computed(
);

const shouldShowFieldPreviews = computed(() => {
if (isHiddenFieldtype.value) return false;

if (! props.config.replicator_preview) return false;

return inject('showReplicatorFieldPreviews', false);
Expand Down Expand Up @@ -238,7 +247,7 @@ const fieldtypeComponentEvents = computed(() => ({
:shouldShowField="shouldShowField"
>
<Field
v-show="shouldShowField"
v-show="shouldRenderField"
:class="`${config.type}-fieldtype`"
:id="fieldId"
:dir="direction"
Expand Down
11 changes: 2 additions & 9 deletions resources/js/components/ui/Publish/Fields.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,12 @@
import { injectContainerContext } from './Container.vue';
import { injectFieldsContext } from './FieldsProvider.vue';
import Field from './Field.vue';
import { computed, inject } from 'vue';
import { computed } from 'vue';

const { asConfig: containerAsConfig } = injectContainerContext();
const { fields: injectedFields, asConfig: fieldsAsConfig } = injectFieldsContext();
const isFormSubmission = inject('isFormSubmission', false);
const { fields, asConfig: fieldsAsConfig } = injectFieldsContext();

const asConfig = computed(() => fieldsAsConfig.value !== undefined ? fieldsAsConfig.value : containerAsConfig.value);

const fields = computed(() => {
let fields = injectedFields.value;
if (!isFormSubmission) fields = fields.filter(field => field.type !== 'hidden');
return fields;
});
</script>

<template>
Expand Down
85 changes: 85 additions & 0 deletions resources/js/tests/components/Fields.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { mount } from '@vue/test-utils';
import { expect, test } from 'vitest';
import { defineComponent, h, nextTick } from 'vue';
import * as Globals from '@/bootstrap/globals';
import HiddenFieldtype from '@/components/fieldtypes/HiddenFieldtype.vue';
import Container, { injectContainerContext } from '@/components/ui/Publish/Container.vue';
import Fields from '@/components/ui/Publish/Fields.vue';
import FieldsProvider from '@/components/ui/Publish/FieldsProvider.vue';

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

window.Statamic = {
$app: { component: (name) => (name === 'hidden-fieldtype' ? HiddenFieldtype : undefined) },
$config: {
get: (key) => (key === 'sites' ? [{ handle: 'default', direction: 'ltr' }] : undefined),
},
$dirty: { has: () => false, add: () => {}, remove: () => {} },
$events: { $emit: () => {} },
};

const fields = [
{ handle: 'protected', type: 'toggle' },
{ handle: 'scheme', type: 'hidden', replicator_preview: true, if: { protected: 'equals true' } },
];

let previews;

const Probe = defineComponent({
setup() {
previews = injectContainerContext().previews;
return () => h('div');
},
});

async function mountFields(values, provide = {}) {
const wrapper = mount(Container, {
props: {
blueprint: { tabs: [] },
modelValue: values,
site: 'default',
},
global: {
components: { 'hidden-fieldtype': HiddenFieldtype },
provide,
},
slots: {
default: () => [h(Probe), h(FieldsProvider, { fields }, () => h(Fields))],
},
});

await nextTick();

return wrapper;
}

test('a hidden fieldtype is not visible', async () => {
const wrapper = await mountFields({ protected: true, scheme: 'password' });

expect(wrapper.find('.hidden-fieldtype').attributes('style')).toBe('display: none;');
});

test('a hidden fieldtype is visible on a form submission', async () => {
const wrapper = await mountFields({ protected: true, scheme: 'password' }, { isFormSubmission: true });

expect(wrapper.find('.hidden-fieldtype').attributes('style')).toBeUndefined();
});

test('the value of a hidden fieldtype is omitted when its conditions fail', async () => {
const wrapper = await mountFields({ protected: false, scheme: 'password' });

expect(wrapper.vm.visibleValues).toEqual({ protected: false });
});

test('the value of a hidden fieldtype is kept when its conditions pass', async () => {
const wrapper = await mountFields({ protected: true, scheme: 'password' });

expect(wrapper.vm.visibleValues).toEqual({ protected: true, scheme: 'password' });
});

test('a hidden fieldtype does not get a replicator preview', async () => {
await mountFields({ protected: true, scheme: 'password' }, { showReplicatorFieldPreviews: true });

expect(previews.value).toEqual({});
});
Loading