Skip to content

Commit

Permalink
First test
Browse files Browse the repository at this point in the history
  • Loading branch information
allevo committed Nov 22, 2024
1 parent 891db6a commit 3e589b2
Show file tree
Hide file tree
Showing 13 changed files with 113 additions and 32 deletions.
6 changes: 6 additions & 0 deletions packages/components/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

.turbo
node_modules
npm-debug.log
coverage
storybook-static
2 changes: 1 addition & 1 deletion packages/components/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@seqflow/components",
"version": "0.0.1-beta.1",
"version": "1.0.0-alpha.1",
"description": "",
"main": "dist/index.js",
"types": "dist/src/index.d.ts",
Expand Down
5 changes: 5 additions & 0 deletions packages/create-seqflow/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

.turbo
node_modules
npm-debug.log
coverage
9 changes: 7 additions & 2 deletions packages/create-seqflow/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ Create a new SeqFlow app with a single command.

## Usage

```tsx
pnpx create-seqflow
```
pnpm create seqflow@latest --template empty
```

Run to know more about the options available:
```
pnpm create seqflow@latest --help
```
12 changes: 9 additions & 3 deletions packages/create-seqflow/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@seqflow/create-seqflow",
"version": "1.0.0",
"name": "create-seqflow",
"version": "1.0.0-alpha.3",
"description": "SeqFlow CLI",
"main": "dist/index.js",
"type": "module",
Expand All @@ -10,6 +10,13 @@
"files": [
"dist"
],
"homepage": "https://seqflow.dev",
"repository": "https://github.com/allevo/seqflow-js",
"bugs": "https://github.com/allevo/seqflow-js/issues",
"author": "Tommaso Allevi <[email protected]>",
"engines": {
"node": ">=20.11.1"
},
"scripts": {
"test": "npm-run-all biome build",
"biome": "biome ci ./src",
Expand All @@ -21,7 +28,6 @@
"seqflow",
"cli"
],
"author": "",
"license": "MIT",
"devDependencies": {
"@biomejs/biome": "1.5.3",
Expand Down
3 changes: 2 additions & 1 deletion packages/create-seqflow/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,14 @@ function parseArguments(args: string[]): Partial<Configuration> {
configurationFromCMD.branch = args[i + 1];
i += 2;
} else if (arg === "--help" || arg === "-h") {
const availableTemplates = templateChoices.map((t) => t.title).join(", ");
console.log(`
Usage: create-seqflow [options]
Options:
--path, -p The destination path of the project
--name, -n The name of the project
--template, -t The template to use
--template, -t The template to use (${availableTemplates})
--branch, -b The branch to use
--help, -h Display this help message
`);
Expand Down
5 changes: 5 additions & 0 deletions packages/seqflow/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

.turbo
node_modules
npm-debug.log
coverage
2 changes: 1 addition & 1 deletion packages/seqflow/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@seqflow/seqflow",
"description": "SeqFlow: A lightweight, domain-driven front-end framework designed to simplify web application development, reduce complexity, and enhance user experience with an event-driven architecture.",
"version": "0.0.1-beta.19",
"version": "1.0.0-alpha.2",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/src/index.d.ts",
Expand Down
19 changes: 11 additions & 8 deletions packages/website/src/pages/GetStarted_1fetchData.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@

The quote application is a simple application that fetches a random quote from an endpoint and shows it in the browser. We will use [this endpoint](https://quotes.seqflow.dev) for fetching the quote.
The quote application is a simple application that fetches a random quote from an endpoint and shows it in the browser. We will use this remote servive provided by SeqFlow to fetch quotes: [https://quotes.seqflow.dev](https://quotes.seqflow.dev).

## Invoking an API

What we want to do is to fetch a random quote from the endpoint `https://quotes.seqflow.dev/api/quotes/random` and show it in the component.
We will use the `fetch` API to do that.

Change the `src/Main.tsx` file content as the following:

```tsx
Expand Down Expand Up @@ -37,21 +40,21 @@ export async function Main({}, { component }: Contexts) {

Let's see what we have.

We defined the `getRandomQuote` function that fetches a random quote from an endpoint. It is an async function that returns a promise with the quote.
We defined the `getRandomQuote` function that fetches a random quote from an endpoint. It is an pure async function that returns a promise with the quote.

The `Main` async component is responsible for invoking the `getRandomQuote` function and showing the quote.
Because the SeqFlow components are async functions, we can just use the `await` keyword to perform any asynchronous operations we want, such HTTP requests. This is a powerful feature that allows you to fetch data from an endpoint and render it in the browser without any state management.
The `Main` component is responsible for invoking the `getRandomQuote` function and showing the quote.
Because the SeqFlow components are async functions, we can just use the `await` keyword to perform any asynchronous operations we want, such HTTP requests. This is a powerful feature that allows us to write a component easier and more readable.

Every SeqFlow component accepts as a second parameter its own context object that exposes some functions to interact with the DOM. In this case we want to render the quote, so we use the `component.renderSync` method to render the quote in the browser. As you can see, SeqFlow supports JSX syntax.

SeqFlow invokes your component once. To update it, you need to call the `component.renderSync` method again or update the component partially with `component.replaceChild`.

Run `pnpm start` and navigate to `http://localhost:5173` to see the quote.
Your page should be updated automatically thanks to the vite server. if you don't run the server yet, you can do it by running `pnpm start` in the terminal and navigate to `http://localhost:5173` to see the quote.

