govuk_frontend_toolkit 1.4.0 → 1.5.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.
data/README.md CHANGED
@@ -49,6 +49,19 @@ conditionals and typography mixins you should add:
49
49
  @import '_conditionals';
50
50
  @import '_typography';
51
51
 
52
+ ## Updating the version of the toolkit that's included with the gem
53
+
54
+ 1. Find the commit of [the toolkit][govuk_frontend_toolkit] you want to update to. In this
55
+ repository, `cd app/assets` and then run `git checkout <NEW TOOLKIT COMMIT>` to change
56
+ the git submodule
57
+ 2. Update the version number in `lib/govuk_frontend_toolkit/version.rb`
58
+ 3. Commit those two changes together and push them.
59
+
60
+ Do not create a version tag in this repository - that's handled automatically by the
61
+ job that publishes the gem to RubyGems.
62
+
52
63
  ## Licence
53
64
 
54
65
  Released under the MIT Licence, a copy of which can be found in the file `LICENCE`.
66
+
67
+ [govuk_frontend_toolkit]: https://github.com/alphagov/govuk_frontend_toolkit
@@ -7,8 +7,8 @@ module.exports = function(grunt) {
7
7
  'javascripts/**/*.js'
8
8
  ],
9
9
  options: {
10
- specs: 'spec/*Spec.js',
11
- helpers: 'spec/*Helper.js'
10
+ specs: 'spec/unit/*Spec.js',
11
+ helpers: 'spec/unit/*Helper.js'
12
12
  }
13
13
  }
14
14
  }
data/app/assets/README.md CHANGED
@@ -48,6 +48,12 @@ Install Node 0.8 or higher and PhantomJS.
48
48
  $ npm install
49
49
  $ npm test
50
50
 
51
+ ### Using the local test runner
52
+
53
+ The test suite can be run by opening the `./spec/support/LocalTestRunner.html` file in a browser for a more detailed trace of errors.
54
+
55
+ The files for unit tests and any supporting JavaScript should be added to `./spec/manifest.js` file.
56
+
51
57
  ## Usage
52
58
 
53
59
  At the top of a Sass file in your project you should use an `@import` rule
@@ -822,6 +828,24 @@ GOVUK.stickAtTopWhenScrolling.init();
822
828
  If you also include the `stopScrollingAtFooter` JavaScript this will also try
823
829
  and stop the elements before they get to the bottom.
824
830
 
831
+ ## Selection buttons
832
+
833
+ Script to support a specific design of radio buttons and checkboxes wrapped in `<label>` tags:
834
+
835
+ <label>
836
+ <input type="radio" name="size" value="medium" />
837
+ </label>
838
+
839
+ When the input is focused or its `checked` attribute is set, classes are added to their parent labels so their styling can show this.
840
+
841
+ To apply this behaviour to elements with the above HTML pattern, call the `GOVUK.selectionButtons` function with their inputs:
842
+
843
+ ```
844
+ var $buttons = $("label input[type='radio'], label input[type='checkbox']");
845
+ GOVUK.selectionButtons($buttons);
846
+ ```
847
+
848
+ Note that `GOVUK.selectionButtons` and the constructors it wraps, `GOVUK.RadioButtons` and `GOVUK.CheckboxButtons` use the `bind.js` polyfill.
825
849
 
826
850
  ## Licence
827
851
 
