crass 0.0.2 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/crass/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Crass
4
- VERSION = '0.0.2'
4
+ VERSION = '0.1.0'
5
5
  end
@@ -0,0 +1,345 @@
1
+ # encoding: utf-8
2
+
3
+ # Includes tests based on Simon Sapin's CSS parsing tests:
4
+ # https://github.com/SimonSapin/css-parsing-tests/
5
+
6
+ shared_tests_for 'parsing a list of rules' do
7
+ it 'should parse an empty stylesheet' do
8
+ assert_equal([], parse(''))
9
+ assert_equal([], parse('foo'))
10
+ assert_equal([], parse('foo 4'))
11
+ end
12
+
13
+ describe 'should parse an at-rule' do
14
+ describe 'without a block' do
15
+ it 'without a prelude' do
16
+ tree = parse('@foo')
17
+ rule = tree[0]
18
+
19
+ assert_equal(1, tree.size)
20
+ assert_equal(:at_rule, rule[:node])
21
+ assert_equal("foo", rule[:name])
22
+ assert_equal([], rule[:prelude])
23
+ assert_tokens("@foo", rule[:tokens])
24
+ end
25
+
26
+ it 'with a prelude followed by a comment' do
27
+ tree = parse("@foo bar; \t/* comment */")
28
+ rule = tree[0]
29
+
30
+ assert_equal(2, tree.size)
31
+ assert_equal(:at_rule, rule[:node])
32
+ assert_equal("foo", rule[:name])
33
+ assert_tokens(" bar", rule[:prelude], 4)
34
+ assert_tokens("@foo bar;", rule[:tokens])
35
+ assert_tokens(" \t", tree[1], 9)
36
+ end
37
+
38
+ it 'with a prelude followed by a comment, when :preserve_comments == true' do
39
+ options = {:preserve_comments => true}
40
+ tree = parse("@foo bar; \t/* comment */", options)
41
+ rule = tree[0]
42
+
43
+ assert_equal(3, tree.size)
44
+ assert_equal(:at_rule, rule[:node])
45
+ assert_equal("foo", rule[:name])
46
+ assert_tokens(" bar", rule[:prelude], 4, options)
47
+ assert_tokens("@foo bar;", rule[:tokens], 0, options)
48
+ assert_tokens(" \t", tree[1], 9, options)
49
+ assert_tokens("/* comment */", tree[2], 11, options)
50
+ end
51
+
52
+ it 'with a prelude containing a simple block' do
53
+ tree = parse("@foo [ bar")
54
+ rule = tree[0]
55
+
56
+ assert_equal(1, tree.size)
57
+ assert_equal(:at_rule, rule[:node])
58
+ assert_equal("foo", rule[:name])
59
+ assert_tokens("@foo [ bar", rule[:tokens])
60
+
61
+ prelude = rule[:prelude]
62
+ assert_equal(2, prelude.size)
63
+ assert_tokens(" ", prelude[0], 4)
64
+
65
+ block = prelude[1]
66
+ assert_equal(:simple_block, block[:node])
67
+ assert_equal("[", block[:start])
68
+ assert_equal("]", block[:end])
69
+ assert_tokens("[ bar", block[:tokens], 5)
70
+ assert_tokens(" bar", block[:value], 6)
71
+ end
72
+ end
73
+
74
+ describe 'with a block' do
75
+ it 'unclosed' do
76
+ tree = parse("@foo { bar")
77
+ rule = tree[0]
78
+
79
+ assert_equal(1, tree.size)
80
+ assert_equal(:at_rule, rule[:node])
81
+ assert_equal("foo", rule[:name])
82
+ assert_tokens(" ", rule[:prelude], 4)
83
+ assert_tokens("@foo { bar", rule[:tokens])
84
+
85
+ block = rule[:block]
86
+ assert_equal(:simple_block, block[:node])
87
+ assert_equal("{", block[:start])
88
+ assert_equal("}", block[:end])
89
+ assert_tokens("{ bar", block[:tokens], 5)
90
+ assert_tokens(" bar", block[:value], 6)
91
+ end
92
+
93
+ it 'unclosed, preceded by a comment' do
94
+ tree = parse(" /**/ @foo bar{[(4")
95
+ rule = tree[2]
96
+
97
+ assert_equal(3, tree.size)
98
+ assert_tokens(" /**/ ", tree[0..1])
99
+ assert_equal(:at_rule, rule[:node])
100
+ assert_equal("foo", rule[:name])
101
+ assert_tokens(" bar", rule[:prelude], 10)
102
+ assert_tokens("@foo bar{[(4", rule[:tokens], 6)
103
+
104
+ block = rule[:block]
105
+ assert_equal(:simple_block, block[:node])
106
+ assert_equal("{", block[:start])
107
+ assert_equal("}", block[:end])
108
+ assert_tokens("{[(4", block[:tokens], 14)
109
+ assert_equal(1, block[:value].size)
110
+
111
+ block = block[:value].first
112
+ assert_equal(:simple_block, block[:node])
113
+ assert_equal("[", block[:start])
114
+ assert_equal("]", block[:end])
115
+ assert_tokens("[(4", block[:tokens], 15)
116
+ assert_equal(1, block[:value].size)
117
+
118
+ block = block[:value].first
119
+ assert_equal(:simple_block, block[:node])
120
+ assert_equal("(", block[:start])
121
+ assert_equal(")", block[:end])
122
+ assert_tokens("(4", block[:tokens], 16)
123
+ assert_tokens("4", block[:value], 17)
124
+ end
125
+
126
+ it 'unclosed, preceded by a comment, when :preserve_comments == true' do
127
+ options = {:preserve_comments => true}
128
+ tree = parse(" /**/ @foo bar{[(4", options)
129
+ rule = tree[3]
130
+
131
+ assert_equal(4, tree.size)
132
+ assert_tokens(" /**/ ", tree[0..2], 0, options)
133
+
134
+ assert_equal(:at_rule, rule[:node])
135
+ assert_equal("foo", rule[:name])
136
+ assert_tokens(" bar", rule[:prelude], 10, options)
137
+ assert_tokens("@foo bar{[(4", rule[:tokens], 6, options)
138
+
139
+ block = rule[:block]
140
+ assert_equal(:simple_block, block[:node])
141
+ assert_equal("{", block[:start])
142
+ assert_equal("}", block[:end])
143
+ assert_tokens("{[(4", block[:tokens], 14, options)
144
+ assert_equal(1, block[:value].size)
145
+
146
+ block = block[:value].first
147
+ assert_equal(:simple_block, block[:node])
148
+ assert_equal("[", block[:start])
149
+ assert_equal("]", block[:end])
150
+ assert_tokens("[(4", block[:tokens], 15, options)
151
+ assert_equal(1, block[:value].size)
152
+
153
+ block = block[:value].first
154
+ assert_equal(:simple_block, block[:node])
155
+ assert_equal("(", block[:start])
156
+ assert_equal(")", block[:end])
157
+ assert_tokens("(4", block[:tokens], 16, options)
158
+ assert_tokens("4", block[:value], 17, options)
159
+ end
160
+
161
+ end
162
+ end
163
+
164
+ describe 'should parse a style rule' do
165
+ it 'with preceding comment, selector, block, comment' do
166
+ tree = parse(" /**/ div > p { color: #aaa; } /**/ ")
167
+
168
+ assert_equal(5, tree.size)
169
+ assert_tokens(" /**/ ", tree[0..1])
170
+ assert_tokens(" /**/ ", tree[3..4], 31)
171
+
172
+ rule = tree[2]
173
+ assert_equal(:style_rule, rule[:node])
174
+
175
+ selector = rule[:selector]
176
+ assert_equal(:selector, selector[:node])
177
+ assert_equal("div > p", selector[:value])
178
+ assert_tokens("div > p ", selector[:tokens], 6)
179
+
180
+ children = rule[:children]
181
+ assert_equal(3, children.size)
182
+ assert_tokens(" ", children[0], 15)
183
+ assert_tokens(" ", children[2], 28)
184
+
185
+ property = children[1]
186
+ assert_equal(:property, property[:node])
187
+ assert_equal("color", property[:name])
188
+ assert_equal("#aaa", property[:value])
189
+ assert_equal(false, property[:important])
190
+ assert_tokens("color: #aaa;", property[:tokens], 16)
191
+ end
192
+
193
+ it 'with preceding comment, selector, block, comment, when :preserve_comments == true' do
194
+ options = {:preserve_comments => true}
195
+ tree = parse(" /**/ div > p { color: #aaa; } /**/ ", options)
196
+
197
+ assert_equal(7, tree.size)
198
+ assert_tokens(" /**/ ", tree[0..2], 0, options)
199
+ assert_tokens(" /**/ ", tree[4..6], 31, options)
200
+
201
+ rule = tree[3]
202
+ assert_equal(:style_rule, rule[:node])
203
+
204
+ selector = rule[:selector]
205
+ assert_equal(:selector, selector[:node])
206
+ assert_equal("div > p", selector[:value])
207
+ assert_tokens("div > p ", selector[:tokens], 6, options)
208
+
209
+ children = rule[:children]
210
+ assert_equal(3, children.size)
211
+ assert_tokens(" ", children[0], 15, options)
212
+ assert_tokens(" ", children[2], 28, options)
213
+
214
+ property = children[1]
215
+ assert_equal(:property, property[:node])
216
+ assert_equal("color", property[:name])
217
+ assert_equal("#aaa", property[:value])
218
+ assert_equal(false, property[:important])
219
+ assert_tokens("color: #aaa;", property[:tokens], 16, options)
220
+ end
221
+
222
+ it 'unclosed, with preceding comment, no selector' do
223
+ tree = parse(" /**/ { color: #aaa ")
224
+
225
+ assert_equal(3, tree.size)
226
+ assert_tokens(" /**/ ", tree[0..1])
227
+
228
+ rule = tree[2]
229
+ assert_equal(:style_rule, rule[:node])
230
+
231
+ selector = rule[:selector]
232
+ assert_equal(:selector, selector[:node])
233
+ assert_equal("", selector[:value])
234
+ assert_equal([], selector[:tokens])
235
+
236
+ children = rule[:children]
237
+ assert_equal(2, children.size)
238
+ assert_tokens(" ", children[0], 7)
239
+
240
+ property = children[1]
241
+ assert_equal(:property, property[:node])
242
+ assert_equal("color", property[:name])
243
+ assert_equal("#aaa", property[:value])
244
+ assert_equal(false, property[:important])
245
+ assert_tokens("color: #aaa ", property[:tokens], 8)
246
+ end
247
+
248
+ it 'unclosed, with preceding comment, no selector, when :preserve_comments == true' do
249
+ options = {:preserve_comments => true}
250
+ tree = parse(" /**/ { color: #aaa ", options)
251
+
252
+ assert_equal(4, tree.size)
253
+ assert_tokens(" /**/ ", tree[0..2], 0, options)
254
+
255
+ rule = tree[3]
256
+ assert_equal(:style_rule, rule[:node])
257
+
258
+ selector = rule[:selector]
259
+ assert_equal(:selector, selector[:node])
260
+ assert_equal("", selector[:value])
261
+ assert_equal([], selector[:tokens])
262
+
263
+ children = rule[:children]
264
+ assert_equal(2, children.size)
265
+ assert_tokens(" ", children[0], 7, options)
266
+
267
+ property = children[1]
268
+ assert_equal(:property, property[:node])
269
+ assert_equal("color", property[:name])
270
+ assert_equal("#aaa", property[:value])
271
+ assert_equal(false, property[:important])
272
+ assert_tokens("color: #aaa ", property[:tokens], 8, options)
273
+ end
274
+ end
275
+
276
+ it 'should parse multiple style rules' do
277
+ tree = parse("div { color: #aaa; } p{}")
278
+
279
+ assert_equal(3, tree.size)
280
+
281
+ rule = tree[0]
282
+ assert_equal(:style_rule, rule[:node])
283
+
284
+ selector = rule[:selector]
285
+ assert_equal(:selector, selector[:node])
286
+ assert_equal("div", selector[:value])
287
+ assert_tokens("div ", selector[:tokens])
288
+
289
+ children = rule[:children]
290
+ assert_equal(3, children.size)
291
+ assert_tokens(" ", children[0], 5)
292
+ assert_tokens(" ", children[2], 18)
293
+
294
+ prop = children[1]
295
+ assert_equal(:property, prop[:node])
296
+ assert_equal("color", prop[:name])
297
+ assert_equal("#aaa", prop[:value])
298
+ assert_equal(false, prop[:important])
299
+ assert_tokens("color: #aaa;", prop[:tokens], 6)
300
+
301
+ assert_tokens(" ", tree[1], 20)
302
+
303
+ rule = tree[2]
304
+ assert_equal(:style_rule, rule[:node])
305
+
306
+ selector = rule[:selector]
307
+ assert_equal(:selector, selector[:node])
308
+ assert_equal("p", selector[:value])
309
+ assert_tokens("p", selector[:tokens], 21)
310
+ end
311
+
312
+ it 'should ignore a block-less selector following a selector-less style rule' do
313
+ tree = parse("{}a")
314
+ assert_equal(1, tree.size)
315
+
316
+ rule = tree[0]
317
+ assert_equal(:style_rule, rule[:node])
318
+ assert_equal([], rule[:children])
319
+
320
+ selector = rule[:selector]
321
+ assert_equal(:selector, selector[:node])
322
+ assert_equal("", selector[:value])
323
+ assert_equal([], selector[:tokens])
324
+ end
325
+
326
+ it 'should handle an at-rule following a selector-less style rule' do
327
+ tree = parse("{}@a")
328
+ assert_equal(2, tree.size)
329
+
330
+ rule = tree[0]
331
+ assert_equal(:style_rule, rule[:node])
332
+ assert_equal([], rule[:children])
333
+
334
+ selector = rule[:selector]
335
+ assert_equal(:selector, selector[:node])
336
+ assert_equal("", selector[:value])
337
+ assert_equal([], selector[:tokens])
338
+
339
+ rule = tree[1]
340
+ assert_equal(:at_rule, rule[:node])
341
+ assert_equal("a", rule[:name])
342
+ assert_equal([], rule[:prelude])
343
+ assert_tokens("@a", rule[:tokens], 2)
344
+ end
345
+ end
@@ -7,6 +7,26 @@ require_relative '../../lib/crass'
7
7
  CP = Crass::Parser
