gestart-admino 1.0.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 (46) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +29 -0
  3. data/.gitignore +20 -0
  4. data/.tool-versions +1 -0
  5. data/CHANGELOG.md +91 -0
  6. data/Gemfile +3 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +716 -0
  9. data/Rakefile +6 -0
  10. data/gestart-admino.gemspec +36 -0
  11. data/lib/admino/action_view_extension.rb +35 -0
  12. data/lib/admino/query/base.rb +124 -0
  13. data/lib/admino/query/base_presenter.rb +27 -0
  14. data/lib/admino/query/builder.rb +24 -0
  15. data/lib/admino/query/configuration.rb +100 -0
  16. data/lib/admino/query/dsl.rb +38 -0
  17. data/lib/admino/query/filter_group.rb +73 -0
  18. data/lib/admino/query/filter_group_presenter.rb +85 -0
  19. data/lib/admino/query/scope_presenter.rb +17 -0
  20. data/lib/admino/query/search_field.rb +43 -0
  21. data/lib/admino/query/sorting.rb +74 -0
  22. data/lib/admino/query/sorting_presenter.rb +60 -0
  23. data/lib/admino/query.rb +15 -0
  24. data/lib/admino/table/head_row.rb +86 -0
  25. data/lib/admino/table/presenter.rb +122 -0
  26. data/lib/admino/table/resource_row.rb +145 -0
  27. data/lib/admino/table/row.rb +56 -0
  28. data/lib/admino/table.rb +7 -0
  29. data/lib/admino/version.rb +3 -0
  30. data/lib/admino.rb +14 -0
  31. data/lib/gestart-admino.rb +1 -0
  32. data/spec/admino/integration_spec.rb +63 -0
  33. data/spec/admino/query/base_presenter_spec.rb +59 -0
  34. data/spec/admino/query/base_spec.rb +192 -0
  35. data/spec/admino/query/dsl_spec.rb +52 -0
  36. data/spec/admino/query/filter_group_presenter_spec.rb +182 -0
  37. data/spec/admino/query/filter_group_spec.rb +133 -0
  38. data/spec/admino/query/search_field_spec.rb +66 -0
  39. data/spec/admino/query/sorting_presenter_spec.rb +206 -0
  40. data/spec/admino/query/sorting_spec.rb +137 -0
  41. data/spec/admino/table/head_row_spec.rb +158 -0
  42. data/spec/admino/table/presenter_spec.rb +180 -0
  43. data/spec/admino/table/resource_row_spec.rb +225 -0
  44. data/spec/admino/table/row_spec.rb +76 -0
  45. data/spec/spec_helper.rb +93 -0
  46. metadata +267 -0
