html-pipeline-task_list 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "task_list",
3
+ "version": "1.0.2",
4
+ "description": "GitHub-flavored-Markdown TaskList components",
5
+ "homepage": "https://github.com/codetre/html-pipeline-task_list",
6
+ "repository": "https://github.com/codetre/html-pipeline-task_list",
7
+ "license": "MIT",
8
+ "dependencies": {
9
+ "jquery": "^3.4.1",
10
+ "jquery-ujs": "^1.2.2"
11
+ },
12
+ "devDependencies": {
13
+ "node-qunit-puppeteer": "^2.0.1",
14
+ "qunit": "^2.9.3"
15
+ },
16
+ "scripts": {
17
+ "server": "node ./test/server.js .",
18
+ "test": "node-qunit-puppeteer ./test/qunit.html 10000 --no-sandbox"
19
+ },
20
+ "main": [
21
+ "app/assets/javascripts/task_list.js",
22
+ "app/assets/stylesheets/task_list.scss"
23
+ ],
24
+ "ignore": [
25
+ ".gitignore",
26
+ ".travis.yml",
27
+ "*.gemspec",
28
+ "*.md",
29
+ "config.ru",
30
+ "Gemfile",
31
+ "lib/",
32
+ "Rakefile",
33
+ "script/",
34
+ "test/"
35
+ ]
36
+ }
@@ -0,0 +1,109 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <script type="text/javascript" src="dist/jquery.js"></script>
6
+ <script type="text/javascript" src="https://code.jquery.com/jquery-migrate-3.1.0.js"></script>
7
+ <script type="text/javascript" src="dist/jquery-ujs.js"></script>
8
+ <script type="text/javascript" src="dist/task_list.js"></script>
9
+ <script type="text/javascript">
10
+ function logEvent(event) {
11
+ console.log(event)
12
+ $('.log').prepend("<li>" + event.type + "</li>")
13
+ }
14
+
15
+ setInterval(function() {
16
+ if (!$('.log li:first').first().hasClass("timestamp"))
17
+ $('.log').prepend("<li class='timestamp'><time>" + new Date + "</time></li>")
18
+ }, 3000)
19
+
20
+ $(document).on('tasklist:enabled', logEvent)
21
+ $(document).on('tasklist:disabled', logEvent)
22
+ $(document).on('tasklist:change', '.js-task-list-field', function(event){
23
+ logEvent(event)
24
+ $(this).closest('.js-task-list-container').taskList('enable')
25
+ })
26
+ $(document).on('tasklist:changed', '.js-task-list-field', function(event){
27
+ logEvent(event)
28
+ $(this).closest('form').submit()
29
+ })
30
+
31
+ $(document).on('ajaxStart', logEvent)
32
+ $(document).on('ajaxError', logEvent)
33
+ $(document).on('ajaxComplete', logEvent)
34
+ $(document).on('ajaxSuccess', function(event){
35
+ logEvent(event)
36
+ $(this).taskList()
37
+ })
38
+ </script>
39
+
40
+ <style>
41
+ body {
42
+ font: 13px Helvetica;
43
+ padding: 20px;
44
+ }
45
+ input {
46
+ font-size: 20px;
47
+ }
48
+ time {
49
+ font-size: 10px;
50
+ font-style: oblique;
51
+ }
52
+
53
+ .task-list {
54
+ list-style-type: none;
55
+ }
56
+ .task-list .task-list-item {
57
+ opacity: 0.75;
58
+ }
59
+ .task-list .task-list-item.enabled {
60
+ opacity: 1.0;
61
+ }
62
+
63
+ .log {
64
+ position: absolute;
65
+ top: 20px;
66
+ right: 50px;
67
+ color: #222;
68
+ list-style: none;
69
+ margin: 0;
70
+ padding: 0;
71
+ }
72
+ </style>
73
+ </head>
74
+ <body>
75
+ <div class="js-task-list-container is-task-list-enabled">
76
+ <div class="markdown">
77
+ <ul class="task-list">
78
+ <li class="task-list-item enabled">
79
+ <input type="checkbox" class="task-list-item-checkbox" enabled />
80
+ I'm a task list item
81
+ </li>
82
+ <li class="task-list-item enabled">
83
+ <input type="checkbox" class="task-list-item-checkbox" enabled />
84
+ with non-breaking space
85
+ </li>
86
+ <li class="task-list-item enabled">
87
+ <input type="checkbox" class="task-list-item-checkbox" enabled checked />
88
+ completed, lower
89
+ </li>
90
+ <li class="task-list-item enabled">
91
+ <input type="checkbox" class="task-list-item-checkbox" enabled checked />
92
+ completed capitalized
93
+ </li>
94
+ </ul>
95
+ </div>
96
+ <form action="/update" method="POST" data-remote="true" data-type="json">
97
+ <textarea name="comment[body]" class="js-task-list-field" cols="40" rows="10">
98
+ - [ ] I'm a task list item
99
+ - [ ] with non-breaking space
100
+ - [x] completed, lower
101
+ - [X] completed capitalized</textarea>
102
+ </form>
103
+ </div>
104
+
105
+ <ul class="log">
106
+ </ul>
107
+ </body>
108
+ </html>
109
+
@@ -0,0 +1 @@
1
+ ../../node_modules/jquery-ujs/src/rails.js
@@ -0,0 +1 @@
1
+ ../../node_modules/jquery/dist/jquery.js
@@ -0,0 +1 @@
1
+ ../../node_modules/qunit/qunit/qunit.css
@@ -0,0 +1 @@
1
+ ../../node_modules/qunit/qunit/qunit.js
@@ -0,0 +1 @@
1
+ ../../app/assets/javascripts/task_list.js
@@ -0,0 +1,197 @@
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path('../../../test_helper', __dir__)
4
+ require 'html/pipeline/task_list/filter'
5
+
6
+ class HTML::Pipeline::TaskList::FilterTest < Minitest::Test
7
+ def setup
8
+ @pipeline = HTML::Pipeline.new [
9
+ HTML::Pipeline::MarkdownFilter,
10
+ HTML::Pipeline::TaskList::Filter
11
+ ], {}, {}
12
+
13
+ @context = {}
14
+ @item_selector = 'input.task-list-item-checkbox[type=checkbox]'
15
+ end
16
+
17
+ def test_filters_items_in_a_list
18
+ text = <<~MARKDOWN
19
+ - [ ] incomplete
20
+ - [x] complete
21
+ MARKDOWN
22
+
23
+ assert_equal 2, filter(text)[:output].css(@item_selector).size
24
+ end
25
+
26
+ def test_filters_items_with_html_contents
27
+ text = <<~MARKDOWN
28
+ - [ ] incomplete **with bold** text
29
+ - [x] complete __with italic__ text
30
+ MARKDOWN
31
+
32
+ assert_equal 2, filter(text)[:output].css(@item_selector).size
33
+ end
34
+
35
+ def test_filters_items_in_a_list_wrapped_in_paras
36
+ # See issue #7951 for details.
37
+ text = <<~MARKDOWN
38
+ - [ ] one
39
+ - [ ] this one will be wrapped in a para
40
+
41
+ - [ ] this one too, wtf
42
+ MARKDOWN
43
+
44
+ assert_equal 3, filter(text)[:output].css(@item_selector).size
45
+ end
46
+
47
+ def test_populates_result_with_task_list_items
48
+ text = <<~MARKDOWN
49
+ - [ ] incomplete
50
+ - [x] complete
51
+ MARKDOWN
52
+
53
+ result = filter(text)
54
+ assert !result[:task_list_items].empty?
55
+ incomplete, complete = result[:task_list_items]
56
+
57
+ assert incomplete
58
+ assert !incomplete.complete?
59
+
60
+ assert complete
61
+ assert complete.complete?
62
+ end
63
+
64
+ def test_skips_lists_in_code_blocks
65
+ code = <<~MARKDOWN
66
+ ```
67
+ - [ ] incomplete
68
+ - [x] complete
69
+ ```
70
+ MARKDOWN
71
+
72
+ assert filter(code)[:output].css(@item_selector).empty?,
73
+ 'should not have any task list items'
74
+ end
75
+
76
+ def test_handles_encoding_correctly
77
+ unicode = '中文'
78
+ text = <<~MARKDOWN
79
+ - [ ] #{unicode}
80
+ MARKDOWN
81
+
82
+ assert item = filter(text)[:output].css('.task-list-item').pop
83
+ assert_equal unicode, item.text.strip
84
+ end
85
+
86
+ def test_handles_nested_items
87
+ text = <<~MARKDOWN
88
+ - [ ] one
89
+ - [ ] one.one
90
+ MARKDOWN
91
+
92
+ assert filter(text)[:output].css('.task-list-item .task-list-item').pop
93
+ end
94
+
95
+ def test_handles_complicated_nested_items
96
+ text = <<~MARKDOWN
97
+ - [ ] one
98
+ - [ ] one.one
99
+ - [x] one.two
100
+ - [ ] one.two.one
101
+ - [ ] one.two.two
102
+ - [ ] one.three
103
+ - [ ] one.four
104
+ - [ ] two
105
+ - [x] two.one
106
+ - [ ] two.two
107
+ - [ ] three
108
+ MARKDOWN
109
+
110
+ output = filter(text)[:output]
111
+
112
+ assert_equal 6 + 2, output.css('.task-list-item .task-list-item').size
113
+ assert_equal 2, output.css('.task-list-item .task-list-item .task-list-item').size
114
+ end
115
+
116
+ # NOTE: This is an edge case experienced regularly by users using a Swiss
117
+ # German keyboard.
118
+ # See: https://github.com/github/github/pull/18362
119
+ def test_non_breaking_space_between_brackets
120
+ text = "- [\xC2\xA0] ok"
121
+
122
+ assert item = filter(text)[:output].css('.task-list-item').pop, 'item expected'
123
+ assert_equal 'ok', item.text.strip
124
+ end
125
+
126
+ # See: https://github.com/github/github/pull/18362
127
+ def test_non_breaking_space_between_brackets_in_paras
128
+ text = <<~MARKDOWN
129
+ - [\xC2\xA0] one
130
+ - [\xC2\xA0] this one will be wrapped in a para
131
+
132
+ - [\xC2\xA0] this one too, wtf
133
+ MARKDOWN
134
+
135
+ assert_equal 3, filter(text)[:output].css(@item_selector).size
136
+ end
137
+
138
+ def test_capital_x
139
+ text = <<~MARKDOWN
140
+ - [x] lower case
141
+ - [X] capital
142
+ MARKDOWN
143
+
144
+ assert_equal 2, filter(text)[:output].css('[checked]').size
145
+ end
146
+
147
+ def test_skips_task_list_when_no_content
148
+ text = <<~MARKDOWN
149
+ - [x]
150
+ - [X]
151
+ - [ ]
152
+ MARKDOWN
153
+
154
+ result = filter(text)
155
+ assert_nil result[:task_list_items]
156
+ assert result[:output].css(@item_selector).empty?,
157
+ 'should not have any task list items'
158
+ end
159
+
160
+ def test_handles_input_with_a_mix_of_lists_and_task_lists
161
+ text = <<~MARKDOWN
162
+ - Item 1
163
+ - Item 2
164
+
165
+ - [x] Task 1
166
+ - [ ] Task 2
167
+ MARKDOWN
168
+
169
+ result = filter(text)
170
+
171
+ output = result[:output].to_s
172
+ complete, incomplete = result[:task_list_items]
173
+ assert complete.complete?
174
+ refute incomplete.complete?
175
+
176
+ # regular list items
177
+ assert_match %r{<li>\n<p>Item 1</p>\n</li>}, output
178
+ assert_match %r{<li>\n<p>Item 2</p>\n</li>}, output
179
+
180
+ # task list items
181
+ assert_match(
182
+ %r{<p><input type="checkbox" class="task-list-item-checkbox" checked disabled> Task 1</p>},
183
+ output
184
+ )
185
+ assert_match(
186
+ %r{<p><input type="checkbox" class="task-list-item-checkbox" disabled> Task 2</p>},
187
+ output
188
+ )
189
+ end
190
+
191
+ protected
192
+
193
+ def filter(input, context = @context, result = nil)
194
+ result ||= {}
195
+ @pipeline.call(input, context, result)
196
+ end
197
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path('../../../test_helper', __dir__)
4
+
5
+ class HTML::Pipeline::TaskList::SummaryTest < Minitest::Test
6
+ def setup
7
+ @complete = make_item '[x]', 'complete'
8
+ @incomplete = make_item '[ ]', 'incomplete'
9
+ @items = [@complete, @incomplete]
10
+ @summary = make_summary @items
11
+ end
12
+
13
+ def test_no_items
14
+ summary = make_summary []
15
+ assert !summary.items?, 'no task list items are expected'
16
+ end
17
+
18
+ def test_items
19
+ assert @summary.items?, 'task list items are expected'
20
+ assert_equal 2, @summary.item_count
21
+ end
22
+
23
+ def test_complete_count
24
+ assert_equal 1, @summary.complete_count
25
+ end
26
+
27
+ def test_incomplete_count
28
+ assert_equal 1, @summary.incomplete_count
29
+ end
30
+
31
+ protected
32
+
33
+ def make_item(checkbox_text = '[ ]', source = 'an item!')
34
+ HTML::Pipeline::TaskList::Item.new(checkbox_text, source)
35
+ end
36
+
37
+ def make_summary(items)
38
+ HTML::Pipeline::TaskList::Summary.new(items)
39
+ end
40
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path('../../test_helper', __dir__)
4
+
5
+ class TaskListTest < Minitest::Test
6
+ Record = Struct.new(:body) do
7
+ def task_list_items
8
+ []
9
+ end
10
+ end
11
+
12
+ def test_has_summary
13
+ assert summary = task_list('- [ ] one').summary, 'summary expected'
14
+ assert_kind_of HTML::Pipeline::TaskList::Summary, summary
15
+ end
16
+
17
+ def test_complete_item
18
+ item = HTML::Pipeline::TaskList::Item.new('[x]', 'complete')
19
+ assert item.complete?, 'expected to be complete'
20
+ end
21
+
22
+ def test_incomplete_item
23
+ item = HTML::Pipeline::TaskList::Item.new('[ ]', 'incomplete')
24
+ assert !item.complete?, 'expected to be incomplete'
25
+ end
26
+
27
+ protected
28
+
29
+ def task_list(text)
30
+ HTML::Pipeline::TaskList.new(Record.new(text))
31
+ end
32
+ end
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <link rel="stylesheet" href="dist/qunit.css">
6
+ <script type="text/javascript" src="dist/jquery.js"></script>
7
+ <script src="https://code.jquery.com/jquery-migrate-3.1.0.js"></script>
8
+ <script type="text/javascript" src="dist/qunit.js"></script>
9
+ <script type="text/javascript" src="dist/task_list.js"></script>
10
+ <script type="text/javascript" src="unit/events_test.js"></script>
11
+ <script type="text/javascript" src="unit/updates_test.js"></script>
12
+ </head>
13
+ <body>
14
+ <div id="qunit"></div>
15
+ <div id="qunit-fixture"></div>
16
+ </body>
17
+ </html>