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