adminterface 0.2.0 → 0.3.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.md +19 -109
  3. data/README.md +33 -27
  4. data/Rakefile +1 -1
  5. data/app/assets/stylesheets/adminterface/components/_form.scss +1 -0
  6. data/app/assets/stylesheets/adminterface/mixins/_utilities.scss +4 -0
  7. data/app/assets/stylesheets/adminterface/vendors/bootstrap/_all.scss +1 -0
  8. data/app/assets/stylesheets/adminterface/vendors/bootstrap/components/_breadcrumb.scss +1 -0
  9. data/app/assets/stylesheets/adminterface/vendors/flatpickr/{main.scss → _main.scss} +6 -0
  10. data/app/javascript/adminterface/initializers/header_toggler.js +1 -1
  11. data/app/javascript/adminterface/initializers/plugins/flatpickr.js +1 -1
  12. data/app/javascript/adminterface/initializers/plugins/input_counter.js +1 -1
  13. data/app/javascript/adminterface/initializers/plugins/password_visibility.js +1 -1
  14. data/app/javascript/adminterface/initializers/plugins/tom_select.js +1 -1
  15. data/app/javascript/adminterface/lib/__tests__/checkbox_toggler.html +5 -0
  16. data/app/javascript/adminterface/lib/__tests__/checkbox_toggler.spec.js +110 -0
  17. data/app/javascript/adminterface/lib/__tests__/detached_dropdown.spec.js +69 -70
  18. data/app/javascript/adminterface/lib/__tests__/filters.spec.js +0 -1
  19. data/app/javascript/adminterface/lib/__tests__/has_many.html +14 -0
  20. data/app/javascript/adminterface/lib/__tests__/has_many.spec.js +224 -0
  21. data/app/javascript/adminterface/lib/__tests__/header_toggler.html +2 -0
  22. data/app/javascript/adminterface/lib/__tests__/header_toggler.spec.js +212 -0
  23. data/app/javascript/adminterface/lib/__tests__/per_page.spec.js +75 -0
  24. data/app/javascript/adminterface/lib/__tests__/utils.spec.js +0 -1
  25. data/app/javascript/adminterface/lib/has_many.js +41 -38
  26. data/app/javascript/adminterface/lib/header_toggler.js +10 -8
  27. data/app/views/layouts/active_admin_logged_out.html.erb +1 -1
  28. data/lib/adminterface/configs.rb +5 -1
  29. data/lib/adminterface/engine.rb +0 -7
  30. data/lib/adminterface/extensions/base_controller.rb +16 -12
  31. data/lib/adminterface/extensions/namespace_settings.rb +3 -0
  32. data/lib/adminterface/extensions/view_helpers/alert_helper.rb +3 -1
  33. data/lib/adminterface/extensions/views/components/dropdown_menu.rb +4 -1
  34. data/lib/adminterface/extensions/views/components/menu_item.rb +8 -3
  35. data/lib/adminterface/extensions/views/components/tabs.rb +38 -13
  36. data/lib/adminterface/extensions/views/header.rb +2 -1
  37. data/lib/adminterface/extensions/views/pages/base.rb +4 -1
  38. data/lib/adminterface/initializers/configurations.rb +1 -1
  39. data/lib/adminterface/version.rb +1 -1
  40. data/lib/adminterface.rb +0 -31
  41. data/lib/generators/adminterface/comments/comments_generator.rb +4 -0
  42. data/lib/generators/adminterface/configs/configs_generator.rb +2 -2
  43. data/lib/generators/adminterface/install/install_generator.rb +21 -23
  44. data/lib/generators/adminterface/views/views_generator.rb +2 -2
  45. data/lib/generators/adminterface/webpacker/webpacker_generator.rb +20 -6
  46. metadata +16 -14
  47. data/lib/adminterface/encryption/encryptor.rb +0 -26
  48. data/lib/adminterface/license.rb +0 -18
  49. data/lib/adminterface/licensing/base.rb +0 -99
  50. data/lib/adminterface/licensing/commercial.rb +0 -7
  51. data/lib/adminterface/licensing/notice.rb +0 -54
  52. data/lib/adminterface/licensing/personal.rb +0 -13
  53. data/lib/adminterface/public.pem +0 -9
