Expect.Elmish and restructure sample/test build

This commit is contained in:
Alfonso Garcia-Caro
2021-09-24 21:30:58 +09:00
parent 051c10365c
commit 3ef11e9506
19 changed files with 155 additions and 86 deletions

9
.gitignore vendored
View File

@@ -1,9 +1,6 @@
*.fs.js
*/public/*.js
*/public/*.txt
*/public/*.png
out/
bundle.js*
build/sample/*
build/test/*
!build/test/__snapshots__
dist/
.idea/

View File

@@ -17,9 +17,7 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Test", "test\Test.fsproj",
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Lit.Test", "src\Lit.Test\Lit.Test.fsproj", "{1920B7A6-ABE1-49EB-B828-826A3ED20A75}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{ABCF0982-DCE7-493F-A9F2-FE6A6BA2EE22}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "MyApp", "sample\src\MyApp.fsproj", "{F6EB9FBF-2713-42EA-A701-88F2150EB988}"
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Sample", "sample\Sample.fsproj", "{316053DB-99B3-4D64-AB08-9CE563102A9E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -106,18 +104,18 @@ Global
{1920B7A6-ABE1-49EB-B828-826A3ED20A75}.Release|x64.Build.0 = Release|Any CPU
{1920B7A6-ABE1-49EB-B828-826A3ED20A75}.Release|x86.ActiveCfg = Release|Any CPU
{1920B7A6-ABE1-49EB-B828-826A3ED20A75}.Release|x86.Build.0 = Release|Any CPU
{F6EB9FBF-2713-42EA-A701-88F2150EB988}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F6EB9FBF-2713-42EA-A701-88F2150EB988}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F6EB9FBF-2713-42EA-A701-88F2150EB988}.Debug|x64.ActiveCfg = Debug|Any CPU
{F6EB9FBF-2713-42EA-A701-88F2150EB988}.Debug|x64.Build.0 = Debug|Any CPU
{F6EB9FBF-2713-42EA-A701-88F2150EB988}.Debug|x86.ActiveCfg = Debug|Any CPU
{F6EB9FBF-2713-42EA-A701-88F2150EB988}.Debug|x86.Build.0 = Debug|Any CPU
{F6EB9FBF-2713-42EA-A701-88F2150EB988}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F6EB9FBF-2713-42EA-A701-88F2150EB988}.Release|Any CPU.Build.0 = Release|Any CPU
{F6EB9FBF-2713-42EA-A701-88F2150EB988}.Release|x64.ActiveCfg = Release|Any CPU
{F6EB9FBF-2713-42EA-A701-88F2150EB988}.Release|x64.Build.0 = Release|Any CPU
{F6EB9FBF-2713-42EA-A701-88F2150EB988}.Release|x86.ActiveCfg = Release|Any CPU
{F6EB9FBF-2713-42EA-A701-88F2150EB988}.Release|x86.Build.0 = Release|Any CPU
{316053DB-99B3-4D64-AB08-9CE563102A9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{316053DB-99B3-4D64-AB08-9CE563102A9E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{316053DB-99B3-4D64-AB08-9CE563102A9E}.Debug|x64.ActiveCfg = Debug|Any CPU
{316053DB-99B3-4D64-AB08-9CE563102A9E}.Debug|x64.Build.0 = Debug|Any CPU
{316053DB-99B3-4D64-AB08-9CE563102A9E}.Debug|x86.ActiveCfg = Debug|Any CPU
{316053DB-99B3-4D64-AB08-9CE563102A9E}.Debug|x86.Build.0 = Debug|Any CPU
{316053DB-99B3-4D64-AB08-9CE563102A9E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{316053DB-99B3-4D64-AB08-9CE563102A9E}.Release|Any CPU.Build.0 = Release|Any CPU
{316053DB-99B3-4D64-AB08-9CE563102A9E}.Release|x64.ActiveCfg = Release|Any CPU
{316053DB-99B3-4D64-AB08-9CE563102A9E}.Release|x64.Build.0 = Release|Any CPU
{316053DB-99B3-4D64-AB08-9CE563102A9E}.Release|x86.ActiveCfg = Release|Any CPU
{316053DB-99B3-4D64-AB08-9CE563102A9E}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{207E7C81-549C-4CF5-830B-E681E383BDAB} = {02A16E23-0ECB-444D-B10B-245EAF6589DD}
@@ -125,6 +123,5 @@ Global
{0EBD5EA0-373F-4C63-B4D2-8C0681552554} = {02A16E23-0ECB-444D-B10B-245EAF6589DD}
{EAD7C6EA-3264-4AA1-A267-DB83425E86B5} = {02A16E23-0ECB-444D-B10B-245EAF6589DD}
{1920B7A6-ABE1-49EB-B828-826A3ED20A75} = {02A16E23-0ECB-444D-B10B-245EAF6589DD}
{F6EB9FBF-2713-42EA-A701-88F2150EB988} = {ABCF0982-DCE7-493F-A9F2-FE6A6BA2EE22}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,7 @@
/* @web/test-runner snapshot v1 */
export const snapshots = {};
snapshots["counter"] =
`%3Cdiv%3E%0A%20%20%20%20%0A%20%20%20%20%3Cp%3EF%23%20counter%3C%2Fp%3E%0A%20%20%20%20%3Cp%3EValue%3A%205%3C%2Fp%3E%0A%20%20%20%20%3Cbutton%3EIncrement%3C%2Fbutton%3E%0A%20%20%20%20%3Cbutton%3EDecrement%3C%2Fbutton%3E%0A%20%20%20%20%3C%2Fdiv%3E`;
/* end snapshot counter */

