Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
46bac3c3
Commit
46bac3c3
authored
Jun 27, 2019
by
Sam Bigelow
Committed by
Mike Greiling
Jun 27, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Extract input of related issues form
This will be used in blocking merge requests later.
parent
cf0fd305
Changes
7
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
364 additions
and
307 deletions
+364
-307
ee/app/assets/javascripts/related_issues/components/add_issuable_form.vue
...vascripts/related_issues/components/add_issuable_form.vue
+25
-139
ee/app/assets/javascripts/related_issues/components/related_issuable_input.vue
...ipts/related_issues/components/related_issuable_input.vue
+192
-0
ee/app/assets/javascripts/related_issues/components/related_issues_root.vue
...scripts/related_issues/components/related_issues_root.vue
+2
-1
ee/app/assets/javascripts/related_issues/constants.js
ee/app/assets/javascripts/related_issues/constants.js
+21
-16
ee/spec/frontend/related_issues/components/related_issuable_input_spec.js
.../related_issues/components/related_issuable_input_spec.js
+114
-0
ee/spec/javascripts/issuable/related_issues/components/add_issuable_form_spec.js
...uable/related_issues/components/add_issuable_form_spec.js
+7
-151
qa/qa/ee/page/group/epic/show.rb
qa/qa/ee/page/group/epic/show.rb
+3
-0
No files found.
ee/app/assets/javascripts/related_issues/components/add_issuable_form.vue
View file @
46bac3c3
<
script
>
<
script
>
import
$
from
'
jquery
'
;
import
GfmAutoComplete
from
'
ee_else_ce/gfm_auto_complete
'
;
import
{
GlLoadingIcon
}
from
'
@gitlab/ui
'
;
import
{
GlLoadingIcon
}
from
'
@gitlab/ui
'
;
import
issueToken
from
'
./issue_token.vue
'
;
import
RelatedIssuableInput
from
'
./related_issuable_input.vue
'
;
import
{
autoCompleteTextMap
,
inputPlaceholderTextMap
}
from
'
../constants
'
;
import
{
issuableTypesMap
}
from
'
../constants
'
;
const
SPACE_FACTOR
=
1
;
export
default
{
export
default
{
name
:
'
AddIssuableForm
'
,
name
:
'
AddIssuableForm
'
,
components
:
{
components
:
{
issueToken
,
GlLoadingIcon
,
GlLoadingIcon
,
RelatedIssuableInput
,
},
},
props
:
{
props
:
{
inputValue
:
{
inputValue
:
{
...
@@ -40,162 +36,52 @@ export default {
...
@@ -40,162 +36,52 @@ export default {
issuableType
:
{
issuableType
:
{
type
:
String
,
type
:
String
,
required
:
false
,
required
:
false
,
default
:
'
issue
'
,
default
:
issuableTypesMap
.
ISSUE
,
},
},
},
data
()
{
return
{
isInputFocused
:
false
,
isAutoCompleteOpen
:
false
,
};
},
},
computed
:
{
computed
:
{
inputPlaceholder
()
{
const
{
issuableType
,
allowAutoComplete
}
=
this
;
const
allowAutoCompleteText
=
autoCompleteTextMap
[
allowAutoComplete
][
issuableType
];
return
`
${
inputPlaceholderTextMap
[
issuableType
]}${
allowAutoCompleteText
}
`
;
},
isSubmitButtonDisabled
()
{
isSubmitButtonDisabled
()
{
return
(
return
(
(
this
.
inputValue
.
length
===
0
&&
this
.
pendingReferences
.
length
===
0
)
||
this
.
isSubmitting
(
this
.
inputValue
.
length
===
0
&&
this
.
pendingReferences
.
length
===
0
)
||
this
.
isSubmitting
);
);
},
},
allowAutoComplete
()
{
return
Object
.
keys
(
this
.
autoCompleteSources
).
length
>
0
;
},
},
},
mounted
()
{
const
$input
=
$
(
this
.
$refs
.
input
);
if
(
this
.
allowAutoComplete
)
{
this
.
gfmAutoComplete
=
new
GfmAutoComplete
(
this
.
autoCompleteSources
);
this
.
gfmAutoComplete
.
setup
(
$input
,
{
issues
:
true
,
epics
:
true
,
});
$input
.
on
(
'
shown-issues.atwho
'
,
this
.
onAutoCompleteToggled
.
bind
(
this
,
true
));
$input
.
on
(
'
hidden-issues.atwho
'
,
this
.
onAutoCompleteToggled
.
bind
(
this
,
false
));
}
this
.
$refs
.
input
.
focus
();
},
beforeDestroy
()
{
const
$input
=
$
(
this
.
$refs
.
input
);
$input
.
off
(
'
shown-issues.atwho
'
);
$input
.
off
(
'
hidden-issues.atwho
'
);
$input
.
off
(
'
inserted-issues.atwho
'
,
this
.
onInput
);
},
methods
:
{
methods
:
{
onInput
()
{
const
{
value
}
=
this
.
$refs
.
input
;
const
caretPos
=
$
(
this
.
$refs
.
input
).
caret
(
'
pos
'
);
const
rawRefs
=
value
.
split
(
/
\s
/
);
let
touchedReference
;
let
position
=
0
;
const
untouchedRawRefs
=
rawRefs
.
filter
(
ref
=>
{
let
isTouched
=
false
;
if
(
caretPos
>=
position
&&
caretPos
<=
position
+
ref
.
length
)
{
touchedReference
=
ref
;
isTouched
=
true
;
}
// `+ SPACE_FACTOR` to factor in the missing space we split at earlier
position
=
position
+
ref
.
length
+
SPACE_FACTOR
;
return
!
isTouched
;
})
.
filter
(
ref
=>
ref
.
trim
().
length
>
0
);
this
.
$emit
(
'
addIssuableFormInput
'
,
{
newValue
:
value
,
untouchedRawReferences
:
untouchedRawRefs
,
touchedReference
,
caretPos
,
});
},
onFocus
()
{
this
.
isInputFocused
=
true
;
},
onBlur
()
{
this
.
isInputFocused
=
false
;
// Avoid tokenizing partial input when clicking an autocomplete item
if
(
!
this
.
isAutoCompleteOpen
)
{
const
{
value
}
=
this
.
$refs
.
input
;
this
.
$emit
(
'
addIssuableFormBlur
'
,
value
);
}
},
onAutoCompleteToggled
(
isOpen
)
{
this
.
isAutoCompleteOpen
=
isOpen
;
},
onInputWrapperClick
()
{
this
.
$refs
.
input
.
focus
();
},
onPendingIssuableRemoveRequest
(
params
)
{
onPendingIssuableRemoveRequest
(
params
)
{
this
.
$emit
(
'
pendingIssuableRemoveRequest
'
,
params
);
this
.
$emit
(
'
pendingIssuableRemoveRequest
'
,
params
);
},
},
onFormSubmit
()
{
onFormSubmit
()
{
const
{
value
}
=
this
.
$refs
.
input
;
this
.
$emit
(
'
addIssuableFormSubmit
'
,
this
.
$refs
.
relatedIssuableInput
.
$refs
.
input
.
value
);
this
.
$emit
(
'
addIssuableFormSubmit
'
,
value
);
},
},
onFormCancel
()
{
onFormCancel
()
{
this
.
$emit
(
'
addIssuableFormCancel
'
);
this
.
$emit
(
'
addIssuableFormCancel
'
);
},
},
onAddIssuableFormInput
(
params
)
{
this
.
$emit
(
'
addIssuableFormInput
'
,
params
);
},
onAddIssuableFormBlur
(
params
)
{
this
.
$emit
(
'
addIssuableFormBlur
'
,
params
);
},
},
},
};
};
</
script
>
</
script
>
<
template
>
<
template
>
<form
@
submit.prevent=
"onFormSubmit"
>
<form
@
submit.prevent=
"onFormSubmit"
>
<div
<related-issuable-input
ref=
"issuableFormWrapper"
ref=
"relatedIssuableInput"
:class=
"
{ focus: isInputFocused }"
:focus-on-mount=
"true"
class="add-issuable-form-input-wrapper form-control"
:references=
"pendingReferences"
role="button"
@click="onInputWrapperClick"
>
<ul
class=
"add-issuable-form-input-token-list"
>
<!--
We need to ensure this key changes any time the pendingReferences array is updated
else two consecutive pending ref strings in an array with the same name will collide
and cause odd behavior when one is removed.
-->
<li
v-for=
"(reference, index) in pendingReferences"
:key=
"`related-issues-token-$
{reference}`"
class="js-add-issuable-form-token-list-item add-issuable-form-token-list-item"
>
<issue-token
:id-key=
"index"
:display-reference=
"reference"
:can-remove=
"true"
:is-condensed=
"true"
:path-id-separator=
"pathIdSeparator"
:path-id-separator=
"pathIdSeparator"
event-namespace=
"pendingIssuable"
:input-value=
"inputValue"
:auto-complete-sources=
"autoCompleteSources"
:auto-complete-options=
"
{ issues: true, epics: true }"
:issuable-type="issuableType"
@pendingIssuableRemoveRequest="onPendingIssuableRemoveRequest"
@pendingIssuableRemoveRequest="onPendingIssuableRemoveRequest"
@formCancel="onFormCancel"
@addIssuableFormBlur="onAddIssuableFormBlur"
@addIssuableFormInput="onAddIssuableFormInput"
/>
/>
</li>
<li
class=
"add-issuable-form-input-list-item"
>
<input
ref=
"input"
:value=
"inputValue"
:placeholder=
"inputPlaceholder"
type=
"text"
class=
"js-add-issuable-form-input add-issuable-form-input qa-add-issue-input"
@
input=
"onInput"
@
focus=
"onFocus"
@
blur=
"onBlur"
@
keyup.escape.exact=
"onFormCancel"
/>
</li>
</ul>
</div>
<div
class=
"add-issuable-form-actions clearfix"
>
<div
class=
"add-issuable-form-actions clearfix"
>
<button
<button
ref=
"addButton"
ref=
"addButton"
...
...
ee/app/assets/javascripts/related_issues/components/related_issuable_input.vue
0 → 100644
View file @
46bac3c3
<
script
>
import
$
from
'
jquery
'
;
import
GfmAutoComplete
from
'
ee_else_ce/gfm_auto_complete
'
;
import
issueToken
from
'
./issue_token.vue
'
;
import
{
autoCompleteTextMap
,
inputPlaceholderTextMap
,
issuableTypesMap
}
from
'
../constants
'
;
const
SPACE_FACTOR
=
1
;
export
default
{
name
:
'
RelatedIssuableInput
'
,
components
:
{
issueToken
,
},
props
:
{
references
:
{
type
:
Array
,
required
:
false
,
default
:
()
=>
[],
},
pathIdSeparator
:
{
type
:
String
,
required
:
true
,
},
inputValue
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
focusOnMount
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
autoCompleteSources
:
{
type
:
Object
,
required
:
false
,
default
:
()
=>
({}),
},
autoCompleteOptions
:
{
type
:
Object
,
required
:
false
,
default
:
()
=>
({}),
},
issuableType
:
{
type
:
String
,
required
:
false
,
default
:
issuableTypesMap
.
ISSUE
,
},
},
data
()
{
return
{
isInputFocused
:
false
,
isAutoCompleteOpen
:
false
,
};
},
computed
:
{
inputPlaceholder
()
{
const
{
issuableType
,
allowAutoComplete
}
=
this
;
const
allowAutoCompleteText
=
autoCompleteTextMap
[
allowAutoComplete
][
issuableType
];
return
`
${
inputPlaceholderTextMap
[
issuableType
]}${
allowAutoCompleteText
}
`
;
},
allowAutoComplete
()
{
return
Object
.
keys
(
this
.
autoCompleteSources
).
length
>
0
;
},
},
mounted
()
{
this
.
setupAutoComplete
();
if
(
this
.
focusOnMount
)
{
this
.
$refs
.
input
.
focus
();
}
},
beforeDestroy
()
{
const
$input
=
$
(
this
.
$refs
.
input
);
$input
.
off
(
'
shown-issues.atwho
'
);
$input
.
off
(
'
hidden-issues.atwho
'
);
$input
.
off
(
'
inserted-issues.atwho
'
,
this
.
onInput
);
},
methods
:
{
onAutoCompleteToggled
(
isOpen
)
{
this
.
isAutoCompleteOpen
=
isOpen
;
},
onInputWrapperClick
()
{
this
.
$refs
.
input
.
focus
();
},
onInput
()
{
const
{
value
}
=
this
.
$refs
.
input
;
const
caretPos
=
this
.
$refs
.
input
.
selectionStart
;
const
rawRefs
=
value
.
split
(
/
\s
/
);
let
touchedReference
;
let
position
=
0
;
const
untouchedRawRefs
=
rawRefs
.
filter
(
ref
=>
{
let
isTouched
=
false
;
if
(
caretPos
>=
position
&&
caretPos
<=
position
+
ref
.
length
)
{
touchedReference
=
ref
;
isTouched
=
true
;
}
position
=
position
+
ref
.
length
+
SPACE_FACTOR
;
return
!
isTouched
;
})
.
filter
(
ref
=>
ref
.
trim
().
length
>
0
);
this
.
$emit
(
'
addIssuableFormInput
'
,
{
newValue
:
value
,
untouchedRawReferences
:
untouchedRawRefs
,
touchedReference
,
caretPos
,
});
},
onBlur
()
{
this
.
isInputFocused
=
false
;
// Avoid tokenizing partial input when clicking an autocomplete item
if
(
!
this
.
isAutoCompleteOpen
)
{
const
{
value
}
=
this
.
$refs
.
input
;
this
.
$emit
(
'
addIssuableFormBlur
'
,
value
);
}
},
onFocus
()
{
this
.
isInputFocused
=
true
;
},
setupAutoComplete
()
{
const
$input
=
$
(
this
.
$refs
.
input
);
if
(
this
.
allowAutoComplete
)
{
this
.
gfmAutoComplete
=
new
GfmAutoComplete
(
this
.
autoCompleteSources
);
this
.
gfmAutoComplete
.
setup
(
$input
,
this
.
autoCompleteOptions
);
}
$input
.
on
(
'
shown-issues.atwho
'
,
this
.
onAutoCompleteToggled
.
bind
(
this
,
true
));
$input
.
on
(
'
hidden-issues.atwho
'
,
this
.
onAutoCompleteToggled
.
bind
(
this
,
true
));
},
onIssuableFormWrapperClick
()
{
this
.
$refs
.
input
.
focus
();
},
},
};
</
script
>
<
template
>
<div
ref=
"issuableFormWrapper"
:class=
"
{ focus: isInputFocused }"
class="add-issuable-form-input-wrapper form-control"
role="button"
@click="onIssuableFormWrapperClick"
>
<ul
class=
"add-issuable-form-input-token-list"
>
<!--
We need to ensure this key changes any time the pendingReferences array is updated
else two consecutive pending ref strings in an array with the same name will collide
and cause odd behavior when one is removed.
-->
<li
v-for=
"(reference, index) in references"
:key=
"`related-issues-token-$
{reference}`"
class="js-add-issuable-form-token-list-item add-issuable-form-token-list-item"
>
<issue-token
:id-key=
"index"
:display-reference=
"reference"
:can-remove=
"true"
:is-condensed=
"true"
:path-id-separator=
"pathIdSeparator"
event-namespace=
"pendingIssuable"
@
pendingIssuableRemoveRequest=
"
params =>
{
$emit('pendingIssuableRemoveRequest', params);
}
"
/>
</li>
<li
class=
"add-issuable-form-input-list-item"
>
<input
ref=
"input"
:value=
"inputValue"
:placeholder=
"inputPlaceholder"
type=
"text"
class=
"js-add-issuable-form-input add-issuable-form-input qa-add-issue-input"
@
input=
"onInput"
@
focus=
"onFocus"
@
blur=
"onBlur"
@
keyup.escape.exact=
"$emit('addIssuableFormCancel')"
/>
</li>
</ul>
</div>
</
template
>
ee/app/assets/javascripts/related_issues/components/related_issues_root.vue
View file @
46bac3c3
...
@@ -32,6 +32,7 @@ import {
...
@@ -32,6 +32,7 @@ import {
relatedIssuesRemoveErrorMap
,
relatedIssuesRemoveErrorMap
,
pathIndeterminateErrorMap
,
pathIndeterminateErrorMap
,
addRelatedIssueErrorMap
,
addRelatedIssueErrorMap
,
issuableTypesMap
,
}
from
'
../constants
'
;
}
from
'
../constants
'
;
export
default
{
export
default
{
...
@@ -67,7 +68,7 @@ export default {
...
@@ -67,7 +68,7 @@ export default {
issuableType
:
{
issuableType
:
{
type
:
String
,
type
:
String
,
required
:
false
,
required
:
false
,
default
:
'
issue
'
,
default
:
issuableTypesMap
.
ISSUE
,
},
},
allowAutoComplete
:
{
allowAutoComplete
:
{
type
:
Boolean
,
type
:
Boolean
,
...
...
ee/app/assets/javascripts/related_issues/constants.js
View file @
46bac3c3
import
{
__
}
from
'
~/locale
'
;
import
{
__
}
from
'
~/locale
'
;
export
const
issuableTypesMap
=
{
ISSUE
:
'
issue
'
,
EPIC
:
'
epic
'
,
};
export
const
autoCompleteTextMap
=
{
export
const
autoCompleteTextMap
=
{
true
:
{
true
:
{
issue
:
__
(
'
or <#issue id>
'
),
[
issuableTypesMap
.
ISSUE
]
:
__
(
'
or <#issue id>
'
),
epic
:
__
(
'
or <#epic id>
'
),
[
issuableTypesMap
.
EPIC
]
:
__
(
'
or <#epic id>
'
),
},
},
false
:
{
false
:
{
issue
:
''
,
[
issuableTypesMap
.
ISSUE
]
:
''
,
epic
:
''
,
[
issuableTypesMap
.
EPIC
]
:
''
,
},
},
};
};
export
const
inputPlaceholderTextMap
=
{
export
const
inputPlaceholderTextMap
=
{
issue
:
__
(
'
Paste issue link
'
),
[
issuableTypesMap
.
ISSUE
]
:
__
(
'
Paste issue link
'
),
epic
:
__
(
'
Paste epic link
'
),
[
issuableTypesMap
.
EPIC
]
:
__
(
'
Paste epic link
'
),
};
};
export
const
relatedIssuesRemoveErrorMap
=
{
export
const
relatedIssuesRemoveErrorMap
=
{
issue
:
__
(
'
An error occurred while removing issues.
'
),
[
issuableTypesMap
.
ISSUE
]
:
__
(
'
An error occurred while removing issues.
'
),
epic
:
__
(
'
An error occurred while removing epics.
'
),
[
issuableTypesMap
.
EPIC
]
:
__
(
'
An error occurred while removing epics.
'
),
};
};
export
const
pathIndeterminateErrorMap
=
{
export
const
pathIndeterminateErrorMap
=
{
issue
:
__
(
'
We could not determine the path to remove the issue
'
),
[
issuableTypesMap
.
ISSUE
]
:
__
(
'
We could not determine the path to remove the issue
'
),
epic
:
__
(
'
We could not determine the path to remove the epic
'
),
[
issuableTypesMap
.
EPIC
]
:
__
(
'
We could not determine the path to remove the epic
'
),
};
};
export
const
addRelatedIssueErrorMap
=
{
export
const
addRelatedIssueErrorMap
=
{
issue
:
__
(
"
We can't find an issue that matches what you are looking for.
"
),
[
issuableTypesMap
.
ISSUE
]
:
__
(
"
We can't find an issue that matches what you are looking for.
"
),
epic
:
__
(
"
We can't find an epic that matches what you are looking for.
"
),
[
issuableTypesMap
.
EPIC
]
:
__
(
"
We can't find an epic that matches what you are looking for.
"
),
};
};
/**
/**
...
@@ -37,8 +42,8 @@ export const addRelatedIssueErrorMap = {
...
@@ -37,8 +42,8 @@ export const addRelatedIssueErrorMap = {
* them inside i18n functions.
* them inside i18n functions.
*/
*/
export
const
issuableIconMap
=
{
export
const
issuableIconMap
=
{
issue
:
'
issues
'
,
[
issuableTypesMap
.
ISSUE
]
:
'
issues
'
,
epic
:
'
epic
'
,
[
issuableTypesMap
.
EPIC
]
:
'
epic
'
,
};
};
/**
/**
...
@@ -47,6 +52,6 @@ export const issuableIconMap = {
...
@@ -47,6 +52,6 @@ export const issuableIconMap = {
* them inside i18n functions.
* them inside i18n functions.
*/
*/
export
const
issuableQaClassMap
=
{
export
const
issuableQaClassMap
=
{
issue
:
'
qa-add-issues-button
'
,
[
issuableTypesMap
.
ISSUE
]
:
'
qa-add-issues-button
'
,
epic
:
'
qa-add-epics-button
'
,
[
issuableTypesMap
.
EPIC
]
:
'
qa-add-epics-button
'
,
};
};
ee/spec/frontend/related_issues/components/related_issuable_input_spec.js
0 → 100644
View file @
46bac3c3
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
TEST_HOST
}
from
'
helpers/test_constants
'
;
import
{
issuableTypesMap
}
from
'
ee/related_issues/constants
'
;
import
RelatedIssuableInput
from
'
ee/related_issues/components/related_issuable_input.vue
'
;
jest
.
mock
(
'
ee_else_ce/gfm_auto_complete
'
,
()
=>
({
__esModule
:
true
,
default
()
{
return
{
constructor
()
{},
setup
()
{},
};
},
}));
describe
(
'
RelatedIssuableInput
'
,
()
=>
{
let
propsData
;
beforeEach
(()
=>
{
propsData
=
{
inputValue
:
''
,
references
:
[],
pathIdSeparator
:
'
#
'
,
issuableType
:
issuableTypesMap
.
issue
,
autoCompleteSources
:
{
issues
:
`
${
TEST_HOST
}
/h5bp/html5-boilerplate/-/autocomplete_sources/issues`
,
},
};
});
describe
(
'
autocomplete
'
,
()
=>
{
describe
(
'
with autoCompleteSources
'
,
()
=>
{
it
(
'
shows placeholder text
'
,
()
=>
{
const
wrapper
=
shallowMount
(
RelatedIssuableInput
,
{
propsData
});
expect
(
wrapper
.
find
({
ref
:
'
input
'
}).
element
.
placeholder
).
toBe
(
'
Paste issue link or <#issue id>
'
,
);
});
it
(
'
has GfmAutoComplete
'
,
()
=>
{
const
wrapper
=
shallowMount
(
RelatedIssuableInput
,
{
propsData
});
expect
(
wrapper
.
vm
.
gfmAutoComplete
).
toBeDefined
();
});
});
describe
(
'
with no autoCompleteSources
'
,
()
=>
{
it
(
'
shows placeholder text
'
,
()
=>
{
const
wrapper
=
shallowMount
(
RelatedIssuableInput
,
{
propsData
:
{
...
propsData
,
references
:
[
'
!1
'
,
'
!2
'
],
},
});
expect
(
wrapper
.
find
({
ref
:
'
input
'
}).
element
.
value
).
toBe
(
''
);
});
it
(
'
does not have GfmAutoComplete
'
,
()
=>
{
const
wrapper
=
shallowMount
(
RelatedIssuableInput
,
{
propsData
:
{
...
propsData
,
autoCompleteSources
:
{},
},
});
expect
(
wrapper
.
vm
.
gfmAutoComplete
).
not
.
toBeDefined
();
});
});
});
describe
(
'
focus
'
,
()
=>
{
it
(
'
when clicking anywhere on the input wrapper it should focus the input
'
,
()
=>
{
const
wrapper
=
shallowMount
(
RelatedIssuableInput
,
{
propsData
:
{
...
propsData
,
references
:
[
'
foo
'
,
'
bar
'
],
},
});
wrapper
.
find
(
'
li
'
).
trigger
(
'
click
'
);
expect
(
document
.
activeElement
).
toBe
(
wrapper
.
find
({
ref
:
'
input
'
}).
element
);
});
});
describe
(
'
when filling in the input
'
,
()
=>
{
it
(
'
emits addIssuableFormInput with data
'
,
()
=>
{
const
wrapper
=
shallowMount
(
RelatedIssuableInput
,
{
propsData
,
});
wrapper
.
vm
.
$emit
=
jest
.
fn
();
const
newInputValue
=
'
filling in things
'
;
const
untouchedRawReferences
=
newInputValue
.
trim
().
split
(
/
\s
/
);
const
touchedReference
=
untouchedRawReferences
.
pop
();
const
input
=
wrapper
.
find
({
ref
:
'
input
'
});
input
.
element
.
value
=
newInputValue
;
input
.
element
.
selectionStart
=
newInputValue
.
length
;
input
.
element
.
selectionEnd
=
newInputValue
.
length
;
input
.
trigger
(
'
input
'
);
expect
(
wrapper
.
vm
.
$emit
).
toHaveBeenCalledWith
(
'
addIssuableFormInput
'
,
{
newValue
:
newInputValue
,
caretPos
:
newInputValue
.
length
,
untouchedRawReferences
,
touchedReference
,
});
});
});
});
ee/spec/javascripts/issuable/related_issues/components/add_issuable_form_spec.js
View file @
46bac3c3
import
$
from
'
jquery
'
;
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
addIssuableForm
from
'
ee/related_issues/components/add_issuable_form.vue
'
;
import
addIssuableForm
from
'
ee/related_issues/components/add_issuable_form.vue
'
;
...
@@ -90,7 +89,7 @@ describe('AddIssuableForm', () => {
...
@@ -90,7 +89,7 @@ describe('AddIssuableForm', () => {
});
});
it
(
'
should put input value in place
'
,
()
=>
{
it
(
'
should put input value in place
'
,
()
=>
{
expect
(
vm
.
$
refs
.
input
.
value
).
toEqual
(
inputValue
);
expect
(
vm
.
$
el
.
querySelector
(
'
.js-add-issuable-form-input
'
)
.
value
).
toEqual
(
inputValue
);
});
});
it
(
'
should render pending issuables items
'
,
()
=>
{
it
(
'
should render pending issuables items
'
,
()
=>
{
...
@@ -102,163 +101,20 @@ describe('AddIssuableForm', () => {
...
@@ -102,163 +101,20 @@ describe('AddIssuableForm', () => {
});
});
});
});
describe
(
'
when submitting
'
,
()
=>
{
it
(
'
when submitting pending issues
'
,
()
=>
{
beforeEach
(()
=>
{
vm
=
new
AddIssuableForm
({
vm
=
new
AddIssuableForm
({
propsData
:
{
propsData
:
{
inputValue
:
'
'
,
inputValue
:
'
foo #123
'
,
pendingReferences
:
[
issuable1
.
reference
,
issuable2
.
reference
],
pendingReferences
:
[
issuable1
.
reference
,
issuable2
.
reference
],
isSubmitting
:
true
,
pathIdSeparator
,
},
}).
$mount
();
});
it
(
'
should have disabled submit button with loading icon
'
,
()
=>
{
expect
(
vm
.
$refs
.
addButton
.
disabled
).
toBe
(
true
);
expect
(
vm
.
$refs
.
loadingIcon
).
toBeDefined
();
});
});
});
describe
(
'
autocomplete
'
,
()
=>
{
describe
(
'
with autoCompleteSources
'
,
()
=>
{
beforeEach
(()
=>
{
vm
=
new
AddIssuableForm
({
propsData
:
{
inputValue
:
''
,
autoCompleteSources
:
{
issues
:
'
/fake/issues/path
'
,
},
pathIdSeparator
,
},
}).
$mount
();
});
it
(
'
shows placeholder text
'
,
()
=>
{
expect
(
vm
.
$refs
.
input
.
placeholder
).
toEqual
(
'
Paste issue link or <#issue id>
'
);
});
it
(
'
has GfmAutoComplete
'
,
()
=>
{
expect
(
vm
.
gfmAutoComplete
).
toBeDefined
();
});
});
describe
(
'
with no autoCompleteSources
'
,
()
=>
{
beforeEach
(()
=>
{
vm
=
new
AddIssuableForm
({
propsData
:
{
inputValue
:
''
,
autoCompleteSources
:
{},
pathIdSeparator
,
},
}).
$mount
();
});
it
(
'
shows placeholder text
'
,
()
=>
{
expect
(
vm
.
$refs
.
input
.
placeholder
).
toEqual
(
'
Paste issue link
'
);
});
it
(
'
does not have GfmAutoComplete
'
,
()
=>
{
expect
(
vm
.
gfmAutoComplete
).
not
.
toBeDefined
();
});
});
});
describe
(
'
methods
'
,
()
=>
{
beforeEach
(()
=>
{
const
el
=
document
.
createElement
(
'
div
'
);
// We need to append to body to get focus tests working
document
.
body
.
appendChild
(
el
);
vm
=
new
AddIssuableForm
({
propsData
:
{
inputValue
:
''
,
pendingIssuables
:
[
issuable1
],
autoCompleteSources
:
{
issues
:
'
/fake/issues/path
'
,
},
pathIdSeparator
,
pathIdSeparator
,
},
},
}).
$mount
(
el
);
});
it
(
'
when clicking somewhere on the input wrapper should focus the input
'
,
done
=>
{
vm
.
onInputWrapperClick
();
setTimeout
(()
=>
{
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$refs
.
issuableFormWrapper
.
classList
.
contains
(
'
focus
'
)).
toEqual
(
true
);
expect
(
document
.
activeElement
).
toEqual
(
vm
.
$refs
.
input
);
done
();
});
});
});
it
(
'
when filling in the input
'
,
()
=>
{
spyOn
(
vm
,
'
$emit
'
);
const
newInputValue
=
'
filling in things
'
;
const
untouchedRawReferences
=
newInputValue
.
trim
().
split
(
/
\s
/
);
const
touchedReference
=
untouchedRawReferences
.
pop
();
vm
.
$refs
.
input
.
value
=
newInputValue
;
vm
.
onInput
();
expect
(
vm
.
$emit
).
toHaveBeenCalledWith
(
'
addIssuableFormInput
'
,
{
newValue
:
newInputValue
,
caretPos
:
newInputValue
.
length
,
untouchedRawReferences
,
touchedReference
,
});
});
it
(
'
when blurring the input
'
,
done
=>
{
spyOn
(
vm
,
'
$emit
'
);
const
newInputValue
=
'
filling in things
'
;
vm
.
$refs
.
input
.
value
=
newInputValue
;
vm
.
onBlur
();
setTimeout
(()
=>
{
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$refs
.
issuableFormWrapper
.
classList
.
contains
(
'
focus
'
)).
toEqual
(
false
);
expect
(
vm
.
$emit
).
toHaveBeenCalledWith
(
'
addIssuableFormBlur
'
,
newInputValue
);
done
();
});
});
});
it
(
'
when using the autocomplete
'
,
done
=>
{
const
$input
=
$
(
vm
.
$refs
.
input
);
vm
.
gfmAutoComplete
.
loadData
(
$input
,
'
#
'
,
[
{
id
:
1
,
iid
:
111
,
title
:
'
foo
'
,
},
]);
$input
.
val
(
'
#
'
)
.
trigger
(
'
input
'
)
.
trigger
(
'
click
'
);
$
(
'
.atwho-container li
'
).
trigger
(
'
click
'
);
setTimeout
(()
=>
{
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$refs
.
input
.
value
).
toEqual
(
''
);
done
();
});
});
});
});
vm
.
$mount
();
it
(
'
when submitting pending issues
'
,
()
=>
{
spyOn
(
vm
,
'
$emit
'
);
spyOn
(
vm
,
'
$emit
'
);
const
newInputValue
=
'
filling in things
'
;
const
newInputValue
=
'
filling in things
'
;
vm
.
$refs
.
input
.
value
=
newInputValue
;
const
inputEl
=
vm
.
$el
.
querySelector
(
'
.js-add-issuable-form-input
'
);
inputEl
.
value
=
newInputValue
;
vm
.
onFormSubmit
();
vm
.
onFormSubmit
();
expect
(
vm
.
$emit
).
toHaveBeenCalledWith
(
'
addIssuableFormSubmit
'
,
newInputValue
);
expect
(
vm
.
$emit
).
toHaveBeenCalledWith
(
'
addIssuableFormSubmit
'
,
newInputValue
);
...
...
qa/qa/ee/page/group/epic/show.rb
View file @
46bac3c3
...
@@ -22,6 +22,9 @@ module QA
...
@@ -22,6 +22,9 @@ module QA
view
'ee/app/assets/javascripts/related_issues/components/add_issuable_form.vue'
do
view
'ee/app/assets/javascripts/related_issues/components/add_issuable_form.vue'
do
element
:add_issue_button
element
:add_issue_button
end
view
'ee/app/assets/javascripts/related_issues/components/related_issuable_input.vue'
do
element
:add_issue_input
element
:add_issue_input
end
end
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment