324 lines
9.2 KiB
Ruby
324 lines
9.2 KiB
Ruby
|
# frozen_string_literal: true
|
||
|
|
||
|
require_relative "test_helper"
|
||
|
require "stringio"
|
||
|
|
||
|
module SassC
|
||
|
class FunctionsTest < MiniTest::Test
|
||
|
include FixtureHelper
|
||
|
|
||
|
def setup
|
||
|
@real_stderr, $stderr = $stderr, StringIO.new
|
||
|
end
|
||
|
|
||
|
def teardown
|
||
|
$stderr = @real_stderr
|
||
|
end
|
||
|
|
||
|
def test_functions_may_return_sass_string_type
|
||
|
assert_sass <<-SCSS, <<-CSS
|
||
|
div { url: url(sass_return_path("foo.svg")); }
|
||
|
SCSS
|
||
|
div { url: url("foo.svg"); }
|
||
|
CSS
|
||
|
end
|
||
|
|
||
|
def test_functions_work_with_varying_quotes_and_string_types
|
||
|
assert_sass <<-SCSS, <<-CSS
|
||
|
div {
|
||
|
url: url(asset-path("foo.svg"));
|
||
|
url: url(image-path("foo.png"));
|
||
|
url: url(video-path("foo.mov"));
|
||
|
url: url(audio-path("foo.mp3"));
|
||
|
url: url(font-path("foo.woff"));
|
||
|
url: url(javascript-path('foo.js'));
|
||
|
url: url(javascript-path("foo.js"));
|
||
|
url: url(stylesheet-path("foo.css"));
|
||
|
}
|
||
|
SCSS
|
||
|
div {
|
||
|
url: url(asset-path("foo.svg"));
|
||
|
url: url(image-path("foo.png"));
|
||
|
url: url(video-path("foo.mov"));
|
||
|
url: url(audio-path("foo.mp3"));
|
||
|
url: url(font-path("foo.woff"));
|
||
|
url: url("/js/foo.js");
|
||
|
url: url("/js/foo.js");
|
||
|
url: url(/css/foo.css);
|
||
|
}
|
||
|
CSS
|
||
|
end
|
||
|
|
||
|
def test_function_with_no_return_value
|
||
|
assert_sass <<-SCSS, <<-CSS
|
||
|
div {url: url(no-return-path('foo.svg'));}
|
||
|
SCSS
|
||
|
div { url: url(); }
|
||
|
CSS
|
||
|
end
|
||
|
|
||
|
def test_function_that_returns_a_color
|
||
|
assert_sass <<-SCSS, <<-CSS
|
||
|
div { background: returns-a-color(); }
|
||
|
SCSS
|
||
|
div { background: black; }
|
||
|
CSS
|
||
|
end
|
||
|
|
||
|
def test_function_that_returns_a_number
|
||
|
assert_sass <<-SCSS, <<-CSS
|
||
|
div { width: returns-a-number(); }
|
||
|
SCSS
|
||
|
div { width: -312rem; }
|
||
|
CSS
|
||
|
end
|
||
|
|
||
|
def test_function_that_takes_a_number
|
||
|
assert_sass <<-SCSS, <<-CSS
|
||
|
div { display: inspect-number(42.1px); }
|
||
|
SCSS
|
||
|
div { display: 42.1px; }
|
||
|
CSS
|
||
|
end
|
||
|
|
||
|
def test_function_that_returns_a_bool
|
||
|
assert_sass <<-SCSS, <<-CSS
|
||
|
div { width: returns-a-bool(); }
|
||
|
SCSS
|
||
|
div { width: true; }
|
||
|
CSS
|
||
|
end
|
||
|
|
||
|
def test_function_that_takes_a_bool
|
||
|
assert_sass <<-SCSS, <<-CSS
|
||
|
div { display: inspect-bool(true)}
|
||
|
SCSS
|
||
|
div { display: true; }
|
||
|
CSS
|
||
|
end
|
||
|
|
||
|
def test_function_with_optional_arguments
|
||
|
assert_sass <<-SCSS, <<-EXPECTED_CSS
|
||
|
div {
|
||
|
url: optional_arguments('first');
|
||
|
url: optional_arguments('second', 'qux');
|
||
|
}
|
||
|
SCSS
|
||
|
div {
|
||
|
url: "first/bar";
|
||
|
url: "second/qux";
|
||
|
}
|
||
|
EXPECTED_CSS
|
||
|
end
|
||
|
|
||
|
def test_functions_may_accept_sass_color_type
|
||
|
assert_sass <<-SCSS, <<-EXPECTED_CSS
|
||
|
div { color: nice_color_argument(red); }
|
||
|
SCSS
|
||
|
div { color: rgb(255, 0, 0); }
|
||
|
EXPECTED_CSS
|
||
|
end
|
||
|
|
||
|
def test_function_with_unsupported_tag
|
||
|
skip('What are other unsupported tags?')
|
||
|
engine = Engine.new("div {url: function_with_unsupported_tag(());}")
|
||
|
|
||
|
exception = assert_raises(SassC::SyntaxError) do
|
||
|
engine.render
|
||
|
end
|
||
|
|
||
|
assert_match /Sass argument of type sass_list unsupported/, exception.message
|
||
|
assert_equal "[SassC::FunctionsHandler] Sass argument of type sass_list unsupported", stderr_output
|
||
|
end
|
||
|
|
||
|
def test_function_with_error
|
||
|
engine = Engine.new("div {url: function_that_raises_errors();}")
|
||
|
|
||
|
exception = assert_raises(SassC::SyntaxError) do
|
||
|
engine.render
|
||
|
end
|
||
|
|
||
|
assert_match /Error: error in C function function_that_raises_errors/, exception.message
|
||
|
assert_match /Intentional wrong thing happened somewhere inside the custom function/, exception.message
|
||
|
assert_equal "[SassC::FunctionsHandler] Intentional wrong thing happened somewhere inside the custom function", stderr_output
|
||
|
end
|
||
|
|
||
|
def test_function_that_returns_a_sass_value
|
||
|
assert_sass <<-SCSS, <<-CSS
|
||
|
div { background: returns-sass-value(); }
|
||
|
SCSS
|
||
|
div { background: black; }
|
||
|
CSS
|
||
|
end
|
||
|
|
||
|
def test_function_that_returns_a_sass_map
|
||
|
assert_sass <<-SCSS, <<-CSS
|
||
|
$my-map: returns-sass-map();
|
||
|
div { background: map-get( $my-map, color ); }
|
||
|
SCSS
|
||
|
div { background: black; }
|
||
|
CSS
|
||
|
end
|
||
|
|
||
|
def test_function_that_takes_a_sass_map
|
||
|
assert_sass <<-SCSS, <<-CSS
|
||
|
div { background-color: map-get( inspect-map(( color: black, number: 1.23px, string: "abc", map: ( x: 'y' ))), color ); }
|
||
|
SCSS
|
||
|
div { background-color: black; }
|
||
|
CSS
|
||
|
end
|
||
|
|
||
|
def test_function_that_returns_a_sass_list
|
||
|
assert_sass <<-SCSS, <<-CSS
|
||
|
$my-list: returns-sass-list();
|
||
|
div { width: nth( $my-list, 2 ); }
|
||
|
SCSS
|
||
|
div { width: 20; }
|
||
|
CSS
|
||
|
end
|
||
|
|
||
|
def test_function_that_takes_a_sass_list
|
||
|
assert_sass <<-SCSS, <<-CSS
|
||
|
div { width: nth(inspect-list((10 20 30)), 2); }
|
||
|
SCSS
|
||
|
div { width: 20; }
|
||
|
CSS
|
||
|
end
|
||
|
|
||
|
def test_concurrency
|
||
|
10.times do
|
||
|
threads = []
|
||
|
10.times do |i|
|
||
|
threads << Thread.new(i) do |id|
|
||
|
out = Engine.new("div { url: inspect_options(); }", {test_key1: 'test_value', test_key2: id}).render
|
||
|
assert_match /test_key1/, out
|
||
|
assert_match /test_key2/, out
|
||
|
assert_match /test_value/, out
|
||
|
assert_match /#{id}/, out
|
||
|
end
|
||
|
end
|
||
|
threads.each(&:join)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
private
|
||
|
|
||
|
def assert_sass(sass, expected_css)
|
||
|
engine = Engine.new(sass)
|
||
|
assert_equal expected_css.strip.gsub!(/\s+/, " "), # poor man's String#squish
|
||
|
engine.render.strip.gsub!(/\s+/, " ")
|
||
|
end
|
||
|
|
||
|
def stderr_output
|
||
|
$stderr.string.gsub("\u0000\n", '').chomp
|
||
|
end
|
||
|
|
||
|
module Script::Functions
|
||
|
|
||
|
def javascript_path(path)
|
||
|
SassC::Script::Value::String.new("/js/#{path.value}", :string)
|
||
|
end
|
||
|
|
||
|
def stylesheet_path(path)
|
||
|
SassC::Script::Value::String.new("/css/#{path.value}", :identifier)
|
||
|
end
|
||
|
|
||
|
def no_return_path(path)
|
||
|
nil
|
||
|
end
|
||
|
|
||
|
def sass_return_path(path)
|
||
|
SassC::Script::Value::String.new("#{path.value}", :string)
|
||
|
end
|
||
|
|
||
|
def optional_arguments(path, optional = nil)
|
||
|
optional ||= SassC::Script::Value::String.new("bar")
|
||
|
SassC::Script::Value::String.new("#{path.value}/#{optional.value}", :string)
|
||
|
end
|
||
|
|
||
|
def function_that_raises_errors
|
||
|
raise StandardError, "Intentional wrong thing happened somewhere inside the custom function"
|
||
|
end
|
||
|
|
||
|
def function_with_unsupported_tag(value)
|
||
|
end
|
||
|
|
||
|
def nice_color_argument(color)
|
||
|
return SassC::Script::Value::String.new(color.to_s, :identifier)
|
||
|
end
|
||
|
|
||
|
def returns_a_color
|
||
|
return SassC::Script::Value::Color.new(red: 0, green: 0, blue: 0)
|
||
|
end
|
||
|
|
||
|
def returns_a_number
|
||
|
return SassC::Script::Value::Number.new(-312,'rem')
|
||
|
end
|
||
|
|
||
|
def returns_a_bool
|
||
|
return SassC::Script::Value::Bool.new(true)
|
||
|
end
|
||
|
|
||
|
def inspect_bool ( argument )
|
||
|
raise StandardError.new "passed value is not a Sass::Script::Value::Bool" unless argument.is_a? SassC::Script::Value::Bool
|
||
|
return argument
|
||
|
end
|
||
|
|
||
|
def inspect_number ( argument )
|
||
|
raise StandardError.new "passed value is not a Sass::Script::Value::Number" unless argument.is_a? SassC::Script::Value::Number
|
||
|
return argument
|
||
|
end
|
||
|
|
||
|
def inspect_map ( argument )
|
||
|
argument.to_h.each_pair do |key, value|
|
||
|
raise StandardError.new "key #{key.inspect} is not a string" unless key.is_a? SassC::Script::Value::String
|
||
|
|
||
|
valueClass = case key.value
|
||
|
when 'string'
|
||
|
SassC::Script::Value::String
|
||
|
when 'number'
|
||
|
SassC::Script::Value::Number
|
||
|
when 'color'
|
||
|
SassC::Script::Value::Color
|
||
|
when 'map'
|
||
|
SassC::Script::Value::Map
|
||
|
end
|
||
|
|
||
|
raise StandardError.new "unknown key #{key.inspect}" unless valueClass
|
||
|
raise StandardError.new "value for #{key.inspect} is not a #{valueClass}" unless value.is_a? valueClass
|
||
|
end
|
||
|
return argument
|
||
|
end
|
||
|
|
||
|
def inspect_list(argument)
|
||
|
raise StandardError.new "passed value is not a Sass::Script::Value::List" unless argument.is_a? SassC::Script::Value::List
|
||
|
return argument
|
||
|
end
|
||
|
|
||
|
def inspect_options
|
||
|
SassC::Script::Value::String.new(self.options.inspect, :string)
|
||
|
end
|
||
|
|
||
|
def returns_sass_value
|
||
|
return SassC::Script::Value::Color.new(red: 0, green: 0, blue: 0)
|
||
|
end
|
||
|
|
||
|
def returns_sass_map
|
||
|
key = SassC::Script::Value::String.new("color", "string")
|
||
|
value = SassC::Script::Value::Color.new(red: 0, green: 0, blue: 0)
|
||
|
values = {}
|
||
|
values[key] = value
|
||
|
map = SassC::Script::Value::Map.new values
|
||
|
return map
|
||
|
end
|
||
|
|
||
|
def returns_sass_list
|
||
|
numbers = [10, 20, 30].map { |n| SassC::Script::Value::Number.new(n, '') }
|
||
|
SassC::Script::Value::List.new(numbers, separator: :space)
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
end
|
||
|
end
|