@@ -0,0 +1,139 @@
1
+ (function () {
2
+ "use strict"
3
+ var root = this,
4
+ $ = root.jQuery;
5
+
6
+ if (typeof GOVUK === 'undefined') { root.GOVUK = {}; }
7
+
8
+ var BaseButtons = function ($elms, opts) {
9
+ this.$elms = $elms;
10
+ this.selectedClass = 'selected';
11
+ this.focusedClass = 'focused';
12
+ if (opts !== undefined) {
13
+ $.each(opts, function (optionName, optionObj) {
14
+ this[optionName] = optionObj;
15
+ }.bind(this));
16
+ }
17
+ this.setEventNames();
18
+ this.getSelections();
19
+ this.bindEvents();
20
+ };
21
+ BaseButtons.prototype.setEventNames = function () {
22
+ this.selectionEvents = 'click';
23
+ this.focusEvents = 'focus blur';
24
+ };
25
+ BaseButtons.prototype.markFocused = function ($elm, state) {
26
+ var elmId = $elm.attr('id');
27
+
28
+ if (state === 'focused') {
29
+ $elm.parent('label').addClass(this.focusedClass);
30
+ } else {
31
+ $elm.parent('label').removeClass(this.focusedClass);
32
+ }
33
+ };
34
+ BaseButtons.prototype.bindEvents = function () {
35
+ var selectionEventHandler = this.markSelected.bind(this),
36
+ focusEventHandler = this.markFocused.bind(this);
37
+
38
+ this.$elms
39
+ .on(this.selectionEvents, function (e) {
40
+ selectionEventHandler($(e.target));
41
+ })
42
+ .on(this.focusEvents, function (e) {
43
+ var state = (e.type === 'focus') ? 'focused' : 'blurred';
44
+
45
+ focusEventHandler($(e.target), state);
46
+ });
47
+ };
48
+
49
+ var RadioButtons = function ($elms, opts) {
50
+ BaseButtons.apply(this, arguments);
51
+ };
52
+ RadioButtons.prototype.setEventNames = function () {
53
+ // some browsers fire the 'click' when the selected radio changes by keyboard
54
+ this.selectionEvents = 'click change';
55
+ this.focusEvents = 'focus blur';
56
+ };
57
+ RadioButtons.prototype.getSelections = function () {
58
+ var selectionEventHandler = this.markSelected.bind(this),
59
+ selections = {};
60
+
61
+ $.each(this.$elms, function (index, elm) {
62
+ var $elm = $(elm),
63
+ radioName = $elm.attr('name');
64
+
65
+ if (typeof selections[radioName] === 'undefined') {
66
+ selections[radioName] = false;
67
+ }
68
+ if ($elm.is(':checked')) {
69
+ selectionEventHandler($elm);
70
+ selections[radioName] = $elm;
71
+ }
72
+ });
73
+ this.selections = selections;
74
+ };
75
+ RadioButtons.prototype.bindEvents = function () {
76
+ BaseButtons.prototype.bindEvents.call(this);
77
+ };
78
+ RadioButtons.prototype.markSelected = function ($elm) {
79
+ var radioName = $elm.attr('name'),
80
+ $previousSelection = this.selections[radioName];
81
+
82
+ if ($previousSelection) {
83
+ $previousSelection.parent('label').removeClass(this.selectedClass);
84
+ }
85
+ $elm.parent('label').addClass(this.selectedClass);
86
+ this.selections[radioName] = $elm;
87
+ };
88
+ RadioButtons.prototype.markFocused = function ($elm) {
89
+ BaseButtons.prototype.markFocused.apply(this, arguments);
90
+ };
91
+
92
+ var CheckboxButtons = function ($elms, opts) {
93
+ BaseButtons.apply(this, arguments);
94
+ };
95
+ CheckboxButtons.prototype.setEventNames = function () {
96
+ BaseButtons.prototype.setEventNames.call(this);
97
+ };
98
+ CheckboxButtons.prototype.getSelections = function () {
99
+ var selectionEventHandler = this.markSelected.bind(this);
100
+
101
+ this.$elms.each(function (idx, elm) {
102
+ var $elm = $(elm);
103
+
104
+ if ($elm.is(':checked')) {
105
+ selectionEventHandler($elm);
106
+ }
107
+ });
108
+ };
109
+ CheckboxButtons.prototype.bindEvents = function () {
110
+ BaseButtons.prototype.bindEvents.call(this);
111
+ };
112
+ CheckboxButtons.prototype.markSelected = function ($elm) {
113
+ if ($elm.is(':checked')) {
114
+ $elm.parent('label').addClass(this.selectedClass);
115
+ } else {
116
+ $elm.parent('label').removeClass(this.selectedClass);
117
+ }
118
+ };
119
+ CheckboxButtons.prototype.markFocused = function ($elm) {
120
+ BaseButtons.prototype.markFocused.apply(this, arguments);
121
+ };
122
+
123
+ root.GOVUK.RadioButtons = RadioButtons;
124
+ root.GOVUK.CheckboxButtons = CheckboxButtons;
125
+
126
+ var selectionButtons = function ($elms, opts) {
127
+ var $radios = $elms.filter('[type=radio]'),
128
+ $checkboxes = $elms.filter('[type=checkbox]');
129
+
130
+ if ($radios) {
131
+ new GOVUK.RadioButtons($radios, opts);
132
+ }
133
+ if ($checkboxes) {
134
+ new GOVUK.CheckboxButtons($checkboxes, opts);
135
+ }
136
+ };
137
+
138
+ root.GOVUK.selectionButtons = selectionButtons;
139
+ }).call(this);
@@ -0,0 +1,40 @@
1
+ // Function.prototype.bind
2
+ //
3
+ // A polyfill for Function.prototype.bind. Which lets you bind a defined
4
+ // value to the `this` keyword in a function call.
5
+ //
6
+ // Bind is natively supported in:
7
+ // IE9+
8
+ // Chrome 7+
9
+ // Firefox 4+
10
+ // Safari 5.1.4+
11
+ // iOS 6+
12
+ // Android Browser 4+
13
+ // Chrome for Android 0.16+
14
+ //
15
+ // Originally from:
16
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
17
+ if (!Function.prototype.bind) {
18
+ Function.prototype.bind = function (oThis) {
19
+ if (typeof this !== "function") {
20
+ // closest thing possible to the ECMAScript 5
21
+ // internal IsCallable function
22
+ throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
23
+ }
24
+
25
+ var aArgs = Array.prototype.slice.call(arguments, 1),
26
+ fToBind = this,
27
+ fNOP = function () {},
28
+ fBound = function () {
29
+ return fToBind.apply(this instanceof fNOP && oThis
30
+ ? this
31
+ : oThis,
32
+ aArgs.concat(Array.prototype.slice.call(arguments)));
33
+ };
34
+
35
+ fNOP.prototype = this.prototype;
36
+ fBound.prototype = new fNOP();
37
+
38
+ return fBound;
39
+ };
40
+ }
@@ -0,0 +1,341 @@
1
+ describe("selection-buttons", function () {
2
+ var $radioButtons,
3
+ $radioLabels,
4
+ $checkboxButtons,
5
+ $checkboxLabels;
6
+
7
+ beforeEach(function () {
8
+ $radioLabels = $(
9
+ '<label class="selectable">' +
10
+ 'Small' +
11
+ '<input type="radio" name="size" id="small" value="small" />' +
12
+ '</label>' +
13
+ '<label class="selectable">' +
14
+ 'Medium' +
15
+ '<input type="radio" name="size" id="medium" value="medium" />' +
16
+ '</label>' +
17
+ '<label class="selectable">' +
18
+ 'Large' +
19
+ '<input type="radio" name="size" id="large" value="large" />' +
20
+ '</label>'
21
+ );
22
+ $checkboxLabels = $(
23
+ '<label class="selectable">' +
24
+ 'Eggs' +
25
+ '<input id="eggs" name="food" value="eggs" type="checkbox" />' +
26
+ '</label>' +
27
+ '<label class="selectable">' +
28
+ 'Bread' +
29
+ '<input id="bread" name="food" value="bread" type="checkbox" />' +
30
+ '</label>' +
31
+ '<label class="selectable">' +
32
+ 'Fruit' +
33
+ '<input id="fruit" name="food" value="fruit" type="checkbox" />' +
34
+ '</label>'
35
+ );
36
+ $radioButtons = $radioLabels.find('input');
37
+ $checkboxButtons = $checkboxLabels.find('input');
38
+ $(document.body).append($radioLabels);
39
+ $(document.body).append($checkboxLabels);
40
+ });
41
+
42
+ afterEach(function () {
43
+ $radioLabels.remove();
44
+ $checkboxLabels.remove();
45
+ });
46
+
47
+ describe("RadioButtons", function () {
48
+ it("Should create a new instance with the correct interface", function () {
49
+ var buttons = new GOVUK.RadioButtons($radioButtons);
50
+
51
+ expect(buttons.getSelections).toBeDefined();
52
+ expect(buttons.bindEvents).toBeDefined();
53
+ expect(buttons.markSelected).toBeDefined();
54
+ expect(buttons.markFocused).toBeDefined();
55
+ });
56
+
57
+ it("Should set the selectedClass property if sent in as an option", function () {
58
+ var buttons = new GOVUK.RadioButtons($radioButtons, { 'selectedClass' : 'selectable-selected' });
59
+
60
+ expect(buttons.selectedClass).toEqual('selectable-selected');
61
+ });
62
+
63
+ it("Should set the focusedClass property if sent in as an option", function () {
64
+ var buttons = new GOVUK.RadioButtons($radioButtons, { 'focusedClass' : 'selectable-focused' });
65
+
66
+ expect(buttons.focusedClass).toEqual('selectable-focused');
67
+ });
68
+
69
+ describe("getSelections method", function () {
70
+ it("Should mark the label of any checked radios as selected", function () {
71
+ var radioButtonsMock = {
72
+ 'markSelected' : function () {},
73
+ '$elms' : $radioButtons
74
+ };
75
+
76
+ $radioButtons.eq(0).attr('checked', true);
77
+ spyOn(radioButtonsMock, 'markSelected');
78
+ GOVUK.RadioButtons.prototype.getSelections.call(radioButtonsMock);
79
+ expect(radioButtonsMock.markSelected).toHaveBeenCalled();
80
+ });
81
+ });
82
+
83
+ describe("setEventNames method", function () {
84
+ it("Should set the selectionEvents and focusEvents properties on the instance", function () {
85
+ var radioButtonsMock = {};
86
+
87
+ GOVUK.RadioButtons.prototype.setEventNames.call(radioButtonsMock);
88
+ expect(typeof radioButtonsMock.focusEvents !== 'undefined').toBe(true);
89
+ expect(typeof radioButtonsMock.selectionEvents !== 'undefined').toBe(true);
90
+ });
91
+ });
92
+
93
+ describe("bindEvents method", function () {
94
+ it("Should bind click and change events to each radio", function () {
95
+ var radioButtonsMock = {
96
+ '$elms' : $radioButtons,
97
+ 'selectionEvents' : 'click change',
98
+ 'focusEvents' : 'focus blur',
99
+ 'markSelected' : function () {},
100
+ 'markFocused' : function () {}
101
+ },
102
+ eventsBound = false;
103
+
104
+ spyOn($.fn, 'on').andCallFake(function (evt, func) {
105
+ if (evt === 'click change') {
106
+ eventsBound = true;
107
+ }
108
+ return $.fn;
109
+ });
110
+ expect($.fn.on.calls.length).toEqual(0);
111
+ GOVUK.RadioButtons.prototype.bindEvents.call(radioButtonsMock);
112
+ expect($.fn.on).toHaveBeenCalled();
113
+ expect(eventsBound).toEqual(true);
114
+ });
115
+
116
+ it("Should call the markSelected method on any checked radio that's the target of an event", function () {
117
+ var radioButtonsMock = {
118
+ '$elms' : $radioButtons,
119
+ 'selectionEvents' : 'click change',
120
+ 'focusEvents' : 'focus blur',
121
+ 'markSelected' : function () {},
122
+ 'markFocused' : function () {}
123
+ },
124
+ eventsBound = false;
125
+
126
+ spyOn($.fn, 'on').andCallFake(function (evt, func) {
127
+ if (evt === 'click change') {
128
+ callback = func;
129
+ }
130
+ return $.fn;
131
+ });
132
+ spyOn(radioButtonsMock, 'markSelected');
133
+ radioButtonsMock.$elms.eq(0).attr('checked', true);
134
+ GOVUK.RadioButtons.prototype.bindEvents.call(radioButtonsMock);
135
+ callback({ 'target' : radioButtonsMock.$elms[0] });
136
+ expect(radioButtonsMock.markSelected).toHaveBeenCalled();
137
+ });
138
+ });
139
+
140
+ describe("markSelected method", function () {
141
+ it("Should add the selectedClass class to the label of the sent in radio", function () {
142
+ var radioButtonsMock = {
143
+ 'selections' : {
144
+ 'size' : false
145
+ },
146
+ 'selectedClass' : 'selected'
147
+ },
148
+ $clickedRadio = $radioButtons.eq(0);
149
+
150
+ GOVUK.RadioButtons.prototype.markSelected.call(radioButtonsMock, $clickedRadio);
151
+ expect($clickedRadio.parent('label').hasClass('selected')).toEqual(true);
152
+ });
153
+
154
+ it("Should remove the selectedClass class from the label of the previously selected radio", function () {
155
+ var radioButtonsMock = {
156
+ 'selections' : {
157
+ 'size' : $radioButtons.eq(1)
158
+ },
159
+ 'selectedClass' : 'selected'
160
+ },
161
+ $clickedRadio = $radioButtons.eq(0);
162
+
163
+ $radioLabels.eq(1).addClass('selected');
164
+ GOVUK.RadioButtons.prototype.markSelected.call(radioButtonsMock, $clickedRadio);
165
+ expect($('#medium').parent('label').hasClass('selected')).toEqual(false);
166
+
167
+ });
168
+ });
169
+
170
+ describe("markFocused method", function () {
171
+ var radioButtonsMock = {
172
+ 'focused' : false,
173
+ 'focusedClass' : 'focused'
174
+ };
175
+
176
+ it("Should add the focusedClass class to the sent radio if it is focused", function () {
177
+ GOVUK.RadioButtons.prototype.markFocused.apply(radioButtonsMock, [$radioButtons.eq(0), 'focused']);
178
+
179
+ expect($radioLabels.eq(0).hasClass(radioButtonsMock.focusedClass)).toBe(true);
180
+ });
181
+
182
+ it("Should remove the focusedClass class from the sent radio if it is blurred", function () {
183
+ $radioLabels.eq(0).addClass(radioButtonsMock.focusedClass);
184
+ GOVUK.RadioButtons.prototype.markFocused.apply(radioButtonsMock, [$radioButtons.eq(0), 'blurred']);
185
+
186
+ expect($radioLabels.eq(0).hasClass(radioButtonsMock.focusedClass)).toBe(false);
187
+ });
188
+ });
189
+ });
190
+
191
+ describe("CheckboxButtons", function () {
192
+ it("Should create a new instance with the correct interface", function () {
193
+ var buttons = new GOVUK.CheckboxButtons($checkboxButtons);
194
+
195
+ expect(buttons.getSelections).toBeDefined();
196
+ expect(buttons.bindEvents).toBeDefined();
197
+ expect(buttons.markSelected).toBeDefined();
198
+ expect(buttons.markFocused).toBeDefined();
199
+ });
200
+
201
+ describe("getSelections method", function () {
202
+ it("Should add the selectedClass class to the label of a checkbox that is checked", function () {
203
+ var checkboxButtonsMock = {
204
+ '$elms' : $checkboxButtons,
205
+ 'markSelected' : function () {}
206
+ };
207
+
208
+ checkboxButtonsMock.$elms.eq(0).attr('checked', true);
209
+ spyOn(checkboxButtonsMock, 'markSelected');
210
+ GOVUK.CheckboxButtons.prototype.getSelections.call(checkboxButtonsMock);
211
+ expect(checkboxButtonsMock.markSelected).toHaveBeenCalled();
212
+ });
213
+ });
214
+
215
+ describe("setEventNames method", function () {
216
+ it("Should set the selectionEvents and focusEvents properties on the instance", function () {
217
+ var checkboxButtonsMock = {};
218
+
219
+ GOVUK.CheckboxButtons.prototype.setEventNames.call(checkboxButtonsMock);
220
+ expect(typeof checkboxButtonsMock.focusEvents !== 'undefined').toBe(true);
221
+ expect(typeof checkboxButtonsMock.selectionEvents !== 'undefined').toBe(true);
222
+ });
223
+ });
224
+
225
+ describe("bindEvents method", function () {
226
+ var checkboxButtonsMock;
227
+
228
+ beforeEach(function () {
229
+ checkboxButtonsMock = {
230
+ '$elms' : $checkboxButtons
231
+ };
232
+ });
233
+
234
+ it("Should add a click event to each checkbox that fires the markSelected method", function () {
235
+ var eventCalled = false;
236
+
237
+ checkboxButtonsMock.markSelected = function () {};
238
+ checkboxButtonsMock.markFocused = function () {};
239
+ checkboxButtonsMock.selectionEvents = 'click';
240
+ checkboxButtonsMock.focusEvents = 'focus blur';
241
+ spyOn(checkboxButtonsMock, 'markSelected');
242
+ spyOn($.fn, 'on').andCallFake(function (evt, func) {
243
+ if (evt === 'click') {
244
+ eventCalled = true;
245
+ callback = func;
246
+ }
247
+ return $.fn;
248
+ });
249
+ $checkboxButtons.eq(0).attr('checked', true);
250
+ GOVUK.CheckboxButtons.prototype.bindEvents.call(checkboxButtonsMock);
251
+ expect(eventCalled).toBe(true);
252
+ callback({ 'target' : $checkboxButtons.eq(0) });
253
+ expect(checkboxButtonsMock.markSelected).toHaveBeenCalled();
254
+ });
255
+
256
+ it("Should add focus and blur events to each checkbox that fires the markFocused method", function () {
257
+ var eventCalled = false;
258
+
259
+ checkboxButtonsMock.markFocused = function () {};
260
+ checkboxButtonsMock.markSelected = function () {};
261
+ checkboxButtonsMock.selectionEvents = 'click';
262
+ checkboxButtonsMock.focusEvents = 'focus blur';
263
+ spyOn(checkboxButtonsMock, 'markFocused');
264
+ spyOn($.fn, 'on').andCallFake(function (evt, func) {
265
+ if (evt === 'focus blur') {
266
+ eventCalled = true;
267
+ callback = func;
268
+ }
269
+ return $.fn;
270
+ });
271
+ GOVUK.CheckboxButtons.prototype.bindEvents.call(checkboxButtonsMock);
272
+ expect(eventCalled).toBe(true);
273
+ callback({
274
+ 'target' : $checkboxButtons.eq(0),
275
+ 'type' : 'focus'
276
+ });
277
+ expect(checkboxButtonsMock.markFocused).toHaveBeenCalled();
278
+ });
279
+ });
280
+
281
+ describe("markSelected method", function () {
282
+ var checkboxButtonsMock = {
283
+ 'selectedClass' : 'selected'
284
+ };
285
+
286
+ it("Should add the selectedClass class to a checked checkbox", function () {
287
+ $checkboxButtons.eq(0).attr('checked', true);
288
+ GOVUK.CheckboxButtons.prototype.markSelected.call(checkboxButtonsMock, $checkboxButtons.eq(0));
289
+ expect($checkboxLabels.eq(0).hasClass(checkboxButtonsMock.selectedClass)).toBe(true);
290
+ });
291
+
292
+ it("Should remove the selectedClass class from an unchecked checkbox", function () {
293
+ $checkboxButtons.eq(0).addClass(checkboxButtonsMock.selectedClass);
294
+ GOVUK.CheckboxButtons.prototype.markSelected.call(checkboxButtonsMock, $checkboxButtons.eq(0));
295
+ expect($checkboxLabels.eq(0).hasClass(checkboxButtonsMock.selectedClass)).toBe(false);
296
+ });
297
+ });
298
+
299
+ describe("markFocused method", function () {
300
+ var checkboxButtonsMock = {
301
+ 'focused' : false,
302
+ 'focusedClass' : 'focused'
303
+ };
304
+
305
+ it("Should add the focusedClass class to the sent radio if it is focused", function () {
306
+ GOVUK.CheckboxButtons.prototype.markFocused.apply(checkboxButtonsMock, [$checkboxButtons.eq(0), 'focused']);
307
+
308
+ expect($checkboxLabels.eq(0).hasClass(checkboxButtonsMock.focusedClass)).toBe(true);
309
+ });
310
+
311
+ it("Should remove the focusedClass class from the sent radio if it is blurred", function () {
312
+ $checkboxLabels.eq(0).addClass(checkboxButtonsMock.focusedClass);
313
+ GOVUK.CheckboxButtons.prototype.markFocused.apply(checkboxButtonsMock, [$checkboxButtons.eq(0), 'blurred']);
314
+
315
+ expect($checkboxLabels.eq(0).hasClass(checkboxButtonsMock.focusedClass)).toBe(false);
316
+ });
317
+ });
318
+ });
319
+
320
+ describe("selectionButtons", function () {
321
+ it("Should create an instance of RadioButtons for a set of radios", function () {
322
+ spyOn(GOVUK, 'RadioButtons');
323
+ GOVUK.selectionButtons($radioButtons);
324
+ expect(GOVUK.RadioButtons).toHaveBeenCalled();
325
+ });
326
+
327
+ it("Should create an instance of CheckboxButtons for a set of checkboxes", function () {
328
+ spyOn(GOVUK, 'CheckboxButtons');
329
+ GOVUK.selectionButtons($checkboxButtons);
330
+ expect(GOVUK.CheckboxButtons).toHaveBeenCalled();
331
+ });
332
+
333
+ it("Should create instances of RadioButtons and CheckboxButtons for a set containing radios and checkboxes", function () {
334
+ spyOn(GOVUK, 'RadioButtons');
335
+ spyOn(GOVUK, 'CheckboxButtons');
336
+ GOVUK.selectionButtons($checkboxButtons.add($radioButtons));
337
+ expect(GOVUK.RadioButtons).toHaveBeenCalled();
338
+ expect(GOVUK.CheckboxButtons).toHaveBeenCalled();
339
+ });
340
+ });
341
+ });
@@ -0,0 +1,15 @@
1
+ // files are loaded from the /spec/support folder so paths are relative to that
2
+ var manifest = {
3
+ support : [
4
+ '../../node_modules/jquery-browser/lib/jquery.js',
5
+ '../../javascripts/govuk/multivariate-test.js',
6
+ '../../javascripts/govuk/primary-links.js',
7
+ '../../javascripts/govuk/stick-at-top-when-scrolling.js',
8
+ '../../javascripts/govuk/stop-scrolling-at-footer.js'
9
+ ],
10
+ test : [
11
+ '../unit/MultivariateTestSpec.js',
12
+ '../unit/PrimaryLinksSpec.js',
13
+ '../unit/StickAtTopWhenScrollingSpec.js'
14
+ ]
15
+ };
@@ -0,0 +1,21 @@
1
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://http://www.w3.org/TR/html4/loose.dtd">
2
+ <html>
3
+ <head>
4
+ <title>Jasmine Test Runner</title>
5
+
6
+ <link rel="stylesheet" type="text/css" href="../../node_modules/grunt-contrib-jasmine/vendor/jasmine-1.3.1/jasmine.css">
7
+ <style>
8
+ #wrapper { display: none; }
9
+ </style>
10
+
11
+ <!-- JASMINE FILES -->
12
+ <script type="text/javascript" src="../../node_modules/grunt-contrib-jasmine/vendor/jasmine-1.3.1/jasmine.js"></script>
13
+ <script type="text/javascript" src="../../node_modules/grunt-contrib-jasmine/vendor/jasmine-1.3.1/jasmine-html.js"></script>
14
+
15
+ <script type="text/javascript" src="./console-runner.js"></script>
16
+ <script type="text/javascript" src="./load.js"></script>
17
+ </head>
18
+ <body>
19
+
20
+ </body>
21
+ </html>
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Jasmine Reporter that outputs test results to the browser console.
3
+ * Useful for running in a headless environment such as PhantomJs, ZombieJs etc.
4
+ *
5
+ * Usage (from your html file that loads jasmine):
6
+ * jasmine.getEnv().addReporter(new jasmine.ConsoleReporter());
7
+ * jasmine.getEnv().execute();
8
+ */
9
+
10
+ (function(jasmine, console) {
11
+ if (!jasmine) {
12
+ throw "jasmine library isn't loaded!";
13
+ }
14
+
15
+ var ANSI = {}
16
+ ANSI.color_map = {
17
+ "green": 32,
18
+ "red": 31
19
+ }
20
+
21
+ ANSI.colorize_text = function(text, color) {
22
+ var color_code = this.color_map[color];
23
+ return "\033[" + color_code + "m" + text + "\033[0m";
24
+ }
25
+
26
+ var ConsoleReporter = function() {
27
+ if (!console || !console.log) { throw "console isn't present!"; }
28
+ this.status = this.statuses.stopped;
29
+ };
30
+
31
+ var proto = ConsoleReporter.prototype;
32
+ proto.statuses = {
33
+ stopped : "stopped",
34
+ running : "running",
35
+ fail : "fail",
36
+ success : "success"
37
+ };
38
+
39
+ proto.reportRunnerStarting = function(runner) {
40
+ this.status = this.statuses.running;
41
+ this.start_time = (new Date()).getTime();
42
+ this.executed_specs = 0;
43
+ this.passed_specs = 0;
44
+ this.log("Starting...");
45
+ };
46
+
47
+ proto.reportRunnerResults = function(runner) {
48
+ var failed = this.executed_specs - this.passed_specs;
49
+ var spec_str = this.executed_specs + (this.executed_specs === 1 ? " spec, " : " specs, ");
50
+ var fail_str = failed + (failed === 1 ? " failure in " : " failures in ");
51
+ var color = (failed > 0)? "red" : "green";
52
+ var dur = (new Date()).getTime() - this.start_time;
53
+
54
+ this.log("");
55
+ this.log("Finished");
56
+ this.log("-----------------");
57
+ this.log(spec_str + fail_str + (dur/1000) + "s.", color);
58
+
59
+ this.status = (failed > 0)? this.statuses.fail : this.statuses.success;
60
+
61
+ /* Print something that signals that testing is over so that headless browsers
62
+ like PhantomJs know when to terminate. */
63
+ this.log("");
64
+ this.log("ConsoleReporter finished");
65
+ };
66
+
67
+
68
+ proto.reportSpecStarting = function(spec) {
69
+ this.executed_specs++;
70
+ };
71
+
72
+ proto.reportSpecResults = function(spec) {
73
+ if (spec.results().passed()) {
74
+ this.passed_specs++;
75
+ return;
76
+ }
77
+
78
+ var resultText = spec.suite.description + " : " + spec.description;
79
+ this.log(resultText, "red");
80
+
81
+ var items = spec.results().getItems()
82
+ for (var i = 0; i < items.length; i++) {
83
+ var trace = items[i].trace.stack || items[i].trace;
84
+ this.log(trace, "red");
85
+ }
86
+ };
87
+
88
+ proto.reportSuiteResults = function(suite) {
89
+ if (!suite.parentSuite) { return; }
90
+ var results = suite.results();
91
+ var failed = results.totalCount - results.passedCount;
92
+ var color = (failed > 0)? "red" : "green";
93
+ this.log(suite.description + ": " + results.passedCount + " of " + results.totalCount + " passed.", color);
94
+ };
95
+
96
+ proto.log = function(str, color) {
97
+ var text = (color != undefined)? ANSI.colorize_text(str, color) : str;
98
+ console.log(text)
99
+ };
100
+
101
+ jasmine.ConsoleReporter = ConsoleReporter;
102
+ })(jasmine, console);
103
+
@@ -0,0 +1,55 @@
1
+ (function (root) {
2
+ "use strict"
3
+ var loadedScripts = 0,
4
+ totalScripts,
5
+ merge,
6
+ loadScript,
7
+ runJasmine,
8
+ manifestScript;
9
+
10
+ merge = function (arrays) {
11
+ var resultingArray = [],
12
+ workingArray,
13
+ a, b, x, y;
14
+
15
+ for (a = 0, b = arrays.length; a < b; a++) {
16
+ workingArray = arrays[a];
17
+ for (x = 0, y = workingArray.length; x < y; x++) {
18
+ resultingArray.push(workingArray[x]);
19
+ }
20
+ }
21
+ return resultingArray;
22
+ };
23
+ loadScript = function (src, nextIdx) {
24
+ var script = document.createElement('script'),
25
+ nextScript;
26
+
27
+ script.type = 'text/javascript';
28
+ script.src = src;
29
+
30
+ document.getElementsByTagName('head')[0].appendChild(script);
31
+ if (nextIdx === undefined) { return script; }
32
+ script.onload = function () {
33
+ if (nextIdx < totalScripts.length) {
34
+ loadScript(totalScripts[nextIdx], nextIdx + 1);
35
+ } else {
36
+ runJasmine();
37
+ }
38
+ };
39
+ return script;
40
+ };
41
+ runJasmine = function () {
42
+ var console_reporter = new jasmine.ConsoleReporter()
43
+ jasmine.getEnv().addReporter(new jasmine.TrivialReporter());
44
+ jasmine.getEnv().addReporter(console_reporter);
45
+ jasmine.getEnv().execute();
46
+ };
47
+ manifestScript = loadScript('../manifest.js');
48
+
49
+ manifestScript.onload = function () {
50
+ var idx = 0;
51
+
52
+ totalScripts = merge([manifest.support, manifest.test]);
53
+ loadScript(totalScripts[idx], idx + 1);
54
+ };
55
+ }).call(this);
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Runs a Jasmine Suite from an html page.
3
+ * `page` is a PhantomJs page object.
4
+ * `exit_func` is the function to call in order to exit the script.
5
+ */
6
+
7
+ var PhantomJasmineRunner, address, page, runner;
8
+
9
+ PhantomJasmineRunner = (function() {
10
+
11
+ function PhantomJasmineRunner(page, exit_func) {
12
+ this.page = page;
13
+ this.exit_func = exit_func != null ? exit_func : phantom.exit;
14
+ this.tries = 0;
15
+ this.max_tries = 10;
16
+ }
17
+
18
+ PhantomJasmineRunner.prototype.get_status = function() {
19
+ return this.page.evaluate(function() {
20
+ return console_reporter.status;
21
+ });
22
+ };
23
+
24
+ PhantomJasmineRunner.prototype.terminate = function() {
25
+ switch (this.get_status()) {
26
+ case "success":
27
+ return this.exit_func(0);
28
+ case "fail":
29
+ return this.exit_func(1);
30
+ default:
31
+ return this.exit_func(2);
32
+ }
33
+ };
34
+
35
+ return PhantomJasmineRunner;
36
+
37
+ })();
38
+
39
+ if (phantom.args.length === 0) {
40
+ console.log("Need a url as the argument");
41
+ phantom.exit(1);
42
+ }
43
+
44
+ page = new WebPage();
45
+
46
+ runner = new PhantomJasmineRunner(page);
47
+
48
+ page.onConsoleMessage = function(msg) {
49
+ console.log(msg);
50
+ if (msg === "ConsoleReporter finished") {
51
+ return runner.terminate();
52
+ }
53
+ };
54
+
55
+ address = phantom.args[0];
56
+
57
+ page.open(address, function(status) {
58
+ if (status !== "success") {
59
+ console.log("can't load the address!");
60
+ return phantom.exit(1);
61
+ }
62
+ });
@@ -1,3 +1,3 @@
1
1
  module GovUKFrontendToolkit
