Completed
Push — master ( ad82e8...b17028 )
by wiese
108:33 queued 43:33
created

  A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
nop 1
1
2
'use strict';
3
4
var test = require( 'tape-catch' ),
5
	sinon = require( 'sinon' ),
6
	formComponents = require( '../lib/form_components' ),
7
	createSpyingElement = function () {
8
		return {
9
			on: sinon.spy(),
10
			is: sinon.stub(),
11
			val: sinon.spy(),
12
			prop: sinon.stub(),
13
			text: sinon.spy(),
14
			change: sinon.spy()
15
		};
16
	},
17
	assertChangeHandlerWasSet = function ( t, spyingElement, expectedCallCount ) {
18
		t.equal( spyingElement.on.callCount, expectedCallCount || 1, 'event handler was set' );
19
		t.equal( spyingElement.on.firstCall.args[ 0 ], 'change', 'event handler was set for change events' );
20
		t.equal( typeof spyingElement.on.firstCall.args[ 1 ], 'function', 'event handler is a function' );
21
	},
22
	createBankDataConfig = function () {
23
		return {
24
			ibanElement: createSpyingElement(),
25
			bicElement: createSpyingElement(),
26
			accountNumberElement: createSpyingElement(),
27
			bankCodeElement: createSpyingElement(),
28
			bankNameFieldElement: createSpyingElement(),
29
			bankNameDisplayElement: createSpyingElement()
30
		};
31
	}
32
33
	;