8
8
  CT = Crass::Tokenizer
9
9
 
10
+ # Hack shared test support into MiniTest.
11
+ MiniTest::Spec.class_eval do
12
+ def self.shared_tests
13
+ @shared_tests ||= {}
14
+ end
15
+ end
16
+
17
+ module MiniTest::Spec::SharedTests
18
+ def behaves_like(desc)
19
+ self.instance_eval(&MiniTest::Spec.shared_tests[desc])
20
+ end
21
+
22
+ def shared_tests_for(desc, &block)
23
+ MiniTest::Spec.shared_tests[desc] = block
24
+ end
25
+ end
26
+
27
+ Object.class_eval { include MiniTest::Spec::SharedTests }
28
+
29
+ # Custom assertions and helpers.
10
30
  def assert_tokens(input, actual, offset = 0, options = {})
11
31
  actual = [actual] unless actual.is_a?(Array)
12
32
  tokens = tokenize(input, offset, options)
data/test/test_crass.rb CHANGED
@@ -1,8 +1,23 @@
1
1
  # encoding: utf-8
2
2
  require_relative 'support/common'
3
3
 
4
- describe 'Crass.parse' do
5
- it 'should call Crass::Parser.parse_stylesheet with input and options' do
4
+ describe 'Crass' do
5
+ make_my_diffs_pretty!
6
+ parallelize_me!
7
+
8
+ it 'parse_properties() should call Crass::Parser.parse_properties' do
9
+ assert_equal(
10
+ CP.parse_properties("a:b; c:d 42!important;\n"),
11
+ Crass.parse_properties("a:b; c:d 42!important;\n")
12
+ )
13
+
14
+ assert_equal(
15
+ CP.parse_properties(";; /**/ ; ;", :preserve_comments => true),
16
+ Crass.parse_properties(";; /**/ ; ;", :preserve_comments => true)
17
+ )
18
+ end
19
+
20
+ it 'parse() should call Crass::Parser.parse_stylesheet' do
6
21
  assert_equal(
7
22
  CP.parse_stylesheet(" /**/ .foo {} #bar {}"),
8
23
  Crass.parse(" /**/ .foo {} #bar {}")
@@ -0,0 +1,175 @@
1
+ # encoding: utf-8
2
+
3
+ # Includes tests based on Simon Sapin's CSS parsing tests:
4
+ # https://github.com/SimonSapin/css-parsing-tests/
5
+
6
+ require_relative 'support/common'
7
+
8
+ describe 'Crass::Parser' do
9
+ make_my_diffs_pretty!
10
+ parallelize_me!
11
+
12
+ describe '#parse_properties' do
13
+ def parse(*args)
14
+ CP.parse_properties(*args)
15
+ end
16
+
17
+ it 'should return an empty tree when given an empty string' do
18
+ assert_equal([], parse(""))
19
+ end
20
+
21
+ # Note: The next two tests verify augmented behavior that isn't defined in
22
+ # CSS Syntax Module Level 3.
23
+ it 'should include semicolon and whitespace tokens' do
24
+ assert_tokens(";; /**/ ; ;", parse(";; /**/ ; ;"))
25
+ end
26
+
27
+ it 'should include semicolon, whitespace, and comment tokens when :preserve_comments == true' do
28
+ tree = parse(";; /**/ ; ;", :preserve_comments => true)
29
+ assert_tokens(";; /**/ ; ;", tree, 0, :preserve_comments => true)
30
+ end
31
+
32
+ it 'should parse a list of declarations' do
33
+ tree = parse("a:b; c:d 42!important;\n")
34
+ assert_equal(4, tree.size)
35
+
36
+ prop = tree[0]
37
+ assert_equal(:property, prop[:node])
38
+ assert_equal("a", prop[:name])
39
+ assert_equal("b", prop[:value])
40
+ assert_equal(false, prop[:important])
41
+ assert_tokens("a:b;", prop[:tokens])
42
+
43
+ assert_tokens(" ", tree[1], 4)
44
+
45
+ prop = tree[2]
46
+ assert_equal(:property, prop[:node])
47
+ assert_equal("c", prop[:name])
48
+ assert_equal("d 42", prop[:value])
49
+ assert_equal(true, prop[:important])
50
+ assert_tokens("c:d 42!important;", prop[:tokens], 5)
51
+
52
+ assert_tokens("\n", tree[3], 22)
53
+ end
54
+
55
+ it 'should parse at-rules even though they may be invalid in the given context' do
56
+ tree = parse("@import 'foo.css'; a:b; @import 'bar.css'")
57
+ assert_equal(5, tree.size)
58
+
59
+ rule = tree[0]
60
+ assert_equal(:at_rule, rule[:node])
61
+ assert_equal("import", rule[:name])
62
+ assert_tokens(" 'foo.css'", rule[:prelude], 7)
63
+ assert_tokens("@import 'foo.css';", rule[:tokens])
64
+
65
+ assert_tokens(" ", tree[1], 18)
66
+
67
+ prop = tree[2]
68
+ assert_equal(:property, prop[:node])
69
+ assert_equal("a", prop[:name])
70
+ assert_equal("b", prop[:value])
71
+ assert_equal(false, prop[:important])
72
+ assert_tokens("a:b;", prop[:tokens], 19)
73
+
74
+ assert_tokens(" ", tree[3], 23)
75
+
76
+ rule = tree[4]
77
+ assert_equal(:at_rule, rule[:node])
78
+ assert_equal("import", rule[:name])
79
+ assert_tokens(" 'bar.css'", rule[:prelude], 31)
80
+ assert_tokens("@import 'bar.css'", rule[:tokens], 24)
81
+ end
82
+
83
+ it 'should not be fazed by extra semicolons or unclosed blocks' do
84
+ tree = parse("@media screen { div{;}} a:b;; @media print{div{")
85
+ assert_equal(6, tree.size)
86
+
87
+ rule = tree[0]
88
+ assert_equal(:at_rule, rule[:node])
89
+ assert_equal("media", rule[:name])
90
+ assert_tokens(" screen ", rule[:prelude], 6)
91
+ assert_tokens("@media screen { div{;}}", rule[:tokens])
92
+
93
+ block = rule[:block]
94
+ assert_equal(:simple_block, block[:node])
95
+ assert_equal("{", block[:start])
96
+ assert_equal("}", block[:end])
97
+ assert_tokens("{ div{;}}", block[:tokens], 14)
98
+
99
+ value = block[:value]
100
+ assert_equal(3, value.size)
101
+ assert_tokens(" div", value[0..1], 15)
102
+
103
+ block = value[2]
104
+ assert_equal(:simple_block, block[:node])
105
+ assert_equal("{", block[:start])
106
+ assert_equal("}", block[:end])
107
+ assert_tokens(";", block[:value], 20)
108
+ assert_tokens("{;}", block[:tokens], 19)
109
+
110
+ assert_tokens(" ", tree[1], 23)
111
+
112
+ prop = tree[2]
113
+ assert_equal(:property, prop[:node])
114
+ assert_equal("a", prop[:name])
115
+ assert_equal("b", prop[:value])
116
+ assert_equal(false, prop[:important])
117
+ assert_tokens("a:b;", prop[:tokens], 24)
118
+
119
+ assert_tokens("; ", tree[3..4], 28)
120
+
121
+ rule = tree[5]
122
+ assert_equal(:at_rule, rule[:node])
123
+ assert_equal("media", rule[:name])
124
+ assert_tokens(" print", rule[:prelude], 36)
125
+ assert_tokens("@media print{div{", rule[:tokens], 30)
126
+
127
+ block = rule[:block]
128
+ assert_equal(:simple_block, block[:node])
129
+ assert_equal("{", block[:start])
130
+ assert_equal("}", block[:end])
131
+ assert_tokens("{div{", block[:tokens], 42)
132
+
133
+ value = block[:value]
134
+ assert_equal(2, value.size)
135
+ assert_tokens("div", value[0], 43)
136
+
137
+ block = value[1]
138
+ assert_equal(:simple_block, block[:node])
139
+ assert_equal("{", block[:start])
140
+ assert_equal("}", block[:end])
141
+ assert_equal([], block[:value])
142
+ assert_tokens("{", block[:tokens], 46)
143
+ end
144
+
145
+ it 'should discard invalid nodes' do
146
+ tree = parse("@ media screen { div{;}} a:b;; @media print{div{")
147
+ assert_equal(3, tree.size)
148
+
149
+ assert_tokens("; ", tree[0..1], 29)
150
+
151
+ rule = tree[2]
152
+ assert_equal(:at_rule, rule[:node])
153
+ assert_equal("media", rule[:name])
154
+ assert_tokens(" print", rule[:prelude], 37)
155
+ assert_tokens("@media print{div{", rule[:tokens], 31)
156
+
157
+ block = rule[:block]
158
+ assert_equal(:simple_block, block[:node])
159
+ assert_equal("{", block[:start])
160
+ assert_equal("}", block[:end])
161
+ assert_tokens("{div{", block[:tokens], 43)
162
+
163
+ value = block[:value]
164
+ assert_equal(2, value.size)
165
+ assert_tokens("div", value[0], 44)
166
+
167
+ block = value[1]
168
+ assert_equal(:simple_block, block[:node])
169
+ assert_equal("{", block[:start])
170
+ assert_equal("}", block[:end])
171
+ assert_equal([], block[:value])
172
+ assert_tokens("{", block[:tokens], 47)
173
+ end
174
+ end
175
+ end