2
- VERSION = "1.4.0"
2
+ VERSION = "1.5.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: govuk_frontend_toolkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.5.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-06-26 00:00:00.000000000 Z
12
+ date: 2014-07-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -224,16 +224,24 @@ files:
224
224
  - app/assets/images/player-icon-volume.png
225
225
  - app/assets/javascripts/govuk/multivariate-test.js
226
226
  - app/assets/javascripts/govuk/primary-links.js
227
+ - app/assets/javascripts/govuk/selection-buttons.js
227
228
  - app/assets/javascripts/govuk/stick-at-top-when-scrolling.js
228
229
  - app/assets/javascripts/govuk/stop-scrolling-at-footer.js
229
230
  - app/assets/javascripts/govuk_toolkit.js
230
231
  - app/assets/javascripts/stageprompt.js
231
232
  - app/assets/javascripts/vendor/jquery/jquery.player.min.js
233
+ - app/assets/javascripts/vendor/polyfills/bind.js
232
234
  - app/assets/jenkins.sh
233
235
  - app/assets/package.json
234
- - app/assets/spec/MultivariateTestSpec.js
235
- - app/assets/spec/PrimaryLinksSpec.js
236
- - app/assets/spec/StickAtTopWhenScrollingSpec.js
236
+ - app/assets/spec/SelectionButtonSpec.js
237
+ - app/assets/spec/manifest.js
238
+ - app/assets/spec/support/LocalTestRunner.html
239
+ - app/assets/spec/support/console-runner.js
240
+ - app/assets/spec/support/load.js
241
+ - app/assets/spec/support/run_jasmine_test.js
242
+ - app/assets/spec/unit/MultivariateTestSpec.js
243
+ - app/assets/spec/unit/PrimaryLinksSpec.js
244
+ - app/assets/spec/unit/StickAtTopWhenScrollingSpec.js
237
245
  - app/assets/stylesheets/.gitkeep
238
246
  - app/assets/stylesheets/_colours.scss
239
247
  - app/assets/stylesheets/_conditionals.scss
@@ -264,7 +272,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
264
272
  version: '0'
265
273
  segments:
266
274
  - 0
267
- hash: -3563452542867821047
275
+ hash: 1799776564228403607
268
276
  required_rubygems_version: !ruby/object:Gem::Requirement
269
277
  none: false
270
278
  requirements:
@@ -273,7 +281,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
273
281
  version: '0'
274
282
  segments:
275
283
  - 0
276
- hash: -3563452542867821047
284
+ hash: 1799776564228403607
277
285
  requirements: []
278
286
  rubyforge_project:
279
287
  rubygems_version: 1.8.23