Skip to content

Commit e5ba894

Browse files
authored
Merge branch 'master' into debug-safe-app
2 parents eb26702 + 6bbdfa5 commit e5ba894

16 files changed

+668
-65
lines changed
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# How do I test the Server?
2+
3+
Testing your Server code in a SAFE app is just the same as in any other dotnet app, and you can use the same tools and frameworks that you are familiar with. These include all of the usual suspects such as [NUnit](https://nunit.org/), [XUnit](https://xunit.net/), [FSUnit](https://fsprojects.github.io/FsUnit/), [Expecto](https://github.com/haf/expecto), [FSCheck](https://fscheck.github.io/FsCheck/), [AutoFixture](https://github.com/AutoFixture/AutoFixture) etc.
4+
5+
In this guide we will look at using **Expecto**, as this is included with the standard SAFE template.
6+
7+
## **I'm using the standard template**
8+
9+
### Using the Expecto runner
10+
11+
If you are using the standard template, then there is nothing more you need to do in order to start testing your Server code.
12+
13+
In the tests/Server folder, there is a project named `Server.Tests` with a single script demonstrating how to use Expecto to test the TODO sample.
14+
15+
In order to run the tests, instead of starting your application using
16+
```powershell
17+
dotnet run
18+
```
19+
20+
you should instead use
21+
```powershell
22+
dotnet run RunTests
23+
```
24+
This will execute the tests and print the results into the console window.
25+
26+
<img src="../../../img/expecto-results.png"/>
27+
28+
> This method builds and runs the Client test project too, which can be slow. If you want to run the Server tests alone, you can simply navigate to the tests/Server directory and run the project using `dotnet run`.
29+
30+
### Using dotnet test or the Visual Studio Test runner
31+
32+
If you would like to use dotnet tests from the command line or the test runner that comes with Visual Studio, there are a couple of extra steps to follow.
33+
34+
#### 1. Install the Test Adapters
35+
36+
Run the following commands at the root of your solution:
37+
```powershell
38+
dotnet paket add Microsoft.NET.Test.Sdk -p Server.Tests
39+
```
40+
```powershell
41+
dotnet paket add YoloDev.Expecto.TestSdk -p Server.Tests
42+
```
43+
44+
#### 2. Disable EntryPoint generation
45+
46+
Open your ServerTests.fsproj file and add the following element:
47+
48+
```xml
49+
<PropertyGroup>
50+
<GenerateProgramFile>false</GenerateProgramFile>
51+
</PropertyGroup>
52+
```
53+
54+
#### 3. Discover tests
55+
56+
To allow your tests to be discovered, you will need to decorate them with a `[<Tests>]` attribute.
57+
58+
The provided test would look like this:
59+
```fsharp
60+
[<Tests>]
61+
let server = testList "Server" [
62+
testCase "Adding valid Todo" <| fun _ ->
63+
let storage = Storage()
64+
let validTodo = Todo.create "TODO"
65+
let expectedResult = Ok ()
66+
67+
let result = storage.AddTodo validTodo
68+
69+
Expect.equal result expectedResult "Result should be ok"
70+
Expect.contains (storage.GetTodos()) validTodo "Storage should contain new todo"
71+
]
72+
```
73+
74+
#### 4. Run tests
75+
76+
There are now two ways to run these tests.
77+
78+
From the command line, you can just run
79+
```powershell
80+
dotnet test tests/Server
81+
```
82+
from the root of your solution.
83+
84+
Alternatively, if you are using Visual Studio or VS Mac you can make use of the built-in test explorers.
85+
86+
<img src="../../../img/test-runner.png" style="height: 200px;"/>
87+
88+
## **I'm using the minimal template**
89+
90+
If you are using the minimal template, you will need to first configure a test project as none are included.
91+
92+
#### 1. Add a test project
93+
94+
Create a `.Net` console project called `Server.Tests` in the tests/Server folder.
95+
96+
```powershell
97+
dotnet new console -lang F# -n Server.Tests -o tests/Server
98+
dotnet sln add tests/Server
99+
```
100+
101+
#### 2. Reference the Server project
102+
103+
Reference the Server project from the Server.Tests project:
104+
105+
```powershell
106+
dotnet add tests/Server reference src/Server
107+
```
108+
109+
#### 3. Add Expecto to the Test project
110+
111+
Run the following command:
112+
113+
```powershell
114+
dotnet add tests/Server package Expecto
115+
```
116+
117+
#### 4. Add something to test
118+
119+
Update the Server.fs file in the Server project to extract the message logic from the router like so:
120+
```fsharp
121+
let getMessage () = "Hello from SAFE!"
122+
123+
let webApp =
124+
router {
125+
get Route.hello (getMessage () |> json )
126+
}
127+
```
128+
129+
#### 5. Add a test
130+
131+
Replace the contents of `tests/Server/Program.fs` with the following:
132+
133+
``` fsharp
134+
open Expecto
135+
136+
let server = testList "Server" [
137+
testCase "Message returned correctly" <| fun _ ->
138+
let expectedResult = "Hello from SAFE!"
139+
let result = Server.getMessage()
140+
Expect.equal result expectedResult "Result should be ok"
141+
]
142+
143+
[<EntryPoint>]
144+
let main _ = runTestsWithCLIArgs [] [||] server
145+
```
146+
147+
#### 6. Run the test
148+
149+
```powershell
150+
dotnet run -p tests/Server
151+
```
152+
153+
This will print out the results in the console window
154+
155+
<img src="../../../img/expecto-results.png"/>
156+
157+
#### 7. Using dotnet test or the Visual Studio Test Explorer
158+
159+
Add the libraries `Microsoft.NET.Test.Sdk` and `YoloDev.Expecto.TestSdk` to your Test project, using NuGet.
160+
161+
162+
> The way you do this will depend on whether you are using NuGet directly or via Paket. See [this recipe](../package-management/add-nuget-package-to-server.md) for more details.
163+
164+
You can now add `[<Test>]` attributes to your tests so that they can be discovered, and then run them using the dotnet tooling in the same way as explained earlier for the standard template.
Lines changed: 61 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,9 @@
1-
To use a third-party React library in a SAFE application, you need to write an F# wrapper around it. There are two ways for doing this - using [Fable.React](https://www.nuget.org/packages/Fable.React/) or using [Feliz](https://zaid-ajaj.github.io/Feliz/).
1+
To use a third-party React library in a SAFE application, you need to write an F# wrapper around it. There are two ways for doing this - using [Feliz](https://zaid-ajaj.github.io/Feliz/) or using [Fable.React](https://www.nuget.org/packages/Fable.React/).
2+
23
## Prerequisites
34

45
This recipe uses the [react-d3-speedometer NPM package](https://www.npmjs.com/package/react-d3-speedometer) for demonstration purposes. [Add it to your Client](../package-management/add-npm-package-to-client.md) before continuing.
56

6-
## Fable.React - Setup
7-
8-
#### 1. Create a new file
9-
10-
Create an empty file named `ReactSpeedometer.fs` in the Client project above `Index.fs` and insert the following statements at the beginning of the file.
11-
12-
```fsharp
13-
module ReactSpeedometer
14-
15-
open Fable.Core
16-
open Fable.Core.JsInterop
17-
open Fable.React
18-
```
19-
20-
#### 2. Define the Props
21-
Prop represents the props of the React component. In this recipe, we're using [the props listed here](https://www.npmjs.com/package/react-d3-speedometer) for `react-d3-speedometer`. We model them in Fable.React using a discriminated union.
22-
23-
```fsharp
24-
type Prop =
25-
| Value of int
26-
| MinValue of int
27-
| MaxValue of int
28-
| StartColor of string
29-
```
30-
31-
> One difference to note is that we use **P**ascalCase rather than **c**amelCase.
32-
>
33-
> Note that we can model any props here, both simple values and "event handler"-style ones.
34-
35-
#### 3. Write the Component
36-
Add the following function to the file. Note that the last argument passed into the `ofImport` function is a list of `ReactElements` to be used as children of the react component. In this case, we are passing an empty list since the component doesn't have children.
37-
38-
```fsharp
39-
let reactSpeedometer (props : Prop list) : ReactElement =
40-
let propsObject = keyValueList CaseRules.LowerFirst props // converts Props to JS object
41-
ofImport "default" "react-d3-speedometer" propsObject [] // import the default function/object from react-d3-speedometer
42-
```
43-
44-
#### 4. Use the Component
45-
With all these in place, you can use the React element in your client like so:
46-
47-
```fsharp
48-
open ReactSpeedometer
49-
50-
reactSpeedometer [
51-
Prop.Value 10 // Since Value is already decalred in HTMLAttr you can use Prop.Value to tell the F# compiler its of type Prop and not HTMLAttr
52-
MaxValue 100
53-
MinValue 0
54-
StartColor "red"
55-
]
56-
```
57-
587
## Feliz - Setup
598

609
If you don't already have [Feliz](https://www.nuget.org/packages/Feliz/) installed, [add it to your client](../ui/add-feliz.md).
@@ -65,6 +14,7 @@ open Fable.Core.JsInterop
6514
```
6615

6716
Within the view function
17+
6818
```fsharp
6919
Feliz.Interop.reactApi.createElement (importDefault "react-d3-speedometer", createObj [
7020
"minValue" ==> 0
@@ -75,15 +25,19 @@ Feliz.Interop.reactApi.createElement (importDefault "react-d3-speedometer", crea
7525

7626
- `createElement` from `Feliz.ReactApi.IReactApi` takes the component you're wrapping react-d3-speedometer, the props that component takes and creates a ReactComponent we can use in F#.
7727
- `importDefault` from ` Fable.Core.JsInterop` is giving us access to the component and is equivalent to
28+
7829
```javascript
7930
import ReactSpeedometer from "react-d3-speedometer"
8031
```
32+
8133
The reason for using `importDefault` is the documentation for the component uses a default export "ReactSpeedometer". Please find a list of common import statetments at the end of this recipe
8234

8335
As a quick check to ensure that the library is being imported and we have no typos you can `console.log` the following at the top within the view function
36+
8437
```fsharp
8538
Browser.Dom.console.log("REACT-D3-IMPORT", importDefault "react-d3-speedometer")
8639
```
40+
8741
In the console window (which can be reached by right-clicking and selecting Insepct Element) you should see some output from the above log.
8842
If nothing is being seen you may need a slightly different import statement, [please refer to this recipe](../../v4-recipes/javascript/import-js-module.md).
8943

@@ -96,7 +50,9 @@ createObj [
9650
"maxValue" ==> 10
9751
]
9852
```
53+
9954
Is equivalent to
55+
10056
```javascript
10157
{ minValue: 0, maxValue: 10 }
10258
```
@@ -108,5 +64,57 @@ That's the bare minimum needed to get going!
10864
Once your component is working you may want to extract out the logic so that it can be used in multiple pages of your app.
10965
For a full detailed tutorial head over to [this blog post](https://www.compositional-it.com/news-blog/f-wrappers-for-react-components/)!
11066

67+
## Fable.React - Setup
68+
69+
### 1. Create a new file
70+
71+
Create an empty file named `ReactSpeedometer.fs` in the Client project above `Index.fs` and insert the following statements at the beginning of the file.
72+
73+
```fsharp
74+
module ReactSpeedometer
75+
76+
open Fable.Core
77+
open Fable.Core.JsInterop
78+
open Fable.React
79+
```
80+
81+
### 2. Define the Props
11182

83+
Prop represents the props of the React component. In this recipe, we're using [the props listed here](https://www.npmjs.com/package/react-d3-speedometer) for `react-d3-speedometer`. We model them in Fable.React using a discriminated union.
84+
85+
```fsharp
86+
type Prop =
87+
| Value of int
88+
| MinValue of int
89+
| MaxValue of int
90+
| StartColor of string
91+
```
11292

93+
> One difference to note is that we use **P**ascalCase rather than **c**amelCase.
94+
>
95+
> Note that we can model any props here, both simple values and "event handler"-style ones.
96+
97+
### 3. Write the Component
98+
99+
Add the following function to the file. Note that the last argument passed into the `ofImport` function is a list of `ReactElements` to be used as children of the react component. In this case, we are passing an empty list since the component doesn't have children.
100+
101+
```fsharp
102+
let reactSpeedometer (props : Prop list) : ReactElement =
103+
let propsObject = keyValueList CaseRules.LowerFirst props // converts Props to JS object
104+
ofImport "default" "react-d3-speedometer" propsObject [] // import the default function/object from react-d3-speedometer
105+
```
106+
107+
### 4. Use the Component
108+
109+
With all these in place, you can use the React element in your client like so:
110+
111+
```fsharp
112+
open ReactSpeedometer
113+
114+
reactSpeedometer [
115+
Prop.Value 10 // Since Value is already decalred in HTMLAttr you can use Prop.Value to tell the F# compiler its of type Prop and not HTMLAttr
116+
MaxValue 100
117+
MinValue 0
118+
StartColor "red"
119+
]
120+
```

docs/recipes/ui/add-bulma.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# How do I add Bulma to a SAFE project?
2+
3+
[Bulma](https://bulma.io/documentation/) is a free open-source UI framework based on flexbox that helps you create modern and responsive layouts.
4+
5+
When using Feliz (the standard for a SAFE app), follow the instructions below. When using Fable.React, use the [Fulma](https://fulma.github.io/Fulma/) wrapper for Bulma.
6+
7+
## 1. Add the Feliz.Bulma NuGet package to the client project
8+
9+
```
10+
dotnet paket add Feliz.Bulma -p Client
11+
```
12+
13+
## 2. Add the Bulma stylesheet to `index.html`
14+
15+
```.diff
16+
...
17+
<head>
18+
...
19+
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
20+
</head>
21+
...
22+
```
23+
24+
## 3. Start using Feliz.Bulma components in your F# files.
25+
26+
```fsharp
27+
open Feliz.Bulma
28+
29+
Bulma.button.button [
30+
str "Click me!"
31+
]
32+
```

docs/recipes/ui/remove-tailwind.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
By default, a full SAFE-stack application uses Tailwind CSS for styling. You might not always want to manage your styling using Tailwind, for example because you want to use a CSS framework like [Bulma](https://bulma.io/). In this recipe we describe how to fully remove Tailwind
2+
3+
## 1. Remove Tailwind css classes
4+
5+
Tailwind uses classes to style UI elements. In `src/Client`, search for all occurrences of `prop.className` and `prop.classes` and remove them if they are used for Tailwind support. In a vanilla SAFE template installation, this means removing **all** occurrences of `prop.className`.
6+
7+
8+
## 2. Uninstall NPM packages
9+
10+
Remove NPM packages that were installed for Tailwind using
11+
12+
```
13+
npm uninstall tailwindcss postcss autoprefixer
14+
```
15+
16+
## 3. Remove configuration files
17+
18+
Remove the following files:
19+
20+
```
21+
src/Client/postcss.config.js
22+
src/Client/tailwind.config.js
23+
src/Client/index.css
24+
```
25+
26+
Your SAFE Stack app is now Tailwind-free.

0 commit comments

Comments
 (0)