Webapps: HTML, CSS, and TypeScript
Please be aware that these notes:
- cover 2 separate class meetings;
- contain supplemental material, like the links below, which may not be covered in class; and
- are meant to be accompanied by the full NYT puzzle example, which contains many comments for your reference.
Today we're going to start learning how to write webapps. In time, you'll have a working front-end web application, which you'll use to query the backend server you're finishing up now.
Quick Links
I'm putting these links at the beginning to make them easier to find, but if this is your first time reading: skip past this section to get to the content these links refer to first!
TypeScript: TypeScript is a programming language. You can write backend programs with it, but it is most commonly associated with web programming. We strongly suggest making use of the TypeScript documentation. In particular, "instanceof narrowing", which we'll sometimes need to use. Notice how different this is from what we're used to in Java. That doesn't make it good or bad, just different. And TypeScript can do some useful things that Java can't easily manage.
OOP in TypeScript: I'm deliberately not using TypeScript's classes in any of my livecode examples. In the same way we'll be covering some OO design ideas, I want to also cover some functional design ideas. You can use whichever style you prefer on the term project. If you want to refer to documentation anyway, the TypeScript docs on classes are available.
React: React is a web framework for JavaScript and TypeScript. We'll use it to make web development a bit easier. You will find the React docs useful. This is especially true for the useEffect
hook (not used in the NYT app) and useState
hooks (which you'll see today). You may need either or both throughout the next few weeks. The useEffect
hook helps if you want to control side effects in your components. Remember that you are not in control of how often a function component is re-evaluated; putting side effects directly in the function is not reliable. Use hooks instead.
Playwright: Playwright is a library for automated testing of web applications. It has a lot of useful features that I hope you'll use. Playwright's documentation, especially its page on locators, will be useful for testing.
Finally, the end of this document says a few words on the keywords await
and async
, which you'll see in the Playwright examples, but you should not need to use await
outside your test files. We'll cover await
and async
more for your next Sprint. For now, just use them in the way we describe for testing.
Logistics
A Word on "Talent"
I'd caution against viewing success in either 0320 or CSCI generally as related to talent, for a couple reasons. We don't talk enough about how talent depends on external factors and experience. Stephen Sondheim (who I imagine knew more about talent than most) said that "everybody is talented, it's just that some people get it developed and some don't." We can often (wrongly) think of "talent" in CSCI as when some skill or concept comes easily. But the development of talent requires time, support, work, etc. And even prodigies need that—e.g., Terrence Tao (who aced the math SATs when he was 8 years old) got a lot of tutoring support growing up, and (crucially) his family was able to provide it.
An Exercise
You'll start the "front end" part of 0320 on Monday. In our context, that means web programming. What's a (work-safe) website that you particularly like, and what's something the website's interface does that you'd like to learn how to do?
Static HTML and CSS Basics
Let's start by looking at a student's website. The site is hosted here. Naturally, I got permission before using one of your fellow student's webpages. The style is rather outdated, but it suffices as a first intro to these concepts.
This website uses three files:
- an HTML file, which defines the content of the page (index.html);
- a CSS File, which defines the styling of the page (styles.css); and
- an image file with a picture of the student (nim.png).
HTML: The Content
Notice that the structure of HTML is treelike. Tags open and close elements in the document. There's some metadata, but largely the document describes visible content and the structure of that content.
If you're used to editing documents via Google Docs or MS Word, you might be wondering where the formatting information comes from. Websites usually separate out content from styling, meaning that the HTML won't say that a certain word should be shown in a particular font, or aligned in a particular way. Instead, any context needed for styling to be done is specified by...
CSS: The Styling
CSS files say how to style elements of a webpage. Because the author of this page gave the uniName
class to "Brown University", this style declaration will apply:
.uniName {
color: brown;
font-family: Trebuchet, sans-serif;
font-size: 18px;
font-weight: normal;
}
The dot before uniName
means that the style is meant to apply to any element on the page with uniName
as its class. This is called a CSS selector; there are lots more that select elements by their id
or other properties.
The result of this separation is that the HTML document can focus on content and context, and leave styling aside. Yes, it's possible to embed your CSS inside the same file, and there are also frameworks that combine the two in a useful way. But the convention we'll follow to start with is to split the two into different files.
Viewing HTML Source
By default, your browser will render the HTML file, rather than showing its raw form. To see the HTML itself, you'll need to view source. Often there's a right-click menu option for this, but if not there's usually a key combination to press:
- In Safari:
Command + Option + U
; - In (Windows) Firefox:
Control + U
; - In (MacOS) Firefox:
Command + U
.
Inspecting Elements
CSS files also have significant influence over how elements are positioned on the page. It can be useful to see where boundaries between div
s and other elements actually are. This is best done in a browser's page-inspection tool. You'll often find this under "Web Developer Tools" or "Dev Tools" or a similarly named menu. Here are some key combinations:
- In Safari:
Command + Option + I
(and click on theElements
tab); - In (Windows) Firefox:
Control + Shift + I
(and click theInspector
tab); - In (MacOS) Firefox:
Command + Option + I
(and click on theInspector
tab).
Notice that when I mouse over the first column in the table, my browser is highlighting the on-page position of that column:
There are better ways of formatting this sort of data than tables. I took this from a webpage written more than a decade ago. However, HTML tables would be a great way to start displaying rows of tabular data on a webpage!
Make sure you're looking at Elements
or Inspector
, not Sources
. Once we start working with pages that change dynamically, Sources
only shows the starting HTML (the source loaded in the file) without updates that are actually displayed.
A Website I Liked
The New York Times website had a puzzle a few years back that I love to use in class. It went something like this:
The Problem
The page is paywalled. Although Brown provides access through your logins, it's kind of a pain to set up under time constraints. So, while you should definitely try the NYT's version if you can, let's build our own. In fact, I've already built one!
This is an example of a webapp with both a front end and a back end. The front-end piece is what you see on the webpage, and all the dynamic functionality is there. The backend (in this app, anyway) is just a database where I keep track of everyone's sequences, and the results the app gave you.
Let's try it out. I didn't implement the "I'm ready to guess" part yet, but once you have a guess, write it down and stop.
Think, then click!
The rule is "any non-decreasing sequence of three numbers." Is that your guess?Perhaps not! The NYT reports that the majority of people who've tried the puzzle made their first guess before ever receiving a false response.
What does that have to do with software engineering? This is an example of confirmation bias; we humans tend to favor examples that meet our expectations. But without first seeing some false
results, how would you really build confidence in your guess? (Maybe any sequence worked!)
By the way, this shows an example of how cognitive bias can impact our testing. It's quite easy to see a lot of true
responses and get complacent...
Accessibility
This site isn't great in terms of accessibility, yet. Notice that whether the pattern matched or didn't is communicated visually by just color, and not with words or other formatting. We'll talk more about that in a future class.
Let's Build A Webapp!
We're going to write a less complex version of that web app today. Crucially, it will have no back-end code, and can just be run in a browser, via a local webserver. The gearup will cover more of this setup.
JavaScript and TypeScript: Dynamic Behavior
Neither HTML nor CSS alone suffice to build a good web application. We need a way to add dynamic behavior to the page, and to do that, we probably want a programming language.
There are dozens of options (including Java) but one tends to be far more popular than others: JavaScript. In fact, many popular web programming languages actually compile to JavaScript---which means browser developers can focus on optimizing JavaScript performance in their engine, confident in a broad impact. (If you took CS 0190 or CS 0111, the language you used the most---Pyret---compiles to JavaScript.)
TypeScript: JavaScript with a Seatbelt
We'll be using TypeScript in 0320. TypeScript is (essentially) JavaScript with types, and as you get experience you'll very quickly see why those types are a good thing. It's quite easy to make mistakes involving types in JavaScript; just try entering an expression like ' ' == 0
in the browser console.
But browsers don't understand TypeScript; they generally have highly optimized engines for running JavaScript. TypeScript code is compiled to JavaScript by the TypeScript compiler. That's our job to run as the web developer, not the client's job. Websites need to load quickly, and so we need the pre-compiled JavaScript code ready to go when a client requests the webpage.
Framing the Web App We Want
I've put a draft of the puzzle in the live code repository here.
You can find the HTML here. There are a couple of new tags in the HTML, but they're just more semantic grouping tags, like section
etc. We'll focus on the code today.
One thing is worth noting: the CSS has only two declarations. These correspond to the formatting cues assigned to correct and incorrect sequences in the history:
.correct-try {
background-color: green;
}
.incorrect-try {
background-color: red;
}
We could make the page a lot better-looking by doing more with CSS styling. But this is meant to be an example focused on the dynamic behavior only.
Adding Dynamic Behavior
Let's start by writing a function that recognizes sequences matching our rule.
function pattern(guess: string[]): boolean {
if(guess.length !== 3) return false;
if(guess[0].length < 1 ||
guess[1].length < 1 ||
guess[2].length < 1) return false;
if(parseInt(guess[0]) >= parseInt(guess[1])) return false;
if(parseInt(guess[1]) >= parseInt(guess[2])) return false;
return true;
}
What Do You Notice?
What do you notice about this TypeScript syntax, or about the style of coding in the function?
Think, then click!
You might notice:
- functions doesn't have to be methods in a class, like they would need to be in Java;
- the function has type annotations on its parameters and return value, like methods in Java;
- the function uses a strange
!==
operator; - ...
Unit Testing TypeScript
This doesn't get called anywhere yet, but it's something we can unit test. To do that, we'll export it for use in other modules:
export {pattern};
How do we write unit tests for TypeScript? Very similarly to how we write unit tests for Java. I'll make a new file called pattern.test.ts
to hold tests for the pattern checking code:
import { test, expect } from '@playwright/test';
import { pattern } from '../src/pattern'
test('pattern false if all empty', () => {
const input = ['','','']
const output = pattern(input)
expect(output).toBe(false);
});
test('pattern true for 1,2,3', () => {
const input = ['1','2','3']
const output = pattern(input)
expect(output).toBe(true);
});
The test
construct defines a test case, which takes the form of an anonymous function (also called an "arrow function" or a "lambda"). The syntax means a function that takes no arguments (() =>
) whose body defines two constants and runs an assertion.
We'd like to write a lot more of these. For now, we'll just run the test suite with this single test in it. (If you haven't yet, you'll first need to run npm install
to download all the dependencies for this application. You might get some deprecation warnings; ignore those for today.) Then run: npm test
. This will run our tests via the testing library we're using, which is called Playwright.
Playwright allows a lot more than what we've seen so far. It's built for front-end testing, so it makes a lot of things easy that JUnit doesn't.
Depending on how you install, you might need to run npx playwright install
when you first run tests. This is normal; just run the command. Where npm
manages your TypeScript and JavaScript packages, npx
runs programs within packages. So npx playwright install
runs an install
command defined by Playwright. (It downloads various browser emulators.)
Why TypeScript, not JavaScript?
Since TypeScript compiles to JavaScript, and our browser understands JavaScript, let's set the types aside for now and just think about programming for the web. This is a good time to talk about how to learn a new programming language productively.
Yeah, it can be useful to read books and articles and ask questions on Ed and check StackOverflow and so on. But there's a sort of deeper checklist I like to follow when learning a new language, and as we follow it for JavaScript together we'll discover a few of the nastier surprise differences.
I like to identify language features that I rely on, and experiment with them. Coming up with these facets isn't always easy, which is why we're doing it together. For instance, let's check out equality, a deceptively simple yet subtle idea that many languages differ on. I'll use Safari's JS console (Command-Option-C):
> 15 == "15"
true
> 15 == true
false
> 1 == true
true
0 == false
true
Already we've learned a great deal. JavaScript's ==
operator performs type conversion before comparing values. It allows us to pretend that the number 1
is "true" and that the number 0
is "false", but other numbers aren't equivalent to either.
Those of you who have programmed in Racket before: notice this is different from Racket! In Racket, every non-false value (Racket calls false #f
) is considered true. The same is true in many other languages.
JavaScript has a second equality operator, ===
, that checks for equality without type conversion. So:
> 15 === "15"
false
Java also has two different types of equality (==
and the .equals
method), but there the difference has nothing to do with implicit type conversion, but with references vs. structural equality. JavaScript's implicit conversion adds an extra layer of complexity.
Arithmetic
Let's try a few arithmetic operators that are often overloaded across different languages.
> '1' + 1
'11'
> 1 + '1'
'11'
> 1 - '1'
0
> '1' - 1
0
JavaScript tries to "do the reasonable thing" whenever possible. When in doubt, use explicit conversion functions (e.g., parseInt
).
In fact, let's try a couple more (Credit for these to Gary Bernhardt from 2012---it's 4 minutes long; watch it.) For context, []
denotes an array or list in JavaScript and {}
denotes an object.
> [] + []
""
> {} + {}
NaN
> [] + {}
[object Object]
> {} + []
0
"Addition" isn't even commutative in JavaScript, because addition isn't always addition.
Do you see why TypeScript is helpful, now? JavaScript lets you throw off the constraints of the type system, but those constraints are like a safety belt on a roller coaster.
Supplemental: What's Really Happening?
The details involve how JavaScript is implicitly converting between different types. Before applying +
, it converts to a "primitive" type, which for an object is a string. An empty array is converted to the empty string. And so on. The details would consume a full class, or more! Just beware, and embrace types.
Objects
While we won't be using TypeScript classes, we can't avoid using objects. Objects are collections of fields; there's no sharp distinction between fields that are methods and fields that are data. If you've used Python before, these may remind you of dictionaries. For example:
cat = {talk: function() { console.log('meow'); }}
cat.talk()
Takeaway: In JavaScript, functions are values.
Aside: Blank Space
I'm including this because it is a strange thing about TypeScript/JavaScript and it's caused confusion before. Let's ask: how does JavaScript handle blank space? How about the ultimate blank space: new lines?
five = function() { return
5; }
JavaScript automatically inserts semicolons where it believes they are needed. It turns out this often makes sense, but can be confusing. I found this great StackOverflow thread that explains the policy in detail.
The language is powerful, and many of its quirks actually make perfect sense when writing web UIs. But still, beware, and treat your JavaScript programs like a science project: if you've got weird behavior, experiment.
Adding Behavior on the Page
We should add some behavior on the webpage. There are two ways we could go about this.
- Option 1: Write the application in plain ("vanilla") TypeScript. This would have the advantage of showing how web programming works at a low level (in short: callback functions and events: lots of overlap with the strategy pattern!) But this would require us to manage program state and update the state of the webpage all by ourselves, which can be complex.
- Option 2: Set up the application using a framework like React which helps manage state, update the page, and much more.
I like Option 2 for now. So, let's introduce React.
React
React provides two useful features (among others):
- React manages your front-end app's state centrally, and when it detects a state change it propagates that change to a virtual copy of the page. The actual page then only gets updated when it actually needs to be changed. This can improve efficiency of complex apps. To make this work, you usually want to manage all state through React.
- React gives us a nice way to align the visual layout of the app with the program. Concretely, a React component is a TypeScript function that returns a special kind of object that resembles HTML: a JSX expression. In effect, JSX is HTML with holes in it where we can plug in the result of running TypeScript code.
Always keep in mind that a React function component must return a JSX expression. This might be plain HTML, but almost always it's got some JavaScript being evaluated inside squiggle-braces.
We're also using Vite, a development server for React applications. This makes it easier to get started. Notice that we're using a lot of helper libraries! This is normal in much of web development, and we want to get more practice managing this.
React components will either be classes or functions. We don't use "class components". They are outdated, from the early days of React. You'll still see them referenced online, though. The React team strongly suggests that new development use functional components instead, though. We follow their advice, and so should you. Use function components.
What are our components for this application?
After you ignore all of the extraneous content, the NYT puzzle is pretty simple:
- 3 input boxes invite you to enter a trio of numbers.
- Once you've entered numbers, you click a button to check whether those numbers are in the hidden set of sequences.
- The 3 input boxes become read-only and get colored red or green depending on success or failure.
- A new trio of inputs appears.
So, for our graphical components, we probably need:
- input boxes and a submission button; and
- a notion of "attempt": one current attempt, and 0 or more past attempts.
That's enough to get a very rough approximation of the puzzle, which is good enough for me!
I like to draw out a prototype UI, and circle different regions that represent important grouping in the application. E.g.:
A starting template
The complete livecode is available in the repository. I'll call out a few points about the code here.
JSX
React component functions return JSX, rather than standard TypeScript, which is why the file extension is now .tsx
rather than .ts
. React automatically renders whatever these functions return into HTML.
All these components except App
take properties. This is either a single argument, props
, that represents information passed down from parent components, or a collection of variables that do the same.
Running
From the console, I'll run npm run start
. Because I'm using React with Vite, it will give me output like this:
VITE v4.4.9 ready in 1240 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h to show help
If I then go to http://localhost:5173/reactNYT
, I can view the app. (By default, it would be served at localhost:3000
, but I've configured it to add reactNYT
so that I could deploy it where I did, rather than the root of my Github pages page. We won't be doing deployment today.)
Configuration
We've got a file called package.json
and another called tsconfig.json
. These respectively control:
- the project's metadata and dependencies, rather than
pom.xml
did in Java; and - how TypeScript should compile (e.g., which version of JavaScript it should emit, whether it should interoperate with raw JavaScript, etc.).
There's also a very large file, package-lock.json
. This gives the low-level details of how dependencies were resolved, even implicit ones, and helps ensure a consistent build. All three of these should be pushed to Github.
On the other hand, node_modules
is where the dependencies have been downloaded. These are often large, and should not be pushed to Github.
You may notice some other configuration:
vite.config.js
configures Vite (which is how we're running React).jest.config.js
configures Jest (the library we'll use for unit testing in TypeScript).
That's a lot of moving parts! Front-end development tends to have more pieces, but just keep in mind:
- Browsers understand JavaScript;
- TypeScript adds types, and gets compiled to JavaScript;
- React is a framework that makes building applications easier; and
- Vite is a development server meant to make building React apps easier.
Application state
The App
component is the entry point into a create-react-app
program. Let's start by adding a NewRound
component:
function App() {
return (
<div>
<NewRound/>
</div>
);
}
This changes nothing, but it raises the question: how do we get the NewRound
component to do what we want? Our application has some state. What does the state look like?
We'll need at least:
- the state of each text input;
- some record of past guesses; and (if we want to get fancy)
- maybe some text state for showing error messages and so on.
Let's focus on the record of past guesses. What's the right component for that record to be kept in? If we keep the record in one big array, it's the App
component. We don't just want to add a global variable for this, though; we want to enable React to register our updates so it can efficiently flow those updates into the UI. For this, we'll use a hook (see this week's lab for more information), and we'll pass both the value and the setter function to the NewRound
component. We'll also tell the NewRound
component about how to update the notification text
function App() {
const [guesses, setGuesses] = useState([]);
const [notification, setNotification] = useState('');
return (
<div>
<NewRound setGuesses={setGuesses}
setNotification={setNotification} />
{notification}
</div>
);
}
The squiggly braces contain JavaScript; the result of evaluating that JavaScript gets substituted into the JSX. As a result, the NewRound
component will have access to the setter for guesses
, and thus have the ability to update the record.
Having referred to a NewRound component, we probably ought to do something in the corresponding function (which is, at the moment, empty except for a <div>
). We've got to do a few things:
- We need a place for the state of those 3 text inputs to go. We'll use another
useState
hook for this. - We need a place for the inputs to go, and the guess button.
- We need to take in some props---at minimum, a way to change the notification message.
See the completed livecode for details. Much of class will be a code-dive exercise with an opportunity to ask questions. Pay special attention to...
- ...how state is declared, updated, and accessed. Never modify a state variable directly; always use the setter provided by React, and don't expect the setter to execute right away.
- ...how the components refer to each other, forming a nested structure. The structure of the program echoes the graphical structure. If you're ever feeling "lost" in React, draw the picture of how the components should relate to each other.
Try adding a console
write immediately after a state update (here's a snippet modified from the full code below):
return (
<input value={props.value} onChange={(ev) => {
props.setValue(ev.target.value);
console.log(props.value);
}
}></input>);
}
The console.log
will print the old value, because React hasn't yet had a chance to run the update. In general, don't expect React state updates to take effect until after the currently running code has ended. (We'll talk more about this in preparation for your next sprint.)
More Testing in React
We'll be using and requiring Playwright for Mock and future sprints. You're strongly encouraged to use it on your term projects as well, since that's what we can best support. Playwright is a great library for scripting front-end tests. There's a guiding principle (quoted from a different testing library) that we'll follow for front-end testing:
The more your tests resemble the way your software is used, the more confidence they can give you.
Broadly, we're going to focus on a more heavy-weight kind of testing on the front end that resembles the integration tests you wrote for Server. We'll call this end-to-end testing, because it can potentially test the entire application. You might think of it as a kind of user-focused system testing.
Contrasting vs. Unit Testing
Unit testing still has a (major) place in our testing lives. It's still useful to test narrow units of code. But why do we unit test? It isn't because of some crude rule like "we should test every line!" but rather because it's important to have confidence about interface boundaries in your application. These boundaries exist at many different levels:
- individual public helper functions used throughout an application that developers (often you) invoke elsewhere, relying on their specific behavior when doing so;
- the behavior of frontend-backend communication (like your API server in Sprint 2) and other connections between large components that developers (often not you) rely on;
- the behavior of actual user interface(s) that end users rely on;
- ...
It's always about the behavior!
My view is that testing should always be rooted in the requirements that specific kinds of users have---whether they're developers or end-users. Hence the way we've framed the user stories in your sprints to reflect the needs of both.
Kent Dodds writes more about this philosophy in the context of a different testing library here. If you read the post, you'll see that he also frames testing in terms of both developers and end-users. Dodds also writes that if a test suite is brittle to low-level changes, it is a timesink to maintain.
If that doesn't seem counter-intuitive, give it more thought. Tests specify behavior at whatever level they operate. And isn't it a good thing whenever we know what our software should do? If so, it must also be a good thing for our software to be completely specified; you'd know exactly what the software does (or, even better, exactly what you should be implementing). No room for ambiguity, no chance of missing any rubric points or making any users unhappy.
That's good, right? ...Right?
Maybe. But there's a price to pay in managing that specification and the effects of change. The more completely the tests specify your application, the less you'll be able to change without breaking tests.
Using Playwright to Test
You can find some example uses of Playwright in the reactNYT
livecode repository. In particular, look at the app.spec.tsx
file. Here's an example:
test('renders guess input fields', async ({ page }) => {
await page.goto(url);
// Leverage accessibility tags we ought to be providing anyway
const guess0 = page.getByRole("textbox", {name: TEXT_number_1_accessible_name})
const guess1 = page.getByRole("textbox", {name: TEXT_number_2_accessible_name})
const guess2 = page.getByRole("textbox", {name: TEXT_number_3_accessible_name})
await expect(guess0).toBeVisible()
await expect(guess1).toBeVisible()
await expect(guess2).toBeVisible()
});
First, the test calls page.goto
to load the page. This works because of how Playwright is configured in the project: when run, Playwright will automatically start up the development server for the project.
Then, the test creates locators for textbox elements with specific labels (in this case, accessibility metadata). Rather than hard-coding the specific string, the module imports an identifier from the app itself in the constants.ts
file:
export const TEXT_number_1_accessible_name = 'first number in sequence'
The advantage of this approach is that, assuming that the application also uses TEXT_number_1_accessible_name
for the accessible label of the input boxes, simple changes to their text won't break our tests.
Notice how we're identifying the three "guess" input boxes based on their accessible role and accessible name. This is less brittle than just getting all elements with that role; we don't need to filter out old-round information, or worry about ordering. Instead, NewRound.tsx
sets up the NewRound
component so that these text boxes all have distinct accessible names.
You'll see this a lot in modern front-end testing. Rather than requesting elements from document
or some other HTML node, we'll use library support for accessibility metadata. Using Playwright it's easy to enter values into the input boxes:
await guess0.fill('100');
await guess1.fill('200');
await guess2.fill('300');
and even script clicking the button, after we find it by its accessibility data:
await submitButton.click();
What's up with await
?
We'll talk more about await
soon. For now, just know it's a way to tell TypeScript to stop here until a value is generated.
You shouldn't need to use await
in your actual application. But it's very important when testing with Playwright, because some actions take time. Loading the page, for example, or clicking a button. If you don't await
those actions, the script will continue to the next line before the page is ready.
If you want to skip ahead to what is actually going on, unhide this text (and remember that you shouldn't need to use this knowledge, yet, except in Playwright tests):
What's really going on? (Optional Information)
In TypeScript, a Promise
is a generic type that represents an eventual value (or error). The await
keyword pauses execution until a Promise
resolves, but only in certain contexts.
For more information, see these docs on Promise
s and these docs on async
and await
.
If you find yourself tempted to use await
within your code (not your tests), ask a staff member first.
We are deliberately delaying this topic to avoid adding more conceptual overhead to the Mock sprint. For now, please await more information; we promise to provide it.
Why aren't you adding types in these test files?
While you must fill in types in your application files, we're not requiring you to fill in explicit type values within your test files. These files are TypeScript; we're just letting TypeScript infer types on its own.