Skip to content

Commit e858fa6

Browse files
authored
MMT-4006: User can add new narrower keyword
* MMT-4006: Initial checkin * MMT-4006: Remove unused updates
1 parent a64399e commit e858fa6

File tree

4 files changed

+121
-4
lines changed

4 files changed

+121
-4
lines changed

static/src/js/components/KeywordTree/KeywordTree.jsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import './KeywordTree.scss'
2525
* @param {Object|Array} props.data - The initial tree data structure
2626
* @param {Function} props.onNodeClick - Callback function when a node is clicked
2727
* @param {Function} props.onNodeEdit - Callback function when a node is edited
28+
* @param {Function} props.onAddNarrower - Callback function when a narrower keyword is added
2829
*
2930
* @example
3031
* const treeData = [
@@ -57,16 +58,21 @@ import './KeywordTree.scss'
5758
* console.log('Node edit requested:', nodeId);
5859
* };
5960
*
61+
* const handleAddNarrower = (parentId, newChild) => {
62+
* console.log('New narrower added:', newChild, 'to parent:', parentId);
63+
* };
64+
*
6065
* return (
6166
* <KeywordTree
6267
* data={treeData}
6368
* onNodeClick={handleNodeClick}
6469
* onNodeEdit={handleNodeEdit}
70+
* onAddNarrower={handleAddNarrower}
6571
* />
6672
* );
6773
*/
6874
export const KeywordTree = ({
69-
data, onNodeClick, onNodeEdit
75+
data, onAddNarrower, onNodeClick, onNodeEdit
7076
}) => {
7177
const [treeData, setTreeData] = useState(Array.isArray(data) ? data : [data])
7278
const treeRef = useRef(null)
@@ -164,6 +170,9 @@ export const KeywordTree = ({
164170
}
165171
}
166172

173+
// Notify the parent component about the new keyword
174+
onAddNarrower(addNarrowerParentId, newChild)
175+
167176
setShowAddNarrowerPopup(false)
168177
setNewNarrowerTitle('')
169178
setAddNarrowerParentId(null)
@@ -269,6 +278,7 @@ KeywordTree.propTypes = {
269278
PropTypes.shape(NodeShape),
270279
PropTypes.arrayOf(PropTypes.shape(NodeShape))
271280
]).isRequired,
281+
onAddNarrower: PropTypes.func.isRequired,
272282
onNodeClick: PropTypes.func.isRequired,
273283
onNodeEdit: PropTypes.func.isRequired
274284
}

