@@ -24,9 +24,16 @@ const mockAsyncSelect = jest.fn(({ defaultValue, ...props }) => (
2424 </ div >
2525) ) ;
2626
27+ // Mock Select to capture props
28+ const mockSelect = jest . fn ( ( { defaultValue, ...props } ) => (
29+ < div className = "ac-form-select-container" data-testid = "select" data-default-value = { JSON . stringify ( defaultValue ) } >
30+ Mocked Select
31+ </ div >
32+ ) ) ;
33+
2734jest . mock ( '@atlaskit/select' , ( ) => ( {
2835 __esModule : true ,
29- default : jest . fn ( ) ,
36+ default : ( props : any ) => mockSelect ( props ) ,
3037 AsyncSelect : ( props : any ) => mockAsyncSelect ( props ) ,
3138 components : {
3239 Option : ( { children, ...props } : any ) => < div { ...props } > { children } </ div > ,
@@ -80,6 +87,33 @@ class TestIssueEditorPage extends AbstractIssueEditorPage<
8087 return this . getInputMarkup ( parentField , editMode , currentIssueType ) ;
8188 }
8289
90+ renderIssueLinksField ( editMode : boolean = false ) {
91+ const issueLinksField : FieldUI = {
92+ key : 'issuelinks' ,
93+ name : 'Linked Issues' ,
94+ required : false ,
95+ uiType : UIType . IssueLinks ,
96+ displayOrder : 2 ,
97+ valueType : ValueType . IssueLinks ,
98+ advanced : true ,
99+ isArray : true ,
100+ schema : 'issuelinks' ,
101+ } ;
102+
103+ const currentIssueType : IssueType = {
104+ id : '1' ,
105+ name : 'Story' ,
106+ iconUrl : 'story-icon.png' ,
107+ subtask : false ,
108+ avatarId : 1 ,
109+ description : 'Story issue type' ,
110+ self : 'https://test.atlassian.net/rest/api/3/issuetype/1' ,
111+ epic : false ,
112+ } ;
113+
114+ return this . getInputMarkup ( issueLinksField , editMode , currentIssueType ) ;
115+ }
116+
83117 override render ( ) {
84118 return < div data-testid = "test-container" > { this . renderParentField ( false ) } </ div > ;
85119 }
@@ -116,6 +150,7 @@ describe('AbstractIssueEditorPage', () => {
116150 beforeEach ( ( ) => {
117151 jest . clearAllMocks ( ) ;
118152 mockAsyncSelect . mockClear ( ) ;
153+ mockSelect . mockClear ( ) ;
119154 } ) ;
120155
121156 describe ( 'Parent Field' , ( ) => {
@@ -224,4 +259,155 @@ describe('AbstractIssueEditorPage', () => {
224259 } ) ;
225260 } ) ;
226261 } ) ;
262+
263+ describe ( 'IssueLinks Field (Linked Issues)' , ( ) => {
264+ describe ( 'Non-Edit Mode (Create Issue)' , ( ) => {
265+ class TestIssueEditorPageIssueLinks extends TestIssueEditorPage {
266+ override render ( ) {
267+ return < div data-testid = "test-container" > { this . renderIssueLinksField ( false ) } </ div > ;
268+ }
269+ }
270+
271+ it ( 'should pass link type value as defaultValue to Select when issuelinks.type exists' , ( ) => {
272+ const linkTypeValue = {
273+ id : '10000' ,
274+ name : 'Blocks' ,
275+ inward : 'is blocked by' ,
276+ outward : 'blocks' ,
277+ type : 'outward' ,
278+ } ;
279+
280+ const component = new TestIssueEditorPageIssueLinks ( { } ) ;
281+ component . state = {
282+ ...emptyCommonEditorState ,
283+ siteDetails : mockSiteDetailsCloud ,
284+ fieldValues : {
285+ issuelinks : {
286+ type : linkTypeValue ,
287+ } ,
288+ project : { key : 'TEST' } ,
289+ } ,
290+ selectFieldOptions : {
291+ issuelinks : [ linkTypeValue ] ,
292+ } ,
293+ } ;
294+
295+ render ( component . render ( ) ) ;
296+
297+ // Check that Select was called with correct defaultValue for link type
298+ expect ( mockSelect ) . toHaveBeenCalled ( ) ;
299+ const selectCallArgs = mockSelect . mock . calls [ 0 ] [ 0 ] ;
300+ expect ( selectCallArgs . defaultValue ) . toEqual ( linkTypeValue ) ;
301+ } ) ;
302+
303+ it ( 'should pass undefined as defaultValue to Select when issuelinks.type is missing' , ( ) => {
304+ const component = new TestIssueEditorPageIssueLinks ( { } ) ;
305+ component . state = {
306+ ...emptyCommonEditorState ,
307+ siteDetails : mockSiteDetailsCloud ,
308+ fieldValues : {
309+ project : { key : 'TEST' } ,
310+ } ,
311+ selectFieldOptions : {
312+ issuelinks : [ ] ,
313+ } ,
314+ } ;
315+
316+ render ( component . render ( ) ) ;
317+
318+ // Check that Select was called with undefined defaultValue
319+ expect ( mockSelect ) . toHaveBeenCalled ( ) ;
320+ const selectCallArgs = mockSelect . mock . calls [ 0 ] [ 0 ] ;
321+ expect ( selectCallArgs . defaultValue ) . toBeUndefined ( ) ;
322+ } ) ;
323+
324+ it ( 'should pass linked issues array as defaultValue to AsyncSelect when issuelinks.issue exists' , ( ) => {
325+ const linkedIssues = [
326+ { key : 'PROJ-123' , summary : 'First linked issue' } ,
327+ { key : 'PROJ-456' , summary : 'Second linked issue' } ,
328+ ] ;
329+
330+ const component = new TestIssueEditorPageIssueLinks ( { } ) ;
331+ component . state = {
332+ ...emptyCommonEditorState ,
333+ siteDetails : mockSiteDetailsCloud ,
334+ fieldValues : {
335+ issuelinks : {
336+ issue : linkedIssues ,
337+ } ,
338+ project : { key : 'TEST' } ,
339+ } ,
340+ selectFieldOptions : {
341+ issuelinks : [ ] ,
342+ } ,
343+ } ;
344+
345+ render ( component . render ( ) ) ;
346+
347+ // Check that AsyncSelect was called with correct defaultValue for linked issues
348+ expect ( mockAsyncSelect ) . toHaveBeenCalled ( ) ;
349+ const asyncSelectCallArgs = mockAsyncSelect . mock . calls [ 0 ] [ 0 ] ;
350+ expect ( asyncSelectCallArgs . defaultValue ) . toEqual ( linkedIssues ) ;
351+ } ) ;
352+
353+ it ( 'should pass undefined as defaultValue to AsyncSelect when issuelinks.issue is missing' , ( ) => {
354+ const component = new TestIssueEditorPageIssueLinks ( { } ) ;
355+ component . state = {
356+ ...emptyCommonEditorState ,
357+ siteDetails : mockSiteDetailsCloud ,
358+ fieldValues : {
359+ project : { key : 'TEST' } ,
360+ } ,
361+ selectFieldOptions : {
362+ issuelinks : [ ] ,
363+ } ,
364+ } ;
365+
366+ render ( component . render ( ) ) ;
367+
368+ // Check that AsyncSelect was called with undefined defaultValue
369+ expect ( mockAsyncSelect ) . toHaveBeenCalled ( ) ;
370+ const asyncSelectCallArgs = mockAsyncSelect . mock . calls [ 0 ] [ 0 ] ;
371+ expect ( asyncSelectCallArgs . defaultValue ) . toBeUndefined ( ) ;
372+ } ) ;
373+
374+ it ( 'should preserve both link type and linked issues when re-rendering' , ( ) => {
375+ const linkTypeValue = {
376+ id : '10001' ,
377+ name : 'Relates' ,
378+ inward : 'relates to' ,
379+ outward : 'relates to' ,
380+ type : 'inward' ,
381+ } ;
382+
383+ const linkedIssues = [ { key : 'PROJ-789' , summary : 'Related issue' } ] ;
384+
385+ const component = new TestIssueEditorPageIssueLinks ( { } ) ;
386+ component . state = {
387+ ...emptyCommonEditorState ,
388+ siteDetails : mockSiteDetailsCloud ,
389+ fieldValues : {
390+ issuelinks : {
391+ type : linkTypeValue ,
392+ issue : linkedIssues ,
393+ } ,
394+ project : { key : 'TEST' } ,
395+ } ,
396+ selectFieldOptions : {
397+ issuelinks : [ linkTypeValue ] ,
398+ } ,
399+ } ;
400+
401+ render ( component . render ( ) ) ;
402+
403+ expect ( mockSelect ) . toHaveBeenCalled ( ) ;
404+ const selectCallArgs = mockSelect . mock . calls [ 0 ] [ 0 ] ;
405+ expect ( selectCallArgs . defaultValue ) . toEqual ( linkTypeValue ) ;
406+
407+ expect ( mockAsyncSelect ) . toHaveBeenCalled ( ) ;
408+ const asyncSelectCallArgs = mockAsyncSelect . mock . calls [ 0 ] [ 0 ] ;
409+ expect ( asyncSelectCallArgs . defaultValue ) . toEqual ( linkedIssues ) ;
410+ } ) ;
411+ } ) ;
412+ } ) ;
227413} ) ;
0 commit comments