unpoly-rails 0.23.0 → 0.24.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of unpoly-rails might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +68 -0
- data/dist/unpoly-bootstrap3.js +1 -1
- data/dist/unpoly-bootstrap3.min.js +1 -1
- data/dist/unpoly.css +18 -8
- data/dist/unpoly.js +498 -265
- data/dist/unpoly.min.css +1 -1
- data/dist/unpoly.min.js +3 -3
- data/lib/assets/javascripts/unpoly-bootstrap3/modal-ext.js.coffee +5 -2
- data/lib/assets/javascripts/unpoly/flow.js.coffee +3 -1
- data/lib/assets/javascripts/unpoly/form.js.coffee +3 -6
- data/lib/assets/javascripts/unpoly/layout.js.coffee +1 -1
- data/lib/assets/javascripts/unpoly/link.js.coffee +4 -2
- data/lib/assets/javascripts/unpoly/log.js.coffee +2 -2
- data/lib/assets/javascripts/unpoly/modal.js.coffee +125 -36
- data/lib/assets/javascripts/unpoly/motion.js.coffee +75 -37
- data/lib/assets/javascripts/unpoly/navigation.js.coffee +38 -26
- data/lib/assets/javascripts/unpoly/proxy.js.coffee +77 -52
- data/lib/assets/javascripts/unpoly/syntax.js.coffee +1 -0
- data/lib/assets/javascripts/unpoly/tooltip.js.coffee +2 -0
- data/lib/assets/javascripts/unpoly/util.js.coffee +138 -46
- data/lib/assets/stylesheets/unpoly/link.css.sass +1 -1
- data/lib/assets/stylesheets/unpoly/modal.css.sass +28 -15
- data/lib/unpoly/rails/version.rb +1 -1
- data/spec_app/Gemfile.lock +7 -7
- data/spec_app/spec/javascripts/helpers/reset_up.js.coffee +2 -0
- data/spec_app/spec/javascripts/helpers/to_contain.js.coffee +5 -0
- data/spec_app/spec/javascripts/up/flow_spec.js.coffee +2 -2
- data/spec_app/spec/javascripts/up/history_spec.js.coffee +6 -4
- data/spec_app/spec/javascripts/up/link_spec.js.coffee +114 -5
- data/spec_app/spec/javascripts/up/modal_spec.js.coffee +28 -18
- data/spec_app/spec/javascripts/up/motion_spec.js.coffee +112 -7
- data/spec_app/spec/javascripts/up/navigation_spec.js.coffee +157 -121
- data/spec_app/spec/javascripts/up/popup_spec.js.coffee +1 -1
- data/spec_app/spec/javascripts/up/proxy_spec.js.coffee +42 -3
- data/spec_app/spec/javascripts/up/syntax_spec.js.coffee +1 -2
- data/spec_app/spec/javascripts/up/util_spec.js.coffee +26 -1
- data/spec_app/vendor/assets/bower_components/jquery/.bower.json +1 -1
- metadata +3 -3
- data/spec_app/spec/javascripts/helpers/set_timer.js.coffee +0 -3
@@ -1,4 +1,6 @@
|
|
1
1
|
describe 'up.motion', ->
|
2
|
+
|
3
|
+
u = up.util
|
2
4
|
|
3
5
|
describe 'Javascript functions', ->
|
4
6
|
|
@@ -11,11 +13,11 @@ describe 'up.motion', ->
|
|
11
13
|
opacity = -> Number($element.css('opacity'))
|
12
14
|
up.animate($element, 'fade-in', duration: 200, easing: 'linear')
|
13
15
|
|
14
|
-
|
16
|
+
u.setTimer 0, ->
|
15
17
|
expect(opacity()).toBeAround(0.0, 0.25)
|
16
|
-
|
18
|
+
u.setTimer 100, ->
|
17
19
|
expect(opacity()).toBeAround(0.5, 0.25)
|
18
|
-
|
20
|
+
u.setTimer 200, ->
|
19
21
|
expect(opacity()).toBeAround(1.0, 0.25)
|
20
22
|
done()
|
21
23
|
|
@@ -42,6 +44,109 @@ describe 'up.motion', ->
|
|
42
44
|
up.animate($element, { 'font-size': '40px' }, duration: 10000, easing: 'linear')
|
43
45
|
expect($element.css('font-size')).toEqual('40px')
|
44
46
|
|
47
|
+
describe 'up.motion.finish', ->
|
48
|
+
|
49
|
+
describe 'when called with an element or selector', ->
|
50
|
+
|
51
|
+
it 'cancels an existing animation on the given element by instantly jumping to the last frame', ->
|
52
|
+
$element = affix('.element').text('content')
|
53
|
+
up.animate($element, { 'font-size': '40px', 'opacity': '0.33' }, duration: 10000)
|
54
|
+
up.motion.finish($element)
|
55
|
+
expect($element.css('font-size')).toEqual('40px')
|
56
|
+
expect($element.css('opacity')).toEqual('0.33')
|
57
|
+
|
58
|
+
it 'cancels animations on children of the given element', ->
|
59
|
+
$parent = affix('.element')
|
60
|
+
$child = $parent.affix('.child')
|
61
|
+
up.animate($child, { 'font-size': '40px' }, duration: 10000)
|
62
|
+
up.motion.finish($parent)
|
63
|
+
expect($child.css('font-size')).toEqual('40px')
|
64
|
+
|
65
|
+
it 'does not cancel animations on other elements', ->
|
66
|
+
$element1 = affix('.element1').text('content1')
|
67
|
+
$element2 = affix('.element2').text('content2')
|
68
|
+
up.animate($element1, 'fade-in', duration: 10000)
|
69
|
+
up.animate($element2, 'fade-in', duration: 10000)
|
70
|
+
up.motion.finish($element1)
|
71
|
+
expect(Number($element1.css('opacity'))).toEqual(1)
|
72
|
+
expect(Number($element2.css('opacity'))).toEqual(0, 0.1)
|
73
|
+
|
74
|
+
it 'restores existing transitions on the element', ->
|
75
|
+
$element = affix('.element').text('content')
|
76
|
+
$element.css('transition': 'font-size 3s ease')
|
77
|
+
oldTransition = $element.css('transition')
|
78
|
+
expect(oldTransition).toContain('font-size') # be paranoid
|
79
|
+
up.animate($element, 'fade-in', duration: 10000)
|
80
|
+
up.motion.finish($element)
|
81
|
+
expect(Number($element.css('opacity'))).toEqual(1)
|
82
|
+
currentTransition = $element.css('transition')
|
83
|
+
expect(currentTransition).toEqual(oldTransition)
|
84
|
+
expect(currentTransition).toContain('font-size')
|
85
|
+
expect(currentTransition).not.toContain('opacity')
|
86
|
+
expect(currentTransition).not.toContain('none')
|
87
|
+
expect(currentTransition).not.toContain('all')
|
88
|
+
|
89
|
+
it 'cancels an existing transition on the element by instantly jumping to the last frame', ->
|
90
|
+
$old = affix('.old').text('old content')
|
91
|
+
$new = affix('.new').text('new content')
|
92
|
+
|
93
|
+
up.morph($old, $new, 'cross-fade', duration: 2000)
|
94
|
+
expect($('.up-ghost').length).toBe(2)
|
95
|
+
|
96
|
+
up.motion.finish($old)
|
97
|
+
|
98
|
+
expect($('.up-ghost').length).toBe(0)
|
99
|
+
expect($old.css('display')).toEqual('none')
|
100
|
+
expect($new.css('display')).toEqual('block')
|
101
|
+
|
102
|
+
it 'can be called on either element involved in a transition', ->
|
103
|
+
$old = affix('.old').text('old content')
|
104
|
+
$new = affix('.new').text('new content')
|
105
|
+
|
106
|
+
up.morph($old, $new, 'cross-fade', duration: 2000)
|
107
|
+
expect($('.up-ghost').length).toBe(2)
|
108
|
+
|
109
|
+
up.motion.finish($new)
|
110
|
+
|
111
|
+
expect($('.up-ghost').length).toBe(0)
|
112
|
+
expect($old.css('display')).toEqual('none')
|
113
|
+
expect($new.css('display')).toEqual('block')
|
114
|
+
|
115
|
+
|
116
|
+
it 'cancels transitions on children of the given element', ->
|
117
|
+
$parent = affix('.parent')
|
118
|
+
$old = $parent.affix('.old').text('old content')
|
119
|
+
$new = $parent.affix('.new').text('new content')
|
120
|
+
|
121
|
+
up.morph($old, $new, 'cross-fade', duration: 2000)
|
122
|
+
expect($('.up-ghost').length).toBe(2)
|
123
|
+
|
124
|
+
up.motion.finish($parent)
|
125
|
+
|
126
|
+
expect($('.up-ghost').length).toBe(0)
|
127
|
+
expect($old.css('display')).toEqual('none')
|
128
|
+
expect($new.css('display')).toEqual('block')
|
129
|
+
|
130
|
+
describe 'when called without arguments', ->
|
131
|
+
|
132
|
+
it 'cancels all animations on the screen', ->
|
133
|
+
$element1 = affix('.element1').text('content1')
|
134
|
+
$element2 = affix('.element2').text('content2')
|
135
|
+
|
136
|
+
up.animate($element1, 'fade-in', duration: 3000)
|
137
|
+
up.animate($element2, 'fade-in', duration: 3000)
|
138
|
+
|
139
|
+
opacity = ($element) -> Number($element.css('opacity'))
|
140
|
+
|
141
|
+
expect(opacity($element1)).toBeAround(0.0, 0.1)
|
142
|
+
expect(opacity($element2)).toBeAround(0.0, 0.1)
|
143
|
+
|
144
|
+
up.motion.finish()
|
145
|
+
|
146
|
+
$element1 = $('.element1')
|
147
|
+
$element2 = $('.element2')
|
148
|
+
expect(opacity($element1)).toBe(1.0)
|
149
|
+
expect(opacity($element2)).toBe(1.0)
|
45
150
|
|
46
151
|
describe 'up.morph', ->
|
47
152
|
|
@@ -119,19 +224,19 @@ describe 'up.motion', ->
|
|
119
224
|
|
120
225
|
opacity = ($element) -> Number($element.css('opacity'))
|
121
226
|
|
122
|
-
|
227
|
+
u.setTimer 0, ->
|
123
228
|
expect(opacity($newGhost)).toBeAround(0.0, 0.25)
|
124
229
|
expect(opacity($oldGhost)).toBeAround(1.0, 0.25)
|
125
230
|
|
126
|
-
|
231
|
+
u.setTimer 80, ->
|
127
232
|
expect(opacity($newGhost)).toBeAround(0.4, 0.25)
|
128
233
|
expect(opacity($oldGhost)).toBeAround(0.6, 0.25)
|
129
234
|
|
130
|
-
|
235
|
+
u.setTimer 140, ->
|
131
236
|
expect(opacity($newGhost)).toBeAround(0.7, 0.25)
|
132
237
|
expect(opacity($oldGhost)).toBeAround(0.3, 0.25)
|
133
238
|
|
134
|
-
|
239
|
+
u.setTimer 250, ->
|
135
240
|
# Once our two ghosts have rendered their visual effect,
|
136
241
|
# we remove them from the DOM.
|
137
242
|
expect($newGhost).not.toBeInDOM()
|
@@ -1,132 +1,168 @@
|
|
1
1
|
describe 'up.navigation', ->
|
2
|
-
|
2
|
+
|
3
|
+
u = up.util
|
4
|
+
|
5
|
+
beforeEach ->
|
6
|
+
up.modal.config.openAnimation = 'none'
|
7
|
+
up.modal.config.closeAnimation = 'none'
|
8
|
+
|
3
9
|
describe 'unobtrusive behavior', ->
|
4
10
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
expect($unrelatedLink).not.toHaveClass('up-current')
|
78
|
-
|
79
|
-
up.modal.close().then ->
|
11
|
+
describe '.up-current', ->
|
12
|
+
|
13
|
+
it 'marks a link as .up-current if it links to the current URL', ->
|
14
|
+
spyOn(up.browser, 'url').and.returnValue('/foo')
|
15
|
+
$currentLink = up.hello(affix('a[href="/foo"]'))
|
16
|
+
$otherLink = up.hello(affix('a[href="/bar"]'))
|
17
|
+
expect($currentLink).toHaveClass('up-current')
|
18
|
+
expect($otherLink).not.toHaveClass('up-current')
|
19
|
+
|
20
|
+
it 'marks any link as .up-current if its up-href attribute matches the current URL', ->
|
21
|
+
spyOn(up.browser, 'url').and.returnValue('/foo')
|
22
|
+
$currentLink = up.hello(affix('span[up-href="/foo"]'))
|
23
|
+
$otherLink = up.hello(affix('span[up-href="/bar"]'))
|
24
|
+
expect($currentLink).toHaveClass('up-current')
|
25
|
+
expect($otherLink).not.toHaveClass('up-current')
|
26
|
+
|
27
|
+
it 'marks any link as .up-current if any of its space-separated up-alias values matches the current URL', ->
|
28
|
+
spyOn(up.browser, 'url').and.returnValue('/foo')
|
29
|
+
$currentLink = up.hello(affix('a[href="/x"][up-alias="/aaa /foo /bbb"]'))
|
30
|
+
$otherLink = up.hello(affix('a[href="/y"][up-alias="/bar"]'))
|
31
|
+
expect($currentLink).toHaveClass('up-current')
|
32
|
+
expect($otherLink).not.toHaveClass('up-current')
|
33
|
+
|
34
|
+
it 'does not throw if the current location does not match an up-alias wildcard (bugfix)', ->
|
35
|
+
inserter = -> up.hello(affix('a[up-alias="/qqqq*"]'))
|
36
|
+
expect(inserter).not.toThrow()
|
37
|
+
|
38
|
+
it 'does not highlight a link to "#" (commonly used for JS-only buttons)', ->
|
39
|
+
$link = up.hello(affix('a[href="#"]'))
|
40
|
+
expect($link).not.toHaveClass('up-current')
|
41
|
+
|
42
|
+
it 'marks URL prefixes as .up-current if an up-alias value ends in *', ->
|
43
|
+
spyOn(up.browser, 'url').and.returnValue('/foo/123')
|
44
|
+
$currentLink = up.hello(affix('a[href="/x"][up-alias="/aaa /foo/* /bbb"]'))
|
45
|
+
$otherLink = up.hello(affix('a[href="/y"][up-alias="/bar"]'))
|
46
|
+
expect($currentLink).toHaveClass('up-current')
|
47
|
+
expect($otherLink).not.toHaveClass('up-current')
|
48
|
+
|
49
|
+
it 'allows to configure a custom "current" class, but always also sets .up-current', ->
|
50
|
+
up.navigation.config.currentClasses = ['highlight']
|
51
|
+
spyOn(up.browser, 'url').and.returnValue('/foo')
|
52
|
+
$currentLink = up.hello(affix('a[href="/foo"]'))
|
53
|
+
expect($currentLink).toHaveClass('highlight up-current')
|
54
|
+
|
55
|
+
describeCapability 'canPushState', ->
|
56
|
+
|
57
|
+
it 'marks a link as .up-current if it links to the current URL, but is missing a trailing slash', ->
|
58
|
+
$link = affix('a[href="/foo"][up-target=".main"]')
|
59
|
+
affix('.main')
|
60
|
+
$link.click()
|
61
|
+
@respondWith
|
62
|
+
responseHeaders: { 'X-Up-Location': '/foo/' }
|
63
|
+
responseText: '<div class="main">new-text</div>'
|
64
|
+
expect($link).toHaveClass('up-current')
|
65
|
+
|
66
|
+
it 'marks a link as .up-current if it links to the current URL, but has an extra trailing slash', ->
|
67
|
+
$link = affix('a[href="/foo/"][up-target=".main"]')
|
68
|
+
affix('.main')
|
69
|
+
$link.click()
|
70
|
+
@respondWith
|
71
|
+
responseHeaders: { 'X-Up-Location': '/foo' }
|
72
|
+
responseText: '<div class="main">new-text</div>'
|
73
|
+
expect($link).toHaveClass('up-current')
|
74
|
+
|
75
|
+
it 'marks a link as .up-current if it links to an URL currently shown either within or below the modal', (done) ->
|
76
|
+
up.history.replace('/foo')
|
77
|
+
$backgroundLink = affix('a[href="/foo"]')
|
78
|
+
$modalLink = affix('a[href="/bar"][up-modal=".main"]')
|
79
|
+
$unrelatedLink = affix('a[href="/baz]')
|
80
|
+
|
81
|
+
Trigger.click($modalLink)
|
82
|
+
@respondWith('<div class="main">new-text</div>')
|
80
83
|
expect($backgroundLink).toHaveClass('up-current')
|
81
|
-
expect($modalLink).
|
84
|
+
expect($modalLink).toHaveClass('up-current')
|
82
85
|
expect($unrelatedLink).not.toHaveClass('up-current')
|
83
|
-
done()
|
84
86
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
87
|
+
up.modal.close().then ->
|
88
|
+
expect($backgroundLink).toHaveClass('up-current')
|
89
|
+
expect($modalLink).not.toHaveClass('up-current')
|
90
|
+
expect($unrelatedLink).not.toHaveClass('up-current')
|
91
|
+
done()
|
90
92
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
93
|
+
it 'marks a link as .up-current if it links to the URL currently either within or below the popup', (done) ->
|
94
|
+
up.history.replace('/foo')
|
95
|
+
$backgroundLink = affix('a[href="/foo"]')
|
96
|
+
$popupLink = affix('a[href="/bar"][up-popup=".main"]')
|
97
|
+
$unrelatedLink = affix('a[href="/baz]')
|
96
98
|
|
97
|
-
|
99
|
+
$popupLink.click()
|
100
|
+
@respondWith('<div class="main">new-text</div>')
|
98
101
|
expect($backgroundLink).toHaveClass('up-current')
|
99
|
-
expect($popupLink).
|
102
|
+
expect($popupLink).toHaveClass('up-current')
|
100
103
|
expect($unrelatedLink).not.toHaveClass('up-current')
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
104
|
+
|
105
|
+
up.popup.close().then ->
|
106
|
+
expect($backgroundLink).toHaveClass('up-current')
|
107
|
+
expect($popupLink).not.toHaveClass('up-current')
|
108
|
+
expect($unrelatedLink).not.toHaveClass('up-current')
|
109
|
+
done()
|
110
|
+
|
111
|
+
it 'changes .up-current marks as the URL changes'
|
112
|
+
|
113
|
+
describe '.up-active', ->
|
114
|
+
|
115
|
+
describeCapability 'canPushState', ->
|
116
|
+
|
117
|
+
it 'marks clicked links as .up-active until the request finishes', ->
|
118
|
+
$link = affix('a[href="/foo"][up-target=".main"]')
|
119
|
+
affix('.main')
|
120
|
+
$link.click()
|
121
|
+
expect($link).toHaveClass('up-active')
|
122
|
+
@respondWith('<div class="main">new-text</div>')
|
123
|
+
expect($link).not.toHaveClass('up-active')
|
124
|
+
|
125
|
+
it 'marks links with [up-instant] on mousedown as .up-active until the request finishes', ->
|
126
|
+
$link = affix('a[href="/foo"][up-instant][up-target=".main"]')
|
127
|
+
affix('.main')
|
128
|
+
Trigger.mousedown($link)
|
129
|
+
expect($link).toHaveClass('up-active')
|
130
|
+
@respondWith('<div class="main">new-text</div>')
|
131
|
+
expect($link).not.toHaveClass('up-active')
|
132
|
+
|
133
|
+
it 'prefers to mark an enclosing [up-expand] click area', ->
|
134
|
+
$area = affix('div[up-expand] a[href="/foo"][up-target=".main"]')
|
135
|
+
up.hello($area)
|
136
|
+
$link = $area.find('a')
|
137
|
+
affix('.main')
|
138
|
+
$link.click()
|
139
|
+
expect($link).not.toHaveClass('up-active')
|
140
|
+
expect($area).toHaveClass('up-active')
|
141
|
+
@respondWith('<div class="main">new-text</div>')
|
142
|
+
expect($area).not.toHaveClass('up-active')
|
143
|
+
|
144
|
+
it 'marks clicked modal openers as .up-active while the modal is loading', ->
|
145
|
+
$link = affix('a[href="/foo"][up-modal=".main"]')
|
146
|
+
affix('.main')
|
147
|
+
$link.click()
|
148
|
+
expect($link).toHaveClass('up-active')
|
149
|
+
@respondWith('<div class="main">new-text</div>')
|
150
|
+
expect($link).not.toHaveClass('up-active')
|
151
|
+
|
152
|
+
it 'removes .up-active from a clicked modal opener if the target is already preloaded (bugfix)', ->
|
153
|
+
$link = affix('a[href="/foo"][up-modal=".main"]')
|
154
|
+
up.proxy.preload($link)
|
155
|
+
@respondWith('<div class="main">new-text</div>')
|
156
|
+
$link.click()
|
157
|
+
expect('.up-modal .main').toHaveText('new-text')
|
158
|
+
expect($link).not.toHaveClass('up-active')
|
159
|
+
|
160
|
+
it 'removes .up-active from a clicked link if the target is already preloaded (bugfix)', ->
|
161
|
+
$link = affix('a[href="/foo"][up-target=".main"]')
|
162
|
+
affix('.main')
|
163
|
+
up.proxy.preload($link)
|
164
|
+
@respondWith('<div class="main">new-text</div>')
|
165
|
+
$link.click()
|
166
|
+
expect('.main').toHaveText('new-text')
|
167
|
+
expect($link).not.toHaveClass('up-active')
|
132
168
|
|
@@ -73,7 +73,7 @@ describe 'up.popup', ->
|
|
73
73
|
|
74
74
|
beforeEach ->
|
75
75
|
@$link = affix('a[href="/path"][up-popup=".target"]')
|
76
|
-
@attachSpy = up.popup.knife.mock('attach')
|
76
|
+
@attachSpy = up.popup.knife.mock('attach').and.returnValue(u.resolvedPromise())
|
77
77
|
@defaultSpy = up.link.knife.mock('allowDefault').and.callFake((event) -> event.preventDefault())
|
78
78
|
|
79
79
|
it 'opens the clicked link in a popup', ->
|