@@ -0,0 +1,192 @@
1
+ require 'spec_helper'
2
+
3
+ module Admino
4
+ module Query
5
+ describe Base do
6
+ subject(:query) { Base.new(params, context, config) }
7
+ let(:params) { {} }
8
+ let(:context) { {} }
9
+ let(:config) { nil }
10
+
11
+ it 'takes a request params' do
12
+ expect(query.params).to eq params
13
+ end
14
+
15
+ context 'with an explicit config' do
16
+ let(:config) { Configuration.new }
17
+
18
+ it 'uses it' do
19
+ expect(query.config).to eq config
20
+ end
21
+ end
22
+
23
+ context 'with a declared search_field' do
24
+ let(:config) { Configuration.new }
25
+ let(:search_field_config) { config.add_search_field(:search_field) }
26
+
27
+ before do
28
+ search_field_config
29
+ end
30
+
31
+ it 'returns a configured SearchField' do
32
+ search_field = query.search_field_by_name(:search_field)
33
+ expect(search_field.config).to eq search_field_config
34
+ expect(search_field.params).to eq params
35
+ end
36
+ end
37
+
38
+ context 'with a declared filter_group' do
39
+ let(:config) { Configuration.new }
40
+ let(:filter_group_config) { config.add_filter_group(:filter_group, [:one, :two]) }
41
+
42
+ before do
43
+ filter_group_config
44
+ end
45
+
46
+ it 'returns a configured FilterGroup' do
47
+ filter_group = query.filter_group_by_name(:filter_group)
48
+ expect(filter_group.config).to eq filter_group_config
49
+ expect(filter_group.params).to eq params
50
+ expect(filter_group.i18n_key).to eq :filter_group
51
+ end
52
+ end
53
+
54
+ context 'with a declared sorting' do
55
+ let(:config) { Configuration.new }
56
+ let(:sorting_config) do
57
+ config.add_sorting_scopes([:by_title, :by_date])
58
+ end
59
+
60
+ before do
61
+ sorting_config
62
+ end
63
+
64
+ it 'returns a configured Sorting' do
65
+ sorting = query.sorting
66
+ expect(sorting.config).to eq sorting_config
67
+ expect(sorting.params).to eq params
68
+ end
69
+ end
70
+
71
+ describe '#scope' do
72
+ let(:config) { Configuration.new }
73
+ let(:result) { query.scope(starting_scope) }
74
+ let(:starting_scope) { ScopeMock.new('explicit') }
75
+
76
+ describe 'starting scope' do
77
+ context 'with an explicit scope' do
78
+ it 'uses it' do
79
+ expect(result.name).to eq 'explicit'
80
+ end
81
+ end
82
+
83
+ context 'with no explicit scope, but a default one configured' do
84
+ let(:result) { query.scope }
85
+
86
+ before do
87
+ config.starting_scope_callable = Proc.new { |query|
88
+ ScopeMock.new('configured').foo(query)
89
+ }
90
+ end
91
+
92
+ before do
93
+ result
94
+ end
95
+
96
+ it 'calls it with self and uses it' do
97
+ expect(result.name).to eq 'configured'
98
+ expect(result.chain).to eq [:foo, [query]]
99
+ end
100
+ end
101
+
102
+ context 'with no scope' do
103
+ let(:result) { query.scope }
104
+
105
+ it 'raises a ArgumentError' do
106
+ expect { result }.to raise_error(ArgumentError)
107
+ end
108
+ end
109
+ end
110
+
111
+ context 'with a set of search_fields, filter_groups and sortings' do
112
+ let(:search_field_config) { config.add_search_field(:search_field) }
113
+ let(:filter_group_config) { config.add_filter_group(:filter_group, [:one, :two]) }
114
+ let(:sorting_config) { config.add_sorting_scopes([:title, :year]) }
115
+
116
+ let(:params) do
117
+ {
118
+ query: {
119
+ search_field: "foo",
120
+ filter_group: "one",
121
+ },
122
+ sorting: "title",
123
+ sort_order: "desc"
124
+ }
125
+ end
126
+
127
+ before do
128
+ search_field_config
129
+ filter_group_config
130
+ sorting_config
131
+ query
132
+ end
133
+
134
+ context 'if query object does not respond to scopes' do
135
+ it 'chains from starting scope' do
136
+ expect(result.chain).to eq [
137
+ :search_field, ["foo"],
138
+ :one, [],
139
+ :title, [:desc]
140
+ ]
141
+ end
142
+ end
143
+
144
+ context 'else' do
145
+ let(:query_klass) do
146
+ Class.new(Base) do
147
+ def self.model_name
148
+ ActiveModel::Name.new(self, nil, "temp")
149
+ end
150
+
151
+ def search_field_scope(scope, foo)
152
+ scope.my_search_field(foo)
153
+ end
154
+
155
+ def one_scope(scope)
156
+ scope.my_one
157
+ end
158
+
159
+ def title_scope(scope, order)
160
+ scope.my_title(order)
161
+ end
162
+ end
163
+ end
164
+
165
+ subject(:query) do
166
+ query_klass.new(params, context, config)
167
+ end
168
+
169
+ it 'chains from starting scope calling query object methods' do
170
+ expect(result.chain).to eq [
171
+ :my_search_field, ["foo"],
172
+ :my_one, [],
173
+ :my_title, [:desc]
174
+ ]
175
+ end
176
+ end
177
+ end
178
+
179
+ context 'with a configured ending scope' do
180
+ before do
181
+ config.ending_scope_callable = Proc.new { |query| bar(query) }
182
+ end
183
+
184
+ it 'calls it with self at the end of the chain' do
185
+ expect(result.chain).to eq [:bar, [query]]
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
192
+
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ module Admino
4
+ module Query
5
+ describe Dsl do
6
+ let(:config) { TestQuery.config }
7
+ let(:instance) { TestQuery.new }
8
+
9
+ it 'allows #search_field declaration' do
10
+ search_field = config.search_fields.last
11
+ expect(search_field.name).to eq :starting_from
12
+ end
13
+
14
+ it 'allows #filter_by declaration' do
15
+ filter_group = config.filter_groups.first
16
+ expect(filter_group.name).to eq :bar
17
+ expect(filter_group.scopes).to eq [:one, :two]
18
+ expect(filter_group.include_empty_scope?).to be_truthy
19
+ end
20
+
21
+ it 'allows #sortings declaration' do
22
+ sorting = config.sorting
23
+ expect(sorting.scopes).to eq [:by_title, :by_date]
24
+ expect(sorting.default_scope).to eq :by_title
25
+ expect(sorting.default_direction).to eq :desc
26
+ end
27
+
28
+ it 'allows #starting_scope block declaration' do
29
+ expect(config.starting_scope_callable.call).to eq 'start'
30
+ end
31
+
32
+ it 'allows #ending_scope block declaration' do
33
+ expect(config.ending_scope_callable.call).to eq 'end'
34
+ end
35
+
36
+ context 'with a search_field' do
37
+ let(:search_field) { double('SearchField', value: 'value') }
38
+
39
+ before do
40
+ allow(instance).to receive(:search_field_by_name).
41
+ with(:foo).
42
+ and_return(search_field)
43
+ end
44
+
45
+ it 'it generates a getter' do
46
+ expect(instance.foo).to eq 'value'
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+
@@ -0,0 +1,182 @@
1
+ require 'spec_helper'
2
+ require 'active_support/hash_with_indifferent_access'
3
+
4
+ module Admino
5
+ module Query
6
+ describe FilterGroupPresenter do
7
+ subject(:presenter) { FilterGroupPresenter.new(filter_group, view) }
8
+ let(:view) { RailsViewContext.new }
9
+ let(:filter_group) do
10
+ double(
11
+ 'FilterGroup',
12
+ query_i18n_key: :query_name,
13
+ i18n_key: :filter_group,
14
+ param_name: :filter_group
15
+ )
16
+ end
17
+ let(:request_object) do
18
+ double(
19
+ 'ActionDispatch::Request',
20
+ query_parameters: ActiveSupport::HashWithIndifferentAccess.new(
21
+ params
22
+ ),
23
+ path: '/'
24
+ )
25
+ end
26
+ let(:params) do
27
+ { 'query' => { 'field' => 'value', 'filter_group' => 'bar' } }
28
+ end
29
+
30
+ before do
31
+ allow(view).to receive(:request).and_return(request_object)
32
+ end
33
+
34
+ describe '#scope_link' do
35
+ subject { presenter.scope_link(:foo) }
36
+ let(:scope_active) { false }
37
+
38
+ before do
39
+ allow(filter_group).to receive(:is_scope_active?).
40
+ with(:foo).and_return(scope_active)
41
+ end
42
+
43
+ context 'active CSS class' do
44
+ let(:scope_active) { true }
45
+
46
+ it 'adds an is-active class' do
47
+ is_expected.to have_tag(:a, with: { class: 'is-active' })
48
+ end
49
+
50
+ context 'if an :active_class option is specified' do
51
+ subject { presenter.scope_link(:foo, active_class: 'active') }
52
+
53
+ it 'adds it' do
54
+ is_expected.to have_tag(:a, with: { class: 'active' })
55
+ end
56
+ end
57
+ end
58
+
59
+ context 'else' do
60
+ it 'does not add it' do
61
+ is_expected.not_to have_tag(:a, with: { class: 'is-active' })
62
+ end
63
+ end
64
+
65
+ context 'label' do
66
+ before do
67
+ allow(presenter).to receive(:scope_name).with(:foo).and_return('scope_name')
68
+ end
69
+
70
+ it 'uses #scope_name method' do
71
+ is_expected.to have_tag(:a, text: 'scope_name')
72
+ end
73
+
74
+ context 'if a second parameter is supplied' do
75
+ subject { presenter.scope_link(:foo, 'test', active_class: 'active') }
76
+
77
+ it 'uses it' do
78
+ is_expected.to have_tag(:a, text: 'test')
79
+ end
80
+ end
81
+ end
82
+
83
+ context 'URL' do
84
+ before do
85
+ allow(presenter).to receive(:scope_path).with(:foo).and_return('URL')
86
+ end
87
+
88
+ it 'uses #scope_path method' do
89
+ is_expected.to have_tag(:a, href: 'URL')
90
+ end
91
+ end
92
+ end
93
+
94
+ describe '#scope_params' do
95
+ let(:scope_active) { false }
96
+ subject { presenter.scope_params(:foo) }
97
+
98
+ before do
99
+ allow(filter_group).to receive(:is_scope_active?).with(:foo).and_return(scope_active)
100
+ end
101
+
102
+ context 'if scope is active' do
103
+ let(:scope_active) { true }
104
+
105
+ it 'deletes the filter_group param' do
106
+ expect(subject[:query]).not_to have_key 'filter_group'
107
+ end
108
+
109
+ it 'keeps the request parameters intact' do
110
+ presenter.scope_params(:foo)
111
+ expect(request_object.query_parameters[:query][:filter_group]).to be_present
112
+ end
113
+
114
+ context 'the resulting query hash becomes empty' do
115
+ let(:params) do
116
+ { 'query' => { 'filter_group' => 'bar' } }
117
+ end
118
+
119
+ it 'removes the param altoghether' do
120
+ expect(subject).not_to have_key 'query'
121
+ end
122
+ end
123
+ end
124
+
125
+ context 'else' do
126
+ let(:scope_active) { false }
127
+
128
+ it 'is set as filter group value' do
129
+ expect(subject[:query][:filter_group]).to eq 'foo'
130
+ end
131
+ end
132
+
133
+ it 'preserves the other params' do
134
+ expect(subject[:query][:field]).to eq 'value'
135
+ end
136
+ end
137
+
138
+ describe '#name' do
139
+ context do
140
+ before do
141
+ I18n.backend.store_translations(
142
+ :en,
143
+ query: { filter_groups: { query_name: { filter_group: { name: 'NAME' } } } }
144
+ )
145
+ end
146
+
147
+ it 'returns a I18n translatable name for the filter_group' do
148
+ expect(presenter.name).to eq 'NAME'
149
+ end
150
+ end
151
+
152
+ context 'if no translation is available' do
153
+ it 'falls back to a titleized version of the filter_group name' do
154
+ expect(presenter.name).to eq 'Filter group'
155
+ end
156
+ end
157
+ end
158
+
159
+ describe '#scope_name' do
160
+ context do
161
+ before do
162
+ I18n.backend.store_translations(
163
+ :en,
164
+ query: { filter_groups: { query_name: { filter_group: { scopes: { bar: 'NAME' } } } } }
165
+ )
166
+ end
167
+
168
+ it 'returns a I18n translatable name for the scope' do
169
+ expect(presenter.scope_name(:bar)).to eq 'NAME'
170
+ end
171
+ end
172
+
173
+ context 'if no translation is available' do
174
+ it 'falls back to a titleized version of the scope name' do
175
+ expect(presenter.scope_name(:bar)).to eq 'Bar'
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
182
+
@@ -0,0 +1,133 @@
1
+ require 'spec_helper'
2
+
3
+ module Admino
4
+ module Query
5
+ describe FilterGroup do
6
+ subject(:filter_group) { FilterGroup.new(config, params) }
7
+ let(:config) do
8
+ Configuration::FilterGroup.new(:foo, [:bar, :other], options)
9
+ end
10
+ let(:options) { {} }
11
+ let(:params) { {} }
12
+
13
+ describe '#active_scope' do
14
+ context 'with no param' do
15
+ let(:params) { {} }
16
+
17
+ it 'returns nil' do
18
+ expect(filter_group.active_scope).to be_nil
19
+ end
20
+
21
+ context 'if include_empty_scope is true' do
22
+ let(:options) { { include_empty_scope: true } }
23
+
24
+ it 'returns the :empty scope' do
25
+ expect(filter_group.active_scope).to eq :empty
26
+ end
27
+
28
+ context 'if default scope is set' do
29
+ let(:config) do
30
+ Configuration::FilterGroup.new(
31
+ :foo,
32
+ [:bar],
33
+ include_empty_scope: true,
34
+ default: :bar
35
+ )
36
+ end
37
+
38
+ it 'returns it' do
39
+ expect(filter_group.active_scope).to eq :bar
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ context 'with "empty" param' do
46
+ let(:params) { { 'query' => { 'foo' => 'empty' } } }
47
+
48
+ context 'if include_empty_scope is true' do
49
+ let(:options) { { include_empty_scope: true, default: :bar } }
50
+
51
+ it 'returns the :empty scope' do
52
+ expect(filter_group.active_scope).to eq :empty
53
+ end
54
+ end
55
+ end
56
+
57
+ context 'with an invalid value' do
58
+ let(:params) { { 'query' => { 'foo' => 'qux' } } }
59
+
60
+ it 'returns nil' do
61
+ expect(filter_group.active_scope).to be_nil
62
+ end
63
+
64
+ context 'if include_empty_scope is true' do
65
+ let(:options) { { include_empty_scope: true, default: :bar } }
66
+
67
+ it 'returns nil' do
68
+ expect(filter_group.active_scope).to be_nil
69
+ end
70
+ end
71
+ end
72
+
73
+ context 'with a valid value' do
74
+ let(:params) { { 'query' => { 'foo' => 'bar' } } }
75
+
76
+ it 'returns the scope name' do
77
+ expect(filter_group.active_scope).to eq :bar
78
+ end
79
+ end
80
+ end
81
+
82
+ describe '#augment_scope' do
83
+ let(:result) { filter_group.augment_scope(scope) }
84
+ let(:scope) { ScopeMock.new('original') }
85
+
86
+ context 'if the search_field has a value' do
87
+ let(:params) { { 'query' => { 'foo' => 'bar' } } }
88
+
89
+ it 'returns the original scope chained with the filter_group scope' do
90
+ expect(result.chain).to eq [:bar, []]
91
+ end
92
+ end
93
+
94
+ context 'else' do
95
+ it 'returns the original scope' do
96
+ expect(result).to eq scope
97
+ end
98
+
99
+ context 'if include_empty_scope is true' do
100
+ let(:options) { { include_empty_scope: true } }
101
+
102
+ it 'returns the original scope' do
103
+ expect(result).to eq scope
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ describe '#is_scope_active?' do
110
+ let(:params) { { 'query' => { 'foo' => 'bar' } } }
111
+
112
+ it 'returns true if the provided scope is the one currently active' do
113
+ expect(filter_group.is_scope_active?('bar')).to be_truthy
114
+ end
115
+ end
116
+
117
+ describe '#scopes' do
118
+ subject { filter_group.scopes }
119
+
120
+ context 'if include_empty_scope is true' do
121
+ let(:options) { { include_empty_scope: true } }
122
+
123
+ it { is_expected.to eq [:empty, :bar, :other] }
124
+ end
125
+
126
+ context 'else' do
127
+ it { is_expected.to eq [:bar, :other] }
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
133
+
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ module Admino
4
+ module Query
5
+ describe SearchField do
6
+ subject(:search_field) { SearchField.new(config, params) }
7
+ let(:config) { Configuration::SearchField.new(:foo) }
8
+ let(:params) { {} }
9
+
10
+ describe '#value' do
11
+ context 'with a value' do
12
+ let(:params) { { 'query' => { 'foo' => 'bar' } } }
13
+
14
+ it 'returns the param value for the search_field' do
15
+ expect(search_field.value).to eq 'bar'
16
+ end
17
+ end
18
+
19
+ context 'else' do
20
+ it 'returns nil' do
21
+ expect(search_field.value).to be_nil
22
+ end
23
+
24
+ context 'with a default value' do
25
+ let(:config) {
26
+ Configuration::SearchField.new(:foo, default: 'foo')
27
+ }
28
+
29
+ it 'returns it' do
30
+ expect(search_field.value).to eq 'foo'
31
+ end
32
+ end
33
+ end
34
+
35
+ context 'with a date-shaped value' do
36
+ let(:params) { { 'query' => { 'foo' => '2014-10-05' } } }
37
+
38
+ it 'returns it untouched, as a String' do
39
+ expect(search_field.value).to eq '2014-10-05'
40
+ expect(search_field.value).to be_a String
41
+ end
42
+ end
43
+ end
44
+
45
+ describe '#augment_scope' do
46
+ let(:result) { search_field.augment_scope(scope) }
47
+ let(:scope) { ScopeMock.new('original') }
48
+
49
+ context 'if the search_field has a value' do
50
+ let(:params) { { 'query' => { 'foo' => 'bar' } } }
51
+
52
+ it 'returns the original scope chained with the search_field scope' do
53
+ expect(result.chain).to eq [:foo, ['bar']]
54
+ end
55
+ end
56
+
57
+ context 'else' do
58
+ it 'returns the original scope' do
59
+ expect(result).to eq scope
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+