@@ -0,0 +1,224 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import Sortable from 'sortablejs'
4
+ import HasManyClass from '../has_many'
5
+
6
+ jest.mock('sortablejs')
7
+
8
+ describe('HasMany', () => {
9
+ const html = fs.readFileSync(path.resolve(__dirname, './has_many.html'))
10
+ let element
11
+
12
+ const options = {
13
+ list: '.has-many-list',
14
+ item: 'fieldset.has_many_fields',
15
+ addLink: 'a.button.has_many_add',
16
+ removeLink: 'a.button.has_many_remove',
17
+ destroyInput: 'input[name$="[_destroy]"]'
18
+ }
19
+
20
+ beforeEach(() => {
21
+ document.documentElement.innerHTML = html
22
+ element = document.querySelector('div')
23
+ global.adminterface = {
24
+ addObserver: jest.fn()
25
+ }
26
+ })
27
+
28
+ afterEach(() => {
29
+ jest.restoreAllMocks()
30
+ })
31
+
32
+ test('constructor initiates correctly', () => {
33
+ const spyOnBind = jest.spyOn(HasManyClass.prototype, '_bind')
34
+ spyOnBind.mockImplementation(() => {})
35
+
36
+ const HasMany = new HasManyClass(element)
37
+ expect(HasMany.element).toStrictEqual(element)
38
+
39
+ expect(spyOnBind).toHaveBeenCalledTimes(1)
40
+ })
41
+
42
+ test('bind calls bindEvents, initSortable and admininterface', () => {
43
+ const spyOnBindEvents = jest.spyOn(HasManyClass.prototype, '_bindEvents')
44
+ spyOnBindEvents.mockImplementation(() => {})
45
+
46
+ const spyOnInitSortable = jest.spyOn(HasManyClass.prototype, '_initSortable')
47
+ spyOnInitSortable.mockImplementation(() => {})
48
+
49
+ const HasMany = new HasManyClass(element)
50
+
51
+ expect(spyOnBindEvents).toHaveBeenCalledWith(element)
52
+ expect(spyOnInitSortable).toHaveBeenCalledTimes(1)
53
+ expect(global.adminterface.addObserver).toHaveBeenCalledTimes(1)
54
+ expect(global.adminterface.addObserver).toHaveBeenCalledWith(
55
+ element,
56
+ HasMany,
57
+ HasMany.constructor.name,
58
+ []
59
+ )
60
+ })
61
+
62
+ test('initSortable assigns new instance of Sortable to sortable property', () => {
63
+ const spyOnBindEvents = jest.spyOn(HasManyClass.prototype, '_bindEvents')
64
+ spyOnBindEvents.mockImplementation(() => {})
65
+
66
+ const HasMany = new HasManyClass(element)
67
+
68
+ const MockSortableArgument = {
69
+ handle: '.handle',
70
+ animation: 150,
71
+ ghostClass: 'sortable-placeholder',
72
+ dragClass: 'sortable-drag',
73
+ onUpdate: expect.any(Function)
74
+ }
75
+
76
+ expect(Sortable).toHaveBeenCalledTimes(1)
77
+ expect(Sortable).toHaveBeenCalledWith(
78
+ document.querySelector(HasMany.options.list),
79
+ MockSortableArgument
80
+ )
81
+ const sortable = new Sortable(
82
+ document.querySelector(HasMany.options.list),
83
+ MockSortableArgument
84
+ )
85
+ expect(HasMany.sortable.toString()).toStrictEqual(sortable.toString())
86
+ })
87
+
88
+ test('recomputePosition changes sortableInput value', () => {
89
+ const query = 'input[name$="[address]"]'
90
+ const sortableInput = element.querySelector(query)
91
+
92
+ expect(sortableInput.value.length).not.toEqual(0)
93
+
94
+ const HasMany = new HasManyClass(element)
95
+ HasMany._recomputePosition()
96
+
97
+ expect(sortableInput.value.length).toEqual(0)
98
+ })
99
+
100
+ test('bindDestroyEvent called with element and change event added', () => {
101
+ const spyOnBindDestroyEvent = jest.spyOn(
102
+ HasManyClass.prototype,
103
+ '_bindDestroyEvent'
104
+ )
105
+ const destroyInput = document.querySelector(options.destroyInput)
106
+ destroyInput.addEventListener = jest.fn()
107
+
108
+ new HasManyClass(element) // eslint-disable-line no-new
109
+
110
+ expect(spyOnBindDestroyEvent).toHaveBeenCalledTimes(1)
111
+ expect(spyOnBindDestroyEvent).toHaveBeenCalledWith(destroyInput)
112
+
113
+ expect(destroyInput.addEventListener).toHaveBeenCalledTimes(1)
114
+ expect(destroyInput.addEventListener).toHaveBeenCalledWith(
115
+ 'change',
116
+ expect.any(Function)
117
+ )
118
+ })
119
+
120
+ test('bindRemoveEvent called with element and click event added', () => {
121
+ const spyOnBindRemoveEvent = jest.spyOn(
122
+ HasManyClass.prototype,
123
+ '_bindRemoveEvent'
124
+ )
125
+ const removeLinks = document.querySelector(options.removeLink)
126
+ removeLinks.addEventListener = jest.fn()
127
+
128
+ new HasManyClass(element) // eslint-disable-line no-new
129
+
130
+ expect(spyOnBindRemoveEvent).toHaveBeenCalledTimes(1)
131
+ expect(spyOnBindRemoveEvent).toHaveBeenCalledWith(removeLinks)
132
+
133
+ expect(removeLinks.addEventListener).toHaveBeenCalledTimes(1)
134
+ expect(removeLinks.addEventListener).toHaveBeenCalledWith(
135
+ 'click',
136
+ expect.any(Function)
137
+ )
138
+ })
139
+
140
+ test('bindRemoveEventCallBack', () => {
141
+ const mockEvent = {
142
+ preventDefault: () => {},
143
+ target: document.body.querySelector('fieldset')
144
+ }
145
+ const spyOnRecomputePosition = jest.spyOn(
146
+ HasManyClass.prototype,
147
+ '_recomputePosition'
148
+ )
149
+ const HasMany = new HasManyClass(element)
150
+ const spyOnDispatch = jest.spyOn(HasMany.element, 'dispatchEvent')
151
+
152
+ const fieldSet = mockEvent.target.closest(HasMany.options.item)
153
+ fieldSet.remove = jest.fn()
154
+
155
+ const parent = HasMany.element
156
+ const removeBeforeEvent = HasMany.events.removeBefore
157
+ removeBeforeEvent.detail = { fieldSet, parent }
158
+ const removeAfterEvent = HasMany.events.removeAfter
159
+ removeAfterEvent.detail = { fieldSet, parent }
160
+
161
+ const output = HasMany._bindRemoveEventCallBack(mockEvent)
162
+
163
+ expect(spyOnRecomputePosition).toHaveBeenCalledTimes(1)
164
+ expect(spyOnDispatch).toHaveBeenCalledTimes(2)
165
+ expect(spyOnDispatch.mock.calls).toMatchObject([
166
+ [removeBeforeEvent],
167
+ [removeAfterEvent]
168
+ ])
169
+ expect(fieldSet.remove).toHaveBeenCalledTimes(1)
170
+ expect(output).toBeTruthy()
171
+ })
172
+
173
+ test('bindAddEvent called with correctly element and click event added', () => {
174
+ const spyOnBindAddEvent = jest.spyOn(
175
+ HasManyClass.prototype,
176
+ '_bindAddEvent'
177
+ )
178
+
179
+ const addLinks = document.querySelector(options.addLink)
180
+ addLinks.addEventListener = jest.fn()
181
+
182
+ new HasManyClass(element) // eslint-disable-line no-new
183
+
184
+ expect(spyOnBindAddEvent).toHaveBeenCalledTimes(1)
185
+ expect(spyOnBindAddEvent).toHaveBeenCalledWith(addLinks)
186
+
187
+ expect(addLinks.addEventListener).toHaveBeenCalledTimes(1)
188
+ expect(addLinks.addEventListener).toHaveBeenCalledWith(
189
+ 'click',
190
+ expect.any(Function)
191
+ )
192
+ })
193
+
194
+ test('bindAddEventCallBack', () => {
195
+ const mockEvent = {
196
+ preventDefault: () => {},
197
+ target: document.body.querySelector(options.addLink)
198
+ }
199
+ const HasMany = new HasManyClass(element)
200
+ const spyOnDispatchEvent = jest.spyOn(HasMany.element, 'dispatchEvent')
201
+ const $list = HasMany.element.querySelector(HasMany.options.list)
202
+ const spyOnListAppendChild = jest.spyOn($list, 'appendChild')
203
+ const spyOnBindEvents = jest.spyOn(HasMany, '_bindEvents')
204
+ const spyOnRecomputePosition = jest.spyOn(HasMany, '_recomputePosition')
205
+ const beforeAdd = HasMany.events.addBefore
206
+ const parent = HasMany.element
207
+ const datasetHTML = document.createElement('div')
208
+
209
+ const output = HasMany._bindAddEventCallBack(mockEvent)
210
+
211
+ expect(spyOnDispatchEvent).toHaveBeenCalledTimes(2)
212
+ expect(spyOnDispatchEvent.mock.calls).toMatchObject([
213
+ [beforeAdd, [parent]],
214
+ [HasMany.events.addAfter]
215
+ ])
216
+
217
+ expect(spyOnListAppendChild).toHaveBeenCalledTimes(1)
218
+ expect(spyOnListAppendChild).toHaveBeenCalledWith(datasetHTML)
219
+ expect(spyOnBindEvents).toHaveBeenCalledTimes(1)
220
+ expect(spyOnBindEvents).toHaveBeenCalledWith(datasetHTML)
221
+ expect(spyOnRecomputePosition).toHaveBeenCalledTimes(1)
222
+ expect(output).toBeTruthy()
223
+ })
224
+ })
@@ -0,0 +1,2 @@
1
+ <div class="breakpoint-up-helper">
2
+ </div>
@@ -0,0 +1,212 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import HeaderTogglerClass from '../header_toggler'
4
+ import * as utilModule from '../utils'
5
+
6
+ describe('CheckboxToggler', () => {
7
+ const html = fs.readFileSync(path.resolve(__dirname, './header_toggler.html'))
8
+ const options = { item: 1 }
9
+ const defaults = {
10
+ container: 'body',
11
+ activeClass: 'header-active',
12
+ cookieName: 'header-state',
13
+ cookieExpireSec: 30 * 24 * 60 * 60
14
+ }
15
+ const mockEvent = {
16
+ preventDefault: jest.fn()
17
+ }
18
+
19
+ let element
20
+ let $container
21
+ let windowSpy
22
+ let spyOnAdd
23
+ let spyOnRemove
24
+ let spyOnCookieGet
25
+ let spyOnCookieSet
26
+
27
+ beforeEach(() => {
28
+ document.body.innerHTML = html
29
+ document.body.classList.remove(defaults.activeClass)
30
+ element = document.body.firstChild
31
+ $container = document.querySelector(defaults.container)
32
+
33
+ windowSpy = jest.spyOn(window, 'window', 'get')
34
+ spyOnCookieGet = jest.spyOn(utilModule, 'cookieGet')
35
+ spyOnCookieSet = jest.spyOn(utilModule, 'cookieSet')
36
+ spyOnCookieSet.mockImplementation(() => {})
37
+ spyOnAdd = jest.spyOn(HeaderTogglerClass.prototype, '_add')
38
+ spyOnRemove = jest.spyOn(HeaderTogglerClass.prototype, '_remove')
39
+
40
+ global.adminterface = {
41
+ addObserver: jest.fn()
42
+ }
43
+ })
44
+
45
+ afterEach(() => {
46
+ jest.restoreAllMocks()
47
+ })
48
+
49
+ test('constructor initiates correctly', () => {
50
+ const spyOnBind = jest.spyOn(HeaderTogglerClass.prototype, '_bind')
51
+ spyOnBind.mockImplementation(() => {})
52
+
53
+ const HeaderToggler = new HeaderTogglerClass(element, options)
54
+
55
+ expect(HeaderToggler.element).toStrictEqual(element)
56
+ expect(HeaderToggler.options).toStrictEqual({ ...defaults, ...options })
57
+
58
+ expect(spyOnBind).toHaveBeenCalledTimes(1)
59
+ })
60
+
61
+ test('bind calls cookieGet once with correct argument', () => {
62
+ const HeaderToggler = new HeaderTogglerClass(element, options)
63
+
64
+ expect(spyOnCookieGet).toHaveBeenCalledTimes(1)
65
+ expect(spyOnCookieGet).toHaveBeenCalledWith(HeaderToggler.options.cookieName)
66
+ })
67
+
68
+ test('bind calls add when cookie is activeClass and breakpointHelper is visible', () => {
69
+ windowSpy.mockImplementation(() => ({
70
+ getComputedStyle: () => ({
71
+ visibility: 'visible'
72
+ })
73
+ }))
74
+ spyOnCookieGet.mockImplementation(() => 'header-active')
75
+
76
+ new HeaderTogglerClass(element, options) // eslint-disable-line no-new
77
+
78
+ expect(spyOnAdd).toHaveBeenCalledTimes(1)
79
+ })
80
+
81
+ test('bind does not call add when cookie is not activeClass and breakpoint is visible', () => {
82
+ windowSpy.mockImplementation(() => ({
83
+ getComputedStyle: () => ({
84
+ visibility: 'visible'
85
+ })
86
+ }))
87
+ spyOnCookieGet.mockImplementation(() => 'header-not-active')
88
+
89
+ new HeaderTogglerClass(element, options) // eslint-disable-line no-new
90
+
91
+ expect(spyOnAdd).not.toHaveBeenCalled()
92
+ })
93
+
94
+ test('bind does not call add when cookie is activeClass and breakpointHelper is not visible', () => {
95
+ windowSpy.mockImplementation(() => ({
96
+ getComputedStyle: () => ({
97
+ visibility: 'not-visible'
98
+ })
99
+ }))
100
+ spyOnCookieGet.mockImplementation(() => 'header-active')
101
+
102
+ new HeaderTogglerClass(element, options) // eslint-disable-line no-new
103
+
104
+ expect(spyOnAdd).not.toHaveBeenCalled()
105
+ })
106
+
107
+ test('bind calls addEventListener on element once with click', () => {
108
+ const HeaderToggler = new HeaderTogglerClass(element, options)
109
+ const spyOnElementAddEventListener = jest.spyOn(HeaderToggler.element, 'addEventListener')
110
+
111
+ HeaderToggler._bind()
112
+
113
+ expect(spyOnElementAddEventListener).toHaveBeenCalledTimes(1)
114
+ expect(spyOnElementAddEventListener).toHaveBeenCalledWith(
115
+ 'click',
116
+ expect.any(Function)
117
+ )
118
+ })
119
+
120
+ test('bind calls adminterface addObserver once with correct argument', () => {
121
+ const spyOnAddObserver = jest.spyOn(global.adminterface, 'addObserver')
122
+
123
+ const HeaderToggler = new HeaderTogglerClass(element, options)
124
+
125
+ expect(spyOnAddObserver).toHaveBeenCalledTimes(1)
126
+ expect(spyOnAddObserver).toHaveBeenCalledWith(
127
+ HeaderToggler.element,
128
+ HeaderToggler,
129
+ HeaderToggler.constructor.name
130
+ )
131
+ })
132
+
133
+ test('clickCallback calls preventDefault', () => {
134
+ spyOnAdd.mockImplementation(() => {})
135
+ spyOnRemove.mockImplementation(() => {})
136
+
137
+ const HeaderToggler = new HeaderTogglerClass(element, options)
138
+
139
+ HeaderToggler._clickCallback(mockEvent, $container, HeaderToggler)
140
+
141
+ expect(mockEvent.preventDefault).toHaveBeenCalledTimes(1)
142
+ })
143
+
144
+ test('clickCallback calls remove when $container class contains activeClass', () => {
145
+ spyOnAdd.mockImplementation(() => {})
146
+ spyOnRemove.mockImplementation(() => {})
147
+
148
+ const HeaderToggler = new HeaderTogglerClass(element, options)
149
+
150
+ document.body.classList.add(HeaderToggler.options.activeClass)
151
+
152
+ spyOnAdd.mockClear()
153
+ HeaderToggler._clickCallback(mockEvent, $container, HeaderToggler)
154
+
155
+ expect(spyOnRemove).toHaveBeenCalledTimes(1)
156
+ expect(spyOnAdd).not.toHaveBeenCalled()
157
+ })
158
+
159
+ test('clickCallback calls add when $container class does not contain activeClass', () => {
160
+ spyOnAdd.mockImplementation(() => {})
161
+ spyOnRemove.mockImplementation(() => {})
162
+
163
+ const HeaderToggler = new HeaderTogglerClass(element, options)
164
+
165
+ spyOnAdd.mockClear()
166
+ HeaderToggler._clickCallback(mockEvent, $container, HeaderToggler)
167
+
168
+ expect(spyOnRemove).not.toHaveBeenCalled()
169
+ expect(spyOnAdd).toHaveBeenCalledTimes(1)
170
+ })
171
+
172
+ test('add calls $container.classList.add and cookieSet', () => {
173
+ const spyOnContainerClassListAdd = jest.spyOn($container.classList, 'add')
174
+
175
+ const HeaderToggler = new HeaderTogglerClass(element, options)
176
+ spyOnContainerClassListAdd.mockClear()
177
+ spyOnCookieSet.mockClear()
178
+ HeaderToggler._add()
179
+
180
+ expect(spyOnContainerClassListAdd).toHaveBeenCalledTimes(1)
181
+ expect(spyOnContainerClassListAdd).toHaveBeenCalledWith(
182
+ HeaderToggler.options.activeClass
183
+ )
184
+
185
+ expect(spyOnCookieSet).toHaveBeenCalledTimes(1)
186
+ expect(spyOnCookieSet).toHaveBeenCalledWith(
187
+ HeaderToggler.options.cookieName,
188
+ HeaderToggler.options.activeClass,
189
+ HeaderToggler.options.cookieExpireSec
190
+ )
191
+ })
192
+
193
+ test('remove calls $container.classList.remove and cookieSet', () => {
194
+ const spyOnContainerClassListRemove = jest.spyOn($container.classList, 'remove')
195
+
196
+ const HeaderToggler = new HeaderTogglerClass(element, options)
197
+ spyOnCookieSet.mockClear()
198
+ HeaderToggler._remove()
199
+
200
+ expect(spyOnContainerClassListRemove).toHaveBeenCalledTimes(1)
201
+ expect(spyOnContainerClassListRemove).toHaveBeenCalledWith(
202
+ HeaderToggler.options.activeClass
203
+ )
204
+
205
+ expect(spyOnCookieSet).toHaveBeenCalledTimes(1)
206
+ expect(spyOnCookieSet).toHaveBeenCalledWith(
207
+ HeaderToggler.options.cookieName,
208
+ null,
209
+ 0
210
+ )
211
+ })
212
+ })
@@ -0,0 +1,75 @@
1
+ import PerPage from '../per_page'
2
+ import * as utilModule from '../utils'
3
+
4
+ describe('CheckboxToggler', () => {
5
+ const mockEvent = {
6
+ preventDefault: jest.fn()
7
+ }
8
+ const mockQueryString = '?email=someone@example.com'
9
+ const value = 1
10
+
11
+ let spyOnQueryStringToParams
12
+ let spyOnTurbolinks
13
+ let spyOnTurbolinksVisit
14
+ let spyOnToQueryString
15
+ let params
16
+
17
+ beforeEach(() => {
18
+ Object.defineProperty(window, 'location', {
19
+ value: {
20
+ search: mockQueryString
21
+ },
22
+ writable: true
23
+ })
24
+
25
+ params = utilModule.queryStringToParams().filter(({ name }) => name !== 'per_page' && name !== 'page')
26
+ params.push({ name: 'per_page', value })
27
+
28
+ spyOnQueryStringToParams = jest.spyOn(utilModule, 'queryStringToParams')
29
+ spyOnTurbolinks = jest.spyOn(utilModule, 'hasTurbolinks')
30
+ spyOnTurbolinksVisit = jest.spyOn(utilModule, 'turbolinksVisit')
31
+ spyOnToQueryString = jest.spyOn(utilModule, 'toQueryString')
32
+
33
+ PerPage.value = value
34
+ })
35
+
36
+ afterEach(() => {
37
+ jest.restoreAllMocks()
38
+ mockEvent.preventDefault.mockClear()
39
+ })
40
+
41
+ test('update triggers queryStringToParams', () => {
42
+ PerPage._update(mockEvent)
43
+
44
+ expect(spyOnQueryStringToParams).toHaveBeenCalledTimes(1)
45
+ })
46
+
47
+ test('hasTurbolinks: true, update triggers preventDefault and turbolinksVisit', () => {
48
+ spyOnTurbolinks.mockImplementation(() => true)
49
+ spyOnTurbolinksVisit.mockImplementation(() => {})
50
+
51
+ PerPage._update(mockEvent)
52
+
53
+ expect(mockEvent.preventDefault).toHaveBeenCalledTimes(1)
54
+
55
+ expect(spyOnTurbolinksVisit).toHaveBeenCalledTimes(1)
56
+ expect(spyOnTurbolinksVisit).toHaveBeenCalledWith(params)
57
+
58
+ expect(spyOnToQueryString).not.toHaveBeenCalled()
59
+ })
60
+
61
+ test('hasTurbolinks: false, sets window.location.search to toQueryString output', () => {
62
+ spyOnTurbolinks.mockImplementation(() => false)
63
+ spyOnToQueryString.mockImplementation(params => params)
64
+
65
+ PerPage._update(mockEvent)
66
+
67
+ expect(mockEvent.preventDefault).not.toHaveBeenCalled()
68
+ expect(spyOnTurbolinksVisit).not.toHaveBeenCalled()
69
+
70
+ expect(spyOnToQueryString).toHaveBeenCalledTimes(1)
71
+ expect(spyOnToQueryString).toHaveBeenCalledWith(params)
72
+
73
+ expect(window.location.search).toStrictEqual(params)
74
+ })
75
+ })
@@ -1,4 +1,3 @@
1
- /* eslint-env jest */
2
1
  import {
3
2
  hasTurbolinks,
4
3
  turbolinksVisit,
@@ -51,54 +51,57 @@ class HasMany {
51
51
  }
52
52
 
53
53
  _bindAddEvent (el) {
54
- el.addEventListener('click', (e) => {
55
- let beforeAdd
56
- const el = e.target
57
- const parent = this.element
54
+ el.addEventListener('click', (e) => this._bindAddEventCallBack(e))
55
+ }
58
56
 
59
- e.preventDefault()
60
- parent.dispatchEvent(beforeAdd = this.events.addBefore, [parent])
57
+ _bindAddEventCallBack (e) {
58
+ let beforeAdd
59
+ const el = e.target
60
+ const parent = this.element
61
+ e.preventDefault()
62
+ parent.dispatchEvent(beforeAdd = this.events.addBefore, [parent])
61
63
 
62
- if (!beforeAdd.defaultPrevented) {
63
- let index = parent.dataset.hasManyIndex || parent.querySelectorAll(this.options.item).length - 1
64
- parent.setAttribute('data-has-many-index', ++index)
64
+ if (!beforeAdd.defaultPrevented) {
65
+ let index = parent.dataset.hasManyIndex || parent.querySelectorAll(this.options.item).length - 1
66
+ parent.setAttribute('data-has-many-index', ++index)
65
67
 
66
- const regex = new RegExp(el.dataset.placeholder, 'g')
67
- const html = el.dataset.html.replaceAll(regex, index)
68
- const newNode = document.createElement('div')
69
- newNode.innerHTML = html
68
+ const regex = new RegExp(el.dataset.placeholder, 'g')
69
+ const html = el.dataset.html.replace(regex, index)
70
+ const newNode = document.createElement('div')
71
+ newNode.innerHTML = html
70
72
 
71
- const fieldset = newNode.firstElementChild
72
- const $list = this.element.querySelector(this.options.list)
73
+ const fieldset = newNode.firstElementChild
74
+ const $list = this.element.querySelector(this.options.list)
73
75
 
74
- $list.appendChild(fieldset)
75
- this._bindEvents(fieldset)
76
- this._recomputePosition()
76
+ $list.appendChild(fieldset)
77
+ this._bindEvents(fieldset)
78
+ this._recomputePosition()
77
79
 
78
- const addAfterEvent = this.events.addAfter
79
- addAfterEvent.detail = { fieldset, parent }
80
- return parent.dispatchEvent(this.events.addAfter)
81
- }
82
- })
80
+ const addAfterEvent = this.events.addAfter
81
+ addAfterEvent.detail = { fieldset, parent }
82
+ return parent.dispatchEvent(this.events.addAfter)
83
+ }
83
84
  }
84
85
 
85
86
  _bindRemoveEvent (el) {
86
- el.addEventListener('click', (e) => {
87
- const el = e.target
88
- const parent = this.element
89
- const fieldset = el.closest(this.options.item)
90
- const removeBeforeEvent = this.events.removeBefore
91
- const removeAfterEvent = this.events.removeAfter
92
-
93
- e.preventDefault()
94
- this._recomputePosition()
87
+ el.addEventListener('click', (e) => this._bindRemoveEventCallBack(e))
88
+ }
95
89
 
96
- removeBeforeEvent.detail = { fieldset, parent }
97
- removeAfterEvent.detail = { fieldset, parent }
98
- parent.dispatchEvent(removeBeforeEvent)
99
- fieldset.remove()
100
- return parent.dispatchEvent(removeAfterEvent)
101
- })
90
+ _bindRemoveEventCallBack (e) {
91
+ const el = e.target
92
+ const parent = this.element
93
+ const fieldset = el.closest(this.options.item)
94
+ const removeBeforeEvent = this.events.removeBefore
95
+ const removeAfterEvent = this.events.removeAfter
96
+
97
+ e.preventDefault()
98
+ this._recomputePosition()
99
+
100
+ removeBeforeEvent.detail = { fieldset, parent }
101
+ removeAfterEvent.detail = { fieldset, parent }
102
+ parent.dispatchEvent(removeBeforeEvent)
103
+ fieldset.remove()
104
+ return parent.dispatchEvent(removeAfterEvent)
102
105
  }
103
106
 
104
107
  _bindEvents (el) {
@@ -38,17 +38,19 @@ class HeaderToggler {
38
38
  this._add()
39
39
  }
40
40
 
41
- this.element.addEventListener('click', function (e) {
42
- e.preventDefault()
43
- if ($container.classList.contains(_self.options.activeClass)) {
44
- _self._remove()
45
- } else {
46
- _self._add()
47
- }
48
- })
41
+ this.element.addEventListener('click', (e) => this._clickCallback(e, $container, _self))
49
42
 
50
43
  adminterface.addObserver(this.element, this, this.constructor.name)
51
44
  }
45
+
46
+ _clickCallback (e, $container, _self) {
47
+ e.preventDefault()
48
+ if ($container.classList.contains(_self.options.activeClass)) {
49
+ _self._remove()
50
+ } else {
51
+ _self._add()
52
+ }
53
+ }
52
54
  }
