sass-embedded 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "test_helper"
4
+
5
+ module Sass
6
+ class CustomImporterTest < MiniTest::Test
7
+ include TempFileTest
8
+
9
+ def setup
10
+ @compiler = Embedded::Compiler.new
11
+ end
12
+
13
+ def teardown
14
+ end
15
+
16
+ def render(data, importer)
17
+ @compiler.render({ data: data, importer: importer })[:css]
18
+ end
19
+
20
+ def test_custom_importer_works
21
+ temp_file("fonts.scss", ".font { color: $var1; }")
22
+
23
+ data = <<SCSS
24
+ @import "styles";
25
+ @import "fonts";
26
+ SCSS
27
+
28
+ output = render(data, [
29
+ lambda { |url, prev|
30
+ if url =~ /styles/
31
+ { contents: "$var1: #000; .hi { color: $var1; }" }
32
+ end
33
+ }
34
+ ])
35
+
36
+ assert_equal <<CSS.chomp, output
37
+ .hi {
38
+ color: #000;
39
+ }
40
+
41
+ .font {
42
+ color: #000;
43
+ }
44
+ CSS
45
+ end
46
+
47
+ def test_custom_importer_works_with_empty_contents
48
+ output = render("@import 'fake.scss';", [
49
+ lambda { |url, prev|
50
+ { contents: "" }
51
+ }
52
+ ])
53
+
54
+ assert_equal "", output
55
+ end
56
+
57
+ def test_custom_importer_works_with_file
58
+ temp_file("test.scss", ".test { color: #000; }")
59
+
60
+ output = render("@import 'fake.scss';", [
61
+ lambda { |url, prev|
62
+ { file: File.absolute_path("test.scss") }
63
+ }
64
+ ])
65
+
66
+ assert_equal <<CSS.chomp, output
67
+ .test {
68
+ color: #000;
69
+ }
70
+ CSS
71
+ end
72
+
73
+ def test_custom_importer_comes_after_local_file
74
+ temp_file("test.scss", ".test { color: #000; }")
75
+
76
+ output = render("@import 'test.scss';", [
77
+ lambda { |url, prev|
78
+ return { contents: '.h1 { color: #fff; }' }
79
+ }
80
+ ])
81
+
82
+ assert_equal <<CSS.chomp, output
83
+ .test {
84
+ color: #000;
85
+ }
86
+ CSS
87
+ end
88
+
89
+ def test_custom_importer_that_does_not_resolve
90
+ assert_raises(CompilationError) do
91
+ output = render("@import 'test.scss';", [
92
+ lambda { |url, prev|
93
+ return nil
94
+ }
95
+ ])
96
+ end
97
+ end
98
+
99
+ def test_custom_importer_that_returns_error
100
+ assert_raises(CompilationError) do
101
+ output = render("@import 'test.scss';", [
102
+ lambda { |url, prev|
103
+ IOError.new "test error"
104
+ }
105
+ ])
106
+ end
107
+ end
108
+
109
+ def test_custom_importer_that_raises_error
110
+ assert_raises(CompilationError) do
111
+ output = render("@import 'test.scss';", [
112
+ lambda { |url, prev|
113
+ raise IOError.new "test error"
114
+ }
115
+ ])
116
+ end
117
+ end
118
+
119
+ def test_parent_path_is_accessible
120
+ output = @compiler.render({
121
+ data: "@import 'parent.scss';",
122
+ file: "import-parent-filename.scss",
123
+ importer: [
124
+ lambda { |url, prev|
125
+ { contents: ".#{prev} { color: red; }" }
126
+ }
127
+ ]})[:css]
128
+
129
+ assert_equal <<CSS.chomp, output
130
+ .import-parent-filename.scss {
131
+ color: red;
132
+ }
133
+ CSS
134
+ end
135
+
136
+ def test_call_compiler_importer
137
+ output = @compiler.render({
138
+ data: "@import 'parent.scss';",
139
+ importer: [
140
+ lambda { |url, prev|
141
+ {
142
+ contents: @compiler.render({
143
+ data: "@import 'parent-parent.scss'",
144
+ importer: [
145
+ lambda { |url, prev|
146
+ { contents: 'h1 { color: black; }' }
147
+ }
148
+ ]})[:css]
149
+ }
150
+ }
151
+ ]})[:css]
152
+
153
+ assert_equal <<CSS.chomp, output
154
+ h1 {
155
+ color: black;
156
+ }
157
+ CSS
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "test_helper"
4
+
5
+ module Sass
6
+ class ErrorTest < MiniTest::Test
7
+
8
+ def setup
9
+ @compiler = Embedded::Compiler.new
10
+ end
11
+
12
+ def teardown
13
+ end
14
+
15
+ def test_first_backtrace_is_sass
16
+ begin
17
+ template = <<-SCSS
18
+ .foo {
19
+ baz: bang;
20
+ padding top: 10px;
21
+ }
22
+ SCSS
23
+
24
+ @compiler.render({
25
+ data: template,
26
+ })
27
+ rescue Sass::CompilationError => err
28
+ expected = "stdin:3:20"
29
+ assert_equal expected, err.backtrace.first
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,375 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "test_helper"
4
+ require "stringio"
5
+
6
+ module Sass
7
+ class FunctionsTest < MiniTest::Test
8
+ def setup
9
+ @compiler = Embedded::Compiler.new
10
+ end
11
+
12
+ def teardown
13
+ end
14
+
15
+ def render(sass)
16
+ @compiler.render({
17
+ data: sass,
18
+ functions: {
19
+ 'javascript_path($path)': lambda { |path|
20
+ path.string.text = "/js/#{path.string.text}"
21
+ path
22
+ },
23
+ 'stylesheet_path($path)': lambda { |path|
24
+ path.string.text = "/css/#{path.string.text}"
25
+ path
26
+ },
27
+ 'sass_return_path($path)': lambda { |path|
28
+ path
29
+ },
30
+ 'no_return_path($path)': lambda { |path|
31
+ Sass::EmbeddedProtocol::Value.new(
32
+ :singleton => Sass::EmbeddedProtocol::SingletonValue::NULL
33
+ )
34
+ },
35
+ 'optional_arguments($path, $optional: null)': lambda { |path, optional|
36
+ Sass::EmbeddedProtocol::Value.new(
37
+ :string => Sass::EmbeddedProtocol::Value::String.new(
38
+ :text => "#{path.string.text}/#{optional.singleton == :NULL ? "bar" : optional.string.text}",
39
+ :quoted => true
40
+ )
41
+ )
42
+ },
43
+ 'function_that_raises_errors()': lambda {
44
+ raise StandardError, "Intentional wrong thing happened somewhere inside the custom function"
45
+ },
46
+ 'nice_color_argument($color)': lambda { |color|
47
+ color
48
+ },
49
+ 'returns_a_color()': lambda {
50
+ Sass::EmbeddedProtocol::Value.new(
51
+ :rgb_color => Sass::EmbeddedProtocol::Value::RgbColor.new(
52
+ :red => 0,
53
+ :green => 0,
54
+ :blue => 0,
55
+ :alpha => 1
56
+ )
57
+ )
58
+ },
59
+ 'returns_a_number()': lambda {
60
+ Sass::EmbeddedProtocol::Value.new(
61
+ :number => Sass::EmbeddedProtocol::Value::Number.new(
62
+ :value => -312,
63
+ :numerators => ['rem']
64
+ )
65
+ )
66
+ },
67
+ 'returns_a_bool()': lambda {
68
+ Sass::EmbeddedProtocol::Value.new(
69
+ :singleton => Sass::EmbeddedProtocol::SingletonValue::TRUE
70
+ )
71
+ },
72
+ 'inspect_bool($argument)': lambda { |argument|
73
+ raise StandardError.new "passed value is not a Sass::EmbeddedProtocol::SingletonValue::TRUE or Sass::EmbeddedProtocol::SingletonValue::FALSE" unless argument&.singleton == :TRUE || argument.singleton == :FALSE
74
+ argument
75
+ },
76
+ 'inspect_number($argument)': lambda { |argument|
77
+ raise StandardError.new "passed value is not a Sass::EmbeddedProtocol::Value::Number" unless argument&.number&.is_a? Sass::EmbeddedProtocol::Value::Number
78
+ argument
79
+ },
80
+ 'inspect_map($argument)': lambda { |argument|
81
+ raise StandardError.new "passed value is not a Sass::EmbeddedProtocol::Value::Map" unless argument&.map&.is_a? Sass::EmbeddedProtocol::Value::Map
82
+ argument
83
+ },
84
+ 'inspect_list($argument)': lambda { |argument|
85
+ raise StandardError.new "passed value is not a Sass::EmbeddedProtocol::Value::List" unless argument&.list&.is_a? Sass::EmbeddedProtocol::Value::List
86
+ argument
87
+ },
88
+ 'returns_sass_value()': lambda {
89
+ Sass::EmbeddedProtocol::Value.new(
90
+ :rgb_color => Sass::EmbeddedProtocol::Value::RgbColor.new(
91
+ :red => 0,
92
+ :green => 0,
93
+ :blue => 0,
94
+ :alpha => 1
95
+ )
96
+ )
97
+ },
98
+ 'returns_sass_map()': lambda {
99
+ Sass::EmbeddedProtocol::Value.new(
100
+ :map => Sass::EmbeddedProtocol::Value::Map.new(
101
+ :entries => [
102
+ Sass::EmbeddedProtocol::Value::Map::Entry.new(
103
+ :key => Sass::EmbeddedProtocol::Value.new(
104
+ :string => Sass::EmbeddedProtocol::Value::String.new(
105
+ :text => "color",
106
+ :quoted => true
107
+ )
108
+ ),
109
+ :value => Sass::EmbeddedProtocol::Value.new(
110
+ :rgb_color => Sass::EmbeddedProtocol::Value::RgbColor.new(
111
+ :red => 0,
112
+ :green => 0,
113
+ :blue => 0,
114
+ :alpha => 1
115
+ )
116
+ )
117
+ )
118
+ ]
119
+ )
120
+ )
121
+ },
122
+ 'returns_sass_list()': lambda {
123
+ Sass::EmbeddedProtocol::Value.new(
124
+ :list => Sass::EmbeddedProtocol::Value::List.new(
125
+ :separator => Sass::EmbeddedProtocol::ListSeparator::COMMA,
126
+ :has_brackets => true,
127
+ :contents => [
128
+ Sass::EmbeddedProtocol::Value.new(
129
+ :number => Sass::EmbeddedProtocol::Value::Number.new(
130
+ :value => 10
131
+ )
132
+ ),
133
+ Sass::EmbeddedProtocol::Value.new(
134
+ :number => Sass::EmbeddedProtocol::Value::Number.new(
135
+ :value => 20
136
+ )
137
+ ),
138
+ Sass::EmbeddedProtocol::Value.new(
139
+ :number => Sass::EmbeddedProtocol::Value::Number.new(
140
+ :value => 30
141
+ )
142
+ ),
143
+ ]
144
+ )
145
+ )
146
+ }
147
+ }
148
+ })[:css]
149
+ end
150
+
151
+ def test_functions_may_return_sass_string_type
152
+ assert_sass <<-SCSS, <<-CSS
153
+ div { url: url(sass_return_path("foo.svg")); }
154
+ SCSS
155
+ div { url: url("foo.svg"); }
156
+ CSS
157
+ end
158
+
159
+ def test_functions_work_with_varying_quotes_and_string_types
160
+ assert_sass <<-SCSS, <<-CSS
161
+ div {
162
+ url: url(asset-path("foo.svg"));
163
+ url: url(image-path("foo.png"));
164
+ url: url(video-path("foo.mov"));
165
+ url: url(audio-path("foo.mp3"));
166
+ url: url(font-path("foo.woff"));
167
+ url: url(javascript-path('foo.js'));
168
+ url: url(javascript-path("foo.js"));
169
+ url: url(stylesheet-path("foo.css"));
170
+ }
171
+ SCSS
172
+ div {
173
+ url: url(asset-path("foo.svg"));
174
+ url: url(image-path("foo.png"));
175
+ url: url(video-path("foo.mov"));
176
+ url: url(audio-path("foo.mp3"));
177
+ url: url(font-path("foo.woff"));
178
+ url: url("/js/foo.js");
179
+ url: url("/js/foo.js");
180
+ url: url("/css/foo.css");
181
+ }
182
+ CSS
183
+ end
184
+
185
+ def test_function_with_empty_unquoted_string
186
+ assert_sass <<-SCSS, <<-CSS
187
+ div {url: url(no-return-path('foo.svg'));}
188
+ SCSS
189
+ div { url: url(); }
190
+ CSS
191
+ end
192
+
193
+ def test_function_that_returns_a_color
194
+ assert_sass <<-SCSS, <<-CSS
195
+ div { background: returns-a-color(); }
196
+ SCSS
197
+ div { background: black; }
198
+ CSS
199
+ end
200
+
201
+ def test_function_that_returns_a_number
202
+ assert_sass <<-SCSS, <<-CSS
203
+ div { width: returns-a-number(); }
204
+ SCSS
205
+ div { width: -312rem; }
206
+ CSS
207
+ end
208
+
209
+ def test_function_that_takes_a_number
210
+ assert_sass <<-SCSS, <<-CSS
211
+ div { display: inspect-number(42.1px); }
212
+ SCSS
213
+ div { display: 42.1px; }
214
+ CSS
215
+ end
216
+
217
+ def test_function_that_returns_a_bool
218
+ assert_sass <<-SCSS, <<-CSS
219
+ div { width: returns-a-bool(); }
220
+ SCSS
221
+ div { width: true; }
222
+ CSS
223
+ end
224
+
225
+ def test_function_that_takes_a_bool
226
+ assert_sass <<-SCSS, <<-CSS
227
+ div { display: inspect-bool(true)}
228
+ SCSS
229
+ div { display: true; }
230
+ CSS
231
+ end
232
+
233
+ def test_function_with_optional_arguments
234
+ assert_sass <<-SCSS, <<-EXPECTED_CSS
235
+ div {
236
+ url: optional_arguments('first');
237
+ url: optional_arguments('second', 'qux');
238
+ }
239
+ SCSS
240
+ div {
241
+ url: "first/bar";
242
+ url: "second/qux";
243
+ }
244
+ EXPECTED_CSS
245
+ end
246
+
247
+ def test_functions_may_accept_sass_color_type
248
+ assert_sass <<-SCSS, <<-EXPECTED_CSS
249
+ div { color: nice_color_argument(red); }
250
+ SCSS
251
+ div { color: red; }
252
+ EXPECTED_CSS
253
+ end
254
+
255
+ def test_function_with_error
256
+ exception = assert_raises(Sass::CompilationError) do
257
+ render("div {url: function_that_raises_errors();}")
258
+ end
259
+ end
260
+
261
+ def test_function_that_returns_a_sass_value
262
+ assert_sass <<-SCSS, <<-CSS
263
+ div { background: returns-sass-value(); }
264
+ SCSS
265
+ div { background: black; }
266
+ CSS
267
+ end
268
+
269
+ def test_function_that_returns_a_sass_map
270
+ assert_sass <<-SCSS, <<-CSS
271
+ $my-map: returns-sass-map();
272
+ div { background: map-get( $my-map, color ); }
273
+ SCSS
274
+ div { background: black; }
275
+ CSS
276
+ end
277
+
278
+ def test_function_that_takes_a_sass_map
279
+ assert_sass <<-SCSS, <<-CSS
280
+ div { background-color: map-get( inspect-map(( color: black, number: 1.23px, string: "abc", map: ( x: 'y' ))), color ); }
281
+ SCSS
282
+ div { background-color: black; }
283
+ CSS
284
+ end
285
+
286
+ def test_function_that_returns_a_sass_list
287
+ assert_sass <<-SCSS, <<-CSS
288
+ $my-list: returns-sass-list();
289
+ div { width: nth( $my-list, 2 ); }
290
+ SCSS
291
+ div { width: 20; }
292
+ CSS
293
+ end
294
+
295
+ def test_function_that_takes_a_sass_list
296
+ assert_sass <<-SCSS, <<-CSS
297
+ div { width: nth(inspect-list((10 20 30)), 2); }
298
+ SCSS
299
+ div { width: 20; }
300
+ CSS
301
+ end
302
+
303
+ def test_concurrency
304
+ skip 'ProtocolError: Bad state: Future already completed'
305
+ 10.times do
306
+ threads = []
307
+ 2.times do |i|
308
+ threads << Thread.new(i) do |id|
309
+ output = @compiler.render({
310
+ data: "div { url: test-function() }",
311
+ functions: {
312
+ 'test_function()': lambda {
313
+ Sass::EmbeddedProtocol::Value.new(
314
+ :string => Sass::EmbeddedProtocol::Value::String.new(
315
+ :text => "{test_key1: 'test_value', test_key2: #{id}}",
316
+ :quoted => true
317
+ )
318
+ )
319
+ }
320
+ }
321
+ })[:css]
322
+ assert_match /test_key1/, output
323
+ assert_match /test_key2/, output
324
+ assert_match /test_value/, output
325
+ assert_match /#{id}/, output
326
+ end
327
+ end
328
+ threads.each(&:join)
329
+ end
330
+ end
331
+
332
+ def test_pass_custom_functions_as_a_parameter
333
+ output = @compiler.render({
334
+ data: "div { url: test-function(); }",
335
+ functions: {
336
+ 'test_function()': lambda {
337
+ Sass::EmbeddedProtocol::Value.new(
338
+ :string => Sass::EmbeddedProtocol::Value::String.new(
339
+ :text => "custom_function",
340
+ :quoted => true
341
+ )
342
+ )
343
+ }
344
+ }
345
+ })[:css]
346
+
347
+ assert_match /custom_function/, output
348
+ end
349
+
350
+ def test_pass_incompatible_type_to_custom_functions
351
+ assert_raises(CompilationError) do
352
+ output = @compiler.render({
353
+ data: "div { url: test-function(); }",
354
+ functions: {
355
+ 'test_function()': lambda {
356
+ Class.new
357
+ }
358
+ }
359
+ })[:css]
360
+ end
361
+ end
362
+
363
+ private
364
+
365
+ def assert_sass(sass, expected_css)
366
+ output = render(sass)
367
+ assert_equal expected_css.strip.gsub!(/\s+/, " "), # poor man's String#squish
368
+ output.strip.gsub!(/\s+/, " ")
369
+ end
370
+
371
+ def stderr_output
372
+ $stderr.string.gsub("\u0000\n", '').chomp
373
+ end
374
+ end
375
+ end