static/src/js/components/KeywordTree/__tests__/KeywordTree.test.jsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ describe('KeywordTree component', () => {
3939

4040
const mockOnNodeClick = vi.fn()
4141
const mockOnNodeEdit = vi.fn()
42+
const mockOnAddNarrower = vi.fn()
4243
let consoleErrorSpy
4344

4445
beforeAll(() => {
@@ -58,6 +59,7 @@ describe('KeywordTree component', () => {
5859
data={mockData}
5960
onNodeClick={mockOnNodeClick}
6061
onNodeEdit={mockOnNodeEdit}
62+
onAddNarrower={mockOnAddNarrower}
6163
/>
6264
)
6365

@@ -70,6 +72,7 @@ describe('KeywordTree component', () => {
7072
data={mockData}
7173
onNodeClick={mockOnNodeClick}
7274
onNodeEdit={mockOnNodeEdit}
75+
onAddNarrower={mockOnAddNarrower}
7376
/>
7477
)
7578

@@ -83,6 +86,7 @@ describe('KeywordTree component', () => {
8386
data={[]}
8487
onNodeClick={vi.fn()}
8588
onNodeEdit={vi.fn()}
89+
onAddNarrower={vi.fn()}
8690
/>
8791
)
8892

@@ -103,6 +107,7 @@ describe('KeywordTree component', () => {
103107
data={mockData}
104108
onNodeClick={mockOnNodeClick}
105109
onNodeEdit={mockOnNodeEdit}
110+
onAddNarrower={mockOnAddNarrower}
106111
/>
107112
)
108113

@@ -139,6 +144,7 @@ describe('KeywordTree component', () => {
139144
data={dataWithGrandchildren}
140145
onNodeClick={vi.fn()}
141146
onNodeEdit={vi.fn()}
147+
onAddNarrower={vi.fn()}
142148
/>
143149
)
144150

@@ -181,6 +187,7 @@ describe('KeywordTree component', () => {
181187
data={mockData}
182188
onNodeClick={mockOnNodeClick}
183189
onNodeEdit={mockOnNodeEdit}
190+
onAddNarrower={mockOnAddNarrower}
184191
/>
185192
)
186193

@@ -194,6 +201,7 @@ describe('KeywordTree component', () => {
194201
data={mockData}
195202
onNodeClick={mockOnNodeClick}
196203
onNodeEdit={mockOnNodeEdit}
204+
onAddNarrower={mockOnAddNarrower}
197205
/>
198206
)
199207

@@ -243,6 +251,7 @@ describe('KeywordTree component', () => {
243251
data={nestedData}
244252
onNodeClick={mockOnNodeClick}
245253
onNodeEdit={mockOnNodeEdit}
254+
onAddNarrower={mockOnAddNarrower}
246255
/>
247256
)
248257

@@ -273,6 +282,7 @@ describe('KeywordTree component', () => {
273282
data={mockData}
274283
onNodeClick={mockOnNodeClick}
275284
onNodeEdit={mockOnNodeEdit}
285+
onAddNarrower={mockOnAddNarrower}
276286
/>
277287
)
278288

@@ -286,6 +296,7 @@ describe('KeywordTree component', () => {
286296
data={mockData}
287297
onNodeClick={mockOnNodeClick}
288298
onNodeEdit={mockOnNodeEdit}
299+
onAddNarrower={mockOnAddNarrower}
289300
/>
290301
)
291302

@@ -302,6 +313,7 @@ describe('KeywordTree component', () => {
302313
data={mockData}
303314
onNodeClick={mockOnNodeClick}
304315
onNodeEdit={mockOnNodeEdit}
316+
onAddNarrower={mockOnAddNarrower}
305317
/>
306318
)
307319

@@ -321,6 +333,7 @@ describe('KeywordTree component', () => {
321333
data={mockData}
322334
onNodeClick={mockOnNodeClick}
323335
onNodeEdit={mockOnNodeEdit}
336+
onAddNarrower={mockOnAddNarrower}
324337
/>
325338
)
326339

@@ -345,6 +358,9 @@ describe('KeywordTree component', () => {
345358
await waitFor(() => {
346359
expect(screen.getByText('New Child')).toBeInTheDocument()
347360
})
361+
362+
// Check if onAddNarrower was called
363+
expect(mockOnAddNarrower).toHaveBeenCalled()
348364
})
349365

350366
test('should close "Add Narrower" modal when Cancel is clicked', async () => {
@@ -353,6 +369,7 @@ describe('KeywordTree component', () => {
353369
data={mockData}
354370
onNodeClick={mockOnNodeClick}
355371
onNodeEdit={mockOnNodeEdit}
372+
onAddNarrower={mockOnAddNarrower}
356373
/>
357374
)
358375

@@ -380,6 +397,7 @@ describe('KeywordTree component', () => {
380397
data={mockData}
381398
onNodeClick={mockOnNodeClick}
382399
onNodeEdit={mockOnNodeEdit}
400+
onAddNarrower={mockOnAddNarrower}
383401
/>
384402
)
385403

@@ -422,6 +440,7 @@ describe('KeywordTree component', () => {
422440
data={collapsedData}
423441
onNodeClick={mockOnNodeClick}
424442
onNodeEdit={mockOnNodeEdit}
443+
onAddNarrower={mockOnAddNarrower}
425444
/>
426445
)
427446

@@ -451,6 +470,7 @@ describe('KeywordTree component', () => {
451470
data={mockData}
452471
onNodeClick={mockOnNodeClick}
453472
onNodeEdit={mockOnNodeEdit}
473+
onAddNarrower={mockOnAddNarrower}
454474
/>
455475
)
456476

@@ -489,6 +509,7 @@ describe('KeywordTree component', () => {
489509
data={longTitleData}
490510
onNodeClick={mockOnNodeClick}
491511
onNodeEdit={mockOnNodeEdit}
512+
onAddNarrower={mockOnAddNarrower}
492513
/>
493514
)
494515

@@ -503,6 +524,7 @@ describe('KeywordTree component', () => {
503524
data={mockData}
504525
onNodeClick={mockOnNodeClick}
505526
onNodeEdit={mockOnNodeEdit}
527+
onAddNarrower={mockOnAddNarrower}
506528
/>
507529
)
508530

@@ -524,6 +546,7 @@ describe('KeywordTree component', () => {
524546
data={mockData}
525547
onNodeClick={mockOnNodeClick}
526548
onNodeEdit={mockOnNodeEdit}
549+
onAddNarrower={mockOnAddNarrower}
527550
/>
528551
)
529552

@@ -545,6 +568,7 @@ describe('KeywordTree component', () => {
545568
data={mockData}
546569
onNodeClick={mockOnNodeClick}
547570
onNodeEdit={mockOnNodeEdit}
571+
onAddNarrower={mockOnAddNarrower}
548572
/>
549573
)
550574

@@ -581,6 +605,7 @@ describe('KeywordTree component', () => {
581605
data={mockData}
582606
onNodeClick={mockOnNodeClick}
583607
onNodeEdit={mockOnNodeEdit}
608+
onAddNarrower={mockOnAddNarrower}
584609
/>
585610
)
586611

@@ -635,6 +660,7 @@ describe('KeywordTree component', () => {
635660
data={dataWithLeafNodes}
636661
onNodeClick={mockOnNodeClick}
637662
onNodeEdit={mockOnNodeEdit}
663+
onAddNarrower={mockOnAddNarrower}
638664
/>
639665
)
640666

@@ -684,6 +710,7 @@ describe('KeywordTree component', () => {
684710
data={dataWithEmptyChildren}
685711
onNodeClick={mockOnNodeClick}
686712
onNodeEdit={mockOnNodeEdit}
713+
onAddNarrower={mockOnAddNarrower}
687714
/>
688715
)
689716

@@ -696,6 +723,7 @@ describe('KeywordTree component', () => {
696723
data={mockData}
697724
onNodeClick={mockOnNodeClick}
698725
onNodeEdit={mockOnNodeEdit}
726+
onAddNarrower={mockOnAddNarrower}
699727
/>
700728
)
701729

static/src/js/pages/KeywordManagerPage/KeywordManagerPage.jsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,18 @@ const KeywordManagerPage = () => {
7474
setIsLoading(false)
7575
}
7676
}, [selectedVersion])
77+
78+
const handleAddNarrower = useCallback((parentId, newKeyword) => {
79+
// Create a new keyword data structure for the form
80+
const newKeywordData = {
81+
KeywordUUID: newKeyword.id,
82+
PreferredLabel: newKeyword.title,
83+
BroaderKeyword: parentId
84+
}
85+
86+
// Update the selected keyword data with the new keyword
87+
setSelectedKeywordData(newKeywordData)
88+
}, [])
7789
/**
7890
* Handles the click event on a tree node
7991
* @param {string} nodeId - The id of the clicked node
@@ -162,7 +174,11 @@ const KeywordManagerPage = () => {
162174
}
163175

164176
if (selectedKeywordData) {
165-
return <KeywordForm initialData={selectedKeywordData} />
177+
return (
178+
<KeywordForm
179+
initialData={selectedKeywordData}
180+
/>
181+
)
166182
}
167183

168184
return null
@@ -184,6 +200,7 @@ const KeywordManagerPage = () => {
184200
data={treeData}
185201
onNodeClick={handleNodeClick}
186202
onNodeEdit={handleShowKeyword}
203+
onAddNarrower={handleAddNarrower}
187204
/>
188205
)
189206
}

static/src/js/pages/KeywordManagerPage/__tests__/KeywordManagerPage.test.jsx

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,17 @@ vi.mock('@/js/components/KeywordTree/KeywordTree', () => ({
2525
<div data-testid="keyword-tree">
2626
<pre>{JSON.stringify(props.data, null, 2)}</pre>
2727
<button type="button" onClick={() => props.onNodeClick('test-node-id')}>Click Node</button>
28+
<button
29+
type="button"
30+
onClick={
31+
() => props.onAddNarrower('parent-id', {
32+
id: 'new-id',
33+
title: 'New Keyword'
34+
})
35+
}
36+
>
37+
Add Narrower
38+
</button>
2839
</div>
2940
)
3041
})
@@ -35,11 +46,22 @@ vi.mock('@/js/utils/createFormDataFromRdf')
3546

3647
vi.mock('@/js/components/KeywordForm/KeywordForm', () => ({
3748
__esModule: true,
38-
default: ({ initialData }) => (
49+
default: vi.fn(({ initialData, onFormDataChange }) => (
3950
<div data-testid="keyword-form">
4051
<pre>{JSON.stringify(initialData, null, 2)}</pre>
52+
<button
53+
type="button"
54+
onClick={
55+
() => onFormDataChange({
56+
...initialData,
57+
PreferredLabel: 'Updated Keyword'
58+
})
59+
}
60+
>
61+
Update Form Data
62+
</button>
4163
</div>
42-
)
64+
))
4365
}))
4466

4567
vi.mock('@/js/components/MetadataPreviewPlaceholder/MetadataPreviewPlaceholder', () => ({
@@ -621,5 +643,45 @@ describe('KeywordManagerPage component', () => {
621643
expect(mockCreateFormDataFromRdf).toHaveBeenCalledWith('<rdf:RDF></rdf:RDF>')
622644
})
623645
})
646+
647+
test('should update selected keyword data when handleAddNarrower is called', async () => {
648+
const { user } = setup()
649+
650+
// Select version and scheme
651+
await waitFor(() => {
652+
expect(screen.getByTestId('version-selector')).toBeInTheDocument()
653+
})
654+
655+
const versionSelector = screen.getByTestId('version-selector')
656+
await user.selectOptions(versionSelector, '2.0')
657+
658+
await waitFor(() => {
659+
expect(screen.getByTestId('scheme-selector')).toBeInTheDocument()
660+
})
661+
662+
const schemeSelector = screen.getByTestId('scheme-selector')
663+
await user.selectOptions(schemeSelector, 'scheme1')
664+
665+
// Wait for the tree to load
666+
await waitFor(() => {
667+
expect(screen.getByTestId('keyword-tree')).toBeInTheDocument()
668+
})
669+
670+
// Find and click the "Add Narrower" button
671+
const addNarrowerButton = screen.getByText('Add Narrower')
672+
await user.click(addNarrowerButton)
673+
674+
// Wait for the KeywordForm to be rendered
675+
let keywordFormContent
676+
await waitFor(() => {
677+
keywordFormContent = screen.getByTestId('keyword-form').textContent
678+
expect(keywordFormContent).toBeTruthy()
679+
})
680+
681+
// Now perform the individual assertions
682+
expect(keywordFormContent).toContain('"KeywordUUID": "new-id"')
683+
expect(keywordFormContent).toContain('"PreferredLabel": "New Keyword"')
684+
expect(keywordFormContent).toContain('"BroaderKeyword": "parent-id"')
685+
})
624686
})
625687
})

0 commit comments

Comments
 (0)