Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhil Hughes <me@iamphill.com>2017-05-05 21:47:01 +0300
committerPhil Hughes <me@iamphill.com>2017-05-05 21:47:01 +0300
commit240400152242924e75ea81918b5cfdcf2441549c (patch)
tree3534cbdece629b2c193a1d09c832ab1d91696b5e
parentecaa68a7095750ef7575fdefdb200ef01ef88748 (diff)
parent8985ea1b9c649183e997a76c32aca927a258b51e (diff)
Merge branch 'issue-title-description-realtime' into 'master'
Render Description Realtime :tada: Closes #25049 and #31355 See merge request !10865
-rw-r--r--app/assets/javascripts/issue_show/actions/tasks.js27
-rw-r--r--app/assets/javascripts/issue_show/index.js6
-rw-r--r--app/assets/javascripts/issue_show/issue_title.vue80
-rw-r--r--app/assets/javascripts/issue_show/issue_title_description.vue180
-rw-r--r--app/assets/stylesheets/pages/issues.scss9
-rw-r--r--app/controllers/projects/issues_controller.rb11
-rw-r--r--app/views/projects/issues/show.html.haml11
-rw-r--r--changelogs/unreleased/issue-title-description-realtime.yml4
-rw-r--r--features/project/issues/issues.feature1
-rw-r--r--spec/features/issues/award_spec.rb6
-rw-r--r--spec/features/task_lists_spec.rb35
-rw-r--r--spec/javascripts/issue_show/issue_title_description_spec.js60
-rw-r--r--spec/javascripts/issue_show/issue_title_spec.js22
-rw-r--r--spec/javascripts/issue_show/mock_data.js26
-rw-r--r--spec/javascripts/issue_spec.js6
-rw-r--r--spec/support/features/issuable_slash_commands_shared_examples.rb4
16 files changed, 353 insertions, 135 deletions
diff --git a/app/assets/javascripts/issue_show/actions/tasks.js b/app/assets/javascripts/issue_show/actions/tasks.js
new file mode 100644
index 00000000000..0740a9f559c
--- /dev/null
+++ b/app/assets/javascripts/issue_show/actions/tasks.js
@@ -0,0 +1,27 @@
+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();
+ }
+};
diff --git a/app/assets/javascripts/issue_show/index.js b/app/assets/javascripts/issue_show/index.js
index 4d491e70d83..eb20a597bb5 100644
--- a/app/assets/javascripts/issue_show/index.js
+++ b/app/assets/javascripts/issue_show/index.js
@@ -1,16 +1,16 @@
import Vue from 'vue';
-import IssueTitle from './issue_title.vue';
+import IssueTitle from './issue_title_description.vue';
import '../vue_shared/vue_resource_interceptor';
(() => {
const issueTitleData = document.querySelector('.issue-title-data').dataset;
- const { initialTitle, endpoint } = issueTitleData;
+ const { canUpdateTasksClass, endpoint } = issueTitleData;
const vm = new Vue({
el: '.issue-title-entrypoint',
render: createElement => createElement(IssueTitle, {
props: {
- initialTitle,
+ canUpdateTasksClass,
endpoint,
},
}),
diff --git a/app/assets/javascripts/issue_show/issue_title.vue b/app/assets/javascripts/issue_show/issue_title.vue
deleted file mode 100644
index 00b0e56030a..00000000000
--- a/app/assets/javascripts/issue_show/issue_title.vue
+++ /dev/null
@@ -1,80 +0,0 @@
-<script>
-import Visibility from 'visibilityjs';
-import Poll from './../lib/utils/poll';
-import Service from './services/index';
-
-export default {
- props: {
- initialTitle: { required: true, type: String },
- endpoint: { 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) => {
- if (process.env.NODE_ENV !== 'production') {
- // eslint-disable-next-line no-console
- console.error('ISSUE SHOW TITLE REALTIME ERROR', err);
- } else {
- throw new Error(err);
- }
- },
- });
-
- return {
- poll,
- timeoutId: null,
- title: this.initialTitle,
- };
- },
- methods: {
- renderResponse(res) {
- const body = JSON.parse(res.body);
- this.triggerAnimation(body);
- },
- triggerAnimation(body) {
- const { title } = body;
-
- /**
- * since opacity is changed, even if there is no diff for Vue to update
- * we must check the title even on a 304 to ensure no visual change
- */
- if (this.title === title) return;
-
- this.$el.style.opacity = 0;
-
- this.timeoutId = setTimeout(() => {
- this.title = title;
-
- this.$el.style.transition = 'opacity 0.2s ease';
- this.$el.style.opacity = 1;
-
- clearTimeout(this.timeoutId);
- }, 100);
- },
- },
- created() {
- if (!Visibility.hidden()) {
- this.poll.makeRequest();
- }
-
- Visibility.change(() => {
- if (!Visibility.hidden()) {
- this.poll.restart();
- } else {
- this.poll.stop();
- }
- });
- },
-};
-</script>
-
-<template>
- <h2 class="title" v-html="title"></h2>
-</template>
diff --git a/app/assets/javascripts/issue_show/issue_title_description.vue b/app/assets/javascripts/issue_show/issue_title_description.vue
new file mode 100644
index 00000000000..dc3ba2550c5
--- /dev/null
+++ b/app/assets/javascripts/issue_show/issue_title_description.vue
@@ -0,0 +1,180 @@
+<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>
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index b18bbc329c3..ad3b6e0344b 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -18,6 +18,15 @@
}
}
+.issue-realtime-pre-pulse {
+ opacity: 0;
+}
+
+.issue-realtime-trigger-pulse {
+ transition: opacity $fade-in-duration linear;
+ opacity: 1;
+}
+
.check-all-holder {
line-height: 36px;
float: left;
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index af9157bfbb5..b7a55a623b7 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -201,7 +201,16 @@ class Projects::IssuesController < Projects::ApplicationController
def rendered_title
Gitlab::PollingInterval.set_header(response, interval: 3_000)
- render json: { title: view_context.markdown_field(@issue, :title) }
+
+ render json: {
+ title: view_context.markdown_field(@issue, :title),
+ title_text: @issue.title,
+ description: view_context.markdown_field(@issue, :description),
+ description_text: @issue.description,
+ task_status: @issue.task_status,
+ issue_number: @issue.iid,
+ updated_at: @issue.updated_at,
+ }
end
def create_merge_request
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 1418ad73553..9084883eb3e 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -51,16 +51,11 @@
.issue-details.issuable-details
.detail-page-description.content-block
- .issue-title-data.hidden{ "data" => { "initial-title" => markdown_field(@issue, :title),
- "endpoint" => rendered_title_namespace_project_issue_path(@project.namespace, @project, @issue),
+ .issue-title-data.hidden{ "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' : '',
} }
.issue-title-entrypoint
- - 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')
#merge-requests{ data: { url: referenced_merge_requests_namespace_project_issue_url(@project.namespace, @project, @issue) } }
diff --git a/changelogs/unreleased/issue-title-description-realtime.yml b/changelogs/unreleased/issue-title-description-realtime.yml
new file mode 100644
index 00000000000..003e1a4ab33
--- /dev/null
+++ b/changelogs/unreleased/issue-title-description-realtime.yml
@@ -0,0 +1,4 @@
+---
+title: Add realtime descriptions to issue show pages
+merge_request:
+author:
diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature
index 4dee0cd23dc..1b00d8a32a0 100644
--- a/features/project/issues/issues.feature
+++ b/features/project/issues/issues.feature
@@ -82,6 +82,7 @@ Feature: Project Issues
# Markdown
+ @javascript
Scenario: Headers inside the description should have ids generated for them.
Given I visit issue page "Release 0.4"
Then Header "Description header" should have correct id and link
diff --git a/spec/features/issues/award_spec.rb b/spec/features/issues/award_spec.rb
index 401e1ea2b89..08e3f99e29f 100644
--- a/spec/features/issues/award_spec.rb
+++ b/spec/features/issues/award_spec.rb
@@ -6,9 +6,12 @@ feature 'Issue awards', js: true, feature: true do
let(:issue) { create(:issue, project: project) }
describe 'logged in' do
+ include WaitForVueResource
+
before do
login_as(user)
visit namespace_project_issue_path(project.namespace, project, issue)
+ wait_for_vue_resource
end
it 'adds award to issue' do
@@ -38,8 +41,11 @@ feature 'Issue awards', js: true, feature: true do
end
describe 'logged out' do
+ include WaitForVueResource
+
before do
visit namespace_project_issue_path(project.namespace, project, issue)
+ wait_for_vue_resource
end
it 'does not see award menu button' do
diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb
index c33692fc4a9..8bd13caf2b0 100644
--- a/spec/features/task_lists_spec.rb
+++ b/spec/features/task_lists_spec.rb
@@ -62,12 +62,15 @@ feature 'Task Lists', feature: true do
visit namespace_project_issue_path(project.namespace, project, issue)
end
- describe 'for Issues' do
- describe 'multiple tasks' do
+ describe 'for Issues', feature: true do
+ describe 'multiple tasks', js: true do
+ include WaitForVueResource
+
let!(:issue) { create(:issue, description: markdown, author: user, project: project) }
it 'renders' do
visit_issue(project, issue)
+ wait_for_vue_resource
expect(page).to have_selector('ul.task-list', count: 1)
expect(page).to have_selector('li.task-list-item', count: 6)
@@ -76,25 +79,24 @@ feature 'Task Lists', feature: true do
it 'contains the required selectors' do
visit_issue(project, issue)
+ wait_for_vue_resource
- container = '.detail-page-description .description.js-task-list-container'
-
- expect(page).to have_selector(container)
- expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox")
- expect(page).to have_selector("#{container} .js-task-list-field")
- expect(page).to have_selector('form.js-issuable-update')
+ expect(page).to have_selector(".wiki .task-list .task-list-item .task-list-item-checkbox")
expect(page).to have_selector('a.btn-close')
end
it 'is only editable by author' do
visit_issue(project, issue)
- expect(page).to have_selector('.js-task-list-container')
+ wait_for_vue_resource
- logout(:user)
+ expect(page).to have_selector(".wiki .task-list .task-list-item .task-list-item-checkbox")
+ logout(:user)
login_as(user2)
visit current_path
- expect(page).not_to have_selector('.js-task-list-container')
+ wait_for_vue_resource
+
+ expect(page).to have_selector(".wiki .task-list .task-list-item .task-list-item-checkbox")
end
it 'provides a summary on Issues#index' do
@@ -103,11 +105,14 @@ feature 'Task Lists', feature: true do
end
end
- describe 'single incomplete task' do
+ describe 'single incomplete task', js: true do
+ include WaitForVueResource
+
let!(:issue) { create(:issue, description: singleIncompleteMarkdown, author: user, project: project) }
it 'renders' do
visit_issue(project, issue)
+ wait_for_vue_resource
expect(page).to have_selector('ul.task-list', count: 1)
expect(page).to have_selector('li.task-list-item', count: 1)
@@ -116,15 +121,18 @@ feature 'Task Lists', feature: true do
it 'provides a summary on Issues#index' do
visit namespace_project_issues_path(project.namespace, project)
+
expect(page).to have_content("0 of 1 task completed")
end
end
- describe 'single complete task' do
+ describe 'single complete task', js: true do
+ include WaitForVueResource
let!(:issue) { create(:issue, description: singleCompleteMarkdown, author: user, project: project) }
it 'renders' do
visit_issue(project, issue)
+ wait_for_vue_resource
expect(page).to have_selector('ul.task-list', count: 1)
expect(page).to have_selector('li.task-list-item', count: 1)
@@ -133,6 +141,7 @@ feature 'Task Lists', feature: true do
it 'provides a summary on Issues#index' do
visit namespace_project_issues_path(project.namespace, project)
+
expect(page).to have_content("1 of 1 task completed")
end
end
diff --git a/spec/javascripts/issue_show/issue_title_description_spec.js b/spec/javascripts/issue_show/issue_title_description_spec.js
new file mode 100644
index 00000000000..1ec4fe58b08
--- /dev/null
+++ b/spec/javascripts/issue_show/issue_title_description_spec.js
@@ -0,0 +1,60 @@
+import Vue from 'vue';
+import $ from 'jquery';
+import '~/render_math';
+import '~/render_gfm';
+import issueTitleDescription from '~/issue_show/issue_title_description.vue';
+import issueShowData from './mock_data';
+
+window.$ = $;
+
+const issueShowInterceptor = data => (request, next) => {
+ next(request.respondWith(JSON.stringify(data), {
+ status: 200,
+ headers: {
+ 'POLL-INTERVAL': 1,
+ },
+ }));
+};
+
+describe('Issue Title', () => {
+ document.body.innerHTML = '<span id="task_status"></span>';
+
+ let IssueTitleDescriptionComponent;
+
+ beforeEach(() => {
+ IssueTitleDescriptionComponent = Vue.extend(issueTitleDescription);
+ });
+
+ 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));
+
+ const issueShowComponent = new IssueTitleDescriptionComponent({
+ propsData: {
+ canUpdateIssue: '.css-stuff',
+ endpoint: '/gitlab-org/gitlab-shell/issues/9/rendered_title',
+ },
+ }).$mount();
+
+ setTimeout(() => {
+ 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(issueShowComponent.$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');
+
+ Vue.http.interceptors.push(issueShowInterceptor(issueShowData.secondRequest));
+
+ setTimeout(() => {
+ expect(document.querySelector('title').innerText).toContain('2 (#1)');
+ expect(issueShowComponent.$el.querySelector('.title').innerHTML).toContain('<p>2</p>');
+ expect(issueShowComponent.$el.querySelector('.wiki').innerHTML).toContain('<p>42</p>');
+ expect(issueShowComponent.$el.querySelector('.js-task-list-field').innerText).toContain('42');
+
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/issue_show/issue_title_spec.js b/spec/javascripts/issue_show/issue_title_spec.js
deleted file mode 100644
index 03edbf9f947..00000000000
--- a/spec/javascripts/issue_show/issue_title_spec.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import Vue from 'vue';
-import issueTitle from '~/issue_show/issue_title.vue';
-
-describe('Issue Title', () => {
- let IssueTitleComponent;
-
- beforeEach(() => {
- IssueTitleComponent = Vue.extend(issueTitle);
- });
-
- it('should render a title', () => {
- const component = new IssueTitleComponent({
- propsData: {
- initialTitle: 'wow',
- endpoint: '/gitlab-org/gitlab-shell/issues/9/rendered_title',
- },
- }).$mount();
-
- expect(component.$el.classList).toContain('title');
- expect(component.$el.innerHTML).toContain('wow');
- });
-});
diff --git a/spec/javascripts/issue_show/mock_data.js b/spec/javascripts/issue_show/mock_data.js
new file mode 100644
index 00000000000..ad5a7b63470
--- /dev/null
+++ b/spec/javascripts/issue_show/mock_data.js
@@ -0,0 +1,26 @@
+export default {
+ initialRequest: {
+ title: '<p>this is a title</p>',
+ title_text: 'this is a title',
+ description: '<p>this is a description!</p>',
+ description_text: 'this is a description',
+ issue_number: 1,
+ task_status: '2 of 4 completed',
+ },
+ secondRequest: {
+ title: '<p>2</p>',
+ title_text: '2',
+ description: '<p>42</p>',
+ description_text: '42',
+ issue_number: 1,
+ task_status: '0 of 0 completed',
+ },
+ issueSpecRequest: {
+ title: '<p>this is a title</p>',
+ 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_text: '- [ ] Task List Item',
+ issue_number: 1,
+ task_status: '0 of 1 completed',
+ },
+};
diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js
index 0fd573eae3f..763f5ee9e50 100644
--- a/spec/javascripts/issue_spec.js
+++ b/spec/javascripts/issue_spec.js
@@ -81,12 +81,6 @@ describe('Issue', function() {
this.issue = new Issue();
});
- it('modifies the Markdown field', function() {
- spyOn(jQuery, 'ajax').and.stub();
- $('input[type=checkbox]').attr('checked', true).trigger('change');
- expect($('.js-task-list-field').val()).toBe('- [x] Task List Item');
- });
-
it('submits an ajax request on tasklist:changed', function() {
spyOn(jQuery, 'ajax').and.callFake(function(req) {
expect(req.type).toBe('PATCH');
diff --git a/spec/support/features/issuable_slash_commands_shared_examples.rb b/spec/support/features/issuable_slash_commands_shared_examples.rb
index 610decdcddb..595bb92f5c0 100644
--- a/spec/support/features/issuable_slash_commands_shared_examples.rb
+++ b/spec/support/features/issuable_slash_commands_shared_examples.rb
@@ -25,7 +25,7 @@ shared_examples 'issuable record that supports slash commands in its description
wait_for_ajax
end
- describe "new #{issuable_type}" do
+ describe "new #{issuable_type}", js: true do
context 'with commands in the description' do
it "creates the #{issuable_type} and interpret commands accordingly" do
visit public_send("new_namespace_project_#{issuable_type}_path", project.namespace, project, new_url_opts)
@@ -44,7 +44,7 @@ shared_examples 'issuable record that supports slash commands in its description
end
end
- describe "note on #{issuable_type}" do
+ describe "note on #{issuable_type}", js: true do
before do
visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable)
end