[
{"name": "under the limit: characters left, plural", "args": ["Hello", 10, "characters", null], "expect": {"count": 5, "remaining": 5, "over": false, "visible": true, "message": "You have 5 characters remaining", "description": "You can enter up to 10 characters"}},
{"name": "nothing typed yet: the whole limit is left", "args": ["", 200, "characters", null], "expect": {"count": 0, "remaining": 200, "over": false, "visible": true, "message": "You have 200 characters remaining", "description": "You can enter up to 200 characters"}},
{"name": "one left is singular", "args": ["abcd", 5, "characters", null], "expect": {"count": 4, "remaining": 1, "over": false, "visible": true, "message": "You have 1 character remaining", "description": "You can enter up to 5 characters"}},
{"name": "exactly at the limit: 0 characters remaining, not over", "args": ["abcde", 5, "characters", null], "expect": {"count": 5, "remaining": 0, "over": false, "visible": true, "message": "You have 0 characters remaining", "description": "You can enter up to 5 characters"}},
{"name": "one over is singular, and remaining goes negative", "args": ["abcdef", 5, "characters", null], "expect": {"count": 6, "remaining": -1, "over": true, "visible": true, "message": "You have 1 character too many", "description": "You can enter up to 5 characters"}},
{"name": "three over", "args": ["abcdefgh", 5, "characters", null], "expect": {"count": 8, "remaining": -3, "over": true, "visible": true, "message": "You have 3 characters too many", "description": "You can enter up to 5 characters"}},
{"name": "an emoji is one character, not two UTF-16 units", "args": ["😀😀😀", 3, "characters", null], "expect": {"count": 3, "remaining": 0, "over": false, "visible": true, "message": "You have 0 characters remaining", "description": "You can enter up to 3 characters"}},
{"name": "a decomposed accent (e + combining acute) is two code points", "args": ["Café", 5, "characters", null], "expect": {"count": 5, "remaining": 0, "over": false, "visible": true, "message": "You have 0 characters remaining", "description": "You can enter up to 5 characters"}},
{"name": "spaces and line breaks are characters too", "args": ["a b\nc", 10, "characters", null], "expect": {"count": 5, "remaining": 5, "over": false, "visible": true, "message": "You have 5 characters remaining", "description": "You can enter up to 10 characters"}},
{"name": "a limit of 1 is said in the singular", "args": ["", 1, "characters", null], "expect": {"count": 0, "remaining": 1, "over": false, "visible": true, "message": "You have 1 character remaining", "description": "You can enter up to 1 character"}},
{"name": "words: runs of non-whitespace", "args": ["The quick brown fox", 10, "words", null], "expect": {"count": 4, "remaining": 6, "over": false, "visible": true, "message": "You have 6 words remaining", "description": "You can enter up to 10 words"}},
{"name": "words: leading, trailing and repeated whitespace, tabs and line breaks do not make words", "args": [" one two\tthree\n", 3, "words", null], "expect": {"count": 3, "remaining": 0, "over": false, "visible": true, "message": "You have 0 words remaining", "description": "You can enter up to 3 words"}},
{"name": "one word left is singular", "args": ["one two", 3, "words", null], "expect": {"count": 2, "remaining": 1, "over": false, "visible": true, "message": "You have 1 word remaining", "description": "You can enter up to 3 words"}},
{"name": "one word too many", "args": ["a b c d", 3, "words", null], "expect": {"count": 4, "remaining": -1, "over": true, "visible": true, "message": "You have 1 word too many", "description": "You can enter up to 3 words"}},
{"name": "three words too many", "args": ["a b c d e f", 3, "words", null], "expect": {"count": 6, "remaining": -3, "over": true, "visible": true, "message": "You have 3 words too many", "description": "You can enter up to 3 words"}},
{"name": "only whitespace is no words", "args": [" \n ", 5, "words", null], "expect": {"count": 0, "remaining": 5, "over": false, "visible": true, "message": "You have 5 words remaining", "description": "You can enter up to 5 words"}},
{"name": "a no-break space separates words; punctuation does not", "args": ["one two,three", 5, "words", null], "expect": {"count": 2, "remaining": 3, "over": false, "visible": true, "message": "You have 3 words remaining", "description": "You can enter up to 5 words"}},
{"name": "below the threshold the message is not shown, but still worked out", "args": ["abcdefghij", 20, "characters", 75], "expect": {"count": 10, "remaining": 10, "over": false, "visible": false, "message": "You have 10 characters remaining", "description": "You can enter up to 20 characters"}},
{"name": "exactly at the threshold it is shown", "args": ["abcdefghijklmno", 20, "characters", 75], "expect": {"count": 15, "remaining": 5, "over": false, "visible": true, "message": "You have 5 characters remaining", "description": "You can enter up to 20 characters"}},
{"name": "50% of 3 is 1.5: one character is below it (whole-number division would say 1 and show it)", "args": ["a", 3, "characters", 50], "expect": {"count": 1, "remaining": 2, "over": false, "visible": false, "message": "You have 2 characters remaining", "description": "You can enter up to 3 characters"}},
{"name": "50% of 3 is 1.5: two characters reach it", "args": ["ab", 3, "characters", 50], "expect": {"count": 2, "remaining": 1, "over": false, "visible": true, "message": "You have 1 character remaining", "description": "You can enter up to 3 characters"}},
{"name": "a threshold of 0 shows the message with nothing typed", "args": ["", 10, "characters", 0], "expect": {"count": 0, "remaining": 10, "over": false, "visible": true, "message": "You have 10 characters remaining", "description": "You can enter up to 10 characters"}},
{"name": "a threshold of 100 hides it until the limit", "args": ["abcdefghi", 10, "characters", 100], "expect": {"count": 9, "remaining": 1, "over": false, "visible": false, "message": "You have 1 character remaining", "description": "You can enter up to 10 characters"}},
{"name": "over the limit is always shown, whatever the threshold", "args": ["abcdefghijk", 10, "characters", 100], "expect": {"count": 11, "remaining": -1, "over": true, "visible": true, "message": "You have 1 character too many", "description": "You can enter up to 10 characters"}},
{"name": "the threshold applies to words when counting words", "args": ["a b", 10, "words", 50], "expect": {"count": 2, "remaining": 8, "over": false, "visible": false, "message": "You have 8 words remaining", "description": "You can enter up to 10 words"}},
{"name": "a limit of 0 is refused", "args": ["abc", 0, "characters", null], "expectError": "limit must be a whole number of at least 1"},
{"name": "a negative limit is refused", "args": ["abc", -5, "words", null], "expectError": "limit must be a whole number of at least 1"},
{"name": "a fractional limit is refused", "args": ["abc", 2.5, "characters", null], "expectError": "limit must be a whole number of at least 1"},
{"name": "a threshold over 100 is refused", "args": ["abc", 10, "characters", 101], "expectError": "threshold must be a whole number from 0 to 100"},
{"name": "a negative threshold is refused", "args": ["abc", 10, "characters", -1], "expectError": "threshold must be a whole number from 0 to 100"},
{"name": "a fractional threshold is refused", "args": ["abc", 10, "characters", 50.5], "expectError": "threshold must be a whole number from 0 to 100"},
{"name": "an unknown unit is refused", "args": ["abc", 10, "letters", null], "expectError": "unit must be characters or words"}
]