View File

@@ -20,7 +20,7 @@
<body>
<div id="app-container"></div>
<script type="module" src="./out/Entry.js"></script>
<script type="module" src="./build/sample/App.js"></script>
</body>
</html>

View File

@@ -3,9 +3,9 @@
"scripts": {
"install": "dotnet tool restore",
"publish": "dotnet fsi build.fsx publish",
"test": "dotnet fable test --run web-test-runner test/**/*Test.fs.js --node-resolve",
"test:watch": "dotnet fable watch test --run web-test-runner test/**/*Test.fs.js --node-resolve --watch",
"start": "cd sample && dotnet fable watch src -o out --runFast vite"
"test": "dotnet fable test -o build/test --run web-test-runner build/test/*Test.js --node-resolve",
"test:watch": "dotnet fable watch test -o build/test --run web-test-runner build/test/*Test.js --node-resolve --watch",
"start": "dotnet fable watch sample -o build/sample --runFast vite --open"
},
"dependencies": {
"lit": "^2.0.0",

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -1,4 +1,4 @@
module MyApp.Main
module Sample.App
open Elmish
open Lit

View File

@@ -5,6 +5,42 @@ open Fable.Core
open Browser.Types
open Elmish
open Lit
module Helpers =
type Time =
| Hour of int
| Minute of int
| Second of int
member this.Value =
match this with
| Hour n -> n
| Second n -> n
| Minute n -> n
member this.ClockPercentage =
(float this.Value) / this.FullRound
member this.StrokeWidth =
match this with
| Hour _ | Minute _ -> 2
| Second _ -> 1
member this.Length =
match this with
| Hour _ -> 25.
| Minute _ -> 35.
| Second _ -> 40.
member this.FullRound =
match this with
| Hour _ -> 12.
| Second _ | Minute _ -> 60.
type System.DateTime with
member this.AsHour = Hour this.Hour
member this.AsMinute = Minute this.Minute
member this.AsSecond = Second this.Second
open Helpers
type Model =

View File

@@ -1,4 +1,4 @@
module MyApp.Components
module Sample.Components
open Browser.Types
open Lit

View File

@@ -3,15 +3,14 @@
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="Types.fs" />
<Compile Include="Helpers.fs" />
<Compile Include="Clock.fs" />
<Compile Include="Types.fs" />
<Compile Include="Components.fs" />
<Compile Include="Entry.fs" />
<Compile Include="App.fs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Lit.React\Lit.React.fsproj" />
<ProjectReference Include="..\..\src\Lit.Elmish\Lit.Elmish.fsproj" />
<ProjectReference Include="..\src\Lit.React\Lit.React.fsproj" />
<ProjectReference Include="..\src\Lit.Elmish\Lit.Elmish.fsproj" />
</ItemGroup>
<ItemGroup>
<!-- <PackageReference Include="Fable.Lit.Elmish" Version="1.0.0-rc-001" /> -->

View File

@@ -1,4 +1,4 @@
namespace MyApp
namespace Sample
type Model =
{ Value: string

View File

@@ -1,35 +0,0 @@
module Helpers
type Time =
| Hour of int
| Minute of int
| Second of int
member this.Value =
match this with
| Hour n -> n
| Second n -> n
| Minute n -> n
member this.ClockPercentage =
(float this.Value) / this.FullRound
member this.StrokeWidth =
match this with
| Hour _ | Minute _ -> 2
| Second _ -> 1
member this.Length =
match this with
| Hour _ -> 25.
| Minute _ -> 35.
| Second _ -> 40.
member this.FullRound =
match this with
| Hour _ -> 12.
| Second _ | Minute _ -> 60.
type System.DateTime with
member this.AsHour = Hour this.Hour
member this.AsMinute = Minute this.Minute
member this.AsSecond = Second this.Second

View File

@@ -0,0 +1,60 @@
module Expect.Elmish
open System
open System.Collections.Generic
open Elmish
open Lit
open Expect.Dom
type Observable<'T>() =
let mutable value: 'T option = None
let listeners = Dictionary<Guid, IObserver<'T>>()
member _.Trigger(v) =
value <- Some v
for l in listeners.Values do
l.OnNext(v)
interface IObservable<'T> with
member _.Subscribe(w) =
value |> Option.iter w.OnNext
let g = Guid.NewGuid()
listeners.Add(g, w)
{ new IDisposable with
member _.Dispose() = listeners.Remove(g) |> ignore }
type LazyDisposable() =
let mutable _disposed = false
let mutable _disposable: IDisposable option = None
member _.Disposable with set(d: IDisposable) =
match _disposed, _disposable with
| false, Some _ -> failwith "Item was already assigned a disposable"
| true, Some _ -> failwith "Item is already disposed"
| false, None -> _disposable <- Some d
| true, None ->
_disposable <- Some d
d.Dispose()
member _.Dispose() =
_disposed <- true
_disposable |> Option.iter (fun d -> d.Dispose())
type IObservable<'T> with
member obs.Await() =
Promise.create(fun resolve _ ->
let disp = LazyDisposable()
disp.Disposable <- obs.Subscribe(fun v ->
disp.Dispose()
resolve v))
module Program =
let runTest (program: Program<unit, 'model, 'msg, Lit.TemplateResult>) = promise {
let obs = Observable<'model>()
let! el = render_html $"<div></div>"
let setState model dispatch =
Program.view program model dispatch |> Lit.render el.El
obs.Trigger model
Program.withSetState setState program
|> Program.run
return el, obs :> IObservable<_>
}

View File

@@ -20,14 +20,15 @@ type JsError(message: string) =
class end
[<AttachMembers>]
type AssertionError<'T>(assertion: string, ?description: string, ?actual: 'T, ?expected: 'T) =
type AssertionError<'T>(assertion: string, ?description: string, ?actual: 'T, ?expected: 'T, ?brief: bool) =
inherit JsError(
let brief = defaultArg brief false
[
"Expected " |> Some
description |> Option.map (fun v -> $"'{v}' ")
actual |> Option.map (fun v -> $"{quote v} ")
if not brief then actual |> Option.map (fun v -> $"{quote v} ")
$"to {assertion} " |> Some
expected |> Option.map (fun v -> $"{quote v} ")
if not brief then expected |> Option.map (fun v -> $"{quote v} ")
] |> List.choose id |> String.concat ""
)
// Test runner requires these properties to be settable, not sure why
@@ -35,8 +36,8 @@ type AssertionError<'T>(assertion: string, ?description: string, ?actual: 'T, ?e
member val expected = expected with get, set
type AssertionError =
static member Throw(assertion: string, ?description, ?actual: 'T, ?expected: 'T) =
AssertionError.Throw(assertion, ?description=description, ?actual=actual, ?expected=expected)
static member Throw(assertion: string, ?description, ?actual: 'T, ?expected: 'T, ?brief: bool) =
AssertionError(assertion, ?description=description, ?actual=actual, ?expected=expected, ?brief=brief) |> throw
// TODO: String and collection assertions
[<RequireQualifiedAccess>]
@@ -81,6 +82,13 @@ module Expect =
if condition actual then
AssertionError.Throw("be false", description=msg)
let find (msg: string) (condition: 'T -> bool) (items: 'T seq) =
items
|> Seq.tryFind condition
|> function
| Some x -> x
| None -> AssertionError.Throw("be found", description=msg)
let error (msg: string) (f: 'T -> 'Result) (actual: 'T) =
try
let _ = f actual

View File

@@ -9,6 +9,7 @@
<ItemGroup>
<Compile Include="Expect.fs" />
<Compile Include="Expect.Dom.fs" />
<Compile Include="Expect.Elmish.fs" />
<Compile Include="WebTestRunner.fs" />
</ItemGroup>
<ItemGroup>

View File

@@ -31,11 +31,11 @@ module Expect =
let private cleanHtml (html: string) =
// Lit inserts comments with different values every time, so remove them
Regex(@"<\!--.*?-->").Replace(html, "").Trim()
Regex(@"<\!--[\s\S]*?-->").Replace(html, "").Trim()
/// Compares the content string with the snapshot of the given name within the current file.
/// If the snapshot doesn't exist or tests are run with `--update-snapshots` option the snapshot will just be saved/updated.
let matchSnapshot (name: string) (content: string) = promise {
let matchSnapshot (description: string) (name: string) (content: string) = promise {
let! config = wtr.getSnapshotConfig()
let! snapshot =
if config.updateSnapshots then Promise.lift null
@@ -47,15 +47,19 @@ module Expect =
else
// Don't use wtr.compareSnapshot because that will update the snapshot
// without encoding the content even with a successful match
return Expect.Expect.equal snapshot content
return
if not(snapshot = content) then
// Snapshots can be large, so use `brief` argument to hide them in the error message
// (Diffing should be displayed correctly)
Expect.AssertionError.Throw("match snapshot", description=description, actual=content, expected=snapshot, brief=true)
}
/// Compares `outerHML` of the element with the snapshot of the given name within the current file.
/// If the snapshot doesn't exist or tests are run with `--update-snapshots` option the snapshot will just be saved/updated.
let matchHtmlSnapshot (name: string) (el: HTMLElement) =
el.outerHTML |> cleanHtml |> matchSnapshot name
el.outerHTML |> cleanHtml |> matchSnapshot "outerHTML" name
/// Compares `shadowRoot.innerHTML` of the element with the snapshot of the given name within the current file.
/// If the snapshot doesn't exist or tests are run with `--update-snapshots` option the snapshot will just be saved/updated.
let matchShadowRootSnapshot (name: string) (el: Element) =
el.shadowRoot.innerHTML |> cleanHtml |> matchSnapshot name
el.shadowRoot.innerHTML |> cleanHtml |> matchSnapshot "shadowRoor" name

View File

@@ -13,6 +13,8 @@ let Counter () =
html
$"""
<div>
<!-- Check snapshot can contain # char. See https://github.com/modernweb-dev/web/issues/1690 -->
<p>F# counter</p>
<p>Value: {value}</p>
<button @click={Ev(fun _ -> value + 1 |> setValue)}>Increment</button>
<button @click={Ev(fun _ -> value - 1 |> setValue)}>Decrement</button>

View File

@@ -1,7 +0,0 @@
/* @web/test-runner snapshot v1 */
export const snapshots = {};
snapshots["counter"] =
`%3Cdiv%3E%0A%20%20%20%20%3Cp%3EValue%3A%205%3C%2Fp%3E%0A%20%20%20%20%3Cbutton%3EIncrement%3C%2Fbutton%3E%0A%20%20%20%20%3Cbutton%3EDecrement%3C%2Fbutton%3E%0A%20%20%20%20%3C%2Fdiv%3E`;
/* end snapshot counter */