Test Driven Development with Svelte - Querying Elements
Tags: svelte, tdd, jest, testinglibrary
Published: 2021-08-31 13:43:10

In the [previous one](https://dev.to/basarbk/test-driven-development-with-svelte-setup-32mg) we have installed the necessary dependencies for testing svelte app. Now in this one we are going to see how we can query the elements how we can interact with them.

## Building the Registration Form
If the test is not running already, open console and run `npm test`
Lets start by creating a new svelte component and corresponding test module.
Let's add just the SignUpPage.svelte file. Leave it empty for now. And lets create SignUpPage.spec.js file.
You will see a fail message in console for SignUpPage.spec, because we don't have a test in it yet. Lets start with our first test

and since this file is just created lets start with the imports

```js
// SignUpPage.spec.js
import SignUpPage from './SignUpPage.svelte';
import { render, screen } from '@testing-library/svelte';
import '@testing-library/jest-dom';
```
now lets add our first test. In this page we will have form form sign up and lets make sure we have a header about it.

```js
// SignUpPage.spec.js
it('has Sign Up header', () => {
    render(SignUpPage);
    const header = screen.getByRole('heading', { name: 'Sign Up'});
    expect(header).toBeInTheDocument();
})
```
We are using two functions of testing-library. First we are rendering the component. The test runner, Jest is using [jsdom](https://github.com/jsdom/jsdom). And it is providing us a browser-like environment. And the testing library is rendering the component into that document. And then we run queries coming with `screen` to get the elements we are looking for in that document.
To get a better view about all available query functions, you can check the [testing-library documentation](https://testing-library.com/docs/queries/about#types-of-queries)
_from docs_
> Queries are the methods that Testing Library gives you to find elements on the page. There are several types of queries ("get", "find", "query"); the difference between them is whether the query will throw an error if no element is found or if it will return a Promise and retry.

So in this one we are using **getByRole** query. With this query, we are able to get the elements based on their [accessibility roles](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques#roles).

We are looking for an element with `heading` role in this page. This means we will have h1-h6 elements or another html element having this role set for it.

After saving this test module, jest will run tests and in console you will see fail message.
```console
TestingLibraryElementError: Unable to find an accessible
element with the role "heading" and name "Sign Up"
```
Now we can fix it by adding header to our SignUpPage.svelte
```html
<!-- SignUpPage.svelte -->
<h1>Sign Up</h1>
```
After adding this one, test will pass.

Now lets add form inputs. User will be entering the sign up data and we need to have couple of inputs for it. Lets start with username input.
We will have multiple input elements and to tell which input is for which field, we need to show some text about it to user. We can use placeholder text for the inputs or we can associate labels with them. Testing-library is providing both query types. Lets go with placeholder first.

```js
// SignUpPage.spec.js
it('has input for username', () => {
    render(SignUpPage);
    const input = screen.getByPlaceholderText('Username');
    expect(input).toBeInTheDocument();
})
```
To get the inputs based on their placeholder, we user **getByPlacholderText** function.

Lets fix this by adding this input

```html
<!-- SignUpPage.svelte -->
<h1>Sign Up</h1>
<!-- Right after this h1 lets add input and
lets set the placeholder for it -->
<input placeholder="Username" />
```
This is fixing the test.
If you would like to use Label, instead of placeholder, then the test would be something like this.
```js
// SignUpPage.spec.js
it('has input for username', () => {
    render(SignUpPage);
    // just replace getByPlaceholderText with getByLabelText
    const input = screen.getByLabelText('Username');
    expect(input).toBeInTheDocument();
})
```
and the corresponding implementation would be like this
```html
<!-- SignUpPage.svelte -->
<label for="username">Username</label>
<input id="username" />
```
We add a label having Username text and associate with the input element.
After this change test will pass.

Just like this one add inputs for email and password. They are basically copy of the username input test
```js
<!-- SignUpPage.spec.js -->
it('has input for email', () => {
    render(SignUpPage);
    const input = screen.getByLabelText('E-mail');
    expect(input).toBeInTheDocument();
})
it('has input for password', () => {
    render(SignUpPage);
    const input = screen.getByLabelText('Password');
    expect(input).toBeInTheDocument();
})
```
and corresponding inputs are here
```html
<!-- SignUpPage.svelte -->
<h1>Sign Up</h1>
<label for="username">Username</label>
<input id="username" />
<!-- -->
<label for="email">E-mail</label>
<input id="email" />
<label for="password">Password</label>
<input id="password" />
```
After these changes the tests will be passing.

To see the progress on browser you can run the application like this.
```console
npm run dev
```
To see the SignUpPage, make sure to update main.js in the project
```js
// main.js
import SignUpPage from './SignUpPage.svelte';

const app = new SignUpPage({
	target: document.body
});

export default app;
```
In password inputs we must be masking the user entries as they type. But currently, if you type anything to password input, all will be in clear text. Lets fix that
```js
// SignUpPage.spec.js
it('has type as password for password input', () => {
    render(SignUpPage);
    const input = screen.getByLabelText('Password');
    expect(input.type).toBe('password');
})
```
Now we need to check an implementation detail for this case. We are looking for input's `type` attribute to have the value of password. 
```html
<!-- SignUpPage.svelte -->
<label for="password">Password</label>
<!-- just setting type for the input -->
<input id="password" type="password"/>
```
Now the tests are passing. And if you check on browser, you will see the password input is masking the user entry.

Since the password is masked now, to make sure user enters the value they intend to, lets add second password input for confirmation.
Just copy and password last two password input tests and update their description and label texts.

```js
// SignUpPage.spec.js
it('has input for password repeat', () => {
    render(SignUpPage);
    const input = screen.getByLabelText('Password Repeat');
    expect(input).toBeInTheDocument();
})
it('has type as password for password repeat input', () => {
    render(SignUpPage);
    const input = screen.getByLabelText('Password Repeat');
    expect(input.type).toBe('password');
})
```
and repeating same step on svelte component too.
```html
<!-- SignUpPage.svelte -->
<label for="password-repeat">Password Repeat</label>
<input id="password-repeat" type="password"/>
```
We are about to complete this form. All we need is just a button to let user send the request. Adding a test for it
```js
// SignUpPage.spec.js
it('has Sign Up button', () => {
    render(SignUpPage);
    const button = screen.getByRole('button', { name: 'Sign Up'});
    expect(button).toBeInTheDocument();
})
```
and lets add button to our page.
```html
<!-- SignUpPage.svelte -->
<button>Sign Up</button>
```
Lets add one final test for this part. Lets make sure the button is initially disabled. Later we are going to enable it after user enters same value to both password and password repeat inputs.
```js
it('disables the button initially', () => {
    render(SignUpPage);
    const button = screen.getByRole('button', { name: 'Sign Up'});
    // for the matcher functions we are using
    // '@testing-library/jest-dom' and for this case
    // we can use
    expect(button).toBeDisabled();

    // or we have option to use matcher with 'not' function
    // expect(button).not.toBeEnabled();
})
```
and we fix it by adding `disabled` attribute to button
```html
<button disabled>Sign Up</button>
```

All tests are passing now. We have our form now.

Next, we will implement the interactions..

### Resources
Github repo for this project can be found here
{% github basarbk/dev-svelte-tdd no-readme %}

You can also check this video tutorial for the same form creation for svelte projects {% youtube xdTGiPRubFU %}

And if you would be interested, I have a full course TDD in svelte. [Svelte with Test Driven Development](https://bafoly.com?course=tdd-svelte)

Thanks for reading