34
35
test( 'Components add change handling function to their elements', function ( t ) {
36
	var element = createSpyingElement(),
37
		store = {},
38
		component = formComponents.createTextComponent( store, element, 'value' );
39
	t.ok( element.on.calledOnce, 'event binding function is called once' );
40
	t.ok( element.on.calledWith( 'change', component.onChange ) );
41
	t.end();
42
} );
43
44
test( 'Validating components add change handling function to their elements', function ( t ) {
45
	var element = createSpyingElement(),
46
		store = {},
47
		component = formComponents.createValidatingTextComponent( store, element, 'value' );
48
	t.ok( element.on.calledTwice, 'event binding function is called twice' );
49
	t.ok( element.on.calledWith( 'change', component.onChange ) );
50
	t.end();
51
} );
52
53
test( 'Change handler of components dispatches change action to store', function ( t ) {
54
	var element = createSpyingElement(),
55
		store = {
56
			dispatch: sinon.spy()
57
		},
58
		fakeEvent = { target: { value: 'current value' } },
59
		expectedAction = { type: 'CHANGE_CONTENT', payload: { value: 'current value', contentName: 'value' } },
60
		component = formComponents.createTextComponent( store, element, 'value' );
61
62
	component.onChange( fakeEvent );
63
64
	t.ok( store.dispatch.calledOnce, 'store dispatch is called once' );
65
	t.ok( store.dispatch.calledWith( expectedAction ), 'action contains event value' );
66
67
	t.end();
68
} );
69
70
test( 'Rendering the text component sets the value', function ( t ) {
71
	var element = createSpyingElement(),
72
		store = {},
73
		component = formComponents.createTextComponent( store, element, 'value' );
74
75
	component.render( { value: 'the new awesome value' } );
76
77
	t.ok( element.val.calledTwice, 'value is called twice (get/set)' );
78
	t.ok( element.val.calledWith( 'the new awesome value' ) );
79
	t.end();
80
} );
81
82
test( 'Text value is only set when element does not have focus', function ( t ) {
83
	var element = createSpyingElement(),
84
		store = {},
85
		component = formComponents.createTextComponent( store, element, 'value' );
86
87
	element.is.withArgs( ':focus' ).returns( true );
88
	component.render( { value: 'the new awesome value' } );
89
90
	t.ok( element.val.calledOnce, 'value is only read' );
91
	t.ok( element.val.getCall( 0 ).notCalledWith( 'the new awesome value' ), 'value is only read' );
92
	t.end();
93
} );
94
95
test( 'Rendering the radio component sets the value as array', function ( t ) {
96
	var element = createSpyingElement(),
97
		store = {},
98
		component = formComponents.createRadioComponent( store, element, 'value' );
99
100
	component.render( { value: 'the new awesome value' } );
101
102
	t.ok( element.val.calledOnce, 'value is set once' );
103
	t.ok( element.val.calledWith( [ 'the new awesome value' ] ) );
104
	t.end();
105
} );
106
107
test( 'Given true, rendering the checkbox component applies the checked property', function ( t ) {
108
	var element = createSpyingElement(),
109
		store = {},
110
		component = formComponents.createCheckboxComponent( store, element, 'value' );
111
112
	component.render( { value: true } );
113
114
	t.ok( element.prop.calledOnce, 'value is set once' );
115
	t.ok( element.prop.calledWith( 'checked', true ) );
116
	t.end();
117
} );
118
119
test( 'Given false, rendering the checkbox component applies the checked property', function ( t ) {
120
	var element = createSpyingElement(),
121
		store = {},
122
		component = formComponents.createCheckboxComponent( store, element, 'value' );
123
124
	component.render( { value: false } );
125
126
	t.ok( element.prop.calledOnce, 'value is set once' );
127
	t.ok( element.prop.calledWith( 'checked', false ) );
128
	t.end();
129
} );
130
131
test( 'Event handler for checkbox component stores checked state', function ( t ) {
132
	var element = createSpyingElement(),
133
		store = { dispatch: sinon.spy() },
134
		fakeEvent = { target: { value: 'current value' } },
135
		expectedAction = { type: 'CHANGE_CONTENT', payload: { value: true, contentName: 'value' } },
136
		component = formComponents.createCheckboxComponent( store, element, 'value' );
137
138
	element.prop.returns( true );
139
140
	component.onChange( fakeEvent );
141
142
	t.ok( store.dispatch.calledOnce, 'action is dispatched' );
143
	t.ok( store.dispatch.calledWith( expectedAction ), 'action contains expected value ' );
144
	t.end();
145
} );
146
147
test( 'Rendering the amount component with custom amount clears selection and sets text and hidden fields', function ( t ) {
148
	var textElement = createSpyingElement(),
149
		selectElement = createSpyingElement(),
150
		hiddenElement = createSpyingElement(),
151
		parser = sinon.stub().returnsArg(0),
152
		dummyFormatter = { format: function ( v ) {
153
			return "XX" + v + "YY";
154
155
		} },
156
		store = {},
157
		parent = {
158
			addClass: sinon.spy()
159
		}
160
	;
161
162
	textElement.parent = function () {
163
		return parent;
164
	};
165
166
	var component = formComponents.createAmountComponent( store, textElement, selectElement, hiddenElement, parser, dummyFormatter );
167
168
	component.render( { amount: 2300, isCustomAmount: true } );
169
170
	t.ok( textElement.val.calledOnce, 'value is set once' );
171
172
	t.ok( textElement.val.calledWith( 'XX2300YY' ) );
173
	t.ok( parent.addClass.withArgs( 'filled' ).calledOnce );
174
	t.ok( selectElement.val.callCount === 0, 'select element value is not set' );
175
	t.ok( selectElement.prop.calledOnce, 'property was set' );
176
	t.ok( selectElement.prop.calledWith( 'checked', false ), 'check property was removed' );
177
	t.ok( hiddenElement.val.calledOnce, 'hidden element value is set' );
178
	t.ok( hiddenElement.val.calledWith( 'XX2300YY' ), 'hidden element value is set' );
179
180
	t.end();
181
} );
182
183
test( 'Rendering the amount component with non-custom amount sets the hidden field and clears the text field', function ( t ) {
184
	var textElement = createSpyingElement(),
185
		selectElement = createSpyingElement(),
186
		hiddenElement = createSpyingElement(),
187
		parser = sinon.stub().returnsArg(0),
188
		dummyFormatter = { format: function ( v ) {
189
			return "XX" + v + "YY";
190
191
		} },
192
		store = {},
193
		parent = {
194
			removeClass: sinon.stub()
195
		}
196
	;
197
198
	textElement.parent = function () {
199
		return parent;
200
	};
201
202
	var component = formComponents.createAmountComponent( store, textElement, selectElement, hiddenElement, parser, dummyFormatter );
203
204
	component.render( { amount: 5000, isCustomAmount: false } );
205
206
	t.ok( textElement.val.calledOnce, 'value is cleared' );
207
	t.ok( textElement.val.calledWith( '' ) );
208
	t.ok( parent.removeClass.withArgs( 'filled' ).calledOnce );
209
	t.ok( selectElement.val.calledOnce, 'select element value is set' );
210
	t.ok( selectElement.val.calledWith( [ '5000' ] ), 'select element value is set' ); // needs to be array for selects
211
	t.ok( hiddenElement.val.calledOnce, 'hidden element value is set' );
212
	t.ok( hiddenElement.val.calledWith( 'XX5000YY' ), 'hidden element value is set' );
213
	t.end();
214
} );
215
216
test( 'Changing the amount selection dispatches select action with selected value', function ( t ) {
217
	var textElement = createSpyingElement(),
218
		selectElement = createSpyingElement(),
219
		hiddenElement = createSpyingElement(),
220
		dummyAmountParser = { parse: function( value ) {
221
			return 'XX' + value + 'YY';
222
		} },
223
		store = {
224
			dispatch: sinon.spy()
225
		},
226
		fakeEvent = { target: { value: 5000 } },
227
		expectedAction = { type: 'SELECT_AMOUNT', payload: { amount: 5000 } };
228
229
	formComponents.createAmountComponent( store, textElement, selectElement, hiddenElement, dummyAmountParser );
230
231
	t.ok( selectElement.on.calledOnce, 'event handler is attached' );
232
233
	// simulate event trigger by calling event handling function
234
	selectElement.on.args[ 0 ][ 1 ]( fakeEvent );
235
236
	t.ok( store.dispatch.calledOnce, 'event handler triggers store update' );
237
	t.deepEqual( store.dispatch.args[ 0 ][ 0 ], expectedAction, 'event handler generates the correct action' );
238
	t.end();
239
} );
240
241
test( 'Changing the amount input dispatches input action with parsed content', function ( t ) {
242
	var textElement = createSpyingElement(),
243
		selectElement = createSpyingElement(),
244
		hiddenElement = createSpyingElement(),
245
		dummyAmountParser = { parse: function( value ) {
246
			return 'XX' + value + 'YY';
247
		} },
248
		store = {
249
			dispatch: sinon.spy()
250
		},
251
		fakeEvent = { target: { value: '99,99' } },
252
		expectedAction = { type: 'INPUT_AMOUNT', payload: { amount: 'XX99,99YY' } };
253
254
	formComponents.createAmountComponent( store, textElement, selectElement, hiddenElement, dummyAmountParser );
255
256
	t.ok( textElement.on.withArgs( 'change' ).calledOnce, 'text input event handler is attached' );
257
258
	// simulate event trigger by calling event handling function
259
	textElement.on.withArgs( 'change' ).args[ 0 ][ 1 ]( fakeEvent );
260
261
	t.ok( store.dispatch.calledOnce, 'event handler triggers store update' );
262
	t.deepEqual( store.dispatch.args[ 0 ][ 0 ], expectedAction, 'event handler generates the correct action' );
263
	t.end();
264
} );
265
266
test( 'Bank data component adds change handling function to its elements', function ( t ) {
267
	var bankDataComponentConfig = createBankDataConfig(),
268
		store = {};
269
270
	formComponents.createBankDataComponent( store, bankDataComponentConfig );
271
272
	assertChangeHandlerWasSet( t, bankDataComponentConfig.ibanElement );
273
	assertChangeHandlerWasSet( t, bankDataComponentConfig.bicElement, 2 );
274
	assertChangeHandlerWasSet( t, bankDataComponentConfig.accountNumberElement );
275
	assertChangeHandlerWasSet( t, bankDataComponentConfig.bankCodeElement );
276
	// Bank data is a pure display field with no change handler
277
	t.end();
278
} );
279
280
test( 'Bank data component renders the store values in its elements', function ( t ) {
281
	var bankDataComponentConfig = createBankDataConfig(),
282
		store = {},
283
		handler = formComponents.createBankDataComponent( store, bankDataComponentConfig );
284
285
	handler.render( {
286
		iban: 'DE12500105170648489890',
287
		bic: 'INGDDEFFXXX',
288
		accountNumber: '0648489890',
289
		bankCode: '50010517',
290
		bankName: 'ING-DiBa',
291
		debitType: 'non-sepa'
292
	} );
293
294
	t.equal( bankDataComponentConfig.ibanElement.val.args[ 0 ][ 0 ], 'DE12500105170648489890', 'IBAN value is set' );
295
	t.equal( bankDataComponentConfig.bicElement.val.args[ 0 ][ 0 ], 'INGDDEFFXXX', 'BIC value is set' );
296
	t.equal( bankDataComponentConfig.accountNumberElement.val.args[ 0 ][ 0 ], '0648489890', 'Account number is set' );
297
	t.equal( bankDataComponentConfig.bankCodeElement.val.args[ 0 ][ 0 ], '50010517', 'BIC value is set' );
298
	t.equal( bankDataComponentConfig.bankNameDisplayElement.text.args[ 0 ][ 0 ], 'ING-DiBa', 'Bank name is displayed' );
299
	t.equal( bankDataComponentConfig.bankNameFieldElement.val.args[ 0 ][ 0 ], 'ING-DiBa', 'Bank name is set in field' );
300
301
	t.end();
302
} );
303
304
test( 'Bank data component checks if all elements in the configuration are set', function ( t ) {
305
	// TODO Do more than a spot check. How do i check all fields without using a loop in the test?
306
	// The test succeeds without checking code in the factory function because of the calls to the event binding
307
	// However, if bankName instead of iban was missing, the test would fail.
308
	var bankDataComponentConfigWithMissingIban = createBankDataConfig(),
309
		store = {};
310
311
	delete bankDataComponentConfigWithMissingIban.ibanElement;
312
313
	t.throws( function () {
314
		formComponents.createBankDataComponent( store, bankDataComponentConfigWithMissingIban );
315
	} );
316
317
	t.end();
318
} );
319