BigW Consortium Gitlab
Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
G
gitlab-ce
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
Forest Godfrey
gitlab-ce
Commits
20987f4f
Commit
20987f4f
authored
May 12, 2017
by
Filipa Lacerda
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'refactor-realtime-issue' into 'master'
Refactored issue tealtime elements See merge request !11242
parents
2ac27a96
3dfce3ab
Hide whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
543 additions
and
262 deletions
+543
-262
tasks.js
app/assets/javascripts/issue_show/actions/tasks.js
+0
-27
app.vue
app/assets/javascripts/issue_show/components/app.vue
+96
-0
description.vue
app/assets/javascripts/issue_show/components/description.vue
+105
-0
title.vue
app/assets/javascripts/issue_show/components/title.vue
+53
-0
index.js
app/assets/javascripts/issue_show/index.js
+36
-14
issue_title_description.vue
...assets/javascripts/issue_show/issue_title_description.vue
+0
-180
animate.js
app/assets/javascripts/issue_show/mixins/animate.js
+13
-0
index.js
app/assets/javascripts/issue_show/services/index.js
+10
-4
index.js
app/assets/javascripts/issue_show/stores/index.js
+25
-0
mobile.scss
app/assets/stylesheets/framework/mobile.scss
+1
-1
issues_controller.rb
app/controllers/projects/issues_controller.rb
+0
-1
issuables_helper.rb
app/helpers/issuables_helper.rb
+3
-5
show.html.haml
app/views/projects/issues/show.html.haml
+9
-4
app_spec.js
spec/javascripts/issue_show/components/app_spec.js
+23
-23
description_spec.js
spec/javascripts/issue_show/components/description_spec.js
+99
-0
title_spec.js
spec/javascripts/issue_show/components/title_spec.js
+67
-0
mock_data.js
spec/javascripts/issue_show/mock_data.js
+3
-3
No files found.
app/assets/javascripts/issue_show/actions/tasks.js
deleted
100644 → 0
View file @
2ac27a96
export
default
(
newStateData
,
tasks
)
=>
{
const
$tasks
=
$
(
'#task_status'
);
const
$tasksShort
=
$
(
'#task_status_short'
);
const
$issueableHeader
=
$
(
'.issuable-header'
);
const
tasksStates
=
{
newState
:
null
,
currentState
:
null
};
if
(
$tasks
.
length
===
0
)
{
if
(
!
(
newStateData
.
task_status
.
indexOf
(
'0 of 0'
)
===
0
))
{
$issueableHeader
.
append
(
`<span id="task_status">
${
newStateData
.
task_status
}
</span>`
);
}
else
{
$issueableHeader
.
append
(
'<span id="task_status"></span>'
);
}
}
else
{
tasksStates
.
newState
=
newStateData
.
task_status
.
indexOf
(
'0 of 0'
)
===
0
;
tasksStates
.
currentState
=
tasks
.
indexOf
(
'0 of 0'
)
===
0
;
}
if
(
$tasks
.
length
!==
0
&&
!
tasksStates
.
newState
)
{
$tasks
.
text
(
newStateData
.
task_status
);
$tasksShort
.
text
(
newStateData
.
task_status
);
}
else
if
(
tasksStates
.
currentState
)
{
$issueableHeader
.
append
(
`<span id="task_status">
${
newStateData
.
task_status
}
</span>`
);
}
else
if
(
tasksStates
.
newState
)
{
$tasks
.
remove
();
$tasksShort
.
remove
();
}
};
app/assets/javascripts/issue_show/components/app.vue
0 → 100644
View file @
20987f4f
<
script
>
import
Visibility
from
'visibilityjs'
;
import
Poll
from
'../../lib/utils/poll'
;
import
Service
from
'../services/index'
;
import
Store
from
'../stores'
;
import
titleComponent
from
'./title.vue'
;
import
descriptionComponent
from
'./description.vue'
;
export
default
{
props
:
{
endpoint
:
{
required
:
true
,
type
:
String
,
},
canUpdate
:
{
required
:
true
,
type
:
Boolean
,
},
issuableRef
:
{
type
:
String
,
required
:
true
,
},
initialTitle
:
{
type
:
String
,
required
:
true
,
},
initialDescriptionHtml
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
initialDescriptionText
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
},
data
()
{
const
store
=
new
Store
({
titleHtml
:
this
.
initialTitle
,
descriptionHtml
:
this
.
initialDescriptionHtml
,
descriptionText
:
this
.
initialDescriptionText
,
});
return
{
store
,
state
:
store
.
state
,
};
},
components
:
{
descriptionComponent
,
titleComponent
,
},
created
()
{
const
resource
=
new
Service
(
this
.
endpoint
);
const
poll
=
new
Poll
({
resource
,
method
:
'getData'
,
successCallback
:
(
res
)
=>
{
this
.
store
.
updateState
(
res
.
json
());
},
errorCallback
(
err
)
{
throw
new
Error
(
err
);
},
});
if
(
!
Visibility
.
hidden
())
{
poll
.
makeRequest
();
}
Visibility
.
change
(()
=>
{
if
(
!
Visibility
.
hidden
())
{
poll
.
restart
();
}
else
{
poll
.
stop
();
}
});
},
};
</
script
>
<
template
>
<div>
<title-component
:issuable-ref=
"issuableRef"
:title-html=
"state.titleHtml"
:title-text=
"state.titleText"
/>
<description-component
v-if=
"state.descriptionHtml"
:can-update=
"canUpdate"
:description-html=
"state.descriptionHtml"
:description-text=
"state.descriptionText"
:updated-at=
"state.updatedAt"
:task-status=
"state.taskStatus"
/>
</div>
</
template
>
app/assets/javascripts/issue_show/components/description.vue
0 → 100644
View file @
20987f4f
<
script
>
import
animateMixin
from
'../mixins/animate'
;
export
default
{
mixins
:
[
animateMixin
],
props
:
{
canUpdate
:
{
type
:
Boolean
,
required
:
true
,
},
descriptionHtml
:
{
type
:
String
,
required
:
true
,
},
descriptionText
:
{
type
:
String
,
required
:
true
,
},
updatedAt
:
{
type
:
String
,
required
:
true
,
},
taskStatus
:
{
type
:
String
,
required
:
true
,
},
},
data
()
{
return
{
preAnimation
:
false
,
pulseAnimation
:
false
,
timeAgoEl
:
$
(
'.js-issue-edited-ago'
),
};
},
watch
:
{
descriptionHtml
()
{
this
.
animateChange
();
this
.
$nextTick
(()
=>
{
const
toolTipTime
=
gl
.
utils
.
formatDate
(
this
.
updatedAt
);
this
.
timeAgoEl
.
attr
(
'datetime'
,
this
.
updatedAt
)
.
attr
(
'title'
,
toolTipTime
)
.
tooltip
(
'fixTitle'
);
this
.
renderGFM
();
});
},
taskStatus
()
{
const
taskRegexMatches
=
this
.
taskStatus
.
match
(
/
(\d
+
)
of
(\d
+
)
/
);
const
$issuableHeader
=
$
(
'.issuable-meta'
);
const
$tasks
=
$
(
'#task_status'
,
$issuableHeader
);
const
$tasksShort
=
$
(
'#task_status_short'
,
$issuableHeader
);
if
(
taskRegexMatches
)
{
$tasks
.
text
(
this
.
taskStatus
);
$tasksShort
.
text
(
`
${
taskRegexMatches
[
1
]}
/
${
taskRegexMatches
[
2
]}
task
${
taskRegexMatches
[
2
]
>
1
?
's'
:
''
}
`
);
}
else
{
$tasks
.
text
(
''
);
$tasksShort
.
text
(
''
);
}
},
},
methods
:
{
renderGFM
()
{
$
(
this
.
$refs
[
'gfm-entry-content'
]).
renderGFM
();
if
(
this
.
canUpdate
)
{
// eslint-disable-next-line no-new
new
gl
.
TaskList
({
dataType
:
'issue'
,
fieldName
:
'description'
,
selector
:
'.detail-page-description'
,
});
}
},
},
mounted
()
{
this
.
renderGFM
();
},
};
</
script
>
<
template
>
<div
class=
"description"
:class=
"
{
'js-task-list-container': canUpdate
}">
<div
class=
"wiki"
:class=
"
{
'issue-realtime-pre-pulse': preAnimation,
'issue-realtime-trigger-pulse': pulseAnimation
}"
v-html="descriptionHtml"
ref="gfm-content">
</div>
<textarea
class=
"hidden js-task-list-field"
v-if=
"descriptionText"
v-model=
"descriptionText"
>
</textarea>
</div>
</
template
>
app/assets/javascripts/issue_show/components/title.vue
0 → 100644
View file @
20987f4f
<
script
>
import
animateMixin
from
'../mixins/animate'
;
export
default
{
mixins
:
[
animateMixin
],
data
()
{
return
{
preAnimation
:
false
,
pulseAnimation
:
false
,
titleEl
:
document
.
querySelector
(
'title'
),
};
},
props
:
{
issuableRef
:
{
type
:
String
,
required
:
true
,
},
titleHtml
:
{
type
:
String
,
required
:
true
,
},
titleText
:
{
type
:
String
,
required
:
true
,
},
},
watch
:
{
titleHtml
()
{
this
.
setPageTitle
();
this
.
animateChange
();
},
},
methods
:
{
setPageTitle
()
{
const
currentPageTitleScope
=
this
.
titleEl
.
innerText
.
split
(
'·'
);
currentPageTitleScope
[
0
]
=
`
${
this
.
titleText
}
(
${
this
.
issuableRef
}
) `
;
this
.
titleEl
.
textContent
=
currentPageTitleScope
.
join
(
'·'
);
},
},
};
</
script
>
<
template
>
<h2
class=
"title"
:class=
"
{
'issue-realtime-pre-pulse': preAnimation,
'issue-realtime-trigger-pulse': pulseAnimation
}"
v-html="titleHtml"
>
</h2>
</
template
>
app/assets/javascripts/issue_show/index.js
View file @
20987f4f
import
Vue
from
'vue'
;
import
Vue
from
'vue'
;
import
IssueTitle
from
'./issue_title_description
.vue'
;
import
issuableApp
from
'./components/app
.vue'
;
import
'../vue_shared/vue_resource_interceptor'
;
import
'../vue_shared/vue_resource_interceptor'
;
(()
=>
{
document
.
addEventListener
(
'DOMContentLoaded'
,
()
=>
new
Vue
({
const
issueTitleData
=
document
.
querySelector
(
'.issue-title-data'
).
dataset
;
el
:
document
.
getElementById
(
'js-issuable-app'
),
const
{
canUpdateTasksClass
,
endpoint
}
=
issueTitleData
;
components
:
{
issuableApp
,
},
data
()
{
const
issuableElement
=
this
.
$options
.
el
;
const
issuableTitleElement
=
issuableElement
.
querySelector
(
'.title'
);
const
issuableDescriptionElement
=
issuableElement
.
querySelector
(
'.wiki'
);
const
issuableDescriptionTextarea
=
issuableElement
.
querySelector
(
'.js-task-list-field'
);
const
{
canUpdate
,
endpoint
,
issuableRef
,
}
=
issuableElement
.
dataset
;
const
vm
=
new
Vue
({
return
{
el
:
'.issue-title-entrypoint'
,
canUpdate
:
gl
.
utils
.
convertPermissionToBoolean
(
canUpdate
),
render
:
createElement
=>
createElement
(
IssueTitle
,
{
endpoint
,
issuableRef
,
initialTitle
:
issuableTitleElement
.
innerHTML
,
initialDescriptionHtml
:
issuableDescriptionElement
?
issuableDescriptionElement
.
innerHTML
:
''
,
initialDescriptionText
:
issuableDescriptionTextarea
?
issuableDescriptionTextarea
.
textContent
:
''
,
};
},
render
(
createElement
)
{
return
createElement
(
'issuable-app'
,
{
props
:
{
props
:
{
canUpdateTasksClass
,
canUpdate
:
this
.
canUpdate
,
endpoint
,
endpoint
:
this
.
endpoint
,
issuableRef
:
this
.
issuableRef
,
initialTitle
:
this
.
initialTitle
,
initialDescriptionHtml
:
this
.
initialDescriptionHtml
,
initialDescriptionText
:
this
.
initialDescriptionText
,
},
},
}),
});
});
},
}));
return
vm
;
})();
app/assets/javascripts/issue_show/issue_title_description.vue
deleted
100644 → 0
View file @
2ac27a96
<
script
>
import
Visibility
from
'visibilityjs'
;
import
Poll
from
'./../lib/utils/poll'
;
import
Service
from
'./services/index'
;
import
tasks
from
'./actions/tasks'
;
export
default
{
props
:
{
endpoint
:
{
required
:
true
,
type
:
String
,
},
canUpdateTasksClass
:
{
required
:
true
,
type
:
String
,
},
},
data
()
{
const
resource
=
new
Service
(
this
.
$http
,
this
.
endpoint
);
const
poll
=
new
Poll
({
resource
,
method
:
'getTitle'
,
successCallback
:
(
res
)
=>
{
this
.
renderResponse
(
res
);
},
errorCallback
:
(
err
)
=>
{
throw
new
Error
(
err
);
},
});
return
{
poll
,
apiData
:
{},
tasks
:
'0 of 0'
,
title
:
null
,
titleText
:
''
,
titleFlag
:
{
pre
:
true
,
pulse
:
false
,
},
description
:
null
,
descriptionText
:
''
,
descriptionChange
:
false
,
descriptionFlag
:
{
pre
:
true
,
pulse
:
false
,
},
timeAgoEl
:
$
(
'.issue_edited_ago'
),
titleEl
:
document
.
querySelector
(
'title'
),
};
},
methods
:
{
updateFlag
(
key
,
toggle
)
{
this
[
key
].
pre
=
toggle
;
this
[
key
].
pulse
=
!
toggle
;
},
renderResponse
(
res
)
{
this
.
apiData
=
res
.
json
();
this
.
triggerAnimation
();
},
updateTaskHTML
()
{
tasks
(
this
.
apiData
,
this
.
tasks
);
},
elementsToVisualize
(
noTitleChange
,
noDescriptionChange
)
{
if
(
!
noTitleChange
)
{
this
.
titleText
=
this
.
apiData
.
title_text
;
this
.
updateFlag
(
'titleFlag'
,
true
);
}
if
(
!
noDescriptionChange
)
{
// only change to true when we need to bind TaskLists the html of description
this
.
descriptionChange
=
true
;
this
.
updateTaskHTML
();
this
.
tasks
=
this
.
apiData
.
task_status
;
this
.
updateFlag
(
'descriptionFlag'
,
true
);
}
},
setTabTitle
()
{
const
currentTabTitleScope
=
this
.
titleEl
.
innerText
.
split
(
'·'
);
currentTabTitleScope
[
0
]
=
`
${
this
.
titleText
}
(#
${
this
.
apiData
.
issue_number
}
) `
;
this
.
titleEl
.
innerText
=
currentTabTitleScope
.
join
(
'·'
);
},
animate
(
title
,
description
)
{
this
.
title
=
title
;
this
.
description
=
description
;
this
.
setTabTitle
();
this
.
$nextTick
(()
=>
{
this
.
updateFlag
(
'titleFlag'
,
false
);
this
.
updateFlag
(
'descriptionFlag'
,
false
);
});
},
triggerAnimation
()
{
// always reset to false before checking the change
this
.
descriptionChange
=
false
;
const
{
title
,
description
}
=
this
.
apiData
;
this
.
descriptionText
=
this
.
apiData
.
description_text
;
const
noTitleChange
=
this
.
title
===
title
;
const
noDescriptionChange
=
this
.
description
===
description
;
/**
* since opacity is changed, even if there is no diff for Vue to update
* we must check the title/description even on a 304 to ensure no visual change
*/
if
(
noTitleChange
&&
noDescriptionChange
)
return
;
this
.
elementsToVisualize
(
noTitleChange
,
noDescriptionChange
);
this
.
animate
(
title
,
description
);
},
updateEditedTimeAgo
()
{
const
toolTipTime
=
gl
.
utils
.
formatDate
(
this
.
apiData
.
updated_at
);
this
.
timeAgoEl
.
attr
(
'datetime'
,
this
.
apiData
.
updated_at
);
this
.
timeAgoEl
.
attr
(
'title'
,
toolTipTime
).
tooltip
(
'fixTitle'
);
},
},
created
()
{
if
(
!
Visibility
.
hidden
())
{
this
.
poll
.
makeRequest
();
}
Visibility
.
change
(()
=>
{
if
(
!
Visibility
.
hidden
())
{
this
.
poll
.
restart
();
}
else
{
this
.
poll
.
stop
();
}
});
},
updated
()
{
// if new html is injected (description changed) - bind TaskList and call renderGFM
if
(
this
.
descriptionChange
)
{
this
.
updateEditedTimeAgo
();
$
(
this
.
$refs
[
'issue-content-container-gfm-entry'
]).
renderGFM
();
const
tl
=
new
gl
.
TaskList
({
dataType
:
'issue'
,
fieldName
:
'description'
,
selector
:
'.detail-page-description'
,
});
return
tl
&&
null
;
}
return
null
;
},
};
</
script
>
<
template
>
<div>
<h2
class=
"title"
:class=
"
{ 'issue-realtime-pre-pulse': titleFlag.pre, 'issue-realtime-trigger-pulse': titleFlag.pulse }"
ref="issue-title"
v-html="title"
>
</h2>
<div
class=
"description is-task-list-enabled"
:class=
"canUpdateTasksClass"
v-if=
"description"
>
<div
class=
"wiki"
:class=
"
{ 'issue-realtime-pre-pulse': descriptionFlag.pre, 'issue-realtime-trigger-pulse': descriptionFlag.pulse }"
v-html="description"
ref="issue-content-container-gfm-entry"
>
</div>
<textarea
class=
"hidden js-task-list-field"
v-if=
"descriptionText"
>
{{
descriptionText
}}
</textarea>
</div>
</div>
</
template
>
app/assets/javascripts/issue_show/mixins/animate.js
0 → 100644
View file @
20987f4f
export
default
{
methods
:
{
animateChange
()
{
this
.
preAnimation
=
true
;
this
.
pulseAnimation
=
false
;
this
.
$nextTick
(()
=>
{
this
.
preAnimation
=
false
;
this
.
pulseAnimation
=
true
;
});
},
},
};
app/assets/javascripts/issue_show/services/index.js
View file @
20987f4f
import
Vue
from
'vue'
;
import
VueResource
from
'vue-resource'
;
Vue
.
use
(
VueResource
);
export
default
class
Service
{
export
default
class
Service
{
constructor
(
resource
,
endpoint
)
{
constructor
(
endpoint
)
{
this
.
resource
=
resource
;
this
.
endpoint
=
endpoint
;
this
.
endpoint
=
endpoint
;
this
.
resource
=
Vue
.
resource
(
this
.
endpoint
);
}
}
get
Title
()
{
get
Data
()
{
return
this
.
resource
.
get
(
this
.
endpoint
);
return
this
.
resource
.
get
();
}
}
}
}
app/assets/javascripts/issue_show/stores/index.js
0 → 100644
View file @
20987f4f
export
default
class
Store
{
constructor
({
titleHtml
,
descriptionHtml
,
descriptionText
,
})
{
this
.
state
=
{
titleHtml
,
titleText
:
''
,
descriptionHtml
,
descriptionText
,
taskStatus
:
''
,
updatedAt
:
''
,
};
}
updateState
(
data
)
{
this
.
state
.
titleHtml
=
data
.
title
;
this
.
state
.
titleText
=
data
.
title_text
;
this
.
state
.
descriptionHtml
=
data
.
description
;
this
.
state
.
descriptionText
=
data
.
description_text
;
this
.
state
.
taskStatus
=
data
.
task_status
;
this
.
state
.
updatedAt
=
data
.
updated_at
;
}
}
app/assets/stylesheets/framework/mobile.scss
View file @
20987f4f
...
@@ -112,7 +112,7 @@
...
@@ -112,7 +112,7 @@
}
}
}
}
.issue
_edited_
ago
,
.issue
-edited-
ago
,
.note_edited_ago
{
.note_edited_ago
{
display
:
none
;
display
:
none
;
}
}
...
...
app/controllers/projects/issues_controller.rb
View file @
20987f4f
...
@@ -208,7 +208,6 @@ class Projects::IssuesController < Projects::ApplicationController
...
@@ -208,7 +208,6 @@ class Projects::IssuesController < Projects::ApplicationController
description:
view_context
.
markdown_field
(
@issue
,
:description
),
description:
view_context
.
markdown_field
(
@issue
,
:description
),
description_text:
@issue
.
description
,
description_text:
@issue
.
description
,
task_status:
@issue
.
task_status
,
task_status:
@issue
.
task_status
,
issue_number:
@issue
.
iid
,
updated_at:
@issue
.
updated_at
updated_at:
@issue
.
updated_at
}
}
end
end
...
...
app/helpers/issuables_helper.rb
View file @
20987f4f
...
@@ -136,11 +136,9 @@ module IssuablesHelper
...
@@ -136,11 +136,9 @@ module IssuablesHelper
author_output
<<
link_to_member
(
project
,
issuable
.
author
,
size:
24
,
by_username:
true
,
avatar:
false
,
mobile_classes:
"hidden-sm hidden-md hidden-lg"
)
author_output
<<
link_to_member
(
project
,
issuable
.
author
,
size:
24
,
by_username:
true
,
avatar:
false
,
mobile_classes:
"hidden-sm hidden-md hidden-lg"
)
end
end
if
issuable
.
tasks?
output
<<
" "
.
html_safe
output
<<
" "
.
html_safe
output
<<
content_tag
(
:span
,
issuable
.
task_status
,
id:
"task_status"
,
class:
"hidden-xs hidden-sm"
)
output
<<
content_tag
(
:span
,
issuable
.
task_status
,
id:
"task_status"
,
class:
"hidden-xs hidden-sm"
)
output
<<
content_tag
(
:span
,
issuable
.
task_status_short
,
id:
"task_status_short"
,
class:
"hidden-md hidden-lg"
)
output
<<
content_tag
(
:span
,
issuable
.
task_status_short
,
id:
"task_status_short"
,
class:
"hidden-md hidden-lg"
)
end
output
output
end
end
...
...
app/views/projects/issues/show.html.haml
View file @
20987f4f
...
@@ -51,12 +51,17 @@
...
@@ -51,12 +51,17 @@
.issue-details.issuable-details
.issue-details.issuable-details
.detail-page-description.content-block
.detail-page-description.content-block
.issue-title-data.hidden
{
"data"
=>
{
"endpoint"
=>
rendered_title_namespace_project_issue_path
(
@project
.
namespace
,
@project
,
@issue
),
#js-issuable-app
{
"data"
=>
{
"endpoint"
=>
rendered_title_namespace_project_issue_path
(
@project
.
namespace
,
@project
,
@issue
),
"can-update-tasks-class"
=>
can?
(
current_user
,
:update_issue
,
@issue
)
?
'js-task-list-container'
:
''
,
"can-update"
=>
can?
(
current_user
,
:update_issue
,
@issue
).
to_s
,
"issuable-ref"
=>
@issue
.
to_reference
,
}
}
}
}
.issue-title-entrypoint
%h2
.title
=
markdown_field
(
@issue
,
:title
)
-
if
@issue
.
description
.
present?
.description
{
class:
can?
(
current_user
,
:update_issue
,
@issue
)
?
'js-task-list-container'
:
''
}
.wiki
=
markdown_field
(
@issue
,
:description
)
%textarea
.hidden.js-task-list-field
=
@issue
.
description
=
edited_time_ago_with_tooltip
(
@issue
,
placement:
'bottom'
,
html_class:
'issue
_edited_
ago'
)
=
edited_time_ago_with_tooltip
(
@issue
,
placement:
'bottom'
,
html_class:
'issue
-edited-ago js-issue-edited-
ago'
)
#merge-requests
{
data:
{
url:
referenced_merge_requests_namespace_project_issue_url
(
@project
.
namespace
,
@project
,
@issue
)
}
}
#merge-requests
{
data:
{
url:
referenced_merge_requests_namespace_project_issue_url
(
@project
.
namespace
,
@project
,
@issue
)
}
}
// This element is filled in using JavaScript.
// This element is filled in using JavaScript.
...
...
spec/javascripts/issue_show/
issue_title_description
_spec.js
→
spec/javascripts/issue_show/
components/app
_spec.js
View file @
20987f4f
import
Vue
from
'vue'
;
import
Vue
from
'vue'
;
import
$
from
'jquery'
;
import
'~/render_math'
;
import
'~/render_math'
;
import
'~/render_gfm'
;
import
'~/render_gfm'
;
import
issueTitleDescription
from
'~/issue_show/issue_title_description.vue'
;
import
issuableApp
from
'~/issue_show/components/app.vue'
;
import
issueShowData
from
'./mock_data'
;
import
issueShowData
from
'../mock_data'
;
window
.
$
=
$
;
const
issueShowInterceptor
=
data
=>
(
request
,
next
)
=>
{
const
issueShowInterceptor
=
data
=>
(
request
,
next
)
=>
{
next
(
request
.
respondWith
(
JSON
.
stringify
(
data
),
{
next
(
request
.
respondWith
(
JSON
.
stringify
(
data
),
{
...
@@ -16,42 +13,45 @@ const issueShowInterceptor = data => (request, next) => {
...
@@ -16,42 +13,45 @@ const issueShowInterceptor = data => (request, next) => {
}));
}));
};
};
describe
(
'Issu
e Title
'
,
()
=>
{
describe
(
'Issu
able output
'
,
()
=>
{
document
.
body
.
innerHTML
=
'<span id="task_status"></span>'
;
document
.
body
.
innerHTML
=
'<span id="task_status"></span>'
;
let
IssueTitleDescriptionComponent
;
let
vm
;
beforeEach
(()
=>
{
beforeEach
(()
=>
{
IssueTitleDescriptionComponent
=
Vue
.
extend
(
issueTitleDescription
);
const
IssuableDescriptionComponent
=
Vue
.
extend
(
issuableApp
);
});
afterEach
(()
=>
{
Vue
.
http
.
interceptors
=
_
.
without
(
Vue
.
http
.
interceptors
,
issueShowInterceptor
);
});
it
(
'should render a title/description and update title/description on update'
,
(
done
)
=>
{
Vue
.
http
.
interceptors
.
push
(
issueShowInterceptor
(
issueShowData
.
initialRequest
));
Vue
.
http
.
interceptors
.
push
(
issueShowInterceptor
(
issueShowData
.
initialRequest
));
const
issueShowComponent
=
new
IssueTit
leDescriptionComponent
({
vm
=
new
Issuab
leDescriptionComponent
({
propsData
:
{
propsData
:
{
canUpdate
Issue
:
'.css-stuff'
,
canUpdate
:
true
,
endpoint
:
'/gitlab-org/gitlab-shell/issues/9/rendered_title'
,
endpoint
:
'/gitlab-org/gitlab-shell/issues/9/rendered_title'
,
issuableRef
:
'#1'
,
initialTitle
:
''
,
initialDescriptionHtml
:
''
,
initialDescriptionText
:
''
,
},
},
}).
$mount
();
}).
$mount
();
});
afterEach
(()
=>
{
Vue
.
http
.
interceptors
=
_
.
without
(
Vue
.
http
.
interceptors
,
issueShowInterceptor
);
});
it
(
'should render a title/description and update title/description on update'
,
(
done
)
=>
{
setTimeout
(()
=>
{
setTimeout
(()
=>
{
expect
(
document
.
querySelector
(
'title'
).
innerText
).
toContain
(
'this is a title (#1)'
);
expect
(
document
.
querySelector
(
'title'
).
innerText
).
toContain
(
'this is a title (#1)'
);
expect
(
issueShowComponent
.
$el
.
querySelector
(
'.title'
).
innerHTML
).
toContain
(
'<p>this is a title</p>'
);
expect
(
vm
.
$el
.
querySelector
(
'.title'
).
innerHTML
).
toContain
(
'<p>this is a title</p>'
);
expect
(
issueShowComponent
.
$el
.
querySelector
(
'.wiki'
).
innerHTML
).
toContain
(
'<p>this is a description!</p>'
);
expect
(
vm
.
$el
.
querySelector
(
'.wiki'
).
innerHTML
).
toContain
(
'<p>this is a description!</p>'
);
expect
(
issueShowComponent
.
$el
.
querySelector
(
'.js-task-list-field'
).
innerText
).
toContain
(
'this is a description'
);
expect
(
vm
.
$el
.
querySelector
(
'.js-task-list-field'
).
value
).
toContain
(
'this is a description'
);
Vue
.
http
.
interceptors
.
push
(
issueShowInterceptor
(
issueShowData
.
secondRequest
));
Vue
.
http
.
interceptors
.
push
(
issueShowInterceptor
(
issueShowData
.
secondRequest
));
setTimeout
(()
=>
{
setTimeout
(()
=>
{
expect
(
document
.
querySelector
(
'title'
).
innerText
).
toContain
(
'2 (#1)'
);
expect
(
document
.
querySelector
(
'title'
).
innerText
).
toContain
(
'2 (#1)'
);
expect
(
issueShowComponent
.
$el
.
querySelector
(
'.title'
).
innerHTML
).
toContain
(
'<p>2</p>'
);
expect
(
vm
.
$el
.
querySelector
(
'.title'
).
innerHTML
).
toContain
(
'<p>2</p>'
);
expect
(
issueShowComponent
.
$el
.
querySelector
(
'.wiki'
).
innerHTML
).
toContain
(
'<p>42</p>'
);
expect
(
vm
.
$el
.
querySelector
(
'.wiki'
).
innerHTML
).
toContain
(
'<p>42</p>'
);
expect
(
issueShowComponent
.
$el
.
querySelector
(
'.js-task-list-field'
).
innerText
).
toContain
(
'42'
);
expect
(
vm
.
$el
.
querySelector
(
'.js-task-list-field'
).
value
).
toContain
(
'42'
);
done
();
done
();
});
});
...
...
spec/javascripts/issue_show/components/description_spec.js
0 → 100644
View file @
20987f4f
import
Vue
from
'vue'
;
import
descriptionComponent
from
'~/issue_show/components/description.vue'
;
describe
(
'Description component'
,
()
=>
{
let
vm
;
beforeEach
(()
=>
{
const
Component
=
Vue
.
extend
(
descriptionComponent
);
if
(
!
document
.
querySelector
(
'.issuable-meta'
))
{
const
metaData
=
document
.
createElement
(
'div'
);
metaData
.
classList
.
add
(
'issuable-meta'
);
metaData
.
innerHTML
=
'<span id="task_status"></span><span id="task_status_short"></span>'
;
document
.
body
.
appendChild
(
metaData
);
}
vm
=
new
Component
({
propsData
:
{
canUpdate
:
true
,
descriptionHtml
:
'test'
,
descriptionText
:
'test'
,
updatedAt
:
new
Date
().
toString
(),
taskStatus
:
''
,
},
}).
$mount
();
});
it
(
'animates description changes'
,
(
done
)
=>
{
vm
.
descriptionHtml
=
'changed'
;
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'.wiki'
).
classList
.
contains
(
'issue-realtime-pre-pulse'
),
).
toBeTruthy
();
setTimeout
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'.wiki'
).
classList
.
contains
(
'issue-realtime-trigger-pulse'
),
).
toBeTruthy
();
done
();
});
});
});
it
(
're-inits the TaskList when description changed'
,
(
done
)
=>
{
spyOn
(
gl
,
'TaskList'
);
vm
.
descriptionHtml
=
'changed'
;
setTimeout
(()
=>
{
expect
(
gl
.
TaskList
,
).
toHaveBeenCalled
();
done
();
});
});
it
(
'does not re-init the TaskList when canUpdate is false'
,
(
done
)
=>
{
spyOn
(
gl
,
'TaskList'
);
vm
.
canUpdate
=
false
;
vm
.
descriptionHtml
=
'changed'
;
setTimeout
(()
=>
{
expect
(
gl
.
TaskList
,
).
not
.
toHaveBeenCalled
();
done
();
});
});
describe
(
'taskStatus'
,
()
=>
{
it
(
'adds full taskStatus'
,
(
done
)
=>
{
vm
.
taskStatus
=
'1 of 1'
;
setTimeout
(()
=>
{
expect
(
document
.
querySelector
(
'.issuable-meta #task_status'
).
textContent
.
trim
(),
).
toBe
(
'1 of 1'
);
done
();
});
});
it
(
'adds short taskStatus'
,
(
done
)
=>
{
vm
.
taskStatus
=
'1 of 1'
;
setTimeout
(()
=>
{
expect
(
document
.
querySelector
(
'.issuable-meta #task_status_short'
).
textContent
.
trim
(),
).
toBe
(
'1/1 task'
);
done
();
});
});
});
});
spec/javascripts/issue_show/components/title_spec.js
0 → 100644
View file @
20987f4f
import
Vue
from
'vue'
;
import
titleComponent
from
'~/issue_show/components/title.vue'
;
describe
(
'Title component'
,
()
=>
{
let
vm
;
beforeEach
(()
=>
{
const
Component
=
Vue
.
extend
(
titleComponent
);
vm
=
new
Component
({
propsData
:
{
issuableRef
:
'#1'
,
titleHtml
:
'Testing <img />'
,
titleText
:
'Testing'
,
},
}).
$mount
();
});
it
(
'renders title HTML'
,
()
=>
{
expect
(
vm
.
$el
.
innerHTML
.
trim
(),
).
toBe
(
'Testing <img>'
);
});
it
(
'updates page title when changing titleHtml'
,
(
done
)
=>
{
spyOn
(
vm
,
'setPageTitle'
);
vm
.
titleHtml
=
'test'
;
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
setPageTitle
,
).
toHaveBeenCalled
();
done
();
});
});
it
(
'animates title changes'
,
(
done
)
=>
{
vm
.
titleHtml
=
'test'
;
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$el
.
classList
.
contains
(
'issue-realtime-pre-pulse'
),
).
toBeTruthy
();
setTimeout
(()
=>
{
expect
(
vm
.
$el
.
classList
.
contains
(
'issue-realtime-trigger-pulse'
),
).
toBeTruthy
();
done
();
});
});
});
it
(
'updates page title after changing title'
,
(
done
)
=>
{
vm
.
titleHtml
=
'changed'
;
vm
.
titleText
=
'changed'
;
Vue
.
nextTick
(()
=>
{
expect
(
document
.
querySelector
(
'title'
).
textContent
.
trim
(),
).
toContain
(
'changed'
);
done
();
});
});
});
spec/javascripts/issue_show/mock_data.js
View file @
20987f4f
...
@@ -4,23 +4,23 @@ export default {
...
@@ -4,23 +4,23 @@ export default {
title_text
:
'this is a title'
,
title_text
:
'this is a title'
,
description
:
'<p>this is a description!</p>'
,
description
:
'<p>this is a description!</p>'
,
description_text
:
'this is a description'
,
description_text
:
'this is a description'
,
issue_number
:
1
,
task_status
:
'2 of 4 completed'
,
task_status
:
'2 of 4 completed'
,
updated_at
:
new
Date
().
toString
(),
},
},
secondRequest
:
{
secondRequest
:
{
title
:
'<p>2</p>'
,
title
:
'<p>2</p>'
,
title_text
:
'2'
,
title_text
:
'2'
,
description
:
'<p>42</p>'
,
description
:
'<p>42</p>'
,
description_text
:
'42'
,
description_text
:
'42'
,
issue_number
:
1
,
task_status
:
'0 of 0 completed'
,
task_status
:
'0 of 0 completed'
,
updated_at
:
new
Date
().
toString
(),
},
},
issueSpecRequest
:
{
issueSpecRequest
:
{
title
:
'<p>this is a title</p>'
,
title
:
'<p>this is a title</p>'
,
title_text
:
'this is a title'
,
title_text
:
'this is a title'
,
description
:
'<li class="task-list-item enabled"><input type="checkbox" class="task-list-item-checkbox">Task List Item</li>'
,
description
:
'<li class="task-list-item enabled"><input type="checkbox" class="task-list-item-checkbox">Task List Item</li>'
,
description_text
:
'- [ ] Task List Item'
,
description_text
:
'- [ ] Task List Item'
,
issue_number
:
1
,
task_status
:
'0 of 1 completed'
,
task_status
:
'0 of 1 completed'
,
updated_at
:
new
Date
().
toString
(),
},
},
};
};
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