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
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import Button from 'primevue/button';
import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome';
import {useSettings} from '@/settings/useSettings';
import {doesSchemaAllowNull} from '@/schema/schemaReadingUtils';

const settings = useSettings();

Expand Down Expand Up @@ -42,13 +43,22 @@ const emit = defineEmits<{
attributeData: SchemaObjectAttributeData,
required: boolean
): void;
(
e: 'update_attribute_nullable',
attributeData: SchemaObjectAttributeData,
nullable: boolean
): void;
(e: 'delete_element', objectData: SchemaElementData): void;
}>();

function updateRequired() {
emit('update_attribute_required', props.data, props.data.required);
}

function updateNullable(event: Event) {
emit('update_attribute_nullable', props.data, (event.target as HTMLInputElement).checked);
}

const attrName = ref(props.data.name);
const selectedType: Ref<AttributeTypeChoice | undefined> = ref(
determineTypeChoiceBySchema(props.typeChoices, props.data.schema)
Expand Down Expand Up @@ -83,6 +93,10 @@ function isHighlighted() {
return props.selectedData && props.selectedData == props.data;
}

function isNullable() {
return doesSchemaAllowNull(props.data.schema);
}

function getHandleId() {
return `source-${props.data.name}`;
}
Expand Down Expand Up @@ -122,9 +136,11 @@ function getHandleTop() {
<div class="vue-flow-attribute-inline">
<input
type="checkbox"
class="vue-flow-required-checkbox"
class="vue-flow-attribute-checkbox"
v-model="props.data.required"
@change="updateRequired"
aria-label="Required property"
title="Required: controls whether this property must be present."
@mousedown.stop
@click.stop
@dblclick.stop
Expand All @@ -141,6 +157,19 @@ function getHandleTop() {
@dblclick.stop
placeholder="Select Type" />

<input
v-if="settings.schemaDiagram.showNullableCheckbox"
type="checkbox"
class="vue-flow-attribute-checkbox"
:checked="isNullable()"
@change="updateNullable"
aria-label="Nullable property"
title="Nullable: allows this property to be null in addition to its current type."
@mousedown.stop
@click.stop
@dblclick.stop
@keydown.stop />

<Button
class="vue-flow-attribute-button vue-flow-attribute-input-dimensions"
size="small"
Expand All @@ -164,7 +193,7 @@ function getHandleTop() {
</template>

<style>
.vue-flow-required-checkbox {
.vue-flow-attribute-checkbox {
margin: 0 4px;
transform: scale(0.8);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ const emit = defineEmits<{
attributeData: SchemaObjectAttributeData,
required: boolean
): void;
(
e: 'update_attribute_nullable',
attributeData: SchemaObjectAttributeData,
nullable: boolean
): void;
}>();

const objectName = ref(props.data.name || '');
Expand All @@ -62,6 +67,10 @@ function updateAttributeRequired(attributeData: SchemaObjectAttributeData, requi
emit('update_attribute_required', attributeData, required);
}

function updateAttributeNullable(attributeData: SchemaObjectAttributeData, nullable: boolean) {
emit('update_attribute_nullable', attributeData, nullable);
}

function isObjectEditable() {
return (isHighlighted() || isAttributeHighlighted()) && settings.value.schemaDiagram.editMode;
}
Expand Down Expand Up @@ -222,6 +231,7 @@ function isAttributeHighlighted() {
@update_attribute_name="updateAttributeName"
@update_attribute_type="updateAttributeType"
@update_attribute_required="updateAttributeRequired"
@update_attribute_nullable="updateAttributeNullable"
@delete_element="deleteElement" />

<div v-if="isObjectEditable()">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import {
copySelectedSchemaToClipboard,
pasteSchemaFromClipboard,
} from '@/components/panels/schema-diagram/schemaClipboardUtils';
import {doesSchemaAllowNull, setSchemaNullable} from '@/schema/schemaReadingUtils';

const emit = defineEmits<{
(e: 'update_current_path', path: Path): void;
Expand Down Expand Up @@ -374,9 +375,18 @@ function updateAttributeType(
attributeData: SchemaObjectAttributeData,
newType: AttributeTypeChoice
) {
//attributeData.typeDescription = newType.label;
const attributeSchema = structuredClone(schemaData.dataAt(attributeData.absolutePath));
const shouldRemainNullable = doesSchemaAllowNull(attributeSchema, schemaData.data.value);
applyNewType(attributeSchema, newType.schema);
if (shouldRemainNullable) {
setSchemaNullable(attributeSchema, true, schemaData.data.value);
}
schemaData.setDataAt(attributeData.absolutePath, attributeSchema);
}

function updateAttributeNullable(attributeData: SchemaObjectAttributeData, nullable: boolean) {
const attributeSchema = structuredClone(schemaData.dataAt(attributeData.absolutePath));
setSchemaNullable(attributeSchema, nullable, schemaData.data.value);
schemaData.setDataAt(attributeData.absolutePath, attributeSchema);
}

Expand Down Expand Up @@ -486,6 +496,7 @@ function updateExternalReferenceValue(
@update_attribute_name="updateAttributeName"
@update_attribute_type="updateAttributeType"
@update_attribute_required="updateAttributeRequired"
@update_attribute_nullable="updateAttributeNullable"
@delete_element="deleteElement"
@add_attribute="addAttribute"
@extract_inlined_element="extractInlinedElement"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {shallowMount} from '@vue/test-utils';
import {describe, expect, it, vi} from 'vitest';
import {ref} from 'vue';
import SchemaObjectAttribute from '../SchemaObjectAttribute.vue';
import {SchemaObjectAttributeData} from '@/schema/graph-representation/schemaGraphTypes';

vi.mock('@/settings/useSettings', () => ({
useSettings() {
return ref({
schemaDiagram: {
editMode: true,
showNullableCheckbox: true,
},
});
},
}));

describe('SchemaObjectAttribute', () => {
function createAttributeData() {
return new SchemaObjectAttributeData(
'nickname',
'string',
'properties',
['properties', 'nickname'],
false,
true,
0,
{
type: 'string',
}
);
}

it('shows the nullable checkbox only for the highlighted attribute when enabled in settings', () => {
const data = createAttributeData();
const wrapper = shallowMount(SchemaObjectAttribute, {
props: {
data,
selectedData: data,
typeChoices: [{label: 'string', schema: {type: 'string'}}],
},
global: {
directives: {
tooltip: vi.fn(),
},
},
});

const checkboxes = wrapper.findAll('input[type="checkbox"]');
expect(checkboxes).toHaveLength(2);
expect(checkboxes[1]!.attributes('aria-label')).toBe('Nullable property');
});

it('emits nullable updates when the nullable checkbox changes', async () => {
const data = createAttributeData();
const wrapper = shallowMount(SchemaObjectAttribute, {
props: {
data,
selectedData: data,
typeChoices: [{label: 'string', schema: {type: 'string'}}],
},
global: {
directives: {
tooltip: vi.fn(),
},
},
});

const nullableCheckbox = wrapper.findAll('input[type="checkbox"]')[1]!;
await nullableCheckbox.setValue(true);

expect(wrapper.emitted('update_attribute_nullable')).toEqual([[data, true]]);
});
});
Loading
Loading