adminterface 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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