mirror of
https://github.com/redmine/redmine.git
synced 2025-10-26 00:36:14 +02:00
Support automatic list marker insertion in textareas (#43095).
Patch by Mizuki ISHIKAWA (user:ishikawa999). git-svn-id: https://svn.redmine.org/redmine/trunk@24003 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
@@ -1438,6 +1438,16 @@ module ApplicationHelper
|
||||
end
|
||||
end
|
||||
|
||||
def list_autofill_data_attributes
|
||||
return {} if Setting.text_formatting.blank?
|
||||
|
||||
{
|
||||
controller: 'list-autofill',
|
||||
action: 'beforeinput->list-autofill#handleBeforeInput',
|
||||
list_autofill_text_formatting_param: Setting.text_formatting
|
||||
}
|
||||
end
|
||||
|
||||
unless const_defined?(:MACROS_RE)
|
||||
MACROS_RE = /(
|
||||
(!)? # escaping
|
||||
|
||||
@@ -87,7 +87,7 @@ module CustomFieldsHelper
|
||||
css += ' wiki-edit'
|
||||
data = {
|
||||
:auto_complete => true
|
||||
}
|
||||
}.merge(list_autofill_data_attributes)
|
||||
end
|
||||
cf.format.edit_tag(
|
||||
self,
|
||||
@@ -137,7 +137,7 @@ module CustomFieldsHelper
|
||||
css += ' wiki-edit'
|
||||
data = {
|
||||
:auto_complete => true
|
||||
}
|
||||
}.merge(list_autofill_data_attributes)
|
||||
end
|
||||
custom_field.format.bulk_edit_tag(
|
||||
self,
|
||||
|
||||
124
app/javascript/controllers/list_autofill_controller.js
Normal file
124
app/javascript/controllers/list_autofill_controller.js
Normal file
@@ -0,0 +1,124 @@
|
||||
import { Controller } from '@hotwired/stimulus'
|
||||
|
||||
class ListAutofillHandler {
|
||||
constructor(inputElement, format) {
|
||||
this.input = inputElement
|
||||
this.format = format
|
||||
}
|
||||
|
||||
run(event) {
|
||||
const { selectionStart, value } = this.input
|
||||
|
||||
const beforeCursor = value.slice(0, selectionStart)
|
||||
const lines = beforeCursor.split("\n")
|
||||
const currentLine = lines[lines.length - 1]
|
||||
const lineStartPos = beforeCursor.lastIndexOf("\n") + 1
|
||||
|
||||
let formatter
|
||||
switch (this.format) {
|
||||
case 'common_mark':
|
||||
formatter = new CommonMarkListFormatter()
|
||||
break
|
||||
case 'textile':
|
||||
formatter = new TextileListFormatter()
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
const result = formatter.format(currentLine)
|
||||
|
||||
if (!result) return
|
||||
|
||||
switch (result.action) {
|
||||
case 'remove':
|
||||
event.preventDefault()
|
||||
this.input.setRangeText('', lineStartPos, selectionStart, 'start')
|
||||
break
|
||||
case 'insert':
|
||||
event.preventDefault()
|
||||
const insertText = "\n" + result.text
|
||||
const newValue = value.slice(0, selectionStart) + insertText + value.slice(selectionStart)
|
||||
const newCursor = selectionStart + insertText.length
|
||||
this.input.value = newValue
|
||||
this.input.setSelectionRange(newCursor, newCursor)
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CommonMarkListFormatter {
|
||||
format(line) {
|
||||
// Match list items in CommonMark syntax.
|
||||
// Captures either an ordered list (e.g., '1. ' or '2) ') or an unordered list (e.g., '* ', '- ', '+ ').
|
||||
// The regex structure:
|
||||
// ^(\s*) → leading whitespace
|
||||
// (?:(\d+)([.)]) → an ordered list marker: number followed by '.' or ')'
|
||||
// |([*+\-]) → OR an unordered list marker: '*', '+', or '-'
|
||||
// (.*)$ → the actual list item content
|
||||
//
|
||||
// Examples:
|
||||
// '2. ordered text' → indent='', number='2', delimiter='.', bullet=undefined, content='ordered text'
|
||||
// ' 3) nested ordered text' → indent=' ', number='3', delimiter=')', bullet=undefined, content='nested ordered text'
|
||||
// '* unordered text' → indent='', number=undefined, delimiter=undefined, bullet='*', content='unordered text'
|
||||
// '+ unordered text' → indent='', number=undefined, delimiter=undefined, bullet='+', content='unordered text'
|
||||
// ' - nested unordered text' → indent=' ', number=undefined, delimiter=undefined, bullet='-', content='nested unordered text'
|
||||
const match = line.match(/^(\s*)(?:(\d+)([.)])|([*+\-])) (.*)$/)
|
||||
if (!match) return null
|
||||
|
||||
const indent = match[1]
|
||||
const number = match[2]
|
||||
const delimiter = match[3]
|
||||
const bullet = match[4]
|
||||
const content = match[5]
|
||||
|
||||
if (content === '') {
|
||||
return { action: 'remove' }
|
||||
}
|
||||
|
||||
if (number) {
|
||||
const nextNumber = parseInt(number, 10) + 1
|
||||
return { action: 'insert', text: `${indent}${nextNumber}${delimiter} ` }
|
||||
} else {
|
||||
return { action: 'insert', text: `${indent}${bullet} ` }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TextileListFormatter {
|
||||
format(line) {
|
||||
// Match list items in Textile syntax.
|
||||
// Captures either an ordered list (using '#') or an unordered list (using '*').
|
||||
// The regex structure:
|
||||
// ^([*#]+) → one or more list markers: '*' for unordered, '#' for ordered
|
||||
// (.*)$ → the actual list item content
|
||||
//
|
||||
// Examples:
|
||||
// '# ordered text' → marker='#', content='ordered text'
|
||||
// '## nested ordered text' → marker='##', content='nested ordered text'
|
||||
// '* unordered text' → marker='*', content='unordered text'
|
||||
// '** nested unordered text' → marker='**', content='nested unordered text'
|
||||
const match = line.match(/^([*#]+) (.*)$/)
|
||||
if (!match) return null
|
||||
|
||||
const marker = match[1]
|
||||
const content = match[2]
|
||||
|
||||
if (content === '') {
|
||||
return { action: 'remove' }
|
||||
}
|
||||
|
||||
return { action: 'insert', text: `${marker} ` }
|
||||
}
|
||||
}
|
||||
|
||||
export default class extends Controller {
|
||||
handleBeforeInput(event) {
|
||||
if (event.inputType != 'insertLineBreak') return
|
||||
|
||||
const format = event.params.textFormatting
|
||||
new ListAutofillHandler(event.currentTarget, format).run(event)
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,8 @@
|
||||
<p><%= f.text_area :description, :cols => 60, :rows => 15, :class => 'wiki-edit',
|
||||
:data => {
|
||||
:auto_complete => true
|
||||
} %></p>
|
||||
}.merge(list_autofill_data_attributes)
|
||||
%></p>
|
||||
|
||||
<% @document.custom_field_values.each do |value| %>
|
||||
<p><%= custom_field_tag_with_label :document, value %></p>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<%= f.text_area :notes, :cols => 60, :rows => 10, :class => 'wiki-edit',
|
||||
:data => {
|
||||
:auto_complete => true
|
||||
},
|
||||
}.merge(list_autofill_data_attributes),
|
||||
:no_label => true %>
|
||||
<%= wikitoolbar_for 'issue_notes', preview_issue_path(:project_id => @project, :issue_id => @issue) %>
|
||||
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
<%= f.text_area :description, :cols => 60, :accesskey => accesskey(:edit), :class => 'wiki-edit',
|
||||
:rows => [[10, @issue.description.to_s.length / 50].max, 20].min,
|
||||
:data => {
|
||||
:auto_complete => true,
|
||||
},
|
||||
:auto_complete => true
|
||||
}.merge(list_autofill_data_attributes),
|
||||
:no_label => true %>
|
||||
<% end %>
|
||||
<%= link_to_function content_tag(:span, sprite_icon('edit', l(:button_edit)), :class => 'icon icon-edit'), '$(this).hide(); $("#issue_description_and_toolbar").show()' unless @issue.new_record? %>
|
||||
|
||||
@@ -223,7 +223,7 @@
|
||||
<%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit',
|
||||
:data => {
|
||||
:auto_complete => true
|
||||
}
|
||||
}.merge(list_autofill_data_attributes)
|
||||
%>
|
||||
<%= wikitoolbar_for 'notes' %>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
:rows => (@journal.notes.blank? ? 10 : [[10, @journal.notes.length / 50].max, 100].min),
|
||||
:data => {
|
||||
:auto_complete => true
|
||||
}
|
||||
}.merge(list_autofill_data_attributes)
|
||||
%>
|
||||
<% if @journal.safe_attribute? 'private_notes' %>
|
||||
<%= hidden_field_tag 'journal[private_notes]', '0' %>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
:accesskey => accesskey(:edit),
|
||||
:data => {
|
||||
:auto_complete => true
|
||||
}
|
||||
}.merge(list_autofill_data_attributes)
|
||||
%></p>
|
||||
<%= wikitoolbar_for 'message_content', preview_board_message_path(:board_id => @board, :id => @message) %>
|
||||
<!--[eoform:message]-->
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<p><%= f.text_area :description, :required => true, :cols => 60, :rows => 15, :class => 'wiki-edit',
|
||||
:data => {
|
||||
:auto_complete => true
|
||||
}
|
||||
}.merge(list_autofill_data_attributes)
|
||||
%></p>
|
||||
<p id="attachments_form"><label><%= l(:label_attachment_plural) %></label><%= render :partial => 'attachments/form', :locals => {:container => @news} %></p>
|
||||
</div>
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
<%= text_area 'comment', 'comments', :cols => 80, :rows => 15, :class => 'wiki-edit',
|
||||
:data => {
|
||||
:auto_complete => true
|
||||
}
|
||||
}.merge(list_autofill_data_attributes)
|
||||
%>
|
||||
<%= wikitoolbar_for 'comment_comments', preview_news_path(:project_id => @project, :id => @news) %>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<!--[form:project]-->
|
||||
<p><%= f.text_field :name, :required => true, :size => 60 %></p>
|
||||
|
||||
<p><%= f.text_area :description, :rows => 8, :class => 'wiki-edit' %></p>
|
||||
<p><%= f.text_area :description, :rows => 8, :class => 'wiki-edit', :data => list_autofill_data_attributes %></p>
|
||||
<p><%= f.text_field :identifier, :required => true, :size => 60, :disabled => @project.identifier_frozen?, :maxlength => Project::IDENTIFIER_MAX_LENGTH %>
|
||||
<% unless @project.identifier_frozen? %>
|
||||
<em class="info"><%= l(:text_length_between, :min => 1, :max => Project::IDENTIFIER_MAX_LENGTH) %> <%= l(:text_project_identifier_info).html_safe %></em>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<p><%= text_field_tag 'q', @question, :size => 60, :id => 'search-input',
|
||||
:data => {
|
||||
:auto_complete => true
|
||||
} %>
|
||||
}.merge(list_autofill_data_attributes) %>
|
||||
<%= project_select_tag %>
|
||||
<%= hidden_field_tag 'all_words', '', :id => nil %>
|
||||
<label><%= check_box_tag 'all_words', 1, @all_words %> <%= l(:label_all_words) %></label>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="box tabular settings">
|
||||
<p><%= setting_text_field :app_title, :size => 30 %></p>
|
||||
|
||||
<p><%= setting_text_area :welcome_text, :cols => 60, :rows => 5, :class => 'wiki-edit' %></p>
|
||||
<p><%= setting_text_area :welcome_text, :cols => 60, :rows => 5, :class => 'wiki-edit', :data => list_autofill_data_attributes %></p>
|
||||
<%= wikitoolbar_for 'settings_welcome_text' %>
|
||||
|
||||
|
||||
|
||||
@@ -19,12 +19,12 @@
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="box"><legend><%= l(:setting_emails_header) %></legend>
|
||||
<%= setting_text_area :emails_header, :label => false, :class => 'wiki-edit', :rows => 5 %>
|
||||
<%= setting_text_area :emails_header, :label => false, :class => 'wiki-edit', :rows => 5, :data => list_autofill_data_attributes %>
|
||||
<%= wikitoolbar_for 'settings_emails_header' %>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="box"><legend><%= l(:setting_emails_footer) %></legend>
|
||||
<%= setting_text_area :emails_footer, :label => false, :class => 'wiki-edit', :rows => 5 %>
|
||||
<%= setting_text_area :emails_footer, :label => false, :class => 'wiki-edit', :rows => 5, :data => list_autofill_data_attributes %>
|
||||
<%= wikitoolbar_for 'settings_emails_footer' %>
|
||||
</fieldset>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
:class => 'wiki-edit',
|
||||
:data => {
|
||||
:auto_complete => true
|
||||
}
|
||||
}.merge(list_autofill_data_attributes)
|
||||
%>
|
||||
|
||||
<% if @page.safe_attribute_names.include?('parent_id') && @wiki.pages.any? %>
|
||||
|
||||
@@ -2427,4 +2427,23 @@ class ApplicationHelperTest < Redmine::HelperTest
|
||||
:class => "wiki-page new"),
|
||||
}
|
||||
end
|
||||
|
||||
def test_list_autofill_data_attributes
|
||||
with_settings :text_formatting => 'textile' do
|
||||
expected = {
|
||||
controller: "list-autofill",
|
||||
action: "keydown->list-autofill#handleEnter",
|
||||
list_autofill_target: "input",
|
||||
list_autofill_text_formatting_param: "textile"
|
||||
}
|
||||
|
||||
assert_equal expected, list_autofill_data_attributes
|
||||
end
|
||||
end
|
||||
|
||||
def test_list_autofill_data_attributes_with_blank_text_formatting
|
||||
with_settings :text_formatting => '' do
|
||||
assert_equal({}, list_autofill_data_attributes)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
297
test/system/list_autofill_test.rb
Normal file
297
test/system/list_autofill_test.rb
Normal file
@@ -0,0 +1,297 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../application_system_test_case'
|
||||
|
||||
class ListAutofillSystemTest < ApplicationSystemTestCase
|
||||
def setup
|
||||
super
|
||||
log_user('jsmith', 'jsmith')
|
||||
end
|
||||
|
||||
def test_autofill_textile_unordered_list
|
||||
with_settings :text_formatting => 'textile' do
|
||||
visit '/projects/ecookbook/issues/new'
|
||||
|
||||
within('form#issue-form') do
|
||||
find('#issue_description').send_keys('* First item')
|
||||
find('#issue_description').send_keys(:enter)
|
||||
|
||||
assert_equal(
|
||||
"* First item\n" \
|
||||
"* ",
|
||||
find('#issue_description').value
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_autofill_textile_ordered_list
|
||||
with_settings :text_formatting => 'textile' do
|
||||
visit '/projects/ecookbook/issues/new'
|
||||
|
||||
within('form#issue-form') do
|
||||
find('#issue_description').send_keys('# First item')
|
||||
find('#issue_description').send_keys(:enter)
|
||||
|
||||
assert_equal(
|
||||
"# First item\n" \
|
||||
"# ",
|
||||
find('#issue_description').value
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_remove_list_marker_for_empty_item
|
||||
with_settings :text_formatting => 'textile' do
|
||||
visit '/projects/ecookbook/issues/new'
|
||||
|
||||
within('form#issue-form') do
|
||||
find('#issue_description').send_keys('* First item')
|
||||
find('#issue_description').send_keys(:enter)
|
||||
find('#issue_description').send_keys(:enter) # Press Enter on empty line removes the marker
|
||||
|
||||
assert_equal(
|
||||
"* First item\n",
|
||||
find('#issue_description').value
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_autofill_markdown_unordered_list
|
||||
with_settings :text_formatting => 'common_mark' do
|
||||
visit '/projects/ecookbook/issues/new'
|
||||
|
||||
within('form#issue-form') do
|
||||
find('#issue_description').send_keys('- First item')
|
||||
find('#issue_description').send_keys(:enter)
|
||||
assert_equal(
|
||||
"- First item\n" \
|
||||
"- ",
|
||||
find('#issue_description').value
|
||||
)
|
||||
|
||||
fill_in 'Description', with: ''
|
||||
find('#issue_description').send_keys('* First item')
|
||||
find('#issue_description').send_keys(:enter)
|
||||
assert_equal(
|
||||
"* First item\n" \
|
||||
"* ",
|
||||
find('#issue_description').value
|
||||
)
|
||||
|
||||
fill_in 'Description', with: ''
|
||||
find('#issue_description').send_keys('+ First item')
|
||||
find('#issue_description').send_keys(:enter)
|
||||
assert_equal(
|
||||
"+ First item\n" \
|
||||
"+ ",
|
||||
find('#issue_description').value
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_autofill_with_markdown_ordered_list
|
||||
with_settings :text_formatting => 'common_mark' do
|
||||
visit '/projects/ecookbook/issues/new'
|
||||
|
||||
within('form#issue-form') do
|
||||
find('#issue_description').send_keys('1. First item')
|
||||
find('#issue_description').send_keys(:enter)
|
||||
|
||||
assert_equal(
|
||||
"1. First item\n" \
|
||||
"2. ",
|
||||
find('#issue_description').value
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_autofill_with_markdown_ordered_list_using_parenthesis
|
||||
with_settings :text_formatting => 'common_mark' do
|
||||
visit '/projects/ecookbook/issues/new'
|
||||
|
||||
within('form#issue-form') do
|
||||
find('#issue_description').send_keys('1) First item')
|
||||
find('#issue_description').send_keys(:enter)
|
||||
|
||||
assert_equal(
|
||||
"1) First item\n" \
|
||||
"2) ",
|
||||
find('#issue_description').value
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_textile_nested_list_autofill
|
||||
with_settings :text_formatting => 'textile' do
|
||||
visit '/projects/ecookbook/issues/new'
|
||||
|
||||
within('form#issue-form') do
|
||||
find('#issue_description').send_keys('* Parent item')
|
||||
find('#issue_description').send_keys(:enter)
|
||||
find('#issue_description').send_keys(:backspace, :backspace) # Remove auto-filled marker
|
||||
find('#issue_description').send_keys('** Child item')
|
||||
find('#issue_description').send_keys(:enter)
|
||||
find('#issue_description').send_keys(:backspace, :backspace, :backspace) # Remove auto-filled marker
|
||||
find('#issue_description').send_keys("*** Grandchild item")
|
||||
find('#issue_description').send_keys(:enter)
|
||||
|
||||
assert_equal(
|
||||
"* Parent item\n" \
|
||||
"** Child item\n" \
|
||||
"*** Grandchild item\n" \
|
||||
"*** ",
|
||||
find('#issue_description').value
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_common_mark_nested_list_autofill
|
||||
with_settings :text_formatting => 'common_mark' do
|
||||
visit '/projects/ecookbook/issues/new'
|
||||
|
||||
within('form#issue-form') do
|
||||
find('#issue_description').send_keys('- Parent item')
|
||||
find('#issue_description').send_keys(:enter)
|
||||
find('#issue_description').send_keys(:backspace, :backspace) # Remove auto-filled marker
|
||||
find('#issue_description').send_keys(' - Child item')
|
||||
find('#issue_description').send_keys(:enter)
|
||||
|
||||
assert_equal(
|
||||
"- Parent item\n" \
|
||||
" - Child item\n" \
|
||||
" - ",
|
||||
find('#issue_description').value
|
||||
)
|
||||
|
||||
find('#issue_description').send_keys(:backspace, :backspace, :backspace, :backspace) # Remove auto-filled marker
|
||||
find('#issue_description').send_keys(' - Grandchild item')
|
||||
find('#issue_description').send_keys(:enter)
|
||||
|
||||
assert_equal(
|
||||
"- Parent item\n" \
|
||||
" - Child item\n" \
|
||||
" - Grandchild item\n" \
|
||||
" - ",
|
||||
find('#issue_description').value
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_common_mark_mixed_list_types
|
||||
with_settings :text_formatting => 'common_mark' do
|
||||
visit '/projects/ecookbook/issues/new'
|
||||
|
||||
within('form#issue-form') do
|
||||
find('#issue_description').send_keys('1. First numbered item')
|
||||
find('#issue_description').send_keys(:enter)
|
||||
find('#issue_description').send_keys(:backspace, :backspace, :backspace) # Remove auto-filled numbered list marker
|
||||
find('#issue_description').send_keys(' - Nested bullet item')
|
||||
find('#issue_description').send_keys(:enter)
|
||||
|
||||
assert_equal(
|
||||
"1. First numbered item\n" \
|
||||
" - Nested bullet item\n" \
|
||||
" - ",
|
||||
find('#issue_description').value
|
||||
)
|
||||
|
||||
find('#issue_description').send_keys(:backspace, :backspace, :backspace, :backspace, :backspace) # Remove auto-filled numbered list marker
|
||||
find('#issue_description').send_keys('2. Second numbered item')
|
||||
find('#issue_description').send_keys(:enter)
|
||||
|
||||
assert_equal(
|
||||
"1. First numbered item\n" \
|
||||
" - Nested bullet item\n" \
|
||||
"2. Second numbered item\n" \
|
||||
"3. ",
|
||||
find('#issue_description').value
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_remove_list_marker_with_single_halfwidth_space_variants
|
||||
with_settings :text_formatting => 'common_mark' do
|
||||
visit '/projects/ecookbook/issues/new'
|
||||
|
||||
within('form#issue-form') do
|
||||
find('#issue_description').click
|
||||
|
||||
# Half-width space only → should remove marker
|
||||
find('#issue_description').send_keys('1. First item', :enter)
|
||||
assert_equal("1. First item\n2. ", find('#issue_description').value)
|
||||
find('#issue_description').send_keys(:enter)
|
||||
assert_equal("1. First item\n", find('#issue_description').value)
|
||||
|
||||
fill_in 'Description', with: ''
|
||||
# Full-width space only → should NOT remove marker
|
||||
find('#issue_description').send_keys('1. First item', :enter)
|
||||
find('#issue_description').send_keys(:backspace, :backspace, :backspace)
|
||||
find('#issue_description').send_keys("2. ", :enter)
|
||||
assert_equal("1. First item\n2. \n", find('#issue_description').value)
|
||||
|
||||
fill_in 'Description', with: ''
|
||||
# Two or more spaces → should NOT remove marker
|
||||
find('#issue_description').send_keys('1. First item', :enter)
|
||||
find('#issue_description').send_keys(:backspace, :backspace, :backspace)
|
||||
find('#issue_description').send_keys("2. ", :enter)
|
||||
assert_equal("1. First item\n2. \n3. ", find('#issue_description').value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_no_autofill_when_content_is_missing_or_invalid_marker
|
||||
with_settings :text_formatting => 'common_mark' do
|
||||
visit '/projects/ecookbook/issues/new'
|
||||
|
||||
within('form#issue-form') do
|
||||
find('#issue_description').click
|
||||
|
||||
# Marker only with no content → should not trigger insert
|
||||
find('#issue_description').send_keys('1.', :enter)
|
||||
assert_equal("1.\n", find('#issue_description').value)
|
||||
|
||||
fill_in 'Description', with: ''
|
||||
# Invalid marker pattern (e.g. double dot) → should not trigger insert
|
||||
find('#issue_description').send_keys('1.. Invalid marker', :enter)
|
||||
assert_equal("1.. Invalid marker\n", find('#issue_description').value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_autofill_ignored_with_none_text_formatting
|
||||
with_settings :text_formatting => '' do
|
||||
visit '/projects/ecookbook/issues/new'
|
||||
|
||||
within('form#issue-form') do
|
||||
find('#issue_description').click
|
||||
|
||||
# Unsupported format → no autofill should occur
|
||||
find('#issue_description').send_keys('* First item', :enter)
|
||||
assert_equal("* First item\n", find('#issue_description').value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_marker_not_inserted_on_empty_line
|
||||
with_settings :text_formatting => 'textile' do
|
||||
visit '/projects/ecookbook/issues/new'
|
||||
|
||||
within('form#issue-form') do
|
||||
find('#issue_description').click
|
||||
|
||||
# Pressing enter on an empty line → should not trigger insert
|
||||
find('#issue_description').send_keys(:enter)
|
||||
assert_equal("\n", find('#issue_description').value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user