From 21732ce4d2a92366f323f26c7aea6e81c20bd243 Mon Sep 17 00:00:00 2001 From: Duncan Stuart Date: Sat, 23 May 2020 00:48:46 +0200 Subject: [PATCH 1/6] Normalise test code formatting - Replace tabs with spaces - Add missing semicolons (prompted by linter) --- test/tests.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/tests.js b/test/tests.js index ba4ecd0..11f3266 100644 --- a/test/tests.js +++ b/test/tests.js @@ -7,15 +7,15 @@ removeEmptyElements = function (arr) { } } return newArray; -} +}; QUnit.test("RemoveEmptyElements", function(assert) { - "use strict"; - var testInput = ",,,A,,B,,,C,"; - var testArray = testInput.split(",") - var cleanedArray = removeEmptyElements(testArray); - assert.equal(cleanedArray.length, 3, "Array has 3 elements") + "use strict"; + var testInput = ",,,A,,B,,,C,"; + var testArray = testInput.split(","); + var cleanedArray = removeEmptyElements(testArray); + assert.equal(cleanedArray.length, 3, "Array has 3 elements"); assert.ok(cleanedArray[0] === "A", "First element is A"); assert.ok(cleanedArray[1] === "B", "Second element is B"); assert.ok(cleanedArray[2] === "C", "Third element is C"); -}); \ No newline at end of file +}); From 6df70f5190a2faf463cafa9472d5626625222b02 Mon Sep 17 00:00:00 2001 From: Duncan Stuart Date: Sat, 23 May 2020 01:07:32 +0200 Subject: [PATCH 2/6] Use "assert.equal" in tests This gives more useful failure messages --- test/tests.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/tests.js b/test/tests.js index 11f3266..525630b 100644 --- a/test/tests.js +++ b/test/tests.js @@ -15,7 +15,7 @@ QUnit.test("RemoveEmptyElements", function(assert) { var testArray = testInput.split(","); var cleanedArray = removeEmptyElements(testArray); assert.equal(cleanedArray.length, 3, "Array has 3 elements"); - assert.ok(cleanedArray[0] === "A", "First element is A"); - assert.ok(cleanedArray[1] === "B", "Second element is B"); - assert.ok(cleanedArray[2] === "C", "Third element is C"); + assert.equal(cleanedArray[0], "A", "First element is A"); + assert.equal(cleanedArray[1], "B", "Second element is B"); + assert.equal(cleanedArray[2], "C", "Third element is C"); }); From 0118dae62c55e4de158859e7a10faa0bbe8a0910 Mon Sep 17 00:00:00 2001 From: Duncan Stuart Date: Sat, 23 May 2020 01:11:01 +0200 Subject: [PATCH 3/6] Standardise spacing in index.html A mixture of tabs and spaces were used: this standardises on spaces --- index.html | 155 ++++++++++++++++++++++++++--------------------------- 1 file changed, 77 insertions(+), 78 deletions(-) diff --git a/index.html b/index.html index 618b7da..885f0d9 100644 --- a/index.html +++ b/index.html @@ -5,44 +5,44 @@ Bingo Card Generator @@ -141,7 +141,7 @@ "use strict"; var possibleSpaces, enoughSpaces, spaces, centerSquare, i, j, output; list = bingoGenerator.escapeHTML(list); - list = bingoGenerator.newlineToBR(list); + list = bingoGenerator.newlineToBR(list); possibleSpaces = bingoGenerator.removeEmptyElements(list.split(",")); // Two options for including random Free Space: @@ -336,45 +336,44 @@

Make your own bingo cards

-

I've provided a sample to create bingo cards for a day in the park. You can enter your own settings to create your own custom bingo cards.

-
- Bingo Card Basics - - -
- -
- Free Space - - +

I've provided a sample to create bingo cards for a day in the park. You can enter your own settings to create your own custom bingo cards.

+
+ Bingo Card Basics + + +
+ +
+ Free Space + + - -
- -
- Card Size -

A 5x5 bingo card is traditional, but you can try other sizes

- - -
- - -
- Printing Options -

Select the landscape page layout when printing for best results

- -
- + +
+ +
+ Card Size +

A 5x5 bingo card is traditional, but you can try other sizes

+ + +
+ +
+ Printing Options +

Select the landscape page layout when printing for best results

