It is time for us to start handling the player controls
by determining the rules that apply to players' guesses.
Let's go from top to bottom.
Since we want to enforce that a player guess is limited to five letters,
we can start by having a test in which, during the act phase,
our player types a guess that is longer than five characters,
but in which the first five characters of that guess match the word of the day.
When this happens,
since our application will only be considering the first five characters,
the player should be seeing the victory message being rendered.
So let's write this assertion.
If we run our tests, we can see that this new test is failing,
because the victory message is not being rendered.
Instead, we're seeing the defeat message.
Okay, let's get it to pass.
The HTML input has a built-in attribute called maxlength,
that would limit the amount of characters the player can type at that input command.
However, if I run the test right now, you can see that we are still failing.
The reason for that is due to how vue test utils works.
You can take a look over here
and see that it is through the invocation of such value
that we emulate the user typing their guess,
but this function bypasses the protection of maxlength.
With that said, right after we're done with this feature,
we'll be going over even more input controls
that will go beyond what the native HTML input helpers can assist us with.
For that reason, going straight towards using Vue.js
to manipulate the input maxlength, is not a big deal in our case.
Vue offers an amazing way
for you to control and transform user input on the fly,
and this is through a writable computed ref.
You start by defining it like you would a regular ref.
I'm going to call this formattedGuessInProgress.
In a computed ref,
you would pass a callback
that returns the value that you are calculating.
Okay, now, if you want to make a writable computed ref,
what you have to do is, instead of passing just a single callback,
we're going to pass an object that has two callbacks inside of it,
a getter and a setter.
The getter will behave just like the usual callback
of a regular computed property,
but the setter will be having whatever is the raw value
that we'll be receiving from our v-model,
and in here we can manipulate that raw value in whatever way we want.
For our situation right now,
what we need is to limit the raw value to only the first five characters,
and we can do it like this.
Great! If we run our tests, we can see that we're passing.
Alright, let's make a commit.
I think a good name for this commit
could be Limit player guesses to 5 characters.
For our refactor phase,
I think we can reduce the complexity of the code base
by extracting this number 5 into a constant.
What does this number 5 mean?
Well, it's the size of the words we're going to be playing with,
so why don't we create a new constant in here called WORD_SIZE.
And now, whenever we're typing the number 5,
we can make use of this constant.
Same with our tests as well.
I can turn this into a template string
and then make use of the WORD_SIZE constant right here.
Great! Our tests are still passing.
This is a great opportunity for us to make another commit.
Extract constant.
Okay, it's time for us to move to our next test.
In this case, we want to prevent the player
from submitting guesses that are not real words.
In this case, I think in our act phase,
we could have our user typing a guess
that is not a real word.
For example, QWERT.
And in this case, because this guess is going to be rejected,
the player should not be seeing the victory message
nor the defeat message because no guess was truly submitted.
So let's just copy this assertion from this prior test over here
and just change it to have the NOT keyword beforehand.
And we're going to do the same thing for the defeat message.
If we run all of our tests again,
we can see that this brand new test is failing
because we're seeing the defeat message.
This is expected because our application right now,
is accepting any kind of guesses.
So let's try to limit this.
And one way we can achieve it,
it is by refining our keydown.enter handler.
So what I'm going to do is I will start by extracting this into a method.
And what this method is going to be doing
is just exactly what we have in here.
Great. So we're still failing and that's okay.
Now what I'm going to do is,
before assigning the value of the guess in progress
to the guess submitted,
I'm going to check to see if the guess in progress is a real English word.
And if it isn't, I'm going to early return.
So let's do this.
If our collection of English words do not include the guess in progress,
do an early return.
If we run our tests, we can see that we're back to the green stage.
Let's make another commit.
Prevent guesses that are not English words from being submitted.
For our refactor phase,
I honestly don't see anything at the moment
that I would like to refactor.
And that's okay.
So let's keep that in mind
that even though the TDD loop is red-green refactor,
sometimes we do come across scenarios like this one
in which we end up skipping a phase.
So let's go to our next test.
To enforce that guesses are not case-sensitive,
we could have the user type a guess that is in lowercase,
but that would otherwise perfectly match the word of the day.
So let's type this act phase.
If the user were to type the word of the day right now,
they would win the game, right?
But what I'm going to do, is,
I'm going to have the user type the word of the day in lowercase.
And we still want the user to be winning the game,
so they should see the victory message.
If we run our tests, we can see, however,
that the player is not seeing the victory message.
In fact, they are not seeing any messages at all
because their guess got rejected
due to not being in the list of English words that we have.
Because remember, our list of English words are all in uppercase.
Okay, let's get it to pass.
There are different approaches that we can do over here,
but what I want to do is emulate what happens in the real wordle game.
And that is, regardless if you're typing a lowercase character
or an uppercase character,
the output in the game board will always be in uppercase.
So we can do the same in our version over here.
To do this, we can go back to our writable computed ref,
and as part of the transformation in the setter,
we can cast the raw value to uppercase, like this.
So let's run our tests,
and we can see that we're back to passing.
Great! Let's make another commit.
A good commit message for this case would be,
make player guesses case-insensitive.
Once again, I don't see anything in here that I would like to refactor,
so I think we're good to move on to our next test.
Now, what we want to do
is prevent our players from typing anything that is not a letter.
Well, a good way for us to start this test
is by having our act phase involving our user typing a guess
that contains numbers and special characters, like this.
And what we want to assert in this case
is that the player input will contain only the H, R, and T.
Let's do this.
Okay, if we run our tests, we can see that we're failing,
because the number and the special character
have gone through our input.
Alright, so once again,
we're going to be refining our setter in the writable computed ref.
And for this point, whenever I want to do this more complex filtering,
I like to rely on RegEx.
I'm going to explain the RegEx that I'm going to be using here,
but if you're not comfortable with RegEx in general,
I strongly recommend checking out regex101.com.
It provides an amazing resource for you to know the rules
and how to test your RegEx.
But okay, let's continue.
In this case, I'm going to use a RegEx to define my target,
and I will replace it for an empty string.
Alright, so let me explain what I've typed so far in here.
Typing anything between slashes
is one of the ways in which you can define RegExes in JavaScript or TypeScript.
After the last / we can pass configuration options.
"g" means global, and it makes the RegEx not return after the first match.
The "i" flag means that our RegEx is going to be case insensitive.
I'm going to be looking for anything that is not a letter,
so one way that you can define it is by doing this RegEx over here.
Let me explain.
The brackets allow me to represent a single character
of whatever set I want to define inside the brackets.
The ^ inside of the bracket means not,
therefore, it represents a set of all the characters
that are not the letters from A to Z.
By adding a + after this set,
I'm determining that I'm looking for one or more occurrences of a character
that matches this set.
Okay, so effectively what I'm doing on line 23
is replacing anything that is not a letter to an empty string.
Effectively removing them from the raw value.
If we run our test, we can see that we're passing.
Great! Let's make another commit.
Only allow player to type letters.
Awesome! Together, we've practiced how to write tests for player inputs.
We also had a great opportunity to leverage Vue writeable computed refs
to allow us to manipulate player input during a v-model.