Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ describe('CustomTitleFieldTemplate', () => {
})

describe('when a title field with hide-header set to true', () => {
it('renders it with no header', () => {
test('renders it with no header', () => {
setup({
uiSchema: {
'ui:hide-header': true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const setup = (draft = undefined) => {

describe('JsonPreview Component', () => {
describe('when draft is not present in the context', () => {
it('renders JSONPretty', () => {
test('renders JSONPretty', () => {
setup()

expect(JSONPretty).toHaveBeenCalledTimes(1)
Expand All @@ -28,7 +28,7 @@ describe('JsonPreview Component', () => {
})

describe('when ummMetadata is not present in draft', () => {
it('renders JSONPretty', () => {
test('renders JSONPretty', () => {
setup({})

expect(JSONPretty).toHaveBeenCalledTimes(1)
Expand All @@ -39,7 +39,7 @@ describe('JsonPreview Component', () => {
})

describe('when draft metadata exists', () => {
it('renders JSONPretty', () => {
test('renders JSONPretty', () => {
setup({
ummMetadata: {
Name: 'Mock Name'
Expand Down
59 changes: 39 additions & 20 deletions static/src/js/components/KeywordForm/KeywordForm.jsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import React, { useState, useEffect } from 'react'
import PropTypes from 'prop-types'
import validator from '@rjsf/validator-ajv8'
import Form from '@rjsf/core'
import validator from '@rjsf/validator-ajv8'
import PropTypes from 'prop-types'
import React, { useEffect, useState } from 'react'

import CustomArrayTemplate from '@/js/components/CustomArrayFieldTemplate/CustomArrayFieldTemplate'
import CustomFieldTemplate from '@/js/components/CustomFieldTemplate/CustomFieldTemplate'
import CustomTextareaWidget from '@/js/components/CustomTextareaWidget/CustomTextareaWidget'
import CustomTextWidget from '@/js/components/CustomTextWidget/CustomTextWidget'
import GridLayout from '@/js/components/GridLayout/GridLayout'

import editKeywordsUiSchema from '@/js/schemas/uiSchemas/keywords/editKeyword'
import keywordSchema from '@/js/schemas/umm/keywordSchema'

import KmsConceptSelectionWidget from '../KmsConceptSelectionWidget/KmsConceptSelectionWidget'

const KeywordForm = ({
initialData,
onFormDataChange
onFormDataChange,
scheme,
version
}) => {
const [formData, setFormData] = useState(initialData)

Expand All @@ -23,11 +26,12 @@ const KeywordForm = ({
}, [initialData])

const fields = {
kmsConceptSelection: KmsConceptSelectionWidget,
layout: GridLayout
}
const widgets = {
TextareaWidget: CustomTextareaWidget,
TextWidget: CustomTextWidget
TextWidget: CustomTextWidget,
TextareaWidget: CustomTextareaWidget
}
const templates = {
ArrayFieldTemplate: CustomArrayTemplate,
Expand Down Expand Up @@ -55,6 +59,12 @@ const KeywordForm = ({
uiSchema={editKeywordsUiSchema}
formData={formData}
onChange={handleChange}
formContext={
{
scheme,
version
}
}
// OnSubmit={handleSubmit}
validator={validator}
>
Expand All @@ -75,29 +85,38 @@ KeywordForm.defaultProps = {

KeywordForm.propTypes = {
initialData: PropTypes.shape({
KeywordUUID: PropTypes.string,
BroaderKeyword: PropTypes.string,
NarrowerKeywords: PropTypes.arrayOf(PropTypes.shape({
NarrowerUUID: PropTypes.string
})),
PreferredLabel: PropTypes.string,
AlternateLabels: PropTypes.arrayOf(PropTypes.shape({
LabelName: PropTypes.string,
LabelType: PropTypes.string
})),
BroaderKeywords: PropTypes.arrayOf(PropTypes.shape({
BroaderUUID: PropTypes.string
})),
ChangeLogs: PropTypes.string,
Definition: PropTypes.string,
DefinitionReference: PropTypes.string,
Resources: PropTypes.arrayOf(PropTypes.shape({
ResourceType: PropTypes.string,
ResourceUri: PropTypes.string
KeywordUUID: PropTypes.string,
NarrowerKeywords: PropTypes.arrayOf(PropTypes.shape({
NarrowerUUID: PropTypes.string
})),
PreferredLabel: PropTypes.string,
RelatedKeywords: PropTypes.arrayOf(PropTypes.shape({
UUID: PropTypes.string,
RelationshipType: PropTypes.string
RelationshipType: PropTypes.string,
UUID: PropTypes.string
})),
ChangeLogs: PropTypes.string
Resources: PropTypes.arrayOf(PropTypes.shape({
ResourceType: PropTypes.string,
ResourceUri: PropTypes.string
}))
}),
onFormDataChange: PropTypes.func
onFormDataChange: PropTypes.func,
scheme: PropTypes.shape({
name: PropTypes.string
}).isRequired,
version: PropTypes.shape({
version: PropTypes.string,
version_type: PropTypes.string
}).isRequired
}

export default KeywordForm
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import React from 'react'
import {
render,
screen,
waitFor
} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import React from 'react'
import {
describe,
test,
expect,
test,
vi
} from 'vitest'
import userEvent from '@testing-library/user-event'

import KeywordForm from '../KeywordForm'

vi.mock('@/js/utils/getUmmSchema', () => ({
Expand Down Expand Up @@ -40,12 +41,22 @@ describe('when KeywordForm is rendered', () => {
}

test('should display the form title', () => {
render(<KeywordForm initialData={mockInitialData} />)
render(<KeywordForm
initialData={mockInitialData}
scheme={{ name: 'sciencekeywords' }}
version={{ version: 'draft' }}
/>)

expect(screen.getByText('Edit Keyword')).toBeInTheDocument()
})

test('should render the form with initial data', () => {
render(<KeywordForm initialData={mockInitialData} />)
render(<KeywordForm
initialData={mockInitialData}
scheme={{ name: 'sciencekeywords' }}
version={{ version: 'draft' }}
/>)

expect(screen.getByDisplayValue('Test Keyword')).toBeInTheDocument()
expect(screen.getByDisplayValue('This is a test keyword')).toBeInTheDocument()
})
Expand All @@ -58,6 +69,8 @@ describe('when user types in the form', () => {

render(<KeywordForm
initialData={{ PreferredLabel: '' }}
scheme={{ name: 'sciencekeywords' }}
version={{ version: 'draft' }}
onFormDataChange={mockOnFormDataChange}
/>)

Expand All @@ -80,10 +93,19 @@ describe('when user types in the form', () => {

describe('when initialData prop changes', () => {
test('should update the form', () => {
const { rerender } = render(<KeywordForm initialData={{ PreferredLabel: 'Initial Keyword' }} />)
const { rerender } = render(<KeywordForm
initialData={{ PreferredLabel: 'Initial Keyword' }}
scheme={{ name: 'sciencekeywords' }}
version={{ version: 'draft' }}
/>)
expect(screen.getByDisplayValue('Initial Keyword')).toBeInTheDocument()

rerender(<KeywordForm initialData={{ PreferredLabel: 'Updated Keyword' }} />)
rerender(<KeywordForm
initialData={{ PreferredLabel: 'Updated Keyword' }}
scheme={{ name: 'sciencekeywords' }}
version={{ version: 'draft' }}
/>)

expect(screen.getByDisplayValue('Updated Keyword')).toBeInTheDocument()
})
})
53 changes: 44 additions & 9 deletions static/src/js/components/KeywordTree/KeywordTree.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import './KeywordTree.scss'
* @param {Object|Array} props.data - The initial tree data structure
* @param {Function} props.onNodeClick - Callback function when a node is clicked
* @param {Function} props.onNodeEdit - Callback function when a node is edited
* @param {Function} props.onAddNarrower - Callback function when a narrower keyword is added
*
* @example
* const treeData = [
Expand Down Expand Up @@ -57,16 +58,21 @@ import './KeywordTree.scss'
* console.log('Node edit requested:', nodeId);
* };
*
* const handleAddNarrower = (parentId, newChild) => {
* console.log('New narrower added:', newChild, 'to parent:', parentId);
* };
*
* return (
* <KeywordTree
* data={treeData}
* onNodeClick={handleNodeClick}
* onNodeEdit={handleNodeEdit}
* onAddNarrower={handleAddNarrower}
* />
* );
*/
export const KeywordTree = ({
data, onNodeClick, onNodeEdit
data, onAddNarrower, onNodeClick, onNodeEdit, searchTerm, selectedNodeId, showContextMenu, openAll
}) => {
const [treeData, setTreeData] = useState(Array.isArray(data) ? data : [data])
const treeRef = useRef(null)
Expand All @@ -91,15 +97,29 @@ export const KeywordTree = ({
}
}, [])

// Effect to manage tree expansion or node selection
useEffect(() => {
if (treeRef.current && treeData.length > 0) {
const tree = treeRef.current
const rootNode = tree.get(treeData[0].id)
if (rootNode) {
rootNode.open()
if (openAll) {
treeRef.current.openAll()
} else if (selectedNodeId) {
treeRef.current.openParents(selectedNodeId)
setTimeout(() => { // Delay to potentially allow tree updates
const node = treeRef.current.get(selectedNodeId)
if (node) {
treeRef.current.select(selectedNodeId)
treeRef.current.scrollTo(selectedNodeId, 'center')
}
}, 0)
} else {
const tree = treeRef.current
const rootNode = tree.get(treeData[0].id)
if (rootNode) {
rootNode.open()
}
}
}
}, [treeData])
}, [treeData, openAll])

const closeAllDescendants = (node) => {
if (node.isOpen) {
Expand Down Expand Up @@ -164,6 +184,9 @@ export const KeywordTree = ({
}
}

// Notify the parent component about the new keyword
onAddNarrower(addNarrowerParentId, newChild)

setShowAddNarrowerPopup(false)
setNewNarrowerTitle('')
setAddNarrowerParentId(null)
Expand Down Expand Up @@ -214,9 +237,9 @@ export const KeywordTree = ({
node={node}
onAdd={handleAdd}
onDelete={handleDelete}
searchTerm={searchTerm}
setContextMenu={setContextMenu}
onToggle={handleToggle}
onClick={onNodeClick}
onEdit={onNodeEdit}
onNodeClick={onNodeClick}
handleAdd={handleAdd}
Expand All @@ -225,7 +248,7 @@ export const KeywordTree = ({
}
</Tree>
{
contextMenu && (
contextMenu && showContextMenu && (
<KeywordTreeContextMenu
{...contextMenu}
onClose={() => setContextMenu(null)}
Expand Down Expand Up @@ -264,11 +287,23 @@ const NodeShape = {
}
NodeShape.children = PropTypes.arrayOf(PropTypes.shape(NodeShape))

KeywordTree.defaultProps = {
searchTerm: null,
selectedNodeId: null,
showContextMenu: true,
openAll: false
}

KeywordTree.propTypes = {
selectedNodeId: PropTypes.string,
data: PropTypes.oneOfType([
PropTypes.shape(NodeShape),
PropTypes.arrayOf(PropTypes.shape(NodeShape))
]).isRequired,
onAddNarrower: PropTypes.func.isRequired,
onNodeClick: PropTypes.func.isRequired,
onNodeEdit: PropTypes.func.isRequired
onNodeEdit: PropTypes.func.isRequired,
searchTerm: PropTypes.string,
showContextMenu: PropTypes.bool,
openAll: PropTypes.bool
}
Loading