53
55
 
54
56
  export default HeaderToggler
@@ -1,5 +1,5 @@
1
1
  <!DOCTYPE html>
2
- <html lang="<%=I18n.locale%>">
2
+ <html lang="<%=I18n.locale%>" dir="<%= ActiveAdmin.application.lang_dir(self) %>">
3
3
  <head>
4
4
  <meta http-equiv="Content-type" content="text/html; charset=utf-8">
5
5
  <title><%= [@page_title, ActiveAdmin.application.site_title(self)].compact.join(" | ") %></title>
@@ -10,7 +10,11 @@ module Adminterface
10
10
  }.call.freeze
11
11
 
12
12
  module Configurable
13
- attr_writer :components, :css_classes, :comments_pager, :comments_per_page, :pager, :comments_input
13
+ attr_writer :lang_dir, :components, :css_classes, :comments_pager, :comments_per_page, :pager, :comments_input
14
+
15
+ def lang_dir(...)
16
+ @lang_dir || namespace.lang_dir(...)
17
+ end
14
18
 
15
19
  def components
16
20
  namespace.components.deep_merge(@components || {})
@@ -11,12 +11,5 @@ module Adminterface
11
11
  include ::Adminterface::Initializers::ViewHelpers
12
12
  include ::Adminterface::Initializers::Views
13
13
  include ::Adminterface::Initializers::Comments
14
-
15
- config.after_initialize do
16
- unless defined?(::Rails::Generators) || defined?(::Rails::Console) ||
17
- File.basename($0).eql?("rake")
18
- ::Adminterface::License.verify!
19
- end
20
- end
21
14
  end
22
15
  end