Skip to content

Commit 1b30aa1

Browse files
committed
refactor(datamapper): split field context menu hooks and tests
Fixes #3165
1 parent a11fd2a commit 1b30aa1

8 files changed

Lines changed: 1029 additions & 849 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { ReactNode } from 'react';
2+
3+
import { MenuGroup } from '../FieldContextMenu';
4+
5+
export interface MenuContributor {
6+
groups: MenuGroup[];
7+
modals: ReactNode;
8+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { NodeData } from '../../../../models/datamapper/visualization';
2+
import { MenuContributor } from './types';
3+
4+
export function useAbstractFieldMenu(_nodeData: NodeData): MenuContributor {
5+
return {
6+
groups: [],
7+
modals: null,
8+
};
9+
}
10+
11+
// Made with Bob
Lines changed: 378 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,378 @@
1+
import { act, fireEvent, render, screen } from '@testing-library/react';
2+
import { FunctionComponent, PropsWithChildren } from 'react';
3+
4+
import { DocumentTree } from '../../../../models/datamapper/document-tree';
5+
import { Types } from '../../../../models/datamapper/types';
6+
import { DocumentNodeData } from '../../../../models/datamapper/visualization';
7+
import { MappingLinksProvider } from '../../../../providers/data-mapping-links.provider';
8+
import { DataMapperProvider } from '../../../../providers/datamapper.provider';
9+
import { ChoiceSelectionService } from '../../../../services/document/choice-selection.service';
10+
import { TreeParsingService } from '../../../../services/visualization/tree-parsing.service';
11+
import { TestUtil } from '../../../../stubs/datamapper/data-mapper';
12+
import { SourceDocumentNodeWithContextMenu } from '../../SourceDocumentNode';
13+
14+
describe('useChoiceContextMenu', () => {
15+
const wrapper: FunctionComponent<PropsWithChildren> = ({ children }) => (
16+
<DataMapperProvider>
17+
<MappingLinksProvider>{children}</MappingLinksProvider>
18+
</DataMapperProvider>
19+
);
20+
21+
const createChoiceFieldNode = (selectMember = false) => {
22+
const document = TestUtil.createSourceOrderDoc();
23+
const parentField = document.fields[0];
24+
const choiceField = {
25+
name: 'contactChoice',
26+
displayName: 'Contact Choice',
27+
type: Types.Container,
28+
wrapperKind: 'choice' as const,
29+
namedTypeFragmentRefs: [],
30+
parent: parentField,
31+
ownerDocument: document,
32+
fields: [] as Record<string, unknown>[],
33+
selectedMemberIndex: selectMember ? 1 : undefined,
34+
};
35+
const members = [
36+
{
37+
name: 'email',
38+
displayName: 'Email',
39+
type: Types.String,
40+
fields: [],
41+
namedTypeFragmentRefs: [],
42+
parent: choiceField,
43+
ownerDocument: document,
44+
},
45+
{
46+
name: 'phone',
47+
displayName: 'Phone',
48+
type: Types.String,
49+
fields: [],
50+
namedTypeFragmentRefs: [],
51+
parent: choiceField,
52+
ownerDocument: document,
53+
},
54+
{
55+
name: 'fax',
56+
displayName: 'Fax',
57+
type: Types.String,
58+
fields: [],
59+
namedTypeFragmentRefs: [],
60+
parent: choiceField,
61+
ownerDocument: document,
62+
},
63+
];
64+
choiceField.fields = members;
65+
parentField.fields.push(choiceField as never);
66+
67+
const documentNodeData = new DocumentNodeData(document);
68+
const tree = new DocumentTree(documentNodeData);
69+
TreeParsingService.parseTree(tree);
70+
const orderNode = tree.root.children[0];
71+
const lastChild = orderNode.children.length - 1;
72+
const choiceNode = orderNode.children[lastChild];
73+
return { document, documentNodeData, choiceNode, choiceField };
74+
};
75+
76+
const createLargeChoiceFieldNode = (size = 11) => {
77+
const document = TestUtil.createSourceOrderDoc();
78+
const parentField = document.fields[0];
79+
const choiceField = {
80+
name: 'largeChoice',
81+
displayName: 'Large Choice',
82+
type: Types.Container,
83+
wrapperKind: 'choice' as const,
84+
namedTypeFragmentRefs: [],
85+
parent: parentField,
86+
ownerDocument: document,
87+
fields: [] as Record<string, unknown>[],
88+
selectedMemberIndex: undefined,
89+
};
90+
const members = Array.from({ length: size }, (_, i) => ({
91+
name: `member${i}`,
92+
displayName: `Member ${i}`,
93+
type: Types.String,
94+
fields: [],
95+
namedTypeFragmentRefs: [],
96+
parent: choiceField,
97+
ownerDocument: document,
98+
}));
99+
choiceField.fields = members;
100+
parentField.fields.push(choiceField as never);
101+
102+
const documentNodeData = new DocumentNodeData(document);
103+
const tree = new DocumentTree(documentNodeData);
104+
TreeParsingService.parseTree(tree);
105+
const orderNode = tree.root.children[0];
106+
const lastChild = orderNode.children.length - 1;
107+
const choiceNode = orderNode.children[lastChild];
108+
return { documentNodeData, choiceNode };
109+
};
110+
111+
it('should show choice members inline for unselected choice wrapper (Case A)', () => {
112+
const { documentNodeData, choiceNode } = createChoiceFieldNode(false);
113+
114+
render(
115+
<SourceDocumentNodeWithContextMenu
116+
treeNode={choiceNode}
117+
documentId={documentNodeData.id}
118+
isReadOnly={false}
119+
rank={1}
120+
/>,
121+
{ wrapper },
122+
);
123+
124+
act(() => {
125+
fireEvent.contextMenu(screen.getByTestId(`node-source-${choiceNode.nodeData.id}`));
126+
});
127+
128+
expect(screen.getByText('Email')).toBeInTheDocument();
129+
expect(screen.getByText('Phone')).toBeInTheDocument();
130+
expect(screen.getByText('Fax')).toBeInTheDocument();
131+
expect(screen.queryByText('Show All Choice Options')).not.toBeInTheDocument();
132+
});
133+
134+
it('should show Show All Choice Options and Override for selected choice (Case B)', () => {
135+
const { documentNodeData, choiceNode } = createChoiceFieldNode(true);
136+
137+
render(
138+
<SourceDocumentNodeWithContextMenu
139+
treeNode={choiceNode}
140+
documentId={documentNodeData.id}
141+
isReadOnly={false}
142+
rank={1}
143+
/>,
144+
{ wrapper },
145+
);
146+
147+
act(() => {
148+
fireEvent.contextMenu(screen.getByTestId(`node-source-${choiceNode.nodeData.id}`));
149+
});
150+
151+
expect(screen.getByText('Show All Choice Options')).toBeInTheDocument();
152+
expect(screen.getByText('Override Field...')).toBeInTheDocument();
153+
});
154+
155+
it('should call setChoiceSelection when clicking a choice member', () => {
156+
const { documentNodeData, choiceNode, choiceField } = createChoiceFieldNode(false);
157+
158+
const setSpy = jest.spyOn(ChoiceSelectionService, 'setChoiceSelection');
159+
160+
render(
161+
<SourceDocumentNodeWithContextMenu
162+
treeNode={choiceNode}
163+
documentId={documentNodeData.id}
164+
isReadOnly={false}
165+
rank={1}
166+
/>,
167+
{ wrapper },
168+
);
169+
170+
act(() => {
171+
fireEvent.contextMenu(screen.getByTestId(`node-source-${choiceNode.nodeData.id}`));
172+
});
173+
174+
act(() => {
175+
fireEvent.click(screen.getByText('Phone'));
176+
});
177+
178+
expect(setSpy).toHaveBeenCalledWith(expect.any(Object), choiceField, 1, expect.any(Object));
179+
setSpy.mockRestore();
180+
});
181+
182+
it('should call clearChoiceSelection when clicking Show All Choice Options', () => {
183+
const { documentNodeData, choiceNode, choiceField } = createChoiceFieldNode(false);
184+
choiceField.selectedMemberIndex = 1;
185+
186+
const clearSpy = jest.spyOn(ChoiceSelectionService, 'clearChoiceSelection');
187+
188+
render(
189+
<SourceDocumentNodeWithContextMenu
190+
treeNode={choiceNode}
191+
documentId={documentNodeData.id}
192+
isReadOnly={false}
193+
rank={1}
194+
/>,
195+
{ wrapper },
196+
);
197+
198+
act(() => {
199+
fireEvent.contextMenu(screen.getByTestId(`node-source-${choiceNode.nodeData.id}`));
200+
});
201+
202+
act(() => {
203+
fireEvent.click(screen.getByText('Show All Choice Options'));
204+
});
205+
206+
expect(clearSpy).toHaveBeenCalledWith(expect.any(Object), choiceField, expect.any(Object));
207+
clearSpy.mockRestore();
208+
});
209+
210+
it('should show Select Member... for choice with more than 10 members (Case A modal)', () => {
211+
const { documentNodeData, choiceNode } = createLargeChoiceFieldNode();
212+
213+
render(
214+
<SourceDocumentNodeWithContextMenu
215+
treeNode={choiceNode}
216+
documentId={documentNodeData.id}
217+
isReadOnly={false}
218+
rank={1}
219+
/>,
220+
{ wrapper },
221+
);
222+
223+
act(() => {
224+
fireEvent.contextMenu(screen.getByTestId(`node-source-${choiceNode.nodeData.id}`));
225+
});
226+
227+
expect(screen.getByText('Select Member...')).toBeInTheDocument();
228+
expect(screen.queryByText('Member 0')).not.toBeInTheDocument();
229+
expect(screen.queryByText('Show All Choice Options')).not.toBeInTheDocument();
230+
});
231+
232+
it('should open ChoiceSelectionModal when clicking Select Member...', () => {
233+
const { documentNodeData, choiceNode } = createLargeChoiceFieldNode();
234+
235+
render(
236+
<SourceDocumentNodeWithContextMenu
237+
treeNode={choiceNode}
238+
documentId={documentNodeData.id}
239+
isReadOnly={false}
240+
rank={1}
241+
/>,
242+
{ wrapper },
243+
);
244+
245+
act(() => {
246+
fireEvent.contextMenu(screen.getByTestId(`node-source-${choiceNode.nodeData.id}`));
247+
});
248+
249+
act(() => {
250+
fireEvent.click(screen.getByText('Select Member...'));
251+
});
252+
253+
expect(screen.getByText('Choice: Large Choice')).toBeInTheDocument();
254+
});
255+
256+
it('should show empty menu for choice wrapper with no members and no selection', () => {
257+
const document = TestUtil.createSourceOrderDoc();
258+
const parentField = document.fields[0];
259+
const choiceField = {
260+
name: 'emptyChoice',
261+
displayName: 'Empty Choice',
262+
type: Types.Container,
263+
wrapperKind: 'choice' as const,
264+
namedTypeFragmentRefs: [],
265+
parent: parentField,
266+
ownerDocument: document,
267+
fields: [],
268+
selectedMemberIndex: undefined,
269+
};
270+
parentField.fields.push(choiceField as never);
271+
272+
const documentNodeData = new DocumentNodeData(document);
273+
const tree = new DocumentTree(documentNodeData);
274+
TreeParsingService.parseTree(tree);
275+
const orderNode = tree.root.children[0];
276+
const lastChild = orderNode.children.length - 1;
277+
const choiceNode = orderNode.children[lastChild];
278+
279+
render(
280+
<SourceDocumentNodeWithContextMenu
281+
treeNode={choiceNode}
282+
documentId={documentNodeData.id}
283+
isReadOnly={false}
284+
rank={1}
285+
/>,
286+
{ wrapper },
287+
);
288+
289+
act(() => {
290+
fireEvent.contextMenu(screen.getByTestId(`node-source-${choiceNode.nodeData.id}`));
291+
});
292+
293+
expect(screen.queryByText('Show All Choice Options')).not.toBeInTheDocument();
294+
expect(screen.queryByText('Override Field...')).not.toBeInTheDocument();
295+
});
296+
297+
it('should close ChoiceSelectionModal when Cancel is clicked', () => {
298+
const { documentNodeData, choiceNode } = createLargeChoiceFieldNode();
299+
300+
render(
301+
<SourceDocumentNodeWithContextMenu
302+
treeNode={choiceNode}
303+
documentId={documentNodeData.id}
304+
isReadOnly={false}
305+
rank={1}
306+
/>,
307+
{ wrapper },
308+
);
309+
310+
act(() => {
311+
fireEvent.contextMenu(screen.getByTestId(`node-source-${choiceNode.nodeData.id}`));
312+
});
313+
314+
act(() => {
315+
fireEvent.click(screen.getByText('Select Member...'));
316+
});
317+
318+
expect(screen.getByText('Choice: Large Choice')).toBeInTheDocument();
319+
320+
act(() => {
321+
fireEvent.click(screen.getByRole('button', { name: 'Cancel' }));
322+
});
323+
324+
expect(screen.queryByText('Choice: Large Choice')).not.toBeInTheDocument();
325+
});
326+
327+
it('should show Select action for choice member child (Case C)', () => {
328+
const { documentNodeData, choiceNode } = createChoiceFieldNode(false);
329+
const memberNode = choiceNode.children[0];
330+
331+
render(
332+
<SourceDocumentNodeWithContextMenu
333+
treeNode={memberNode}
334+
documentId={documentNodeData.id}
335+
isReadOnly={false}
336+
rank={1}
337+
/>,
338+
{ wrapper },
339+
);
340+
341+
act(() => {
342+
fireEvent.contextMenu(screen.getByTestId(`node-source-${memberNode.nodeData.id}`));
343+
});
344+
345+
expect(screen.getByText("Select 'Email' in '(Email | Phone | Fax)'")).toBeInTheDocument();
346+
expect(screen.getByText('Override Field...')).toBeInTheDocument();
347+
});
348+
349+
it('should call setChoiceSelection when clicking Select action on choice member (Case C)', () => {
350+
const { documentNodeData, choiceNode, choiceField } = createChoiceFieldNode(false);
351+
const memberNode = choiceNode.children[1];
352+
353+
const setSpy = jest.spyOn(ChoiceSelectionService, 'setChoiceSelection').mockImplementation(jest.fn());
354+
355+
render(
356+
<SourceDocumentNodeWithContextMenu
357+
treeNode={memberNode}
358+
documentId={documentNodeData.id}
359+
isReadOnly={false}
360+
rank={1}
361+
/>,
362+
{ wrapper },
363+
);
364+
365+
act(() => {
366+
fireEvent.contextMenu(screen.getByTestId(`node-source-${memberNode.nodeData.id}`));
367+
});
368+
369+
act(() => {
370+
fireEvent.click(screen.getByText(/Select 'Phone'/));
371+
});
372+
373+
expect(setSpy).toHaveBeenCalledWith(expect.any(Object), choiceField, 1, expect.any(Object));
374+
setSpy.mockRestore();
375+
});
376+
});
377+
378+
// Made with Bob

0 commit comments

Comments
 (0)