+ +
+
From 330063196cdf675dc3728b31e413ed72b22b8052 Mon Sep 17 00:00:00 2001 From: Duncan Stuart Date: Sun, 24 May 2020 01:17:52 +0200 Subject: [PATCH 4/6] Make javascript testable The goal here is to have the tests be able to test the javascript code which is used in the page. In order to do that we need to extract the javascript out to a separate file so that the the QUnit tests can test it. This approach follows the advice in the qunit docs: https://qunitjs.com/intro/#make-things-testable ...but modified to allow running via the command line (and therefore on Travis) by following this stackoverflow post: https://stackoverflow.com/a/51861149 --- bingo_generator.js | 249 ++++++++++++++++++++++++++++++++++++++++++++ index.html | 252 +-------------------------------------------- test/tests.js | 13 +-- 3 files changed, 255 insertions(+), 259 deletions(-) create mode 100644 bingo_generator.js diff --git a/bingo_generator.js b/bingo_generator.js new file mode 100644 index 0000000..1fc032c --- /dev/null +++ b/bingo_generator.js @@ -0,0 +1,249 @@ +/* jslint browser: true, devel: true */ +/* global $ */ + +var dauber = { + // If the target bingo card square has a daub, remove it. Otherwise, add a daub + toggle: function (e) { + "use strict"; + var i; + var target = e.currentTarget; + if (target.classList.contains("daub")) { + for (i = 1; i <= 5; i = i + 1) { + target.classList.remove("daubImg" + i); + target.classList.remove("daubPos" + i); + } + target.classList.remove("daub"); + } else { + target.classList.add("daub"); + target.classList.add("daubPos" + Math.ceil(Math.random() * 5)); + target.classList.add("daubImg" + Math.ceil(Math.random() * 5)); + } + }, + + // Iterate over any bingo cards on the page and attach mouse/touch events to each square + init: function () { + "use strict"; + var bingoSquare; + var bingoSquares = document.getElementById("results").getElementsByTagName("td"); + for (bingoSquare of bingoSquares) { + bingoSquare.addEventListener("mousedown", this.toggle); + } + } +}; + +var bingoGenerator = { + pageBreaks: 'on', // automatically inserts a page break after every 2 cards + + // Given an array, return the array minus any empty elements + removeEmptyElements: function (arr) { + "use strict"; + var newArray = [], i = 0; + for (i = 0; i < arr.length; i += 1) { + if (arr[i]) { + newArray.push(arr[i]); + } + } + return newArray; + }, + + formatFreespace: function (str, subheading) { + "use strict"; + return '' + str + '
' + subheading + '
'; + }, + + bingoCard: function (title, width, height, freespace, freespaceValue, freespaceSubheadingValue, freespaceRandom, list) { + "use strict"; + var possibleSpaces, enoughSpaces, spaces, centerSquare, i, j, output; + list = bingoGenerator.escapeHTML(list); + list = bingoGenerator.newlineToBR(list); + possibleSpaces = bingoGenerator.removeEmptyElements(list.split(",")); + + // Two options for including random Free Space: + // Option 1: We don't have enough. Include in random placement. + // Option 2:- We have enough. Overwrite other option. + enoughSpaces = true; + if (possibleSpaces.length < width * height) { + enoughSpaces = false; + } + + // Option 1 + if (freespace === "true" && freespaceRandom === "true" && !enoughSpaces) { + possibleSpaces.push(bingoGenerator.formatFreespace(freespaceValue, freespaceSubheadingValue)); + } + + // Create a random selection of the possible values + spaces = []; + + // This works for squares with odd dimensions,, e.g. 3x3 or 5x5, but not for even squares or rectangles + // var centerSquare = Math.floor(width*height/2); + // We'll say center is the center row of the center column + centerSquare = Math.floor(height / 2) * width + Math.floor(width / 2); + + for (i = 0; i <= width * height; i += 1) { + if (i === centerSquare && freespace === "true" && freespaceRandom === "false") { + spaces.push(bingoGenerator.formatFreespace(freespaceValue, freespaceSubheadingValue)); + } else { + spaces.push(possibleSpaces.splice(Math.floor(Math.random() * possibleSpaces.length), 1)[0]); + } + } + + // Option 2 + if (freespace === "true" && freespaceRandom === "true" && enoughSpaces) { + spaces[Math.floor(Math.random() * spaces.length)] = bingoGenerator.formatFreespace(freespaceValue, freespaceSubheadingValue); + } + + // Output + output = ''; + output += ''; + for (i = 0; i < height; i += 1) { + output += ''; + for (j = 0; j < width; j += 1) { + output += ''; + } + output += ''; + } + output += "
' + title + '
' + spaces.shift() + '
"; + $("#results").append(output); + }, + + // Generate Bingo Cards + generate: function () { + "use strict"; + var colsFilled, rowsFilled, spaces, i; + + // Check to make sure enough options are provided to fill the bingo card spaces + spaces = bingoGenerator.removeEmptyElements($("#words").val().split(",")).length; + if ($("#freespace").val()) { + spaces += 1; + } + if (spaces < $("#width").val() * $("#height").val()) { + alert("Warning: you do not have enough possible board options for the size of the board you selected!"); + } + + // Clear any previous results + $("#results").html(""); + + // Try to make nice printable pages -- we can fit about 10 pieces across and 6 down + colsFilled = 0; + rowsFilled = 1; + + // Loop over number of cards requested + for (i = 0; i < document.getElementById("number").value; i += 1) { + + colsFilled += 1; + if (bingoGenerator.pageBreaks === 'on' && colsFilled * $("#width").val() > 10) { + rowsFilled += 1; + if (rowsFilled * $("#height").val() > 6) { + $("#results").append('
 
