express_templates 0.3.6 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/core_extensions/proc.rb +1 -5
- data/lib/express_templates/compiler.rb +10 -9
- data/lib/express_templates/components.rb +1 -1
- data/lib/express_templates/components/capabilities/adoptable.rb +20 -0
- data/lib/express_templates/components/capabilities/building.rb +6 -0
- data/lib/express_templates/components/capabilities/parenting.rb +7 -2
- data/lib/express_templates/components/capabilities/templating.rb +1 -1
- data/lib/express_templates/components/for_each.rb +2 -0
- data/lib/express_templates/components/form_rails_support.rb +4 -1
- data/lib/express_templates/components/forms.rb +15 -0
- data/lib/express_templates/components/forms/basic_fields.rb +53 -0
- data/lib/express_templates/components/forms/checkbox.rb +37 -0
- data/lib/express_templates/components/forms/express_form.rb +66 -0
- data/lib/express_templates/components/forms/form_component.rb +60 -0
- data/lib/express_templates/components/forms/option_support.rb +43 -0
- data/lib/express_templates/components/forms/radio.rb +84 -0
- data/lib/express_templates/components/forms/select.rb +61 -0
- data/lib/express_templates/components/forms/submit.rb +26 -0
- data/lib/express_templates/components/null_wrap.rb +33 -1
- data/lib/express_templates/expander.rb +1 -0
- data/lib/express_templates/version.rb +1 -1
- data/test/components/container_test.rb +2 -0
- data/test/components/forms/basic_fields_test.rb +52 -0
- data/test/components/forms/checkbox_test.rb +44 -0
- data/test/components/forms/express_form_test.rb +120 -0
- data/test/components/forms/radio_test.rb +91 -0
- data/test/components/forms/select_test.rb +75 -0
- data/test/components/forms/submit_test.rb +12 -0
- data/test/components/iterating_test.rb +18 -0
- data/test/components/null_wrap_test.rb +28 -0
- data/test/components/table_for_test.rb +6 -6
- data/test/dummy/log/test.log +13758 -0
- data/test/markup/tag_test.rb +1 -1
- metadata +26 -5
- data/lib/express_templates/components/form_for.rb +0 -469
- data/test/components/form_for_test.rb +0 -246
@@ -0,0 +1,61 @@
|
|
1
|
+
module ExpressTemplates
|
2
|
+
module Components
|
3
|
+
module Forms
|
4
|
+
# Provides a form Select component based on the Rails *select_tag* helper.
|
5
|
+
# Parameters:
|
6
|
+
# field_name, select_options, helper_options
|
7
|
+
#
|
8
|
+
# Select options may be specified as an Array or Hash which will be
|
9
|
+
# supplied to the *options_for_select* helper.
|
10
|
+
#
|
11
|
+
# If the select_options are omitted, the component attempts to check
|
12
|
+
# whether the field is an association. If an association exists,
|
13
|
+
# the options will be generated using *options_from_collection_for_select*
|
14
|
+
# with the assumption that :id and :name are the value and name fields
|
15
|
+
# on the collection. If no association exists, we use all the existing
|
16
|
+
# unique values for the field on the collection to which the resource belongs
|
17
|
+
# as the list of possible values for the select.
|
18
|
+
class Select < FormComponent
|
19
|
+
include OptionSupport
|
20
|
+
|
21
|
+
emits -> {
|
22
|
+
div(class: field_wrapper_class) {
|
23
|
+
label_tag(label_name, label_text)
|
24
|
+
select_tag(field_name_attribute, select_options, field_options)
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
# Returns the options which will be supplied to the select_tag helper.
|
29
|
+
def select_options
|
30
|
+
options_specified = [Array, Hash].include?(@args.second.class)
|
31
|
+
if options_specified
|
32
|
+
options = @args.second
|
33
|
+
else
|
34
|
+
options = "@#{resource_name}.pluck(:#{field_name}).distinct"
|
35
|
+
end
|
36
|
+
|
37
|
+
if belongs_to_association && !options_specified
|
38
|
+
"{{options_from_collection_for_select(#{related_collection}, :id, :name, @#{resource_name}.#{field_name})}}"
|
39
|
+
else
|
40
|
+
if selection = field_options.delete(:selected)
|
41
|
+
"{{options_for_select(#{options}, \"#{selection}\")}}"
|
42
|
+
else
|
43
|
+
"{{options_for_select(#{options}, @#{resource_name}.#{field_name})}}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def field_options
|
49
|
+
# If field_otions is omitted the Expander will be
|
50
|
+
# in last or 3rd position and we don't want that
|
51
|
+
if @args.size > 3 && @args[2].is_a?(Hash)
|
52
|
+
@args[2]
|
53
|
+
else
|
54
|
+
{}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ExpressTemplates
|
2
|
+
module Components
|
3
|
+
module Forms
|
4
|
+
class Submit < FormComponent
|
5
|
+
|
6
|
+
emits -> {
|
7
|
+
div(class: field_wrapper_class) {
|
8
|
+
submit_tag(value, html_options)
|
9
|
+
}
|
10
|
+
}
|
11
|
+
|
12
|
+
def value
|
13
|
+
if @args.first.is_a?(String)
|
14
|
+
@args.first
|
15
|
+
else
|
16
|
+
'Save'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def html_options
|
21
|
+
@config
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -1,8 +1,40 @@
|
|
1
1
|
module ExpressTemplates
|
2
2
|
module Components
|
3
|
+
# NullWrap is useful for creating a node in the component tree that does
|
4
|
+
# not produce any markup. It can be used in a template fragment to
|
5
|
+
# wrap bare text or string content where such would not normally be possible.
|
6
|
+
#
|
7
|
+
# Example:
|
8
|
+
#
|
9
|
+
# ```ruby
|
10
|
+
# div {
|
11
|
+
# some_component
|
12
|
+
# null_wrap {
|
13
|
+
# "Some text"
|
14
|
+
# }
|
15
|
+
# other_component
|
16
|
+
# }
|
17
|
+
# ```
|
18
|
+
#
|
19
|
+
# Otherwise you can use it to hold already-complied code meant for
|
20
|
+
# evaluation in the view that needs to be protected from being compiled
|
21
|
+
# again. This use is largely internal to express_templates. It is not
|
22
|
+
# expected that users of this library would need this.
|
23
|
+
#
|
24
|
+
# Example:
|
25
|
+
#
|
26
|
+
# ```ruby
|
27
|
+
# null_wrap("(@collection.map do |member| \"<li>#{member.name}</li>\").join")
|
28
|
+
# ```
|
29
|
+
#
|
3
30
|
class NullWrap < Components::Container
|
31
|
+
def initialize(*args)
|
32
|
+
@already_compiled_stuff = args.shift if args.first.is_a?(String)
|
33
|
+
super(*args)
|
34
|
+
end
|
35
|
+
|
4
36
|
def compile
|
5
|
-
compile_children
|
37
|
+
@already_compiled_stuff || compile_children
|
6
38
|
end
|
7
39
|
end
|
8
40
|
end
|
@@ -22,6 +22,8 @@ class ContainerTest < ActiveSupport::TestCase
|
|
22
22
|
child1, child2 = Minitest::Mock.new, Minitest::Mock.new
|
23
23
|
child1.expect(:compile, '"one"')
|
24
24
|
child2.expect(:compile, '"two"')
|
25
|
+
child1.expect(:kind_of?, false, [ExpressTemplates::Components::Capabilities::Adoptable])
|
26
|
+
child2.expect(:kind_of?, false, [ExpressTemplates::Components::Capabilities::Adoptable])
|
25
27
|
return child1, child2
|
26
28
|
end
|
27
29
|
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class BasicFieldsTest < ActiveSupport::TestCase
|
4
|
+
|
5
|
+
BASIC_FIELDS = %w(email phone text password color date datetime
|
6
|
+
datetime_local number range
|
7
|
+
search telephone time url week)
|
8
|
+
|
9
|
+
test "text requires parent" do
|
10
|
+
fragment = -> {
|
11
|
+
text :name
|
12
|
+
}
|
13
|
+
assert_raises(RuntimeError) {
|
14
|
+
ExpressTemplates.compile(&fragment)
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
test "all fields work" do
|
19
|
+
BASIC_FIELDS.each do |type|
|
20
|
+
fragment = -> {
|
21
|
+
express_form(:foo) {
|
22
|
+
send(type, :bar)
|
23
|
+
}
|
24
|
+
}
|
25
|
+
assert_match '#{label_tag("foo_bar", "Bar")', ExpressTemplates.compile(&fragment)
|
26
|
+
assert_match "#{type}_field(:foo, :bar)", ExpressTemplates.compile(&fragment)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
test "textarea uses rails text_area helper" do
|
31
|
+
fragment = -> {
|
32
|
+
express_form(:foo) {
|
33
|
+
textarea :bar
|
34
|
+
}
|
35
|
+
}
|
36
|
+
assert_match '#{label_tag("foo_bar", "Bar")', ExpressTemplates.compile(&fragment)
|
37
|
+
assert_match "text_area(:foo, :bar)", ExpressTemplates.compile(&fragment)
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
test "hidden uses rails hidden_tag helper" do
|
42
|
+
fragment = -> {
|
43
|
+
express_form(:foo) {
|
44
|
+
hidden :bar
|
45
|
+
}
|
46
|
+
}
|
47
|
+
assert_no_match '#{label_tag("foo_bar", "Bar")', ExpressTemplates.compile(&fragment)
|
48
|
+
assert_match "hidden_field(:foo, :bar)", ExpressTemplates.compile(&fragment)
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class CheckboxTest < ActiveSupport::TestCase
|
4
|
+
|
5
|
+
test "Checkbox requires a parent form" do
|
6
|
+
fragment = -> {
|
7
|
+
checkbox :permission_granted
|
8
|
+
}
|
9
|
+
assert_raises(RuntimeError) {
|
10
|
+
ExpressTemplates.compile(&fragment)
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
test "checkbox places the label before the input" do
|
15
|
+
fragment = -> {
|
16
|
+
express_form(:account) {
|
17
|
+
checkbox :eula
|
18
|
+
}
|
19
|
+
}
|
20
|
+
compiled = ExpressTemplates.compile(&fragment)
|
21
|
+
label_helper = '#{label_tag("account_eula", "Eula")}'
|
22
|
+
field_helper = '#{check_box(:account, :eula, {}, "1", "0")}'
|
23
|
+
assert_match label_helper, compiled
|
24
|
+
assert_match field_helper, compiled
|
25
|
+
label_idx = compiled.index(label_helper)
|
26
|
+
field_idx = compiled.index(field_helper)
|
27
|
+
assert (field_idx > label_idx), "label must come first"
|
28
|
+
end
|
29
|
+
|
30
|
+
test "checkbox respects label_after: true " do
|
31
|
+
fragment = -> {
|
32
|
+
express_form(:account) {
|
33
|
+
checkbox :eula, label_after: true
|
34
|
+
}
|
35
|
+
}
|
36
|
+
compiled = ExpressTemplates.compile(&fragment)
|
37
|
+
label_helper = '#{label_tag("account_eula", "Eula")}'
|
38
|
+
field_helper = '#{check_box(:account, :eula, {}, "1", "0")}'
|
39
|
+
label_idx = compiled.index(label_helper)
|
40
|
+
field_idx = compiled.index(field_helper)
|
41
|
+
assert (field_idx < label_idx), "label must come after when label_after: true"
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
class ExpressFormTest < ActiveSupport::TestCase
|
5
|
+
class Context
|
6
|
+
def initialize(resource)
|
7
|
+
@resource = resource
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def resource
|
12
|
+
OpenStruct.new(
|
13
|
+
id: 1,
|
14
|
+
name: 'Foo',
|
15
|
+
body: 'Hello world',
|
16
|
+
email: 'some@email.com',
|
17
|
+
phone: '123123123',
|
18
|
+
url: 'http://someurl.com',
|
19
|
+
number: 123,
|
20
|
+
dropdown: 'yes',
|
21
|
+
gender: 'Male'
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def simplest_form(resource)
|
26
|
+
ctx = Context.new(resource)
|
27
|
+
fragment = -> {
|
28
|
+
express_form(:resource) {
|
29
|
+
submit value: 'Save it!'
|
30
|
+
}
|
31
|
+
}
|
32
|
+
return ctx, fragment
|
33
|
+
end
|
34
|
+
|
35
|
+
def express_form
|
36
|
+
"ExpressTemplates::Components::Forms::ExpressForm".constantize
|
37
|
+
end
|
38
|
+
|
39
|
+
test "express_form component exists" do
|
40
|
+
assert express_form
|
41
|
+
end
|
42
|
+
|
43
|
+
def compile_simplest_form
|
44
|
+
ctx, fragment = simplest_form(resource)
|
45
|
+
ExpressTemplates.compile(&fragment)
|
46
|
+
end
|
47
|
+
|
48
|
+
test "simplest form renders" do
|
49
|
+
assert compile_simplest_form
|
50
|
+
end
|
51
|
+
|
52
|
+
test "simplest form contains form tag" do
|
53
|
+
assert_match "<form", compile_simplest_form
|
54
|
+
end
|
55
|
+
|
56
|
+
test "simplest form contains rails form helpers" do
|
57
|
+
compiled_src = compile_simplest_form
|
58
|
+
assert_match "utf8_enforcer_tag", compiled_src
|
59
|
+
assert_match "method_tag(", compiled_src
|
60
|
+
assert_match "token_tag", compiled_src
|
61
|
+
end
|
62
|
+
|
63
|
+
test "simplest_form contains submit" do
|
64
|
+
assert_match 'submit_tag', compile_simplest_form
|
65
|
+
end
|
66
|
+
|
67
|
+
test "simplest_form adopts children (submit has reference to parent)" do
|
68
|
+
ctx, fragment = simplest_form(resource)
|
69
|
+
expanded_nodes = ExpressTemplates::Expander.new(nil).expand(fragment.source_body)
|
70
|
+
assert_instance_of ExpressTemplates::Components::Forms::ExpressForm,
|
71
|
+
expanded_nodes.first.children.last.parent
|
72
|
+
end
|
73
|
+
|
74
|
+
test "#form_action uses url helpers" do
|
75
|
+
assert_equal "{{foos_path}}", express_form.new(:foo).form_action
|
76
|
+
end
|
77
|
+
|
78
|
+
test "#form_action uses correct path helper for update/patch" do
|
79
|
+
assert_equal "{{foo_path(@foo)}}", express_form.new(:foo, method: :put).form_action
|
80
|
+
end
|
81
|
+
|
82
|
+
test "simplest_form uses form_action for the action" do
|
83
|
+
form_open_tag = compile_simplest_form.match(/<form[^>]*>/)[0]
|
84
|
+
assert_match 'action=\"#{resources_path}\"', form_open_tag
|
85
|
+
end
|
86
|
+
|
87
|
+
test "express_form default method is POST" do
|
88
|
+
form_open_tag = compile_simplest_form.match(/<form[^>]*>/)[0]
|
89
|
+
assert_match 'method=\"POST\"', form_open_tag
|
90
|
+
end
|
91
|
+
|
92
|
+
test "express_form accepts :resource_name for removing namespace" do
|
93
|
+
fragment = -> {
|
94
|
+
express_form(:admin_foo, resource_name: 'foo') {
|
95
|
+
submit "Save!"
|
96
|
+
}
|
97
|
+
}
|
98
|
+
expanded_nodes = ExpressTemplates::Expander.new(nil).expand(fragment.source_body)
|
99
|
+
assert_equal 'foo', expanded_nodes.first.resource_name
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
# test "simplest form compiled source is legible " do
|
104
|
+
# @example_compiled = -> {
|
105
|
+
# "<form action=\"/resources/#{@resource.id}\" method=\"post\">
|
106
|
+
# <div style=\"display:none\">
|
107
|
+
# "+%Q(#{utf8_enforcer_tag})+%Q(#{method_tag(:post)})+%Q(#{token_tag})+"
|
108
|
+
# </div>
|
109
|
+
# <div class=\"form-group widget-buttons\">
|
110
|
+
# "+%Q(#{submit_tag("Save it!", class: "submit primary")})+"</div>
|
111
|
+
# </form>
|
112
|
+
# "
|
113
|
+
# }.source_body
|
114
|
+
# ExpressTemplates::Markup::Tag.formatted do
|
115
|
+
# ctx, fragment = simplest_form(resource)
|
116
|
+
# assert_equal @example_compiled, ExpressTemplates.compile(&fragment)
|
117
|
+
# end
|
118
|
+
# end
|
119
|
+
|
120
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class RadioTest < ActiveSupport::TestCase
|
4
|
+
|
5
|
+
test "radio requires a parent component" do
|
6
|
+
fragment = -> {
|
7
|
+
radio :preferred_email_format, ['HTML', 'Text']
|
8
|
+
}
|
9
|
+
assert_raises(RuntimeError) {
|
10
|
+
ExpressTemplates.compile(&fragment)
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
def radio_with_array_options
|
15
|
+
fragment = -> {
|
16
|
+
express_form(:person) {
|
17
|
+
radio :preferred_email_format, ['HTML', 'Text']
|
18
|
+
}
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
test "radio has correct label field name and text" do
|
23
|
+
assert_match '#{label_tag("person_preferred_email_format", "Preferred Email Format")}',
|
24
|
+
ExpressTemplates.compile(&radio_with_array_options)
|
25
|
+
end
|
26
|
+
|
27
|
+
test "radio options present with class 'radio'" do
|
28
|
+
compiled = ExpressTemplates.compile(&radio_with_array_options)
|
29
|
+
assert_match 'radio_button(:person, :preferred_email_format, "Text", class: "radio"', compiled
|
30
|
+
assert_match '_format, "HTML", class: "radio"', compiled
|
31
|
+
end
|
32
|
+
|
33
|
+
def radio_with_hash_options
|
34
|
+
fragment = -> {
|
35
|
+
express_form(:person) {
|
36
|
+
radio :subscribed, {1 => 'Yes', 0 => 'No'}, wrapper_class: 'my-wrapper'
|
37
|
+
}
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
test "radio options may be specified with a hash" do
|
42
|
+
compiled = ExpressTemplates.compile(&radio_with_hash_options)
|
43
|
+
assert_match '<label class=\"my-wrapper\">', compiled
|
44
|
+
assert_match 'radio_button(:person, :subscribed, 0, class: "radio"', compiled
|
45
|
+
assert_match 'radio_button(:person, :subscribed, 1, class: "radio"', compiled
|
46
|
+
end
|
47
|
+
|
48
|
+
test "radio throws error if given improper options" do
|
49
|
+
fragment = -> {
|
50
|
+
express_form(:person) {
|
51
|
+
radio :subscribed, "Garbage options"
|
52
|
+
}
|
53
|
+
}
|
54
|
+
assert_raises(RuntimeError) {
|
55
|
+
ExpressTemplates.compile(&fragment)
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
def radio_with_options_omitted
|
60
|
+
fragment = -> {
|
61
|
+
express_form(:employee) {
|
62
|
+
radio :department_id
|
63
|
+
}
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
class ::Department ; end
|
68
|
+
class ::Employee
|
69
|
+
def self.reflect_on_association(field)
|
70
|
+
if field.eql? :department_id
|
71
|
+
dummy_association = Object.new
|
72
|
+
class << dummy_association
|
73
|
+
def macro ; :belongs_to ; end
|
74
|
+
def klass ; ::Department ; end
|
75
|
+
end
|
76
|
+
return dummy_association
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
test "radio options from collection when options omitted" do
|
82
|
+
assert_match 'collection_radio_buttons(:employee, :department_id, Department.all.select(:id, :name).order(:name), :id, :name, {}, {}',
|
83
|
+
ExpressTemplates.compile(&radio_with_options_omitted)
|
84
|
+
end
|
85
|
+
|
86
|
+
# test "radio supports html options"
|
87
|
+
|
88
|
+
# test "html_options passed correctly when collection is omitted"
|
89
|
+
|
90
|
+
# test "radio helper options passed to collection_radio_buttons"
|
91
|
+
end
|