# Specifications for behavior common to all objects with executable attributes.
# It takes a `issuable_type`, and expect an `issuable`.

shared_examples 'issuable record that supports quick actions in its description and notes' do |issuable_type|
  include Spec::Support::Helpers::Features::NotesHelpers

  let(:maintainer) { create(:user) }
  let(:project) do
    case issuable_type
    when :merge_request
      create(:project, :public, :repository)
    when :issue
      create(:project, :public)
    end
  end
  let!(:milestone) { create(:milestone, project: project, title: 'ASAP') }
  let!(:label_bug) { create(:label, project: project, title: 'bug') }
  let!(:label_feature) { create(:label, project: project, title: 'feature') }
  let(:new_url_opts) { {} }

  before do
    project.add_maintainer(maintainer)

    gitlab_sign_in(maintainer)
  end

  after do
    # Ensure all outstanding Ajax requests are complete to avoid database deadlocks
    wait_for_requests
  end

  describe "new #{issuable_type}", :js do
    context 'with commands in the description' do
      it "creates the #{issuable_type} and interpret commands accordingly" do
        case issuable_type
        when :merge_request
          visit public_send("namespace_project_new_merge_request_path", project.namespace, project, new_url_opts)
        when :issue
          visit public_send("new_namespace_project_issue_path", project.namespace, project, new_url_opts)
        end
        fill_in "#{issuable_type}_title", with: 'bug 345'
        fill_in "#{issuable_type}_description", with: "bug description\n/label ~bug\n/milestone %\"ASAP\""
        click_button "Submit #{issuable_type}".humanize

        issuable = project.public_send(issuable_type.to_s.pluralize).first

        expect(issuable.description).to eq "bug description"
        expect(issuable.labels).to eq [label_bug]
        expect(issuable.milestone).to eq milestone
        expect(page).to have_content 'bug 345'
        expect(page).to have_content 'bug description'
      end
    end
  end

  describe "note on #{issuable_type}", :js do
    before do
      visit public_send("project_#{issuable_type}_path", project, issuable)
    end

    context 'with a note containing commands' do
      it 'creates a note without the commands and interpret the commands accordingly' do
        assignee = create(:user, username: 'bob')
        add_note("Awesome!\n\n/assign @bob\n\n/label ~bug\n\n/milestone %\"ASAP\"")

        expect(page).to have_content 'Awesome!'
        expect(page).not_to have_content '/assign @bob'
        expect(page).not_to have_content '/label ~bug'
        expect(page).not_to have_content '/milestone %"ASAP"'

        wait_for_requests
        issuable.reload
        note = issuable.notes.user.first

        expect(note.note).to eq "Awesome!"
        expect(issuable.assignees).to eq [assignee]
        expect(issuable.labels).to eq [label_bug]
        expect(issuable.milestone).to eq milestone
      end

      it 'removes the quick action from note and explains it in the preview' do
        preview_note("Awesome!\n\n/close")

        expect(page).to have_content 'Awesome!'
        expect(page).not_to have_content '/close'
        issuable_name = issuable.is_a?(Issue) ? 'issue' : 'merge request'
        expect(page).to have_content "Closes this #{issuable_name}."
      end
    end

    context 'with a note containing only commands' do
      it 'does not create a note but interpret the commands accordingly' do
        assignee = create(:user, username: 'bob')
        add_note("/assign @bob\n\n/label ~bug\n\n/milestone %\"ASAP\"")

        expect(page).not_to have_content '/assign @bob'
        expect(page).not_to have_content '/label ~bug'
        expect(page).not_to have_content '/milestone %"ASAP"'
        expect(page).to have_content 'Commands applied'

        issuable.reload

        expect(issuable.notes.user).to be_empty
        expect(issuable.assignees).to eq [assignee]
        expect(issuable.labels).to eq [label_bug]
        expect(issuable.milestone).to eq milestone
      end
    end

    context "with a note closing the #{issuable_type}" do
      before do
        expect(issuable).to be_open
      end

      context "when current user can close #{issuable_type}" do
        it "closes the #{issuable_type}" do
          add_note("/close")

          expect(page).not_to have_content '/close'
          expect(page).to have_content 'Commands applied'

          expect(issuable.reload).to be_closed
        end
      end

      context "when current user cannot close #{issuable_type}" do
        before do
          guest = create(:user)
          project.add_guest(guest)

          gitlab_sign_out
          gitlab_sign_in(guest)
          visit public_send("project_#{issuable_type}_path", project, issuable)
        end

        it "does not close the #{issuable_type}" do
          add_note("/close")

          expect(page).not_to have_content 'Commands applied'

          expect(issuable).to be_open
        end
      end
    end

    context "with a note reopening the #{issuable_type}" do
      before do
        issuable.close
        expect(issuable).to be_closed
      end

      context "when current user can reopen #{issuable_type}" do
        it "reopens the #{issuable_type}" do
          add_note("/reopen")

          expect(page).not_to have_content '/reopen'
          expect(page).to have_content 'Commands applied'

          expect(issuable.reload).to be_open
        end
      end

      context "when current user cannot reopen #{issuable_type}" do
        before do
          guest = create(:user)
          project.add_guest(guest)

          gitlab_sign_out
          gitlab_sign_in(guest)
          visit public_send("project_#{issuable_type}_path", project, issuable)
        end

        it "does not reopen the #{issuable_type}" do
          add_note("/reopen")

          expect(page).not_to have_content 'Commands applied'

          expect(issuable).to be_closed
        end
      end
    end

    context "with a note changing the #{issuable_type}'s title" do
      context "when current user can change title of #{issuable_type}" do
        it "reopens the #{issuable_type}" do
          add_note("/title Awesome new title")

          expect(page).not_to have_content '/title'
          expect(page).to have_content 'Commands applied'

          expect(issuable.reload.title).to eq 'Awesome new title'
        end
      end

      context "when current user cannot change title of #{issuable_type}" do
        before do
          guest = create(:user)
          project.add_guest(guest)

          gitlab_sign_out
          gitlab_sign_in(guest)
          visit public_send("project_#{issuable_type}_path", project, issuable)
        end

        it "does not change the #{issuable_type} title" do
          add_note("/title Awesome new title")

          expect(page).not_to have_content 'Commands applied'

          expect(issuable.reload.title).not_to eq 'Awesome new title'
        end
      end
    end

    context "with a note marking the #{issuable_type} as todo" do
      it "creates a new todo for the #{issuable_type}" do
        add_note("/todo")

        expect(page).not_to have_content '/todo'
        expect(page).to have_content 'Commands applied'

        todos = TodosFinder.new(maintainer).execute
        todo = todos.first

        expect(todos.size).to eq 1
        expect(todo).to be_pending
        expect(todo.target).to eq issuable
        expect(todo.author).to eq maintainer
        expect(todo.user).to eq maintainer
      end
    end

    context "with a note marking the #{issuable_type} as done" do
      before do
        TodoService.new.mark_todo(issuable, maintainer)
      end

      it "creates a new todo for the #{issuable_type}" do
        todos = TodosFinder.new(maintainer).execute
        todo = todos.first

        expect(todos.size).to eq 1
        expect(todos.first).to be_pending
        expect(todo.target).to eq issuable
        expect(todo.author).to eq maintainer
        expect(todo.user).to eq maintainer

        add_note("/done")

        expect(page).not_to have_content '/done'
        expect(page).to have_content 'Commands applied'

        expect(todo.reload).to be_done
      end
    end

    context "with a note subscribing to the #{issuable_type}" do
      it "creates a new todo for the #{issuable_type}" do
        expect(issuable.subscribed?(maintainer, project)).to be_falsy

        add_note("/subscribe")

        expect(page).not_to have_content '/subscribe'
        expect(page).to have_content 'Commands applied'

        expect(issuable.subscribed?(maintainer, project)).to be_truthy
      end
    end

    context "with a note unsubscribing to the #{issuable_type} as done" do
      before do
        issuable.subscribe(maintainer, project)
      end

      it "creates a new todo for the #{issuable_type}" do
        expect(issuable.subscribed?(maintainer, project)).to be_truthy

        add_note("/unsubscribe")

        expect(page).not_to have_content '/unsubscribe'
        expect(page).to have_content 'Commands applied'

        expect(issuable.subscribed?(maintainer, project)).to be_falsy
      end
    end

    context "with a note assigning the #{issuable_type} to the current user" do
      it "assigns the #{issuable_type} to the current user" do
        add_note("/assign me")

        expect(page).not_to have_content '/assign me'
        expect(page).to have_content 'Commands applied'

        expect(issuable.reload.assignees).to eq [maintainer]
      end
    end

    context "with a note locking the #{issuable_type} discussion" do
      before do
        issuable.update(discussion_locked: false)
        expect(issuable).not_to be_discussion_locked
      end

      context "when current user can lock #{issuable_type} discussion" do
        it "locks the #{issuable_type} discussion" do
          add_note("/lock")

          expect(page).not_to have_content '/lock'
          expect(page).to have_content 'Commands applied'

          expect(issuable.reload).to be_discussion_locked
        end
      end

      context "when current user cannot lock #{issuable_type}" do
        before do
          guest = create(:user)
          project.add_guest(guest)

          gitlab_sign_out
          sign_in(guest)
          visit public_send("project_#{issuable_type}_path", project, issuable)
        end

        it "does not lock the #{issuable_type} discussion" do
          add_note("/lock")

          expect(page).not_to have_content 'Commands applied'

          expect(issuable).not_to be_discussion_locked
        end
      end
    end

    context "with a note unlocking the #{issuable_type} discussion" do
      before do
        issuable.update(discussion_locked: true)
        expect(issuable).to be_discussion_locked
      end

      context "when current user can unlock #{issuable_type} discussion" do
        it "unlocks the #{issuable_type} discussion" do
          add_note("/unlock")

          expect(page).not_to have_content '/unlock'
          expect(page).to have_content 'Commands applied'

          expect(issuable.reload).not_to be_discussion_locked
        end
      end

      context "when current user cannot unlock #{issuable_type}" do
        before do
          guest = create(:user)
          project.add_guest(guest)

          gitlab_sign_out
          sign_in(guest)
          visit public_send("project_#{issuable_type}_path", project, issuable)
        end

        it "does not unlock the #{issuable_type} discussion" do
          add_note("/unlock")

          expect(page).not_to have_content 'Commands applied'

          expect(issuable).to be_discussion_locked
        end
      end
    end
  end

  describe "preview of note on #{issuable_type}", :js do
    it 'removes quick actions from note and explains them' do
      create(:user, username: 'bob')

      visit public_send("project_#{issuable_type}_path", project, issuable)

      page.within('.js-main-target-form') do
        fill_in 'note[note]', with: "Awesome!\n/assign @bob "
        click_on 'Preview'

        expect(page).to have_content 'Awesome!'
        expect(page).not_to have_content '/assign @bob'
        expect(page).to have_content 'Assigns @bob.'
      end
    end
  end
end