'); + rowsFilled = 1; + } else { + $("#results").append('
'); + } + colsFilled = 1; + } + + // Create bingo card + bingoGenerator.bingoCard( + $("#title").val(), + $("#width").val(), + $("#height").val(), + $("#freespace").val(), + $("#freespaceValue").val(), + $("#freespaceSubheadingValue").val(), + $("#freespaceRandom").val(), + $("#words").val() + ); + } + + // Jump to results + location.href = '#results'; + + }, + + // Replace + with spaces, <> with entities, and URL-decode &/,:;+=?%$ + urlUnencode: function (str) { + "use strict"; + return decodeURI(str).replace(//g, ">"); + }, + + newlineToBR: function (str) { + "use strict"; + return str.replace(/\n/g, "
"); + }, + + parseQS: function () { + "use strict"; + var qsArray, json, i, kv, key, val; + // Remove leading question mark ("?") and split into array of key/value pairs + qsArray = location.search.slice(1).split("&"); + // Initialize object to store key/value pairs + json = {}; + // Loop through key/value pairs and separate into keys and values + for (i = 0; i < qsArray.length; i += 1) { + kv = qsArray[i].split("="); + key = kv[0]; + if (kv.length === 1) { + val = key; + } else { + // A key may be present without a value, so set a placeholder value + val = kv[1]; + } + json[key] = val; + } + return json; + }, + + checkQS: function () { + "use strict"; + var qs = bingoGenerator.parseQS(); + if (!(qs.title === undefined) || !(qs.words === undefined)) { + $("#intro").hide(); + if (!(qs.title === undefined)) { + $("#title").val(bingoGenerator.urlUnencode(qs.title)); + } + if (!(qs.words === undefined)) { + $("#words").val(bingoGenerator.urlUnencode(qs.words)); + } + if (!(qs.freespace === undefined || qs.freespace === "false")) { + document.getElementById("freespace").selectedIndex = 0; + $("#freespaceValue").val(bingoGenerator.urlUnencode(qs.freespaceValue)); + if (!(qs.freespaceSubheadingValue === undefined)) { + $("#freespaceSubheadingValue").val(bingoGenerator.urlUnencode(qs.freespaceSubheadingValue)); + } else { + $("#freespaceSubheadingValue").val("Free Space"); + } + if (!(qs.freespaceRandom === undefined) && qs.freespaceRandom === "true") { + document.getElementById("freespaceRandom").selectedIndex = 0; + } + } else { + document.getElementById("freespace").selectedIndex = 1; + $("#freespaceValue").val(""); + } + if (!(qs.width === undefined)) { + $("#width").val(qs.width); + } + if (!(qs.height === undefined)) { + $("#height").val(qs.height); + } + if (!(qs.number === undefined)) { + $("#number").val(qs.number); + } + } + if (!(qs.pageBreaks === undefined)) { + bingoGenerator.pageBreaks = qs.pageBreaks; + } + if (!(qs.title === undefined) && !(qs.words === undefined)) { + bingoGenerator.generate(); + dauber.init(); + } + } +}; + +if (typeof module !== 'undefined' && module.exports) { + exports.bingoGenerator = bingoGenerator; +} diff --git a/index.html b/index.html index 885f0d9..02bd491 100644 --- a/index.html +++ b/index.html @@ -83,254 +83,10 @@ - + diff --git a/test/tests.js b/test/tests.js index 525630b..b389d7f 100644 --- a/test/tests.js +++ b/test/tests.js @@ -1,19 +1,10 @@ -removeEmptyElements = function (arr) { - "use strict"; - var newArray = [], i = 0; - for (i = 0; i < arr.length; i += 1) { - if (arr[i]) { - newArray.push(arr[i]); - } - } - return newArray; -}; +bingoGenerator = require('../bingo_generator.js').bingoGenerator; QUnit.test("RemoveEmptyElements", function(assert) { "use strict"; var testInput = ",,,A,,B,,,C,"; var testArray = testInput.split(","); - var cleanedArray = removeEmptyElements(testArray); + var cleanedArray = bingoGenerator.removeEmptyElements(testArray); assert.equal(cleanedArray.length, 3, "Array has 3 elements"); assert.equal(cleanedArray[0], "A", "First element is A"); assert.equal(cleanedArray[1], "B", "Second element is B"); From 8a375dcaf65cf5b63fdd56e555df874e94cd3eeb Mon Sep 17 00:00:00 2001 From: Duncan Stuart Date: Sun, 24 May 2020 01:51:39 +0200 Subject: [PATCH 5/6] Refactor: extract a wordList function We need to make the behaviour here slightly more complicated, so let's first isolate it and cover it with tests. --- bingo_generator.js | 10 +++++++--- test/tests.js | 42 +++++++++++++++++++++++++++++++++--------- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/bingo_generator.js b/bingo_generator.js index 1fc032c..9c25d7e 100644 --- a/bingo_generator.js +++ b/bingo_generator.js @@ -54,9 +54,7 @@ var bingoGenerator = { bingoCard: function (title, width, height, freespace, freespaceValue, freespaceSubheadingValue, freespaceRandom, list) { "use strict"; var possibleSpaces, enoughSpaces, spaces, centerSquare, i, j, output; - list = bingoGenerator.escapeHTML(list); - list = bingoGenerator.newlineToBR(list); - possibleSpaces = bingoGenerator.removeEmptyElements(list.split(",")); + possibleSpaces = bingoGenerator.wordList(list); // Two options for including random Free Space: // Option 1: We don't have enough. Include in random placement. @@ -160,6 +158,12 @@ var bingoGenerator = { }, + wordList: function(str) { + list = bingoGenerator.escapeHTML(str); + list = bingoGenerator.newlineToBR(list); + return bingoGenerator.removeEmptyElements(list.split(",")); + }, + // Replace + with spaces, <> with entities, and URL-decode &/,:;+=?%$ urlUnencode: function (str) { "use strict"; diff --git a/test/tests.js b/test/tests.js index b389d7f..71122d3 100644 --- a/test/tests.js +++ b/test/tests.js @@ -1,12 +1,36 @@ bingoGenerator = require('../bingo_generator.js').bingoGenerator; -QUnit.test("RemoveEmptyElements", function(assert) { - "use strict"; - var testInput = ",,,A,,B,,,C,"; - var testArray = testInput.split(","); - var cleanedArray = bingoGenerator.removeEmptyElements(testArray); - assert.equal(cleanedArray.length, 3, "Array has 3 elements"); - assert.equal(cleanedArray[0], "A", "First element is A"); - assert.equal(cleanedArray[1], "B", "Second element is B"); - assert.equal(cleanedArray[2], "C", "Third element is C"); +QUnit.module("wordList"); + +QUnit.test("it removes empty elements", function(assert) { + "use strict"; + var wordListString = ",,,A,,B,,,C,"; + + var wordList = bingoGenerator.wordList(wordListString); + + assert.equal(wordList.length, 3, "Array has 3 elements"); + assert.equal(wordList[0], "A", "First element is A"); + assert.equal(wordList[1], "B", "Second element is B"); + assert.equal(wordList[2], "C", "Third element is C"); +}); + +QUnit.test("it escapes angle brackets", function(assert) { + "use strict"; + var wordListString = ""; + + var wordList = bingoGenerator.wordList(wordListString); + + assert.equal(wordList.length, 1, "Array has 1 element"); + assert.equal(wordList[0], "<tag>", "Tag is escaped"); +}); + +QUnit.test("it replaces newlines with break tags", function(assert) { + "use strict"; + var wordListString = "\nA\nB,C\n"; + + var wordList = bingoGenerator.wordList(wordListString); + + assert.equal(wordList.length, 2, "Array has 2 elements"); + assert.equal(wordList[0], "
A
B", "First element is AB with breaks"); + assert.equal(wordList[1], "C
", "Second element is C with a break"); }); From b0943086c3964466ab4946747de2795b4f8d36a2 Mon Sep 17 00:00:00 2001 From: Duncan Stuart Date: Sun, 24 May 2020 02:16:39 +0200 Subject: [PATCH 6/6] Remove trailing and leading newlines from wordlist For long word lists it's useful to be able to break the list up into sub-sections with line breaks. Previously if there was a line break before or after a word, this would be rendered in the bingo card. This change preserves internal line breaks, but removes leading and trailing breaks. Note that this handles multiple line breaks in a row. --- bingo_generator.js | 24 +++++++++++++++++++++ test/tests.js | 52 ++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/bingo_generator.js b/bingo_generator.js index 9c25d7e..6bf387f 100644 --- a/bingo_generator.js +++ b/bingo_generator.js @@ -160,6 +160,10 @@ var bingoGenerator = { wordList: function(str) { list = bingoGenerator.escapeHTML(str); + list = bingoGenerator.removeLeadingNewlines(list); + list = bingoGenerator.removeTrailingNewlines(list); + list = bingoGenerator.removeStartOfFileNewlines(list); + list = bingoGenerator.removeEndOfFileNewlines(list); list = bingoGenerator.newlineToBR(list); return bingoGenerator.removeEmptyElements(list.split(",")); }, @@ -175,6 +179,26 @@ var bingoGenerator = { return str.replace(//g, ">"); }, + removeLeadingNewlines: function (str) { + "use strict"; + return str.replace(/,\n+/g, ","); + }, + + removeTrailingNewlines: function (str) { + "use strict"; + return str.replace(/\n+,/g, ","); + }, + + removeEndOfFileNewlines: function (str) { + "use strict"; + return str.replace(/\n+$/g, ""); + }, + + removeStartOfFileNewlines: function (str) { + "use strict"; + return str.replace(/^\n+/g, ""); + }, + newlineToBR: function (str) { "use strict"; return str.replace(/\n/g, "
"); diff --git a/test/tests.js b/test/tests.js index 71122d3..3de53d2 100644 --- a/test/tests.js +++ b/test/tests.js @@ -24,13 +24,57 @@ QUnit.test("it escapes angle brackets", function(assert) { assert.equal(wordList[0], "<tag>", "Tag is escaped"); }); -QUnit.test("it replaces newlines with break tags", function(assert) { +QUnit.test("it replaces internal newlines with break tags", function(assert) { "use strict"; - var wordListString = "\nA\nB,C\n"; + var wordListString = "A\nB,C\n\nD"; var wordList = bingoGenerator.wordList(wordListString); assert.equal(wordList.length, 2, "Array has 2 elements"); - assert.equal(wordList[0], "
A
B", "First element is AB with breaks"); - assert.equal(wordList[1], "C
", "Second element is C with a break"); + assert.equal(wordList[0], "A
B", "First element is AB with an internal break"); + assert.equal(wordList[1], "C

