express_templates 0.3.6 → 0.4.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.
- 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
|