@@ -159,9 +159,178 @@ AssertionError: True is not false
159
159
And we can commit this as the first cut of our FT.
160
160
161
161
162
+ === A Quick Spike of a solution
163
+
164
+ This will be our first bit of JavaScript.
165
+ We're also interacting with the Bootstrap CSS framework,
166
+ which we maybe don't know very well.
167
+
168
+ In <<chapter_14_simple_form>> we saw that you
169
+ can use a unit test as a way of exploring a new API or tool.
170
+ Sometimes though, you just want to hack something together
171
+ without any tests at all, just to see if it works,
172
+ to learn it or get a feel for it.
173
+
174
+ That's absolutely fine.
175
+ When learning a new tool or exploring a new possible solution,
176
+ it's often appropriate to leave the rigorous TDD process to one side,
177
+ and build a little prototype without tests, or perhaps with very few tests.
178
+ The goat doesn't mind looking the other way for a bit.
179
+
180
+ This kind of prototyping activity is often called a "spike",
181
+ for http://stackoverflow.com/questions/249969/why-are-tdd-spikes-called-spikes[
182
+ reasons that aren't entirely clear].
183
+
184
+
185
+ ==== A Simple Inline Script
186
+
187
+ I hacked around for a bit,
188
+ and here's more or less the first thing I came up with.
189
+ I'm adding the code inline, in a `<script>` tag
190
+ at the bottom of our _base.html_ template:
191
+
192
+ [role="sourcecode"]
193
+ .lists/templates/base.html
194
+ ====
195
+ [source,javascript]
196
+ ----
197
+ </div>
198
+
199
+ <script>
200
+ const textInput = document.querySelector("#id_text"); //<1>
201
+ textInput.oninput = () => { //<2><3>
202
+ const errorMsg = document.querySelector(".invalid-feedback");
203
+ errorMsg.style.display = "none"; //<4>
204
+ }
205
+ </script>
206
+ ----
207
+ ====
208
+
209
+ <1> `document.querySelector` is a way of finding an element in the DOM,
210
+ using CSS selector syntax, very much like the Selenium
211
+ `find_element(By.CSS_SELECTOR)` method from our FTs.
212
+
213
+ <2> `oninput` is how you attach an event listener "callback" function,
214
+ which will be called whenever the user inputs something into the text box.
215
+
216
+ <3> Arrow functions `() => {...}` are the new way of writing anonymous functions
217
+ in JavaScript, a bit like Python's `lambda` syntax.
218
+ I think they're cute!
219
+ Arguments go in the round brackets,
220
+ the function body goes in the curly braces.
221
+ So this is a function that takes no arguments,
222
+ or I should say, ignores any arguments you try to give it.
223
+ What does it do?
224
+
225
+ <4> It finds the error message element,
226
+ and then hides it by setting its `style.display` to "none".
227
+
228
+ That's actually good enough to get our FT passing:
229
+
230
+ [subs="specialcharacters,quotes"]
231
+ ----
232
+ $ *python src/manage.py test functional_tests.test_list_item_validation.\
233
+ ItemValidationTest.test_error_messages_are_cleared_on_input
234
+ Found 1 test(s).
235
+ [...]
236
+ .
237
+ ---------------------------------------------------------------------
238
+ Ran 1 test in 3.284s
239
+
240
+ OK
241
+ ----
242
+
243
+
244
+ ==== Using the Browser Devtools
245
+
246
+ The test might be happy, but our solution is a little unsatisfactory.
247
+ If you actually try it in your browser,
248
+ you'll see that although the error message is gone,
249
+ the input is still red an invalid-looking, see <<input-still-red>>.
250
+
251
+ [[input-still-red]]
252
+ .The error message is gone but the input box is still red
253
+ image::images/error-gone-but-input-still-red.png["Screenshot of our page where the error div is gone but the input is still red."]
254
+
255
+ You're probably imagining that this is something to do with Bootstrap.
256
+ We might have been able to hide the error message,
257
+ but we also need to tell bootstrap that this input no longer has invalid contents.
258
+
259
+ This is where I'd normally open up the browser
260
+ https://firefox-source-docs.mozilla.org/devtools-user/[devtools].
261
+ If level 1 of hacking is spiking code directly into an inline `<script>` tag,
262
+ level 2 is hacking things directly in the browser,
263
+ where it's not even saved to a file!
264
+
265
+ [[editing-html-in-devtools]]
266
+ .Editing the HTML in the browser devtools
267
+ image::images/editing-html-via-devtools.png["Screenshot of the browser devtools with us editing the classes for the input element"]
268
+
269
+ In <<editing-html-in-devtools>> you can see me directly editing the HTML of the page,
270
+ and finding out that removing the `is-invalid` class from the input element
271
+ seems to do the trick.
272
+ It not only removes the error message,
273
+ but also the red border around the input box.
274
+
275
+ We have a reasonable solution now, time to de-spike!
276
+
277
+
278
+ .Do we Really Need to Write Unit Tests for This?
279
+ *******************************************************************************
280
+
281
+ By this point in the book, you probably know I'm going to say "yes",
282
+ but let's talk about it anyway.
283
+
284
+ Our FT definitely covers this funcitonality,
285
+ and we could extend it if we wanted to,
286
+ to check on the colour of the input box,
287
+ or to look at the input element's CSS classes.
288
+
289
+ And if I was really sure that this was the only bit of JavaScript
290
+ we were ever going to write,
291
+ I probably would be tempted to leave it at that.
292
+
293
+ But I want to press on for two reasons.
294
+ Firstly, because any book on web development has to talk about JavaScript,
295
+ and in a TDD book, I have to show a bit of TDD in JavaScript.
296
+
297
+ More importantly though, as always we have the boiled frog problem.
298
+ We might not have enough JavaScript _yet_ to justify a full test suite,
299
+ but what about when we come along later and add a tiny bit more?
300
+ And a tiny bit more again?
301
+
302
+ It's always a judgement call, and on the one hand YAGNI,
303
+ but on the other hand, I think it's best to but the scaffolding in place early,
304
+ so that going test-first is the easy choice later.
305
+
306
+ I can already think of several extra things I'd want to do in the frontend!
307
+ What about re-setting the input to being invalid if someone types in the
308
+ exact duplicate text again?
309
+
310
+ *******************************************************************************
311
+
312
+
162
313
=== Setting Up a Basic JavaScript Test Runner
163
314
164
315
316
+ ////
317
+ === TODO: new plan
318
+
319
+ this chapter is a decent first pass now, want to improve it as follows:
320
+
321
+ * start with basic inline script that just hides the thing on input,
322
+ see FT pass [DONE]
323
+
324
+ * install jasmine browser runner via npm/npx. use esm/modules straight away
325
+
326
+ * see if just specifying it as a .js works, and we can do the chat about execution times. then switch to .mjs and imports
327
+
328
+ * split out tests, especially sense-check bits, into 3 different its
329
+ ////
330
+
331
+
332
+
333
+
165
334
((("test running libraries")))
166
335
((("JavaScript testing", "test running libraries", id="JStestrunner16")))
167
336
((("pytest")))
@@ -473,25 +642,6 @@ Superlists tests
473
642
474
643
475
644
476
- ////
477
-
478
- === TODO: new plan
479
-
480
- this chapter is a decent first pass now, want to improve it as follows:
481
-
482
- * start with basic inline script that just hides the thing on input,
483
- see FT pass
484
-
485
- * install jasmine browser runner via npm/npx. use esm/modules straight away
486
-
487
- * see if just specifying it as a .js works, and we can do the chat about execution times. then switch to .mjs and imports
488
-
489
- * split out tests, especially sense-check bits, into 3 different its
490
- ////
491
-
492
-
493
-
494
-
495
645
=== Building a JavaScript Unit Test for Our Desired Functionality
496
646
497
647
@@ -501,9 +651,9 @@ Now that we're acquainted with our JavaScript testing tools,
501
651
we can switch back to just one test and start to write the real thing:
502
652
503
653
[role="sourcecode small-code"]
504
- .lists/static/tests/Spec .js (ch16l012)
654
+ .lists/static/tests/spec .js (ch16l012)
505
655
====
506
- [source,html ]
656
+ [source,javascript ]
507
657
----
508
658
it("error message should be hidden on input", () => { //<1>
509
659
const inputSelector = "input#id_text";
@@ -918,36 +1068,6 @@ $ *git commit -m"add jamsine specrunner, js tests, and lists.js with onpinput li
918
1068
919
1069
TODO: resume here:
920
1070
921
- * open in browser, not quite right. see <<input-still-red>>
922
-
923
- [[input-still-red]]
924
- .The error message is gone but the input box is still red
925
- image::images/error-gone-but-input-still-red.png["Screenshot of our page where the error div is gone but the input is still red."]
926
-
927
- * Also you might notice some errors in the js console see <<js-console-errors>>
928
-
929
- [[js-console-errors]]
930
- .An error in the browser devtools console
931
- image::images/js-errordiv-is-null-in-console.png["Screenshot of the browser devtools console with an error saying errorDiv is Null"]
932
-
933
-
934
- How to fix?
935
-
936
- === Experimenting with the browser devtools
937
-
938
-
939
- this calls for a spike really, we don't know bootstrap well enough.
940
- let's hack about in devtools
941
-
942
-
943
- see <<editing-html-in-devtools>>
944
-
945
- [[editing-html-in-devtools]]
946
- .Editing the HTML in the browser devtools
947
- image::images/editing-html-via-devtools.png["Screenshot of the browser devtools with us editing the classes for the input element"]
948
-
949
-
950
- removing the `is-invalid` class from the input element seems to do the trick
951
1071
952
1072
953
1073
0 commit comments