D", "Second element is CD with an internal break"); +}); + +QUnit.test("it ignores leading newlines", function(assert) { + "use strict"; + var wordListString = "A,\nB,\n\nC"; + + var wordList = bingoGenerator.wordList(wordListString); + + assert.equal(wordList.length, 3, "Array has 3 elements"); + assert.equal(wordList[0], "A", "First element is A"); + assert.equal(wordList[1], "B", "Second element is B"); + assert.equal(wordList[2], "C", "Third element is C"); +}); + +QUnit.test("it ignores trailing newlines", function(assert) { + "use strict"; + var wordListString = "A\n,B\n\n,C"; + + var wordList = bingoGenerator.wordList(wordListString); + + assert.equal(wordList.length, 3, "Array has 3 elements"); + assert.equal(wordList[0], "A", "First element is A"); + assert.equal(wordList[1], "B", "Second element is B"); + assert.equal(wordList[2], "C", "Third element is C"); +}); + +QUnit.test("it ignores start-of-file newlines", function(assert) { + "use strict"; + var wordListString = "\nA"; + + var wordList = bingoGenerator.wordList(wordListString); + + assert.equal(wordList.length, 1, "Array has 1 element"); + assert.equal(wordList[0], "A", "First element is A"); +}); + +QUnit.test("it ignores end-of-file newlines", function(assert) { + "use strict"; + var wordListString = "A\n"; + + var wordList = bingoGenerator.wordList(wordListString); + + assert.equal(wordList.length, 1, "Array has 1 element"); + assert.equal(wordList[0], "A", "First element is A"); });