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.
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