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.
- checksums.yaml +4 -4
- data/LICENSE.md +19 -109
- data/README.md +33 -27
- data/Rakefile +1 -1
- data/app/assets/stylesheets/adminterface/components/_form.scss +1 -0
- data/app/assets/stylesheets/adminterface/mixins/_utilities.scss +4 -0
- data/app/assets/stylesheets/adminterface/vendors/bootstrap/_all.scss +1 -0
- data/app/assets/stylesheets/adminterface/vendors/bootstrap/components/_breadcrumb.scss +1 -0
- data/app/assets/stylesheets/adminterface/vendors/flatpickr/{main.scss → _main.scss} +6 -0
- data/app/javascript/adminterface/initializers/header_toggler.js +1 -1
- data/app/javascript/adminterface/initializers/plugins/flatpickr.js +1 -1
- data/app/javascript/adminterface/initializers/plugins/input_counter.js +1 -1
- data/app/javascript/adminterface/initializers/plugins/password_visibility.js +1 -1
- data/app/javascript/adminterface/initializers/plugins/tom_select.js +1 -1
- data/app/javascript/adminterface/lib/__tests__/checkbox_toggler.html +5 -0
- data/app/javascript/adminterface/lib/__tests__/checkbox_toggler.spec.js +110 -0
- data/app/javascript/adminterface/lib/__tests__/detached_dropdown.spec.js +69 -70
- data/app/javascript/adminterface/lib/__tests__/filters.spec.js +0 -1
- data/app/javascript/adminterface/lib/__tests__/has_many.html +14 -0
- data/app/javascript/adminterface/lib/__tests__/has_many.spec.js +224 -0
- data/app/javascript/adminterface/lib/__tests__/header_toggler.html +2 -0
- data/app/javascript/adminterface/lib/__tests__/header_toggler.spec.js +212 -0
- data/app/javascript/adminterface/lib/__tests__/per_page.spec.js +75 -0
- data/app/javascript/adminterface/lib/__tests__/utils.spec.js +0 -1
- data/app/javascript/adminterface/lib/has_many.js +41 -38
- data/app/javascript/adminterface/lib/header_toggler.js +10 -8
- data/app/views/layouts/active_admin_logged_out.html.erb +1 -1
- data/lib/adminterface/configs.rb +5 -1
- data/lib/adminterface/engine.rb +0 -7
- data/lib/adminterface/extensions/base_controller.rb +16 -12
- data/lib/adminterface/extensions/namespace_settings.rb +3 -0
- data/lib/adminterface/extensions/view_helpers/alert_helper.rb +3 -1
- data/lib/adminterface/extensions/views/components/dropdown_menu.rb +4 -1
- data/lib/adminterface/extensions/views/components/menu_item.rb +8 -3
- data/lib/adminterface/extensions/views/components/tabs.rb +38 -13
- data/lib/adminterface/extensions/views/header.rb +2 -1
- data/lib/adminterface/extensions/views/pages/base.rb +4 -1
- data/lib/adminterface/initializers/configurations.rb +1 -1
- data/lib/adminterface/version.rb +1 -1
- data/lib/adminterface.rb +0 -31
- data/lib/generators/adminterface/comments/comments_generator.rb +4 -0
- data/lib/generators/adminterface/configs/configs_generator.rb +2 -2
- data/lib/generators/adminterface/install/install_generator.rb +21 -23
- data/lib/generators/adminterface/views/views_generator.rb +2 -2
- data/lib/generators/adminterface/webpacker/webpacker_generator.rb +20 -6
- metadata +16 -14
- data/lib/adminterface/encryption/encryptor.rb +0 -26
- data/lib/adminterface/license.rb +0 -18
- data/lib/adminterface/licensing/base.rb +0 -99
- data/lib/adminterface/licensing/commercial.rb +0 -7
- data/lib/adminterface/licensing/notice.rb +0 -54
- data/lib/adminterface/licensing/personal.rb +0 -13
- 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,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
|
+
})
|
@@ -51,54 +51,57 @@ class HasMany {
|
|
51
51
|
}
|
52
52
|
|
53
53
|
_bindAddEvent (el) {
|
54
|
-
el.addEventListener('click', (e) =>
|
55
|
-
|
56
|
-
const el = e.target
|
57
|
-
const parent = this.element
|
54
|
+
el.addEventListener('click', (e) => this._bindAddEventCallBack(e))
|
55
|
+
}
|
58
56
|
|
59
|
-
|
60
|
-
|
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
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
-
|
72
|
-
|
73
|
+
const fieldset = newNode.firstElementChild
|
74
|
+
const $list = this.element.querySelector(this.options.list)
|
73
75
|
|
74
|
-
|
75
|
-
|
76
|
-
|
76
|
+
$list.appendChild(fieldset)
|
77
|
+
this._bindEvents(fieldset)
|
78
|
+
this._recomputePosition()
|
77
79
|
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
-
|
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
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
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',
|
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>
|
data/lib/adminterface/configs.rb
CHANGED
@@ -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 || {})
|
data/lib/adminterface/engine.rb
CHANGED
@@ -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
|