## Handle loading and error states

Even if we thought API requests are always fast and successful, they are not. Let's handle the loading and error states.
Let's do that. Replace the `src/Main.tsx` file content with the following:

Let's put a loading message before fetching the quote and an error message if the fetch operation fails.
Replace the `src/Main.tsx` file content with the following:

```tsx
import { Contexts } from "@seqflow/seqflow";
Expand Down
17 changes: 11 additions & 6 deletions packages/website/src/pages/GetStarted_2splitComponents.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@

Our application is simple, but where is the reusability? Let's split the application into components.
Our application is simple for now.
But typically, applications are more complex and have multiple components.
Let's split the application into different components.

## Create new components

Let's start by creating a new component that will show the quote. Replace the `src/Main.tsx` file content with the following:
Let's start by creating a new component that will show the quote.
We continue to overwrite the `src/Main.tsx` file just to keep things simple to follow.

Replace the `src/Main.tsx` file content with the following:

```tsx
import { Prose } from "@seqflow/components";
Expand Down Expand Up @@ -74,9 +79,9 @@ As you can see, SeqFlow components can be created as sync or async functions tha
- the context object used to interact with the component.

In the above code, we created:
- a new component called `Loading` to show a loading message;
- a new component called `ErrorMessage` to show an error message.
- a new component called `Quote` to show the quote;
- the `Loading` component to show a loading message;
- the `ErrorMessage` component to show an error message.
- the `Quote` component to show the quote;

Finally, we updated the `Main` component accordingly.

Expand All @@ -87,5 +92,5 @@ We split the application into multiple components.
In the next guide, we will learn how to handle user interactions.

:::next:::
{"label": "Learn how to handle events", "next": "/get-started/refresh-quote"}
{"label": "Refresh the quote", "next": "/get-started/refresh-quote"}
:::end-next:::
24 changes: 21 additions & 3 deletions packages/website/src/pages/GetStarted_3refreshQuote.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ Our application works fine, but we force the user to refresh the page to see a n

## Insert a button

Our goal now is to add a button that, when clicked, will refresh the quote.
We will use the `component.replaceChild` method that allows us to replace only a portion of the component.

Let's start by replacing the `src/Main.tsx` file content with the following:

```tsx
Expand Down Expand Up @@ -107,6 +110,10 @@ Anyway, we can improve the above code by avoiding duplicated code. Let's see how

## Create a Spot component

The code duplication is not a good practice. We invoke `getRandomQuote` twice: once when the page is loaded and once when the button is clicked.

What we want is to create a function that can be invoked when the component is mounted the first time and when the button is clicked. This function should shows the loading message, fetches the quote, and renders it.

Let's start by replacing the `src/Main.tsx` file content with the following:

```tsx
Expand Down Expand Up @@ -195,7 +202,11 @@ In the above code, we created a new component called `Spot`. It is empty and tag

## Avoid double fetch

The above code has a subtle bug: if the user clicks the button twice before the first fetch is completed, the application will fetch the quote twice. Let's fix it using the below code:
The above code has a subtle bug: if the user clicks the button twice before the first fetch is completed, the application will fetch the quote twice.

Using the component library provided by SeqFlow team, we can avoid this issue. We can disable the button while the quote is being fetched.

Let's fix it using the below code:

```tsx
import { Button, ButtonComponent, Prose } from "@seqflow/components";
Expand Down Expand Up @@ -288,11 +299,18 @@ export async function Main({}, { component }: Contexts) {
}
```

You can refer to child elements using the `getChild` method. This is useful when you need to access the real HTML element and change its properties. Anyway, Typescript doesn't understand what the real element is. This is why we have to cast the button to `HTMLButtonElement`. With this cast, we can use the `disabled` attribute to disable the button while the quote is being fetched.
`ButtonComponent` is a special component that allows you to change the button properties.
It exposts `transition` method that allows you to change the button properties easily. In the above code, we use it to disable the button while the quote is being fetched and show a loading spinner.

