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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/lib/core_extensions/proc.rb +1 -5
  3. data/lib/express_templates/compiler.rb +10 -9
  4. data/lib/express_templates/components.rb +1 -1
  5. data/lib/express_templates/components/capabilities/adoptable.rb +20 -0
  6. data/lib/express_templates/components/capabilities/building.rb +6 -0
  7. data/lib/express_templates/components/capabilities/parenting.rb +7 -2
  8. data/lib/express_templates/components/capabilities/templating.rb +1 -1
  9. data/lib/express_templates/components/for_each.rb +2 -0
  10. data/lib/express_templates/components/form_rails_support.rb +4 -1
  11. data/lib/express_templates/components/forms.rb +15 -0
  12. data/lib/express_templates/components/forms/basic_fields.rb +53 -0
  13. data/lib/express_templates/components/forms/checkbox.rb +37 -0
  14. data/lib/express_templates/components/forms/express_form.rb +66 -0
  15. data/lib/express_templates/components/forms/form_component.rb +60 -0
  16. data/lib/express_templates/components/forms/option_support.rb +43 -0
  17. data/lib/express_templates/components/forms/radio.rb +84 -0
  18. data/lib/express_templates/components/forms/select.rb +61 -0
  19. data/lib/express_templates/components/forms/submit.rb +26 -0
  20. data/lib/express_templates/components/null_wrap.rb +33 -1
  21. data/lib/express_templates/expander.rb +1 -0
  22. data/lib/express_templates/version.rb +1 -1
  23. data/test/components/container_test.rb +2 -0
  24. data/test/components/forms/basic_fields_test.rb +52 -0
  25. data/test/components/forms/checkbox_test.rb +44 -0
  26. data/test/components/forms/express_form_test.rb +120 -0
  27. data/test/components/forms/radio_test.rb +91 -0
  28. data/test/components/forms/select_test.rb +75 -0
  29. data/test/components/forms/submit_test.rb +12 -0
  30. data/test/components/iterating_test.rb +18 -0
  31. data/test/components/null_wrap_test.rb +28 -0
  32. data/test/components/table_for_test.rb +6 -6
  33. data/test/dummy/log/test.log +13758 -0
  34. data/test/markup/tag_test.rb +1 -1
  35. metadata +26 -5
  36. data/lib/express_templates/components/form_for.rb +0 -469
  37. 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
@@ -76,6 +76,7 @@ module ExpressTemplates
76
76
  end
77
77
 
78
78
  def method_missing(name, *args, &block)
79
+ raise "#{self.class} unexpected macro: \"#{name}\"." if locals.nil?
79
80
  return locals[name] if locals.keys.include?(name)
80
81
 
81
82
  if handlers.keys.include?(name)
@@ -1,3 +1,3 @@
1
1
  module ExpressTemplates
2
- VERSION = "0.3.6"
2
+ VERSION = "0.4.0"
3
3
  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