When the quote is fetched, we enable the button again.

NB: Typescript doesn't understand what the real element is. This is why we have to cast the button to `ButtonComponent` (which extends `HTMLButtonElement`).

## Conclusion

In this tutorial, we have learned how to handle click events and avoid double fetches. We have also learned how to use the `key` attribute to track the components. Now, we have a fully functional application that shows a random quote and allows the user to refresh it by clicking a button!
In this tutorial, we have learned how to handle click events and avoid double fetches. We have also learned how to use the `key` attribute to track the components.

Now, we have a fully functional application that shows a random quote and allows the user to refresh it by clicking a button. Well done!

:::next:::
{"label": "Learn how to configure the application", "next": "/get-started/configuration"}
Expand Down
15 changes: 13 additions & 2 deletions packages/website/src/pages/GetStarted_4configuration.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
Our application fully works now. But we can improve it by putting the hard-coded URL in configuration. Let's do that.
Our application fully works now.

Anyway, a good application should have a configuration object to hold the application settings. In this guide, we will learn how to define a configuration object and how to use it in our application.

## Application configuration

We have to define a configuration object that will hold the URL of the quote endpoint. Replace the `src/index.ts` file content with the following:
We have to define:
- the configuration object that will hold the URL of the quote endpoint.
- configure TypeScript to recognize the configuration object.
- update the code to use the configuration object.

Let's start by defining the configuration object and the TypeScript type.

Replace the `src/index.ts` file content with the following:

```ts
import "@seqflow/components/style.css";
Expand Down Expand Up @@ -127,6 +136,8 @@ export async function Main({}, { component, app }: Contexts) {

We changed the `getRandomQuote` function to receive the `baseUrl` as an argument. We used the `app.config` object to access the configuration object.

NB: the tests fail because we didn't update the tests to pass the configuration object. We will cover this in the next guide.

## Conclusion

Even if the configuration part is not commonly covered in tutorials, it's essential to any application. In this guide, we learned how to define a configuration object and how to use it in our application.
Expand Down
26 changes: 21 additions & 5 deletions packages/website/src/pages/GetStarted_5test.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
In this last part of the tutorial, we will cover how to test our SeqFlow application.

With the word "test", we could mean different things. In this tutorial, we will cover format and linsting, and unit testing.

## Format of the application

SeqFlow suggests using `biome` for formatting the code. The configuration file is `biome.json`. The configuration file is already created when the project is created. To format the code, run:
Expand All @@ -16,15 +18,28 @@ pnpm run biome:check

## Unit testing

SeqFlow suggests to use `@testing-library/dom`.
In this tutorial, we use the following libraries for testing:
- `@testing-library/dom` for testing the DOM (already installed).
- `msw` for mocking the API requests.

Because our application involves fetching data from an API, we need to provide a way to mock the API response. We will use `msw` to mock the API requests. Install it by running:
Install `msw` by running:

```bash
pnpm i -D msw
```

The codebase contains the file `tests/index.test.ts` which is the test file for the application. Replace its content with the following:
The codebase contains the file `tests/index.test.ts` which is the test file for the application.

The following test code will:
- configure the mock server to return a quote.
- start the application with the right configuration.
- check if the quote is displayed.
- click the refresh button.
- check if the *new* quote is displayed.
- click the refresh button.
- check if the *first* quote is displayed.

Replace its content with the following:

```tsx
import { screen } from "@testing-library/dom";
Expand Down Expand Up @@ -73,7 +88,7 @@ test("should render the quote and refresh it", async () => {
await screen.findByText(new RegExp(quotes[0].author, "i"));

// When the user clicks the refresh button, the quote should change
const button = await screen.findByText("Refresh quote")
const button = await screen.findByText("Refresh")
button.click();

// And the second quote should be displayed
Expand Down Expand Up @@ -104,7 +119,7 @@ pnpm test

The test should pass.

Broadly speaking, you can run the whole application inside a test environment and interact with it as if it were a real browser. This is possible because SeqFlow consumes less memory and CPU than other frameworks.
Broadly speaking, you can run the whole application inside a test environment and interact with it as if it were a real browser. This is possible because SeqFlow consumes less memory and CPU than other frameworks. You should? Probably not. But it's good to know that you can.

## Conclusion

Expand All @@ -115,6 +130,7 @@ This tutorial taught us how to create a simple application using SeqFlow. We hav
- How to create a new component.
- How to configure the application.
- How to test the application.
- Split the application into domains [In progress](https://github.com/allevo/seqflow-js/issues/9).

Any comments or suggestions are really appreciated. Feel free to open an issue on the [GitHub repository](https://github.com/allevo/seqflow-js/issues).

Expand Down

0 comments on commit 3e589b2

Please sign in to comment.